Add support for `u64` and `i64` types

a8794ea239ca4cd8f42d96a761749fc9bce596d22b4bf0b96d9b26b58c74f88d
Add full 64-bit integer support to the language. This is a breaking
change that requires the preceding seed update, since the compiler's
own source now uses `u64`/`i64` types internally.
Alexis Sellier committed ago 1 parent 2d07c0bd
lib/std/arch/rv64/emit.rad +37 -11
453 453
454 454
    return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo };
455 455
}
456 456
457 457
/// Load an immediate value into a register.
458 -
/// Uses `LI` pseudo-instruction: `LUI, ADDIW` for large values.
459 -
/// On RV64, `addiw` ensures the result is correctly sign-extended to 64 bits
460 -
/// after a `lui` upper-immediate load.
461 -
pub fn loadImm(e: *mut Emitter, rd: super::Reg, imm: i32) {
462 -
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
463 -
        emit(e, encode::addi(rd, super::ZERO, imm));
464 -
    } else {
465 -
        let s = splitImm(imm);
458 +
/// Handles the full range of 64-bit immediates.
459 +
/// For values fitting in 12 bits, uses a single `ADDI`.
460 +
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
461 +
/// For wider values, loads upper and lower halves then combines with shift and add.
462 +
pub fn loadImm(e: *mut Emitter, rd: super::Reg, imm: i64) {
463 +
    let immMin = super::MIN_IMM as i64;
464 +
    let immMax = super::MAX_IMM as i64;
465 +
466 +
    if imm >= immMin and imm <= immMax {
467 +
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
468 +
        return;
469 +
    }
470 +
    // Check if the value fits in 32 bits (sign-extended).
471 +
    let lo32 = imm as i32;
472 +
    if lo32 as i64 == imm {
473 +
        let s = splitImm(lo32);
466 474
        emit(e, encode::lui(rd, s.hi));
467 -
468 475
        if s.lo != 0 {
469 476
            emit(e, encode::addiw(rd, rd, s.lo));
470 477
        }
478 +
        return;
471 479
    }
480 +
    // Full 64-bit immediate: use only rd, no scratch registers.
481 +
    // Load upper 32 bits first via the 32-bit path (LUI+ADDIW),
482 +
    // then shift and add lower bits in 11-bit groups to avoid
483 +
    // sign-extension issues with ADDI's 12-bit signed immediate.
484 +
    let hi32 = (imm >> 32) as i32;
485 +
    let lower = imm as i32;
486 +
487 +
    // Load upper 32 bits.
488 +
    loadImm(e, rd, hi32 as i64);
489 +
    // Shift left by 11, add bits [31:21].
490 +
    emit(e, encode::slli(rd, rd, 11));
491 +
    emit(e, encode::addi(rd, rd, (lower >> 21) & 0x7FF));
492 +
    // Shift left by 11, add bits [20:10].
493 +
    emit(e, encode::slli(rd, rd, 11));
494 +
    emit(e, encode::addi(rd, rd, (lower >> 10) & 0x7FF));
495 +
    // Shift left by 10, add bits [9:0].
496 +
    emit(e, encode::slli(rd, rd, 10));
497 +
    emit(e, encode::addi(rd, rd, lower & 0x3FF));
472 498
}
473 499
474 500
/// Emit add-immediate, handling large immediates.
475 501
pub fn emitAddImm(e: *mut Emitter, rd: super::Reg, rs: super::Reg, imm: i32) {
476 502
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
477 503
        emit(e, encode::addi(rd, rs, imm));
478 504
    } else {
479 -
        loadImm(e, super::SCRATCH1, imm);
505 +
        loadImm(e, super::SCRATCH1, imm as i64);
480 506
        emit(e, encode::add(rd, rs, super::SCRATCH1));
481 507
    }
482 508
}
483 509
484 510
////////////////////////
570 596
    // Allocate stack frame.
571 597
    let negFrame = 0 - totalSize;
572 598
    if negFrame >= super::MIN_IMM {
573 599
        emit(e, encode::addi(super::SP, super::SP, negFrame));
574 600
    } else {
575 -
        loadImm(e, super::SCRATCH1, totalSize);
601 +
        loadImm(e, super::SCRATCH1, totalSize as i64);
576 602
        emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
577 603
    }
578 604
    // Save return address and frame pointer.
579 605
    emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
580 606
    emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
lib/std/arch/rv64/encode.rad +5 -0
71 71
/// Returns `true` if the value fits in a signed 12-bit immediate.
72 72
pub fn isSmallImm(value: i32) -> bool {
73 73
    return value >= super::MIN_IMM and value <= super::MAX_IMM;
74 74
}
75 75
76 +
/// Returns `true` if a 64-bit value fits in a 12-bit signed immediate.
77 +
pub fn isSmallImm64(value: i64) -> bool {
78 +
    return value >= (super::MIN_IMM as i64) and value <= (super::MAX_IMM as i64);
79 +
}
80 +
76 81
/// Returns `true` if the value is valid for branch immediates.
77 82
/// Branch immediates are 13-bit signed, even aligned.
78 83
pub fn isBranchImm(value: i32) -> bool {
79 84
    return value >= -(1 << 12) and value <= ((1 << 12) - 2) and (value & 1) == 0;
80 85
}
lib/std/arch/rv64/isel.rad +22 -19
162 162
        },
163 163
        case il::Val::DataSym(name) => {
164 164
            let addr = try lookupDataSym(s, name) catch {
165 165
                panic "resolveVal: data symbol not found";
166 166
            };
167 -
            emit::loadImm(s.e, scratch, addr as i32);
167 +
            emit::loadImm(s.e, scratch, addr as i64);
168 168
            return scratch;
169 169
        },
170 170
        case il::Val::FnAddr(name) => {
171 171
            emit::recordAddrLoad(s.e, name, scratch);
172 172
            return scratch;
239 239
        for i in 0..block.instrs.len {
240 240
            match block.instrs.list[i] {
241 241
                case il::Instr::Reserve { size, alignment, .. } => {
242 242
                    if let case il::Val::Imm(sz) = size {
243 243
                        offset = mem::alignUpI32(offset, alignment as i32);
244 -
                        offset = offset + sz;
244 +
                        offset = offset + (sz as i32);
245 245
                    }
246 246
                },
247 247
                else => {},
248 248
            }
249 249
        }
378 378
                    let rd = getDstReg(s, dst, super::SCRATCH1);
379 379
                    let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32);
380 380
                    let fpOffset: i32 = s.ralloc.spill.frameSize + aligned - s.frameSize;
381 381
382 382
                    emit::emitAddImm(s.e, rd, super::FP, fpOffset);
383 -
                    s.reserveOffset = aligned + sz;
383 +
                    s.reserveOffset = aligned + (sz as i32);
384 384
                },
385 385
                case il::Val::Reg(r) => {
386 386
                    // Dynamic-sized reserve: runtime SP adjustment.
387 387
                    let rd = getDstReg(s, dst, super::SCRATCH1);
388 388
                    let rs = getSrcReg(s, r, super::SCRATCH2);
788 788
789 789
/// Select 32-bit addition with immediate optimization.
790 790
/// Uses `addiw`/`addw` to operate on 32 bits and sign-extend the result.
791 791
fn selectBinOpW(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, scratch: super::Reg) {
792 792
    if let case il::Val::Imm(imm) = b {
793 -
        if encode::isSmallImm(imm) {
794 -
            emit::emit(s.e, encode::addiw(rd, rs1, imm));
793 +
        if encode::isSmallImm64(imm) {
794 +
            emit::emit(s.e, encode::addiw(rd, rs1, imm as i32));
795 795
            return;
796 796
        }
797 797
    }
798 798
    let rs2 = resolveVal(s, scratch, b);
799 799
    emit::emit(s.e, encode::addw(rd, rs1, rs2));
801 801
802 802
/// Select binary operation with immediate optimization.
803 803
fn selectBinOp(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, op: BinOp, scratch: super::Reg) {
804 804
    // Try immediate optimization first.
805 805
    if let case il::Val::Imm(imm) = b {
806 -
        if encode::isSmallImm(imm) {
806 +
        if encode::isSmallImm64(imm) {
807 +
            let simm = imm as i32;
807 808
            match op {
808 -
                case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, imm)),
809 -
                case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, imm)),
810 -
                case BinOp::Or  => emit::emit(s.e, encode::ori(rd, rs1, imm)),
811 -
                case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, imm)),
809 +
                case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, simm)),
810 +
                case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, simm)),
811 +
                case BinOp::Or  => emit::emit(s.e, encode::ori(rd, rs1, simm)),
812 +
                case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, simm)),
812 813
            }
813 814
            return;
814 815
        }
815 816
    }
816 817
    // Fallback: load into register.
832 833
    // Try immediate optimization first.
833 834
    if let case il::Val::Imm(shamt) = b {
834 835
        // Keep immediate forms only for encodable shift amounts.
835 836
        // Otherwise fall back to register shifts, which naturally mask the count.
836 837
        if shamt >= 0 and ((isW32 and shamt < 32) or (not isW32 and shamt < 64)) {
838 +
            let sa = shamt as i32;
837 839
            if isW32 {
838 840
                match op {
839 -
                    case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, shamt)),
840 -
                    case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, shamt)),
841 -
                    case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, shamt)),
841 +
                    case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, sa)),
842 +
                    case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, sa)),
843 +
                    case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, sa)),
842 844
                }
843 845
            } else {
844 846
                match op {
845 -
                    case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, shamt)),
846 -
                    case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, shamt)),
847 -
                    case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, shamt)),
847 +
                    case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, sa)),
848 +
                    case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, sa)),
849 +
                    case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, sa)),
848 850
                }
849 851
            }
850 852
            return;
851 853
        }
852 854
    }
1029 1031
1030 1032
/// Select comparison with immediate optimization.
1031 1033
fn selectCmp(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, op: CmpOp, scratch: super::Reg) {
1032 1034
    // Try immediate optimization first.
1033 1035
    if let case il::Val::Imm(imm) = b {
1034 -
        if encode::isSmallImm(imm) {
1036 +
        if encode::isSmallImm64(imm) {
1037 +
            let simm = imm as i32;
1035 1038
            match op {
1036 -
                case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, imm)),
1037 -
                case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, imm)),
1039 +
                case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, simm)),
1040 +
                case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, simm)),
1038 1041
            }
1039 1042
            return;
1040 1043
        }
1041 1044
    }
1042 1045
    // Fallback: load into register.
lib/std/arch/rv64/tests/arith.w64.rad added +255 -0
1 +
//! Test 64-bit integer arithmetic.
2 +
//! Verifies that i64/u64 operations use full-width instructions
3 +
//! and that widening/narrowing casts work correctly.
4 +
5 +
fn testI64Add() -> bool {
6 +
    let a: i64 = 100;
7 +
    let b: i64 = 200;
8 +
    let result: i64 = a + b;
9 +
    if result != 300 {
10 +
        return false;
11 +
    }
12 +
    return true;
13 +
}
14 +
15 +
fn testU64Add() -> bool {
16 +
    let a: u64 = 100;
17 +
    let b: u64 = 200;
18 +
    let result: u64 = a + b;
19 +
    if result != 300 {
20 +
        return false;
21 +
    }
22 +
    return true;
23 +
}
24 +
25 +
fn testI64Sub() -> bool {
26 +
    let a: i64 = 500;
27 +
    let b: i64 = 300;
28 +
    let result: i64 = a - b;
29 +
    if result != 200 {
30 +
        return false;
31 +
    }
32 +
    return true;
33 +
}
34 +
35 +
fn testI64Mul() -> bool {
36 +
    let a: i64 = 1000;
37 +
    let b: i64 = 2000;
38 +
    let result: i64 = a * b;
39 +
    if result != 2000000 {
40 +
        return false;
41 +
    }
42 +
    return true;
43 +
}
44 +
45 +
fn testI64Div() -> bool {
46 +
    let a: i64 = 1000;
47 +
    let b: i64 = 10;
48 +
    let result: i64 = a / b;
49 +
    if result != 100 {
50 +
        return false;
51 +
    }
52 +
    return true;
53 +
}
54 +
55 +
fn testU64Div() -> bool {
56 +
    let a: u64 = 1000;
57 +
    let b: u64 = 10;
58 +
    let result: u64 = a / b;
59 +
    if result != 100 {
60 +
        return false;
61 +
    }
62 +
    return true;
63 +
}
64 +
65 +
fn testI64Neg() -> bool {
66 +
    let a: i64 = 42;
67 +
    let result: i64 = -a;
68 +
    if result != -42 {
69 +
        return false;
70 +
    }
71 +
    return true;
72 +
}
73 +
74 +
fn testI64Shift() -> bool {
75 +
    let a: i64 = 1;
76 +
    let result: i64 = a << 40;
77 +
    if result != 1099511627776 {
78 +
        return false;
79 +
    }
80 +
    let back: i64 = result >> 40;
81 +
    if back != 1 {
82 +
        return false;
83 +
    }
84 +
    return true;
85 +
}
86 +
87 +
fn testU64Shift() -> bool {
88 +
    let a: u64 = 1;
89 +
    let shifted: u64 = a << 32;
90 +
    // Shift by 32 should NOT wrap like u32 would.
91 +
    let back: u64 = shifted >> 32;
92 +
    if back != 1 {
93 +
        return false;
94 +
    }
95 +
    return true;
96 +
}
97 +
98 +
fn testI64Bitwise() -> bool {
99 +
    let a: i64 = 0xFF00;
100 +
    let b: i64 = 0x0FF0;
101 +
    let andResult: i64 = a & b;
102 +
    if andResult != 0x0F00 {
103 +
        return false;
104 +
    }
105 +
    let orResult: i64 = a | b;
106 +
    if orResult != 0xFFF0 {
107 +
        return false;
108 +
    }
109 +
    let xorResult: i64 = a ^ b;
110 +
    if xorResult != 0xF0F0 {
111 +
        return false;
112 +
    }
113 +
    return true;
114 +
}
115 +
116 +
fn testWidenI32ToI64() -> bool {
117 +
    let a: i32 = -42;
118 +
    let b: i64 = a as i64;
119 +
    if b != -42 {
120 +
        return false;
121 +
    }
122 +
    return true;
123 +
}
124 +
125 +
fn testWidenU32ToU64() -> bool {
126 +
    let a: u32 = 0xFFFFFFFF;
127 +
    let b: u64 = a as u64;
128 +
    // Should zero-extend, not sign-extend.
129 +
    // If sign-extended, this would be a very large negative number in i64.
130 +
    let c: u64 = b >> 32;
131 +
    if c != 0 {
132 +
        return false;
133 +
    }
134 +
    return true;
135 +
}
136 +
137 +
fn testNarrowI64ToI32() -> bool {
138 +
    let a: i64 = 42;
139 +
    let b: i32 = a as i32;
140 +
    if b != 42 {
141 +
        return false;
142 +
    }
143 +
    return true;
144 +
}
145 +
146 +
fn testNarrowU64ToU32() -> bool {
147 +
    let a: u64 = 42;
148 +
    let b: u32 = a as u32;
149 +
    if b != 42 {
150 +
        return false;
151 +
    }
152 +
    return true;
153 +
}
154 +
155 +
fn testI64Compare() -> bool {
156 +
    let a: i64 = -1;
157 +
    let b: i64 = 1;
158 +
    if a >= b {
159 +
        return false;
160 +
    }
161 +
    if b <= a {
162 +
        return false;
163 +
    }
164 +
    return true;
165 +
}
166 +
167 +
fn testU64Compare() -> bool {
168 +
    let a: u64 = 100;
169 +
    let b: u64 = 200;
170 +
    if a >= b {
171 +
        return false;
172 +
    }
173 +
    if b <= a {
174 +
        return false;
175 +
    }
176 +
    return true;
177 +
}
178 +
179 +
fn testI64Modulo() -> bool {
180 +
    let a: i64 = 17;
181 +
    let b: i64 = 5;
182 +
    let result: i64 = a % b;
183 +
    if result != 2 {
184 +
        return false;
185 +
    }
186 +
    return true;
187 +
}
188 +
189 +
fn testU64Modulo() -> bool {
190 +
    let a: u64 = 17;
191 +
    let b: u64 = 5;
192 +
    let result: u64 = a % b;
193 +
    if result != 2 {
194 +
        return false;
195 +
    }
196 +
    return true;
197 +
}
198 +
199 +
@default fn main() -> i32 {
200 +
    if not testI64Add() {
201 +
        return 1;
202 +
    }
203 +
    if not testU64Add() {
204 +
        return 2;
205 +
    }
206 +
    if not testI64Sub() {
207 +
        return 3;
208 +
    }
209 +
    if not testI64Mul() {
210 +
        return 4;
211 +
    }
212 +
    if not testI64Div() {
213 +
        return 5;
214 +
    }
215 +
    if not testU64Div() {
216 +
        return 6;
217 +
    }
218 +
    if not testI64Neg() {
219 +
        return 7;
220 +
    }
221 +
    if not testI64Shift() {
222 +
        return 8;
223 +
    }
224 +
    if not testU64Shift() {
225 +
        return 9;
226 +
    }
227 +
    if not testI64Bitwise() {
228 +
        return 10;
229 +
    }
230 +
    if not testWidenI32ToI64() {
231 +
        return 11;
232 +
    }
233 +
    if not testWidenU32ToU64() {
234 +
        return 12;
235 +
    }
236 +
    if not testNarrowI64ToI32() {
237 +
        return 13;
238 +
    }
239 +
    if not testNarrowU64ToU32() {
240 +
        return 14;
241 +
    }
242 +
    if not testI64Compare() {
243 +
        return 15;
244 +
    }
245 +
    if not testU64Compare() {
246 +
        return 16;
247 +
    }
248 +
    if not testI64Modulo() {
249 +
        return 17;
250 +
    }
251 +
    if not testU64Modulo() {
252 +
        return 18;
253 +
    }
254 +
    return 0;
255 +
}
lib/std/arch/rv64/tests/literal.w64.rad added +96 -0
1 +
//! Test 64-bit integer literals.
2 +
//! Verifies that large constants are loaded correctly.
3 +
4 +
fn testU64MaxLiteral() -> bool {
5 +
    let x: u64 = 18446744073709551615;
6 +
    // Check low and high bits.
7 +
    let lo: u64 = x & 0xFFFFFFFF;
8 +
    let hi: u64 = x >> 32;
9 +
    if lo != 0xFFFFFFFF {
10 +
        return false;
11 +
    }
12 +
    if hi != 0xFFFFFFFF {
13 +
        return false;
14 +
    }
15 +
    return true;
16 +
}
17 +
18 +
fn testI64MaxLiteral() -> bool {
19 +
    let x: i64 = 9223372036854775807;
20 +
    let shifted: i64 = x >> 32;
21 +
    if shifted != 0x7FFFFFFF {
22 +
        return false;
23 +
    }
24 +
    return true;
25 +
}
26 +
27 +
fn testI64MinLiteral() -> bool {
28 +
    let x: i64 = -9223372036854775808;
29 +
    if x >= 0 {
30 +
        return false;
31 +
    }
32 +
    let shifted: i64 = x >> 63;
33 +
    if shifted != -1 {
34 +
        return false;
35 +
    }
36 +
    return true;
37 +
}
38 +
39 +
fn testHexW64Literal() -> bool {
40 +
    let x: u64 = 0xDEADBEEFCAFEBABE;
41 +
    let lo: u32 = x as u32;
42 +
    let hi: u32 = (x >> 32) as u32;
43 +
    if lo != 0xCAFEBABE {
44 +
        return false;
45 +
    }
46 +
    if hi != 0xDEADBEEF {
47 +
        return false;
48 +
    }
49 +
    return true;
50 +
}
51 +
52 +
fn testLargeI64Arith() -> bool {
53 +
    let a: i64 = 1000000000000;
54 +
    let b: i64 = 2000000000000;
55 +
    let result: i64 = a + b;
56 +
    if result != 3000000000000 {
57 +
        return false;
58 +
    }
59 +
    return true;
60 +
}
61 +
62 +
fn testU64Overflow32() -> bool {
63 +
    // 2^32 + 1 = 4294967297
64 +
    let x: u64 = 4294967297;
65 +
    let lo: u32 = x as u32;
66 +
    let hi: u32 = (x >> 32) as u32;
67 +
    if lo != 1 {
68 +
        return false;
69 +
    }
70 +
    if hi != 1 {
71 +
        return false;
72 +
    }
73 +
    return true;
74 +
}
75 +
76 +
@default fn main() -> i32 {
77 +
    if not testU64MaxLiteral() {
78 +
        return 1;
79 +
    }
80 +
    if not testI64MaxLiteral() {
81 +
        return 2;
82 +
    }
83 +
    if not testI64MinLiteral() {
84 +
        return 3;
85 +
    }
86 +
    if not testHexW64Literal() {
87 +
        return 4;
88 +
    }
89 +
    if not testLargeI64Arith() {
90 +
        return 5;
91 +
    }
92 +
    if not testU64Overflow32() {
93 +
        return 6;
94 +
    }
95 +
    return 0;
96 +
}
lib/std/fmt.rad +54 -0
4 4
5 5
/// Maximum string length for a formatted u32 (eg. "4294967295").
6 6
pub const U32_STR_LEN: u32 = 10;
7 7
/// Maximum string length for a formatted i32 (eg. "-2147483648").
8 8
pub const I32_STR_LEN: u32 = 11;
9 +
/// Maximum string length for a formatted u64 (eg. "18446744073709551615").
10 +
pub const U64_STR_LEN: u32 = 20;
11 +
/// Maximum string length for a formatted i64 (eg. "-9223372036854775808").
12 +
pub const I64_STR_LEN: u32 = 20;
9 13
/// Maximum string length for a formatted bool (eg. "false").
10 14
pub const BOOL_STR_LEN: u32 = 5;
11 15
12 16
/// Format a u32 by writing it to the provided buffer.
13 17
pub fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
64 68
        }
65 69
    }
66 70
    return buffer[i..];
67 71
}
68 72
73 +
/// Format a u64 by writing it to the provided buffer.
74 +
pub fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
75 +
    debug::assert(buffer.len >= U64_STR_LEN);
76 +
77 +
    let mut x: u64 = val;
78 +
    let mut i: u32 = buffer.len;
79 +
80 +
    if x == 0 {
81 +
        i = i - 1;
82 +
        buffer[i] = '0';
83 +
    } else {
84 +
        while x != 0 {
85 +
            i = i - 1;
86 +
            buffer[i] = ('0' + (x % 10) as u8);
87 +
            x = x / 10;
88 +
        }
89 +
    }
90 +
    return buffer[i..];
91 +
}
92 +
93 +
/// Format a i64 by writing it to the provided buffer.
94 +
pub fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
95 +
    debug::assert(buffer.len >= I64_STR_LEN);
96 +
97 +
    let mut x: u64 = 0;
98 +
    let mut i: u32 = buffer.len;
99 +
    let neg: bool = val < 0;
100 +
101 +
    if neg {
102 +
        x = -val as u64;
103 +
    } else {
104 +
        x = val as u64;
105 +
    }
106 +
    if x == 0 {
107 +
        i = i - 1;
108 +
        buffer[i] = '0';
109 +
    } else {
110 +
        while x != 0 {
111 +
            i = i - 1;
112 +
            buffer[i] = '0' + (x % 10) as u8;
113 +
            x = x / 10;
114 +
        }
115 +
        if neg {
116 +
            i = i - 1;
117 +
            buffer[i] = '-';
118 +
        }
119 +
    }
120 +
    return buffer[i..];
121 +
}
122 +
69 123
/// Format a i8 by writing it to the provided buffer.
70 124
pub fn formatI8(val: i8, buffer: *mut [u8]) -> *[u8] {
71 125
    return formatI32(val as i32, buffer);
72 126
}
73 127
lib/std/lang/ast.rad +1 -1
144 144
/// Parsed integer literal metadata.
145 145
pub record IntLiteral {
146 146
    /// Raw characters that comprised the literal.
147 147
    text: *[u8],
148 148
    /// Absolute magnitude parsed from the literal.
149 -
    magnitude: u32,
149 +
    magnitude: u64,
150 150
    /// Radix used by the literal.
151 151
    radix: Radix,
152 152
    /// Whether the literal spelled an explicit sign.
153 153
    signed: bool,
154 154
    /// Whether the literal used a negative sign.
lib/std/lang/il.rad +3 -3
131 131
/// Instruction value.
132 132
pub union Val {
133 133
    /// Register reference.
134 134
    Reg(Reg),
135 135
    /// Immediate integer value.
136 -
    Imm(i32),
136 +
    Imm(i64),
137 137
    /// Data symbol address (globals, constants, string literals).
138 138
    DataSym(*[u8]),
139 139
    /// Function address by symbol name. Used directly in call instructions
140 140
    /// for direct calls, or stored first for indirect calls.
141 141
    FnAddr(*[u8]),
175 175
}
176 176
177 177
/// Switch case mapping a constant value to a branch target.
178 178
pub record SwitchCase {
179 179
    /// The constant value to match against.
180 -
    value: i32,
180 +
    value: i64,
181 181
    /// The target block index.
182 182
    target: u32,
183 183
    /// Arguments to pass to the target block.
184 184
    args: *mut [Val],
185 185
}
342 342
/////////////
343 343
344 344
/// Data initializer item.
345 345
pub union DataItem {
346 346
    /// Typed value: `w32 42;` or `w8 255;`
347 -
    Val { typ: Type, val: i32 },
347 +
    Val { typ: Type, val: i64 },
348 348
    /// Symbol reference: `$symbol`
349 349
    Sym(*[u8]),
350 350
    /// Function reference: `$fnName`
351 351
    Fn(*[u8]),
352 352
    /// String literal bytes: `"hello"` (no auto null-terminator)
lib/std/lang/il/printer.rad +14 -3
29 29
    try! mem::copy(slice, text);
30 30
31 31
    return slice;
32 32
}
33 33
34 +
/// Format an `i64` into a string in the arena.
35 +
fn formatI64(a: *mut alloc::Arena, val: i64) -> *[u8] {
36 +
    let mut digits: [u8; 20] = undefined;
37 +
    let text = fmt::formatI64(val, &mut digits[..]);
38 +
    let ptr = try! alloc::allocSlice(a, 1, 1, text.len);
39 +
    let slice = ptr as *mut [u8];
40 +
    try! mem::copy(slice, text);
41 +
42 +
    return slice;
43 +
}
44 +
34 45
/// Concatenate prefix and string in the arena.
35 46
fn prefixStr(a: *mut alloc::Arena, prefix: u8, str: *[u8]) -> *[u8] {
36 47
    let len = str.len + 1;
37 48
    let ptr = try! alloc::allocSlice(a, 1, 1, len);
38 49
    let slice = ptr as *mut [u8];
152 163
153 164
/// Write a value (register, immediate, symbol, or undefined).
154 165
fn writeVal(out: *mut sexpr::Output, a: *mut alloc::Arena, val: super::Val) {
155 166
    match val {
156 167
        case super::Val::Reg(reg) => write(out, regStr(a, reg)),
157 -
        case super::Val::Imm(v) => write(out, formatI32(a, v)),
168 +
        case super::Val::Imm(v) => write(out, formatI64(a, v)),
158 169
        case super::Val::DataSym(name) => writeSymbol(out, name),
159 170
        case super::Val::FnAddr(name) => writeSymbol(out, name),
160 171
        case super::Val::Undef => write(out, "undefined"),
161 172
    }
162 173
}
325 336
            write(out, "switch ");
326 337
            writeVal(out, a, val);
327 338
            for i in 0..cases.len {
328 339
                let c = cases[i];
329 340
                write(out, " (");
330 -
                write(out, formatI32(a, c.value));
341 +
                write(out, formatI64(a, c.value));
331 342
                write(out, " @");
332 343
                write(out, blocks[c.target].label);
333 344
                if c.args.len > 0 {
334 345
                    writeArgs(out, a, c.args);
335 346
                }
474 485
fn writeDataItem(out: *mut sexpr::Output, a: *mut alloc::Arena, item: super::DataItem) {
475 486
    match item {
476 487
        case super::DataItem::Val { typ, val } => {
477 488
            writeType(out, typ);
478 489
            write(out, " ");
479 -
            write(out, formatI32(a, val));
490 +
            write(out, formatI64(a, val));
480 491
        }
481 492
        case super::DataItem::Sym(name) => {
482 493
            write(out, "sym ");
483 494
            writeSymbol(out, name);
484 495
        }
lib/std/lang/lower.rad +43 -43
47 47
//!
48 48
//! # Expression Lowering
49 49
//!
50 50
//! Expressions produce IL values which can be:
51 51
//!
52 -
//! - Imm(i32): immediate/constant values
52 +
//! - Imm(i64): immediate/constant values
53 53
//! - Reg(u32): SSA register references
54 54
//! - Sym(name): symbol references (for function pointers, data addresses)
55 55
//! - Undef: for unused values
56 56
//!
57 57
//! For aggregate types (records, arrays, slices, optionals), the "value" is
1003 1003
///////////////////////////////
1004 1004
1005 1005
// Functions for building the data section from const/static
1006 1006
// declarations and inline literals.
1007 1007
1008 -
/// Convert a constant integer payload to a signed 32-bit value.
1009 -
fn constIntToI32(intVal: resolver::ConstInt) -> i32 {
1010 -
    let mut value = intVal.magnitude as i32;
1008 +
/// Convert a constant integer payload to a signed 64-bit value.
1009 +
fn constIntToI64(intVal: resolver::ConstInt) -> i64 {
1010 +
    let mut value = intVal.magnitude as i64;
1011 1011
    if intVal.negative {
1012 1012
        value = 0 - value;
1013 1013
    }
1014 1014
    return value;
1015 1015
}
1016 1016
1017 -
/// Convert a scalar constant value to an i32.
1017 +
/// Convert a scalar constant value to an i64.
1018 1018
/// String constants are not handled here and should be checked before calling.
1019 1019
/// Panics if a non-scalar value is passed.
1020 -
fn constToScalar(val: resolver::ConstValue) -> i32 {
1020 +
fn constToScalar(val: resolver::ConstValue) -> i64 {
1021 1021
    match val {
1022 1022
        case resolver::ConstValue::Bool(b) => {
1023 1023
            if b {
1024 1024
                return 1;
1025 1025
            } else {
1026 1026
                return 0;
1027 1027
            }
1028 1028
        }
1029 -
        case resolver::ConstValue::Char(c) => return c as i32,
1030 -
        case resolver::ConstValue::Int(i) => return constIntToI32(i),
1029 +
        case resolver::ConstValue::Char(c) => return c as i64,
1030 +
        case resolver::ConstValue::Int(i) => return constIntToI64(i),
1031 1031
        else => panic,
1032 1032
    }
1033 1033
}
1034 1034
1035 1035
/// Convert a constant value to an IL value.
1090 1090
        count: 1
1091 1091
    });
1092 1092
    dataBuilderPush(b, il::DataValue {
1093 1093
        item: il::DataItem::Val {
1094 1094
            typ: il::Type::W32,
1095 -
            val: len as i32
1095 +
            val: len as i64
1096 1096
        },
1097 1097
        count: 1
1098 1098
    });
1099 1099
    dataBuilderPush(b, il::DataValue {
1100 1100
        item: il::DataItem::Undef,
1365 1365
1366 1366
    // Tag byte.
1367 1367
    dataBuilderPush(b, il::DataValue {
1368 1368
        item: il::DataItem::Val {
1369 1369
            typ: il::Type::W8,
1370 -
            val: index as i32
1370 +
            val: index as i64
1371 1371
        },
1372 1372
        count: 1
1373 1373
    });
1374 1374
    // Padding between tag and payload.
1375 1375
    if unionInfo.valOffset > 1 {
1603 1603
    // Get data address.
1604 1604
    let ptrReg = nextReg(self);
1605 1605
    emit(self, il::Instr::Copy { dst: ptrReg, val: il::Val::DataSym(dataName) });
1606 1606
1607 1607
    return try buildSliceValue(
1608 -
        self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i32)
1608 +
        self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i64)
1609 1609
    );
1610 1610
}
1611 1611
1612 1612
/// Generate a unique data name for inline literals, eg. `fnName/N`
1613 1613
fn nextDataName(self: *mut FnLowerer) -> *[u8] throws (LowerError) {
2174 2174
                        let base = try emitValToReg(self, subject.val);
2175 2175
                        tagVal = loadTag(self, base, 0, il::Type::W8);
2176 2176
                    }
2177 2177
                    case resolver::MatchBy::Value => {}
2178 2178
                }
2179 -
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, tagVal, il::Val::Imm(variantTag as i32), matchBlock, fallthrough);
2179 +
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, tagVal, il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2180 2180
            } else {
2181 2181
                let base = try emitValToReg(self, subject.val);
2182 2182
                let tagReg = tvalTagReg(self, base);
2183 2183
2184 -
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i32), matchBlock, fallthrough);
2184 +
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2185 2185
            }
2186 2186
        }
2187 2187
        else => { // Scalar comparison.
2188 2188
            debug::assert(not isNil);
2189 2189
            let pattVal = try lowerExpr(self, pattern);
3618 3618
}
3619 3619
3620 3620
/// Check if a node is a void union variant literal (e.g. `Color::Red`).
3621 3621
/// If so, returns the variant's tag index. This enables optimized comparisons
3622 3622
/// that only check the tag instead of doing full aggregate comparison.
3623 -
fn voidVariantIndex(res: *resolver::Resolver, node: *ast::Node) -> ?i32 {
3623 +
fn voidVariantIndex(res: *resolver::Resolver, node: *ast::Node) -> ?i64 {
3624 3624
    let data = resolver::nodeData(res, node);
3625 3625
    let sym = data.sym else {
3626 3626
        return nil;
3627 3627
    };
3628 3628
    let case resolver::SymbolData::Variant { type: payloadType, index, .. } = sym.data else {
3630 3630
    };
3631 3631
    // Only void variants can use tag-only comparison.
3632 3632
    if payloadType != resolver::Type::Void {
3633 3633
        return nil;
3634 3634
    }
3635 -
    return index as i32;
3635 +
    return index as i64;
3636 3636
}
3637 3637
3638 3638
/// Check if an expression has persistent storage, ie. is an "lvalue".
3639 3639
/// Such expressions need to be copied when used to initialize a variable,
3640 3640
/// since their storage continues to exist independently. Temporaries (literals,
3659 3659
fn emitReserveLayout(self: *mut FnLowerer, layout: resolver::Layout) -> il::Reg throws (LowerError) {
3660 3660
    let dst = nextReg(self);
3661 3661
3662 3662
    emit(self, il::Instr::Reserve {
3663 3663
        dst,
3664 -
        size: il::Val::Imm(layout.size as i32),
3664 +
        size: il::Val::Imm(layout.size as i64),
3665 3665
        alignment: layout.alignment,
3666 3666
    });
3667 3667
    return dst;
3668 3668
}
3669 3669
3700 3700
/// Reserves space based on the provided layout, stores the tag, and optionally
3701 3701
/// stores the payload value at `valOffset`.
3702 3702
fn buildTagged(
3703 3703
    self: *mut FnLowerer,
3704 3704
    layout: resolver::Layout,
3705 -
    tag: i32,
3705 +
    tag: i64,
3706 3706
    payload: ?il::Val,
3707 3707
    payloadType: resolver::Type,
3708 3708
    tagSize: u32,
3709 3709
    valOffset: i32
3710 3710
) -> il::Val throws (LowerError) {
3711 3711
    let dst = nextReg(self);
3712 3712
    emit(self, il::Instr::Reserve {
3713 3713
        dst,
3714 -
        size: il::Val::Imm(layout.size as i32),
3714 +
        size: il::Val::Imm(layout.size as i64),
3715 3715
        alignment: layout.alignment,
3716 3716
    });
3717 3717
    if tagSize == 1 {
3718 3718
        emitStoreW8At(self, il::Val::Imm(tag), dst, TVAL_TAG_OFFSET);
3719 3719
    } else {
3762 3762
}
3763 3763
3764 3764
/// Build a result value for throwing functions.
3765 3765
fn buildResult(
3766 3766
    self: *mut FnLowerer,
3767 -
    tag: i32,
3767 +
    tag: i64,
3768 3768
    payload: ?il::Val,
3769 3769
    payloadType: resolver::Type
3770 3770
) -> il::Val throws (LowerError) {
3771 3771
    let successType = *self.fnType.returnType;
3772 3772
    let errType = *self.fnType.throwList[0]; // TODO: Support more errors.
3803 3803
    emit(self, il::Instr::BinOp {
3804 3804
        op: il::BinOp::Add,
3805 3805
        typ: il::Type::W64,
3806 3806
        dst,
3807 3807
        a: il::Val::Reg(base),
3808 -
        b: il::Val::Imm(offset),
3808 +
        b: il::Val::Imm(offset as i64),
3809 3809
    });
3810 3810
    return dst;
3811 3811
}
3812 3812
3813 3813
/// Emit an element address computation for array/slice indexing.
3822 3822
    emit(self, il::Instr::BinOp {
3823 3823
        op: il::BinOp::Mul,
3824 3824
        typ: il::Type::W64,
3825 3825
        dst: offset,
3826 3826
        a: idx,
3827 -
        b: il::Val::Imm(stride as i32)
3827 +
        b: il::Val::Imm(stride as i64)
3828 3828
    });
3829 3829
    // Compute `dst = base + offset`.
3830 3830
    let dst = nextReg(self);
3831 3831
    emit(self, il::Instr::BinOp {
3832 3832
        op: il::BinOp::Add,
3844 3844
    emit(self, il::Instr::BinOp { op, typ, dst, a, b });
3845 3845
    return il::Val::Reg(dst);
3846 3846
}
3847 3847
3848 3848
/// Emit a tag comparison for void variant equality/inequality.
3849 -
fn emitTagCmp(self: *mut FnLowerer, op: ast::BinaryOp, val: il::Val, tagIdx: i32, valType: resolver::Type) -> il::Val
3849 +
fn emitTagCmp(self: *mut FnLowerer, op: ast::BinaryOp, val: il::Val, tagIdx: i64, valType: resolver::Type) -> il::Val
3850 3850
    throws (LowerError)
3851 3851
{
3852 3852
    let reg = try emitValToReg(self, val);
3853 3853
    let mut tag: il::Val = undefined;
3854 3854
4094 4094
4095 4095
    let mut caseBlocks: [?BlockId; resolver::MAX_UNION_VARIANTS] = undefined;
4096 4096
    for i in 0..unionInfo.variantsLen {
4097 4097
        if unionInfo.variants[i].valueType == resolver::Type::Void {
4098 4098
            cases[i] = il::SwitchCase {
4099 -
                value: i as i32,
4099 +
                value: i as i64,
4100 4100
                target: mergeBlock.n,
4101 4101
                args: trueArgs
4102 4102
            };
4103 4103
            caseBlocks[i] = nil;
4104 4104
        } else {
4105 4105
            let payloadBlock = try createBlock(self, "eq#payload");
4106 4106
            cases[i] = il::SwitchCase {
4107 -
                value: i as i32,
4107 +
                value: i as i64,
4108 4108
                target: payloadBlock.n,
4109 4109
                args: &mut []
4110 4110
            };
4111 4111
            caseBlocks[i] = payloadBlock;
4112 4112
        }
4251 4251
        throw LowerError::MissingMetadata;
4252 4252
    };
4253 4253
    let valOffset = unionInfo.valOffset as i32;
4254 4254
    let dst = try emitReserve(self, typ);
4255 4255
4256 -
    emitStoreW8At(self, il::Val::Imm(index as i32), dst, TVAL_TAG_OFFSET);
4256 +
    emitStoreW8At(self, il::Val::Imm(index as i64), dst, TVAL_TAG_OFFSET);
4257 4257
    try lowerRecordFields(self, dst, &recInfo, lit.fields, valOffset);
4258 4258
4259 4259
    return il::Val::Reg(dst);
4260 4260
}
4261 4261
4382 4382
        let case resolver::Type::Nominal(payloadNominal) = payloadType else {
4383 4383
            throw LowerError::MissingMetadata;
4384 4384
        };
4385 4385
        payloadVal = try lowerRecordCtor(self, payloadNominal, call.args);
4386 4386
    }
4387 -
    return try buildTagged(self, resolver::getTypeLayout(unionTy), index as i32, payloadVal, payloadType, 1, valOffset);
4387 +
    return try buildTagged(self, resolver::getTypeLayout(unionTy), index as i64, payloadVal, payloadType, 1, valOffset);
4388 4388
}
4389 4389
4390 4390
/// Lower a field access into a pointer to the field.
4391 4391
fn lowerFieldRef(self: *mut FnLowerer, access: ast::Access) -> FieldRef throws (LowerError) {
4392 4392
    let parentTy = resolver::typeFor(self.low.resolver, access.parent) else {
4430 4430
4431 4431
    // Extract data pointer and container length.
4432 4432
    let mut dataReg = baseReg;
4433 4433
    let mut containerLen: il::Val = undefined;
4434 4434
    if let cap = info.capacity { // Slice from array.
4435 -
        containerLen = il::Val::Imm(cap as i32);
4435 +
        containerLen = il::Val::Imm(cap as i64);
4436 4436
    } else { // Slice from slice.
4437 4437
        dataReg = loadSlicePtr(self, baseReg);
4438 4438
        containerLen = loadSliceLen(self, baseReg);
4439 4439
    }
4440 4440
4598 4598
    let arrayReg = nextReg(self);
4599 4599
4600 4600
    // Reserve stack space for slice elements.
4601 4601
    emit(self, il::Instr::Reserve {
4602 4602
        dst: arrayReg,
4603 -
        size: il::Val::Imm(arraySize as i32),
4603 +
        size: il::Val::Imm(arraySize as i64),
4604 4604
        alignment: elemLayout.alignment
4605 4605
    });
4606 4606
    // Store each element.
4607 4607
    for i in 0..elements.len {
4608 4608
        let elemNode = elements.list[i];
4609 4609
        let elemVal = try lowerExpr(self, elemNode);
4610 4610
        let offset = i * elemLayout.size;
4611 4611
4612 4612
        try emitStore(self, arrayReg, offset as i32, *elemTy, elemVal);
4613 4613
    }
4614 -
    let lenVal = il::Val::Imm(elements.len as i32);
4614 +
    let lenVal = il::Val::Imm(elements.len as i64);
4615 4615
4616 4616
    return try buildSliceValue(self, elemTy, mutable, il::Val::Reg(arrayReg), lenVal);
4617 4617
}
4618 4618
4619 4619
/// Lower the common element pointer computation for subscript operations.
4647 4647
            elemType = *arrInfo.item;
4648 4648
            // Runtime safety check: index must be strictly less than array length.
4649 4649
            // Skip when the index is a compile-time constant, since we check
4650 4650
            // that in the resolver.
4651 4651
            if not resolver::isConstExpr(self.low.resolver, index) {
4652 -
                let arrLen = il::Val::Imm(arrInfo.length as i32);
4652 +
                let arrLen = il::Val::Imm(arrInfo.length as i64);
4653 4653
                try emitTrapUnlessCmp(self, il::CmpOp::Ult, il::Type::W32, indexVal, arrLen);
4654 4654
            }
4655 4655
        }
4656 4656
        else => throw LowerError::ExpectedSliceOrArray,
4657 4657
    }
5052 5052
            let containerReg = try emitValToReg(self, containerVal);
5053 5053
5054 5054
            let mut dataReg = containerReg;
5055 5055
            let mut lengthVal: il::Val = undefined;
5056 5056
            if let len = length { // Array (length is known).
5057 -
                lengthVal = il::Val::Imm(len as i32);
5057 +
                lengthVal = il::Val::Imm(len as i64);
5058 5058
            } else { // Slice (length must be loaded).
5059 5059
                lengthVal = loadSliceLen(self, containerReg);
5060 5060
                dataReg = loadSlicePtr(self, containerReg);
5061 5061
            }
5062 5062
            // Declare index value binidng.
5219 5219
    // Block that skips evaluating `b`.
5220 5220
    let mut shortCircuitBlock: BlockId = undefined;
5221 5221
    // Block that evaluates `b`.
5222 5222
    let mut evalBlock: BlockId = undefined;
5223 5223
    // Result when short-circuiting (`0` or `1`).
5224 -
    let mut shortCircuitVal: i32 = undefined;
5224 +
    let mut shortCircuitVal: i64 = undefined;
5225 5225
5226 5226
    match op {
5227 5227
        case LogicalOp::And => {
5228 5228
            shortCircuitBlock = elseBlock;
5229 5229
            evalBlock = thenBlock;
5505 5505
}
5506 5506
5507 5507
/// Check whether a resolver type is a signed integer type.
5508 5508
fn isSignedType(t: resolver::Type) -> bool {
5509 5509
    match t {
5510 -
        case resolver::Type::I8, resolver::Type::I16, resolver::Type::I32 => return true,
5510 +
        case resolver::Type::I8, resolver::Type::I16, resolver::Type::I32, resolver::Type::I64 => return true,
5511 5511
        else => return false,
5512 5512
    }
5513 5513
}
5514 5514
5515 5515
/// Check whether a resolver type is an unsigned integer type.
5516 5516
fn isUnsignedType(t: resolver::Type) -> bool {
5517 5517
    match t {
5518 -
        case resolver::Type::U8, resolver::Type::U16, resolver::Type::U32 => return true,
5518 +
        case resolver::Type::U8, resolver::Type::U16, resolver::Type::U32, resolver::Type::U64 => return true,
5519 5519
        else => return false,
5520 5520
    }
5521 5521
}
5522 5522
5523 5523
/// Lower a string literal to a slice value.
5975 5975
                throw LowerError::MissingType(node);
5976 5976
            }
5977 5977
            // All-void unions are passed as scalars (the tag byte).
5978 5978
            // Return an immediate instead of building a tagged aggregate.
5979 5979
            if resolver::isVoidUnion(data.ty) {
5980 -
                return il::Val::Imm(index as i32);
5980 +
                return il::Val::Imm(index as i64);
5981 5981
            }
5982 5982
            let unionInfo = unionInfoFromType(data.ty) else {
5983 5983
                throw LowerError::MissingMetadata;
5984 5984
            };
5985 5985
            let valOffset = unionInfo.valOffset as i32;
5986 -
            return try buildTagged(self, resolver::getTypeLayout(data.ty), index as i32, nil, resolver::Type::Void, 1, valOffset);
5986 +
            return try buildTagged(self, resolver::getTypeLayout(data.ty), index as i64, nil, resolver::Type::Void, 1, valOffset);
5987 5987
        }
5988 5988
        case resolver::SymbolData::Constant { type, .. } => {
5989 5989
            // Constant without compile-time value (e.g. record constant);
5990 5990
            // load from data section.
5991 5991
            let src = emitDataAddr(self, sym);
6040 6040
        }
6041 6041
        case ast::NodeValue::ScopeAccess(_) => {
6042 6042
            val = try lowerScopeAccess(self, node);
6043 6043
        }
6044 6044
        case ast::NodeValue::Number(lit) => {
6045 -
            // FIXME: Handle large numbers without casting.
6046 -
            let mag = lit.magnitude as i32;
6045 +
            let mut mag = lit.magnitude as i64;
6047 6046
            if lit.negative {
6048 -
                val = il::Val::Imm(-mag);
6049 -
            } else {
6050 -
                val = il::Val::Imm(mag);
6047 +
                mag = -mag;
6051 6048
            }
6049 +
            val = il::Val::Imm(mag);
6052 6050
        }
6053 6051
        case ast::NodeValue::Bool(b) => {
6054 6052
            if b {
6055 6053
                val = il::Val::Imm(1);
6056 6054
            } else {
6057 6055
                val = il::Val::Imm(0);
6058 6056
            }
6059 6057
        }
6060 6058
        case ast::NodeValue::Char(c) => {
6061 -
            val = il::Val::Imm(c as i32);
6059 +
            val = il::Val::Imm(c as i64);
6062 6060
        }
6063 6061
        case ast::NodeValue::Nil => {
6064 6062
            let typ = resolver::typeFor(self.low.resolver, node) else {
6065 6063
                throw LowerError::MissingType(node);
6066 6064
            };
6105 6103
            // Check for compile-time constant (e.g., `arr.len` on fixed-size arrays).
6106 6104
            if let constVal = resolver::constValueFor(self.low.resolver, node) {
6107 6105
                match constVal {
6108 6106
                    // TODO: Handle `u32` values that don't fit in an `i32`.
6109 6107
                    //       Perhaps just store the `ConstInt`.
6110 -
                    case resolver::ConstValue::Int(i) => val = il::Val::Imm(constIntToI32(i)),
6108 +
                    case resolver::ConstValue::Int(i) => val = il::Val::Imm(constIntToI64(i)),
6111 6109
                    else => val = try lowerFieldAccess(self, access),
6112 6110
                }
6113 6111
            } else {
6114 6112
                val = try lowerFieldAccess(self, access);
6115 6113
            }
6192 6190
        case resolver::Type::I16 => return il::Type::W16,
6193 6191
        case resolver::Type::I32 => return il::Type::W32,
6194 6192
        case resolver::Type::U8 => return il::Type::W8,
6195 6193
        case resolver::Type::U16 => return il::Type::W16,
6196 6194
        case resolver::Type::U32 => return il::Type::W32,
6195 +
        case resolver::Type::I64 => return il::Type::W64,
6196 +
        case resolver::Type::U64 => return il::Type::W64,
6197 6197
        // Reference types are all pointer-sized (64-bit on RV64).
6198 6198
        case resolver::Type::Pointer { .. } => return il::Type::W64,
6199 6199
        // Aggregates are represented as pointers to stack memory.
6200 6200
        case resolver::Type::Slice { .. } => return il::Type::W64,
6201 6201
        case resolver::Type::Array(_) => return il::Type::W64,
lib/std/lang/lower/tests/arith.w64.rad added +69 -0
1 +
/// Add two i64 values.
2 +
fn addI64(a: i64, b: i64) -> i64 {
3 +
    return a + b;
4 +
}
5 +
6 +
/// Subtract two i64 values.
7 +
fn subI64(a: i64, b: i64) -> i64 {
8 +
    return a - b;
9 +
}
10 +
11 +
/// Multiply two i64 values.
12 +
fn mulI64(a: i64, b: i64) -> i64 {
13 +
    return a * b;
14 +
}
15 +
16 +
/// Add two u64 values.
17 +
fn addU64(a: u64, b: u64) -> u64 {
18 +
    return a + b;
19 +
}
20 +
21 +
/// Subtract two u64 values.
22 +
fn subU64(a: u64, b: u64) -> u64 {
23 +
    return a - b;
24 +
}
25 +
26 +
/// Multiply two u64 values.
27 +
fn mulU64(a: u64, b: u64) -> u64 {
28 +
    return a * b;
29 +
}
30 +
31 +
/// Left-shift u64.
32 +
fn shlU64(a: u64, b: u64) -> u64 {
33 +
    return a << b;
34 +
}
35 +
36 +
/// Right-shift i64 (arithmetic).
37 +
fn shrI64(a: i64, b: i64) -> i64 {
38 +
    return a >> b;
39 +
}
40 +
41 +
/// Divide i64 values.
42 +
fn divI64(a: i64, b: i64) -> i64 {
43 +
    return a / b;
44 +
}
45 +
46 +
/// Divide u64 values.
47 +
fn divU64(a: u64, b: u64) -> u64 {
48 +
    return a / b;
49 +
}
50 +
51 +
/// Widen i32 to i64.
52 +
fn widenI32ToI64(a: i32) -> i64 {
53 +
    return a as i64;
54 +
}
55 +
56 +
/// Widen u32 to u64.
57 +
fn widenU32ToU64(a: u32) -> u64 {
58 +
    return a as u64;
59 +
}
60 +
61 +
/// Narrow i64 to i32.
62 +
fn narrowI64ToI32(a: i64) -> i32 {
63 +
    return a as i32;
64 +
}
65 +
66 +
/// Narrow u64 to u32.
67 +
fn narrowU64ToU32(a: u64) -> u32 {
68 +
    return a as u32;
69 +
}
lib/std/lang/lower/tests/arith.w64.ril added +83 -0
1 +
fn w64 $addI64(w64 %0, w64 %1) {
2 +
  @entry0
3 +
    add w64 %2 %0 %1;
4 +
    ret %2;
5 +
}
6 +
7 +
fn w64 $subI64(w64 %0, w64 %1) {
8 +
  @entry0
9 +
    sub w64 %2 %0 %1;
10 +
    ret %2;
11 +
}
12 +
13 +
fn w64 $mulI64(w64 %0, w64 %1) {
14 +
  @entry0
15 +
    mul w64 %2 %0 %1;
16 +
    ret %2;
17 +
}
18 +
19 +
fn w64 $addU64(w64 %0, w64 %1) {
20 +
  @entry0
21 +
    add w64 %2 %0 %1;
22 +
    ret %2;
23 +
}
24 +
25 +
fn w64 $subU64(w64 %0, w64 %1) {
26 +
  @entry0
27 +
    sub w64 %2 %0 %1;
28 +
    ret %2;
29 +
}
30 +
31 +
fn w64 $mulU64(w64 %0, w64 %1) {
32 +
  @entry0
33 +
    mul w64 %2 %0 %1;
34 +
    ret %2;
35 +
}
36 +
37 +
fn w64 $shlU64(w64 %0, w64 %1) {
38 +
  @entry0
39 +
    shl w64 %2 %0 %1;
40 +
    ret %2;
41 +
}
42 +
43 +
fn w64 $shrI64(w64 %0, w64 %1) {
44 +
  @entry0
45 +
    sshr w64 %2 %0 %1;
46 +
    ret %2;
47 +
}
48 +
49 +
fn w64 $divI64(w64 %0, w64 %1) {
50 +
  @entry0
51 +
    sdiv w64 %2 %0 %1;
52 +
    ret %2;
53 +
}
54 +
55 +
fn w64 $divU64(w64 %0, w64 %1) {
56 +
  @entry0
57 +
    udiv w64 %2 %0 %1;
58 +
    ret %2;
59 +
}
60 +
61 +
fn w64 $widenI32ToI64(w32 %0) {
62 +
  @entry0
63 +
    sext w32 %1 %0;
64 +
    ret %1;
65 +
}
66 +
67 +
fn w64 $widenU32ToU64(w32 %0) {
68 +
  @entry0
69 +
    zext w32 %1 %0;
70 +
    ret %1;
71 +
}
72 +
73 +
fn w32 $narrowI64ToI32(w64 %0) {
74 +
  @entry0
75 +
    sext w32 %1 %0;
76 +
    ret %1;
77 +
}
78 +
79 +
fn w32 $narrowU64ToU32(w64 %0) {
80 +
  @entry0
81 +
    zext w32 %1 %0;
82 +
    ret %1;
83 +
}
lib/std/lang/parser.rad +16 -6
9 9
use std::lang::strings;
10 10
use std::lang::scanner;
11 11
12 12
/// Maximum `u32` value.
13 13
pub const U32_MAX: u32 = 0xFFFFFFFF;
14 +
/// Maximum representable `u64` value.
15 +
pub const U64_MAX: u64 = 0xFFFFFFFFFFFFFFFF;
14 16
/// Maximum number of fields in a record.
15 17
pub const MAX_RECORD_FIELDS: u32 = 32;
16 18
17 19
/// Maximum number of parser errors before aborting.
18 20
const MAX_ERRORS: u32 = 8;
216 218
        }
217 219
        if start >= text.len {
218 220
            throw failParsing(p, "integer literal prefix must be followed by digits");
219 221
        }
220 222
    }
221 -
    let mut value: u32 = 0;
222 -
223 +
    let mut value: u64 = 0;
224 +
    let radix64: u64 = radix as u64;
223 225
    for i in start..text.len {
224 226
        let ch = text[i];
225 227
        let digit = digitFromAscii(ch, radix) else {
226 228
            throw failParsing(p, "invalid digit in integer literal");
227 229
        };
228 -
        if value > (U32_MAX / radix) {
230 +
        if value > (U64_MAX / radix64) {
229 231
            throw failParsing(p, "integer literal overflow");
230 232
        }
231 -
        value = value * radix;
233 +
        value = value * radix64;
232 234
233 -
        if value > U32_MAX - digit {
235 +
        if value > U64_MAX - (digit as u64) {
234 236
            throw failParsing(p, "integer literal overflow");
235 237
        }
236 -
        value = value + digit;
238 +
        value = value + (digit as u64);
237 239
    }
238 240
    return ast::IntLiteral {
239 241
        text, magnitude: value, radix: radixType, signed, negative,
240 242
    };
241 243
}
1974 1976
        }
1975 1977
        case scanner::TokenKind::U32 => {
1976 1978
            advance(p);
1977 1979
            return nodeTypeInt(p, 4, ast::Signedness::Unsigned);
1978 1980
        }
1981 +
        case scanner::TokenKind::U64 => {
1982 +
            advance(p);
1983 +
            return nodeTypeInt(p, 8, ast::Signedness::Unsigned);
1984 +
        }
1979 1985
        case scanner::TokenKind::I8 => {
1980 1986
            advance(p);
1981 1987
            return nodeTypeInt(p, 1, ast::Signedness::Signed);
1982 1988
        }
1983 1989
        case scanner::TokenKind::I16 => {
1986 1992
        }
1987 1993
        case scanner::TokenKind::I32 => {
1988 1994
            advance(p);
1989 1995
            return nodeTypeInt(p, 4, ast::Signedness::Signed);
1990 1996
        }
1997 +
        case scanner::TokenKind::I64 => {
1998 +
            advance(p);
1999 +
            return nodeTypeInt(p, 8, ast::Signedness::Signed);
2000 +
        }
1991 2001
        case scanner::TokenKind::Bool => {
1992 2002
            advance(p);
1993 2003
            return node(p, ast::NodeValue::TypeSig(ast::TypeSig::Bool));
1994 2004
        }
1995 2005
        case scanner::TokenKind::Void => {
lib/std/lang/parser/tests.rad +3 -1
348 348
    try expectRangeNumbers(node, nil, "5");
349 349
}
350 350
351 351
/// Literals with out-of-range digits for their base are rejected.
352 352
@test fn testParseInvalidIntLiteral() throws (testing::TestError) {
353 -
    try expectNumberLiteralFail("4294967296");
353 +
    // 2^64 overflows u64.
354 +
    try expectNumberLiteralFail("18446744073709551616");
355 +
    try expectNumberLiteralFail("0x10000000000000000");
354 356
    try expectNumberLiteralFail("0x1G");
355 357
    try expectNumberLiteralFail("0b102");
356 358
    try expectNumberLiteralFail("+0x1G");
357 359
}
358 360
lib/std/lang/resolver.rad +49 -30
65 65
66 66
/// Minimum `i32` value.
67 67
const I32_MIN: i32 = -2147483648;
68 68
/// Maximum `i32` value.
69 69
const I32_MAX: i32 = 2147483647;
70 +
/// Minimum `i64` value: -(2^63).
71 +
const I64_MIN: i64 = -9223372036854775808;
72 +
/// Maximum `i64` value: 2^63 - 1.
73 +
const I64_MAX: i64 = 9223372036854775807;
70 74
71 75
/// Size of a pointer in bytes.
72 76
pub const PTR_SIZE: u32 = 8;
73 77
74 78
/// Information about a record or tuple field.
199 203
    /// Types only used during inference.
200 204
    Nil, Undefined, Int,
201 205
    /// Primitive types.
202 206
    Void, Opaque, Never, Bool,
203 207
    /// Integer types.
204 -
    U8, U16, U32, I8, I16, I32,
208 +
    U8, U16, U32, U64, I8, I16, I32, I64,
205 209
    /// Range types, eg. `start..end`.
206 210
    Range {
207 211
        start: ?*Type,
208 212
        end: ?*Type,
209 213
    },
303 307
}
304 308
305 309
/// Integer constant payload.
306 310
pub record ConstInt {
307 311
    /// Absolute magnitude of the value.
308 -
    magnitude: u32,
312 +
    magnitude: u64,
309 313
    /// Bit width of the integer.
310 314
    bits: u8,
311 315
    /// Whether the integer is signed.
312 316
    signed: bool,
313 317
    /// Whether the value is negative (only valid when `signed` is true).
324 328
325 329
/// Integer range metadata for primitive integer types.
326 330
union IntegerRange {
327 331
    Signed {
328 332
        bits: u8,
329 -
        min: i32,
330 -
        max: i32,
331 -
        lim: u32,
333 +
        min: i64,
334 +
        max: i64,
335 +
        lim: u64,
332 336
    },
333 337
    Unsigned {
334 338
        bits: u8,
335 -
        max: u32,
339 +
        max: u64,
336 340
    },
337 341
}
338 342
339 343
/// Diagnostic emitted by the analyzer.
340 344
pub record Error {
1224 1228
}
1225 1229
1226 1230
/// Check if a type is a numeric type.
1227 1231
fn isNumericType(ty: Type) -> bool {
1228 1232
    match ty {
1229 -
        case Type::U8, Type::U16, Type::U32,
1230 -
             Type::I8, Type::I16, Type::I32,
1233 +
        case Type::U8, Type::U16, Type::U32, Type::U64,
1234 +
             Type::I8, Type::I16, Type::I32, Type::I64,
1231 1235
             Type::Int => return true,
1232 1236
        else => return false,
1233 1237
    }
1234 1238
}
1235 1239
1248 1252
        case Type::Void, Type::Never => return Layout { size: 0, alignment: 0 },
1249 1253
        case Type::Bool => return Layout { size: 1, alignment: 1 },
1250 1254
        case Type::U8, Type::I8 => return Layout { size: 1, alignment: 1 },
1251 1255
        case Type::U16, Type::I16 => return Layout { size: 2, alignment: 2 },
1252 1256
        case Type::U32, Type::I32, Type::Int => return Layout { size: 4, alignment: 4 },
1257 +
        case Type::U64, Type::I64 => return Layout { size: 8, alignment: 8 },
1253 1258
        case Type::Pointer { .. } => return Layout { size: PTR_SIZE, alignment: PTR_SIZE },
1254 1259
        case Type::Slice { .. } => return Layout { size: PTR_SIZE * 2, alignment: PTR_SIZE },
1255 1260
        case Type::Array(arr) => return getArrayLayout(arr),
1256 1261
        case Type::Optional(inner) => return getOptionalLayout(*inner),
1257 1262
        case Type::Nominal(info) => return getNominalLayout(*info),
1385 1390
/// Return the representable range for an integer type.
1386 1391
fn integerRange(ty: Type) -> ?IntegerRange {
1387 1392
    match ty {
1388 1393
        case Type::I8 => return IntegerRange::Signed {
1389 1394
            bits: 8,
1390 -
            min: I8_MIN,
1391 -
            max: I8_MAX,
1392 -
            lim: (I8_MAX as u32) + 1,
1395 +
            min: I8_MIN as i64,
1396 +
            max: I8_MAX as i64,
1397 +
            lim: (I8_MAX as u64) + 1,
1393 1398
        },
1394 1399
        case Type::I16 => return IntegerRange::Signed {
1395 1400
            bits: 16,
1396 -
            min: I16_MIN,
1397 -
            max: I16_MAX,
1398 -
            lim: (I16_MAX as u32) + 1,
1401 +
            min: I16_MIN as i64,
1402 +
            max: I16_MAX as i64,
1403 +
            lim: (I16_MAX as u64) + 1,
1399 1404
        },
1400 1405
        case Type::I32 => return IntegerRange::Signed {
1401 1406
            bits: 32,
1402 -
            min: I32_MIN,
1403 -
            max: I32_MAX,
1404 -
            lim: (I32_MAX as u32) + 1,
1407 +
            min: I32_MIN as i64,
1408 +
            max: I32_MAX as i64,
1409 +
            lim: (I32_MAX as u64) + 1,
1410 +
        },
1411 +
        case Type::I64 => return IntegerRange::Signed {
1412 +
            bits: 64,
1413 +
            min: I64_MIN,
1414 +
            max: I64_MAX,
1415 +
            lim: (I64_MAX as u64) + 1,
1405 1416
        },
1406 1417
        case Type::Int => return IntegerRange::Signed {
1407 1418
            bits: 32,
1408 -
            min: I32_MIN,
1409 -
            max: I32_MAX,
1410 -
            lim: (I32_MAX as u32) + 1,
1419 +
            min: I32_MIN as i64,
1420 +
            max: I32_MAX as i64,
1421 +
            lim: (I32_MAX as u64) + 1,
1411 1422
        },
1412 -
        case Type::U8 => return IntegerRange::Unsigned { bits: 8, max: U8_MAX },
1413 -
        case Type::U16 => return IntegerRange::Unsigned { bits: 16, max: U16_MAX },
1414 -
        case Type::U32 => return IntegerRange::Unsigned { bits: 32, max: parser::U32_MAX },
1423 +
        case Type::U8 => return IntegerRange::Unsigned { bits: 8, max: U8_MAX as u64 },
1424 +
        case Type::U16 => return IntegerRange::Unsigned { bits: 16, max: U16_MAX as u64 },
1425 +
        case Type::U32 => return IntegerRange::Unsigned { bits: 32, max: parser::U32_MAX as u64 },
1426 +
        case Type::U64 => return IntegerRange::Unsigned { bits: 64, max: parser::U64_MAX },
1415 1427
        else => return nil,
1416 1428
    }
1417 1429
}
1418 1430
1419 1431
/// Validate that an integer constant fits within the target type's range.
2764 2776
        }
2765 2777
    }
2766 2778
}
2767 2779
2768 2780
/// Construct an integer constant descriptor.
2769 -
fn constInt(magnitude: u32, bits: u8, signed: bool, negative: bool) -> ConstValue {
2781 +
fn constInt(magnitude: u64, bits: u8, signed: bool, negative: bool) -> ConstValue {
2770 2782
    return ConstValue::Int(ConstInt { magnitude, bits, signed, negative });
2771 2783
}
2772 2784
2773 2785
/// Return the constant `u32` value for a slice bound when known.
2774 2786
fn constSliceIndex(self: *mut Resolver, node: *ast::Node) -> ?u32 {
2777 2789
    let case ConstValue::Int(int) = value
2778 2790
        else return nil;
2779 2791
    if int.negative {
2780 2792
        return nil;
2781 2793
    }
2782 -
    return int.magnitude;
2794 +
    return int.magnitude as u32;
2783 2795
}
2784 2796
2785 2797
/// Validates and extracts a non-negative integer constant from a compile-time expression.
2786 2798
///
2787 2799
/// This function ensures that a node represents a valid, non-negative integer constant
2807 2819
        throw emitError(self, node, ErrorKind::NumericLiteralOverflow);
2808 2820
    }
2809 2821
    debug::assert(not int.negative);
2810 2822
    try setNodeType(self, node, Type::U32);
2811 2823
2812 -
    return int.magnitude;
2824 +
    return int.magnitude as u32;
2813 2825
}
2814 2826
2815 2827
/// Check that constructor arguments match record fields.
2816 2828
///
2817 2829
/// Verifies argument count matches field count, and that each argument is
3118 3130
        try visitOptional(self, variantDecl.value, variantType);
3119 3131
        let mut variantTag: u32 = iota;
3120 3132
        if let valueNode = variantDecl.value {
3121 3133
            let case ast::NodeValue::Number(lit) = valueNode.value
3122 3134
                else panic "resolveUnionBody: expected number literal for variant value";
3123 -
            variantTag = lit.magnitude;
3135 +
            variantTag = lit.magnitude as u32;
3124 3136
        }
3125 3137
        iota = variantTag + 1;
3126 3138
        // Create a symbol for this variant.
3127 3139
        let data = SymbolData::Variant { type: variantType, decl: node, ordinal: i, index: variantTag };
3128 3140
        let variantSym = allocSymbol(self, data, variantName, variantNode, 0);
4076 4088
            panic "unreachable: @sliceOf handled above";
4077 4089
        }
4078 4090
    }
4079 4091
    // Record as constant value for constant folding.
4080 4092
    try setNodeConstValue(self, node, ConstValue::Int(ConstInt {
4081 -
        magnitude: value,
4093 +
        magnitude: value as u64,
4082 4094
        bits: 32,
4083 4095
        signed: false,
4084 4096
        negative: false,
4085 4097
    }));
4086 4098
    return try setNodeType(self, node, Type::U32);
4563 4575
            let ty = typeFor(self, node)
4564 4576
                else throw emitError(self, node, ErrorKind::Internal);
4565 4577
            // For unions without payload, store the variant index as a constant.
4566 4578
            if isVoidUnion(ty) {
4567 4579
                try setNodeConstValue(self, node, ConstValue::Int(ConstInt {
4568 -
                    magnitude: index,
4580 +
                    magnitude: index as u64,
4569 4581
                    bits: 32,
4570 4582
                    signed: false,
4571 4583
                    negative: false,
4572 4584
                }));
4573 4585
            }
4604 4616
        case Type::Array(arrayInfo) => {
4605 4617
            let fieldNode = access.child;
4606 4618
            let fieldName = try nodeName(self, fieldNode);
4607 4619
4608 4620
            if mem::eq(fieldName, LEN_FIELD) {
4609 -
                let lengthConst = constInt(arrayInfo.length, 32, false, false);
4621 +
                let lengthConst = constInt(arrayInfo.length as u64, 32, false, false);
4610 4622
                try setNodeConstValue(self, node, lengthConst);
4611 4623
4612 4624
                return try setNodeType(self, node, Type::U32);
4613 4625
            }
4614 4626
            throw emitError(self, node, ErrorKind::ArrayFieldUnknown(fieldName));
5207 5219
                        return Type::U32;
5208 5220
                    } else {
5209 5221
                        return Type::I32;
5210 5222
                    }
5211 5223
                }
5224 +
                case 8 => {
5225 +
                    if sign == ast::Signedness::Unsigned {
5226 +
                        return Type::U64;
5227 +
                    } else {
5228 +
                        return Type::I64;
5229 +
                    }
5230 +
                }
5212 5231
                else => {
5213 5232
                    panic "resolveTypeSig: invalid integer width";
5214 5233
                }
5215 5234
            }
5216 5235
        }
lib/std/lang/resolver/printer.rad +6 -0
66 66
            io::print("u16");
67 67
        }
68 68
        case super::Type::U32 => {
69 69
            io::print("u32");
70 70
        }
71 +
        case super::Type::U64 => {
72 +
            io::print("u64");
73 +
        }
71 74
        case super::Type::I8 => {
72 75
            io::print("i8");
73 76
        }
74 77
        case super::Type::I16 => {
75 78
            io::print("i16");
76 79
        }
77 80
        case super::Type::I32 => {
78 81
            io::print("i32");
79 82
        }
83 +
        case super::Type::I64 => {
84 +
            io::print("i64");
85 +
        }
80 86
        case super::Type::Pointer { target, mutable } => {
81 87
            io::print("*");
82 88
            if mutable {
83 89
                io::print("mut ");
84 90
            }
lib/std/lang/resolver/tests.rad +2 -5
1785 1785
    try expectAnalyzeOk("let x: i16 = -32768;");
1786 1786
    try expectAnalyzeOk("let x: u16 = 0xFFFF;");
1787 1787
    try expectAnalyzeOk("let x: i32 = 2147483647;");
1788 1788
    try expectAnalyzeOk("let x: i32 = -2147483648;");
1789 1789
    try expectAnalyzeOk("let x: u32 = 0xFFFFFFFF;");
1790 +
1790 1791
    try expectAnalyzeOk("const LIMIT: u8 = 0xFF;");
1791 1792
1792 1793
    try expectIntMismatch("let x: i8 = 128;", super::Type::I8);
1793 1794
    try expectIntMismatch("let x: i8 = -129;", super::Type::I8);
1794 1795
    try expectIntMismatch("let x: i8 = 0x80;", super::Type::I8);
1802 1803
    try expectIntMismatch("let x: u16 = -1;", super::Type::U16);
1803 1804
    try expectIntMismatch("let x: i32 = 2147483648;", super::Type::I32);
1804 1805
    try expectIntMismatch("let x: i32 = -2147483649;", super::Type::I32);
1805 1806
    try expectIntMismatch("let x: i32 = 0xFFFFFFFF;", super::Type::I32);
1806 1807
    try expectIntMismatch("let x: u32 = -1;", super::Type::U32);
1808 +
    try expectIntMismatch("let x: u32 = 0x100000000;", super::Type::U32);
1807 1809
    try expectIntMismatch("const LIMIT: u8 = 512;", super::Type::U8);
1808 1810
    try expectIntMismatch("const LIMIT: u8 = -5;", super::Type::U8);
1809 -
1810 -
    {
1811 -
        let parsed: ?*ast::Node = try? parser::tests::parseExprStr("0x100000000");
1812 -
        try testing::expect(parsed == nil);
1813 -
    }
1814 1811
}
1815 1812
1816 1813
@test fn testNilCoercions() throws (testing::TestError) {
1817 1814
    {
1818 1815
        let mut a = testResolver();
lib/std/lang/scanner.rad +6 -2
101 101
102 102
    // Type or function attributes.
103 103
    Pub, Extern, Static,
104 104
105 105
    // Type-related tokens.
106 -
    I8, I16, I32, U8, U16, U32,
106 +
    I8, I16, I32, I64, U8, U16, U32, U64,
107 107
    Void, Opaque, Fn, Bool, Union, Record, As
108 108
}
109 109
110 110
/// Convert a token kind to its string representation.
111 111
pub fn tokenKindToString(kind: TokenKind) -> *[u8] {
198 198
        case TokenKind::Extern => return "Extern",
199 199
        case TokenKind::Static => return "Static",
200 200
        case TokenKind::I8 => return "I8",
201 201
        case TokenKind::I16 => return "I16",
202 202
        case TokenKind::I32 => return "I32",
203 +
        case TokenKind::I64 => return "I64",
203 204
        case TokenKind::U8 => return "U8",
204 205
        case TokenKind::U16 => return "U16",
205 206
        case TokenKind::U32 => return "U32",
207 +
        case TokenKind::U64 => return "U64",
206 208
        case TokenKind::Void => return "Void",
207 209
        case TokenKind::Opaque => return "Opaque",
208 210
        case TokenKind::Fn => return "Fn",
209 211
        case TokenKind::Bool => return "Bool",
210 212
        case TokenKind::Union => return "Union",
220 222
    /// Corresponding token.
221 223
    tok: TokenKind,
222 224
}
223 225
224 226
/// Sorted keyword table for binary search.
225 -
const KEYWORDS: [Keyword; 47] = [
227 +
const KEYWORDS: [Keyword; 49] = [
226 228
    { name: "align", tok: TokenKind::Align },
227 229
    { name: "and", tok: TokenKind::And },
228 230
    { name: "as", tok: TokenKind::As },
229 231
    { name: "bool", tok: TokenKind::Bool },
230 232
    { name: "break", tok: TokenKind::Break },
237 239
    { name: "false", tok: TokenKind::False },
238 240
    { name: "fn", tok: TokenKind::Fn },
239 241
    { name: "for", tok: TokenKind::For },
240 242
    { name: "i16", tok: TokenKind::I16 },
241 243
    { name: "i32", tok: TokenKind::I32 },
244 +
    { name: "i64", tok: TokenKind::I64 },
242 245
    { name: "i8", tok: TokenKind::I8 },
243 246
    { name: "if", tok: TokenKind::If },
244 247
    { name: "in", tok: TokenKind::In },
245 248
    { name: "let", tok: TokenKind::Let },
246 249
    { name: "log", tok: TokenKind::Log },
262 265
    { name: "throws", tok: TokenKind::Throws },
263 266
    { name: "true", tok: TokenKind::True },
264 267
    { name: "try", tok: TokenKind::Try },
265 268
    { name: "u16", tok: TokenKind::U16 },
266 269
    { name: "u32", tok: TokenKind::U32 },
270 +
    { name: "u64", tok: TokenKind::U64 },
267 271
    { name: "u8", tok: TokenKind::U8 },
268 272
    { name: "undefined", tok: TokenKind::Undefined },
269 273
    { name: "union", tok: TokenKind::Union },
270 274
    { name: "use", tok: TokenKind::Use },
271 275
    { name: "void", tok: TokenKind::Void },
lib/std/tests.rad +28 -0
75 75
    let result: *[u8] = fmt::formatI32(-2147483648, &mut buffer[..]);
76 76
    try testing::expect(result.len == 11);
77 77
    try testing::expectBytesEq(result, "-2147483648");
78 78
}
79 79
80 +
@test fn testFormatU64Zero() throws (testing::TestError) {
81 +
    let mut buffer: [u8; 20] = [0; 20];
82 +
    let result: *[u8] = fmt::formatU64(0, &mut buffer[..]);
83 +
    try testing::expect(result.len == 1);
84 +
    try testing::expectBytesEq(result, "0");
85 +
}
86 +
87 +
@test fn testFormatU64Max() throws (testing::TestError) {
88 +
    let mut buffer: [u8; 20] = [0; 20];
89 +
    let result: *[u8] = fmt::formatU64(18446744073709551615, &mut buffer[..]);
90 +
    try testing::expect(result.len == 20);
91 +
    try testing::expectBytesEq(result, "18446744073709551615");
92 +
}
93 +
94 +
@test fn testFormatI64Negative() throws (testing::TestError) {
95 +
    let mut buffer: [u8; 20] = [0; 20];
96 +
    let result: *[u8] = fmt::formatI64(-9876543210, &mut buffer[..]);
97 +
    try testing::expect(result.len == 11);
98 +
    try testing::expectBytesEq(result, "-9876543210");
99 +
}
100 +
101 +
@test fn testFormatI64Min() throws (testing::TestError) {
102 +
    let mut buffer: [u8; 20] = [0; 20];
103 +
    let result: *[u8] = fmt::formatI64(-9223372036854775808, &mut buffer[..]);
104 +
    try testing::expect(result.len == 20);
105 +
    try testing::expectBytesEq(result, "-9223372036854775808");
106 +
}
107 +
80 108
// mem /////////////////////////////////////////////////////////////////////////
81 109
82 110
@test fn testCopyFullSlice() throws (testing::TestError) {
83 111
    let mut xs: [u8; 3] = [1, 2, 3];
84 112
    let mut ys: [u8; 3] = [4, 5, 6];
vim/radiance.vim +1 -1
17 17
" Keywords
18 18
syntax keyword radianceKeyword mod fn return if else while true false and or not case align static
19 19
syntax keyword radianceKeyword pub extern break continue use loop in for match nil undefined
20 20
syntax keyword radianceKeyword let mut as register device const log record union
21 21
syntax keyword radianceKeyword throws throw try catch panic super
22 -
syntax keyword radianceType i8 i16 i32 u8 u16 u32 f32 void bool bit opaque
22 +
syntax keyword radianceType i8 i16 i32 i64 u8 u16 u32 u64 f32 void bool bit opaque
23 23
24 24
" Double-quoted strings
25 25
syntax region radianceString start=/"/ skip=/\\"/ end=/"/ contains=radianceEscape
26 26
" Characters
27 27
syntax region radianceCharacter start=/'/ skip=/\\'|\\\\/ end=/'/ oneline contains=radianceEscape