1use 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
11pub const LIBC: &str = "c";
13const MODULE_ALIASES: [(&str, &str); 2] = [("libm", crate::libm::LIBM), ("libc", LIBC)];
15
16static NATIVE_CALL_TURN: Mutex<()> = Mutex::new(());
20
21const NOT_THREAD_SAFE_LIBRARIES: [&str; 2] = ["sdl", "raylib"];
23
24pub 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
45pub fn link_libc_functions<T: CallsC>(linker: &mut Linker<T>) -> Result<()> {
47 linker.func_wrap(LIBC, "abs", |x: i32| unsafe { abs(x) })?;
48 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 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
87pub 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
93fn 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
98fn 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
104fn 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
116static LOADED_LIBRARIES: OnceLock<Mutex<HashMap<String, Arc<Library>>>> = OnceLock::new();
118
119pub 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
136fn 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 format!("lib{name}.so.6"),
151 ];
152 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
153 return vec![format!("{name}.dll")];
154}
155
156pub 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
170pub 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
179fn 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
191fn 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
209pub 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 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 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#[derive(Default)]
277struct CallMemory {
278 out_slot: Box<usize>,
280 error_slots: Vec<usize>,
282 buffers: Vec<Vec<u8>>,
284 output: Option<(CStruct, Option<usize>, usize)>,
286}
287
288fn 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
306pub 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#[derive(Default)]
315struct NativeArguments {
316 integers: [u64; 8],
317 floats: [f64; 8],
318}
319
320fn 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 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 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
416fn 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
425fn 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
434fn 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
464fn 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
481fn 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#[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
517fn 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), RetType::F64 => Val::F64(returned),
524 RetType::Bool => Val::I32(i32::from(returned as u8 != 0)), }
526}
527
528#[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}