Sorting

A quicksort of 100,000 pseudo-random integers, which reports the smallest and the largest. It allocates as it goes, so it measures the NaNVM runtime's allocation as much as its arithmetic. The demo below runs it as JavaScript and as WebAssembly, and shows what each run wrote, how long each took, and the second time as a multiple of the first.

See WASM demos for the other pages.

Demo

The same FunctionalScript program, run two ways: as the JavaScript it already is, and compiled by fjs compile to Rust on the NaNVM runtime, then to WebAssembly. Run calls each in turn and shows what it wrote to standard output and how long the call took.

FunctionalScript source:

/**
 * Sorts a long array of pseudo-random integers with a quicksort, then
 * writes a line to standard output saying what it sorted.
 *
 * The program is an effect: one `write` command, whose text is encoded to
 * UTF-8 bytes in a bit vector by {@link bytes} here, because `fjs compile`
 * cannot compile `log` and `utf8` yet.
 *
 * @module
 */

/** How many integers to sort. */
const count = 100_000

/**
 * The next state of the Park–Miller generator: every product stays below
 * 2^53, so it is exact in a `number`.
 *
 * @type {(seed: number) => number}
 */
const next = seed => seed * 48_271 % 2_147_483_647

/**
 * `n` pseudo-random integers from `seed`, and the state after them. Split
 * in halves rather than one call per element, so the depth is `log n`.
 *
 * @type {(n: number, seed: number) => readonly [readonly number[], number]}
 */
const random = (n, seed) => {
    if (n === 1) { return [[seed], next(seed)] }
    const half = n >> 1
    const [left, middle] = random(half, seed)
    const [right, end] = random(n - half, middle)
    return [[...left, ...right], end]
}

/**
 * Quicksort: the elements below the pivot, sorted, then those equal to it,
 * then those above it, sorted. The pivot is the middle element, passed to
 * `around` rather than bound to a `const` past the guard, since a value read
 * only in one branch has no Rust spelling yet; and `around` is inside `sort`,
 * since a module `const` cannot name one declared after it.
 *
 * @type {(a: readonly number[]) => readonly number[]}
 */
const sort = a => {
    /** @type {(pivot: number) => readonly number[]} */
    const around = pivot => [
        ...sort(a.filter(x => x < pivot)),
        ...a.filter(x => x === pivot),
        ...sort(a.filter(x => x > pivot)),
    ]
    return a.length < 2 ? a : around(a[Number(a.length >> 1)])
}

/** Whether `a` is in ascending order. @type {(a: readonly number[]) => boolean} */
const ascending = a => a.every((x, i) => i === 0 || a[Number(i - 1)] <= x)

/**
 * ASCII text as the bit vector a `write` command carries: the bytes, most
 * significant bit first. An ASCII byte's first bit is `0`, so
 * `fjs/types/bit_vec` spells the vector negated, with that bit set to `1`
 * as the stop bit: the loop starts past it, with `1n`.
 *
 * @type {(text: string) => bigint}
 */
const bytes = text => {
    /** @type {readonly [number, bigint]} */
    let s = [1, 1n]
    while (s[0] < text.length * 8) {
        const i = s[0]
        s = [i + 1, s[1] * 2n + ((text.charCodeAt(i >> 3) >> (7 - i % 8)) & 1 ? 1n : 0n)]
    }
    return -s[1]
}

/** What the program writes, once it has sorted. @type {(a: readonly number[]) => string} */
const report = a => ascending(a)
    ? `sorted ${a.length} integers: smallest ${a[0]}, largest ${a.at(-1)}\n`
    : 'not sorted\n'

/**
 * The entry: everything happens in the call, none of it as the module loads,
 * so that the time the page shows holds the generating and the sorting.
 */
export default () => {
    const [input] = random(count, 1)
    return {
        command: 'write',
        payload: ['stdout', bytes(report(sort(input)))],
        continuation: () => () => ['ok', undefined],
    }
}

Rust that fjs compile writes from it, on the NaNVM runtime:

// @generated by `fjs compile`. Do not edit: recompile the source module instead.

use nanvm_lib::vm::unstable::{
    bigint_any, f64_any, spread_array, spread_item, strict_eq, string_any, string_key,
};
use nanvm_lib::vm::{Any, Function, IStaticFunction, Nullish, ToAny, ToArray, ToObject};

#[rustfmt::skip]
#[allow(clippy::if_same_then_else)]
pub fn module<A: IStaticFunction>(runtime: Any<A>) -> Result<Any<A>, Any<A>> {
    let _b3: Any<A> = A::static_function(|_self, _args| { (_args.argument(0) * f64_any(0x40e791e000000000))? % f64_any(0x41dfffffffc00000) }, 1, [runtime.clone()].to_array(), Some("($0)=>$0*48271%2147483647")).to_any();
    let _b26: Any<A> = A::static_function(|_self, _args| { if Any::<A>::to_boolean((strict_eq(_args.argument(0), f64_any(0x3ff0000000000000)))?) { Ok([[_args.argument(1)].to_array().to_any(), (Any::call(A::frame(_self)[0].clone(), [_args.argument(1)].to_array().to_any()))?].to_array().to_any()) } else {
        let _b12: Any<A> = (_args.argument(0) >> f64_any(0x3ff0000000000000))?;
        let _b14: Any<A> = (spread_array([spread_item((Any::call(Function::new(_self.clone()).to_any(), [_b12.clone(), _args.argument(1)].to_array().to_any()))?)?]))?;
        let _b15: Any<A> = (Any::dot(_b14.clone(), f64_any(0x0000000000000000)).end())?;
        let _b16: Any<A> = (Any::dot(_b14.clone(), f64_any(0x3ff0000000000000)).end())?;
        let _b19: Any<A> = (spread_array([spread_item((Any::call(Function::new(_self.clone()).to_any(), [(_args.argument(0) - _b12.clone())?, _b16.clone()].to_array().to_any()))?)?]))?;
        let _b20: Any<A> = (Any::dot(_b19.clone(), f64_any(0x0000000000000000)).end())?;
        Ok([(spread_array([spread_item(_b15.clone())?, spread_item(_b20.clone())?]))?, (Any::dot(_b19.clone(), f64_any(0x3ff0000000000000)).end())?].to_array().to_any())
    } }, 2, [_b3.clone(), runtime.clone()].to_array(), Some("(function $1($2,$3){return $2===1?[[$3],$0($3)]:(()=>{const $4=$2>>1;const $5=[...$1($4,$3)];const $6=$5[0];const $7=$5[1];const $8=[...$1($2-$4,$7)];const $9=$8[0];return [[...$6,...$9],$8[1]];})();})")).to_any();
    let _b55: Any<A> = A::static_function(|_self, _args| {
        let _b50: Any<A> = A::static_function(|_self, _args| {
            let mut _b23294: Any<A> = _args.argument(0);
            '_loop50: while Any::<A>::to_boolean({
                let _b23295: Any<A> = _b23294.clone();
                (Any::lt((Any::dot(_b23295.clone(), f64_any(0x0000000000000000)).end())?, ((Any::dot(A::frame(_self)[0].clone(), string_any("length")).end())? * f64_any(0x4020000000000000))?))?
            }) {
                let _b8656: Any<A> = _b23294.clone();
                let _b35: Any<A> = (Any::dot(_b8656.clone(), f64_any(0x0000000000000000)).end())?;
                let _b23296: Any<A> = [(_b35.clone() + f64_any(0x3ff0000000000000))?, (((Any::dot(_b8656.clone(), f64_any(0x3ff0000000000000)).end())? * bigint_any(2))? + (if Any::<A>::to_boolean((((Any::dot(A::frame(_self)[0].clone(), string_any("charCodeAt")).end_call(&A::frame(_self)[1], || Ok([(_b35.clone() >> f64_any(0x4008000000000000))?].to_array().to_any())))? >> (f64_any(0x401c000000000000) - (_b35.clone() % f64_any(0x4020000000000000))?)?)? & f64_any(0x3ff0000000000000))?) { bigint_any(1) } else { bigint_any(0) }))?].to_array().to_any();
                _b23294 = _b23296.clone();
            }
            let _b8657: Any<A> = _b23294;
            Ok(_b8657.clone())
        }, 1, [_args.argument(0), A::frame(_self)[0].clone()].to_array(), Some("($1)=>{let $2=$1;while((()=>{const $3=$2;return $3[0]<$0.length*8;})()){const $4=$2;const $5=$4[0];const $6=[$5+1,$4[1]*2n+($0.charCodeAt($5>>3)>>7-$5%8&1?1n:0n)];$2=$6;}return $2;}")).to_any();
        <Any<A> as core::ops::Neg>::neg((Any::dot((Any::call(_b50.clone(), [[f64_any(0x3ff0000000000000), bigint_any(1)].to_array().to_any()].to_array().to_any()))?, f64_any(0x3ff0000000000000)).end())?)
    }, 1, [runtime.clone()].to_array(), Some("($0)=>{const $1=($2)=>{let $3=$2;while((()=>{const $4=$3;return $4[0]<$0.length*8;})()){const $5=$3;const $6=$5[0];const $7=[$6+1,$5[1]*2n+($0.charCodeAt($6>>3)>>7-$6%8&1?1n:0n)];$3=$7;}return $3;};return -$1([1,1n])[1];}")).to_any();
    let _b68: Any<A> = A::static_function(|_self, _args| { Any::dot(_args.argument(0), string_any("every")).end_call(&A::frame(_self)[0], || Ok([A::static_function(|_self, _args| {
        let _b58: Any<A> = (strict_eq(_args.argument(1), f64_any(0x0000000000000000)))?;
        if Any::<A>::to_boolean(_b58.clone()) { Ok(_b58.clone()) } else { Any::le((Any::dot(A::frame(_self)[0].clone(), (Any::number((_args.argument(1) - f64_any(0x3ff0000000000000))?))?).end())?, _args.argument(0)) }
    }, 2, [_args.argument(0), A::frame(_self)[0].clone()].to_array(), Some("($1,$2)=>{const $3=$2===0;return $3?$3:$0[Number($2-1)]<=$1;}")).to_any()].to_array().to_any())) }, 1, [runtime.clone()].to_array(), Some("($0)=>$0.every(($1,$2)=>{const $3=$2===0;return $3?$3:$0[Number($2-1)]<=$1;})")).to_any();
    let _b119: Any<A> = A::static_function(|_self, _args| {
        let _b109: Any<A> = A::static_function(|_self, _args| { spread_array([spread_item((Any::call(A::frame(_self)[0].clone(), [(Any::dot(A::frame(_self)[1].clone(), string_any("filter")).end_call(&A::frame(_self)[2], || Ok([A::static_function(|_self, _args| { Any::lt(_args.argument(0), A::frame(_self)[0].clone()) }, 1, [_args.argument(0), A::frame(_self)[2].clone()].to_array(), Some("($1)=>$1<$0")).to_any()].to_array().to_any())))?].to_array().to_any()))?)?, spread_item((Any::dot(A::frame(_self)[1].clone(), string_any("filter")).end_call(&A::frame(_self)[2], || Ok([A::static_function(|_self, _args| { strict_eq(_args.argument(0), A::frame(_self)[0].clone()) }, 1, [_args.argument(0), A::frame(_self)[2].clone()].to_array(), Some("($1)=>$1===$0")).to_any()].to_array().to_any())))?)?, spread_item((Any::call(A::frame(_self)[0].clone(), [(Any::dot(A::frame(_self)[1].clone(), string_any("filter")).end_call(&A::frame(_self)[2], || Ok([A::static_function(|_self, _args| { Any::gt(_args.argument(0), A::frame(_self)[0].clone()) }, 1, [_args.argument(0), A::frame(_self)[2].clone()].to_array(), Some("($1)=>$1>$0")).to_any()].to_array().to_any())))?].to_array().to_any()))?)?]) }, 1, [Function::new(_self.clone()).to_any(), _args.argument(0), A::frame(_self)[0].clone()].to_array(), Some("($2)=>[...$0($1.filter(($3)=>$3<$2)),...$1.filter(($4)=>$4===$2),...$0($1.filter(($5)=>$5>$2))]")).to_any();
        if Any::<A>::to_boolean((Any::lt((Any::dot(_args.argument(0), string_any("length")).end())?, f64_any(0x4000000000000000)))?) { Ok(_args.argument(0)) } else { Any::call(_b109.clone(), [(Any::dot(_args.argument(0), (Any::number(((Any::dot(_args.argument(0), string_any("length")).end())? >> f64_any(0x3ff0000000000000))?))?).end())?].to_array().to_any()) }
    }, 1, [runtime.clone()].to_array(), Some("(function $0($1){const $2=($3)=>[...$0($1.filter(($4)=>$4<$3)),...$1.filter(($5)=>$5===$3),...$0($1.filter(($6)=>$6>$3))];return $1.length<2?$1:$2($1[Number($1.length>>1)]);})")).to_any();
    Ok([(string_key("default"), {
        let _b85: Any<A> = A::static_function(|_self, _args| { if Any::<A>::to_boolean((Any::call(A::frame(_self)[0].clone(), [_args.argument(0)].to_array().to_any()))?) { (((((string_any("sorted ") + ((Any::dot(_args.argument(0), string_any("length")).end())?.to_string().map(|v| v.to_any()))?)? + string_any(" integers: smallest "))? + ((Any::dot(_args.argument(0), f64_any(0x0000000000000000)).end())?.to_string().map(|v| v.to_any()))?)? + string_any(", largest "))? + ((Any::dot(_args.argument(0), string_any("at")).end_call(&A::frame(_self)[1], || Ok([f64_any(0xbff0000000000000)].to_array().to_any())))?.to_string().map(|v| v.to_any()))?)? + string_any("\n") } else { Ok(string_any("not sorted\n")) } }, 1, [_b68.clone(), runtime.clone()].to_array(), Some("($1)=>$0($1)?\"sorted \"+String($1.length)+\" integers: smallest \"+String($1[0])+\", largest \"+String($1.at(-1))+\"\\n\":\"not sorted\\n\"")).to_any();
        A::static_function(|_self, _args| { Ok([(string_key("command"), string_any("write")), (string_key("payload"), [string_any("stdout"), (Any::call(A::frame(_self)[1].clone(), [(Any::call(A::frame(_self)[2].clone(), [(Any::call(A::frame(_self)[3].clone(), [(Any::dot((spread_array([spread_item((Any::call(A::frame(_self)[0].clone(), [f64_any(0x40f86a0000000000), f64_any(0x3ff0000000000000)].to_array().to_any()))?)?]))?, f64_any(0x0000000000000000)).end())?].to_array().to_any()))?].to_array().to_any()))?].to_array().to_any()))?].to_array().to_any()), (string_key("continuation"), A::static_function(|_self, _args| { Ok(A::static_function(|_self, _args| { Ok([string_any("ok"), Nullish::Undefined.to_any()].to_array().to_any()) }, 0, [A::frame(_self)[0].clone()].to_array(), Some("()=>[\"ok\",undefined]")).to_any()) }, 0, [A::frame(_self)[4].clone()].to_array(), Some("()=>()=>[\"ok\",undefined]")).to_any())].to_object().to_any()) }, 0, [_b26.clone(), _b55.clone(), _b85.clone(), _b119.clone(), runtime.clone()].to_array(), Some("()=>{return {\"command\":\"write\",\"payload\":[\"stdout\",$1($2($3([...$0(100000,1)][0])))],\"continuation\":()=>()=>[\"ok\",undefined]};}")).to_any()
    })].to_object().to_any())
}

WebAssembly text (WAT) of the functions compiled from this source, without the NaNVM runtime:

Refused:

File size 778087 exceeds maximum allowed size of 131072 bytes: 'nanvm/demos/wasm/sort.wat.marked'

Emergent Testing

Idle. Press Run to start the suite.

  • ./proof.f.mjs