add run-make test for wasm-exceptions

This commit is contained in:
Jan-Mirko Otter 2023-07-01 19:03:50 +02:00
parent 61441302a2
commit 211946c357
6 changed files with 252 additions and 0 deletions

View File

@ -0,0 +1,12 @@
include ../tools.mk
# only-wasm32-bare
# Add a few command line args to make exceptions work
RUSTC := $(RUSTC) -C llvm-args=-wasm-enable-eh
RUSTC := $(RUSTC) -C target-feature=+exception-handling
RUSTC := $(RUSTC) -C panic=unwind
all:
$(RUSTC) src/lib.rs --target wasm32-unknown-unknown
$(NODE) verify.mjs $(TMPDIR)/lib.wasm

View File

@ -0,0 +1,67 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
#[global_allocator]
static ALLOCATOR: ArenaAllocator = ArenaAllocator::new();
/// Very simple allocator which never deallocates memory
///
/// Based on the example from
/// https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
pub struct ArenaAllocator {
arena: UnsafeCell<Arena>,
}
impl ArenaAllocator {
pub const fn new() -> Self {
Self {
arena: UnsafeCell::new(Arena::new()),
}
}
}
/// Safe because we are singlethreaded
unsafe impl Sync for ArenaAllocator {}
unsafe impl GlobalAlloc for ArenaAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let arena = &mut *self.arena.get();
arena.alloc(layout)
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}
const ARENA_SIZE: usize = 64 * 1024; // more than enough
#[repr(C, align(4096))]
struct Arena {
buf: [u8; ARENA_SIZE], // aligned at 4096
allocated: usize,
}
impl Arena {
pub const fn new() -> Self {
Self {
buf: [0x55; ARENA_SIZE],
allocated: 0,
}
}
pub unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
if layout.align() > 4096 || layout.size() > ARENA_SIZE {
return core::ptr::null_mut();
}
let align_minus_one = layout.align() - 1;
let start = (self.allocated + align_minus_one) & !align_minus_one; // round up
let new_cursor = start + layout.size();
if new_cursor >= ARENA_SIZE {
return core::ptr::null_mut();
}
self.allocated = new_cursor;
self.buf.as_mut_ptr().add(start)
}
}

View File

@ -0,0 +1,60 @@
#![no_std]
#![crate_type = "cdylib"]
// Allow a few unstable features because we create a panic
// runtime for native wasm exceptions from scratch
#![feature(core_intrinsics)]
#![feature(lang_items)]
#![feature(link_llvm_intrinsics)]
#![feature(panic_info_message)]
extern crate alloc;
/// This module allows us to use `Box`, `String`, ... even in no-std
mod arena_alloc;
/// This module allows logging text, even in no-std
mod logging;
/// This module allows exceptions, even in no-std
#[cfg(target_arch = "wasm32")]
mod panicking;
use alloc::boxed::Box;
use alloc::string::String;
struct LogOnDrop;
impl Drop for LogOnDrop {
fn drop(&mut self) {
logging::log_str("Dropped");
}
}
#[allow(unreachable_code)]
#[allow(unconditional_panic)]
#[no_mangle]
pub extern "C" fn start() -> usize {
let data = 0x1234usize as *mut u8; // Something to recognize
unsafe {
core::intrinsics::r#try(|data: *mut u8| {
let _log_on_drop = LogOnDrop;
logging::log_str(&alloc::format!("`r#try` called with ptr {:?}", data));
let x = [12];
let _ = x[4]; // should panic
logging::log_str("This line should not be visible! :(");
}, data, |data, exception| {
let exception = *Box::from_raw(exception as *mut String);
logging::log_str("Caught something!");
logging::log_str(&alloc::format!(" data : {:?}", data));
logging::log_str(&alloc::format!(" exception: {:?}", exception));
});
}
logging::log_str("This program terminates correctly.");
0
}

View File

@ -0,0 +1,9 @@
extern "C" {
fn __log_utf8(ptr: *const u8, size: usize);
}
pub fn log_str(text: &str) {
unsafe {
__log_utf8(text.as_ptr(), text.len());
}
}

View File

@ -0,0 +1,29 @@
#[lang = "eh_personality"]
fn eh_personality() {}
mod internal {
extern "C" {
#[link_name = "llvm.wasm.throw"]
pub fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
}
}
unsafe fn wasm_throw(ptr: *mut u8) -> ! {
internal::wasm_throw(0, ptr);
}
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! {
use alloc::boxed::Box;
use alloc::string::ToString;
let msg = info
.message()
.map(|msg| msg.to_string())
.unwrap_or("(no message)".to_string());
let exception = Box::new(msg.to_string());
unsafe {
let exception_raw = Box::into_raw(exception);
wasm_throw(exception_raw as *mut u8);
}
}

View File

@ -0,0 +1,75 @@
import fs from 'fs';
const dec = new TextDecoder("utf-8");
if (process.argv.length != 3) {
console.log("Usage: node verify.mjs <wasm-file>");
process.exit(0);
}
const wasmfile = process.argv[2];
if (!fs.existsSync(wasmfile)) {
console.log("Error: File not found:", wasmfile);
process.exit(1);
}
const wasmBuffer = fs.readFileSync(wasmfile);
async function main() {
let memory = new ArrayBuffer(0) // will be changed after instantiate
const captured_output = [];
const imports = {
env: {
__log_utf8: (ptr, size) => {
const str = dec.decode(new DataView(memory, ptr, size));
captured_output.push(str);
console.log(str);
}
}
};
const wasmModule = await WebAssembly.instantiate(wasmBuffer, imports);
memory = wasmModule.instance.exports.memory.buffer;
const start = wasmModule.instance.exports.start;
const return_code = start();
console.log("Return-Code:", return_code);
if (return_code !== 0) {
console.error("Expected return code 0");
process.exit(return_code);
}
const expected_output = [
'`r#try` called with ptr 0x1234',
'Dropped',
'Caught something!',
' data : 0x1234',
' exception: "index out of bounds: the len is 1 but the index is 4"',
'This program terminates correctly.',
];
assert_equal(captured_output, expected_output);
}
function assert_equal(captured_output, expected_output) {
if (captured_output.length != expected_output.length) {
console.error("Unexpected number of output lines. Got", captured_output.length, "but expected", expected_output.length);
process.exit(1); // exit with error
}
for (let idx = 0; idx < expected_output.length; ++idx) {
if (captured_output[idx] !== expected_output[idx]) {
console.error("Unexpected output");
console.error("[got] ", captured_output[idx]);
console.error("[expected]", expected_output[idx]);
process.exit(2); // exit with error
}
}
}
await main();