Skip to content

Commit

Permalink
Rollup merge of rust-lang#113247 - mirkootter:test-wasm-exceptions-no…
Browse files Browse the repository at this point in the history
…std, r=Mark-Simulacrum

Add Tests for native wasm exceptions

### Motivation
In PR rust-lang#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.
  • Loading branch information
matthiaskrgr authored Jul 9, 2023
2 parents 4406a92 + a0bd381 commit 6a20f68
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/ci/docker/host-x86_64/test-various/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions tests/assembly/wasm_exceptions.rs
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions tests/codegen/repr-transparent-aggregates-1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// ignore-s390x
// ignore-windows
// ignore-loongarch64
// ignore-wasm32-bare
// See repr-transparent.rs

#![feature(transparent_unions)]
Expand Down
51 changes: 51 additions & 0 deletions tests/codegen/wasm_exceptions.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions tests/run-make/wasm-exceptions-nostd/Makefile
Original file line number Diff line number Diff line change
@@ -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 changes: 67 additions & 0 deletions tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 60 additions & 0 deletions tests/run-make/wasm-exceptions-nostd/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions tests/run-make/wasm-exceptions-nostd/src/logging.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 29 additions & 0 deletions tests/run-make/wasm-exceptions-nostd/src/panicking.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 6a20f68

Please sign in to comment.