mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
page: stateful Linux providers (B1): Unity's Mono, wasm64, replaces libmonobdwgc in Chromium
- cgen/libprov.rs: a shared object named in ?wasmdlls=soname=path is replaced when it maps (exports bound to the provider's gexp_*, constructors to gexp__noop, its code page and reverse slots, data below 4 GiB), installed as Wine's PE module is (PROVIDER | number); guest_sym, guest_stack_end, abi, table_size, missing; the function JIT leaves its text alone; - gex.mjs: the wasm64 provider_* imports (try/throw on a per-Worker WebAssembly.Tag, i64 stack), its syscall marked so the shim serves it without leaving the provider's frames; - shim: such a syscall is served as a wasm32 program's (threads staged, waits in place); FUTEX_LOCK_PI/LOCK_PI2 in the page (taken when free, else retried), outside the host lock; provider data and stacks below 4 GiB; - page: ?wasmdlls=, browser-linux.mjs --wasmdlls. tests/unity-dotnet/embed in Chromium (interpreter, GEXMONO_WASM_JIT=0): every line as natively, once with "worker thread: 1 bad of 640" (the GC does not stop guest threads in the page yet). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_d2453757b8a84fdbb4ee7c9e093141df
This commit is contained in:
1 parent
1e4ae9d7d9
commit
182e7cfbfc
12 files changed
+562
-18
No files matched your search
@@ -9,6 +9,7 @@ crate-type = ["cdylib"]
|
||||
[dependencies]
|
||||
gex-abi.workspace = true
|
||||
gex-kernel.workspace = true
|
||||
gex-atomic.workspace = true
|
||||
gex-wayland.workspace = true
|
||||
gex-xwm.workspace = true
|
||||
gex-xlate = { workspace = true, features = ["fjit"] }
|
||||
|
||||
+105
-3
@@ -688,6 +688,8 @@ export class Gex {
|
||||
/// writer of `DISPATCH_DIR` reports itself, naming the one that moves it.
|
||||
/// See #136.
|
||||
static watchDir = -1;
|
||||
/** `soname=path,...` (the page's `?wasmdlls=`): libraries replaced by wasm64 providers. */
|
||||
static wasmDlls = '';
|
||||
static #whySlot = 0;
|
||||
|
||||
#bytes() {
|
||||
@@ -999,6 +1001,7 @@ export class Gex {
|
||||
// there, and their units are looked up then.
|
||||
if (this.#cgenMode) this.#shim.exports.gex_cgen_set_mode?.(this.#cgenMode);
|
||||
if (this.#noElfProviders) this.#shim.exports.gex_elfprov_off?.();
|
||||
this.#libprovConfig();
|
||||
this.#shim.exports.gex_set_wl?.(this.#wl ? 1 : 0);
|
||||
// `kernel.net` is decided inside `gex_load` from this flag, so it must be
|
||||
// set before it (not merely in `useNetwork`, before the shim exists).
|
||||
@@ -1440,6 +1443,7 @@ export class Gex {
|
||||
// its parent's providers made code (cgen/elfprov.rs).
|
||||
if (this.#cgenMode) this.#shim.exports.gex_cgen_set_mode?.(this.#cgenMode);
|
||||
if (this.#noElfProviders) this.#shim.exports.gex_elfprov_off?.();
|
||||
this.#libprovConfig();
|
||||
// Narrate one page's directory writes, in every process. See #136.
|
||||
if (Gex.watchDir >= 0) this.#shim.exports.gex_watch_dir?.(BigInt(Gex.watchDir));
|
||||
}
|
||||
@@ -2410,6 +2414,18 @@ export class Gex {
|
||||
#cgenMode = 0;
|
||||
/** No ELF export providers (cgen/elfprov.rs): A/B runs, every process of
|
||||
* the run (a forked child's Worker is told too). Before the shim exists. */
|
||||
/** `?wasmdlls=soname=path,...`: libraries this process replaces with wasm64
|
||||
* providers (cgen/libprov.rs). */
|
||||
#libprovConfig() {
|
||||
const e = this.#shim.exports;
|
||||
if (!Gex.wasmDlls || !e.gex_libprov_config) return;
|
||||
const b = new TextEncoder().encode(Gex.wasmDlls);
|
||||
const p = e.gex_alloc(Math.max(8, b.length)) >>> 0;
|
||||
this.#bytes().set(b, p);
|
||||
e.gex_libprov_config(p, b.length);
|
||||
e.gex_free?.(p, Math.max(8, b.length));
|
||||
}
|
||||
|
||||
noElfProviders() {
|
||||
this.#noElfProviders = true;
|
||||
this.#shim?.exports.gex_elfprov_off?.();
|
||||
@@ -2525,6 +2541,8 @@ export class Gex {
|
||||
const wasmdll = prov >= 0 && (e.gex_cgen_pmod_wasm_len?.(prov) >>> 0) > 0;
|
||||
// A native wasm32 program (cgen/wasmprog.rs): its bytes come the same way.
|
||||
const wasmprog = wasmdll && !!e.gex_cgen_pmod_wasmprog?.(prov);
|
||||
// A replaced Linux library's wasm64 provider (cgen/libprov.rs): the 64-bit provider_* imports.
|
||||
const libprov = wasmdll && !!e.gex_cgen_pmod_libprov?.(prov);
|
||||
const mod = wasmdll
|
||||
? this.#cgenFileModule(this.#cgenStr(e.gex_cgen_pmod_file, prov),
|
||||
new Uint8Array(this.#memory.buffer, e.gex_cgen_pmod_wasm_ptr(prov) >>> 0, e.gex_cgen_pmod_wasm_len(prov) >>> 0).slice())
|
||||
@@ -2550,7 +2568,7 @@ export class Gex {
|
||||
// but a position-independent module imports its pointer all the same.
|
||||
// A stateful provider's is this Worker's (one C stack per thread).
|
||||
// A wasm32 program sets its own at each thread's entry.
|
||||
__stack_pointer: memBase && !wasmprog ? this.#providerStack() : new WebAssembly.Global(m64() ? { value: 'i64', mutable: true } : { value: 'i32', mutable: true }, m64() ? 0n : 0),
|
||||
__stack_pointer: memBase && !wasmprog ? (libprov ? this.#providerStack64() : this.#providerStack()) : new WebAssembly.Global(m64() ? { value: 'i64', mutable: true } : { value: 'i32', mutable: true }, m64() ? 0n : 0),
|
||||
},
|
||||
gex: {
|
||||
...this.#imports, lookup: st.lookup, ftab: st.ftab,
|
||||
@@ -2569,8 +2587,8 @@ export class Gex {
|
||||
code_changed: (addr, len) => e.gex_code_changed?.(addr, len),
|
||||
wine_pe_slot: (ptr, len) => e.gex_wine_pe_slot?.(ptr, len) ?? 0,
|
||||
} : {}),
|
||||
...(memBase && !wasmprog ? this.#providerImports() : {}),
|
||||
...(wasmprog ? this.#wasmprogImports(() => inst) : wasmdll ? { syscall: this.#nativeSyscall } : {}),
|
||||
...(memBase && !wasmprog ? (libprov ? this.#providerImports64() : this.#providerImports()) : {}),
|
||||
...(wasmprog ? this.#wasmprogImports(() => inst) : libprov ? { syscall: this.#providerSyscall } : wasmdll ? { syscall: this.#nativeSyscall } : {}),
|
||||
},
|
||||
});
|
||||
if (wasmprog ? e.gex_wasmprog_relocate(prov) : memBase && e.gex_provider_relocate(memBase)) inst.exports.__wasm_apply_data_relocs?.();
|
||||
@@ -2612,6 +2630,71 @@ export class Gex {
|
||||
return this.#provStack;
|
||||
}
|
||||
|
||||
/** This Worker's stack for a wasm64 provider (i64 `__stack_pointer`), and its lowest address. */
|
||||
#provStack64 = null;
|
||||
#provStackLo = 0n;
|
||||
#providerStack64() {
|
||||
if (!this.#provStack64) {
|
||||
const size = 1 << 20;
|
||||
const e = this.#shim.exports;
|
||||
const base = e.gex_provider_stack(size) >>> 0;
|
||||
if (!base) throw new Error('gex: no memory for a provider stack');
|
||||
this.#provStackLo = BigInt(base);
|
||||
this.#provStack64 = new WebAssembly.Global({ value: 'i64', mutable: true }, BigInt(base + size - 16));
|
||||
}
|
||||
return this.#provStack64;
|
||||
}
|
||||
|
||||
/** This Worker's exception tag for a wasm64 provider's `provider_throw`: a wasm
|
||||
* exception, so code the provider publishes at run time (its JIT, which imports it)
|
||||
* can catch it with `try_table`, and `provider_try` takes it when nothing did. */
|
||||
#provTag = null;
|
||||
#providerTag() {
|
||||
this.#provTag ??= new WebAssembly.Tag({ parameters: ['i32'] });
|
||||
return this.#provTag;
|
||||
}
|
||||
|
||||
/** What a replaced Linux library's wasm64 provider imports from `gex` (the native
|
||||
* `libprov::register64`; docs/unity-dotnet.md P5 B1). Pointers and table indices
|
||||
* are i64 (BigInt). */
|
||||
#providerImports64() {
|
||||
const e = this.#shim.exports;
|
||||
const ftab = this.#cgen.ftab;
|
||||
const tag = this.#providerTag();
|
||||
return {
|
||||
provider_try: (f, arg) => {
|
||||
try {
|
||||
ftab.get(f)(arg);
|
||||
return 0;
|
||||
} catch (err) {
|
||||
if (err instanceof WebAssembly.Exception && err.is(tag)) return err.getArg(tag, 0);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
provider_throw: (code) => {
|
||||
throw new WebAssembly.Exception(tag, [code]);
|
||||
},
|
||||
provider_missing: (addr) => e.gex_libprov_missing(addr),
|
||||
provider_thread_entry: () => e.gex_libprov_thread_entry(),
|
||||
provider_code_page: () => e.gex_libprov_thread_entry(),
|
||||
provider_stack: () => this.#provStackLo,
|
||||
provider_guest_sym: (ptr, len) => e.gex_libprov_guest_sym(ptr, len),
|
||||
provider_guest_stack_end: (sp) => e.gex_libprov_guest_stack_end(sp),
|
||||
provider_abi: (which) => e.gex_libprov_abi(which),
|
||||
provider_table_size: (base) => e.gex_libprov_table_size(base),
|
||||
// Stopping the guest's threads around the runtime's collection: not yet in the page
|
||||
// (each guest thread is a Worker; the stop needs the shim's safepoints).
|
||||
provider_thread_register: () => {},
|
||||
provider_park: () => {},
|
||||
provider_stop_guest: () => {},
|
||||
provider_start_guest: () => {},
|
||||
// The runtime's wasm JIT: not yet in the page (B2); it runs interpreted.
|
||||
provider_jit_reserve: () => 0n,
|
||||
provider_jit_instantiate: () => -1,
|
||||
provider_jit_table: () => 0n,
|
||||
};
|
||||
}
|
||||
|
||||
/** What a stateful provider imports from `gex` (the native `libprov`). */
|
||||
#providerImports() {
|
||||
const e = this.#shim.exports;
|
||||
@@ -2668,6 +2751,13 @@ export class Gex {
|
||||
const e = this.#shim.exports;
|
||||
const n = e.gex_cgen_provider_count?.(u) >>> 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
// A replaced Linux library (cgen/libprov.rs): its wasm64 provider, installed as a module.
|
||||
if (e.gex_cgen_provider_libprov?.(u, i)) {
|
||||
const number = e.gex_libprov_publish(u, i);
|
||||
this.#cgenCatchUp();
|
||||
if (number >= 0) e.gex_cgen_pmod_installed(number);
|
||||
continue;
|
||||
}
|
||||
if (e.gex_cgen_provider_wasmdll?.(u, i)) {
|
||||
const number = e.gex_wasmdll_publish(u, i);
|
||||
this.#cgenCatchUp();
|
||||
@@ -2846,6 +2936,18 @@ export class Gex {
|
||||
* install, a thread to start -- is served here and returns, so the native
|
||||
* frames below it survive, as natively. Anything else (the process or
|
||||
* thread ending, a callback's return) goes on up. */
|
||||
/** A wasm64 provider's `gex.syscall(ctx, pc)` (cgen/libprov.rs): marked, so the shim
|
||||
* serves it without leaving the provider's frames (a blocking call waits in place, a
|
||||
* thread is staged); the host then starts what it staged, and the call returns. */
|
||||
#providerSyscall = (ctx, pc) => {
|
||||
const e = this.#shim.exports;
|
||||
e.gex_syscall(ctx, BigInt.asIntN(64, BigInt(pc) | (1n << 61n)));
|
||||
if (e.gex_wasmprog_staged()) {
|
||||
this.#drainFork().catch((err) => console.error(`[gex] starting a process: ${err.message ?? err}`));
|
||||
this.#drainThread().catch((err) => console.error(`[gex] starting a thread: ${err.message ?? err}`));
|
||||
}
|
||||
};
|
||||
|
||||
#nativeSyscall = (ctx, pc) => {
|
||||
const e = this.#shim.exports;
|
||||
const p = BigInt.asUintN(64, BigInt(pc));
|
||||
|
||||
@@ -414,6 +414,7 @@ async function handle(msg) {
|
||||
if (msg.denseDebug) globalThis.GEX_DENSE_DEBUG = 1;
|
||||
// A 64-bit guest memory (docs/mem64.md step 6): the wasm64 shim's process.
|
||||
if (msg.mem64) globalThis.GEX_MEM64 = 1;
|
||||
if (msg.wasmDlls) Gex.wasmDlls = msg.wasmDlls;
|
||||
if (msg.calloutTrace) globalThis.GEX_CALLOUT_TRACE = 1;
|
||||
if (msg.lazyThread) globalThis.GEX_LAZY_THREAD = 1;
|
||||
// `kernelStack` is this Worker's slice of the kernel's stack array. Absent for
|
||||
|
||||
@@ -36,6 +36,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
pub(crate) mod elfprov;
|
||||
pub(crate) mod libprov;
|
||||
mod wasmdll;
|
||||
pub(crate) mod wasmprog;
|
||||
|
||||
@@ -866,7 +867,8 @@ fn provider_alloc_bytes(size: u64, name: &str) -> Result<u64, String> {
|
||||
fn linux_alloc(size: u64, name: &str) -> Result<u64, String> {
|
||||
use gex_kernel::mem::{flags, prot};
|
||||
let len = (size + 0xfff) & !0xfff;
|
||||
crate::with_host(|h| h.kernel.mem.mmap(0, len, prot::READ | prot::WRITE, flags::PRIVATE | flags::ANONYMOUS, name)).map_err(|e| format!("mapping {name}: errno {}", e.0))
|
||||
// Below 4 GiB in a 64-bit memory too: the page hands these addresses around as 32 bits.
|
||||
crate::with_host(|h| h.kernel.mem.mmap_low(0, len, prot::READ | prot::WRITE, flags::PRIVATE | flags::ANONYMOUS, name)).map_err(|e| format!("mapping {name}: errno {}", e.0))
|
||||
}
|
||||
|
||||
/// [`provider_alloc_bytes`] from the top down (`boot::alloc_private_top`): a
|
||||
|
||||
@@ -80,8 +80,12 @@ fn rescan_if_new() -> bool {
|
||||
/// After a syscall: bind the providers of each newly mapped object. Returns
|
||||
/// whether JS has providers to serve before the guest goes on.
|
||||
pub(crate) fn maps(mapped: &[gex_kernel::CodeMap]) -> bool {
|
||||
// A replaced library (a stateful Linux provider, libprov.rs) is served the same way.
|
||||
if let Some(u) = super::libprov::maps(mapped) {
|
||||
lock(&QUEUE).push_back(u);
|
||||
}
|
||||
if !on() {
|
||||
return false;
|
||||
return !lock(&QUEUE).is_empty();
|
||||
}
|
||||
if PID.load(Ordering::Acquire) == 0 {
|
||||
PID.store(crate::with_host(|h| h.kernel.pid), Ordering::Release);
|
||||
@@ -191,7 +195,7 @@ fn unbind_overlapped(m: &gex_kernel::CodeMap, trace: bool) {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_elfprov_wanted() -> i32 {
|
||||
if !on() {
|
||||
return -1;
|
||||
return lock(&QUEUE).pop_front().map_or(-1, |u| u as i32);
|
||||
}
|
||||
rescan_if_new();
|
||||
lock(&QUEUE).pop_front().map_or(-1, |u| u as i32)
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
//! Stateful Linux providers in the page (docs/unity-dotnet.md P5 B1): the
|
||||
//! native host's `libprov` for a Linux process with a 64-bit memory. A shared
|
||||
//! object named in the page's `wasmdlls` (`soname=guest path of a wasm64 PIC
|
||||
//! module`, Unity's `libmonobdwgc-2.0.so=/gex/mono.wasm`) is replaced when it
|
||||
//! maps: every export it defines is bound to the module's `gexp_<name>` (or
|
||||
//! `gexp__missing`), its constructors and destructors to `gexp__noop`, and none
|
||||
//! of its own code runs. The module is installed as Wine's PE module is
|
||||
//! (`wasmdll.rs`): `PROVIDER | number` in the function table, its data from
|
||||
//! low guest memory, its bytes in the shim's heap for every Worker to
|
||||
//! instantiate (`gex.mjs` `#cgenInstall`, with the wasm64 `provider_*` imports).
|
||||
//!
|
||||
//! The protocol with `gex.mjs` (`#cgenProviders`):
|
||||
//! - a unit's provider `i` with `gex_cgen_provider_libprov(u, i)` set is
|
||||
//! published by `gex_libprov_publish(u, i)`, which returns its provider
|
||||
//! module number; JS catches up (instantiating it) and calls
|
||||
//! `gex_cgen_pmod_installed`.
|
||||
//! - `gex_cgen_pmod_libprov(number)` says a module is one of these (its
|
||||
//! imports are the 64-bit `provider_*` set).
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
|
||||
/// `soname=path` pairs from the page (`gex_libprov_config`).
|
||||
static DLLS: Mutex<Vec<(String, String)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// One replaced library, mapped and waiting for (or past) its publish.
|
||||
struct LinuxProv {
|
||||
/// The library's path, for messages.
|
||||
path: String,
|
||||
/// The provider module.
|
||||
wasm: Vec<u8>,
|
||||
/// (guest address, slot) pairs.
|
||||
pairs: Vec<(u64, u32)>,
|
||||
/// Its data (`__memory_base`), below 4 GiB.
|
||||
data: u64,
|
||||
/// Its provider module number once published.
|
||||
number: Option<u32>,
|
||||
}
|
||||
|
||||
static LIBS: Mutex<Vec<LinuxProv>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The guest address bound to the module's `__gex_thread_start` (threads it
|
||||
/// creates start there; its code page: return addresses and reverse slots).
|
||||
static THREAD_ENTRY: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// The replaced libraries' text: the function JIT leaves it alone (it would
|
||||
/// otherwise follow a PLT into code that must never run).
|
||||
static REPLACED: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Exports bound to `gexp__missing`, by guest address: for the message.
|
||||
static MISSING: Mutex<Option<HashMap<u64, String>>> = Mutex::new(None);
|
||||
|
||||
const CODE_PAGE: u64 = 0x10000;
|
||||
const REVERSE_FIRST: u64 = 0x1000;
|
||||
const REVERSE_STRIDE: u64 = 16;
|
||||
|
||||
/// `soname=path,...`: the libraries this process replaces (the page's
|
||||
/// `?wasmdlls=`), before the program starts.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_config(ptr: u32, len: u32) {
|
||||
let spec = String::from_utf8_lossy(&crate::guest_bytes()[ptr as usize..ptr as usize + len as usize]).into_owned();
|
||||
let mut v = lock(&DLLS);
|
||||
for item in spec.split(',') {
|
||||
if let Some((so, path)) = item.split_once('=') {
|
||||
let pair = (so.trim().to_string(), path.trim().to_string());
|
||||
if !v.contains(&pair) {
|
||||
v.push(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `addr` is in a replaced library's text.
|
||||
pub(crate) fn replaced(addr: u64) -> bool {
|
||||
lock(&REPLACED).iter().any(|&(lo, hi)| addr >= lo && addr < hi)
|
||||
}
|
||||
|
||||
/// Guest pages below 4 GiB: a provider's data and code page (its data base
|
||||
/// and the guest addresses it binds are 32-bit numbers to the page).
|
||||
fn alloc_low(size: u64, prot: u32, name: &str) -> Result<u64, String> {
|
||||
use gex_kernel::mem::flags;
|
||||
let len = (size + 0xfff) & !0xfff;
|
||||
crate::with_host(|h| h.kernel.mem.mmap_low(0, len, prot, flags::PRIVATE | flags::ANONYMOUS, name)).map_err(|e| format!("mapping {name}: errno {}", e.0))
|
||||
}
|
||||
|
||||
/// After a syscall that mapped code: a replaced library becomes a unit with
|
||||
/// one provider for JS to serve (`elfprov`'s queue). Returns whether one was
|
||||
/// queued.
|
||||
pub(crate) fn maps(mapped: &[gex_kernel::CodeMap]) -> Option<usize> {
|
||||
let dlls = lock(&DLLS).clone();
|
||||
if dlls.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut queued = None;
|
||||
for m in mapped {
|
||||
let file = m.path.rsplit('/').next().unwrap_or(&m.path);
|
||||
let Some((_, wasm_path)) = dlls.iter().find(|(so, _)| so == file) else { continue };
|
||||
match prepare(m, wasm_path) {
|
||||
Ok(Some(u)) => queued = Some(u),
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
crate::warn_line(&format!("gex: replacing {}: {e}\n", m.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
queued
|
||||
}
|
||||
|
||||
fn prepare(m: &gex_kernel::CodeMap, wasm_path: &str) -> Result<Option<usize>, String> {
|
||||
let bytes = crate::with_host(|h| h.kernel.read_file(&m.path)).ok_or("cannot read the library")?;
|
||||
let image = gex_ld::elf::load(&bytes, 0).map_err(|e| format!("{e:?}"))?;
|
||||
let text = image.segments.iter().find(|s| s.executable).ok_or("no executable segment")?;
|
||||
let bias = m.start.wrapping_sub(text.vaddr & !0xfff);
|
||||
if lock(&LIBS).iter().any(|l| l.path == m.path) {
|
||||
return Ok(None);
|
||||
}
|
||||
let (lo, hi) = (text.vaddr, text.vaddr + text.bytes.len() as u64);
|
||||
lock(&REPLACED).push((lo.wrapping_add(bias), hi.wrapping_add(bias)));
|
||||
let wasm = crate::with_host(|h| h.kernel.read_file(wasm_path)).ok_or_else(|| format!("cannot read its provider module {wasm_path}"))?;
|
||||
let slots = gex_cgen::provider::slot_names(&wasm).map_err(|e| e.to_string())?;
|
||||
let slot = |n: &str| slots.iter().position(|s| s.as_deref() == Some(n)).map(|k| k as u32);
|
||||
let missing = slot("gexp__missing").ok_or("no gexp__missing in its module's table")?;
|
||||
let mut provided: HashMap<&str, u32> = HashMap::new();
|
||||
for (k, s) in slots.iter().enumerate() {
|
||||
if let Some(e) = s.as_deref().and_then(|s| s.strip_prefix("gexp_")).filter(|e| !e.starts_with('_') && !e.starts_with("ptr_")) {
|
||||
provided.insert(e, k as u32);
|
||||
}
|
||||
}
|
||||
let mut pairs = Vec::new();
|
||||
let mut unprovided = Vec::new();
|
||||
for y in image.dynsyms.iter().filter(|y| !y.undefined && !y.tls && y.value >= lo && y.value < hi) {
|
||||
let addr = y.value.wrapping_add(bias);
|
||||
match provided.get(y.name.as_str()) {
|
||||
Some(&k) => pairs.push((addr, k)),
|
||||
None => {
|
||||
pairs.push((addr, missing));
|
||||
unprovided.push((addr, y.name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(k) = slot("__gex_thread_start") {
|
||||
let at = alloc_low(CODE_PAGE, gex_kernel::mem::prot::READ, &format!("[provider {file} code]", file = wasm_path))?;
|
||||
pairs.push((at, k));
|
||||
THREAD_ENTRY.store(at, Ordering::Release);
|
||||
if let Some(r) = slot("gexp__reverse") {
|
||||
for a in (at + REVERSE_FIRST..at + CODE_PAGE).step_by(REVERSE_STRIDE as usize) {
|
||||
pairs.push((a, r));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(k) = slot("gexp__noop") {
|
||||
let mut ctors: Vec<u64> = image.init_array.clone();
|
||||
ctors.extend(elf_fini(&bytes));
|
||||
for a in ctors.into_iter().filter(|&v| v >= lo && v < hi).map(|v| v.wrapping_add(bias)) {
|
||||
pairs.retain(|&(x, _)| x != a);
|
||||
pairs.push((a, k));
|
||||
}
|
||||
}
|
||||
pairs.sort_by_key(|&(a, k)| (a, k == missing));
|
||||
pairs.dedup_by_key(|&mut (a, _)| a);
|
||||
lock(&MISSING).get_or_insert_with(Default::default).extend(unprovided);
|
||||
let (size, align) = dylink_memory(&wasm)?;
|
||||
let a = 1u64 << align;
|
||||
let data = alloc_low((size as u64).max(16) + a, gex_kernel::mem::prot::READ | gex_kernel::mem::prot::WRITE, &format!("[provider {wasm_path} data]"))?;
|
||||
let data = (data + a - 1) & !(a - 1);
|
||||
crate::warn_line(&format!("gex: {} replaced by {wasm_path}: {} addresses bound, data at {data:#x}\n", m.path, pairs.len()));
|
||||
let index = {
|
||||
let mut libs = lock(&LIBS);
|
||||
libs.push(LinuxProv { path: m.path.clone(), wasm, pairs, data, number: None });
|
||||
libs.len() - 1
|
||||
};
|
||||
let u = {
|
||||
let mut units = lock(&UNITS);
|
||||
units.push(Unit {
|
||||
key: String::new(),
|
||||
path: m.path.clone(),
|
||||
base: m.start,
|
||||
wine: true,
|
||||
pages: Vec::new(),
|
||||
regs: Vec::new(),
|
||||
dirs: Vec::new(),
|
||||
slots: 0,
|
||||
providers: vec![Provided { file: format!("libprov:{index}"), binds: Vec::new(), stateful: None, data: false, skeleton: None }],
|
||||
identity: String::new(),
|
||||
key_base: String::new(),
|
||||
});
|
||||
units.len() - 1
|
||||
};
|
||||
Ok(Some(u))
|
||||
}
|
||||
|
||||
/// Whether provider `i` of unit `u` is a replaced Linux library's.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_cgen_provider_libprov(u: u32, i: u32) -> i32 {
|
||||
lock(&UNITS).get(u as usize).and_then(|x| x.providers.get(i as usize)).is_some_and(|p| p.file.starts_with("libprov:")) as i32
|
||||
}
|
||||
|
||||
/// Publish provider `i` of unit `u` (a replaced library): its provider
|
||||
/// module number, or -1 (said).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_publish(u: u32, i: u32) -> i32 {
|
||||
let index = {
|
||||
let units = lock(&UNITS);
|
||||
let Some(p) = units.get(u as usize).and_then(|x| x.providers.get(i as usize)) else { return -1 };
|
||||
let Some(Ok(index)) = p.file.strip_prefix("libprov:").map(|s| s.parse::<usize>()) else { return -1 };
|
||||
index
|
||||
};
|
||||
let mut libs = lock(&LIBS);
|
||||
let Some(l) = libs.get_mut(index) else { return -1 };
|
||||
if let Some(n) = l.number {
|
||||
return n as i32;
|
||||
}
|
||||
let len = match gex_cgen::provider::slot_names(&l.wasm) {
|
||||
Ok(s) => s.len() as u32,
|
||||
Err(e) => {
|
||||
crate::warn_line(&format!("gex: {}: {e}\n", l.path));
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
let number = lock(&PROVIDERS).len() as u32;
|
||||
ftab(|t| t.publish(len, PROVIDER | number, Tier::Unit, &l.pairs));
|
||||
let addrs: Vec<u64> = l.pairs.iter().map(|&(a, _)| a).collect();
|
||||
let mem = crate::guest_bytes();
|
||||
for &a in &addrs {
|
||||
gex_kernel::ftab::fcache_clear(mem, a);
|
||||
}
|
||||
let (regs, dirs, slots) = bridges(&addrs);
|
||||
lock(&PROVIDERS).push(ProviderModule { file: format!("libprov:{}", l.path), regs, dirs, slots, memory_base: l.data as u32 });
|
||||
l.number = Some(number);
|
||||
number as i32
|
||||
}
|
||||
|
||||
/// The module bytes of provider module `number` when it is a replaced
|
||||
/// Linux library's (`gex_cgen_pmod_wasm_ptr`/`len`).
|
||||
pub(crate) fn wasm(number: u32) -> Option<(u32, u32)> {
|
||||
lock(&LIBS).iter().find(|l| l.number == Some(number)).map(|l| (l.wasm.as_ptr() as usize as u32, l.wasm.len() as u32))
|
||||
}
|
||||
|
||||
/// Whether provider module `number` is a replaced Linux library's: its
|
||||
/// imports are the 64-bit `provider_*` set.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_cgen_pmod_libprov(number: u32) -> i32 {
|
||||
lock(&LIBS).iter().any(|l| l.number == Some(number)) as i32
|
||||
}
|
||||
|
||||
/// `gex.provider_thread_entry()` / `provider_code_page()`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_thread_entry() -> u64 {
|
||||
THREAD_ENTRY.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// `gex.provider_missing(addr)`: the program called an export the provider
|
||||
/// does not implement. Ends the process, saying which.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_missing(addr: u64) {
|
||||
let name = lock(&MISSING).as_ref().and_then(|m| m.get(&addr).cloned()).unwrap_or_else(|| "?".into());
|
||||
panic!("the program called {name} ({addr:#x}), an export of a replaced library that its provider does not implement");
|
||||
}
|
||||
|
||||
/// `gex.provider_table_size(base)`: the slots of the install at table
|
||||
/// index `base`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_table_size(base: u64) -> u64 {
|
||||
ftab(|t| t.installs().iter().find(|i| i.base as u64 == base).map_or(0, |i| i.len as u64))
|
||||
}
|
||||
|
||||
/// The dynamic symbols of a mapped object, by path: (name -> value, its
|
||||
/// executable segment's page-aligned vaddr).
|
||||
type ObjSyms = (HashMap<String, u64>, u64);
|
||||
static OBJ_SYMS: Mutex<Option<HashMap<String, Arc<ObjSyms>>>> = Mutex::new(None);
|
||||
|
||||
/// `gex.provider_guest_sym(name, len)`: the guest address of `name` in the
|
||||
/// first executable mapping that defines it (RTLD_DEFAULT's order), or 0.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_guest_sym(ptr: u64, len: u64) -> u64 {
|
||||
let name = String::from_utf8_lossy(crate::guest_bytes().get(ptr as usize..(ptr + len) as usize).unwrap_or(&[])).into_owned();
|
||||
let maps: Vec<(u64, String)> = crate::with_host(|h| {
|
||||
h.kernel.mem.vmas().iter().filter(|v| v.prot & gex_kernel::mem::prot::EXEC != 0).filter_map(|v| v.code.as_ref().map(|(p, _)| (v.start, p.clone()))).collect()
|
||||
});
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for (start, path) in maps {
|
||||
if !seen.insert(path.clone()) {
|
||||
continue;
|
||||
}
|
||||
let cached = lock(&OBJ_SYMS).get_or_insert_with(Default::default).get(&path).cloned();
|
||||
let syms = match cached {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let Some(bytes) = crate::with_host(|h| h.kernel.read_file(&path)) else { continue };
|
||||
let Ok(image) = gex_ld::elf::load(&bytes, 0) else { continue };
|
||||
let Some(text) = image.segments.iter().find(|s| s.executable) else { continue };
|
||||
let map = image.dynsyms.iter().filter(|y| !y.undefined && !y.tls && y.value != 0).map(|y| (y.name.clone(), y.value)).collect();
|
||||
let s = Arc::new((map, text.vaddr & !0xfff));
|
||||
lock(&OBJ_SYMS).get_or_insert_with(Default::default).insert(path.clone(), Arc::clone(&s));
|
||||
s
|
||||
}
|
||||
};
|
||||
if let Some(v) = syms.0.get(&name) {
|
||||
return start.wrapping_sub(syms.1).wrapping_add(*v);
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// `gex.provider_guest_stack_end(sp)`: the end of the mapping holding `sp`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_guest_stack_end(sp: u64) -> u64 {
|
||||
crate::with_host(|h| h.kernel.mem.vmas().iter().find(|v| v.start <= sp && sp < v.end).map_or(0, |v| v.end))
|
||||
}
|
||||
|
||||
/// `gex.provider_abi(which)`: constants of gex's ABI a provider calling
|
||||
/// guest code needs.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_libprov_abi(which: u32) -> u64 {
|
||||
match which {
|
||||
0 => gex_abi::FCACHE,
|
||||
1 => gex_abi::FCACHE_BITS as u64,
|
||||
2 => gex_abi::ctx::V as u64,
|
||||
_ => u64::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
/// DT_FINI and FINI_ARRAY's entries (unbiased), as the native host reads them.
|
||||
fn elf_fini(bytes: &[u8]) -> Vec<u64> {
|
||||
let u64at = |o: usize| bytes.get(o..o + 8).map(|b| u64::from_le_bytes(b.try_into().unwrap()));
|
||||
let u32at = |o: usize| bytes.get(o..o + 4).map(|b| u32::from_le_bytes(b.try_into().unwrap()));
|
||||
let u16at = |o: usize| bytes.get(o..o + 2).map(|b| u16::from_le_bytes(b.try_into().unwrap()));
|
||||
let mut out = Vec::new();
|
||||
let (Some(phoff), Some(phentsize), Some(phnum)) = (u64at(0x20), u16at(0x36), u16at(0x38)) else { return out };
|
||||
let mut loads = Vec::new();
|
||||
let mut dynamic = None;
|
||||
for i in 0..phnum as usize {
|
||||
let ph = phoff as usize + i * phentsize as usize;
|
||||
let (Some(ty), Some(off), Some(va), Some(fsz)) = (u32at(ph), u64at(ph + 8), u64at(ph + 16), u64at(ph + 32)) else { continue };
|
||||
if ty == 1 {
|
||||
loads.push((va, off, fsz));
|
||||
}
|
||||
if ty == 2 {
|
||||
dynamic = Some((off, fsz));
|
||||
}
|
||||
}
|
||||
let file_of = |va: u64| loads.iter().find(|&&(v, _, s)| va >= v && va < v + s).map(|&(v, o, _)| (va - v + o) as usize);
|
||||
let Some((doff, dsz)) = dynamic else { return out };
|
||||
let (mut fini_array, mut fini_arraysz) = (None, 0u64);
|
||||
for i in 0..dsz / 16 {
|
||||
let (Some(tag), Some(val)) = (u64at((doff + i * 16) as usize), u64at((doff + i * 16 + 8) as usize)) else { break };
|
||||
match tag {
|
||||
0 => break,
|
||||
13 => out.push(val),
|
||||
26 => fini_array = Some(val),
|
||||
28 => fini_arraysz = val,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(fa) = fini_array {
|
||||
for i in 0..fini_arraysz / 8 {
|
||||
if let Some(v) = file_of(fa + i * 8).and_then(u64at) {
|
||||
out.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -217,7 +217,7 @@ fn bind(install: u32, pairs: &[(u64, u32)]) {
|
||||
/// wasm32 program (in the shim's heap), else 0.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_cgen_pmod_wasm_ptr(number: u32) -> u32 {
|
||||
if let Some((p, _)) = super::wasmprog::wasm(number) {
|
||||
if let Some((p, _)) = super::wasmprog::wasm(number).or_else(|| super::libprov::wasm(number)) {
|
||||
return p;
|
||||
}
|
||||
lock(&WINE_PE).as_ref().filter(|w| w.number == number).map_or(0, |w| w.wasm.as_ptr() as u32)
|
||||
@@ -225,7 +225,7 @@ pub extern "C" fn gex_cgen_pmod_wasm_ptr(number: u32) -> u32 {
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_cgen_pmod_wasm_len(number: u32) -> u32 {
|
||||
if let Some((_, n)) = super::wasmprog::wasm(number) {
|
||||
if let Some((_, n)) = super::wasmprog::wasm(number).or_else(|| super::libprov::wasm(number)) {
|
||||
return n;
|
||||
}
|
||||
lock(&WINE_PE).as_ref().filter(|w| w.number == number).map_or(0, |w| w.wasm.len() as u32)
|
||||
|
||||
@@ -157,7 +157,7 @@ pub(crate) fn translate(addr: u64) -> Option<u32> {
|
||||
return None;
|
||||
}
|
||||
// An address an ELF provider holds is not code to translate (cgen::elfprov).
|
||||
let executable = |a: u64| exec.iter().any(|&(s, e)| a >= s && a < e) && !crate::cgen::elfprov::provided(a);
|
||||
let executable = |a: u64| exec.iter().any(|&(s, e)| a >= s && a < e) && !crate::cgen::elfprov::provided(a) && !crate::cgen::libprov::replaced(a);
|
||||
let read_only = |a: u64, n: u64| ro.iter().any(|&(s, e)| a >= s && a.saturating_add(n) <= e);
|
||||
// Writable-and-executable code (a JIT's) stays tier-1, as natively.
|
||||
let volatile = |a: u64| wx.iter().any(|&(s, e)| a >= s && a < e);
|
||||
|
||||
@@ -5871,6 +5871,15 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
if let Some(p) = nt::process() {
|
||||
return nt::syscall(p, ctx, pc);
|
||||
}
|
||||
// A stateful provider's syscall (`PROVIDER_SYSCALL` set by the page's glue,
|
||||
// cgen/libprov.rs): its wasm frames cannot be left, so it is served as a
|
||||
// wasm32 program's is -- a blocking call waits here, a thread or a child
|
||||
// is staged for the host -- and the call returns.
|
||||
let pc = {
|
||||
let provider = (pc as u64) & PROVIDER_SYSCALL != 0;
|
||||
IN_PROVIDER.with(|c| c.set(provider));
|
||||
((pc as u64) & !PROVIDER_SYSCALL) as i64
|
||||
};
|
||||
// Which syscall is running, so the allocator can name who was asking (wasm
|
||||
// has no backtrace). See `HEAP_BY_CALL`.
|
||||
{
|
||||
@@ -6404,7 +6413,7 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
h.jit.ever_threaded = true;
|
||||
}
|
||||
stage_thread(tid, ctx_addr, start);
|
||||
if h.kernel.wasm32 {
|
||||
if frames_stay(h) {
|
||||
// A wasm32 program's frames cannot be left: the call
|
||||
// returns, and `#wasmprogSyscall` starts the thread.
|
||||
WASM32_STAGED.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
@@ -6452,10 +6461,10 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
deadline: deadline.map(|d| now_ns() + d.as_nanos() as u64),
|
||||
},
|
||||
gex_kernel::BlockOp::Sleep(d) => Waiting::Sleep(now_ns() + d.as_nanos() as u64),
|
||||
// Priority-inheritance locks are not served here yet: the
|
||||
// call finishes at once with ENOSYS, as before gex had them
|
||||
// (`finish_parked`). docs/mem64.md step 6 brings the page.
|
||||
gex_kernel::BlockOp::FutexLockPi { .. } => Waiting::Sleep(now_ns()),
|
||||
// A priority-inheritance lock: tried when the thread runs
|
||||
// again (`finish_parked`), and the syscall made again until
|
||||
// it is taken (`PI_RETRY`); a short sleep between tries.
|
||||
gex_kernel::BlockOp::FutexLockPi { .. } => Waiting::Sleep(now_ns() + 100_000),
|
||||
gex_kernel::BlockOp::ReadStream { handle, .. } => {
|
||||
// What the thing being waited on says about itself,
|
||||
// at the moment of parking. A reader on a pipe whose
|
||||
@@ -6528,7 +6537,7 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
child_stack,
|
||||
tls,
|
||||
} => {
|
||||
if h.kernel.wasm32 {
|
||||
if frames_stay(h) {
|
||||
// A wasm32 process clones at its thread entry (the
|
||||
// context's PC, as the musl fork's __clone leaves it);
|
||||
// the call returns, and `#wasmprogSyscall` hands the
|
||||
@@ -6614,7 +6623,7 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
recompile_objects(mapped);
|
||||
// 3 means the running thread gave up the wasm stack: pick another, waiting
|
||||
// first if it is waiting on another Worker (see `park_until_ready`).
|
||||
let stop = if stop == 3 && with_host(|h| h.kernel.wasm32) {
|
||||
let stop = if stop == 3 && with_host(|h| frames_stay(h)) {
|
||||
wasm32_wait(pc as u64)
|
||||
} else if stop == 3 {
|
||||
// Ask first, wait second: most parked calls are already ready by now
|
||||
@@ -6736,6 +6745,14 @@ pub extern "C" fn gex_syscall(ctx: i32, pc: i64) {
|
||||
};
|
||||
trace_line(&format!("[gex] t{i} = {}{split}{clk}\n", x0 as i64));
|
||||
}
|
||||
// A provider's priority-inheritance lock still held (`PI_RETRY` set the
|
||||
// thread to make the call again): it has no instruction to go back to, so
|
||||
// the call is made again here, outside the host lock.
|
||||
if stop != 0 && IN_PROVIDER.with(|c| c.get()) && resume() == Some(pc as u64) {
|
||||
set_resume_none();
|
||||
set_resume_ctx_none();
|
||||
return gex_syscall(ctx, (pc as u64 | PROVIDER_SYSCALL) as i64);
|
||||
}
|
||||
if stop != 0 {
|
||||
trap();
|
||||
}
|
||||
@@ -6760,6 +6777,21 @@ const LOW_END: u64 = gex_abi::DISPATCH_DIR + gex_abi::DISPATCH_MAX_PAGES * 4;
|
||||
// parent's regions, `wait4`/`kill`/`exit` route through the kernel's table, and
|
||||
// each process is a Worker the engine schedules.
|
||||
|
||||
/// The mark the page's glue puts on a stateful provider's syscalls (as gexrt's
|
||||
/// `NATIVE_SYSCALL` on native Wine's, `nt.rs`).
|
||||
const PROVIDER_SYSCALL: u64 = 1 << 61;
|
||||
|
||||
thread_local! {
|
||||
/// The syscall being served is a stateful provider's (see `gex_syscall`).
|
||||
static IN_PROVIDER: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
/// Whether the syscall being served cannot leave its wasm frames: a wasm32
|
||||
/// program's, or a stateful provider's.
|
||||
fn frames_stay(h: &Host) -> bool {
|
||||
h.kernel.wasm32 || IN_PROVIDER.with(|c| c.get())
|
||||
}
|
||||
|
||||
/// A thread or a fork a wasm32 program's syscall staged without trapping
|
||||
/// (`gex_wasmprog_staged`).
|
||||
static WASM32_STAGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
@@ -9518,6 +9550,12 @@ fn schedule() -> i32 {
|
||||
let mut gm = GuestMem::new(guest_bytes());
|
||||
h.kernel.enter_thread(&kstate);
|
||||
let r = finish_parked(&op, timed_out, by_signal, &mut gm);
|
||||
// A priority-inheritance lock still held: the syscall again,
|
||||
// from its instruction (the registers are as the guest made the call).
|
||||
if r == Err(PI_RETRY) {
|
||||
h.kernel.leave_thread(&mut h.threads[cur()].kstate);
|
||||
return Some((kstate.ctx_addr, pc - gex_abi::SYSCALL_INST_BYTES));
|
||||
}
|
||||
// A parked call's answer never reaches the syscall ring (the
|
||||
// ring is written when the call is made), so an error here is
|
||||
// otherwise invisible -- and glibc aborts over a bad futex one.
|
||||
@@ -9594,6 +9632,10 @@ fn schedule() -> i32 {
|
||||
///
|
||||
/// A woken futex returns zero and the guest re-checks its own condition, the
|
||||
/// same as on Linux, where a wake is never a promise about the word.
|
||||
/// `finish_parked`'s answer for a priority-inheritance lock still held: make
|
||||
/// the syscall again (never seen by the guest).
|
||||
const PI_RETRY: Errno = Errno(0x7fff);
|
||||
|
||||
fn finish_parked(
|
||||
op: &gex_kernel::BlockOp,
|
||||
timed_out: bool,
|
||||
@@ -9626,7 +9668,30 @@ fn finish_parked(
|
||||
Err(Errno::ETIMEDOUT)
|
||||
}
|
||||
gex_kernel::BlockOp::FutexWait { .. } | gex_kernel::BlockOp::Sleep(_) => Ok(0),
|
||||
gex_kernel::BlockOp::FutexLockPi { .. } => Err(Errno::ENOSYS),
|
||||
// Take the lock if it is free (the waiters bit stays set, so the
|
||||
// next unlock goes through the kernel); else `PI_RETRY`: the caller
|
||||
// makes the syscall again, which parks once more.
|
||||
gex_kernel::BlockOp::FutexLockPi { uaddr, tid, .. } => {
|
||||
use gex_kernel::futex::pi;
|
||||
loop {
|
||||
let v = gex_atomic::load_u32(gm.bytes, *uaddr).ok_or(Errno::EFAULT)?;
|
||||
match v & pi::TID_MASK {
|
||||
0 => {
|
||||
if gex_atomic::cas_u32(gm.bytes, *uaddr, v, *tid | pi::WAITERS).ok_or(Errno::EFAULT)?.is_ok() {
|
||||
return Ok(0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
owner if owner == *tid => return Err(Errno::EDEADLK),
|
||||
_ => {}
|
||||
}
|
||||
let want = v | pi::WAITERS;
|
||||
if v != want && gex_atomic::cas_u32(gm.bytes, *uaddr, v, want).ok_or(Errno::EFAULT)?.is_err() {
|
||||
continue;
|
||||
}
|
||||
return Err(PI_RETRY);
|
||||
}
|
||||
}
|
||||
// The read the guest asked for happens now, into the buffer it named.
|
||||
// Doing it here rather than re-executing the instruction is what keeps
|
||||
// the guest's pc moving forward: a syscall that parks is finished by
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// node tests/games/browser-linux.mjs --name NAME --dir GAME_DIR --exe REL
|
||||
// [--cwd REL] [--args ARGS] [--libs DIR:DIR] [--files LIST]
|
||||
// [--input SCRIPT.wl] [--size WxH] [--minutes N] [--shot DIR]
|
||||
// [--gameplay N] [--mem64] [--fjit] [--env K=V,K=V] [--end-on-exit]
|
||||
// [--gameplay N] [--mem64] [--fjit] [--wasmdlls SO=PATH] [--env K=V,K=V] [--end-on-exit]
|
||||
//
|
||||
// --files lists host files the game opens, one per line (collected from a
|
||||
// native `gexc run --strace`; tests/games/retrocycles-linux-files.txt).
|
||||
@@ -128,6 +128,7 @@ async function main() {
|
||||
const { evaluate, state, shot, consoleLines } = await openPage(
|
||||
`tar=/gex/nt/${basename(tar)}&exe=/game/${exe}&cwd=/game/${cwd}&gl=1&glbackend=wgpu&hostx=1`
|
||||
+ (argv.includes('--mem64') ? '&mem64=1' : '') + (argv.includes('--fjit') ? '&fjit=1' : '')
|
||||
+ (opt('--wasmdlls', null) ? `&wasmdlls=${encodeURIComponent(opt('--wasmdlls'))}` : '')
|
||||
+ `&size=${width}x${height}&trapstack=1&env=${encodeURIComponent(genv)}&args=${encodeURIComponent(opt('--args', ''))}`,
|
||||
{ width: width + 20, height: height + 100, shotDir },
|
||||
);
|
||||
|
||||
@@ -115,6 +115,8 @@ async function main() {
|
||||
if (q.get('mem64') === '1') globalThis.GEX_MEM64 = 1;
|
||||
// `?fjit=1`: the function JIT (gexc run --fjit's browser half, fjit.rs).
|
||||
if (q.get('fjit')) globalThis.GEX_FJIT = Number(q.get('fjit')) || 1;
|
||||
// `?wasmdlls=soname=path,...`: shared objects replaced by wasm64 providers (cgen/libprov.rs).
|
||||
if (q.get('wasmdlls')) globalThis.GEX_WASM_DLLS = q.get('wasmdlls');
|
||||
if (q.get('dense') === '0') globalThis.GEX_DENSE = 0;
|
||||
if (q.get('dense') === '2') globalThis.GEX_DENSE = 2;
|
||||
// cgen=1|2 (prefer|require units), in the unguarded flavor of i386 units
|
||||
|
||||
@@ -671,6 +671,8 @@ export async function runProgram(
|
||||
mem64: globalThis.GEX_MEM64 ? 1 : undefined,
|
||||
// `?fjit=1`: the function JIT in every process.
|
||||
fjit: globalThis.GEX_FJIT || undefined,
|
||||
// `?wasmdlls=soname=path,...`: libraries replaced by wasm64 providers (cgen/libprov.rs).
|
||||
wasmDlls: globalThis.GEX_WASM_DLLS || undefined,
|
||||
traceMisses: !!globalThis.GEX_TRACE_MISSES,
|
||||
// `?sharetrace=1`: say how compiled modules are shared between a process's Workers.
|
||||
shareTrace: flag('sharetrace') ? 1 : undefined,
|
||||
|
||||
Reference in new issue
Block a user