Add unsafe keyword for pointer-integer boundary crossing

985a1b9224f9ad669ee4b5f0f93fdff28618d6113b1e089bdf0d8a26016b7898
Phase 5: add unsafe fn/block to control integer-to-pointer casts.

Language changes:
- 'unsafe fn' declares an unsafe function
- 'unsafe { ... }' introduces an unsafe block
- Integer-to-pointer casts (e.g. addr as *i32) are only allowed
  inside unsafe functions or unsafe blocks
- The resolver rejects int-to-ptr casts outside unsafe context
  with 'unsafe operation outside of unsafe context'

Implementation:
- scanner.rad: Add Unsafe keyword token
- ast.rad: Add Unsafe attribute variant, UnsafeBlock node
- parser.rad: Parse 'unsafe fn' and 'unsafe { }', add UnsafeBlock
  to expectsSemicolon whitelist
- resolver.rad: Track unsafeDepth, add isUnsafe to FnType, allow
  numeric-to-pointer casts in isValidCast but enforce unsafe context
  in resolveAs. Add UnsafeRequired/UnsafeCallRequired errors.
- lower.rad: Handle UnsafeBlock as transparent to lowering
- il.rad: Update WordToPtr doc referencing unsafe
- Add unsafe.basic test with int-to-ptr roundtrip
Alexis Sellier committed ago 1 parent 2f7beb74
lib/std/lang/ast.rad +5 -0
45 45
    Extern = 0b100,
46 46
    /// Test-only declaration attribute.
47 47
    Test = 0b1000,
48 48
    /// Compiler intrinsic attribute.
49 49
    Intrinsic = 0b10000,
50 +
    /// Unsafe function/method attribute.
51 +
    Unsafe = 0b100000,
50 52
}
51 53
52 54
/// Ordered collection of attribute nodes applied to a declaration.
53 55
pub record Attributes {
54 56
    list: *mut [*Node],
643 645
        /// Argument list.
644 646
        args: *mut [*Node],
645 647
    },
646 648
    /// Block expression or statement body.
647 649
    Block(Block),
650 +
    /// Unsafe block expression: `unsafe { ... }`.
651 +
    /// The inner node is a Block.
652 +
    UnsafeBlock(*Node),
648 653
    /// Call expression, eg. `f(x)`.
649 654
    Call(Call),
650 655
    /// Field access expression (e.g. `foo.bar`).
651 656
    FieldAccess(Access),
652 657
    /// Scope access expression (e.g. `foo::bar`).
lib/std/lang/ast/printer.rad +4 -0
305 305
                case super::Attribute::Pub => return sexpr::sym("@pub"),
306 306
                case super::Attribute::Default => return sexpr::sym("@default"),
307 307
                case super::Attribute::Extern => return sexpr::sym("@extern"),
308 308
                case super::Attribute::Test => return sexpr::sym("@test"),
309 309
                case super::Attribute::Intrinsic => return sexpr::sym("@intrinsic"),
310 +
                case super::Attribute::Unsafe => return sexpr::sym("unsafe"),
310 311
            }
311 312
        }
312 313
        case super::NodeValue::Try(t) => {
313 314
            let mut head = "try";
314 315
            if t.shouldPanic { head = "try!"; }
336 337
        }
337 338
        case super::NodeValue::Block(blk) => {
338 339
            let children = nodeListToExprs(a, &blk.statements[..]);
339 340
            return sexpr::block(a, "block", &[], children);
340 341
        }
342 +
        case super::NodeValue::UnsafeBlock(inner) => {
343 +
            return sexpr::list(a, "unsafe", &[toExpr(a, inner)]);
344 +
        }
341 345
        case super::NodeValue::Let(decl) => {
342 346
            let mut head = "let";
343 347
            if decl.mutable { head = "let-mut"; }
344 348
            return sexpr::list(a, head, &[
345 349
                toExpr(a, decl.ident),
lib/std/lang/lower.rad +8 -0
3697 3697
    }
3698 3698
    match node.value {
3699 3699
        case ast::NodeValue::Block(_) => {
3700 3700
            try lowerBlock(self, node);
3701 3701
        }
3702 +
        case ast::NodeValue::UnsafeBlock(inner) => {
3703 +
            try lowerNode(self, inner);
3704 +
        }
3702 3705
        case ast::NodeValue::Return { value } => {
3703 3706
            try lowerReturnStmt(self, node, value);
3704 3707
        }
3705 3708
        case ast::NodeValue::Throw { expr } => {
3706 3709
            try lowerThrowStmt(self, expr);
6966 6969
        }
6967 6970
        case ast::NodeValue::Block(_) => {
6968 6971
            try lowerBlock(self, node);
6969 6972
            val = il::Val::Undef;
6970 6973
        }
6974 +
        case ast::NodeValue::UnsafeBlock(inner) => {
6975 +
            // Unsafe blocks are transparent to the lowerer; safety is
6976 +
            // enforced by the resolver.
6977 +
            val = try lowerExpr(self, inner);
6978 +
        }
6971 6979
        case ast::NodeValue::ExprStmt(expr) => {
6972 6980
            let _ = expr;
6973 6981
            val = il::Val::Undef;
6974 6982
        }
6975 6983
        // Lower these as statements.
lib/std/lang/parser.rad +29 -0
643 643
             ast::NodeValue::WhileLet(_),
644 644
             ast::NodeValue::For(_),
645 645
             ast::NodeValue::Loop { .. },
646 646
             ast::NodeValue::Match(_),
647 647
             ast::NodeValue::Block(_),
648 +
             ast::NodeValue::UnsafeBlock(_),
648 649
             ast::NodeValue::FnDecl(_),
649 650
             ast::NodeValue::RecordDecl(_),
650 651
             ast::NodeValue::UnionDecl(_),
651 652
             ast::NodeValue::TraitDecl { .. },
652 653
             ast::NodeValue::InstanceDecl { .. },
746 747
            if p.context != Context::Normal {
747 748
                throw failParsing(p, "unexpected `{` in this context");
748 749
            }
749 750
            return try parseRecordLit(p, nil);
750 751
        }
752 +
        case scanner::TokenKind::Unsafe => {
753 +
            advance(p); // Consume `unsafe`.
754 +
            let block = try parseBlock(p);
755 +
            return node(p, ast::NodeValue::UnsafeBlock(block));
756 +
        }
751 757
        else => {
752 758
            throw failParsing(p, "expected expression");
753 759
        }
754 760
    }
755 761
}
867 873
        ));
868 874
    }
869 875
    return node(p, ast::NodeValue::ExprStmt(expr));
870 876
}
871 877
878 +
/// Merge an attribute node into an existing (possibly nil) attribute list.
879 +
fn mergeAttr(p: *mut Parser, attrs: ?ast::Attributes, attrNode: *ast::Node) -> ?ast::Attributes {
880 +
    if let a = attrs {
881 +
        a.list.append(attrNode, p.allocator);
882 +
        return a;
883 +
    }
884 +
    let mut list = ast::nodeSlice(p.arena, 4);
885 +
    list.append(attrNode, p.allocator);
886 +
    return ast::Attributes { list };
887 +
}
888 +
872 889
/// Parse leading attributes attached to the next declaration statement.
873 890
fn parseAttributes(p: *mut Parser) -> ?ast::Attributes {
874 891
    let mut attrs = ast::nodeSlice(p.arena, 4);
875 892
876 893
    if let attr = tryParseAnnotation(p) {
994 1011
            return try parseConst(p, attrs);
995 1012
        }
996 1013
        case scanner::TokenKind::Static => {
997 1014
            return try parseStatic(p, attrs);
998 1015
        }
1016 +
        case scanner::TokenKind::Unsafe => {
1017 +
            advance(p); // Consume `unsafe`.
1018 +
            if check(p, scanner::TokenKind::Fn) {
1019 +
                // Add the Unsafe attribute and parse as a normal fn decl.
1020 +
                let attrNode = nodeAttribute(p, ast::Attribute::Unsafe);
1021 +
                let mut unsafeAttrs = mergeAttr(p, attrs, attrNode);
1022 +
                return try parseFnDecl(p, unsafeAttrs);
1023 +
            }
1024 +
            // `unsafe { ... }` block expression.
1025 +
            let block = try parseBlock(p);
1026 +
            return node(p, ast::NodeValue::UnsafeBlock(block));
1027 +
        }
999 1028
        case scanner::TokenKind::Fn => {
1000 1029
            return try parseFnDecl(p, attrs);
1001 1030
        }
1002 1031
        case scanner::TokenKind::Union => {
1003 1032
            return try parseUnionDecl(p, attrs);
lib/std/lang/resolver.rad +61 -2
259 259
pub record FnType {
260 260
    paramTypes: *[*Type],
261 261
    returnType: *Type,
262 262
    throwList: *[*Type],
263 263
    localCount: u32,
264 +
    /// Whether the function is declared `unsafe`.
265 +
    isUnsafe: bool,
264 266
}
265 267
266 268
/// Describes a type computed during semantic analysis.
267 269
pub union Type {
268 270
    /// A type that couldn't be decided.
572 574
    FnThrowOverflow(CountMismatch),
573 575
    /// Trait declaration has too many methods.
574 576
    TraitMethodOverflow(CountMismatch),
575 577
    /// Instance declaration is missing a required supertrait instance.
576 578
    MissingSupertraitInstance(*[u8]),
579 +
    /// Unsafe operation outside of an `unsafe` block or function.
580 +
    UnsafeRequired,
581 +
    /// Call to an unsafe function outside of an `unsafe` context.
582 +
    UnsafeCallRequired,
577 583
    /// Internal error.
578 584
    Internal,
579 585
}
580 586
581 587
/// Diagnostics returned by the analyzer.
744 750
    loopStack: [LoopCtx; MAX_LOOP_DEPTH],
745 751
    /// Current loop depth, indexes into loop stack.
746 752
    loopDepth: u32,
747 753
    /// Signature of the function currently being analyzed.
748 754
    currentFn: ?*FnType,
755 +
    /// Unsafe context depth. Non-zero inside `unsafe fn` or `unsafe { }` blocks.
756 +
    unsafeDepth: u32,
749 757
    /// Current module being analyzed.
750 758
    currentMod: u16,
751 759
    /// Configuration for semantic analysis.
752 760
    config: Config,
753 761
    /// Unified arena for symbols, scopes, and nominal type.
902 910
        scope: storage.pkgScope,
903 911
        pkgScope: storage.pkgScope,
904 912
        loopStack: undefined,
905 913
        loopDepth: 0,
906 914
        currentFn: nil,
915 +
        unsafeDepth: 0,
907 916
        currentMod: 0,
908 917
        config,
909 918
        arena,
910 919
        nodeData: NodeDataTable { entries: storage.nodeData },
911 920
        types: nil,
2457 2466
            try infer(self, node);
2458 2467
        }
2459 2468
        case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => {
2460 2469
            try resolveInstanceDecl(self, node, traitName, targetType, methods);
2461 2470
        }
2462 -
        case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, attrs } => {
2471 +
        case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, attrs, .. } => {
2463 2472
            try resolveMethodDecl(self, node, name, receiverName, receiverType, sig, attrs);
2464 2473
        }
2465 2474
        else => {
2466 2475
            // Ignore non-declaration nodes.
2467 2476
        }
2568 2577
        },
2569 2578
        case ast::NodeValue::Call(call) => return try resolveCall(self, node, call, CallCtx::Normal),
2570 2579
        case ast::NodeValue::FieldAccess(access) => return try resolveFieldAccess(self, node, access),
2571 2580
        case ast::NodeValue::BinOp(binop) => return try resolveBinOp(self, node, binop),
2572 2581
        case ast::NodeValue::Block(block) => return try resolveBlock(self, node, block),
2582 +
        case ast::NodeValue::UnsafeBlock(inner) => {
2583 +
            self.unsafeDepth += 1;
2584 +
            let ty = try visit(self, inner, hint) catch e {
2585 +
                self.unsafeDepth -= 1;
2586 +
                throw e;
2587 +
            };
2588 +
            self.unsafeDepth -= 1;
2589 +
            return setNodeType(self, node, ty);
2590 +
        }
2573 2591
        case ast::NodeValue::Let(decl) => return try resolveLet(self, node, decl),
2574 2592
        case ast::NodeValue::ConstDecl(decl) => return try resolveConstOrStatic(
2575 2593
            self, node, decl.ident, decl.type, decl.value, decl.attrs, true
2576 2594
        ),
2577 2595
        case ast::NodeValue::StaticDecl(decl) => return try resolveConstOrStatic(
3023 3041
    let mut fnType = FnType {
3024 3042
        paramTypes: &[],
3025 3043
        returnType: allocType(self, retTy),
3026 3044
        throwList: &[],
3027 3045
        localCount: 0,
3046 +
        isUnsafe: ast::hasAttribute(attrMask, ast::Attribute::Unsafe),
3028 3047
    };
3029 3048
    // Enter the function scope to process parameters.
3030 3049
    enterFn(self, node, &fnType);
3031 3050
3032 3051
    if decl.sig.params.len > MAX_FN_PARAMS {
3090 3109
    if let body = decl.body {
3091 3110
        if isExtern {
3092 3111
            throw emitError(self, node, ErrorKind::FnUnexpectedBody);
3093 3112
        }
3094 3113
        enterFn(self, node, fnType); // Enter function scope for body analysis.
3095 -
3114 +
        if fnType.isUnsafe {
3115 +
            self.unsafeDepth += 1;
3116 +
        }
3096 3117
        let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
3118 +
            if fnType.isUnsafe {
3119 +
                self.unsafeDepth -= 1;
3120 +
            }
3097 3121
            exitFn(self);
3098 3122
            throw e;
3099 3123
        };
3100 3124
        if retTy != Type::Void and bodyTy != Type::Never {
3125 +
            if fnType.isUnsafe {
3126 +
                self.unsafeDepth -= 1;
3127 +
            }
3101 3128
            exitFn(self);
3102 3129
            throw emitError(self, body, ErrorKind::FnMissingReturn);
3103 3130
        }
3131 +
        if fnType.isUnsafe {
3132 +
            self.unsafeDepth -= 1;
3133 +
        }
3104 3134
        exitFn(self);
3105 3135
    } else if not isExtern {
3106 3136
        throw emitError(self, node, ErrorKind::FnMissingBody);
3107 3137
    }
3108 3138
}
3349 3379
        let fnType = FnType {
3350 3380
            paramTypes: &paramTypes[..],
3351 3381
            returnType: retType,
3352 3382
            throwList: &throwList[..],
3353 3383
            localCount: 0,
3384 +
            isUnsafe: false,
3354 3385
        };
3355 3386
        traitType.methods.append(TraitMethod {
3356 3387
            name: methodName,
3357 3388
            fnType: allocFnType(self, fnType),
3358 3389
            mutable,
3532 3563
        let fnType = FnType {
3533 3564
            paramTypes: &paramTypes[..],
3534 3565
            returnType: tm.fnType.returnType,
3535 3566
            throwList: tm.fnType.throwList,
3536 3567
            localCount: 0,
3568 +
            isUnsafe: false,
3537 3569
        };
3538 3570
3539 3571
        // Create a symbol for the instance method without binding it into the
3540 3572
        // module scope. Instance methods are dispatched via v-table, so they
3541 3573
        // must not pollute the enclosing scope.
3612 3644
    let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data
3613 3645
        else panic "resolveMethodBody: expected value symbol";
3614 3646
3615 3647
    // Enter function scope.
3616 3648
    enterFn(self, node, fnType);
3649 +
    if fnType.isUnsafe {
3650 +
        self.unsafeDepth += 1;
3651 +
    }
3617 3652
3618 3653
    // Bind the receiver parameter.
3619 3654
    let receiverTy = *fnType.paramTypes[0];
3620 3655
    try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch e {
3656 +
        if fnType.isUnsafe { self.unsafeDepth -= 1; }
3621 3657
        exitFn(self);
3622 3658
        throw e;
3623 3659
    };
3624 3660
    // Bind the remaining parameters from the signature.
3625 3661
    for paramNode in sig.params {
3626 3662
        let paramTy = try infer(self, paramNode) catch e {
3663 +
            if fnType.isUnsafe { self.unsafeDepth -= 1; }
3627 3664
            exitFn(self);
3628 3665
            throw e;
3629 3666
        };
3630 3667
    }
3631 3668
3632 3669
    // Resolve the body.
3633 3670
    let retTy = *fnType.returnType;
3634 3671
    let bodyTy = try checkAssignable(self, body, Type::Void) catch e {
3672 +
        if fnType.isUnsafe { self.unsafeDepth -= 1; }
3635 3673
        exitFn(self);
3636 3674
        throw e;
3637 3675
    };
3638 3676
    if retTy != Type::Void and bodyTy != Type::Never {
3677 +
        if fnType.isUnsafe { self.unsafeDepth -= 1; }
3639 3678
        exitFn(self);
3640 3679
        throw emitError(self, body, ErrorKind::FnMissingReturn);
3641 3680
    }
3681 +
    if fnType.isUnsafe { self.unsafeDepth -= 1; }
3642 3682
    exitFn(self);
3643 3683
}
3644 3684
3645 3685
/// Resolve a standalone method declaration (signature only).
3646 3686
/// Validates the receiver type and registers the method in the method table.
3716 3756
    }
3717 3757
3718 3758
    let retTypePtr = allocType(self, returnType);
3719 3759
    let throwList = &throwTypes[..];
3720 3760
3761 +
    let isUnsafe = ast::hasAttribute(resolveAttributes(self, attrs), ast::Attribute::Unsafe);
3762 +
3721 3763
    // Full function type (receiver + params) for lowering.
3722 3764
    let fullFnType = FnType {
3723 3765
        paramTypes: &paramTypes[..], returnType: retTypePtr, throwList, localCount: 0,
3766 +
        isUnsafe,
3724 3767
    };
3725 3768
    let fnTy = Type::Fn(allocFnType(self, fullFnType));
3726 3769
3727 3770
    // Function type excluding receiver, for call arg checking.
3728 3771
    let checkFnType = FnType {
3729 3772
        paramTypes: &paramTypes[1..], returnType: retTypePtr, throwList, localCount: 0,
3773 +
        isUnsafe,
3730 3774
    };
3731 3775
3732 3776
    // Compute attribute mask.
3733 3777
    let mut attrMask: u32 = 0;
3734 3778
    if let a = attrs {
5882 5926
    if let case Type::Slice { .. } = source {
5883 5927
        // Disallow slice to numeric; slices are fat pointers.
5884 5928
    } else if isAddressType(source) and isNumericType(target) {
5885 5929
        return true;
5886 5930
    }
5931 +
    // Allow numeric to pointer (requires unsafe context; checked in resolveAs).
5932 +
    if isNumericType(source) {
5933 +
        if let case Type::Pointer { .. } = target {
5934 +
            return true;
5935 +
        }
5936 +
    }
5887 5937
    // Allow pointer casts if one side is `*opaque` or target types are castable.
5888 5938
    if let case Type::Pointer { target: sourceTarget, .. } = source {
5889 5939
        if let case Type::Pointer { target: targetTarget, .. } = target {
5890 5940
            if isOpaquePointer(source) or isOpaquePointer(target) {
5891 5941
                return true;
5918 5968
5919 5969
    assert sourceTy != Type::Unknown;
5920 5970
    assert targetTy != Type::Unknown;
5921 5971
5922 5972
    if isValidCast(sourceTy, targetTy) {
5973 +
        // Integer-to-pointer cast requires unsafe context.
5974 +
        if isNumericType(sourceTy) {
5975 +
            if let case Type::Pointer { .. } = targetTy {
5976 +
                if self.unsafeDepth == 0 {
5977 +
                    throw emitError(self, node, ErrorKind::UnsafeRequired);
5978 +
                }
5979 +
            }
5980 +
        }
5923 5981
        // Propagate constant value through the cast, adjusting integer
5924 5982
        // metadata to match the target type.
5925 5983
        if let value = constValueEntry(self, expr.value) {
5926 5984
            if let case ConstValue::Int(i) = value {
5927 5985
                if let range = integerRange(targetTy) {
6552 6610
            let fnType = FnType {
6553 6611
                paramTypes: &paramTypes[..],
6554 6612
                returnType: retType,
6555 6613
                throwList: &throwList[..],
6556 6614
                localCount: 0,
6615 +
                isUnsafe: false,
6557 6616
            };
6558 6617
            return Type::Fn(allocFnType(self, fnType));
6559 6618
        }
6560 6619
        case ast::TypeSig::TraitObject { traitName, mutable } => {
6561 6620
            let sym = try resolveNamePath(self, traitName);
lib/std/lang/resolver/printer.rad +6 -0
483 483
        case super::ErrorKind::TraitMethodOverflow(m) =>
484 484
            printMismatch("too many trait methods", "maximum", m),
485 485
        case super::ErrorKind::MissingSupertraitInstance(name) => {
486 486
            printQuoted("missing instance for supertrait '", name);
487 487
        }
488 +
        case super::ErrorKind::UnsafeRequired => {
489 +
            io::print("unsafe operation outside of unsafe context");
490 +
        }
491 +
        case super::ErrorKind::UnsafeCallRequired => {
492 +
            io::print("call to unsafe function requires unsafe context");
493 +
        }
488 494
        case super::ErrorKind::Internal => {
489 495
            io::print("internal compiler error");
490 496
        }
491 497
        case super::ErrorKind::RecordFieldOutOfOrder { .. } => {
492 498
            io::print("record field out of order");
lib/std/lang/scanner.rad +6 -2
102 102
    // Trait-related tokens.
103 103
    Trait, Instance,
104 104
105 105
    // Type-related tokens.
106 106
    I8, I16, I32, I64, U8, U16, U32, U64,
107 -
    Opaque, Fn, Bool, Union, Record, As
107 +
    Opaque, Fn, Bool, Union, Record, As,
108 +
109 +
    // Safety.
110 +
    Unsafe,
108 111
}
109 112
110 113
/// A reserved keyword.
111 114
record Keyword {
112 115
    /// Keyword string.
114 117
    /// Corresponding token.
115 118
    tok: TokenKind,
116 119
}
117 120
118 121
/// Sorted keyword table for binary search.
119 -
const KEYWORDS: [Keyword; 51] = [
122 +
const KEYWORDS: [Keyword; 52] = [
120 123
    { name: "align", tok: TokenKind::Align },
121 124
    { name: "and", tok: TokenKind::And },
122 125
    { name: "as", tok: TokenKind::As },
123 126
    { name: "assert", tok: TokenKind::Assert },
124 127
    { name: "bool", tok: TokenKind::Bool },
164 167
    { name: "u32", tok: TokenKind::U32 },
165 168
    { name: "u64", tok: TokenKind::U64 },
166 169
    { name: "u8", tok: TokenKind::U8 },
167 170
    { name: "undefined", tok: TokenKind::Undefined },
168 171
    { name: "union", tok: TokenKind::Union },
172 +
    { name: "unsafe", tok: TokenKind::Unsafe },
169 173
    { name: "use", tok: TokenKind::Use },
170 174
    { name: "while", tok: TokenKind::While },
171 175
];
172 176
173 177
/// Describes where source code originated from.
test/tests/unsafe.basic.rad added +28 -0
1 +
//! returns: 0
2 +
3 +
/// An unsafe function that converts an address to a pointer and reads it.
4 +
unsafe fn unsafeRead(addr: u64) -> i32 {
5 +
    let p = addr as *i32;
6 +
    return *p;
7 +
}
8 +
9 +
@default fn main() -> i32 {
10 +
    let x: i32 = 42;
11 +
    let addr = &x as u64;
12 +
13 +
    // Calling unsafe fn and int-to-ptr cast inside unsafe block.
14 +
    let mut val: i32 = 0;
15 +
    unsafe {
16 +
        val = unsafeRead(addr);
17 +
    }
18 +
    assert val == 42;
19 +
20 +
    // Direct int-to-ptr cast in unsafe block.
21 +
    let mut p: *i32 = &x;
22 +
    unsafe {
23 +
        p = addr as *i32;
24 +
    }
25 +
    assert *p == 42;
26 +
27 +
    return 0;
28 +
}
test/tests/unsafe.basic.ril added +26 -0
1 +
fn w32 $unsafeRead(w64 %0) {
2 +
  @entry0
3 +
    wtp %1 %0;
4 +
    sload w32 %2 %1 0;
5 +
    ret %2;
6 +
}
7 +
8 +
fn w32 $main() {
9 +
  @entry0
10 +
    reserve %0 4 4;
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;
15 +
  @assert.fail1
16 +
    unreachable;
17 +
  @assert.ok2
18 +
    wtp %3 %1;
19 +
    sload w32 %4 %3 0;
20 +
    br.eq w32 %4 42 @assert.ok4 @assert.fail3;
21 +
  @assert.fail3
22 +
    unreachable;
23 +
  @assert.ok4
24 +
    ret 0;
25 +
}
26 +