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