Expose memory ordering to safe code

673ae4f6c906ace05e15a21ff85400cab3f15027891a7a17e03f90e76b025423
Alexis Sellier committed ago 1 parent 3797da49
lib/std/arch/rv64/isel.rad +3 -0
745 745
            emitMv(s, ecallRd, super::A0);
746 746
        },
747 747
        case il::Instr::Ebreak => {
748 748
            emit::emit(s.e, encode::ebreak());
749 749
        },
750 +
        case il::Instr::MemoryFence => {
751 +
            emit::emit(s.e, encode::fence());
752 +
        },
750 753
    }
751 754
}
752 755
753 756
/// Choose the cheapest canonical representation that preserves the comparison.
754 757
/// RV64 word operations naturally sign-extend, and sign-extension preserves
lib/std/intrinsics.rad +3 -0
7 7
/// emulator addresses and 64-bit native (AMD64) pointers.
8 8
@intrinsic export fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
9 9
10 10
/// Break out of program.
11 11
@intrinsic export fn ebreak();
12 +
13 +
/// Full acquire/release memory fence.
14 +
@intrinsic export fn memoryFence();
lib/std/lang/il.rad +4 -1
293 293
    /// Environment call: `ecall %dst <num> <a0> <a1> <a2> <a3>;`
294 294
    Ecall { dst: Reg, num: Val, a0: Val, a1: Val, a2: Val, a3: Val },
295 295
    /// Environment break: `ebreak;`.
296 296
    /// Triggers a breakpoint exception for debugging.
297 297
    Ebreak,
298 +
    /// Full acquire/release memory fence.
299 +
    MemoryFence,
298 300
}
299 301
300 302
//////////////////////////
301 303
// Blocks and Functions //
302 304
//////////////////////////
495 497
            withReg(a1, f, ctx);
496 498
            withReg(a2, f, ctx);
497 499
            withReg(a3, f, ctx);
498 500
        },
499 501
        case Instr::Unreachable,
500 -
             Instr::Ebreak => {},
502 +
             Instr::Ebreak,
503 +
             Instr::MemoryFence => {},
501 504
    }
502 505
}
503 506
504 507
/// Call callback if value is a register.
505 508
fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
lib/std/lang/il/printer.rad +3 -0
358 358
            writeVal(out, a, a3);
359 359
        }
360 360
        case super::Instr::Ebreak => {
361 361
            write(out, "ebreak");
362 362
        }
363 +
        case super::Instr::MemoryFence => {
364 +
            write(out, "memory-fence");
365 +
        }
363 366
    }
364 367
}
365 368
366 369
/// Write a typed binary operation: `op type %dst %a %b`.
367 370
fn writeTypedBinOp(
lib/std/lang/lower.rad +11 -0
6860 6860
    // Check for known intrinsic names.
6861 6861
    if mem::eq(sym.name, "ecall") {
6862 6862
        return try lowerEcall(self, call);
6863 6863
    } else if mem::eq(sym.name, "ebreak") {
6864 6864
        return try lowerEbreak(self, call);
6865 +
    } else if mem::eq(sym.name, "memoryFence") {
6866 +
        return try lowerMemoryFence(self, call);
6865 6867
    } else {
6866 6868
        throw LowerError::UnknownIntrinsic;
6867 6869
    }
6868 6870
}
6869 6871
6892 6894
    emit(self, il::Instr::Ebreak);
6893 6895
6894 6896
    return il::Val::Undef;
6895 6897
}
6896 6898
6899 +
/// Lower `memoryFence()`.
6900 +
fn lowerMemoryFence(self: *mut FnLowerer, call: ast::Call) -> il::Val throws (LowerError) {
6901 +
    if call.args.len <> 0 {
6902 +
        throw LowerError::InvalidArgCount;
6903 +
    }
6904 +
    emit(self, il::Instr::MemoryFence);
6905 +
    return il::Val::Undef;
6906 +
}
6907 +
6897 6908
/// Resolve callee to an IL value. For direct function calls, use the symbol name.
6898 6909
/// For variables holding function pointers or complex expressions (eg. `array[i]()`),
6899 6910
/// lower the callee expression.
6900 6911
fn lowerCallee(self: *mut FnLowerer, callee: *ast::Node) -> il::Val throws (LowerError) {
6901 6912
    if let sym = resolver::nodeData(self.low.resolver, callee).sym {
test/tests/builtin.memory.fence.rad added +7 -0
1 +
//! returns: 0
2 +
@intrinsic fn memoryFence();
3 +
4 +
@default fn main() -> i32 {
5 +
    memoryFence();
6 +
    return 0;
7 +
}