Rollup merge of #113247 - mirkootter:test-wasm-exceptions-nostd, r=Mark-Simulacrum
Add Tests for native wasm exceptions ### Motivation In PR #111322, I added support for native WASM exceptions. I was asked by ``@davidtwco`` to add some tests for it in a follow up PR, which seems like a very good idea. This PR adds three tests for this feature: * codegen: ensure the correct LLVM instructions are used * assembly: ensure the correct WASM instructions are used * run-make: ensure the exception handling works; the WASM code is run using a small nodejs script which demonstrates the exception handling ### Complications There are a few changes beside adding the tests, which were necessary * Tests for the wasm32-unknown-unknown target are (as far as I know) only run on `test-various`. Its docker image uses nodejs-15, which is very old. Experimental support for wasm-exceptions was added in nodejs16. In nodejs 18.12 (LTS), they are stable. - --> increase nodejs to 18.12 in `test-various` * codegen/assembly tests are not performed for the wasm32-unknown-unknown target yet - --> add those to `test-various` as well Due to the last point, some tests are run which have not run before (assembly+codegen tests for wasm32-unknown-unknown). I added `// ignore wasm32-bare` for those which failed ### Local testing I run all tests locally using both `test-various` and `wasm32`. As far as I know, none of the other systems run any test for wasm32 targets.
This commit is contained in:
commit
6a20f681d5
@ -24,7 +24,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
|
||||
qemu-system-x86 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -sL https://nodejs.org/dist/v15.14.0/node-v15.14.0-linux-x64.tar.xz | \
|
||||
RUN curl -sL https://nodejs.org/dist/v18.12.0/node-v18.12.0-linux-x64.tar.xz | \
|
||||
tar -xJ
|
||||
|
||||
# Install 32-bit OVMF files for the i686-unknown-uefi test. This package
|
||||
@ -42,7 +42,7 @@ RUN sh /scripts/sccache.sh
|
||||
|
||||
ENV RUST_CONFIGURE_ARGS \
|
||||
--musl-root-x86_64=/usr/local/x86_64-linux-musl \
|
||||
--set build.nodejs=/node-v15.14.0-linux-x64/bin/node \
|
||||
--set build.nodejs=/node-v18.12.0-linux-x64/bin/node \
|
||||
--set rust.lld
|
||||
|
||||
# Some run-make tests have assertions about code size, and enabling debug
|
||||
@ -58,6 +58,8 @@ ENV WASM_SCRIPT python3 /checkout/x.py --stage 2 test --host='' --target $WASM_T
|
||||
tests/ui \
|
||||
tests/mir-opt \
|
||||
tests/codegen-units \
|
||||
tests/codegen \
|
||||
tests/assembly \
|
||||
library/core
|
||||
|
||||
ENV NVPTX_TARGETS=nvptx64-nvidia-cuda
|
||||
|
@ -3,6 +3,7 @@
|
||||
// ignore-macos slightly different policy on stack protection of arrays
|
||||
// ignore-windows stack check code uses different function names
|
||||
// ignore-nvptx64 stack protector is not supported
|
||||
// ignore-wasm32-bare
|
||||
// [all] compile-flags: -Z stack-protector=all
|
||||
// [strong] compile-flags: -Z stack-protector=strong
|
||||
// [basic] compile-flags: -Z stack-protector=basic
|
||||
|
60
tests/assembly/wasm_exceptions.rs
Normal file
60
tests/assembly/wasm_exceptions.rs
Normal file
@ -0,0 +1,60 @@
|
||||
// only-wasm32-bare
|
||||
// assembly-output: emit-asm
|
||||
// compile-flags: -C target-feature=+exception-handling
|
||||
// compile-flags: -C panic=unwind
|
||||
// compile-flags: -C llvm-args=-wasm-enable-eh
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
extern {
|
||||
fn may_panic();
|
||||
|
||||
#[rustc_nounwind]
|
||||
fn log_number(number: usize);
|
||||
}
|
||||
|
||||
struct LogOnDrop;
|
||||
|
||||
impl Drop for LogOnDrop {
|
||||
fn drop(&mut self) {
|
||||
unsafe { log_number(0); }
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: test_cleanup:
|
||||
#[no_mangle]
|
||||
pub fn test_cleanup() {
|
||||
let _log_on_drop = LogOnDrop;
|
||||
unsafe { may_panic(); }
|
||||
|
||||
// CHECK-NOT: call
|
||||
// CHECK: try
|
||||
// CHECK: call may_panic
|
||||
// CHECK: catch_all
|
||||
// CHECK: rethrow
|
||||
// CHECK: end_try
|
||||
}
|
||||
|
||||
// CHECK-LABEL: test_rtry:
|
||||
#[no_mangle]
|
||||
pub fn test_rtry() {
|
||||
unsafe {
|
||||
core::intrinsics::r#try(|_| {
|
||||
may_panic();
|
||||
}, core::ptr::null_mut(), |data, exception| {
|
||||
log_number(data as usize);
|
||||
log_number(exception as usize);
|
||||
});
|
||||
}
|
||||
|
||||
// CHECK-NOT: call
|
||||
// CHECK: try
|
||||
// CHECK: call may_panic
|
||||
// CHECK: catch
|
||||
// CHECK: call log_number
|
||||
// CHECK: call log_number
|
||||
// CHECK-NOT: rethrow
|
||||
// CHECK: end_try
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
// ignore-s390x
|
||||
// ignore-windows
|
||||
// ignore-loongarch64
|
||||
// ignore-wasm32-bare
|
||||
// See repr-transparent.rs
|
||||
|
||||
#![feature(transparent_unions)]
|
||||
|
51
tests/codegen/wasm_exceptions.rs
Normal file
51
tests/codegen/wasm_exceptions.rs
Normal file
@ -0,0 +1,51 @@
|
||||
// only-wasm32-bare
|
||||
// compile-flags: -C panic=unwind
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
extern {
|
||||
fn may_panic();
|
||||
|
||||
#[rustc_nounwind]
|
||||
fn log_number(number: usize);
|
||||
}
|
||||
|
||||
struct LogOnDrop;
|
||||
|
||||
impl Drop for LogOnDrop {
|
||||
fn drop(&mut self) {
|
||||
unsafe { log_number(0); }
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @test_cleanup() {{.*}} @__gxx_wasm_personality_v0
|
||||
#[no_mangle]
|
||||
pub fn test_cleanup() {
|
||||
let _log_on_drop = LogOnDrop;
|
||||
unsafe { may_panic(); }
|
||||
|
||||
// CHECK-NOT: call
|
||||
// CHECK: invoke void @may_panic()
|
||||
// CHECK: %cleanuppad = cleanuppad within none []
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @test_rtry() {{.*}} @__gxx_wasm_personality_v0
|
||||
#[no_mangle]
|
||||
pub fn test_rtry() {
|
||||
unsafe {
|
||||
core::intrinsics::r#try(|_| {
|
||||
may_panic();
|
||||
}, core::ptr::null_mut(), |data, exception| {
|
||||
log_number(data as usize);
|
||||
log_number(exception as usize);
|
||||
});
|
||||
}
|
||||
|
||||
// CHECK-NOT: call
|
||||
// CHECK: invoke void @may_panic()
|
||||
// CHECK: {{.*}} = catchswitch within none [label {{.*}}] unwind to caller
|
||||
// CHECK: {{.*}} = catchpad within {{.*}} [ptr null]
|
||||
// CHECK: catchret
|
||||
}
|
12
tests/run-make/wasm-exceptions-nostd/Makefile
Normal file
12
tests/run-make/wasm-exceptions-nostd/Makefile
Normal 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
|
67
tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs
Normal file
67
tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs
Normal 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)
|
||||
}
|
||||
}
|
60
tests/run-make/wasm-exceptions-nostd/src/lib.rs
Normal file
60
tests/run-make/wasm-exceptions-nostd/src/lib.rs
Normal 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
|
||||
}
|
9
tests/run-make/wasm-exceptions-nostd/src/logging.rs
Normal file
9
tests/run-make/wasm-exceptions-nostd/src/logging.rs
Normal 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());
|
||||
}
|
||||
}
|
29
tests/run-make/wasm-exceptions-nostd/src/panicking.rs
Normal file
29
tests/run-make/wasm-exceptions-nostd/src/panicking.rs
Normal 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);
|
||||
}
|
||||
}
|
75
tests/run-make/wasm-exceptions-nostd/verify.mjs
Normal file
75
tests/run-make/wasm-exceptions-nostd/verify.mjs
Normal 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();
|
Loading…
Reference in New Issue
Block a user