Skip to content

Commit 6a20f68

Browse files
authored
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.
2 parents 4406a92 + a0bd381 commit 6a20f68

File tree

11 files changed

+369
-2
lines changed

11 files changed

+369
-2
lines changed

src/ci/docker/host-x86_64/test-various/Dockerfile

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
2424
qemu-system-x86 \
2525
&& rm -rf /var/lib/apt/lists/*
2626

27-
RUN curl -sL https://nodejs.org/dist/v15.14.0/node-v15.14.0-linux-x64.tar.xz | \
27+
RUN curl -sL https://nodejs.org/dist/v18.12.0/node-v18.12.0-linux-x64.tar.xz | \
2828
tar -xJ
2929

3030
# Install 32-bit OVMF files for the i686-unknown-uefi test. This package
@@ -42,7 +42,7 @@ RUN sh /scripts/sccache.sh
4242

4343
ENV RUST_CONFIGURE_ARGS \
4444
--musl-root-x86_64=/usr/local/x86_64-linux-musl \
45-
--set build.nodejs=/node-v15.14.0-linux-x64/bin/node \
45+
--set build.nodejs=/node-v18.12.0-linux-x64/bin/node \
4646
--set rust.lld
4747

4848
# 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
5858
tests/ui \
5959
tests/mir-opt \
6060
tests/codegen-units \
61+
tests/codegen \
62+
tests/assembly \
6163
library/core
6264

6365
ENV NVPTX_TARGETS=nvptx64-nvidia-cuda

tests/assembly/stack-protector/stack-protector-heuristics-effect.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// ignore-macos slightly different policy on stack protection of arrays
44
// ignore-windows stack check code uses different function names
55
// ignore-nvptx64 stack protector is not supported
6+
// ignore-wasm32-bare
67
// [all] compile-flags: -Z stack-protector=all
78
// [strong] compile-flags: -Z stack-protector=strong
89
// [basic] compile-flags: -Z stack-protector=basic

tests/assembly/wasm_exceptions.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// only-wasm32-bare
2+
// assembly-output: emit-asm
3+
// compile-flags: -C target-feature=+exception-handling
4+
// compile-flags: -C panic=unwind
5+
// compile-flags: -C llvm-args=-wasm-enable-eh
6+
7+
#![crate_type = "lib"]
8+
#![feature(core_intrinsics)]
9+
#![feature(rustc_attrs)]
10+
11+
extern {
12+
fn may_panic();
13+
14+
#[rustc_nounwind]
15+
fn log_number(number: usize);
16+
}
17+
18+
struct LogOnDrop;
19+
20+
impl Drop for LogOnDrop {
21+
fn drop(&mut self) {
22+
unsafe { log_number(0); }
23+
}
24+
}
25+
26+
// CHECK-LABEL: test_cleanup:
27+
#[no_mangle]
28+
pub fn test_cleanup() {
29+
let _log_on_drop = LogOnDrop;
30+
unsafe { may_panic(); }
31+
32+
// CHECK-NOT: call
33+
// CHECK: try
34+
// CHECK: call may_panic
35+
// CHECK: catch_all
36+
// CHECK: rethrow
37+
// CHECK: end_try
38+
}
39+
40+
// CHECK-LABEL: test_rtry:
41+
#[no_mangle]
42+
pub fn test_rtry() {
43+
unsafe {
44+
core::intrinsics::r#try(|_| {
45+
may_panic();
46+
}, core::ptr::null_mut(), |data, exception| {
47+
log_number(data as usize);
48+
log_number(exception as usize);
49+
});
50+
}
51+
52+
// CHECK-NOT: call
53+
// CHECK: try
54+
// CHECK: call may_panic
55+
// CHECK: catch
56+
// CHECK: call log_number
57+
// CHECK: call log_number
58+
// CHECK-NOT: rethrow
59+
// CHECK: end_try
60+
}

tests/codegen/repr-transparent-aggregates-1.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// ignore-s390x
1212
// ignore-windows
1313
// ignore-loongarch64
14+
// ignore-wasm32-bare
1415
// See repr-transparent.rs
1516

1617
#![feature(transparent_unions)]

tests/codegen/wasm_exceptions.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// only-wasm32-bare
2+
// compile-flags: -C panic=unwind
3+
4+
#![crate_type = "lib"]
5+
#![feature(core_intrinsics)]
6+
#![feature(rustc_attrs)]
7+
8+
extern {
9+
fn may_panic();
10+
11+
#[rustc_nounwind]
12+
fn log_number(number: usize);
13+
}
14+
15+
struct LogOnDrop;
16+
17+
impl Drop for LogOnDrop {
18+
fn drop(&mut self) {
19+
unsafe { log_number(0); }
20+
}
21+
}
22+
23+
// CHECK-LABEL: @test_cleanup() {{.*}} @__gxx_wasm_personality_v0
24+
#[no_mangle]
25+
pub fn test_cleanup() {
26+
let _log_on_drop = LogOnDrop;
27+
unsafe { may_panic(); }
28+
29+
// CHECK-NOT: call
30+
// CHECK: invoke void @may_panic()
31+
// CHECK: %cleanuppad = cleanuppad within none []
32+
}
33+
34+
// CHECK-LABEL: @test_rtry() {{.*}} @__gxx_wasm_personality_v0
35+
#[no_mangle]
36+
pub fn test_rtry() {
37+
unsafe {
38+
core::intrinsics::r#try(|_| {
39+
may_panic();
40+
}, core::ptr::null_mut(), |data, exception| {
41+
log_number(data as usize);
42+
log_number(exception as usize);
43+
});
44+
}
45+
46+
// CHECK-NOT: call
47+
// CHECK: invoke void @may_panic()
48+
// CHECK: {{.*}} = catchswitch within none [label {{.*}}] unwind to caller
49+
// CHECK: {{.*}} = catchpad within {{.*}} [ptr null]
50+
// CHECK: catchret
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
include ../tools.mk
2+
3+
# only-wasm32-bare
4+
5+
# Add a few command line args to make exceptions work
6+
RUSTC := $(RUSTC) -C llvm-args=-wasm-enable-eh
7+
RUSTC := $(RUSTC) -C target-feature=+exception-handling
8+
RUSTC := $(RUSTC) -C panic=unwind
9+
10+
all:
11+
$(RUSTC) src/lib.rs --target wasm32-unknown-unknown
12+
$(NODE) verify.mjs $(TMPDIR)/lib.wasm
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use core::alloc::{GlobalAlloc, Layout};
2+
use core::cell::UnsafeCell;
3+
4+
#[global_allocator]
5+
static ALLOCATOR: ArenaAllocator = ArenaAllocator::new();
6+
7+
/// Very simple allocator which never deallocates memory
8+
///
9+
/// Based on the example from
10+
/// https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html
11+
pub struct ArenaAllocator {
12+
arena: UnsafeCell<Arena>,
13+
}
14+
15+
impl ArenaAllocator {
16+
pub const fn new() -> Self {
17+
Self {
18+
arena: UnsafeCell::new(Arena::new()),
19+
}
20+
}
21+
}
22+
23+
/// Safe because we are singlethreaded
24+
unsafe impl Sync for ArenaAllocator {}
25+
26+
unsafe impl GlobalAlloc for ArenaAllocator {
27+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
28+
let arena = &mut *self.arena.get();
29+
arena.alloc(layout)
30+
}
31+
32+
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
33+
}
34+
35+
const ARENA_SIZE: usize = 64 * 1024; // more than enough
36+
37+
#[repr(C, align(4096))]
38+
struct Arena {
39+
buf: [u8; ARENA_SIZE], // aligned at 4096
40+
allocated: usize,
41+
}
42+
43+
impl Arena {
44+
pub const fn new() -> Self {
45+
Self {
46+
buf: [0x55; ARENA_SIZE],
47+
allocated: 0,
48+
}
49+
}
50+
51+
pub unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
52+
if layout.align() > 4096 || layout.size() > ARENA_SIZE {
53+
return core::ptr::null_mut();
54+
}
55+
56+
let align_minus_one = layout.align() - 1;
57+
let start = (self.allocated + align_minus_one) & !align_minus_one; // round up
58+
let new_cursor = start + layout.size();
59+
60+
if new_cursor >= ARENA_SIZE {
61+
return core::ptr::null_mut();
62+
}
63+
64+
self.allocated = new_cursor;
65+
self.buf.as_mut_ptr().add(start)
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#![no_std]
2+
#![crate_type = "cdylib"]
3+
4+
// Allow a few unstable features because we create a panic
5+
// runtime for native wasm exceptions from scratch
6+
7+
#![feature(core_intrinsics)]
8+
#![feature(lang_items)]
9+
#![feature(link_llvm_intrinsics)]
10+
#![feature(panic_info_message)]
11+
12+
extern crate alloc;
13+
14+
/// This module allows us to use `Box`, `String`, ... even in no-std
15+
mod arena_alloc;
16+
17+
/// This module allows logging text, even in no-std
18+
mod logging;
19+
20+
/// This module allows exceptions, even in no-std
21+
#[cfg(target_arch = "wasm32")]
22+
mod panicking;
23+
24+
use alloc::boxed::Box;
25+
use alloc::string::String;
26+
27+
struct LogOnDrop;
28+
29+
impl Drop for LogOnDrop {
30+
fn drop(&mut self) {
31+
logging::log_str("Dropped");
32+
}
33+
}
34+
35+
#[allow(unreachable_code)]
36+
#[allow(unconditional_panic)]
37+
#[no_mangle]
38+
pub extern "C" fn start() -> usize {
39+
let data = 0x1234usize as *mut u8; // Something to recognize
40+
41+
unsafe {
42+
core::intrinsics::r#try(|data: *mut u8| {
43+
let _log_on_drop = LogOnDrop;
44+
45+
logging::log_str(&alloc::format!("`r#try` called with ptr {:?}", data));
46+
let x = [12];
47+
let _ = x[4]; // should panic
48+
49+
logging::log_str("This line should not be visible! :(");
50+
}, data, |data, exception| {
51+
let exception = *Box::from_raw(exception as *mut String);
52+
logging::log_str("Caught something!");
53+
logging::log_str(&alloc::format!(" data : {:?}", data));
54+
logging::log_str(&alloc::format!(" exception: {:?}", exception));
55+
});
56+
}
57+
58+
logging::log_str("This program terminates correctly.");
59+
0
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
extern "C" {
2+
fn __log_utf8(ptr: *const u8, size: usize);
3+
}
4+
5+
pub fn log_str(text: &str) {
6+
unsafe {
7+
__log_utf8(text.as_ptr(), text.len());
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#[lang = "eh_personality"]
2+
fn eh_personality() {}
3+
4+
mod internal {
5+
extern "C" {
6+
#[link_name = "llvm.wasm.throw"]
7+
pub fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
8+
}
9+
}
10+
11+
unsafe fn wasm_throw(ptr: *mut u8) -> ! {
12+
internal::wasm_throw(0, ptr);
13+
}
14+
15+
#[panic_handler]
16+
fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! {
17+
use alloc::boxed::Box;
18+
use alloc::string::ToString;
19+
20+
let msg = info
21+
.message()
22+
.map(|msg| msg.to_string())
23+
.unwrap_or("(no message)".to_string());
24+
let exception = Box::new(msg.to_string());
25+
unsafe {
26+
let exception_raw = Box::into_raw(exception);
27+
wasm_throw(exception_raw as *mut u8);
28+
}
29+
}

0 commit comments

Comments
 (0)