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)
Alexis Sellier committed ago 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 -