Skip to main content

scheme_rs/
runtime.rs

1//! Scheme-rs core runtime.
2//!
3//! The [`Runtime`] struct initializes and stores the core runtime for
4//! scheme-rs. It contains a registry of libraries and the memory associated
5//! with the JIT compiled [`Procedures`](Procedure).
6
7use crate::{
8    ast::{DefinitionBody, Primitive},
9    cps::{Compile, Cps, codegen::RuntimeFunctionsBuilder},
10    env::{Environment, Global, TopLevelEnvironment},
11    exceptions::{Exception, SourceCache, raise},
12    gc::{Gc, GcInner, Trace, init_gc},
13    hashtables::EqualHashSet,
14    lists::{Pair, list_to_vec},
15    num,
16    ports::{BufferMode, Port, Transcoder},
17    proc::{Application, ContBarrier, ContinuationPtr, FuncPtr, ProcDebugInfo, Procedure, UserPtr},
18    registry::Registry,
19    symbols::Symbol,
20    syntax::{Identifier, Span, Syntax},
21    value::{Cell, UnpackedValue, Value},
22};
23use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockWriteGuard};
24use scheme_rs_macros::{maybe_async, maybe_await, runtime_fn};
25use std::{
26    collections::{BTreeSet, HashSet},
27    mem::ManuallyDrop,
28    path::Path,
29    sync::Arc,
30};
31
32/// Scheme-rs core runtime
33///
34/// Practically, the runtime is the core entry point for running Scheme programs
35/// with scheme-rs. It initializes the garbage collector and JIT compiler tasks
36/// and creates a new library registry.
37///
38/// There is not much you can do with a Runtime beyond creating it and using it
39/// to [run programs](Runtime::run_program), however a lot of functions require
40/// it as an arguments, such as [TopLevelEnvironment::new_repl].
41///
42/// You can also use the runtime to [define libraries](Runtime::def_lib) from
43/// Rust code.
44///
45/// Runtime is automatically reference counted, so if you have all of the
46/// procedures you need you can drop it without any issue.
47///
48/// # Safety
49///
50/// The runtime contains the only live references to the Cranelift Context and
51/// therefore modules and allocated functions in the form a Sender of
52/// compilation tasks.
53///
54/// When that sender's ref count is zero, it will cause the receiver to fail and
55/// the compilation task will exit, allowing for a graceful shutdown.
56///
57/// However, this is dropping a lifetime. If we clone a procedure and drop the
58/// runtime from whence it was cleaved, we're left with a dangling pointer.
59///
60/// In order to remedy this it is vitally important the closure has a back
61/// pointer to the runtime.
62#[derive(Trace, Clone)]
63pub struct Runtime(pub(crate) Gc<RwLock<RuntimeInner>>);
64
65impl Default for Runtime {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl Runtime {
72    /// Creates a new runtime. Also initializes the garbage collector and
73    /// creates a default registry with the bridge functions populated.
74    pub fn new() -> Self {
75        let this = Self(Gc::new(RwLock::new(RuntimeInner::new())));
76        let new_registry = Registry::new(&this);
77        this.0.write().registry = new_registry;
78        this
79    }
80
81    /// Run a program at the given location and return the values.
82    #[maybe_async]
83    pub fn run_program(&self, path: &Path) -> Result<Vec<Value>, Exception> {
84        #[cfg(not(feature = "async"))]
85        use std::fs::File;
86
87        #[cfg(feature = "tokio")]
88        use tokio::fs::File;
89
90        let progm = TopLevelEnvironment::new_program(self, path);
91        let env = Environment::Top(progm.clone());
92
93        let mut form = {
94            let port = Port::new(
95                path.display(),
96                maybe_await!(File::open(path)).map_err(Exception::io_error)?,
97                BufferMode::Block,
98                Some(Transcoder::native()),
99            );
100            let file_name = path.file_name().unwrap().to_str().unwrap_or("<unknown>");
101            let span = Span::new(file_name);
102            maybe_await!(port.all_sexprs(span)).map_err(Exception::from)?
103        };
104
105        form.add_scope(progm.scope());
106
107        // Check if the first form is an import
108        let add_rnrs_import = if let Some(first_form) = form.car()
109            && let Some(Syntax::Identifier { ident, .. }) = first_form.car()
110            && let Some(binding) = ident.resolve()
111        {
112            env.lookup_primitive(binding) != Some(Primitive::Import)
113        } else {
114            true
115        };
116
117        // If the first form is not an import, import (rnrs)
118        if add_rnrs_import {
119            maybe_await!(env.import("(library (rnrs))".parse()?))?;
120        }
121
122        let body = maybe_await!(DefinitionBody::parse_lib_body(self, &form, &env))?;
123        let compiled = body.compile_top_level();
124        let closure = maybe_await!(self.compile_expr(compiled));
125
126        maybe_await!(Application::new(closure, Vec::new()).eval(&mut ContBarrier::default()))
127    }
128
129    /// Define a library from Rust code. Useful if file system access is disabled.
130    #[cfg(not(feature = "async"))]
131    #[track_caller]
132    pub fn def_lib(&self, lib: &str) -> Result<(), Exception> {
133        use std::panic::Location;
134
135        self.get_registry()
136            .def_lib(self, lib, Location::caller().file())
137    }
138
139    /// Define a library from Rust code. Useful if file system access is disabled.
140    #[cfg(feature = "async")]
141    pub async fn def_lib(&self, lib: &str) -> Result<(), Exception> {
142        use std::panic::Location;
143
144        self.get_registry()
145            .def_lib(self, lib, Location::caller().file())
146            .await
147    }
148
149    pub(crate) fn get_registry(&self) -> Registry {
150        self.0.read().registry.clone()
151    }
152
153    #[maybe_async]
154    pub(crate) fn compile_expr(&self, expr: Cps) -> Procedure {
155        let (completion_tx, completion_rx) = completion();
156        let task = CompilationTask {
157            completion_tx,
158            compilation_unit: expr,
159            runtime: self.clone(),
160        };
161        let sender = { self.0.read().compilation_buffer_tx.clone() };
162        let _ = maybe_await!(sender.send(task));
163        // Wait for the compilation task to complete:
164        maybe_await!(recv_procedure(completion_rx))
165    }
166
167    pub(crate) unsafe fn from_raw_inc_rc(rt: *mut GcInner<RwLock<RuntimeInner>>) -> Self {
168        unsafe { Self(Gc::from_raw_inc_rc(rt)) }
169    }
170
171    pub fn source_cache(&self) -> MappedRwLockWriteGuard<'_, SourceCache> {
172        RwLockWriteGuard::map(self.0.write(), |inner| &mut inner.source_cache)
173    }
174}
175
176#[allow(unused)]
177#[cfg(not(feature = "async"))]
178fn read_to_string(path: &Path) -> std::io::Result<String> {
179    std::fs::read_to_string(path)
180}
181
182#[allow(unused)]
183#[cfg(feature = "tokio")]
184async fn read_to_string(path: &Path) -> std::io::Result<String> {
185    tokio::fs::read_to_string(path).await
186}
187
188#[cfg(not(feature = "async"))]
189type CompilationBufferTx = std::sync::mpsc::SyncSender<CompilationTask>;
190#[cfg(not(feature = "async"))]
191type CompilationBufferRx = std::sync::mpsc::Receiver<CompilationTask>;
192
193#[cfg(feature = "async")]
194type CompilationBufferTx = tokio::sync::mpsc::Sender<CompilationTask>;
195#[cfg(feature = "async")]
196type CompilationBufferRx = tokio::sync::mpsc::Receiver<CompilationTask>;
197
198#[derive(Trace)]
199pub(crate) struct RuntimeInner {
200    /// Package registry
201    pub(crate) registry: Registry,
202    /// Channel to compilation task
203    compilation_buffer_tx: CompilationBufferTx,
204    pub(crate) constants_pool: EqualHashSet,
205    pub(crate) globals_pool: HashSet<Global>,
206    pub(crate) debug_info: DebugInfo,
207    pub(crate) source_cache: SourceCache,
208}
209
210impl Default for RuntimeInner {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216const MAX_COMPILATION_TASKS: usize = 5; // Shrug
217
218#[cfg(not(feature = "async"))]
219fn compilation_buffer() -> (CompilationBufferTx, CompilationBufferRx) {
220    std::sync::mpsc::sync_channel(MAX_COMPILATION_TASKS)
221}
222
223#[cfg(feature = "async")]
224fn compilation_buffer() -> (CompilationBufferTx, CompilationBufferRx) {
225    tokio::sync::mpsc::channel(MAX_COMPILATION_TASKS)
226}
227
228impl RuntimeInner {
229    fn new() -> Self {
230        // Ensure the GC is initialized:
231        init_gc();
232        let (compilation_buffer_tx, compilation_buffer_rx) = compilation_buffer();
233        std::thread::spawn(move || compilation_task(compilation_buffer_rx));
234        RuntimeInner {
235            registry: Registry::empty(),
236            compilation_buffer_tx,
237            constants_pool: EqualHashSet::new(),
238            globals_pool: HashSet::new(),
239            debug_info: DebugInfo::default(),
240            source_cache: SourceCache::default(),
241        }
242    }
243}
244
245#[derive(Trace, Clone, Debug, Default)]
246pub(crate) struct DebugInfo {
247    /// Stored user function debug information:
248    stored_func_info: Vec<Arc<ProcDebugInfo>>,
249}
250
251impl DebugInfo {
252    pub fn store_func_info(&mut self, debug_info: Arc<ProcDebugInfo>) {
253        self.stored_func_info.push(debug_info);
254    }
255}
256
257#[cfg(not(feature = "async"))]
258type CompletionTx = std::sync::mpsc::SyncSender<Procedure>;
259#[cfg(not(feature = "async"))]
260type CompletionRx = std::sync::mpsc::Receiver<Procedure>;
261
262#[cfg(feature = "async")]
263type CompletionTx = tokio::sync::oneshot::Sender<Procedure>;
264#[cfg(feature = "async")]
265type CompletionRx = tokio::sync::oneshot::Receiver<Procedure>;
266
267#[cfg(not(feature = "async"))]
268fn completion() -> (CompletionTx, CompletionRx) {
269    std::sync::mpsc::sync_channel(1)
270}
271
272#[cfg(feature = "async")]
273fn completion() -> (CompletionTx, CompletionRx) {
274    tokio::sync::oneshot::channel()
275}
276
277#[cfg(not(feature = "async"))]
278fn recv_procedure(rx: CompletionRx) -> Procedure {
279    rx.recv().unwrap()
280}
281
282#[cfg(feature = "async")]
283async fn recv_procedure(rx: CompletionRx) -> Procedure {
284    rx.await.unwrap()
285}
286
287struct CompilationTask {
288    compilation_unit: Cps,
289    completion_tx: CompletionTx,
290    /// Since Contexts are per-thread, we will only ever see the same Runtime.
291    /// However, we can't cache the Runtime, as that would cause a ref cycle
292    /// that would prevent the last compilation buffer sender to drop.
293    /// Therefore, its lifetime is that of the compilation task
294    runtime: Runtime,
295}
296
297#[cfg(not(feature = "async"))]
298fn recv_compilation_task(rx: &mut CompilationBufferRx) -> Option<CompilationTask> {
299    rx.recv().ok()
300}
301
302#[cfg(feature = "async")]
303fn recv_compilation_task(rx: &mut CompilationBufferRx) -> Option<CompilationTask> {
304    rx.blocking_recv()
305}
306
307fn compilation_task(mut compilation_queue_rx: CompilationBufferRx) {
308    use cranelift::prelude::*;
309    use cranelift_jit::{JITBuilder, JITModule};
310
311    let mut flag_builder = settings::builder();
312    flag_builder.set("use_colocated_libcalls", "false").unwrap();
313    // FIXME set back to true once the x64 backend supports it.
314    flag_builder.set("is_pic", "false").unwrap();
315    let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
316        panic!("host machine is not supported: {msg}");
317    });
318    let isa = isa_builder
319        .finish(settings::Flags::new(flag_builder))
320        .unwrap();
321
322    let mut jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
323
324    for runtime_fn in inventory::iter::<RuntimeFn> {
325        (runtime_fn.install_symbol)(&mut jit_builder);
326    }
327
328    let mut module = JITModule::new(jit_builder);
329    let mut runtime_funcs_builder = RuntimeFunctionsBuilder::default();
330
331    for runtime_fn in inventory::iter::<RuntimeFn> {
332        (runtime_fn.install_decl)(&mut runtime_funcs_builder, &mut module);
333    }
334
335    let runtime_funcs = runtime_funcs_builder.build().unwrap();
336
337    // By storing all of the debug information in the same lifetime as the
338    // Context, we can directly put pointers referencing the debug information
339    // in our JIT compiled functions:
340    let mut debug_info = DebugInfo::default();
341
342    while let Some(task) = recv_compilation_task(&mut compilation_queue_rx) {
343        let CompilationTask {
344            completion_tx,
345            compilation_unit,
346            runtime,
347        } = task;
348
349        let proc =
350            compilation_unit.into_procedure(runtime, &runtime_funcs, &mut module, &mut debug_info);
351
352        let _ = completion_tx.send(proc);
353    }
354
355    // Free the JITed memory
356    unsafe {
357        module.free_memory();
358    }
359}
360
361pub(crate) struct RuntimeFn {
362    install_decl:
363        for<'a> fn(&'a mut RuntimeFunctionsBuilder, module: &'a mut cranelift_jit::JITModule),
364    install_symbol: for<'a> fn(&'a mut cranelift_jit::JITBuilder),
365}
366
367impl RuntimeFn {
368    pub(crate) const fn new(
369        install_decl: for<'a> fn(
370            &'a mut RuntimeFunctionsBuilder,
371            module: &'a mut cranelift_jit::JITModule,
372        ),
373        install_symbol: for<'a> fn(&'a mut cranelift_jit::JITBuilder),
374    ) -> Self {
375        Self {
376            install_decl,
377            install_symbol,
378        }
379    }
380}
381
382inventory::collect!(RuntimeFn);
383
384unsafe fn arc_from_ptr<T>(ptr: *const T) -> Option<Arc<T>> {
385    unsafe {
386        if ptr.is_null() {
387            return None;
388        }
389        Arc::increment_strong_count(ptr);
390        Some(Arc::from_raw(ptr))
391    }
392}
393
394/// Allocate a new Gc with a value of undefined
395#[runtime_fn]
396unsafe extern "C" fn alloc_cell() -> *const () {
397    Value::into_raw(Value::from(Cell(Gc::new(RwLock::new(Value::undefined())))))
398}
399
400/// Read the value of a Cell
401#[runtime_fn]
402unsafe extern "C" fn read_cell(cell: *const ()) -> *const () {
403    unsafe {
404        let cell = Value::from_raw(cell);
405        let cell: Cell = cell.try_into().unwrap();
406        // We do not need to increment the reference count of the cell, it is going to
407        // be decremented at the end of this function.
408        let cell = ManuallyDrop::new(cell);
409        let cell_read = cell.0.read();
410        Value::as_raw(&cell_read)
411    }
412}
413
414/// Decrement the reference count of a value
415#[runtime_fn]
416unsafe extern "C" fn dropv(val: *const *const (), num_drops: u32) {
417    unsafe {
418        for i in 0..num_drops {
419            drop(Value::from_raw(val.add(i as usize).read()));
420        }
421    }
422}
423
424/// Create a boxed application
425#[runtime_fn]
426unsafe extern "C" fn apply(
427    runtime: *mut GcInner<RwLock<RuntimeInner>>,
428    op: *const (),
429    args: *const *const (),
430    num_args: u32,
431    barrier: *mut ContBarrier,
432) -> *mut Application {
433    unsafe {
434        let args: Vec<_> = (0..num_args)
435            .map(|i| Value::from_raw_inc_rc(args.add(i as usize).read()))
436            .collect();
437
438        let op = match Value::from_raw_inc_rc(op).unpack() {
439            UnpackedValue::Procedure(op) => op,
440            x => {
441                let raised = raise(
442                    Runtime::from_raw_inc_rc(runtime),
443                    Exception::invalid_operator(x.type_name()).into(),
444                    barrier.as_mut().unwrap_unchecked(),
445                );
446                return Box::into_raw(Box::new(raised));
447            }
448        };
449
450        let app = Application::new(op, args);
451
452        Box::into_raw(Box::new(app))
453    }
454}
455
456/// Get a frame from a procedure and a span
457#[runtime_fn]
458unsafe extern "C" fn get_frame(op: *const (), span: *const ()) -> *const () {
459    unsafe {
460        let op = Value::from_raw_inc_rc(op);
461        let Some(op) = op.cast_to_scheme_type::<Procedure>() else {
462            return Value::into_raw(Value::null());
463        };
464        let span = Value::from_raw_inc_rc(span);
465        let span = span.cast_to_rust_type::<Span>().unwrap();
466        let frame = Syntax::Identifier {
467            ident: Identifier {
468                sym: op
469                    .get_debug_info()
470                    .map_or_else(|| Symbol::intern("<lambda>"), |dbg| dbg.name),
471                scopes: BTreeSet::new(),
472            },
473            span: span.as_ref().clone(),
474        };
475        Value::into_raw(Value::from(frame))
476    }
477}
478
479/// Set the value for continuation mark
480#[runtime_fn]
481unsafe extern "C" fn set_continuation_mark(
482    tag: *const (),
483    val: *const (),
484    barrier: *mut ContBarrier,
485) {
486    unsafe {
487        let tag = Value::from_raw_inc_rc(tag);
488        let val = Value::from_raw_inc_rc(val);
489        barrier
490            .as_mut()
491            .unwrap()
492            .set_continuation_mark(tag.cast_to_scheme_type().unwrap(), val);
493    }
494}
495
496/// Create a boxed application that simply returns its arguments
497#[runtime_fn]
498pub(crate) unsafe extern "C" fn halt(args: *const ()) -> *mut Application {
499    unsafe {
500        // We do not need to increment the rc here, it will be incremented in list_to_vec
501        let args = ManuallyDrop::new(Value::from_raw(args));
502        let mut flattened = Vec::new();
503        list_to_vec(&args, &mut flattened);
504        let app = Application::halt_ok(flattened);
505        Box::into_raw(Box::new(app))
506    }
507}
508
509/// Replace the value pointed to at to with the value contained in from.
510#[runtime_fn]
511unsafe extern "C" fn store(from: *const (), to: *const ()) {
512    unsafe {
513        // We do not need to increment the ref count for to, it is dropped
514        // immediately.
515        let from = Value::from_raw_inc_rc(from);
516        let to: ManuallyDrop<Cell> = ManuallyDrop::new(Value::from_raw(to).try_into().unwrap());
517        *to.0.write() = from;
518    }
519}
520
521/// Return the car of the argument
522#[runtime_fn]
523unsafe extern "C" fn car(val: *const (), error: *mut Value) -> *const () {
524    unsafe {
525        let val = ManuallyDrop::new(Value::from_raw(val));
526        match val.try_to_scheme_type::<Pair>() {
527            Ok(pair) => Value::into_raw(pair.car()),
528            Err(condition) => {
529                error.write(condition.into());
530                Value::into_raw(Value::undefined())
531            }
532        }
533    }
534}
535
536/// Return the cdr of the argument
537#[runtime_fn]
538unsafe extern "C" fn cdr(val: *const (), error: *mut Value) -> *const () {
539    unsafe {
540        let val = ManuallyDrop::new(Value::from_raw(val));
541        match val.try_to_scheme_type::<Pair>() {
542            Ok(pair) => Value::into_raw(pair.cdr()),
543            Err(condition) => {
544                error.write(condition.into());
545                Value::into_raw(Value::undefined())
546            }
547        }
548    }
549}
550
551/// Return the cons of the two arguments
552#[runtime_fn]
553unsafe extern "C" fn cons(car: *const (), cdr: *const ()) -> *const () {
554    unsafe {
555        let car = Value::from_raw_inc_rc(car);
556        let cdr = Value::from_raw_inc_rc(cdr);
557        Value::into_raw(Value::from(Pair::mutable(car, cdr)))
558    }
559}
560
561/// Return the proper list of the arguments
562#[runtime_fn]
563unsafe extern "C" fn list(vals: *const *const (), num_vals: u32) -> *const () {
564    let mut list = Value::null();
565    unsafe {
566        for i in (0..num_vals).rev() {
567            list = Value::from(Pair::mutable(
568                Value::from_raw_inc_rc(vals.add(i as usize).read()),
569                list,
570            ));
571        }
572    }
573    Value::into_raw(list)
574}
575
576/// Allocate a continuation
577#[runtime_fn]
578unsafe extern "C" fn make_continuation(
579    runtime: *mut GcInner<RwLock<RuntimeInner>>,
580    fn_ptr: ContinuationPtr,
581    env: *const *const (),
582    num_envs: u32,
583    num_required_args: u32,
584    variadic: bool,
585    barrier: *mut ContBarrier,
586) -> *const () {
587    unsafe {
588        // Collect the environment:
589        let env: Vec<_> = (0..num_envs)
590            .map(|i| Value::from_raw_inc_rc(env.add(i as usize).read()))
591            .collect();
592
593        let proc = barrier.as_mut().unwrap().new_k(
594            Runtime::from_raw_inc_rc(runtime),
595            env,
596            fn_ptr,
597            num_required_args as usize,
598            variadic,
599        );
600
601        Value::into_raw(Value::from(proc))
602    }
603}
604
605/// Allocate a user function
606#[runtime_fn]
607unsafe extern "C" fn make_user(
608    runtime: *mut GcInner<RwLock<RuntimeInner>>,
609    fn_ptr: UserPtr,
610    env: *const *const (),
611    num_envs: u32,
612    num_required_args: u32,
613    variadic: bool,
614    debug_info: *const ProcDebugInfo,
615) -> *const () {
616    unsafe {
617        // Collect the environment:
618        let env: Vec<_> = (0..num_envs)
619            .map(|i| Value::from_raw_inc_rc(env.add(i as usize).read()))
620            .collect();
621
622        let proc = Procedure::with_debug_info(
623            Runtime::from_raw_inc_rc(runtime),
624            env,
625            FuncPtr::User(fn_ptr),
626            num_required_args as usize,
627            variadic,
628            arc_from_ptr(debug_info),
629        );
630
631        Value::into_raw(Value::from(proc))
632    }
633}
634
635/// Return an error in the case that a value is undefined
636#[runtime_fn]
637unsafe extern "C" fn error_unbound_variable(symbol: u32) -> *const () {
638    let sym = Symbol(symbol);
639    let condition = Exception::error(format!("undefined variable {sym}"));
640    Value::into_raw(Value::from(condition))
641}
642
643#[runtime_fn]
644unsafe extern "C" fn add(vals: *const *const (), num_vals: u32, error: *mut Value) -> *const () {
645    unsafe {
646        let vals: Vec<_> = (0..num_vals)
647            // Can't easily wrap these in a ManuallyDrop, so we dec the rc.
648            .map(|i| Value::from_raw_inc_rc(vals.add(i as usize).read()))
649            .collect();
650        match num::add_prim(&vals) {
651            Ok(num) => Value::into_raw(Value::from(num)),
652            Err(condition) => {
653                error.write(condition.into());
654                Value::into_raw(Value::undefined())
655            }
656        }
657    }
658}
659
660#[runtime_fn]
661unsafe extern "C" fn sub(vals: *const *const (), num_vals: u32, error: *mut Value) -> *const () {
662    unsafe {
663        let vals: Vec<_> = (0..num_vals)
664            .map(|i| Value::from_raw_inc_rc(vals.add(i as usize).read()))
665            .collect();
666        match num::sub_prim(&vals[0], &vals[1..]) {
667            Ok(num) => Value::into_raw(Value::from(num)),
668            Err(condition) => {
669                error.write(condition.into());
670                Value::into_raw(Value::undefined())
671            }
672        }
673    }
674}
675
676#[runtime_fn]
677unsafe extern "C" fn mul(vals: *const *const (), num_vals: u32, error: *mut Value) -> *const () {
678    unsafe {
679        let vals: Vec<_> = (0..num_vals)
680            .map(|i| Value::from_raw_inc_rc(vals.add(i as usize).read()))
681            .collect();
682        match num::mul_prim(&vals) {
683            Ok(num) => Value::into_raw(Value::from(num)),
684            Err(condition) => {
685                error.write(condition.into());
686                Value::into_raw(Value::undefined())
687            }
688        }
689    }
690}
691
692#[runtime_fn]
693unsafe extern "C" fn div(vals: *const *const (), num_vals: u32, error: *mut Value) -> *const () {
694    unsafe {
695        let vals: Vec<_> = (0..num_vals)
696            .map(|i| Value::from_raw_inc_rc(vals.add(i as usize).read()))
697            .collect();
698        match num::div_prim(&vals[0], &vals[1..]) {
699            Ok(num) => Value::into_raw(Value::from(num)),
700            Err(condition) => {
701                error.write(condition.into());
702                Value::into_raw(Value::undefined())
703            }
704        }
705    }
706}
707
708macro_rules! define_comparison_fn {
709    ( $name:ident, $prim:ident ) => {
710        #[runtime_fn]
711        unsafe extern "C" fn $name(
712            vals: *const *const (),
713            num_vals: u32,
714            error: *mut Value,
715        ) -> *const () {
716            unsafe {
717                let vals: Vec<_> = (0..num_vals)
718                    .map(|i| Value::from_raw_inc_rc(vals.add(i as usize).read()))
719                    .collect();
720                match num::$prim(&vals) {
721                    Ok(res) => Value::into_raw(Value::from(res)),
722                    Err(condition) => {
723                        error.write(condition.into());
724                        Value::into_raw(Value::undefined())
725                    }
726                }
727            }
728        }
729    };
730}
731
732define_comparison_fn!(equal, equal_prim);
733define_comparison_fn!(greater, greater_prim);
734define_comparison_fn!(greater_equal, greater_equal_prim);
735define_comparison_fn!(lesser, lesser_prim);
736define_comparison_fn!(lesser_equal, lesser_equal_prim);