il: add PtrToWord/WordToPtr conversion instructions
e8882a039bd97307529c9f499e8010916ce36c958f5264499e4d177ab2697306
Phase 3: make pointer/integer boundary crossings explicit in RIL. - il.rad: Add PtrToWord (ptw) and WordToPtr (wtp) instructions - il/printer.rad: Print 'ptw'/'wtp' mnemonics - lower.rad: lowerCast emits PtrToWord for ptr-to-int casts and WordToPtr for int-to-ptr casts instead of silent bitcasts. Add isPtrLike helper to classify pointer-like resolver types. - rv64/isel.rad: Both instructions are no-ops (register move) - Add cast.ptr test covering pointer-to-integer cast
1 parent
aedb2dec
lib/std/arch/rv64/isel.rad
+11 -0
| 598 | 598 | case il::Instr::Sext { typ, dst, val } => { |
|
| 599 | 599 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 600 | 600 | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 601 | 601 | emitSext(s.e, rd, rs, typ); |
|
| 602 | 602 | }, |
|
| 603 | + | // PtrToInt/IntToPtr are no-ops on RV64: both are 64-bit register values. |
|
| 604 | + | case il::Instr::PtrToWord { dst, val } => { |
|
| 605 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 606 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 607 | + | emitMv(s, rd, rs); |
|
| 608 | + | }, |
|
| 609 | + | case il::Instr::WordToPtr { dst, val } => { |
|
| 610 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 611 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 612 | + | emitMv(s, rd, rs); |
|
| 613 | + | }, |
|
| 603 | 614 | case il::Instr::Ret { val } => { |
|
| 604 | 615 | if let v = val { |
|
| 605 | 616 | let rs = resolveVal(s, super::SCRATCH1, v); |
|
| 606 | 617 | emitMv(s, super::A0, rs); |
|
| 607 | 618 | } |
lib/std/lang/il.rad
+13 -0
| 256 | 256 | /// Destination register, always I32. |
|
| 257 | 257 | dst: Reg, |
|
| 258 | 258 | /// Source value. |
|
| 259 | 259 | val: Val, |
|
| 260 | 260 | }, |
|
| 261 | + | /// Convert a pointer to a word: `ptw %dst <val>;` |
|
| 262 | + | /// The result is W64. The word cannot be used as a memory base. |
|
| 263 | + | PtrToWord { dst: Reg, val: Val }, |
|
| 264 | + | /// Convert a word to a pointer: `wtp %dst <val>;` |
|
| 265 | + | /// The result is Ptr. A future verifier will reject this in |
|
| 266 | + | /// non-trusted code. |
|
| 267 | + | WordToPtr { dst: Reg, val: Val }, |
|
| 261 | 268 | ||
| 262 | 269 | //////////////////// |
|
| 263 | 270 | // Function calls // |
|
| 264 | 271 | //////////////////// |
|
| 265 | 272 |
| 413 | 420 | case Instr::Copy { dst, .. } => return dst, |
|
| 414 | 421 | case Instr::BinOp { dst, .. } => return dst, |
|
| 415 | 422 | case Instr::UnOp { dst, .. } => return dst, |
|
| 416 | 423 | case Instr::Zext { dst, .. } => return dst, |
|
| 417 | 424 | case Instr::Sext { dst, .. } => return dst, |
|
| 425 | + | case Instr::PtrToWord { dst, .. } => return dst, |
|
| 426 | + | case Instr::WordToPtr { dst, .. } => return dst, |
|
| 418 | 427 | case Instr::Call { dst, .. } => return dst, |
|
| 419 | 428 | case Instr::Ecall { dst, .. } => return dst, |
|
| 420 | 429 | else => return nil, |
|
| 421 | 430 | } |
|
| 422 | 431 | } |
| 457 | 466 | withReg(a, f, ctx), |
|
| 458 | 467 | case Instr::Zext { val, .. } => |
|
| 459 | 468 | withReg(val, f, ctx), |
|
| 460 | 469 | case Instr::Sext { val, .. } => |
|
| 461 | 470 | withReg(val, f, ctx), |
|
| 471 | + | case Instr::PtrToWord { val, .. } => |
|
| 472 | + | withReg(val, f, ctx), |
|
| 473 | + | case Instr::WordToPtr { val, .. } => |
|
| 474 | + | withReg(val, f, ctx), |
|
| 462 | 475 | case Instr::Call { func, args, .. } => { |
|
| 463 | 476 | withReg(func, f, ctx); |
|
| 464 | 477 | for arg in args { |
|
| 465 | 478 | withReg(arg, f, ctx); |
|
| 466 | 479 | } |
lib/std/lang/il/printer.rad
+12 -0
| 264 | 264 | // Conversion operations. |
|
| 265 | 265 | case super::Instr::Zext { dst, typ, val } => |
|
| 266 | 266 | writeTypedUnaryOp(out, a, "zext", typ, dst, val), |
|
| 267 | 267 | case super::Instr::Sext { dst, typ, val } => |
|
| 268 | 268 | writeTypedUnaryOp(out, a, "sext", typ, dst, val), |
|
| 269 | + | case super::Instr::PtrToWord { dst, val } => { |
|
| 270 | + | write(out, "ptw "); |
|
| 271 | + | writeReg(out, a, dst); |
|
| 272 | + | write(out, " "); |
|
| 273 | + | writeVal(out, a, val); |
|
| 274 | + | } |
|
| 275 | + | case super::Instr::WordToPtr { dst, val } => { |
|
| 276 | + | write(out, "wtp "); |
|
| 277 | + | writeReg(out, a, dst); |
|
| 278 | + | write(out, " "); |
|
| 279 | + | writeVal(out, a, val); |
|
| 280 | + | } |
|
| 269 | 281 | ||
| 270 | 282 | // Call. |
|
| 271 | 283 | case super::Instr::Call { dst, retTy, func, args } => { |
|
| 272 | 284 | write(out, "call "); |
|
| 273 | 285 | writeType(out, retTy); |
lib/std/lang/lower.rad
+22 -0
| 5868 | 5868 | let srcType = try typeOf(self, cast.value); |
|
| 5869 | 5869 | let dstType = try typeOf(self, node); |
|
| 5870 | 5870 | if resolver::typesEqual(srcType, dstType) { |
|
| 5871 | 5871 | return val; |
|
| 5872 | 5872 | } |
|
| 5873 | + | // Pointer-to-integer: emit explicit PtrToInt. |
|
| 5874 | + | if isPtrLike(srcType) and not isPtrLike(dstType) { |
|
| 5875 | + | let dst = nextReg(self); |
|
| 5876 | + | emit(self, il::Instr::PtrToWord { dst, val }); |
|
| 5877 | + | return il::Val::Reg(dst); |
|
| 5878 | + | } |
|
| 5879 | + | // Word-to-pointer: emit explicit WordToPtr. |
|
| 5880 | + | if not isPtrLike(srcType) and isPtrLike(dstType) { |
|
| 5881 | + | let dst = nextReg(self); |
|
| 5882 | + | emit(self, il::Instr::WordToPtr { dst, val }); |
|
| 5883 | + | return il::Val::Reg(dst); |
|
| 5884 | + | } |
|
| 5873 | 5885 | return lowerNumericCast(self, val, srcType, dstType); |
|
| 5874 | 5886 | } |
|
| 5875 | 5887 | ||
| 5888 | + | /// Check whether a resolver type lowers to il::Type::Ptr. |
|
| 5889 | + | fn isPtrLike(t: resolver::Type) -> bool { |
|
| 5890 | + | match t { |
|
| 5891 | + | case resolver::Type::Pointer { .. }, |
|
| 5892 | + | resolver::Type::Fn(_), |
|
| 5893 | + | resolver::Type::TraitObject { .. } => return true, |
|
| 5894 | + | else => return false, |
|
| 5895 | + | } |
|
| 5896 | + | } |
|
| 5897 | + | ||
| 5876 | 5898 | /// Check whether a resolver type is a signed integer type. |
|
| 5877 | 5899 | fn isSignedType(t: resolver::Type) -> bool { |
|
| 5878 | 5900 | match t { |
|
| 5879 | 5901 | case resolver::Type::I8, resolver::Type::I16, resolver::Type::I32, resolver::Type::I64, |
|
| 5880 | 5902 | resolver::Type::Int => return true, |
test/tests/cast.ptr.rad
added
+17 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Cast a pointer to u64. |
|
| 4 | + | fn ptrToU64(p: *i32) -> u64 { |
|
| 5 | + | return p as u64; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | let x: i32 = 42; |
|
| 10 | + | let addr = ptrToU64(&x); |
|
| 11 | + | ||
| 12 | + | // A stack address must be non-zero. |
|
| 13 | + | if addr == 0 { |
|
| 14 | + | return 1; |
|
| 15 | + | } |
|
| 16 | + | return 0; |
|
| 17 | + | } |
test/tests/cast.ptr.ril
added
+18 -0
| 1 | + | fn w64 $ptrToU64(ptr %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | ptw %1 %0; |
|
| 4 | + | ret %1; |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | fn w32 $main() { |
|
| 8 | + | @entry0 |
|
| 9 | + | reserve %0 4 4; |
|
| 10 | + | store w32 42 %0 0; |
|
| 11 | + | call w64 %1 $ptrToU64(%0); |
|
| 12 | + | br.eq w64 %1 0 @then1 @merge2; |
|
| 13 | + | @then1 |
|
| 14 | + | ret 1; |
|
| 15 | + | @merge2 |
|
| 16 | + | ret 0; |
|
| 17 | + | } |
|
| 18 | + |