mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
unity-dotnet P5 JIT: methods with exception clauses are JITted (wasm exceptions)
Under a wasm64 provider gex.provider_throw is now a wasm exception with a per-store tag ((i32) code) instead of a host error, and provider_try takes it as the store's pending exception. JIT modules import the tag as x.e and wrap an EH method's dispatch in try_table (catch x.e) in place of the fork's legacy try/catch; the landing pad and Mono's island handoff (mini-exceptions.c, now WJ_RUNTIME) are the fork's. embed: 513 methods JITted at threshold 1, T.Guest (catching a throw from a guest icall) and T.Work (catch/finally) among them, every result right. Also: MONO_WASM_JIT_STATS_EVERY=S prints the JIT's counters every S s; embed's T.Work gains catch/finally and EMBED_WORK=N (timed, bench box only). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01PeRutwFgZ66XpxjasKrPn1
This commit is contained in:
1 parent
aca50c7e12
commit
eccc327ef6
8 files changed
+393
-191
No files matched your search
@@ -62,6 +62,8 @@ pub(crate) struct Store {
|
||||
/// The stack pointer this thread's stateful providers share
|
||||
/// (`libprov::stack`), made at the first one's instantiation.
|
||||
pub provider_sp: Option<wasmtime::Global>,
|
||||
/// The wasm exception tag of `gex.provider_throw` (wasm64 providers; [`crate::libprov::tag`]).
|
||||
pub provider_tag: Option<wasmtime::Tag>,
|
||||
/// How far through the map's log this table is.
|
||||
replayed: usize,
|
||||
}
|
||||
@@ -225,7 +227,7 @@ pub(crate) fn ensure(mut cx: impl AsContextMut<Data = HostState>) -> Result<()>
|
||||
table.set(&mut c, gex_abi::ftab::FALLBACK as u64, wasmtime::Ref::Func(Some(fallback)))?;
|
||||
let jit = c.data_mut().jit.as_mut().unwrap();
|
||||
jit.instances.push(inst);
|
||||
jit.ftab = Some(Store { table, resolve, bridge, root, provider_sp: None, replayed: 0 });
|
||||
jit.ftab = Some(Store { table, resolve, bridge, root, provider_sp: None, provider_tag: None, replayed: 0 });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use wasmtime::AsContextMut;
|
||||
|
||||
/// One stateful provider.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -121,6 +122,40 @@ pub(crate) fn stack(cx: &mut wasmtime::StoreContextMut<'_, HostState>) -> Result
|
||||
Ok(g)
|
||||
}
|
||||
|
||||
/// This store's exception tag for `gex.provider_throw` under a wasm64
|
||||
/// provider: `(i32)`, the throw's code. A throw is a wasm exception, so code
|
||||
/// the provider publishes at run time (Mono's wasm JIT, whose modules import
|
||||
/// it as `x.e`) catches it with `try_table`; `gex.provider_try` takes it as
|
||||
/// the pending exception when nothing in between did.
|
||||
pub(crate) fn tag(cx: &mut wasmtime::StoreContextMut<'_, HostState>) -> Result<wasmtime::Tag> {
|
||||
if let Some(t) = cx.data().jit.as_ref().and_then(|j| j.ftab.as_ref()).and_then(|s| s.provider_tag.clone()) {
|
||||
return Ok(t);
|
||||
}
|
||||
let ft = wasmtime::FuncType::new(cx.engine(), [wasmtime::ValType::I32], []);
|
||||
let t = wasmtime::Tag::new(&mut *cx, &wasmtime::TagType::new(ft)).map_err(anyhow::Error::from)?;
|
||||
if let Some(s) = cx.data_mut().jit.as_mut().and_then(|j| j.ftab.as_mut()) {
|
||||
s.provider_tag = Some(t.clone());
|
||||
}
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
/// The code of the pending `gex.provider_throw` exception, taken off the
|
||||
/// store; None (and the exception left pending) for any other tag.
|
||||
fn take_throw(caller: &mut Caller<'_, HostState>) -> Result<Option<i32>> {
|
||||
let mut cx = caller.as_context_mut();
|
||||
let Some(exn) = cx.take_pending_exception() else { return Ok(None) };
|
||||
let ours = tag(&mut cx)?;
|
||||
let t = exn.tag(&mut cx).map_err(anyhow::Error::from)?;
|
||||
if !wasmtime::Tag::eq(&t, &ours, &cx) {
|
||||
let _ = cx.throw::<()>(exn);
|
||||
return Ok(None);
|
||||
}
|
||||
match exn.field(&mut cx, 0).map_err(anyhow::Error::from)? {
|
||||
Val::I32(c) => Ok(Some(c)),
|
||||
v => bail!("gex.provider_throw exception with payload {v:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Guest pages from the NT allocator (a Windows process; there is no other
|
||||
/// kind of stateful provider yet).
|
||||
pub(crate) fn alloc(state: &mut HostState, size: u64, name: &str) -> Result<u64> {
|
||||
@@ -380,13 +415,27 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
|
||||
let typed = func.typed::<i64, ()>(&caller).context("gex.provider_try: the function is not void(void *)")?;
|
||||
match typed.call(&mut caller, arg) {
|
||||
Ok(()) => Ok(0),
|
||||
Err(e) => match e.downcast_ref::<Throw>() {
|
||||
Some(t) => Ok(t.0),
|
||||
None => Err(anyhow::Error::from(e)),
|
||||
},
|
||||
Err(e) => {
|
||||
if let Some(c) = take_throw(&mut caller)? {
|
||||
return Ok(c);
|
||||
}
|
||||
match e.downcast_ref::<Throw>() {
|
||||
Some(t) => Ok(t.0),
|
||||
None => Err(anyhow::Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})()) })?;
|
||||
linker.func_wrap(IMPORT_MODULE, "provider_throw", |_caller: Caller<'_, HostState>, code: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> { Err(Throw(code).into()) })()) })?;
|
||||
// A wasm exception (see [`tag`]), returned as wasmtime's own error so it
|
||||
// unwinds as one: through wasm frames to the nearest handler.
|
||||
linker.func_wrap(IMPORT_MODULE, "provider_throw", |mut caller: Caller<'_, HostState>, code: i32| -> wasmtime::Result<()> {
|
||||
let mut cx = caller.as_context_mut();
|
||||
let t = crate::host(tag(&mut cx))?;
|
||||
let ty = wasmtime::ExnType::from_tag_type(&t.ty(&cx))?;
|
||||
let pre = wasmtime::ExnRefPre::new(&mut cx, ty);
|
||||
let exn = wasmtime::ExnRef::new(&mut cx, &pre, &t, &[Val::I32(code)])?;
|
||||
cx.throw(exn)
|
||||
})?;
|
||||
linker.func_wrap(IMPORT_MODULE, "provider_ctx", |caller: Caller<'_, HostState>| -> i32 { caller.data().thread.ctx_addr as i32 })?;
|
||||
linker.func_wrap(IMPORT_MODULE, "provider_missing", |mut caller: Caller<'_, HostState>, addr: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
|
||||
let addr = addr as u64;
|
||||
|
||||
@@ -149,6 +149,10 @@ fn instantiate(
|
||||
};
|
||||
wasmtime::Global::new(&mut *caller, wasmtime::GlobalType::new(gt.content().clone(), gt.mutability()), val)?.into()
|
||||
}
|
||||
("x", "e") => {
|
||||
let mut cx = caller.as_context_mut();
|
||||
crate::libprov::tag(&mut cx)?.into()
|
||||
}
|
||||
("h", idx) => {
|
||||
let i: u64 = idx.parse().map_err(|_| anyhow!("import h.{idx}: not a table index"))?;
|
||||
table_get(caller, table, i).ok_or_else(|| anyhow!("import h.{i}: table slot holds no function"))?.into()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef void *P;
|
||||
static P (*mono_jit_init_version) (const char *, const char *);
|
||||
@@ -152,8 +153,13 @@ main (int argc, char **argv)
|
||||
P work = mono_class_get_method_from_name (k, "Work", 1);
|
||||
int wn = getenv ("EMBED_WORK") ? atoi (getenv ("EMBED_WORK")) : 200;
|
||||
void *wargs [1] = { &wn };
|
||||
struct timespec w0, w1;
|
||||
clock_gettime (CLOCK_MONOTONIC, &w0);
|
||||
P wr = mono_runtime_invoke (work, NULL, wargs, &exc);
|
||||
clock_gettime (CLOCK_MONOTONIC, &w1);
|
||||
check ("Work", exc);
|
||||
if (getenv ("EMBED_WORK")) /* a timing, for the bench box only */
|
||||
fprintf (stderr, "Work time %.3f s\n", (w1.tv_sec - w0.tv_sec) + (w1.tv_nsec - w0.tv_nsec) / 1e9);
|
||||
printf ("Work(%d) = %lld\n", wn, *(long long *)mono_object_unbox (wr));
|
||||
P back = mono_class_get_method_from_name (k, "Back", 0);
|
||||
P br = mono_runtime_invoke (back, NULL, NULL, &exc);
|
||||
|
||||
@@ -22,7 +22,7 @@ cp "$FW/System.Runtime.dll" "$FW/System.Collections.dll" "$FW/System.Runtime.Int
|
||||
cd "$T"
|
||||
GEX_WASM_DLLS=libmonobdwgc-2.0.so="$OUT/provider/mono.wasm" timeout 300 "$GEXC" run --mem64 \
|
||||
--env GEXMONO_ASSEMBLIES="$T/lib" ./embed ./libmonobdwgc-2.0.so "$T/asm/t.dll" | tee out.txt
|
||||
if ! { grep -q "^Compute(50) = 50002$" out.txt && grep -q "^Hello = hello gex from t ptr=8$" out.txt && grep -q "^Guest = twice 42; concat guest strings; mix 323.25; scale 2,4,6; big 11,25,52,3; caught from the guest$" out.txt && grep -q "^Back = callback 806.50; unmanaged 1015$" out.txt && grep -q "^Work(200) = 24982818$" out.txt && grep -q "^held across collections: 64 of 64 intact$" out.txt && grep -q "^worker thread: 0 bad of 640$" out.txt && grep -q "^gc ok$" out.txt; }; then echo FAIL; exit 1; fi
|
||||
if ! { grep -q "^Compute(50) = 50002$" out.txt && grep -q "^Hello = hello gex from t ptr=8$" out.txt && grep -q "^Guest = twice 42; concat guest strings; mix 323.25; scale 2,4,6; big 11,25,52,3; caught from the guest$" out.txt && grep -q "^Back = callback 806.50; unmanaged 1015$" out.txt && grep -q "^Work(200) = 24983091$" out.txt && grep -q "^held across collections: 64 of 64 intact$" out.txt && grep -q "^worker thread: 0 bad of 640$" out.txt && grep -q "^gc ok$" out.txt; }; then echo FAIL; exit 1; fi
|
||||
# The same under the function JIT, which must leave the replaced library's own code alone: it once
|
||||
# followed the PLT into the stub ("x86 Mono stub ... called"), as it followed Muck's into Boehm's GC.
|
||||
GEX_WASM_DLLS=libmonobdwgc-2.0.so="$OUT/provider/mono.wasm" timeout 300 "$GEXC" run --mem64 --fjit \
|
||||
|
||||
@@ -80,6 +80,8 @@ public static class T {
|
||||
class Mul2 : IStep { public int Step (int x) => x * 2; }
|
||||
struct Pt { public int X; public long Y; }
|
||||
static int Run<TS> (TS s, int x) where TS : IStep => s.Step (x);
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int Thrower (int r) { if (r % 30 == 0) throw new InvalidOperationException ("odd " + r); return 3; }
|
||||
public static long Work (int n) {
|
||||
long sum = 0;
|
||||
var shapes = new List<Shape> ();
|
||||
@@ -97,6 +99,10 @@ public static class T {
|
||||
sum += (int) fx.GetValue (boxed);
|
||||
d [r % 50] = r.ToString ();
|
||||
sum += d [r % 50].Length;
|
||||
if (r % 10 == 0) {
|
||||
try { sum += Thrower (r); } catch (InvalidOperationException x) { sum += x.Message.Length; } finally { sum += 1; }
|
||||
try { sum += int.Parse (r % 20 == 0 ? "x" : "12"); } catch (FormatException) { sum += 5; }
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include "gexmono-adapters.h"
|
||||
|
||||
void mono_icall_table_init (void);
|
||||
@@ -247,6 +249,20 @@ __attribute__((import_module ("gex"), import_name ("provider_jit_reserve"))) int
|
||||
void mono_jiterp_initialize_table (int type, int first_index, int last_index);
|
||||
|
||||
|
||||
/* MONO_WASM_JIT_STATS_EVERY=S (with MONO_WASM_JIT_STATS=1): the JIT's counters every S seconds, which the
|
||||
* browser reads from JS. */
|
||||
void mono_wasm_jit_dump_stats (void);
|
||||
static void *
|
||||
stats_thread (void *arg)
|
||||
{
|
||||
for (;;) {
|
||||
sleep ((unsigned) (intptr_t) arg);
|
||||
mono_wasm_jit_dump_stats ();
|
||||
fflush (stdout);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
preinit (void)
|
||||
{
|
||||
@@ -281,6 +297,10 @@ preinit (void)
|
||||
mono_jiterp_initialize_table (1 /* JITERPRETER_TABLE_JIT_CALL */, (int)base, (int)(base + n - 1));
|
||||
else
|
||||
fprintf (stderr, "[gexmono] no function-table slots for the wasm JIT\n");
|
||||
if (getenv ("MONO_WASM_JIT_STATS_EVERY") && atoi (getenv ("MONO_WASM_JIT_STATS_EVERY")) > 0) {
|
||||
pthread_t t;
|
||||
pthread_create (&t, NULL, stats_thread, (void *) (intptr_t) atoi (getenv ("MONO_WASM_JIT_STATS_EVERY")));
|
||||
}
|
||||
} else
|
||||
setenv ("MONO_WASM_JIT_AUTO", "0", 0);
|
||||
gexmono_trace_on = getenv ("GEXMONO_TRACE") != NULL;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user