il: remove PtrToWord instruction
24e1308647d3b2657f06691532195397efd9154d90190aeb4c1642d112db3f5a
Pointer-to-integer is a safe demotion -- a Ptr value flowing into a W64 context loses its pointer status but can't cause memory unsafety. No dedicated instruction needed. - Remove PtrToWord from Instr, printer, verifier, backend - Lowerer returns the value as-is for ptr-to-int casts - Update test snapshots (ptw gone, just ret %0)
1 parent
01ef2d04
.ai/RIL.TODO.md
+4 -3
| 6 | 6 | `il::Type::Ptr` distinct from `W64`. All memory ops use `Ptr` for base |
|
| 7 | 7 | registers. Loads/stores of pointer values use `Ptr` as the type. |
|
| 8 | 8 | Backend treats `Ptr` identically to `W64` for codegen. |
|
| 9 | 9 | ||
| 10 | 10 | ### Explicit casts (Phase 3) |
|
| 11 | - | `PtrToWord` (`ptw`) and `WordToPtr` (`wtp`) make pointer/integer |
|
| 12 | - | boundary crossings explicit. No silent bitcasts. |
|
| 11 | + | `WordToPtr` (`wtp`) makes integer-to-pointer crossings explicit. |
|
| 12 | + | Pointer-to-integer is a safe demotion (no instruction needed). |
|
| 13 | 13 | ||
| 14 | 14 | ### Provenance rules (Phase 4) |
|
| 15 | 15 | Closed set of pointer-producing instructions documented in `il.rad`: |
|
| 16 | 16 | Reserve, Copy(DataSym/FnAddr), Load with Ptr type, Elem, BinOp::Add |
|
| 17 | - | on Ptr, Call returning Ptr, block params, WordToPtr. |
|
| 17 | + | on Ptr, Call returning Ptr, block params, WordToPtr. Pointer-to-integer |
|
| 18 | + | flows are safe and need no dedicated instruction. |
|
| 18 | 19 | ||
| 19 | 20 | ### unsafe keyword (Phase 5) |
|
| 20 | 21 | `unsafe fn` and `unsafe { }` blocks. Integer-to-pointer casts |
|
| 21 | 22 | (`addr as *T`) only allowed in unsafe context. Resolver enforces. |
|
| 22 | 23 |
lib/std/arch/rv64/isel.rad
+1 -6
| 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 | - | }, |
|
| 603 | + | // WordToPtr is a no-op on RV64: both are 64-bit register values. |
|
| 609 | 604 | case il::Instr::WordToPtr { dst, val } => { |
|
| 610 | 605 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 611 | 606 | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 612 | 607 | emitMv(s, rd, rs); |
|
| 613 | 608 | }, |
lib/std/lang/il.rad
+1 -8
| 70 | 70 | //! | `Call` returning `Ptr` | Callee-produced pointer | |
|
| 71 | 71 | //! | Block parameter | Merges pointer values from predecessors | |
|
| 72 | 72 | //! | `WordToPtr` | Explicit escape hatch (trusted code) | |
|
| 73 | 73 | //! |
|
| 74 | 74 | //! Any other instruction producing a register used as a memory base is |
|
| 75 | - | //! a verifier error. `PtrToWord` converts a pointer to `W64`; the |
|
| 76 | - | //! resulting integer cannot be used as a memory base. |
|
| 75 | + | //! a verifier error. |
|
| 77 | 76 | //! |
|
| 78 | 77 | //! Pointer arithmetic (`BinOp::Add` with `typ: Ptr`) requires exactly |
|
| 79 | 78 | //! one `Ptr` operand and one integer operand. The result is `Ptr` with |
|
| 80 | 79 | //! the same provenance as the pointer operand. |
|
| 81 | 80 | //! |
| 288 | 287 | /// Destination register, always I32. |
|
| 289 | 288 | dst: Reg, |
|
| 290 | 289 | /// Source value. |
|
| 291 | 290 | val: Val, |
|
| 292 | 291 | }, |
|
| 293 | - | /// Convert a pointer to a word: `ptw %dst <val>;` |
|
| 294 | - | /// The result is W64. The word cannot be used as a memory base. |
|
| 295 | - | PtrToWord { dst: Reg, val: Val }, |
|
| 296 | 292 | /// Convert a word to a pointer: `wtp %dst <val>;` |
|
| 297 | 293 | /// The result is Ptr. A future verifier will reject this in |
|
| 298 | 294 | /// non-trusted code. |
|
| 299 | 295 | WordToPtr { dst: Reg, val: Val }, |
|
| 300 | 296 |
| 473 | 469 | case Instr::Copy { dst, .. } => return dst, |
|
| 474 | 470 | case Instr::BinOp { dst, .. } => return dst, |
|
| 475 | 471 | case Instr::UnOp { dst, .. } => return dst, |
|
| 476 | 472 | case Instr::Zext { dst, .. } => return dst, |
|
| 477 | 473 | case Instr::Sext { dst, .. } => return dst, |
|
| 478 | - | case Instr::PtrToWord { dst, .. } => return dst, |
|
| 479 | 474 | case Instr::WordToPtr { dst, .. } => return dst, |
|
| 480 | 475 | case Instr::Call { dst, .. } => return dst, |
|
| 481 | 476 | case Instr::Ecall { dst, .. } => return dst, |
|
| 482 | 477 | case Instr::Elem { dst, .. } => return dst, |
|
| 483 | 478 | else => return nil, |
| 520 | 515 | withReg(a, f, ctx), |
|
| 521 | 516 | case Instr::Zext { val, .. } => |
|
| 522 | 517 | withReg(val, f, ctx), |
|
| 523 | 518 | case Instr::Sext { val, .. } => |
|
| 524 | 519 | withReg(val, f, ctx), |
|
| 525 | - | case Instr::PtrToWord { val, .. } => |
|
| 526 | - | withReg(val, f, ctx), |
|
| 527 | 520 | case Instr::WordToPtr { val, .. } => |
|
| 528 | 521 | withReg(val, f, ctx), |
|
| 529 | 522 | case Instr::Call { func, args, .. } => { |
|
| 530 | 523 | withReg(func, f, ctx); |
|
| 531 | 524 | for arg in args { |
lib/std/lang/il/printer.rad
+0 -6
| 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 | 269 | case super::Instr::WordToPtr { dst, val } => { |
|
| 276 | 270 | write(out, "wtp "); |
|
| 277 | 271 | writeReg(out, a, dst); |
|
| 278 | 272 | write(out, " "); |
|
| 279 | 273 | writeVal(out, a, val); |
lib/std/lang/il/verify.rad
+0 -2
| 217 | 217 | setReg(ctx.regs, dst, RegType::Word(typ)), |
|
| 218 | 218 | case super::Instr::Zext { dst, .. } => |
|
| 219 | 219 | setReg(ctx.regs, dst, RegType::Word(super::Type::W64)), |
|
| 220 | 220 | case super::Instr::Sext { dst, .. } => |
|
| 221 | 221 | setReg(ctx.regs, dst, RegType::Word(super::Type::W64)), |
|
| 222 | - | case super::Instr::PtrToWord { dst, .. } => |
|
| 223 | - | setReg(ctx.regs, dst, RegType::Word(super::Type::W64)), |
|
| 224 | 222 | case super::Instr::WordToPtr { dst, .. } => { |
|
| 225 | 223 | *wtpCount += 1; |
|
| 226 | 224 | setReg(ctx.regs, dst, RegType::Ptr(UNKNOWN_BOUND)); |
|
| 227 | 225 | } |
|
| 228 | 226 | case super::Instr::Call { retTy, dst, .. } => { |
lib/std/lang/lower.rad
+2 -4
| 5892 | 5892 | let srcType = try typeOf(self, cast.value); |
|
| 5893 | 5893 | let dstType = try typeOf(self, node); |
|
| 5894 | 5894 | if resolver::typesEqual(srcType, dstType) { |
|
| 5895 | 5895 | return val; |
|
| 5896 | 5896 | } |
|
| 5897 | - | // Pointer-to-integer: emit explicit PtrToInt. |
|
| 5897 | + | // Pointer-to-integer: safe demotion, no instruction needed. |
|
| 5898 | 5898 | if isPtrLike(srcType) and not isPtrLike(dstType) { |
|
| 5899 | - | let dst = nextReg(self); |
|
| 5900 | - | emit(self, il::Instr::PtrToWord { dst, val }); |
|
| 5901 | - | return il::Val::Reg(dst); |
|
| 5899 | + | return val; |
|
| 5902 | 5900 | } |
|
| 5903 | 5901 | // Word-to-pointer: emit explicit WordToPtr. |
|
| 5904 | 5902 | if not isPtrLike(srcType) and isPtrLike(dstType) { |
|
| 5905 | 5903 | let dst = nextReg(self); |
|
| 5906 | 5904 | emit(self, il::Instr::WordToPtr { dst, val }); |
test/tests/cast.ptr.ril
+1 -3
| 1 | 1 | fn w64 $ptrToU64(ptr %0) { |
|
| 2 | 2 | @entry0 |
|
| 3 | - | ptw %1 %0; |
|
| 4 | - | ret %1; |
|
| 3 | + | ret %0; |
|
| 5 | 4 | } |
|
| 6 | 5 | ||
| 7 | 6 | fn w32 $main() { |
|
| 8 | 7 | @entry0 |
|
| 9 | 8 | reserve %0 4 4; |
| 13 | 12 | @then1 |
|
| 14 | 13 | ret 1; |
|
| 15 | 14 | @merge2 |
|
| 16 | 15 | ret 0; |
|
| 17 | 16 | } |
|
| 18 | - |
test/tests/unsafe.basic.ril
+5 -7
| 7 | 7 | ||
| 8 | 8 | fn w32 $main() { |
|
| 9 | 9 | @entry0 |
|
| 10 | 10 | reserve %0 4 4; |
|
| 11 | 11 | store w32 42 %0 0; |
|
| 12 | - | ptw %1 %0; |
|
| 13 | - | call w32 %2 $unsafeRead(%1); |
|
| 14 | - | br.eq w32 %2 42 @assert.ok2 @assert.fail1; |
|
| 12 | + | call w32 %1 $unsafeRead(%0); |
|
| 13 | + | br.eq w32 %1 42 @assert.ok2 @assert.fail1; |
|
| 15 | 14 | @assert.fail1 |
|
| 16 | 15 | unreachable; |
|
| 17 | 16 | @assert.ok2 |
|
| 18 | - | wtp %3 %1; |
|
| 19 | - | sload w32 %4 %3 0; |
|
| 20 | - | br.eq w32 %4 42 @assert.ok4 @assert.fail3; |
|
| 17 | + | wtp %2 %0; |
|
| 18 | + | sload w32 %3 %2 0; |
|
| 19 | + | br.eq w32 %3 42 @assert.ok4 @assert.fail3; |
|
| 21 | 20 | @assert.fail3 |
|
| 22 | 21 | unreachable; |
|
| 23 | 22 | @assert.ok4 |
|
| 24 | 23 | ret 0; |
|
| 25 | 24 | } |
|
| 26 | - |