Skip to main content

warp_runtime/
c_calls.rs

1//! Calling C from a program, for a store of any state that keeps the run's C handles: loading libraries, the
2//! hand-linked libc functions, and one wrapper for every C function as c_signatures.rs describes it. warp links these
3//! after reading the headers (src/ffi/link.rs); an executable links the functions it carries (standalone.rs)
4use crate::c_signatures::{CFunction, CPointer, Failure, ParamType, RetType, HAND_LINKED_LIBC};
5use crate::c_structs::CStruct;
6use libloading::Library;
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex, OnceLock};
9use wasmtime::{Caller, Engine, FuncType, Linker, Result, Val, ValType};
10
11/// The import module of libc
12pub const LIBC: &str = "c";
13/// The other names programs import libc and libm under: (alias, module)
14const MODULE_ALIASES: [(&str, &str); 2] = [("libm", crate::libm::LIBM), ("libc", LIBC)];
15
16/// Takes turns for calls into libraries with global state that is not thread-safe (SDL_Init and SDL_Quit crash when
17/// two runs of one process call them at once): programs running on several threads queue here. Thread-safe
18/// libraries (libm, sqlite with its own connections) are called in parallel
19static NATIVE_CALL_TURN: Mutex<()> = Mutex::new(());
20
21/// Lowercase name prefixes of the libraries whose calls take the native call turn
22const NOT_THREAD_SAFE_LIBRARIES: [&str; 2] = ["sdl", "raylib"];
23
24/// The state of a store whose program calls C: the run's handle table
25pub trait CallsC: 'static {
26	fn c_handles(&mut self) -> &mut CHandles;
27}
28
29impl CallsC for CHandles {
30	fn c_handles(&mut self) -> &mut CHandles {
31		self
32	}
33}
34
35extern "C" {
36	fn abs(x: i32) -> i32;
37	fn atoi(s: *const i8) -> i32;
38	fn atol(s: *const i8) -> i64;
39	fn atof(s: *const i8) -> f64;
40	fn strcmp(s1: *const i8, s2: *const i8) -> i32;
41	fn strncmp(s1: *const i8, s2: *const i8, n: usize) -> i32;
42	fn rand() -> i32;
43}
44
45/// The hand-linked libc functions under the import module "c", and the modules "libm" and "libc" as aliases
46pub fn link_libc_functions<T: CallsC>(linker: &mut Linker<T>) -> Result<()> {
47	linker.func_wrap(LIBC, "abs", |x: i32| unsafe { abs(x) })?;
48	// strlen, atoi, atol and atof get a pointer to a null-terminated string in the module's memory
49	linker.func_wrap(LIBC, "strlen", |mut caller: Caller<'_, T>, ptr: i32| -> i64 {
50		memory_bytes(&mut caller)
51			.and_then(|data| data.get(ptr as usize..))
52			.map_or(0, |text| text.iter().take_while(|&&byte| byte != 0).count()) as i64
53	})?;
54	linker.func_wrap(LIBC, "atoi", |mut caller: Caller<'_, T>, ptr: i32| {
55		parse_c_text(&mut caller, ptr, atoi).unwrap_or(0)
56	})?;
57	linker.func_wrap(LIBC, "atol", |mut caller: Caller<'_, T>, ptr: i32| {
58		parse_c_text(&mut caller, ptr, atol).unwrap_or(0)
59	})?;
60	linker.func_wrap(LIBC, "atof", |mut caller: Caller<'_, T>, ptr: i32| {
61		parse_c_text(&mut caller, ptr, atof).unwrap_or(0.0)
62	})?;
63	// strcmp(ptr1, len1, ptr2, len2) and strncmp(…, n) compare two texts of the module's memory
64	linker.func_wrap(LIBC, "strcmp", |mut caller: Caller<'_, T>, ptr1: i32, len1: i32, ptr2: i32, len2: i32| {
65		text_pair(&mut caller, [ptr1, len1, ptr2, len2])
66			.map(|(first, second)| unsafe { strcmp(first.as_ptr() as *const i8, second.as_ptr() as *const i8) })
67			.unwrap_or(0)
68	})?;
69	linker.func_wrap(
70		LIBC,
71		"strncmp",
72		|mut caller: Caller<'_, T>, ptr1: i32, len1: i32, ptr2: i32, len2: i32, n: i64| {
73			text_pair(&mut caller, [ptr1, len1, ptr2, len2])
74				.map(|(first, second)| unsafe {
75					strncmp(first.as_ptr() as *const i8, second.as_ptr() as *const i8, n as usize)
76				})
77				.unwrap_or(0)
78		},
79	)?;
80	linker.func_wrap(LIBC, "rand", || unsafe { rand() })?;
81	for (alias, module) in MODULE_ALIASES {
82		linker.alias_module(module, alias)?;
83	}
84	Ok(())
85}
86
87/// The imports link_libc_functions provides: (module, name)
88pub fn libc_names() -> impl Iterator<Item = (&'static str, &'static str)> {
89	let libm = crate::libm::libm_names().map(|name| ("libm", name));
90	[LIBC, "libc"].into_iter().flat_map(|module| HAND_LINKED_LIBC.map(|name| (module, name))).chain(libm)
91}
92
93/// The module's linear memory, if it exports one
94fn memory_bytes<'caller, T>(caller: &'caller mut Caller<'_, T>) -> Option<&'caller [u8]> {
95	Some(caller.get_export(crate::gc_values::MEMORY)?.into_memory()?.data(caller))
96}
97
98/// `parse` (atoi, atol, atof) of the null-terminated string the pointer `ptr` names, none outside the memory
99fn parse_c_text<T, R>(caller: &mut Caller<'_, T>, ptr: i32, parse: unsafe extern "C" fn(*const i8) -> R) -> Option<R> {
100	let text = memory_bytes(caller)?.get(ptr as usize..).filter(|text| !text.is_empty())?;
101	Some(unsafe { parse(text.as_ptr() as *const i8) })
102}
103
104/// The UTF-8 texts at (ptr1, len1, ptr2, len2), null-terminated for C
105fn text_pair<T>(caller: &mut Caller<'_, T>, places: [i32; 4]) -> Option<(Vec<u8>, Vec<u8>)> {
106	let data = memory_bytes(caller)?;
107	let text = |index: usize| {
108		let (ptr, len) = (places[index] as usize, places[index + 1] as usize);
109		let bytes = data.get(ptr..ptr + len)?;
110		std::str::from_utf8(bytes).ok()?;
111		Some([bytes, &[0]].concat())
112	};
113	Some((text(0)?, text(2)?))
114}
115
116/// The libraries loaded so far, by name
117static LOADED_LIBRARIES: OnceLock<Mutex<HashMap<String, Arc<Library>>>> = OnceLock::new();
118
119/// The library `name` (`m`, `sqlite3`, `SDL2`), loaded once per process; libc is the process itself
120pub fn get_or_load_library(name: &str) -> Option<Arc<Library>> {
121	let mut loaded = LOADED_LIBRARIES.get_or_init(Default::default).lock().ok()?;
122	if let Some(library) = loaded.get(name) {
123		return Some(Arc::clone(library));
124	}
125	#[cfg(unix)]
126	let this_process = (name == LIBC).then(|| Library::from(libloading::os::unix::Library::this()));
127	#[cfg(not(unix))]
128	let this_process = None;
129	let library = this_process
130		.or_else(|| library_paths(name).iter().find_map(|path| unsafe { Library::new(path) }.ok()))
131		.map(Arc::new)?;
132	loaded.insert(name.to_string(), Arc::clone(&library));
133	Some(library)
134}
135
136/// Where the library `name` may be
137fn library_paths(name: &str) -> Vec<String> {
138	#[cfg(target_os = "macos")]
139	return vec![
140		format!("/opt/homebrew/lib/lib{name}.dylib"),
141		format!("/usr/local/lib/lib{name}.dylib"),
142		format!("lib{name}.dylib"),
143	];
144	#[cfg(target_os = "linux")]
145	return vec![
146		format!("/usr/lib/lib{name}.so"),
147		format!("/usr/local/lib/lib{name}.so"),
148		format!("lib{name}.so"),
149		// glibc's libm.so and libc.so are linker scripts dlopen refuses: the libraries go by their soname
150		format!("lib{name}.so.6"),
151	];
152	#[cfg(not(any(target_os = "macos", target_os = "linux")))]
153	return vec![format!("{name}.dll")];
154}
155
156/// The address of `function` in its loaded library
157pub fn function_pointer(library: &Library, function: &str) -> Result<usize> {
158	let pointer = unsafe {
159		let symbol: libloading::Symbol<*const ()> = library
160			.get(function.as_bytes())
161			.map_err(|failure| wasmtime::Error::msg(format!("symbol {function} not found: {failure}")))?;
162		*symbol as usize
163	};
164	match pointer {
165		0 => Err(wasmtime::Error::msg(format!("null function pointer for {function}"))),
166		pointer => Ok(pointer),
167	}
168}
169
170/// Load `function`'s library and link it: what an executable does for each C function it carries
171pub fn link_carried_function<T: CallsC>(linker: &mut Linker<T>, engine: &Engine, function: &CFunction) -> Result<()> {
172	let library = get_or_load_library(&function.library).ok_or_else(|| {
173		wasmtime::Error::msg(format!("{}: cannot load the C library {}", function.name, function.library))
174	})?;
175	let pointer = function_pointer(&library, &function.name)?;
176	link_c_function(linker, engine, function.clone(), pointer)
177}
178
179/// The wasm type of a parameter as the warp call passes it: none for an out-pointer
180fn wasm_param(param: ParamType) -> Option<ValType> {
181	match param {
182		ParamType::I64 => Some(ValType::I64),
183		ParamType::F32 => Some(ValType::F32),
184		ParamType::F64 => Some(ValType::F64),
185		ParamType::Bytes | ParamType::StructIn | ParamType::StructsIn => Some(ValType::ANYREF),
186		ParamType::Out | ParamType::ErrorOut | ParamType::Length | ParamType::StructOut | ParamType::StructsOut => None,
187		ParamType::I32 | ParamType::Ptr | ParamType::Handle => Some(ValType::I32),
188	}
189}
190
191/// The wasm results of a C function: a text node, a handle id, else its number
192fn wasm_results(function: &CFunction) -> Vec<ValType> {
193	match (function.result, function.ret_type) {
194		(Some(CPointer::Text | CPointer::Struct), _) => vec![ValType::ANYREF],
195		(Some(_), _) => vec![ValType::I32],
196		(None, RetType::Void) => vec![],
197		(None, RetType::I64) => vec![ValType::I64],
198		(None, RetType::F32) => vec![ValType::F32],
199		(None, RetType::F64) => vec![ValType::F64],
200		(None, RetType::I32 | RetType::Bool) => vec![ValType::I32],
201	}
202}
203
204fn is_thread_safe(library: &str) -> bool {
205	let name = library.to_lowercase();
206	!NOT_THREAD_SAFE_LIBRARIES.iter().any(|prefix| name.trim_start_matches("lib").starts_with(prefix))
207}
208
209/// One wrapper for every C function at `func_ptr`: its arguments go in the integer and float registers by the C calling
210/// convention (call_native_function), whatever the signature. `sqlite3_column_text(stmt, i)` takes a handle and gives
211/// a text, `sqlite3_open(name)` gives the handle its out-pointer received (NULL there is an error naming the C status),
212/// a `char *` result is copied into the module as a warp text (NULL is ø). One of a library that is not thread-safe
213/// holds the native call turn while it runs
214pub fn link_c_function<T: CallsC>(
215	linker: &mut Linker<T>,
216	engine: &Engine,
217	function: CFunction,
218	func_ptr: usize,
219) -> Result<()> {
220	let params: Vec<ValType> = function.param_types.iter().filter_map(|param| wasm_param(*param)).collect();
221	let func_type = FuncType::new(engine, params, wasm_results(&function));
222	let (module, name) = (function.module.clone(), function.name.clone());
223	let thread_safe = is_thread_safe(&function.library);
224	let layouts: Vec<Option<CStruct>> = function.layouts.iter().map(|text| CStruct::of_text(text)).collect();
225	linker.func_new(&module, &name, func_type, move |mut caller, params, results| {
226		let _turn = (!thread_safe).then(|| NATIVE_CALL_TURN.lock().unwrap_or_else(|poisoned| poisoned.into_inner()));
227		call_c_function(&mut caller, &function, &layouts, func_ptr, params, results)
228	})?;
229	Ok(())
230}
231
232fn call_c_function<T: CallsC>(
233	caller: &mut Caller<'_, T>,
234	function: &CFunction,
235	layouts: &[Option<CStruct>],
236	func_ptr: usize,
237	params: &[Val],
238	results: &mut [Val],
239) -> Result<()> {
240	// live through the call: the out-pointer slots, the copies of byte and struct parameters, the struct outputs
241	let mut memory = CallMemory::default();
242	let args = native_arguments(caller, function, layouts, params, &mut memory)?;
243	let returned = unsafe { call_native_function(func_ptr, &args, function.ret_type) };
244	let failed = match function.failure {
245		Failure::None => false,
246		Failure::NotNull => returned != 0,
247		Failure::False => returned as u8 == 0,
248	};
249	if failed || memory.error_slots.iter().any(|slot| *slot != 0) {
250		wasmtime::bail!("{} failed (C result {returned:#x})", function.name);
251	}
252	if let Some(pointee) = &function.out_pointee {
253		if *memory.out_slot == 0 {
254			wasmtime::bail!("{} gave no {pointee} (C status {})", function.name, returned as i32);
255		}
256	}
257	let pointer = if function.out_pointee.is_some() { *memory.out_slot } else { returned as usize };
258	let Some(result) = results.first_mut() else {
259		return Ok(());
260	};
261	*result = match function.result {
262		Some(CPointer::Text) => {
263			// copied before anything else runs: a C string from getenv may change with the next call
264			let pointer = pointer as *const std::ffi::c_char;
265			let text = (!pointer.is_null()).then(|| unsafe { std::ffi::CStr::from_ptr(pointer) }.to_bytes().to_vec());
266			text_node(caller, text.as_deref())?
267		}
268		Some(CPointer::Struct) => struct_output(caller, &memory)?,
269		Some(_) => Val::I32(caller.data_mut().c_handles().id_of(pointer)),
270		None => native_result(returned, function.ret_type),
271	};
272	Ok(())
273}
274
275/// What a call's arguments point to, alive through the call
276#[derive(Default)]
277struct CallMemory {
278	/// the first out-pointer's (a `char **` like strtod's endptr, or the handle that is the result)
279	out_slot: Box<usize>,
280	/// one per error-shaped out-pointer, sized before the call so their addresses stay put
281	error_slots: Vec<usize>,
282	/// the copies of byte and struct parameters
283	buffers: Vec<Vec<u8>>,
284	/// the first struct output: its layout, how many, and its buffer
285	output: Option<(CStruct, Option<usize>, usize)>,
286}
287
288/// The struct output of a call as a warp map, or a list of maps
289fn struct_output<T: CallsC>(caller: &mut Caller<'_, T>, memory: &CallMemory) -> Result<Val> {
290	let Some((layout, count, buffer)) = &memory.output else {
291		return Ok(Val::AnyRef(None));
292	};
293	let bytes = &memory.buffers[*buffer];
294	let handles = caller.data_mut().c_handles();
295	let mut pointer_id = |pointer| handles.id_of(pointer);
296	let value = match count {
297		Some(count) => serde_json::Value::Array(
298			(0..*count).map(|index| layout.read(&bytes[index * layout.size()..], &mut pointer_id)).collect(),
299		),
300		None => layout.read(bytes, &mut pointer_id),
301	};
302	let builders = crate::gc_values::Builders::of(&mut |name| caller.get_export(name))?;
303	builders.build(&value, &mut wasmtime::AsContextMut::as_context_mut(caller))
304}
305
306/// A warp text of `bytes` in the calling module (its memory, text heap and new_text), or ø
307pub fn text_node<T>(caller: &mut Caller<'_, T>, bytes: Option<&[u8]>) -> Result<Val> {
308	let builders = crate::gc_values::Builders::of(&mut |name| caller.get_export(name))?;
309	builders.text_node(bytes, &mut wasmtime::AsContextMut::as_context_mut(caller))
310}
311
312/// The raw arguments of a native call: up to 8 integers and pointers, and up to 8 floats, each kind in the order of the
313/// C parameters (the ARM64 and x86-64 conventions give them separate registers: `ldexp(double, int)` takes d0 and x0)
314#[derive(Default)]
315struct NativeArguments {
316	integers: [u64; 8],
317	floats: [f64; 8],
318}
319
320/// The arguments of a native call, one per C parameter: numbers as they are (a `float` in the low half of its
321/// register), a memory pointer as the address of its text in linear memory, a handle id as its C pointer; the first
322/// out-pointer gets the out slot, others NULL, error-shaped ones a slot each; a byte or struct parameter points to its
323/// copy, the length after it says how many; a struct output points to zeroed bytes read back after the call
324fn native_arguments<T: CallsC>(
325	caller: &mut Caller<'_, T>,
326	function: &CFunction,
327	layouts: &[Option<CStruct>],
328	params: &[Val],
329	memory: &mut CallMemory,
330) -> Result<NativeArguments> {
331	let name = function.name.as_str();
332	let mut args = NativeArguments::default();
333	let (mut integer, mut float) = (0, 0);
334	let mut warp_params = params.iter().peekable();
335	let mut first_out = true;
336	let error_outs = function.param_types.iter().filter(|param| **param == ParamType::ErrorOut).count();
337	memory.error_slots = vec![0; error_outs];
338	let mut error_index = 0;
339	// how many items the last bytes or structs parameter had: the Length after it
340	let mut count = 0;
341	for (index, ptype) in function.param_types.iter().enumerate() {
342		let layout = || {
343			layouts
344				.get(index)
345				.cloned()
346				.flatten()
347				.ok_or_else(|| wasmtime::Error::msg(format!("{name}: no struct layout")))
348		};
349		let value = match ptype {
350			ParamType::Out => {
351				let slot = std::mem::take(&mut first_out).then_some(&mut *memory.out_slot as *mut usize as u64);
352				Some(slot.unwrap_or(0))
353			}
354			ParamType::ErrorOut => {
355				error_index += 1;
356				memory.error_slots.get_mut(error_index - 1).map(|slot| slot as *mut usize as u64)
357			}
358			ParamType::Length => Some(count),
359			ParamType::StructOut | ParamType::StructsOut => {
360				let layout = layout()?;
361				// the size_t after a list output is passed by warp: how many
362				let items = (*ptype == ParamType::StructsOut)
363					.then(|| warp_params.peek().map_or(0, |next| next.i64().unwrap_or(0).max(0) as usize));
364				memory.buffers.push(vec![0; layout.size() * items.unwrap_or(1)]);
365				let pointer = memory.buffers.last().map(|bytes| bytes.as_ptr() as u64);
366				if memory.output.is_none() {
367					memory.output = Some((layout, items, memory.buffers.len() - 1));
368				}
369				pointer
370			}
371			passed => {
372				let Some(param) = warp_params.next() else { break };
373				match passed {
374					ParamType::I32 => Some(param.unwrap_i32() as u64),
375					ParamType::I64 => Some(param.unwrap_i64() as u64),
376					ParamType::Ptr => Some(memory_pointer(caller, param.unwrap_i32() as usize) as u64),
377					ParamType::Handle => Some(handle_pointer(caller, name, param.unwrap_i32())? as u64),
378					ParamType::F32 | ParamType::F64 => {
379						let bits = if *passed == ParamType::F32 {
380							param.unwrap_f32().to_bits() as u64
381						} else {
382							crate::floats::canonical_nan(param.unwrap_f64()).to_bits()
383						};
384						*args
385							.floats
386							.get_mut(float)
387							.ok_or_else(|| wasmtime::Error::msg(format!("{name}: more than 8 float parameters")))? = f64::from_bits(bits);
388						float += 1;
389						None
390					}
391					ParamType::Bytes => {
392						memory.buffers.push(byte_values(caller, name, param)?);
393						count = memory.buffers.last().map_or(0, Vec::len) as u64;
394						memory.buffers.last().map(|bytes| bytes.as_ptr() as u64)
395					}
396					ParamType::StructIn | ParamType::StructsIn => {
397						let (pointer, items) = struct_values(caller, name, param, &layout()?, *passed, memory)?;
398						count = items as u64;
399						Some(pointer as u64)
400					}
401					_ => unreachable!("out-pointers, lengths and outputs are filled above"),
402				}
403			}
404		};
405		if let Some(value) = value {
406			*args
407				.integers
408				.get_mut(integer)
409				.ok_or_else(|| wasmtime::Error::msg(format!("{name}: more than 8 integer or pointer parameters")))? = value;
410			integer += 1;
411		}
412	}
413	Ok(args)
414}
415
416/// The C pointer of a handle id
417fn handle_pointer<T: CallsC>(caller: &mut Caller<'_, T>, name: &str, id: i32) -> Result<usize> {
418	caller
419		.data_mut()
420		.c_handles()
421		.pointer(id)
422		.ok_or_else(|| wasmtime::Error::msg(format!("{name}: {id} is no C handle of this run")))
423}
424
425/// A warp value as JSON, as C parameters read it
426fn json_value<T>(caller: &mut Caller<'_, T>, name: &str, value: &Val) -> Result<serde_json::Value> {
427	let memory = caller
428		.get_export(crate::gc_values::MEMORY)
429		.and_then(|export| export.into_memory())
430		.ok_or_else(|| wasmtime::Error::msg(format!("{name}: the program exports no memory")))?;
431	crate::gc_values::json_of(value, &mut wasmtime::AsContextMut::as_context_mut(caller), memory)
432}
433
434/// A struct parameter's pointer and item count: a map (a list of maps for StructsIn) written into a buffer of
435/// `memory`, a handle id as its pointer, ø as NULL
436fn struct_values<T: CallsC>(
437	caller: &mut Caller<'_, T>,
438	name: &str,
439	value: &Val,
440	layout: &CStruct,
441	ptype: ParamType,
442	memory: &mut CallMemory,
443) -> Result<(usize, usize)> {
444	let value = json_value(caller, name, value)?;
445	let items = match (value, ptype) {
446		(serde_json::Value::Null, _) => return Ok((0, 0)),
447		(serde_json::Value::Number(id), ParamType::StructIn) => {
448			return Ok((handle_pointer(caller, name, id.as_i64().unwrap_or(-1) as i32)?, 1));
449		}
450		(serde_json::Value::Array(items), ParamType::StructsIn) => items,
451		(item, _) => vec![item],
452	};
453	let mut bytes = vec![0; layout.size() * items.len()];
454	let handles = caller.data_mut().c_handles();
455	for (index, item) in items.iter().enumerate() {
456		layout
457			.write(item, &mut bytes[index * layout.size()..], &mut |id| handles.pointer(id))
458			.map_err(|problem| wasmtime::Error::msg(format!("{name}: {problem}")))?;
459	}
460	memory.buffers.push(bytes);
461	Ok((memory.buffers.last().map_or(0, |bytes| bytes.as_ptr() as usize), items.len()))
462}
463
464/// The bytes a warp value gives a `const uint8_t *`: a text's UTF-8, a list's byte values (each 0 to 255)
465fn byte_values<T>(caller: &mut Caller<'_, T>, name: &str, value: &Val) -> Result<Vec<u8>> {
466	let value = json_value(caller, name, value)?;
467	let not_bytes = |what: &dyn std::fmt::Display| {
468		wasmtime::Error::msg(format!("{name} takes bytes: a text or a list of 0 to 255, not {what}"))
469	};
470	match value {
471		serde_json::Value::String(text) => Ok(text.into_bytes()),
472		serde_json::Value::Null => Ok(vec![]),
473		serde_json::Value::Array(items) => items
474			.iter()
475			.map(|item| item.as_u64().and_then(|byte| u8::try_from(byte).ok()).ok_or_else(|| not_bytes(item)))
476			.collect(),
477		other => Err(not_bytes(&other)),
478	}
479}
480
481/// The address of `offset` in the module's linear memory, NULL without one
482fn memory_pointer<T>(caller: &mut Caller<'_, T>, offset: usize) -> *const u8 {
483	match caller.get_export(crate::gc_values::MEMORY).and_then(|export| export.into_memory()) {
484		Some(memory) => unsafe { memory.data_ptr(&caller).add(offset) },
485		None => std::ptr::null(),
486	}
487}
488
489/// The C pointers a run got, handed to warp as ids: id n is the n-th pointer, 0 is NULL; the same pointer keeps its id.
490/// The table lives in the run's state and goes with it: ids are never addresses, and never outlive the run
491#[derive(Default)]
492pub struct CHandles {
493	pointers: Vec<usize>,
494}
495
496impl CHandles {
497	pub fn id_of(&mut self, pointer: usize) -> i32 {
498		if pointer == 0 {
499			return 0;
500		}
501		let index = self.pointers.iter().position(|known| *known == pointer).unwrap_or_else(|| {
502			self.pointers.push(pointer);
503			self.pointers.len() - 1
504		});
505		index as i32 + 1
506	}
507
508	pub fn pointer(&self, id: i32) -> Option<usize> {
509		match id {
510			0 => Some(0),
511			id if id > 0 => self.pointers.get(id as usize - 1).copied(),
512			_ => None,
513		}
514	}
515}
516
517/// A C function's raw return value as the wasm value of its type
518fn native_result(returned: u64, ret_type: RetType) -> Val {
519	match ret_type {
520		RetType::Void | RetType::I32 => Val::I32(returned as i32),
521		RetType::I64 => Val::I64(returned as i64),
522		RetType::F32 => Val::F32(returned as u32), // the low half of the float register
523		RetType::F64 => Val::F64(returned),
524		RetType::Bool => Val::I32(i32::from(returned as u8 != 0)), // a C bool is its register's low byte
525	}
526}
527
528/// Call a native function with its integer and float arguments in their registers: a C function reads only the ones
529/// its parameters name. Its result: the integer register, or the bits of the float register for a float result
530#[inline(never)]
531unsafe fn call_native_function(func_ptr: usize, args: &NativeArguments, ret_type: RetType) -> u64 {
532	let [i0, i1, i2, i3, i4, i5, i6, i7] = args.integers;
533	let [f0, f1, f2, f3, f4, f5, f6, f7] = args.floats;
534	if matches!(ret_type, RetType::F32 | RetType::F64) {
535		let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
536			std::mem::transmute(func_ptr);
537		f(i0, i1, i2, i3, i4, i5, i6, i7, f0, f1, f2, f3, f4, f5, f6, f7).to_bits()
538	} else {
539		let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64, f64, f64, f64, f64, f64, f64, f64, f64) -> u64 =
540			std::mem::transmute(func_ptr);
541		f(i0, i1, i2, i3, i4, i5, i6, i7, f0, f1, f2, f3, f4, f5, f6, f7)
542	}
543}