diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile
index 1dc7b79872438..a1d06ab18443c 100644
--- a/src/ci/docker/host-x86_64/test-various/Dockerfile
+++ b/src/ci/docker/host-x86_64/test-various/Dockerfile
@@ -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
diff --git a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs
index 011a253c6ff61..a7c9e4845c70a 100644
--- a/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs
+++ b/tests/assembly/stack-protector/stack-protector-heuristics-effect.rs
@@ -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
diff --git a/tests/assembly/wasm_exceptions.rs b/tests/assembly/wasm_exceptions.rs
new file mode 100644
index 0000000000000..b7d20881b624f
--- /dev/null
+++ b/tests/assembly/wasm_exceptions.rs
@@ -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
+}
diff --git a/tests/codegen/repr-transparent-aggregates-1.rs b/tests/codegen/repr-transparent-aggregates-1.rs
index 9c4b0e58e7187..ba3ba272efbb8 100644
--- a/tests/codegen/repr-transparent-aggregates-1.rs
+++ b/tests/codegen/repr-transparent-aggregates-1.rs
@@ -11,6 +11,7 @@
 // ignore-s390x
 // ignore-windows
 // ignore-loongarch64
+// ignore-wasm32-bare
 // See repr-transparent.rs
 
 #![feature(transparent_unions)]
diff --git a/tests/codegen/wasm_exceptions.rs b/tests/codegen/wasm_exceptions.rs
new file mode 100644
index 0000000000000..2b2359f5b6caa
--- /dev/null
+++ b/tests/codegen/wasm_exceptions.rs
@@ -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
+}
diff --git a/tests/run-make/wasm-exceptions-nostd/Makefile b/tests/run-make/wasm-exceptions-nostd/Makefile
new file mode 100644
index 0000000000000..34755ec14b745
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/Makefile
@@ -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
diff --git a/tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs b/tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs
new file mode 100644
index 0000000000000..572d253309cee
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/src/arena_alloc.rs
@@ -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)
+    }
+}
diff --git a/tests/run-make/wasm-exceptions-nostd/src/lib.rs b/tests/run-make/wasm-exceptions-nostd/src/lib.rs
new file mode 100644
index 0000000000000..7049d2fd9e0d1
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/src/lib.rs
@@ -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
+}
diff --git a/tests/run-make/wasm-exceptions-nostd/src/logging.rs b/tests/run-make/wasm-exceptions-nostd/src/logging.rs
new file mode 100644
index 0000000000000..569d03ec82f57
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/src/logging.rs
@@ -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());
+    }
+}
diff --git a/tests/run-make/wasm-exceptions-nostd/src/panicking.rs b/tests/run-make/wasm-exceptions-nostd/src/panicking.rs
new file mode 100644
index 0000000000000..4a8923fd43db6
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/src/panicking.rs
@@ -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);
+    }
+}
diff --git a/tests/run-make/wasm-exceptions-nostd/verify.mjs b/tests/run-make/wasm-exceptions-nostd/verify.mjs
new file mode 100644
index 0000000000000..e6c44d89d331d
--- /dev/null
+++ b/tests/run-make/wasm-exceptions-nostd/verify.mjs
@@ -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();
\ No newline at end of file