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.
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 |