Skip to main content

myriad/
builtins.rs

1use crate::{Heap, Value};
2use crate::devices::DeviceTable;
3use crate::devices::{console, CONSOLE_ID};
4use crate::value::{alloc_string, read_string};
5use alloc::collections::BTreeMap;
6use alloc::rc::Rc;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9
10pub mod fmath {
11    pub fn sqrt(x: f64) -> f64 { libm::sqrt(x) }
12    pub fn sin(x: f64) -> f64 { libm::sin(x) }
13    pub fn cos(x: f64) -> f64 { libm::cos(x) }
14    pub fn floor(x: f64) -> f64 { libm::floor(x) }
15    pub fn ceil(x: f64) -> f64 { libm::ceil(x) }
16    pub fn abs(x: f64) -> f64 { libm::fabs(x) }
17    pub fn fmax(a: f64, b: f64) -> f64 { libm::fmax(a, b) }
18    pub fn fmin(a: f64, b: f64) -> f64 { libm::fmin(a, b) }
19}
20
21pub struct NativeCtx<'a> {
22    pub heap: &'a mut Heap,
23    pub devices: &'a mut DeviceTable,
24    pub halted: &'a mut bool,
25    pub exit_code: &'a mut Option<i64>,
26}
27
28// (raw, is_handle) — interpreter updates caller's frame mask bit accordingly.
29pub type NativeFn = Rc<dyn for<'a> Fn(&mut NativeCtx<'a>, &[Value]) -> Result<(Value, bool), String>>;
30
31#[derive(Default, Clone)]
32pub struct NativeRegistry {
33    fns: BTreeMap<String, NativeFn>,
34}
35
36impl NativeRegistry {
37    pub fn new() -> Self { Self { fns: BTreeMap::new() } }
38
39    pub fn register<S: Into<String>>(&mut self, name: S, func: NativeFn) {
40        self.fns.insert(name.into(), func);
41    }
42
43    pub fn get(&self, name: &str) -> Option<&NativeFn> {
44        self.fns.get(name)
45    }
46}
47
48pub fn register_default_builtins(reg: &mut NativeRegistry) {
49    reg.register("__concat",    concat_native());
50    reg.register("__to_str",    to_str_native());
51    reg.register("__float_abs", float_abs_native());
52    reg.register("__float_max", float_max_native());
53    reg.register("__float_min", float_min_native());
54    reg.register("__int_to_f",    int_to_f_native());
55    reg.register("__char_to_f",   char_to_f_native());
56    reg.register("__bool_to_f",   bool_to_f_native());
57    reg.register("__float_to_i",  float_to_i_native());
58    reg.register("__char_to_i",   char_to_i_native());
59    reg.register("__bool_to_i",   bool_to_i_native());
60    reg.register("__int_to_c",    int_to_c_native());
61    reg.register("__int_to_s",    int_to_s_native());
62    reg.register("__float_to_s",  float_to_s_native());
63    reg.register("__bool_to_s",   bool_to_s_native());
64    reg.register("__char_to_s",   char_to_s_native());
65    reg.register("__string_to_s", string_to_s_native());
66    reg.register("__unit_to_s",   unit_to_s_native());
67
68    reg.register("print",       print_native());
69    reg.register("println",     println_native());
70
71    reg.register("__int_abs",   abs_native());
72    reg.register("ceil",        ceil_native());
73    reg.register("flr",         flr_native());
74    reg.register("cos",         cos_native());
75    reg.register("sin",         sin_native());
76    reg.register("sqrt",        sqrt_native());
77    reg.register("__int_max",   max_native());
78    reg.register("__int_min",   min_native());
79
80    reg.register("halt",        halt_native());
81    reg.register("abort",       abort_native());
82
83    // Frame-yield intrinsic: do_call intercepts it before the body runs, so this
84    // registration only makes the name resolvable like any other runtime native.
85    reg.register("__frame_present", frame_present_native());
86}
87
88fn frame_present_native() -> NativeFn {
89    Rc::new(|_ctx, _args| Err("__frame_present must be intercepted as a frame yield".into()))
90}
91
92#[inline]
93fn plain(v: Value) -> (Value, bool) { (v, false) }
94#[inline]
95fn handle(v: Value) -> (Value, bool) { (v, true) }
96
97fn concat_native() -> NativeFn {
98    Rc::new(|ctx, args| {
99        if args[0].is_handle_none() {
100            return Err(format!("__concat: arg0 not a String: {:?}", args[0]));
101        }
102        if args[1].is_handle_none() {
103            return Err(format!("__concat: arg1 not a String: {:?}", args[1]));
104        }
105        let (a_slot, a_gen) = args[0].as_handle();
106        let (b_slot, b_gen) = args[1].as_handle();
107
108        // Clamp the user-stated length to the cell's actual payload capacity.
109        // Without this, a malicious St on slot 0 could drive copy_nonoverlapping
110        // past the cell buffer and into host memory.
111        let a_len = {
112            let d = ctx.heap.cell_data(a_slot, a_gen)?;
113            (d[0] as usize).min(d.len().saturating_sub(1) * 8)
114        };
115        let b_len = {
116            let d = ctx.heap.cell_data(b_slot, b_gen)?;
117            (d[0] as usize).min(d.len().saturating_sub(1) * 8)
118        };
119        let total = a_len + b_len;
120        let size = 1 + (total + 7) / 8;
121
122        let (slot, gen_) = ctx.heap.try_alloc(size)?;
123
124        // Source `Box<[u64]>` buffers stay put even if cells Vec reallocs,
125        // so these raw pointers remain valid until next free of a/b.
126        let a_src = ctx.heap.cell_data(a_slot, a_gen)?[1..].as_ptr() as *const u8;
127        let b_src = ctx.heap.cell_data(b_slot, b_gen)?[1..].as_ptr() as *const u8;
128
129        let dst = ctx.heap.cell_data_mut(slot, gen_)?;
130        dst[0] = total as u64;
131        let dst_ptr = dst[1..].as_mut_ptr() as *mut u8;
132        unsafe {
133            core::ptr::copy_nonoverlapping(a_src, dst_ptr, a_len);
134            core::ptr::copy_nonoverlapping(b_src, dst_ptr.add(a_len), b_len);
135        }
136
137        Ok(handle(Value::from_handle(slot, gen_)))
138    })
139}
140
141fn to_str_native() -> NativeFn {
142    // Fallback when compile-time dispatch can't determine type (effect-op returns, etc).
143    Rc::new(|ctx, args| {
144        if let Some(s) = read_string(ctx.heap, args[0]) {
145            let v = alloc_string(ctx.heap, &s)?;
146            return Ok(handle(v));
147        }
148        let v = alloc_string(ctx.heap, &args[0].as_int().to_string())?;
149        Ok(handle(v))
150    })
151}
152
153fn print_native() -> NativeFn {
154    Rc::new(|ctx, args| {
155        if args[0].is_handle_none() {
156            return Err(format!("print: arg0 not a String: {:?}", args[0]));
157        }
158        let (slot, gen_) = args[0].as_handle();
159        let bytes: Vec<u8> = {
160            let d = ctx.heap.cell_data(slot, gen_)?;
161            // Clamp stated length to actual cell payload capacity.
162            let len = (d[0] as usize).min(d.len().saturating_sub(1) * 8);
163            let ptr = d[1..].as_ptr() as *const u8;
164            unsafe { core::slice::from_raw_parts(ptr, len).to_vec() }
165        };
166        write_console(ctx.devices, ctx.heap, &bytes, "print")?;
167        Ok(plain(Value::ZERO))
168    })
169}
170
171fn println_native() -> NativeFn {
172    Rc::new(|ctx, args| {
173        if args[0].is_handle_none() {
174            return Err(format!("println: arg0 not a String: {:?}", args[0]));
175        }
176        let (slot, gen_) = args[0].as_handle();
177        let bytes: Vec<u8> = {
178            let d = ctx.heap.cell_data(slot, gen_)?;
179            let len = (d[0] as usize).min(d.len().saturating_sub(1) * 8);
180            let ptr = d[1..].as_ptr() as *const u8;
181            unsafe { core::slice::from_raw_parts(ptr, len).to_vec() }
182        };
183        write_console(ctx.devices, ctx.heap, &bytes, "println")?;
184        write_console(ctx.devices, ctx.heap, b"\n", "println")?;
185        Ok(plain(Value::ZERO))
186    })
187}
188
189fn write_console(devices: &mut DeviceTable, heap: &mut Heap, bytes: &[u8], op: &str) -> Result<(), String> {
190    let dev = devices.get_mut(CONSOLE_ID)
191        .ok_or_else(|| format!("{}: Console device 0x{:02x} not installed", op, CONSOLE_ID))?;
192    dev.write_bytes(console::PORT_STDOUT, bytes, heap)
193}
194
195fn halt_native() -> NativeFn {
196    Rc::new(|ctx, args| {
197        let code = args[0].as_int();
198        *ctx.exit_code = Some(code & 0xFFFF_FFFF);
199        *ctx.halted = true;
200        Ok(plain(Value::ZERO))
201    })
202}
203
204fn abort_native() -> NativeFn {
205    Rc::new(|ctx, args| {
206        let msg = read_string(ctx.heap, args[0])
207            .ok_or_else(|| format!("abort: arg0 not a String: {:?}", args[0]))?;
208        Err(format!("abort: {}", msg))
209    })
210}
211
212fn abs_native() -> NativeFn {
213    Rc::new(|_ctx, args| {
214        let n = args[0].as_int();
215        Ok(plain(Value::from_int(n.wrapping_abs())))
216    })
217}
218
219fn max_native() -> NativeFn {
220    Rc::new(|_ctx, args| {
221        let a = args[0].as_int();
222        let b = args[1].as_int();
223        Ok(plain(Value::from_int(a.max(b))))
224    })
225}
226
227fn min_native() -> NativeFn {
228    Rc::new(|_ctx, args| {
229        let a = args[0].as_int();
230        let b = args[1].as_int();
231        Ok(plain(Value::from_int(a.min(b))))
232    })
233}
234
235fn float_abs_native() -> NativeFn {
236    Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::abs(args[0].as_float())))))
237}
238
239fn float_max_native() -> NativeFn {
240    Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::fmax(args[0].as_float(), args[1].as_float())))))
241}
242
243fn float_min_native() -> NativeFn {
244    Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::fmin(args[0].as_float(), args[1].as_float())))))
245}
246
247fn int_to_f_native() -> NativeFn {
248    Rc::new(|_ctx, args| Ok(plain(Value::from_float(args[0].as_int() as f64))))
249}
250
251fn char_to_f_native() -> NativeFn {
252    Rc::new(|_ctx, args| {
253        let c = args[0].as_char().ok_or_else(|| format!("__char_to_f: arg0 not Char: {:?}", args[0]))?;
254        Ok(plain(Value::from_float(c as u32 as f64)))
255    })
256}
257
258fn bool_to_f_native() -> NativeFn {
259    Rc::new(|_ctx, args| Ok(plain(Value::from_float(if args[0].as_bool() { 1.0 } else { 0.0 }))))
260}
261
262fn float_to_i_native() -> NativeFn {
263    Rc::new(|_ctx, args| Ok(plain(Value::from_int(args[0].as_float() as i64))))
264}
265
266fn char_to_i_native() -> NativeFn {
267    Rc::new(|_ctx, args| {
268        let c = args[0].as_char().ok_or_else(|| format!("__char_to_i: arg0 not Char: {:?}", args[0]))?;
269        Ok(plain(Value::from_int(c as i64)))
270    })
271}
272
273fn bool_to_i_native() -> NativeFn {
274    Rc::new(|_ctx, args| Ok(plain(Value::from_int(if args[0].as_bool() { 1 } else { 0 }))))
275}
276
277fn int_to_c_native() -> NativeFn {
278    Rc::new(|_ctx, args| {
279        let n = args[0].as_int();
280        let u = u32::try_from(n).map_err(|_| format!("abort: invalid codepoint {}", n))?;
281        let c = char::from_u32(u).ok_or_else(|| format!("abort: invalid codepoint U+{:X}", u))?;
282        Ok(plain(Value::from_char(c)))
283    })
284}
285
286fn int_to_s_native() -> NativeFn {
287    Rc::new(|ctx, args| {
288        let v = alloc_string(ctx.heap, &args[0].as_int().to_string())?;
289        Ok(handle(v))
290    })
291}
292
293fn float_to_s_native() -> NativeFn {
294    Rc::new(|ctx, args| {
295        let v = alloc_string(ctx.heap, &args[0].as_float().to_string())?;
296        Ok(handle(v))
297    })
298}
299
300fn bool_to_s_native() -> NativeFn {
301    Rc::new(|ctx, args| {
302        let v = alloc_string(ctx.heap, &args[0].as_bool().to_string())?;
303        Ok(handle(v))
304    })
305}
306
307fn char_to_s_native() -> NativeFn {
308    Rc::new(|ctx, args| {
309        let c = args[0].as_char().ok_or_else(|| format!("__char_to_s: arg0 not Char: {:?}", args[0]))?;
310        let v = alloc_string(ctx.heap, &c.to_string())?;
311        Ok(handle(v))
312    })
313}
314
315fn string_to_s_native() -> NativeFn {
316    Rc::new(|ctx, args| {
317        if args[0].is_handle_none() {
318            return Err(format!("__string_to_s: arg0 not String: {:?}", args[0]));
319        }
320        let (slot, gen_) = args[0].as_handle();
321        ctx.heap.rc_inc(slot, gen_)?;
322        Ok(handle(args[0]))
323    })
324}
325
326fn unit_to_s_native() -> NativeFn {
327    Rc::new(|ctx, _args| {
328        let v = alloc_string(ctx.heap, "()")?;
329        Ok(handle(v))
330    })
331}
332
333fn ceil_native() -> NativeFn {
334    Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::ceil(args[0].as_float())))))
335}
336
337fn flr_native() -> NativeFn {
338    Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::floor(args[0].as_float())))))
339}
340
341fn cos_native()  -> NativeFn { Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::cos(args[0].as_float()))))) }
342fn sin_native()  -> NativeFn { Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::sin(args[0].as_float()))))) }
343fn sqrt_native() -> NativeFn { Rc::new(|_ctx, args| Ok(plain(Value::from_float(fmath::sqrt(args[0].as_float()))))) }