il: rename WordToPtr to MakePtr, print as 'ptr'

07d2c0e8d14991c3cc338c98d3d1fd8ee8061d725c7ad8028df9e320c1f9cda6
Cleaner name: 'ptr %dst %val' reads as 'make a pointer from val'.
Alexis Sellier committed ago 1 parent 24e13086
.ai/RIL.TODO.md +3 -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 -
`WordToPtr` (`wtp`) makes integer-to-pointer crossings explicit.
11 +
`MakePtr` (`ptr`) makes integer-to-pointer crossings explicit.
12 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. Pointer-to-integer
17 +
on Ptr, Call returning Ptr, block params, MakePtr. Pointer-to-integer
18 18
flows are safe and need no dedicated instruction.
19 19
20 20
### unsafe keyword (Phase 5)
21 21
`unsafe fn` and `unsafe { }` blocks. Integer-to-pointer casts
22 22
(`addr as *T`) only allowed in unsafe context. Resolver enforces.
31 31
SSA type checker in `il/verify.rad`. Tracks `Ptr(bound)` per register.
32 32
Checks:
33 33
- Memory op bases are Ptr
34 34
- Static offset access within allocation bounds
35 35
- Elem base is Ptr, result is Ptr(stride)
36 -
- WordToPtr counted as provenance escape
36 +
- MakePtr counted as provenance escape
37 37
38 38
## Remaining gaps
39 39
40 40
### Slice re-slicing uses unchecked `add ptr`
41 41
`&slice[start..end]` offsets the data pointer via `add ptr` with no
lib/std/arch/rv64/isel.rad +2 -2
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 -
        // WordToPtr is a no-op on RV64: both are 64-bit register values.
604 -
        case il::Instr::WordToPtr { dst, val } => {
603 +
        // MakePtr is a no-op on RV64: both are 64-bit register values.
604 +
        case il::Instr::MakePtr { dst, val } => {
605 605
            let rd = getDstReg(s, dst, super::SCRATCH1);
606 606
            let rs = resolveVal(s, super::SCRATCH1, val);
607 607
            emitMv(s, rd, rs);
608 608
        },
609 609
        case il::Instr::Ret { val } => {
lib/std/lang/il.rad +6 -7
67 67
//! | `Load` with `typ: Ptr` | Derived from an existing pointer         |
68 68
//! | `Elem`                 | Bounds-checked element pointer           |
69 69
//! | `BinOp::Add` on `Ptr`  | Pointer arithmetic (derived)             |
70 70
//! | `Call` returning `Ptr`  | Callee-produced pointer                 |
71 71
//! | Block parameter         | Merges pointer values from predecessors  |
72 -
//! | `WordToPtr`            | Explicit escape hatch (trusted code)     |
72 +
//! | `MakePtr`              | Explicit escape hatch (unsafe code)      |
73 73
//!
74 74
//! Any other instruction producing a register used as a memory base is
75 75
//! a verifier error.
76 76
//!
77 77
//! Pointer arithmetic (`BinOp::Add` with `typ: Ptr`) requires exactly
287 287
        /// Destination register, always I32.
288 288
        dst: Reg,
289 289
        /// Source value.
290 290
        val: Val,
291 291
    },
292 -
    /// Convert a word to a pointer: `wtp %dst <val>;`
293 -
    /// The result is Ptr. A future verifier will reject this in
294 -
    /// non-trusted code.
295 -
    WordToPtr { dst: Reg, val: Val },
292 +
    /// Construct a pointer from a word: `ptr %dst <val>;`
293 +
    /// The result is Ptr. Only valid in unsafe code.
294 +
    MakePtr { dst: Reg, val: Val },
296 295
297 296
    ////////////////////
298 297
    // Function calls //
299 298
    ////////////////////
300 299
469 468
        case Instr::Copy { dst, .. } => return dst,
470 469
        case Instr::BinOp { dst, .. } => return dst,
471 470
        case Instr::UnOp { dst, .. } => return dst,
472 471
        case Instr::Zext { dst, .. } => return dst,
473 472
        case Instr::Sext { dst, .. } => return dst,
474 -
        case Instr::WordToPtr { dst, .. } => return dst,
473 +
        case Instr::MakePtr { dst, .. } => return dst,
475 474
        case Instr::Call { dst, .. } => return dst,
476 475
        case Instr::Ecall { dst, .. } => return dst,
477 476
        case Instr::Elem { dst, .. } => return dst,
478 477
        else => return nil,
479 478
    }
515 514
            withReg(a, f, ctx),
516 515
        case Instr::Zext { val, .. } =>
517 516
            withReg(val, f, ctx),
518 517
        case Instr::Sext { val, .. } =>
519 518
            withReg(val, f, ctx),
520 -
        case Instr::WordToPtr { val, .. } =>
519 +
        case Instr::MakePtr { val, .. } =>
521 520
            withReg(val, f, ctx),
522 521
        case Instr::Call { func, args, .. } => {
523 522
            withReg(func, f, ctx);
524 523
            for arg in args {
525 524
                withReg(arg, f, ctx);
lib/std/lang/il/printer.rad +2 -2
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::WordToPtr { dst, val } => {
270 -
            write(out, "wtp ");
269 +
        case super::Instr::MakePtr { dst, val } => {
270 +
            write(out, "ptr ");
271 271
            writeReg(out, a, dst);
272 272
            write(out, " ");
273 273
            writeVal(out, a, val);
274 274
        }
275 275
lib/std/lang/il/verify.rad +16 -16
3 3
//! Walks each function's blocks and checks pointer-provenance rules:
4 4
//!
5 5
//! 1. Every register is assigned a type (W8/W16/W32/W64/Ptr) based on
6 6
//!    its defining instruction.
7 7
//! 2. Load/Store/Blit base registers must have type Ptr.
8 -
//! 3. WordToPtr is flagged as a provenance violation.
8 +
//! 3. MakePtr is flagged as a provenance violation.
9 9
//! 4. BinOp::Add with Ptr type requires exactly one Ptr operand.
10 10
//! 5. Copy(DataSym)/Copy(FnAddr) produces Ptr; Copy(Imm) produces W64.
11 11
//! 6. Reserve always produces Ptr.
12 12
//! 7. Static offset checks: field access at constant offsets is validated
13 13
//!    against the known allocation size when available.
37 37
38 38
/// Result of verifying a program.
39 39
pub record VerifyResult {
40 40
    /// Errors found during verification.
41 41
    errors: *mut [VerifyError],
42 -
    /// Number of WordToPtr instructions found (provenance escapes).
43 -
    wtpCount: u32,
42 +
    /// Number of MakePtr instructions found (provenance escapes).
43 +
    makePtrCount: u32,
44 44
}
45 45
46 46
/// Verify pointer-type safety of an IL program.
47 47
///
48 48
/// Returns a list of errors. An empty list means the program passes
49 -
/// all checks. WordToPtr instructions are counted but not treated as
49 +
/// all checks. MakePtr instructions are counted but not treated as
50 50
/// errors (they are legal in unsafe code).
51 51
pub fn verifyProgram(program: *super::Program, arena: *mut alloc::Arena) -> VerifyResult {
52 52
    let a = alloc::arenaAllocator(arena);
53 53
    let mut errors: *mut [VerifyError] = &mut [];
54 -
    let mut wtpCount: u32 = 0;
54 +
    let mut makePtrCount: u32 = 0;
55 55
56 56
    for func in program.fns {
57 57
        if func.isExtern {
58 58
            continue;
59 59
        }
60 -
        verifyFn(func, &mut errors, &mut wtpCount, a);
60 +
        verifyFn(func, &mut errors, &mut makePtrCount, a);
61 61
    }
62 -
    return VerifyResult { errors, wtpCount };
62 +
    return VerifyResult { errors, makePtrCount };
63 63
}
64 64
65 65
/// Inferred type of an SSA register: either a word width or pointer.
66 66
union RegType {
67 67
    /// Not yet defined.
124 124
125 125
/// Verify a single function.
126 126
fn verifyFn(
127 127
    func: *super::Fn,
128 128
    errors: *mut *mut [VerifyError],
129 -
    wtpCount: *mut u32,
129 +
    makePtrCount: *mut u32,
130 130
    a: alloc::Allocator,
131 131
) {
132 132
    // Type map: regId -> RegType.
133 133
    let mut regTypes: [RegType; MAX_REGS] = undefined;
134 134
    for i in 0..MAX_REGS {
155 155
                instrIdx: instrIdx as u32,
156 156
                regs: &mut regTypes[..],
157 157
                errors,
158 158
                a,
159 159
            };
160 -
            verifyInstr(&mut ctx, instr, wtpCount);
160 +
            verifyInstr(&mut ctx, instr, makePtrCount);
161 161
        }
162 162
    }
163 163
}
164 164
165 165
/// Verify a single instruction: assign dst type and check operands.
166 -
fn verifyInstr(ctx: *mut VerifyCtx, instr: super::Instr, wtpCount: *mut u32) {
166 +
fn verifyInstr(ctx: *mut VerifyCtx, instr: super::Instr, makePtrCount: *mut u32) {
167 167
    match instr {
168 168
        case super::Instr::Reserve { dst, size, .. } => {
169 169
            let bound = immVal(size) else {
170 170
                setReg(ctx.regs, dst, RegType::Ptr(UNKNOWN_BOUND));
171 171
                return;
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::WordToPtr { dst, .. } => {
223 -
            *wtpCount += 1;
222 +
        case super::Instr::MakePtr { dst, .. } => {
223 +
            *makePtrCount += 1;
224 224
            setReg(ctx.regs, dst, RegType::Ptr(UNKNOWN_BOUND));
225 225
        }
226 226
        case super::Instr::Call { retTy, dst, .. } => {
227 227
            if let d = dst {
228 228
                setReg(ctx.regs, d, regTypeFromIl(retTy));
342 342
    }, a);
343 343
}
344 344
345 345
/// Print verification results to stderr.
346 346
pub fn printResult(result: *VerifyResult) {
347 -
    if result.errors.len == 0 and result.wtpCount == 0 {
347 +
    if result.errors.len == 0 and result.makePtrCount == 0 {
348 348
        io::printError("verify: ok\n");
349 349
        return;
350 350
    }
351 351
    let mut buf: [u8; 10] = undefined;
352 352
353 -
    if result.wtpCount > 0 {
353 +
    if result.makePtrCount > 0 {
354 354
        io::printError("verify: ");
355 -
        io::printError(fmt::formatU32(result.wtpCount, &mut buf[..]));
356 -
        io::printError(" WordToPtr instruction(s) found\n");
355 +
        io::printError(fmt::formatU32(result.makePtrCount, &mut buf[..]));
356 +
        io::printError(" MakePtr instruction(s) found\n");
357 357
    }
358 358
    for err in result.errors {
359 359
        io::printError("verify: ");
360 360
        io::printError(err.fnName);
361 361
        io::printError(" block ");
lib/std/lang/lower.rad +2 -2
5896 5896
    }
5897 5897
    // Pointer-to-integer: safe demotion, no instruction needed.
5898 5898
    if isPtrLike(srcType) and not isPtrLike(dstType) {
5899 5899
        return val;
5900 5900
    }
5901 -
    // Word-to-pointer: emit explicit WordToPtr.
5901 +
    // Word-to-pointer: emit explicit MakePtr.
5902 5902
    if not isPtrLike(srcType) and isPtrLike(dstType) {
5903 5903
        let dst = nextReg(self);
5904 -
        emit(self, il::Instr::WordToPtr { dst, val });
5904 +
        emit(self, il::Instr::MakePtr { dst, val });
5905 5905
        return il::Val::Reg(dst);
5906 5906
    }
5907 5907
    return lowerNumericCast(self, val, srcType, dstType);
5908 5908
}
5909 5909
test/tests/unsafe.basic.ril +2 -2
1 1
fn w32 $unsafeRead(w64 %0) {
2 2
  @entry0
3 -
    wtp %1 %0;
3 +
    ptr %1 %0;
4 4
    sload w32 %2 %1 0;
5 5
    ret %2;
6 6
}
7 7
8 8
fn w32 $main() {
12 12
    call w32 %1 $unsafeRead(%0);
13 13
    br.eq w32 %1 42 @assert.ok2 @assert.fail1;
14 14
  @assert.fail1
15 15
    unreachable;
16 16
  @assert.ok2
17 -
    wtp %2 %0;
17 +
    ptr %2 %0;
18 18
    sload w32 %3 %2 0;
19 19
    br.eq w32 %3 42 @assert.ok4 @assert.fail3;
20 20
  @assert.fail3
21 21
    unreachable;
22 22
  @assert.ok4