Skip to main content

nanvm_lib/l0/
mod.rs

1//! L0, the core of the WASM runtime interface
2//! ([`l0-design.md`](../../../../doc/sasha-gil/wasm/l0-design.md)), as typed
3//! Rust over [`Any`].
4//!
5//! One function per L0 function, under its L0 name, generic over [`IVm`].
6//! The types are Rust's, not the ABI's: an index is a `u32`, the bytes of
7//! a string or a bigint are a slice, a container is built through a
8//! [`Builder`], and [`call`] answers a `Result`. `dup` and `drop` are
9//! `Clone` and `Drop`, and an argument is borrowed unless the design says
10//! the function takes it.
11//!
12//! Where WASM traps, a function here panics, with the text the
13//! FunctionalScript view of L0 throws, `l0.<name>: <reason>`, so the tests
14//! below and that view's proofs check the same preconditions.
15//!
16//! An object answers its properties as [`Object::own_entries`] lists them:
17//! each key once, array-index keys first, as a JavaScript host and the
18//! FunctionalScript view do. NaNVM keeps an object's properties as they
19//! were written, so a read computes that view each time.
20
21pub mod abi;
22
23#[cfg(test)]
24mod test;
25
26use crate::{
27    common::sized_index::SizedIndex,
28    sign::Sign,
29    vm::{
30        Any, Array, BigInt, Function, IFunction, IVm, Nullish, Number, Object, Property,
31        StaticCode, String, ToAny, ToArray, ToObject, ToString, Unpacked,
32    },
33};
34
35/// A value's kind, as L0 `kind` answers it. The tags are part of the ABI
36/// version.
37#[repr(u8)]
38#[derive(Debug, PartialEq, Eq, Clone, Copy)]
39pub enum Kind {
40    Undefined = 0,
41    Null = 1,
42    Boolean = 2,
43    Number = 3,
44    String = 4,
45    BigInt = 5,
46    Array = 6,
47    Object = 7,
48    Function = 8,
49}
50
51/// The panic of a broken precondition: what traps in WASM.
52fn trap(name: &str, reason: &str) -> ! {
53    panic!("l0.{name}: {reason}")
54}
55
56fn unpack<A: IVm>(v: &Any<A>) -> Unpacked<A> {
57    v.clone().into()
58}
59
60/// L0 `kind`.
61pub fn kind<A: IVm>(v: &Any<A>) -> Kind {
62    match unpack(v) {
63        Unpacked::Nullish(Nullish::Undefined) => Kind::Undefined,
64        Unpacked::Nullish(Nullish::Null) => Kind::Null,
65        Unpacked::Boolean(_) => Kind::Boolean,
66        Unpacked::Number(_) => Kind::Number,
67        Unpacked::String(_) => Kind::String,
68        Unpacked::BigInt(_) => Kind::BigInt,
69        Unpacked::Array(_) => Kind::Array,
70        Unpacked::Object(_) => Kind::Object,
71        Unpacked::Function(_) => Kind::Function,
72    }
73}
74
75// Casts: the typed value inside an `any` of a known kind, or the trap.
76
77fn as_string<A: IVm>(name: &str, v: &Any<A>) -> String<A> {
78    v.clone()
79        .try_into()
80        .unwrap_or_else(|_| trap(name, "not a string"))
81}
82
83fn as_bigint<A: IVm>(name: &str, v: &Any<A>) -> BigInt<A> {
84    v.clone()
85        .try_into()
86        .unwrap_or_else(|_| trap(name, "not a bigint"))
87}
88
89fn as_array<A: IVm>(name: &str, v: &Any<A>) -> Array<A> {
90    v.clone()
91        .try_into()
92        .unwrap_or_else(|_| trap(name, "not an array"))
93}
94
95fn as_object<A: IVm>(name: &str, v: &Any<A>) -> Object<A> {
96    v.clone()
97        .try_into()
98        .unwrap_or_else(|_| trap(name, "not an object"))
99}
100
101fn as_function<A: IVm>(name: &str, v: &Any<A>) -> Function<A> {
102    v.clone()
103        .try_into()
104        .unwrap_or_else(|_| trap(name, "not a function"))
105}
106
107/// `i` when it indexes `n` positions.
108fn index(name: &str, i: u32, n: u32) -> u32 {
109    if i >= n {
110        trap(name, "not an index")
111    }
112    i
113}
114
115// Scalars.
116
117/// L0 `undefined`.
118pub fn undefined<A: IVm>() -> Any<A> {
119    Any::undefined()
120}
121
122/// L0 `null`.
123pub fn null<A: IVm>() -> Any<A> {
124    Nullish::Null.to_any()
125}
126
127/// L0 `to_f64`.
128pub fn to_f64<A: IVm>(v: &Any<A>) -> f64 {
129    let n: Number = v
130        .clone()
131        .try_into()
132        .unwrap_or_else(|_| trap("to_f64", "not a number"));
133    n.into()
134}
135
136/// L0 `from_f64`. A `NaN` loses its payload.
137pub fn from_f64<A: IVm>(x: f64) -> Any<A> {
138    Number::from(x).to_any()
139}
140
141/// L0 `to_bool`.
142pub fn to_bool<A: IVm>(v: &Any<A>) -> bool {
143    v.clone()
144        .try_into()
145        .unwrap_or_else(|_| trap("to_bool", "not a boolean"))
146}
147
148/// L0 `from_bool`, which also makes the constants `true` and `false`.
149pub fn from_bool<A: IVm>(b: bool) -> Any<A> {
150    b.to_any()
151}
152
153// Bytes.
154
155/// L0 `make_string`: a string of UTF-16 units.
156pub fn make_string<A: IVm>(units: &[u16]) -> Any<A> {
157    fits("make_string", units.len() as u64);
158    units.iter().copied().to_string().to_any()
159}
160
161/// L0 `read_units`: the units of `s` from `start`, as many as `out` holds.
162pub fn read_units<A: IVm>(s: &Any<A>, start: u32, out: &mut [u16]) {
163    let s = as_string("read_units", s);
164    let range = range("read_units", start, out.len(), s.length());
165    for (o, i) in out.iter_mut().zip(range) {
166        *o = s[i];
167    }
168}
169
170/// L0 `make_bigint`: a sign, `0`, `1` or `-1`, and a magnitude in `u64`
171/// words, least significant first, in its canonical form only: no leading
172/// zero word, and the sign `0` for zero alone
173/// ([`l0-design.md` §19](../../../../doc/sasha-gil/wasm/l0-design.md#19-bigints-are-canonical)).
174pub fn make_bigint<A: IVm>(sign: i32, words: &[u64]) -> Any<A> {
175    let sign = match (sign, words.last()) {
176        (0, None) => Sign::Positive,
177        (1, Some(&w)) if w != 0 => Sign::Positive,
178        (-1, Some(&w)) if w != 0 => Sign::Negative,
179        _ => trap("make_bigint", "not canonical"),
180    };
181    BigInt::<A>::normalize_new(sign, words.iter().copied()).to_any()
182}
183
184/// L0 `sign`: `0` for zero, `1` for a positive bigint and `-1` for a
185/// negative one.
186pub fn sign<A: IVm>(b: &Any<A>) -> i32 {
187    let b = as_bigint("sign", b);
188    if b.length() == 0 {
189        return 0;
190    }
191    match b.sign() {
192        Sign::Positive => 1,
193        Sign::Negative => -1,
194    }
195}
196
197/// L0 `read_words`: the words of `b`'s magnitude from `start`, as many as
198/// `out` holds.
199pub fn read_words<A: IVm>(b: &Any<A>, start: u32, out: &mut [u64]) {
200    let b = as_bigint("read_words", b);
201    let range = range("read_words", start, out.len(), b.length());
202    for (o, i) in out.iter_mut().zip(range) {
203        *o = b[i];
204    }
205}
206
207/// The positions `start..start + len` when they lie inside `n`.
208fn range(name: &str, start: u32, len: usize, n: u32) -> core::ops::Range<u32> {
209    match u32::try_from(len)
210        .ok()
211        .and_then(|len| start.checked_add(len))
212    {
213        Some(end) if end <= n => start..end,
214        _ => trap(name, "not a range"),
215    }
216}
217
218// Strings.
219
220/// A string's length is a `u32`: a longer one traps, as running out of
221/// memory does, rather than wrap.
222fn fits(name: &str, len: u64) {
223    if len > u64::from(u32::MAX) {
224        trap(name, "too long")
225    }
226}
227
228/// L0 `unit`: the UTF-16 unit `i` of a string.
229pub fn unit<A: IVm>(s: &Any<A>, i: u32) -> u16 {
230    let s = as_string("unit", s);
231    s[index("unit", i, s.length())]
232}
233
234/// L0 `from_unit`: the string of one UTF-16 unit.
235pub fn from_unit<A: IVm>(u: u16) -> Any<A> {
236    [u].to_string().to_any()
237}
238
239/// L0 `concat`: two strings joined.
240pub fn concat<A: IVm>(a: &Any<A>, b: &Any<A>) -> Any<A> {
241    let a = as_string("concat", a);
242    let b = as_string("concat", b);
243    fits("concat", u64::from(a.length()) + u64::from(b.length()));
244    a.index_iter().chain(b.index_iter()).to_string().to_any()
245}
246
247/// L0 `substring`: the units of `s` from `start` up to `end`, where
248/// `start <= end <= length`. It neither clamps nor counts from the end.
249pub fn substring<A: IVm>(s: &Any<A>, start: u32, end: u32) -> Any<A> {
250    let s = as_string("substring", s);
251    let end = index("substring", end, s.length() + 1);
252    let start = index("substring", start, end + 1);
253    (start..end).map(|i| s[i]).to_string().to_any()
254}
255
256/// L0 `join`: the strings of an array joined, with `separator` between two
257/// of them ([`l0-design.md` §22](../../../../doc/sasha-gil/wasm/l0-design.md#22-building-in-one-pass)).
258/// Each item and the separator must be strings.
259pub fn join<A: IVm>(parts: &Any<A>, separator: &Any<A>) -> Any<A> {
260    let parts = as_array("join", parts);
261    let separator = as_string("join", separator);
262    let parts: Vec<String<A>> = parts.index_iter().map(|p| as_string("join", &p)).collect();
263    let len = parts.iter().map(|p| u64::from(p.length())).sum::<u64>()
264        + u64::from(separator.length()) * parts.len().saturating_sub(1) as u64;
265    fits("join", len);
266    let mut units: Vec<u16> = Vec::with_capacity(len as usize);
267    for (i, p) in parts.into_iter().enumerate() {
268        if i > 0 {
269            units.extend(separator.clone().index_iter());
270        }
271        units.extend(p.index_iter());
272    }
273    units.to_string().to_any()
274}
275
276// Containers.
277
278/// An open container: what L0's `begin_array` and `begin_object` answer,
279/// filled by `push` or `push_property` with exactly the number of items it
280/// was begun with, then sealed.
281pub struct Builder<A: IVm> {
282    len: u32,
283    items: Items<A>,
284}
285
286enum Items<A: IVm> {
287    Array(Vec<Any<A>>),
288    Object(Vec<Property<A>>),
289}
290
291impl<A: IVm> Builder<A> {
292    fn count(&self) -> usize {
293        match &self.items {
294            Items::Array(items) => items.len(),
295            Items::Object(properties) => properties.len(),
296        }
297    }
298
299    fn has_room(&self, name: &str) {
300        if self.count() >= self.len as usize {
301            trap(name, "full")
302        }
303    }
304
305    /// L0 `push`: the next item of an array. It takes `v`.
306    pub fn push(&mut self, v: Any<A>) {
307        self.has_room("push");
308        match &mut self.items {
309            Items::Array(items) => items.push(v),
310            Items::Object(_) => trap("push", "not an array builder"),
311        }
312    }
313
314    /// L0 `push_property`: the next property of an object. It takes `key`,
315    /// which must be a string, and `v`.
316    pub fn push_property(&mut self, key: Any<A>, v: Any<A>) {
317        self.has_room("push_property");
318        let key = as_string("push_property", &key);
319        match &mut self.items {
320            Items::Object(properties) => properties.push((key, v)),
321            Items::Array(_) => trap("push_property", "not an object builder"),
322        }
323    }
324
325    /// L0 `seal`: the container. An object keeps each key once, at its
326    /// first position with its last value, as the literal `{ a: 1, a: 2 }`
327    /// does.
328    pub fn seal(self) -> Any<A> {
329        if self.count() != self.len as usize {
330            trap("seal", "not full")
331        }
332        match self.items {
333            Items::Array(items) => items.to_array().to_any(),
334            Items::Object(properties) => {
335                let o: Object<A> = properties.to_object();
336                o.own_entries().to_object().to_any()
337            }
338        }
339    }
340}
341
342/// L0 `begin_array`: a builder of an array of `len` items.
343pub fn begin_array<A: IVm>(len: u32) -> Builder<A> {
344    Builder {
345        len,
346        items: Items::Array(Vec::with_capacity(len as usize)),
347    }
348}
349
350/// L0 `begin_object`: a builder of an object of `len` properties, before
351/// repeated keys merge.
352pub fn begin_object<A: IVm>(len: u32) -> Builder<A> {
353    Builder {
354        len,
355        items: Items::Object(Vec::with_capacity(len as usize)),
356    }
357}
358
359/// L0 `length`
360/// ([`l0-design.md` §18](../../../../doc/sasha-gil/wasm/l0-design.md#18-what-length-item-and-key-mean-for-each-kind)):
361/// a string's UTF-16 units, a bigint's `u64` words, an array's items, an
362/// object's properties, and a function's declared parameters.
363pub fn length<A: IVm>(v: &Any<A>) -> u32 {
364    match unpack(v) {
365        Unpacked::String(s) => s.length(),
366        Unpacked::BigInt(b) => b.length(),
367        Unpacked::Array(a) => a.length(),
368        Unpacked::Object(o) => o.own_entries().len() as u32,
369        Unpacked::Function(f) => f.length(),
370        _ => trap(
371            "length",
372            "not a string, a bigint, an array, an object or a function",
373        ),
374    }
375}
376
377/// L0 `item`: the item `i` of an array, or the value of the property `i`
378/// of an object.
379pub fn item<A: IVm>(v: &Any<A>, i: u32) -> Any<A> {
380    match unpack(v) {
381        Unpacked::Array(a) => a[index("item", i, a.length())].clone(),
382        Unpacked::Object(o) => property("item", &o, i).1,
383        _ => trap("item", "not an array or an object"),
384    }
385}
386
387/// L0 `key`: the key of the property `i` of an object.
388pub fn key<A: IVm>(o: &Any<A>, i: u32) -> Any<A> {
389    property("key", &as_object("key", o), i).0.to_any()
390}
391
392fn property<A: IVm>(name: &str, o: &Object<A>, i: u32) -> Property<A> {
393    let mut entries = o.own_entries();
394    let i = index(name, i, entries.len() as u32);
395    entries.swap_remove(i as usize)
396}
397
398/// L0 `find`: the index of the property `key` of an object, or `-1`. The
399/// key must be a string.
400pub fn find<A: IVm>(o: &Any<A>, key: &Any<A>) -> i32 {
401    let o = as_object("find", o);
402    let key = as_string("find", key);
403    o.own_entries()
404        .iter()
405        .position(|(k, _)| *k == key)
406        .map_or(-1, |i| i as i32)
407}
408
409// Functions.
410
411/// L0 `make_function`: a function of `code` over `frame`, a sealed array,
412/// declaring `length` parameters. NaNVM keeps a function's text as a
413/// `&'static str`, where L0 passes a string value.
414pub fn make_function<A: IVm>(
415    code: StaticCode<A>,
416    frame: Any<A>,
417    length: u32,
418    text: Option<&'static str>,
419) -> Any<A> {
420    let frame = as_array("make_function", &frame);
421    A::static_function(code, length, frame, text).to_any()
422}
423
424/// L0 `call`: `f` called with `args`, a sealed array. `Err` is a thrown
425/// value, status 1 in the ABI.
426pub fn call<A: IVm>(f: &Any<A>, args: &Any<A>) -> Result<Any<A>, Any<A>> {
427    let f = as_function("call", f);
428    f.call(as_array("call", args))
429}
430
431/// L0 `frame`: the item `i` of the frame of the function whose code is
432/// running, read through the code's `self_`.
433pub fn frame<A: IVm>(self_: &A::InternalFunction, i: u32) -> Any<A> {
434    let frame = A::frame(self_);
435    frame[index("frame", i, frame.length())].clone()
436}
437
438/// L0 `text`: a function's text, or `undefined` for a function that has
439/// none.
440pub fn text<A: IVm>(f: &Any<A>) -> Any<A> {
441    as_function("text", f)
442        .internal()
443        .text()
444        .map_or_else(Any::undefined, Any::from)
445}
446
447// Identity.
448
449/// L0 `same`: whether two arrays, objects or functions are the same value.
450/// A primitive traps: L1 compares those by content.
451pub fn same<A: IVm>(a: &Any<A>, b: &Any<A>) -> bool {
452    match (unpack(a), unpack(b)) {
453        (Unpacked::Array(a), Unpacked::Array(b)) => a == b,
454        (Unpacked::Object(a), Unpacked::Object(b)) => a == b,
455        (Unpacked::Function(a), Unpacked::Function(b)) => a == b,
456        (
457            Unpacked::Array(_) | Unpacked::Object(_) | Unpacked::Function(_),
458            Unpacked::Array(_) | Unpacked::Object(_) | Unpacked::Function(_),
459        ) => false,
460        _ => trap("same", "not an array, an object or a function"),
461    }
462}