use std::path::PathBuf;
use std::process::Command;
use super::WasmLowerError;
type R<T> = Result<T, WasmLowerError>;
const I64: u8 = 0x7e;
fn leb_u(mut v: u64, out: &mut Vec<u8>) {
loop {
let byte = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
out.push(byte | 0x80);
} else {
out.push(byte);
break;
}
}
}
fn leb_u32_padded(v: u32, out: &mut Vec<u8>) {
let mut v = v;
for i in 0..5 {
let mut byte = (v & 0x7f) as u8;
v >>= 7;
if i < 4 {
byte |= 0x80;
}
out.push(byte);
}
}
fn leb_i64(mut v: i64, out: &mut Vec<u8>) {
loop {
let byte = (v & 0x7f) as u8;
v >>= 7; let done = (v == 0 && byte & 0x40 == 0) || (v == -1 && byte & 0x40 != 0);
out.push(if done { byte } else { byte | 0x80 });
if done {
break;
}
}
}
fn section(id: u8, body: &[u8], out: &mut Vec<u8>) {
out.push(id);
leb_u(body.len() as u64, out);
out.extend_from_slice(body);
}
fn custom_section(name: &str, body: &[u8], out: &mut Vec<u8>) {
let mut payload = Vec::new();
leb_u(name.len() as u64, &mut payload);
payload.extend_from_slice(name.as_bytes());
payload.extend_from_slice(body);
section(0, &payload, out);
}
struct FuncSymbol {
func_index: u32,
defined_name: Option<String>,
}
struct CodeReloc {
offset: u32,
sym_index: u32,
}
pub(crate) fn emit_probe_object() -> Vec<u8> {
let mut types = Vec::new();
leb_u(2, &mut types);
types.extend_from_slice(&[0x60, 0x01, I64, 0x01, I64]); types.extend_from_slice(&[0x60, 0x00, 0x01, I64]);
let mut imports = Vec::new();
leb_u(1, &mut imports);
let name_bytes = |m: &str, f: &str, out: &mut Vec<u8>| {
leb_u(m.len() as u64, out);
out.extend_from_slice(m.as_bytes());
leb_u(f.len() as u64, out);
out.extend_from_slice(f.as_bytes());
};
name_bytes("env", "logos_rt_probe", &mut imports);
imports.push(0x00); leb_u(0, &mut imports);
let probe_func_index: u32 = 0;
let main_func_index: u32 = 1;
let mut funcs = Vec::new();
leb_u(1, &mut funcs);
leb_u(1, &mut funcs);
let mut body = Vec::new();
leb_u(0, &mut body); body.push(0x42); leb_u(41, &mut body); body.push(0x10); let call_leb_off_in_body = body.len() as u32; leb_u32_padded(probe_func_index, &mut body);
body.push(0x0b);
let mut code = Vec::new();
leb_u(1, &mut code); let size_field_start = code.len();
leb_u(body.len() as u64, &mut code); let body_start_in_content = code.len() as u32; code.extend_from_slice(&body);
let reloc_offset = body_start_in_content + call_leb_off_in_body;
let _ = size_field_start;
let symbols = vec![
FuncSymbol { func_index: probe_func_index, defined_name: None },
FuncSymbol { func_index: main_func_index, defined_name: Some("main".to_string()) },
];
let relocs = vec![CodeReloc { offset: reloc_offset, sym_index: 0 }];
let mut out = Vec::new();
out.extend_from_slice(b"\0asm");
out.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); section(1, &types, &mut out);
section(2, &imports, &mut out);
section(3, &funcs, &mut out);
let code_section_index: u32 = 3; section(10, &code, &mut out);
custom_section("linking", &encode_linking(&symbols), &mut out);
custom_section("reloc.CODE", &encode_reloc_code(code_section_index, &relocs), &mut out);
out
}
pub(crate) fn emit_load_object(imported_fn: &str) -> Vec<u8> {
let mut types = Vec::new();
leb_u(2, &mut types);
types.extend_from_slice(&[0x60, 0x00, 0x01, 0x7f]); types.extend_from_slice(&[0x60, 0x00, 0x01, I64]);
let mut imports = Vec::new();
leb_u(2, &mut imports);
let import_name = |m: &str, f: &str, out: &mut Vec<u8>| {
leb_u(m.len() as u64, out);
out.extend_from_slice(m.as_bytes());
leb_u(f.len() as u64, out);
out.extend_from_slice(f.as_bytes());
};
import_name("env", "__linear_memory", &mut imports);
imports.push(0x02); imports.push(0x00); leb_u(0, &mut imports); import_name("env", imported_fn, &mut imports);
imports.push(0x00); leb_u(0, &mut imports);
let write_func_index: u32 = 0;
let main_func_index: u32 = 1;
let mut funcs = Vec::new();
leb_u(1, &mut funcs);
leb_u(1, &mut funcs);
let mut body = Vec::new();
leb_u(0, &mut body); body.push(0x10); let call_leb_off_in_body = body.len() as u32;
leb_u32_padded(write_func_index, &mut body);
body.push(0x29); leb_u(3, &mut body); leb_u(0, &mut body); body.push(0x0b);
let mut code = Vec::new();
leb_u(1, &mut code);
leb_u(body.len() as u64, &mut code);
let body_start_in_content = code.len() as u32;
code.extend_from_slice(&body);
let reloc_offset = body_start_in_content + call_leb_off_in_body;
let symbols = vec![
FuncSymbol { func_index: write_func_index, defined_name: None },
FuncSymbol { func_index: main_func_index, defined_name: Some("main".to_string()) },
];
let relocs = vec![CodeReloc { offset: reloc_offset, sym_index: 0 }];
let mut out = Vec::new();
out.extend_from_slice(b"\0asm");
out.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
section(1, &types, &mut out);
section(2, &imports, &mut out);
section(3, &funcs, &mut out);
let code_section_index: u32 = 3;
section(10, &code, &mut out);
custom_section("linking", &encode_linking(&symbols), &mut out);
custom_section("reloc.CODE", &encode_reloc_code(code_section_index, &relocs), &mut out);
out
}
pub(crate) fn emit_mul_text_object(a: i64, b: i64) -> Vec<u8> {
let mut types = Vec::new();
leb_u(2, &mut types);
types.extend_from_slice(&[0x60, 0x02, I64, I64, 0x01, 0x7f]); types.extend_from_slice(&[0x60, 0x00, 0x01, 0x7f]);
let mut imports = Vec::new();
leb_u(2, &mut imports);
let import_name = |m: &str, f: &str, out: &mut Vec<u8>| {
leb_u(m.len() as u64, out);
out.extend_from_slice(m.as_bytes());
leb_u(f.len() as u64, out);
out.extend_from_slice(f.as_bytes());
};
import_name("env", "__linear_memory", &mut imports);
imports.push(0x02);
imports.push(0x00);
leb_u(0, &mut imports);
import_name("env", "logos_rt_i64_mul_to_text", &mut imports);
imports.push(0x00);
leb_u(0, &mut imports);
let mul_func_index: u32 = 0;
let main_func_index: u32 = 1;
let mut funcs = Vec::new();
leb_u(1, &mut funcs);
leb_u(1, &mut funcs);
let mut body = Vec::new();
leb_u(0, &mut body);
body.push(0x42);
leb_i64(a, &mut body);
body.push(0x42);
leb_i64(b, &mut body);
body.push(0x10);
let call_leb_off_in_body = body.len() as u32;
leb_u32_padded(mul_func_index, &mut body);
body.push(0x0b);
let mut code = Vec::new();
leb_u(1, &mut code);
leb_u(body.len() as u64, &mut code);
let body_start_in_content = code.len() as u32;
code.extend_from_slice(&body);
let reloc_offset = body_start_in_content + call_leb_off_in_body;
let symbols = vec![
FuncSymbol { func_index: mul_func_index, defined_name: None },
FuncSymbol { func_index: main_func_index, defined_name: Some("main".to_string()) },
];
let relocs = vec![CodeReloc { offset: reloc_offset, sym_index: 0 }];
let mut out = Vec::new();
out.extend_from_slice(b"\0asm");
out.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
section(1, &types, &mut out);
section(2, &imports, &mut out);
section(3, &funcs, &mut out);
section(10, &code, &mut out);
custom_section("linking", &encode_linking(&symbols), &mut out);
custom_section("reloc.CODE", &encode_reloc_code(3, &relocs), &mut out);
out
}
pub(crate) fn emit_bigint_object() -> Vec<u8> {
let mut types = Vec::new();
leb_u(4, &mut types);
types.extend_from_slice(&[0x60, 0x01, I64, 0x01, 0x7f]);
types.extend_from_slice(&[0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f]);
types.extend_from_slice(&[0x60, 0x01, 0x7f, 0x01, 0x7f]);
types.extend_from_slice(&[0x60, 0x00, 0x01, 0x7f]);
let mut imports = Vec::new();
leb_u(4, &mut imports);
let import_name = |m: &str, f: &str, out: &mut Vec<u8>| {
leb_u(m.len() as u64, out);
out.extend_from_slice(m.as_bytes());
leb_u(f.len() as u64, out);
out.extend_from_slice(f.as_bytes());
};
import_name("env", "__linear_memory", &mut imports);
imports.push(0x02);
imports.push(0x00);
leb_u(0, &mut imports);
for (name, ty) in [("logos_rt_bigint_from_i64", 0u64), ("logos_rt_bigint_mul", 1), ("logos_rt_bigint_to_text", 2)] {
import_name("env", name, &mut imports);
imports.push(0x00);
leb_u(ty, &mut imports);
}
let (from_i64, mul, to_text, main_idx): (u32, u32, u32, u32) = (0, 1, 2, 3);
let mut funcs = Vec::new();
leb_u(1, &mut funcs);
leb_u(3, &mut funcs);
fn emit_call(body: &mut Vec<u8>, func: u32, sym: u32, offs: &mut Vec<(u32, u32)>) {
body.push(0x10); offs.push((body.len() as u32, sym));
leb_u32_padded(func, body);
}
let mut body = Vec::new();
leb_u(1, &mut body); leb_u(1, &mut body); body.push(0x7f); let mut calls: Vec<(u32, u32)> = Vec::new();
body.push(0x42); leb_i64(1_000_000_000_000, &mut body);
emit_call(&mut body, from_i64, 0, &mut calls); body.extend_from_slice(&[0x21, 0x00]); for _ in 0..3 {
body.extend_from_slice(&[0x20, 0x00, 0x20, 0x00]); emit_call(&mut body, mul, 1, &mut calls); body.extend_from_slice(&[0x21, 0x00]); }
body.extend_from_slice(&[0x20, 0x00]); emit_call(&mut body, to_text, 2, &mut calls); body.push(0x0b);
let mut code = Vec::new();
leb_u(1, &mut code);
leb_u(body.len() as u64, &mut code);
let prefix = code.len() as u32;
code.extend_from_slice(&body);
let relocs: Vec<CodeReloc> =
calls.iter().map(|&(off, sym)| CodeReloc { offset: prefix + off, sym_index: sym }).collect();
let symbols = vec![
FuncSymbol { func_index: from_i64, defined_name: None },
FuncSymbol { func_index: mul, defined_name: None },
FuncSymbol { func_index: to_text, defined_name: None },
FuncSymbol { func_index: main_idx, defined_name: Some("main".to_string()) },
];
let mut out = Vec::new();
out.extend_from_slice(b"\0asm");
out.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
section(1, &types, &mut out);
section(2, &imports, &mut out);
section(3, &funcs, &mut out);
section(10, &code, &mut out);
custom_section("linking", &encode_linking(&symbols), &mut out);
custom_section("reloc.CODE", &encode_reloc_code(3, &relocs), &mut out);
out
}
fn encode_linking(symbols: &[FuncSymbol]) -> Vec<u8> {
const WASM_SYM_UNDEFINED: u32 = 0x10;
let mut symtab = Vec::new();
leb_u(symbols.len() as u64, &mut symtab);
for s in symbols {
symtab.push(0x00); match &s.defined_name {
Some(name) => {
leb_u(0, &mut symtab); leb_u(u64::from(s.func_index), &mut symtab);
leb_u(name.len() as u64, &mut symtab);
symtab.extend_from_slice(name.as_bytes());
}
None => {
leb_u(u64::from(WASM_SYM_UNDEFINED), &mut symtab); leb_u(u64::from(s.func_index), &mut symtab); }
}
}
let mut body = Vec::new();
leb_u(2, &mut body); body.push(0x08); leb_u(symtab.len() as u64, &mut body);
body.extend_from_slice(&symtab);
body
}
fn encode_reloc_code(section_index: u32, relocs: &[CodeReloc]) -> Vec<u8> {
const R_WASM_FUNCTION_INDEX_LEB: u8 = 0x00;
let mut body = Vec::new();
leb_u(u64::from(section_index), &mut body);
leb_u(relocs.len() as u64, &mut body);
for r in relocs {
body.push(R_WASM_FUNCTION_INDEX_LEB);
leb_u(u64::from(r.offset), &mut body);
leb_u(u64::from(r.sym_index), &mut body);
}
body
}
fn emit_shim_object(name: &str, params: &[u8], results: &[u8], trap: bool) -> Vec<u8> {
let mut types = Vec::new();
leb_u(1, &mut types);
types.push(0x60);
leb_u(params.len() as u64, &mut types);
types.extend_from_slice(params);
leb_u(results.len() as u64, &mut types);
types.extend_from_slice(results);
let mut funcs = Vec::new();
leb_u(1, &mut funcs);
leb_u(0, &mut funcs);
let mut body = vec![0x00u8];
if trap {
body.push(0x00); }
body.push(0x0b); let mut code = Vec::new();
leb_u(1, &mut code);
leb_u(body.len() as u64, &mut code);
code.extend_from_slice(&body);
let symbols = vec![FuncSymbol { func_index: 0, defined_name: Some(name.to_string()) }];
let mut out = Vec::new();
out.extend_from_slice(b"\0asm");
out.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
section(1, &types, &mut out);
section(3, &funcs, &mut out);
section(10, &code, &mut out);
custom_section("linking", &encode_linking(&symbols), &mut out);
out
}
fn read_uleb(bytes: &[u8], i: &mut usize) -> u64 {
let mut r = 0u64;
let mut s = 0;
loop {
let b = bytes[*i];
*i += 1;
r |= u64::from(b & 0x7f) << s;
if b & 0x80 == 0 {
break;
}
s += 7;
}
r
}
struct FuncImportSig {
name: String,
params: Vec<u8>,
results: Vec<u8>,
}
fn parse_types(object: &[u8]) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut out = Vec::new();
if object.len() < 8 {
return out;
}
let mut i = 8;
while i < object.len() {
let sid = object[i];
i += 1;
let size = read_uleb(object, &mut i) as usize;
let end = i + size;
if sid == 1 {
let mut j = i;
let count = read_uleb(object, &mut j);
for _ in 0..count {
j += 1; let np = read_uleb(object, &mut j) as usize;
let params = object[j..j + np].to_vec();
j += np;
let nr = read_uleb(object, &mut j) as usize;
let results = object[j..j + nr].to_vec();
j += nr;
out.push((params, results));
}
}
i = end;
}
out
}
fn function_imports(object: &[u8]) -> Vec<FuncImportSig> {
let types = parse_types(object);
let mut out = Vec::new();
if object.len() < 8 {
return out;
}
let mut i = 8;
while i < object.len() {
let sid = object[i];
i += 1;
let size = read_uleb(object, &mut i) as usize;
let end = i + size;
if sid == 2 {
let mut j = i;
let count = read_uleb(object, &mut j);
for _ in 0..count {
let ml = read_uleb(object, &mut j) as usize;
j += ml;
let fl = read_uleb(object, &mut j) as usize;
let field = String::from_utf8_lossy(&object[j..j + fl]).to_string();
j += fl;
let kind = object[j];
j += 1;
match kind {
0 => {
let ti = read_uleb(object, &mut j) as usize;
let (params, results) = types.get(ti).cloned().unwrap_or_default();
out.push(FuncImportSig { name: field, params, results });
}
1 => {
j += 1;
let flags = object[j];
j += 1;
read_uleb(object, &mut j);
if flags & 1 != 0 {
read_uleb(object, &mut j);
}
}
2 => {
let flags = object[j];
j += 1;
read_uleb(object, &mut j);
if flags & 1 != 0 {
read_uleb(object, &mut j);
}
}
3 => j += 2,
_ => {}
}
}
}
i = end;
}
out
}
fn host_triple() -> R<String> {
let out = Command::new("rustc")
.arg("-vV")
.output()
.map_err(|_| WasmLowerError::Unsupported("rustc not found for the wasm linker"))?;
let text = String::from_utf8_lossy(&out.stdout);
text.lines()
.find_map(|l| l.strip_prefix("host: "))
.map(|s| s.trim().to_string())
.ok_or(WasmLowerError::Unsupported("could not determine the host triple"))
}
fn sysroot() -> R<PathBuf> {
let out = Command::new("rustc")
.args(["--print", "sysroot"])
.output()
.map_err(|_| WasmLowerError::Unsupported("rustc not found for the wasm linker"))?;
Ok(PathBuf::from(String::from_utf8_lossy(&out.stdout).trim().to_string()))
}
pub(crate) fn rust_lld_path() -> R<PathBuf> {
let host = host_triple()?;
let p = sysroot()?.join("lib/rustlib").join(host).join("bin/rust-lld");
if p.exists() {
Ok(p)
} else {
Err(WasmLowerError::Unsupported("rust-lld not present in the toolchain"))
}
}
fn wasm_runtime_rlibs() -> R<Vec<PathBuf>> {
let dir = sysroot()?.join("lib/rustlib/wasm32-unknown-unknown/lib");
let find = |prefix: &str| -> R<PathBuf> {
let entries = std::fs::read_dir(&dir)
.map_err(|_| WasmLowerError::Unsupported("wasm32 sysroot lib dir missing"))?;
for entry in entries.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with(prefix) && name.ends_with(".rlib") {
return Ok(entry.path());
}
}
Err(WasmLowerError::Unsupported("a wasm32 sysroot rlib is missing"))
};
Ok(vec![find("liballoc-")?, find("libcore-")?, find("libcompiler_builtins-")?])
}
pub(crate) fn toolchain_available() -> bool {
if rust_lld_path().is_err() {
return false;
}
Command::new("rustc")
.args(["--print", "target-list"])
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).lines().any(|l| l == "wasm32-unknown-unknown"))
.unwrap_or(false)
}
pub(crate) fn build_runtime_object(src: &str, dir: &std::path::Path) -> R<Vec<u8>> {
std::fs::create_dir_all(dir).map_err(|_| WasmLowerError::Unsupported("cannot create link scratch dir"))?;
let rs = dir.join("logos_rt.rs");
let obj = dir.join("logos_rt.o");
std::fs::write(&rs, src).map_err(|_| WasmLowerError::Unsupported("cannot write runtime source"))?;
let status = Command::new("rustc")
.args(["--target", "wasm32-unknown-unknown", "--crate-type=lib", "--emit=obj", "-Copt-level=2"])
.arg(&rs)
.arg("-o")
.arg(&obj)
.status()
.map_err(|_| WasmLowerError::Unsupported("failed to invoke rustc for the runtime object"))?;
if !status.success() {
return Err(WasmLowerError::Unsupported("rustc failed to build the runtime object"));
}
std::fs::read(&obj).map_err(|_| WasmLowerError::Unsupported("cannot read the runtime object"))
}
pub(crate) fn link_objects(objects: &[&[u8]], dir: &std::path::Path) -> R<Vec<u8>> {
link_objects_with_rlibs(objects, &wasm_runtime_rlibs()?, true, dir)
}
pub(crate) fn link_objects_with_rlibs(
objects: &[&[u8]],
rlibs: &[PathBuf],
emit_handler_shim: bool,
dir: &std::path::Path,
) -> R<Vec<u8>> {
std::fs::create_dir_all(dir).map_err(|_| WasmLowerError::Unsupported("cannot create link scratch dir"))?;
let lld = rust_lld_path()?;
let mut shim_objs: Vec<Vec<u8>> = Vec::new();
let mut seen: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for obj in objects {
for imp in function_imports(obj) {
if imp.name.contains("no_alloc_shim") && seen.insert(imp.name.clone()) {
shim_objs.push(emit_shim_object(&imp.name, &imp.params, &imp.results, false));
if emit_handler_shim {
if let Some(idx) = imp.name.find("___rustc") {
let prefix = &imp.name[..idx + "___rustc".len()];
let handler = format!("{prefix}26___rust_alloc_error_handler");
if seen.insert(handler.clone()) {
shim_objs.push(emit_shim_object(&handler, &[0x7f, 0x7f], &[], true));
}
}
}
}
}
}
let mut obj_paths = Vec::new();
for (i, obj) in objects.iter().copied().chain(shim_objs.iter().map(|v| v.as_slice())).enumerate() {
let p = dir.join(format!("obj{i}.o"));
std::fs::write(&p, obj).map_err(|_| WasmLowerError::Unsupported("cannot write link input"))?;
obj_paths.push(p);
}
let out = dir.join("linked.wasm");
let mut cmd = Command::new(&lld);
cmd.args([
"-flavor",
"wasm",
"--no-entry",
"--export=main",
"--export=__heap_base",
"--export=__data_end",
"-z",
"stack-size=1048576",
"--stack-first",
"--no-demangle",
"--gc-sections",
"--allow-undefined",
]);
for p in &obj_paths {
cmd.arg(p);
}
for rlib in rlibs {
cmd.arg(rlib);
}
cmd.arg("-o").arg(&out);
let output = cmd.output().map_err(|_| WasmLowerError::Unsupported("failed to invoke rust-lld"))?;
if !output.success() {
return Err(WasmLowerError::Unsupported("rust-lld failed to link the module"));
}
std::fs::read(&out).map_err(|_| WasmLowerError::Unsupported("cannot read the linked module"))
}
fn wasm_all_sysroot_rlibs() -> R<Vec<PathBuf>> {
let dir = sysroot()?.join("lib/rustlib/wasm32-unknown-unknown/lib");
let entries =
std::fs::read_dir(&dir).map_err(|_| WasmLowerError::Unsupported("wasm32 sysroot lib dir missing"))?;
let mut rlibs: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().map_or(false, |x| x == "rlib"))
.collect();
rlibs.sort();
Ok(rlibs)
}
trait Success {
fn success(&self) -> bool;
}
impl Success for std::process::Output {
fn success(&self) -> bool {
self.status.success()
}
}
#[cfg(test)]
const PROBE_RUNTIME_SRC: &str = r#"#![no_std]
#[panic_handler] fn ph(_: &core::panic::PanicInfo) -> ! { loop {} }
#[no_mangle] pub extern "C" fn logos_rt_probe(x: i64) -> i64 { x.wrapping_add(1) }
"#;
#[cfg(test)]
const MEM_RUNTIME_SRC: &str = r#"#![no_std]
#[panic_handler] fn ph(_: &core::panic::PanicInfo) -> ! { loop {} }
static mut CELL: i64 = 0;
#[no_mangle] pub extern "C" fn logos_rt_write_answer() -> i32 {
unsafe { let p = core::ptr::addr_of_mut!(CELL); *p = 42; p as i32 }
}
"#;
#[cfg(test)]
const ALLOC_RUNTIME_SRC: &str = r#"#![no_std]
extern crate alloc;
use core::alloc::{GlobalAlloc, Layout};
#[panic_handler] fn ph(_: &core::panic::PanicInfo) -> ! { loop {} }
struct Bump;
static mut ARENA: [u8; 1 << 20] = [0; 1 << 20];
static mut OFF: usize = 0;
unsafe impl GlobalAlloc for Bump {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
let base = core::ptr::addr_of_mut!(ARENA) as *mut u8;
let off = core::ptr::addr_of!(OFF).read();
let aligned = (off + l.align() - 1) & !(l.align() - 1);
core::ptr::addr_of_mut!(OFF).write(aligned + l.size());
base.add(aligned)
}
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {}
}
#[global_allocator] static A: Bump = Bump;
#[no_mangle] pub extern "C" fn logos_rt_alloc_answer() -> i32 {
alloc::boxed::Box::into_raw(alloc::boxed::Box::new(42i64)) as i32
}
"#;
#[cfg(test)]
const MUL_TEXT_RUNTIME_SRC: &str = r#"#![no_std]
extern crate alloc;
use core::alloc::{GlobalAlloc, Layout};
use alloc::string::ToString;
#[panic_handler] fn ph(_: &core::panic::PanicInfo) -> ! { loop {} }
struct Bump;
static mut ARENA: [u8; 1 << 20] = [0; 1 << 20];
static mut OFF: usize = 0;
unsafe impl GlobalAlloc for Bump {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
let base = core::ptr::addr_of_mut!(ARENA) as *mut u8;
let off = core::ptr::addr_of!(OFF).read();
let a = (off + l.align() - 1) & !(l.align() - 1);
core::ptr::addr_of_mut!(OFF).write(a + l.size());
base.add(a)
}
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {}
}
#[global_allocator] static A: Bump = Bump;
#[no_mangle] pub extern "C" fn logos_rt_i64_mul_to_text(a: i64, b: i64) -> i32 {
let product: i128 = (a as i128) * (b as i128);
let s = product.to_string();
let bytes = s.as_bytes();
let len = bytes.len();
unsafe {
let data = alloc::alloc::alloc(Layout::from_size_align_unchecked(len.max(1), 1));
core::ptr::copy_nonoverlapping(bytes.as_ptr(), data, len);
let header = alloc::alloc::alloc(Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data as i32;
*header.add(3) = 0;
header as i32
}
}
"#;
const BIGINT_RUNTIME_SRC: &str = r#"#[global_allocator] static GLOBAL: std::alloc::System = std::alloc::System;
use logicaffeine_base::BigInt;
#[no_mangle] pub extern "C" fn logos_rt_bigint_from_i64(x: i64) -> i32 {
Box::into_raw(Box::new(BigInt::from_i64(x))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_alloc(size: i32) -> i32 {
// A raw 8-aligned block from the runtime's global allocator (dlmalloc), so the emitter's bump
// allocator can carve a SLAB the runtime owns — the two allocators never overlap in the shared memory.
unsafe { std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(size.max(8) as usize, 8)) as i32 }
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_mul(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const BigInt) };
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(a.mul(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const BigInt) };
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(a.add(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const BigInt) };
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(a.sub(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_div(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const BigInt) };
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(a.div_rem(b).expect("division by zero").0)) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_mod(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const BigInt) };
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(a.div_rem(b).expect("division by zero").1)) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_pow(base: i32, exp: i64) -> i32 {
let base = unsafe { &*(base as *const BigInt) };
Box::into_raw(Box::new(base.pow(exp as u32))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_bigint_to_text(h: i32) -> i32 {
let n = unsafe { &*(h as *const BigInt) };
let bytes = n.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Complex (numeric tower): an EXACT `Complex { re: Rational, im: Rational }` behind an i32 handle
// (a leaked Box pointer), mirroring the BigInt ABI. `complex(re, im)` takes two integer components.
use logicaffeine_base::{Complex, Rational};
#[no_mangle] pub extern "C" fn logos_rt_complex_from_i64(re: i64, im: i64) -> i32 {
Box::into_raw(Box::new(Complex::new(Rational::from_i64(re), Rational::from_i64(im)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_complex_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Complex) };
let b = unsafe { &*(b as *const Complex) };
Box::into_raw(Box::new(a.add(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_complex_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Complex) };
let b = unsafe { &*(b as *const Complex) };
Box::into_raw(Box::new(a.sub(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_complex_mul(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Complex) };
let b = unsafe { &*(b as *const Complex) };
Box::into_raw(Box::new(a.mul(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_complex_to_text(h: i32) -> i32 {
let c = unsafe { &*(h as *const Complex) };
let bytes = c.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Modular (ℤ/nℤ): an exact `Modular { value, modulus }` behind an i32 handle. `modular(v, n)`
// reduces on construction; `+ - *` wrap in the ring (moduli must match — the VM guarantees it).
use logicaffeine_base::Modular;
#[no_mangle] pub extern "C" fn logos_rt_modular_from_i64(v: i64, n: i64) -> i32 {
Box::into_raw(Box::new(Modular::from_i64(v, n).expect("invalid modulus"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_modular_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Modular) };
let b = unsafe { &*(b as *const Modular) };
Box::into_raw(Box::new(a.add(b).expect("modulus mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_modular_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Modular) };
let b = unsafe { &*(b as *const Modular) };
Box::into_raw(Box::new(a.sub(b).expect("modulus mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_modular_mul(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Modular) };
let b = unsafe { &*(b as *const Modular) };
Box::into_raw(Box::new(a.mul(b).expect("modulus mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_modular_to_text(h: i32) -> i32 {
let m = unsafe { &*(h as *const Modular) };
let bytes = m.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Decimal (exact base-10): `decimal("19.99")` parses a Text handle (read from the SHARED linear
// memory: len@0, data_ptr@8) into an exact `Decimal`; an Int operand promotes via `from_i64`; `+ - *`
// keep exact scale; `to_text` renders it. (Division/comparison carry a scale/mode — deferred.)
use logicaffeine_base::Decimal;
#[no_mangle] pub extern "C" fn logos_rt_decimal_from_text(h: i32) -> i32 {
let (len, dp) = unsafe { (*(h as *const i32) as usize, *((h as *const i32).add(2))) };
let s = unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(dp as *const u8, len)) };
Box::into_raw(Box::new(Decimal::parse(s.trim()).expect("invalid decimal literal"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_decimal_from_i64(x: i64) -> i32 {
Box::into_raw(Box::new(Decimal::from_i64(x))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_decimal_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Decimal) };
let b = unsafe { &*(b as *const Decimal) };
Box::into_raw(Box::new(a.add(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_decimal_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Decimal) };
let b = unsafe { &*(b as *const Decimal) };
Box::into_raw(Box::new(a.sub(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_decimal_mul(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Decimal) };
let b = unsafe { &*(b as *const Decimal) };
Box::into_raw(Box::new(a.mul(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_decimal_to_text(h: i32) -> i32 {
let d = unsafe { &*(h as *const Decimal) };
let bytes = d.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Money (exact amount + currency): `money(amount, "USD")` where amount is a Decimal handle or an
// Int; the currency is a Text CODE read from shared memory (`currency::by_code`). `+ -` require matching
// currencies; `to_text` renders it. (Division / cross-currency deferred.)
use logicaffeine_base::Money;
unsafe fn logos_rt_read_text(h: i32) -> String {
let len = *(h as *const i32) as usize;
let dp = *((h as *const i32).add(2));
String::from_utf8_lossy(std::slice::from_raw_parts(dp as *const u8, len)).into_owned()
}
#[no_mangle] pub extern "C" fn logos_rt_money_from_decimal(dec: i32, cur: i32) -> i32 {
let amount = unsafe { &*(dec as *const Decimal) }.clone();
let code = unsafe { logos_rt_read_text(cur) };
let c = logicaffeine_base::money::currency::by_code(code.trim()).expect("unknown currency");
Box::into_raw(Box::new(Money::of(amount, c))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_money_from_i64(v: i64, cur: i32) -> i32 {
let code = unsafe { logos_rt_read_text(cur) };
let c = logicaffeine_base::money::currency::by_code(code.trim()).expect("unknown currency");
Box::into_raw(Box::new(Money::of(Decimal::from_i64(v), c))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_money_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Money) };
let b = unsafe { &*(b as *const Money) };
Box::into_raw(Box::new(a.add(b).expect("currency mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_money_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Money) };
let b = unsafe { &*(b as *const Money) };
Box::into_raw(Box::new(a.sub(b).expect("currency mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_money_to_text(h: i32) -> i32 {
let m = unsafe { &*(h as *const Money) };
let bytes = m.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Quantity (exact magnitude + display unit): `quantity(value, "unit")` and `X in "unit"`
// (`convert`). The magnitude rides the exact rational tower normalized to the SI base; the display
// unit is carried alongside (presentation only). `+ -` keep the left display unit (dimension-checked
// at compile time); `× ÷` combine dimensions and render in the SI/dimension form; `to_text` mirrors
// the interpreter's `QuantityValue::display` byte-for-byte (the arithmetic itself stays in base).
use logicaffeine_base::{Quantity, Unit};
struct Qv { q: Quantity, unit: Unit }
unsafe fn qv_ref(h: i32) -> &'static Qv { &*(h as *const Qv) }
fn qv_box(q: Quantity, unit: Unit) -> i32 { Box::into_raw(Box::new(Qv { q, unit })) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_quantity_of_i64(v: i64, name: i32) -> i32 {
let nm = unsafe { logos_rt_read_text(name) };
let unit = logicaffeine_base::quantity::units::by_name(nm.trim()).expect("unknown unit");
let q = Quantity::of(Rational::from_i64(v), &unit);
qv_box(q, unit)
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_convert(h: i32, name: i32) -> i32 {
let a = unsafe { qv_ref(h) };
let nm = unsafe { logos_rt_read_text(name) };
let unit = logicaffeine_base::quantity::units::by_name(nm.trim()).expect("unknown unit");
qv_box(a.q.clone(), unit)
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_add(a: i32, b: i32) -> i32 {
let a = unsafe { qv_ref(a) };
let b = unsafe { qv_ref(b) };
qv_box(a.q.add(&b.q).expect("dimension mismatch"), a.unit.clone())
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_sub(a: i32, b: i32) -> i32 {
let a = unsafe { qv_ref(a) };
let b = unsafe { qv_ref(b) };
qv_box(a.q.sub(&b.q).expect("dimension mismatch"), a.unit.clone())
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_mul(a: i32, b: i32) -> i32 {
let a = unsafe { qv_ref(a) };
let b = unsafe { qv_ref(b) };
let q = a.q.mul(&b.q);
let u = Unit::linear("", q.dimension(), Rational::one());
qv_box(q, u)
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_div(a: i32, b: i32) -> i32 {
let a = unsafe { qv_ref(a) };
let b = unsafe { qv_ref(b) };
let q = a.q.div(&b.q).expect("division by a zero quantity");
let u = Unit::linear("", q.dimension(), Rational::one());
qv_box(q, u)
}
#[no_mangle] pub extern "C" fn logos_rt_quantity_to_text(h: i32) -> i32 {
let a = unsafe { qv_ref(h) };
let magnitude = a.q.in_unit(&a.unit).expect("a display unit shares its quantity's dimension");
let s = if a.unit.symbol.is_empty() {
format!("{} {}", magnitude, a.q.dimension())
} else {
format!("{} {}", magnitude, a.unit.symbol)
};
let bytes = s.into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Rational (exact BigInt-backed fraction): `a / b` in a Rational context and `+ - * /` on
// rationals. num/den are BigInt so num/den can exceed i64 without drift (unlike the self-contained
// i64/i64 form); `to_text` renders `num/den` (or `num` when the fraction is a whole number), exactly
// the VM's `Rational::to_string`. Int operands promote via `from_i64`, BigInt operands via `from_bigint`.
#[no_mangle] pub extern "C" fn logos_rt_rational_from_i64(x: i64) -> i32 {
Box::into_raw(Box::new(Rational::from_i64(x))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_from_bigint(b: i32) -> i32 {
let b = unsafe { &*(b as *const BigInt) };
Box::into_raw(Box::new(Rational::from_bigint(b.clone()))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_add(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Rational) };
let b = unsafe { &*(b as *const Rational) };
Box::into_raw(Box::new(a.add(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_sub(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Rational) };
let b = unsafe { &*(b as *const Rational) };
Box::into_raw(Box::new(a.sub(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_mul(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Rational) };
let b = unsafe { &*(b as *const Rational) };
Box::into_raw(Box::new(a.mul(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_div(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Rational) };
let b = unsafe { &*(b as *const Rational) };
Box::into_raw(Box::new(a.div(b).expect("rational division by zero"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_to_text(h: i32) -> i32 {
let r = unsafe { &*(h as *const Rational) };
let bytes = r.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// Rounding a Rational: `floor`/`ceil`/`round` are EXACT (BigInt num/den, never `as f64`) and return a
// BigInt handle; `abs` stays a Rational handle (`|-7/2| = 7/2`). These match the VM's exact rational
// floor/ceil/round/abs — the WASM `f64` floor/ceil path (a lossy cast) is only for Float/Int operands.
#[no_mangle] pub extern "C" fn logos_rt_rational_floor(h: i32) -> i32 {
let r = unsafe { &*(h as *const Rational) };
Box::into_raw(Box::new(r.floor())) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_ceil(h: i32) -> i32 {
let r = unsafe { &*(h as *const Rational) };
Box::into_raw(Box::new(r.ceil())) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_round(h: i32) -> i32 {
let r = unsafe { &*(h as *const Rational) };
Box::into_raw(Box::new(r.round())) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_rational_abs(h: i32) -> i32 {
let r = unsafe { &*(h as *const Rational) };
Box::into_raw(Box::new(r.abs())) as i32
}
// ---- Uuid (RFC 9562, a 16-byte value): `uuid("…")` parses, `uuid_nil`/`uuid_max`/`uuid_dns`/… are
// constants, `uuid_version` reads the version nibble, equality compares the 16 bytes, `to_text` renders
// the canonical lowercase form — all delegating to `base::Uuid` (parse + Display), never reimplemented.
use logicaffeine_base::Uuid;
#[no_mangle] pub extern "C" fn logos_rt_uuid_parse(text: i32) -> i32 {
let s = unsafe { logos_rt_read_text(text) };
Box::into_raw(Box::new(Uuid::parse(s.trim()).expect("invalid uuid"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_uuid_nil() -> i32 { Box::into_raw(Box::new(Uuid::NIL)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_max() -> i32 { Box::into_raw(Box::new(Uuid::MAX)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_dns() -> i32 { Box::into_raw(Box::new(Uuid::NAMESPACE_DNS)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_url() -> i32 { Box::into_raw(Box::new(Uuid::NAMESPACE_URL)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_oid() -> i32 { Box::into_raw(Box::new(Uuid::NAMESPACE_OID)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_x500() -> i32 { Box::into_raw(Box::new(Uuid::NAMESPACE_X500)) as i32 }
#[no_mangle] pub extern "C" fn logos_rt_uuid_version(h: i32) -> i64 {
let u = unsafe { &*(h as *const Uuid) };
u.version() as i64
}
#[no_mangle] pub extern "C" fn logos_rt_uuid_eq(a: i32, b: i32) -> i32 {
let a = unsafe { &*(a as *const Uuid) };
let b = unsafe { &*(b as *const Uuid) };
(a == b) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_uuid_from_ptr(ptr: i32) -> i32 {
let mut b = [0u8; 16];
unsafe { std::ptr::copy_nonoverlapping(ptr as *const u8, b.as_mut_ptr(), 16); }
Box::into_raw(Box::new(Uuid::from_bytes(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_uuid_to_text(h: i32) -> i32 {
let u = unsafe { &*(h as *const Uuid) };
let bytes = u.to_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
// ---- Extended temporal that needs base's calendar logic (LINKER MODE): `format_timestamp(m)` renders
// a Moment as its RFC-3339 UTC string (a `Text` handle), `months_between`/`years_between` count complete
// calendar months/years. Each delegates to the SAME `base::temporal` the VM uses, so bit-identical.
#[no_mangle] pub extern "C" fn logos_rt_format_timestamp(nanos: i64) -> i32 {
let bytes = logicaffeine_base::temporal::format_rfc3339(nanos).into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
#[no_mangle] pub extern "C" fn logos_rt_months_between(a: i64, b: i64) -> i64 {
logicaffeine_base::temporal::months_between(a, b)
}
#[no_mangle] pub extern "C" fn logos_rt_years_between(a: i64, b: i64) -> i64 {
logicaffeine_base::temporal::years_between(a, b)
}
// Zoned temporal: the zone name is an EMITTER-side `Text` handle (`[len@0][cap@4][data_ptr@8]`) in the
// SAME linear memory (valid after linking), so the runtime reads its bytes in place. `in_zone(m, zone)`
// → the local wall-clock `Text` (`…±HH:MM`), `local_instant(m, zone)` → the local-as-UTC `Moment` nanos.
// An unknown zone traps (the VM errors — no output-comparable value). Delegates to `base::temporal`.
unsafe fn logos_rt_zone_str<'a>(zone: i32) -> &'a str {
let h = zone as *const i32;
let len = *h as usize;
let dp = *h.add(2) as usize;
std::str::from_utf8(std::slice::from_raw_parts(dp as *const u8, len)).expect("zone name is UTF-8")
}
#[no_mangle] pub extern "C" fn logos_rt_in_zone(nanos: i64, zone: i32) -> i32 {
let s = unsafe { logos_rt_zone_str(zone) };
let bytes = logicaffeine_base::temporal::format_zoned(nanos, s).expect("unknown time zone").into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
#[no_mangle] pub extern "C" fn logos_rt_local_instant(nanos: i64, zone: i32) -> i64 {
let s = unsafe { logos_rt_zone_str(zone) };
logicaffeine_base::temporal::local_instant_nanos(nanos, s).expect("unknown time zone")
}
// ---- SIMD lane vectors (general `base::LanesVal`, LINKER MODE): the SSE byte/word-lane vocabulary a
// Logos codec (hex nibbles, Poly1305 limbs, …) compiles to. A lane vector is a leaked `Box<LanesVal>`
// i32 handle; the constructors read a `Seq` (i64-per-slot) from the SHARED memory, the extractor builds
// one, the ops delegate to the pure-Rust `base::word` spec (bit-identical to the VM — SSSE3 on x86, a
// scalar fallback on wasm32). A width-mismatched op traps (the VM errors). `LanesVal` is `Copy`.
unsafe fn logos_rt_seq_vals(handle: i32) -> Vec<i64> {
let h = handle as *const i32;
let len = *h as usize;
let dp = *h.add(2) as usize;
(0..len).map(|i| *((dp + i * 8) as *const i64)).collect()
}
unsafe fn logos_rt_lanes_build_seq(vals: &[i64]) -> i32 {
let n = vals.len();
let data = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked((n * 8).max(8), 8)) as *mut i64;
for (i, v) in vals.iter().enumerate() { *data.add(i) = *v; }
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = n as i32;
*header.add(1) = n as i32;
*header.add(2) = data as i32;
*header.add(3) = 0;
header as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes16_from_bytes(seq: i32) -> i32 {
let bytes: Vec<u8> = unsafe { logos_rt_seq_vals(seq) }.iter().map(|&v| v as u8).collect();
Box::into_raw(Box::new(logicaffeine_base::LanesVal::L16W8(logicaffeine_base::Lanes16Word8::from_bytes(&bytes)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes8_from_words(seq: i32) -> i32 {
let words: Vec<logicaffeine_base::Word32> = unsafe { logos_rt_seq_vals(seq) }.iter().map(|&v| logicaffeine_base::Word32(v as u32)).collect();
Box::into_raw(Box::new(logicaffeine_base::LanesVal::L8W32(logicaffeine_base::Lanes8Word32::from_words(&words)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes4w64_from_words(seq: i32) -> i32 {
let words: Vec<logicaffeine_base::Word64> = unsafe { logos_rt_seq_vals(seq) }.iter().map(|&v| logicaffeine_base::Word64(v as u64)).collect();
Box::into_raw(Box::new(logicaffeine_base::LanesVal::L4W64(logicaffeine_base::Lanes4Word64::from_words(&words)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_splat16(x: i64) -> i32 {
Box::into_raw(Box::new(logicaffeine_base::LanesVal::L16W8(logicaffeine_base::Lanes16Word8::splat(x as u8)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_splat8(x: i64) -> i32 {
Box::into_raw(Box::new(logicaffeine_base::LanesVal::L8W32(logicaffeine_base::Lanes8Word32::splat(x as u32)))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_to_seq(handle: i32) -> i32 {
let v = unsafe { *(handle as *const logicaffeine_base::LanesVal) };
let vals: Vec<i64> = (0..v.lanes()).map(|i| v.lane(i) as i64).collect();
unsafe { logos_rt_lanes_build_seq(&vals) }
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_shuffle(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.shuffle(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_interleave_lo(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.interleave_lo(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_interleave_hi(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.interleave_hi(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_byte_add(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.byte_add(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_maddubs(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.maddubs_bytes(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_packus(a: i32, b: i32) -> i32 {
let (a, b) = unsafe { (*(a as *const logicaffeine_base::LanesVal), *(b as *const logicaffeine_base::LanesVal)) };
Box::into_raw(Box::new(a.packus_bytes(b).expect("lane width mismatch"))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_lanes_shr_bytes(a: i32, n: i64) -> i32 {
let a = unsafe { *(a as *const logicaffeine_base::LanesVal) };
Box::into_raw(Box::new(a.shr_bytes(n as u32).expect("lane width mismatch"))) as i32
}
// ---- Money FX (LINKER MODE): `base::money`'s ambient exchange-rate table is a `thread_local`
// (`AMBIENT_RATES`), wasm-safe and persistent across calls in ONE module. `set_rate`/`set_rates` install
// rates (the runtime reads the currency-code `Text` handle + the rate as a `Rational`; the emitter coerces
// Int/Decimal→Rational), `to_currency` converts a `Money` (reads the code Text, looks up the currency,
// `ambient_convert`s). An unknown currency / missing rate traps (the VM errors). `set_rate*` return the
// `Nothing` handle (0). Reads a `Map`'s 16-byte `[key:i64][value:i64]` entries from the shared memory.
#[no_mangle] pub extern "C" fn logos_rt_decimal_to_rational(h: i32) -> i32 {
Box::into_raw(Box::new(unsafe { &*(h as *const Decimal) }.to_rational())) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_set_rate(code: i32, rate: i32) -> i32 {
let code = unsafe { logos_rt_zone_str(code) };
let rate = unsafe { &*(rate as *const Rational) };
logicaffeine_base::money::set_ambient_rate(code, rate.clone());
0
}
#[no_mangle] pub extern "C" fn logos_rt_to_currency(money: i32, code: i32) -> i32 {
let m = unsafe { &*(money as *const Money) };
let code = unsafe { logos_rt_zone_str(code) };
let to = logicaffeine_base::money::currency::by_code(code).expect("unknown currency");
let out = logicaffeine_base::money::ambient_convert(m, to).expect("no exchange rate in scope");
Box::into_raw(Box::new(out)) as i32
}
unsafe fn logos_rt_map_entries(map: i32) -> Vec<(i64, i64)> {
let h = map as *const i32;
let num = *h as usize;
let dp = *h.add(2) as usize;
(0..num).map(|i| (*((dp + i * 16) as *const i64), *((dp + i * 16 + 8) as *const i64))).collect()
}
#[no_mangle] pub extern "C" fn logos_rt_set_rates_int(map: i32) -> i32 {
for (k, v) in unsafe { logos_rt_map_entries(map) } {
logicaffeine_base::money::set_ambient_rate(unsafe { logos_rt_zone_str(k as i32) }, Rational::from_i64(v));
}
0
}
#[no_mangle] pub extern "C" fn logos_rt_set_rates_rational(map: i32) -> i32 {
for (k, v) in unsafe { logos_rt_map_entries(map) } {
let rate = unsafe { &*((v as i32) as *const Rational) };
logicaffeine_base::money::set_ambient_rate(unsafe { logos_rt_zone_str(k as i32) }, rate.clone());
}
0
}
#[no_mangle] pub extern "C" fn logos_rt_set_rates_decimal(map: i32) -> i32 {
for (k, v) in unsafe { logos_rt_map_entries(map) } {
let dec = unsafe { &*((v as i32) as *const Decimal) };
logicaffeine_base::money::set_ambient_rate(unsafe { logos_rt_zone_str(k as i32) }, dec.to_rational());
}
0
}
// ---- Wire codec (`wireBytes`, LINKER MODE): marshal a value to its wire bytes via the REAL codec
// (`logicaffeine_compile::concurrency::marshal::encode_value_raw` over a reconstructed `RuntimeValue`),
// NOT a reimplementation — bit-identical to the VM's `bytes_to_seq(encode_value_raw(v))`. The emitter
// dispatches on the arg's Kind to the right reconstruction. `gc-sections` strips this whole path (and
// the compiler it pulls in) from any program that does not call `wireBytes`.
fn logos_rt_wire_seq(v: &logicaffeine_compile::interpreter::RuntimeValue) -> i32 {
let bytes = logicaffeine_compile::concurrency::marshal::encode_value_raw(v).expect("wire encode");
let vals: Vec<i64> = bytes.iter().map(|&b| b as i64).collect();
unsafe { logos_rt_lanes_build_seq(&vals) }
}
#[no_mangle] pub extern "C" fn logos_rt_wire_bytes_int(n: i64) -> i32 {
logos_rt_wire_seq(&logicaffeine_compile::interpreter::RuntimeValue::Int(n))
}
#[no_mangle] pub extern "C" fn logos_rt_wire_bytes_bool(b: i64) -> i32 {
logos_rt_wire_seq(&logicaffeine_compile::interpreter::RuntimeValue::Bool(b != 0))
}
#[no_mangle] pub extern "C" fn logos_rt_wire_bytes_float(f: f64) -> i32 {
logos_rt_wire_seq(&logicaffeine_compile::interpreter::RuntimeValue::Float(f))
}
#[no_mangle] pub extern "C" fn logos_rt_wire_bytes_text(handle: i32) -> i32 {
let s = unsafe { logos_rt_zone_str(handle) }.to_string();
logos_rt_wire_seq(&logicaffeine_compile::interpreter::RuntimeValue::Text(std::rc::Rc::new(s)))
}
// ---- Wire INPUT + received-code eval (LINKER MODE, over the REAL codec): `readWireProgram` decodes a
// host-supplied frame into a leaked `Box<RuntimeValue>` — Kind::Dynamic, the ONE boxed value in the AOT,
// because a wire program's type is only known at runtime. `dynamic_to_text` renders it (`to_display_string`)
// and `run_accepted` sandbox-evals a wire-received SHIPPED function through the acceptance contract.
#[no_mangle] pub extern "C" fn logos_rt_read_wire_program(buf: i32, len: i32) -> i32 {
let bytes = unsafe { std::slice::from_raw_parts(buf as *const u8, len as usize) };
let v = logicaffeine_compile::concurrency::marshal::decode_value_raw(bytes).expect("malformed wire program");
Box::into_raw(Box::new(v)) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_dynamic_to_text(h: i32) -> i32 {
let v = unsafe { &*(h as *const logicaffeine_compile::interpreter::RuntimeValue) };
let bytes = v.to_display_string().into_bytes();
let len = bytes.len();
let data = bytes.leak();
unsafe {
let header = std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(16, 4)) as *mut i32;
*header.add(0) = len as i32;
*header.add(1) = len as i32;
*header.add(2) = data.as_ptr() as i32;
*header.add(3) = 0;
header as i32
}
}
#[no_mangle] pub extern "C" fn logos_rt_run_accepted(fn_h: i32, arg: i64, lo: i64, hi: i64) -> i64 {
let f = unsafe { &*(fn_h as *const logicaffeine_compile::interpreter::RuntimeValue) };
logicaffeine_compile::semantics::acceptance::AcceptanceContract::new(lo, hi)
.apply(f, arg)
.expect("run_accepted: the acceptance contract refused this function/argument")
}
// ---- Calendar span arithmetic: `Moment + <span>` / `Date + <span>` (civil, months clamp end-of-month
// and respect leap years, the time-of-day rides along). `moment_add_span` delegates to the SAME
// `base::temporal` the VM uses; `date_add_span` reuses it via a midnight days↔nanos conversion.
#[no_mangle] pub extern "C" fn logos_rt_moment_add_span(nanos: i64, months: i32, days: i32) -> i64 {
let dt = logicaffeine_base::temporal::civil_from_unix_nanos(nanos);
let shifted = logicaffeine_base::temporal::add_span(dt, months as i64, days as i64);
logicaffeine_base::temporal::unix_nanos_from_civil(shifted)
}
// `date_add_span` is the VM's OWN inline civil-date logic (NOT base::add_span, which the moment path
// uses) — replicated VERBATIM so `Date + <span>` is bit-identical to the interpreter, including the
// end-of-month clamp (Jan 31 + 1 month = Feb 28/29).
fn logos_rt_days_in_month(year: i32, month: i32) -> i32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) { 29 } else { 28 },
_ => 30,
}
}
#[no_mangle] pub extern "C" fn logos_rt_date_add_span(days_since_epoch: i32, months: i32, days: i32) -> i32 {
let z = days_since_epoch + 719468;
let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i32 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let mut year = y + if m <= 2 { 1 } else { 0 };
let mut month = m as i32;
let mut day = d as i32;
let total_months = year.wrapping_mul(12).wrapping_add(month - 1).wrapping_add(months);
year = total_months / 12;
month = total_months % 12 + 1;
if month <= 0 {
month += 12;
year -= 1;
}
let dim = logos_rt_days_in_month(year, month);
if day > dim {
day = dim;
}
let yp = year - if month <= 2 { 1 } else { 0 };
let era2 = if yp >= 0 { yp / 400 } else { (yp - 399) / 400 };
let yoe2 = (yp - era2 * 400) as u32;
let mp2 = if month > 2 { month as u32 - 3 } else { month as u32 + 9 };
let doy2 = (153 * mp2 + 2) / 5 + day as u32 - 1;
let doe2 = yoe2 * 365 + yoe2 / 4 - yoe2 / 100 + doy2;
let result = era2 * 146097 + doe2 as i32 - 719468;
result.wrapping_add(days)
}
// ---- SHA-1 SHA-NI lane intrinsics: `sha1rnds4`/`sha1msg1`/`sha1msg2`/`sha1nexte` over a 128-bit
// `Lanes4Word32` (a 16-byte `[u32; 4]` block in shared memory). The emitter builds/reads the lane blocks
// inline; only these four ops (the actual SHA-1 rounds) delegate to the SAME `base` software spec the VM
// uses — so the compiled Logos SHA-1 (and thus `uuid_v3`/`uuid_v5`) is bit-identical to the interpreter.
use logicaffeine_base::Lanes4Word32;
#[no_mangle] pub extern "C" fn logos_rt_sha1rnds4(abcd: i32, msg: i32, func: i64) -> i32 {
let a = unsafe { *(abcd as *const Lanes4Word32) };
let m = unsafe { *(msg as *const Lanes4Word32) };
Box::into_raw(Box::new(a.sha1rnds4(m, func as u32))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_sha1msg1(a: i32, b: i32) -> i32 {
let a = unsafe { *(a as *const Lanes4Word32) };
let b = unsafe { *(b as *const Lanes4Word32) };
Box::into_raw(Box::new(a.sha1msg1(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_sha1msg2(a: i32, b: i32) -> i32 {
let a = unsafe { *(a as *const Lanes4Word32) };
let b = unsafe { *(b as *const Lanes4Word32) };
Box::into_raw(Box::new(a.sha1msg2(b))) as i32
}
#[no_mangle] pub extern "C" fn logos_rt_sha1nexte(a: i32, b: i32) -> i32 {
let a = unsafe { *(a as *const Lanes4Word32) };
let b = unsafe { *(b as *const Lanes4Word32) };
Box::into_raw(Box::new(a.sha1nexte(b))) as i32
}
// Lane-wise wrapping add of two lane vectors (the `st + abcdSave` fold at the end of a SHA-1 block).
#[no_mangle] pub extern "C" fn logos_rt_lanes4_add(a: i32, b: i32) -> i32 {
let a = unsafe { *(a as *const Lanes4Word32) };
let b = unsafe { *(b as *const Lanes4Word32) };
Box::into_raw(Box::new(a + b)) as i32
}
// Lane-wise `xor` (the `m0 xor m2` message-schedule fold).
#[no_mangle] pub extern "C" fn logos_rt_lanes4_xor(a: i32, b: i32) -> i32 {
let a = unsafe { *(a as *const Lanes4Word32) };
let b = unsafe { *(b as *const Lanes4Word32) };
Box::into_raw(Box::new(a ^ b)) as i32
}
"#;
fn build_bigint_runtime(dir: &std::path::Path) -> R<(Vec<u8>, Vec<PathBuf>)> {
std::fs::create_dir_all(dir).map_err(|_| WasmLowerError::Unsupported("cannot create link scratch dir"))?;
let shared = std::env::temp_dir().join("logos_wire_compile_shared");
let ok = Command::new("cargo")
.args(["build", "-p", "logicaffeine-compile", "--target", "wasm32-unknown-unknown"])
.arg("--target-dir")
.arg(&shared)
.current_dir(env!("CARGO_MANIFEST_DIR"))
.status()
.map_err(|_| WasmLowerError::Unsupported("cargo not found"))?
.success();
if !ok {
return Err(WasmLowerError::Unsupported("cargo failed to build logicaffeine-compile for wasm32"));
}
let wasm_deps = shared.join("wasm32-unknown-unknown/debug/deps");
let host_deps = shared.join("debug/deps"); let find = |prefix: &str| -> R<PathBuf> {
let mut best: Option<(std::time::SystemTime, PathBuf)> = None;
for e in std::fs::read_dir(&wasm_deps)
.map_err(|_| WasmLowerError::Unsupported("compile wasm32 deps dir missing"))?
.flatten()
{
let n = e.file_name();
let n = n.to_string_lossy();
if n.starts_with(prefix) && n.ends_with(".rlib") {
let t = e.metadata().and_then(|m| m.modified()).unwrap_or(std::time::UNIX_EPOCH);
if best.as_ref().is_none_or(|(bt, _)| t >= *bt) {
best = Some((t, e.path()));
}
}
}
best.map(|(_, p)| p).ok_or(WasmLowerError::Unsupported("a compile-crate wasm32 rlib is missing"))
};
let base = find("liblogicaffeine_base-")?;
let compile = find("liblogicaffeine_compile-")?;
let rs = dir.join("bigint_rt.rs");
let obj = dir.join("bigint_rt.o");
std::fs::write(&rs, BIGINT_RUNTIME_SRC).map_err(|_| WasmLowerError::Unsupported("cannot write runtime source"))?;
let ok = Command::new("rustc")
.args(["--target", "wasm32-unknown-unknown", "--edition", "2021", "--crate-type=lib", "--emit=obj", "-Copt-level=2"])
.arg("--extern")
.arg(format!("logicaffeine_base={}", base.display()))
.arg("--extern")
.arg(format!("logicaffeine_compile={}", compile.display()))
.arg("-L")
.arg(&wasm_deps)
.arg("-L")
.arg(&host_deps)
.arg(&rs)
.arg("-o")
.arg(&obj)
.status()
.map_err(|_| WasmLowerError::Unsupported("failed to invoke rustc for the bigint+wire runtime"))?
.success();
if !ok {
return Err(WasmLowerError::Unsupported("rustc failed to build the bigint+wire runtime object"));
}
let bytes = std::fs::read(&obj).map_err(|_| WasmLowerError::Unsupported("cannot read the bigint runtime object"))?;
let mut rlibs = Vec::new();
for e in std::fs::read_dir(&wasm_deps)
.map_err(|_| WasmLowerError::Unsupported("compile wasm32 deps dir missing"))?
.flatten()
{
let n = e.file_name();
if n.to_string_lossy().ends_with(".rlib") {
rlibs.push(e.path());
}
}
Ok((bytes, rlibs))
}
static BIGINT_RUNTIME: std::sync::Mutex<Option<(Vec<u8>, Vec<PathBuf>)>> = std::sync::Mutex::new(None);
pub(crate) fn link_relocatable_bigint(relocatable: &[u8]) -> R<Vec<u8>> {
let dir = std::env::temp_dir().join("logos_wasm_linked_bigint");
let (runtime, rlibs) = {
let mut guard = BIGINT_RUNTIME.lock().unwrap_or_else(|p| p.into_inner());
if guard.is_none() {
let (rt, mut rlibs) = build_bigint_runtime(&dir)?;
rlibs.extend(wasm_all_sysroot_rlibs()?);
*guard = Some((rt, rlibs));
}
guard.as_ref().expect("runtime cache populated above").clone()
};
link_objects_with_rlibs(&[relocatable, &runtime], &rlibs, false, &dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_object_links_against_runtime_and_runs() {
if !toolchain_available() {
eprintln!("SKIP probe_object_links_against_runtime_and_runs: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_probe");
let runtime = build_runtime_object(PROBE_RUNTIME_SRC, &dir).expect("build runtime object");
let program = emit_probe_object();
let linked = link_objects(&[&program, &runtime], &dir).expect("link program + runtime");
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &linked[..]).expect("linked module is valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::new(&engine)
.instantiate(&mut store, &module)
.expect("instantiate linked module")
.start(&mut store)
.expect("start linked module");
let main = instance.get_typed_func::<(), i64>(&store, "main").expect("main export");
assert_eq!(main.call(&mut store, ()).expect("main runs"), 42, "linked runtime call must return probe(41)");
}
#[test]
fn emitted_code_reads_runtime_written_shared_memory() {
if !toolchain_available() {
eprintln!("SKIP emitted_code_reads_runtime_written_shared_memory: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_mem");
let runtime = build_runtime_object(MEM_RUNTIME_SRC, &dir).expect("build memory runtime object");
let program = emit_load_object("logos_rt_write_answer");
let linked = link_objects(&[&program, &runtime], &dir).expect("link program + runtime");
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &linked[..]).expect("linked module is valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::new(&engine)
.instantiate(&mut store, &module)
.expect("instantiate linked module")
.start(&mut store)
.expect("start linked module");
let main = instance.get_typed_func::<(), i64>(&store, "main").expect("main export");
assert_eq!(
main.call(&mut store, ()).expect("main runs"),
42,
"the pointer the runtime returned must be valid in the shared memory our code loads from"
);
}
#[test]
fn emitted_code_reads_runtime_heap_allocation() {
if !toolchain_available() {
eprintln!("SKIP emitted_code_reads_runtime_heap_allocation: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_alloc");
let runtime = build_runtime_object(ALLOC_RUNTIME_SRC, &dir).expect("build allocator runtime object");
let program = emit_load_object("logos_rt_alloc_answer");
let linked = link_objects(&[&program, &runtime], &dir).expect("link program + runtime");
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &linked[..]).expect("linked module is valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::new(&engine)
.instantiate(&mut store, &module)
.expect("instantiate linked module")
.start(&mut store)
.expect("start linked module");
let main = instance.get_typed_func::<(), i64>(&store, "main").expect("main export");
assert_eq!(
main.call(&mut store, ()).expect("main runs"),
42,
"a runtime heap allocation must be readable through the shared memory"
);
}
#[test]
fn runtime_returns_a_text_handle_our_side_reads() {
if !toolchain_available() {
eprintln!("SKIP runtime_returns_a_text_handle_our_side_reads: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_text");
let runtime = build_runtime_object(MUL_TEXT_RUNTIME_SRC, &dir).expect("build mul-text runtime object");
let program = emit_mul_text_object(1_000_000_000_000, 1_000_000_000_000);
let linked = link_objects(&[&program, &runtime], &dir).expect("link program + runtime");
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &linked[..]).expect("linked module is valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::new(&engine)
.instantiate(&mut store, &module)
.expect("instantiate linked module")
.start(&mut store)
.expect("start linked module");
let main = instance.get_typed_func::<(), i32>(&store, "main").expect("main export");
let handle = main.call(&mut store, ()).expect("main runs") as usize;
let mem = instance.get_memory(&store, "memory").expect("exported memory");
let data = mem.data(&store);
let len = i32::from_le_bytes(data[handle..handle + 4].try_into().unwrap()) as usize;
let dptr = i32::from_le_bytes(data[handle + 8..handle + 12].try_into().unwrap()) as usize;
let text = std::str::from_utf8(&data[dptr..dptr + len]).expect("utf8 Text bytes");
assert_eq!(text, "1000000000000000000000000", "the runtime-built Text handle must hold 10^24");
}
#[test]
fn runtime_computes_real_base_bigint_beyond_i128() {
if !toolchain_available() {
eprintln!("SKIP runtime_computes_real_base_bigint_beyond_i128: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_bigint");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP runtime_computes_real_base_bigint_beyond_i128: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let program = emit_bigint_object();
let linked = link_objects_with_rlibs(&[&program, &runtime], &rlibs, false, &dir).expect("link bigint program");
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, &linked[..]).expect("linked module is valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::new(&engine)
.instantiate(&mut store, &module)
.expect("instantiate linked module")
.start(&mut store)
.expect("start linked module");
let main = instance.get_typed_func::<(), i32>(&store, "main").expect("main export");
let handle = main.call(&mut store, ()).expect("main runs") as usize;
let mem = instance.get_memory(&store, "memory").expect("exported memory");
let data = mem.data(&store);
let len = i32::from_le_bytes(data[handle..handle + 4].try_into().unwrap()) as usize;
let dptr = i32::from_le_bytes(data[handle + 8..handle + 12].try_into().unwrap()) as usize;
let text = std::str::from_utf8(&data[dptr..dptr + len]).expect("utf8 Text bytes");
let expected = format!("1{}", "0".repeat(96)); assert_eq!(text, expected, "real base BigInt must compute 10^96 exactly (far beyond i128)");
}
#[cfg(test)]
thread_local! {
static WIRE_FRAME: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
}
fn run_linked_capturing_text(linked: &[u8]) -> String {
use std::cell::RefCell;
use std::rc::Rc;
let engine = wasmi::Engine::default();
let module = wasmi::Module::new(&engine, linked).expect("linked module is valid wasm");
let out: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let mut store = wasmi::Store::new(&engine, out.clone());
let mut l = wasmi::Linker::<Rc<RefCell<Vec<String>>>>::new(&engine);
l.func_wrap("env", "print_i64", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, v: i64| {
c.data().borrow_mut().push(v.to_string());
})
.unwrap();
l.func_wrap("env", "print_bool", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, v: i32| {
c.data().borrow_mut().push(if v != 0 { "true" } else { "false" }.to_string());
})
.unwrap();
l.func_wrap("env", "print_text", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, h: i32| {
let mem = c.get_export("memory").unwrap().into_memory().unwrap();
let d = mem.data(&c);
let h = h as usize;
let len = i32::from_le_bytes(d[h..h + 4].try_into().unwrap()) as usize;
let dp = i32::from_le_bytes(d[h + 8..h + 12].try_into().unwrap()) as usize;
c.data().borrow_mut().push(String::from_utf8_lossy(&d[dp..dp + len]).to_string());
})
.unwrap();
l.func_wrap("env", "print_seq_i64", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, h: i32| {
let mem = c.get_export("memory").unwrap().into_memory().unwrap();
let d = mem.data(&c);
let h = h as usize;
let len = i32::from_le_bytes(d[h..h + 4].try_into().unwrap()) as usize;
let dp = i32::from_le_bytes(d[h + 8..h + 12].try_into().unwrap()) as usize;
let items: Vec<String> = (0..len)
.map(|i| i64::from_le_bytes(d[dp + i * 8..dp + i * 8 + 8].try_into().unwrap()).to_string())
.collect();
c.data().borrow_mut().push(format!("[{}]", items.join(", ")));
})
.unwrap();
l.func_wrap("env", "print_moment", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, nanos: i64| {
c.data().borrow_mut().push(crate::interpreter::RuntimeValue::Moment(nanos).to_display_string());
})
.unwrap();
l.func_wrap("env", "print_seq_word32", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, h: i32| {
let mem = c.get_export("memory").unwrap().into_memory().unwrap();
let d = mem.data(&c);
let h = h as usize;
let len = i32::from_le_bytes(d[h..h + 4].try_into().unwrap()) as usize;
let dp = i32::from_le_bytes(d[h + 8..h + 12].try_into().unwrap()) as usize;
let items: Vec<String> = (0..len)
.map(|i| u32::from_le_bytes(d[dp + i * 8..dp + i * 8 + 4].try_into().unwrap()).to_string())
.collect();
c.data().borrow_mut().push(format!("[{}]", items.join(", ")));
})
.unwrap();
l.func_wrap("env", "parse_timestamp", |c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, h: i32| -> i64 {
let mem = c.get_export("memory").unwrap().into_memory().unwrap();
let d = mem.data(&c);
let h = h as usize;
let len = i32::from_le_bytes(d[h..h + 4].try_into().unwrap()) as usize;
let dp = i32::from_le_bytes(d[h + 8..h + 12].try_into().unwrap()) as usize;
let s = std::str::from_utf8(&d[dp..dp + len]).unwrap().trim();
logicaffeine_base::temporal::parse_rfc3339(s).expect("valid RFC 3339 timestamp")
})
.unwrap();
l.func_wrap("env", "read_wire_frame", |mut c: wasmi::Caller<'_, Rc<RefCell<Vec<String>>>>, buf: i32, max: i32| -> i32 {
let frame = WIRE_FRAME.with(|f| f.borrow().clone());
let n = frame.len().min(max as usize);
let mem = c.get_export("memory").unwrap().into_memory().unwrap();
let d = mem.data_mut(&mut c);
d[buf as usize..buf as usize + n].copy_from_slice(&frame[..n]);
n as i32
})
.unwrap();
let instance =
l.instantiate(&mut store, &module).expect("instantiate linked module").start(&mut store).expect("start");
instance.get_typed_func::<(), ()>(&store, "main").expect("main export").call(&mut store, ()).expect("main runs");
let lines = out.borrow().clone();
lines.join("\n")
}
fn lcg(state: &mut u64) -> u32 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(*state >> 33) as u32
}
fn fuzz_leaf(state: &mut u64) -> String {
match lcg(state) % 3 {
0 => format!("{}", 1 + lcg(state) % 90),
1 => format!("({} to the power of {})", 2 + lcg(state) % 4, 10 + lcg(state) % 30),
_ => format!("{}", 1 + lcg(state) % 5_000_000),
}
}
fn fuzz_expr(state: &mut u64, depth: u32) -> String {
if depth == 0 || lcg(state) % 3 == 0 {
return fuzz_leaf(state);
}
let l = fuzz_expr(state, depth - 1);
match lcg(state) % 4 {
0 => format!("({l} plus {})", fuzz_expr(state, depth - 1)),
1 => format!("({l} minus {})", fuzz_expr(state, depth - 1)),
2 => format!("({l} times {})", fuzz_expr(state, depth - 1)),
_ => format!("({l} divided by {})", 1 + lcg(state) % 90),
}
}
#[test]
fn fuzz_linked_bigint_arithmetic_is_byte_identical_to_the_vm() {
if !toolchain_available() {
eprintln!("SKIP fuzz_linked_bigint_arithmetic_is_byte_identical_to_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_fuzz");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP fuzz_linked_bigint_arithmetic_...: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
let mut state: u64 = 0x2545_F491_4F6C_DD1D;
let mut checked = 0;
for _ in 0..40 {
let src = format!("## Main\nShow {}.\n", fuzz_expr(&mut state, 3));
let vm = crate::compile::vm_outcome(&src);
if vm.error.is_some() {
continue; }
let reloc = match compile_reloc(&src) {
Ok(r) => r,
Err(_) => continue, };
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked wasm DIVERGED from the VM on:\n{src}");
checked += 1;
}
assert!(checked >= 20, "fuzz exercised too few programs ({checked}/40) — the generator or skip logic is off");
}
#[test]
fn linked_complex_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_complex_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_complex");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_complex_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let i be complex(0, 1).\n Show i * i.\n", "## Main\n Let a be complex(2, 3).\n Let b be complex(1, 0 - 1).\n Show a + b.\n", "## Main\n Let a be complex(1, 1).\n Let b be complex(1, 0 - 1).\n Show a * b.\n", "## Main\n Let z be complex(2, 3).\n Show z.\n", "## Main\n Let a be complex(5, 2).\n Let b be complex(3, 4).\n Show a - b.\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Complex DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_modular_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_modular_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_modular");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_modular_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let a be modular(10, 7).\n Show a.\n", "## Main\n Let a be modular(5, 7).\n Let b be modular(4, 7).\n Show a + b.\n", "## Main\n Let a be modular(5, 7).\n Let b be modular(4, 7).\n Show a * b.\n", "## Main\n Let a be modular(3, 7).\n Let b be modular(5, 7).\n Show a - b.\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Modular DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_decimal_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_decimal_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_decimal");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_decimal_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let a be decimal(\"0.1\").\n Let b be decimal(\"0.2\").\n Show a + b.\n", "## Main\n Let a be decimal(\"19.99\").\n Let b be decimal(\"0.01\").\n Show a + b.\n", "## Main\n Let a be decimal(\"5.5\").\n Let b be decimal(\"2.2\").\n Show a * b.\n", "## Main\n Let a be decimal(\"5.75\").\n Let b be decimal(\"1.25\").\n Show a - b.\n", "## Main\n Let x be decimal(\"19.99\").\n Show x.\n", "## Main\n Let p be decimal(\"19.99\").\n Show p * 3.\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Decimal DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_money_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_money_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_money");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_money_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let a be money(decimal(\"0.10\"), \"USD\").\n Let b be money(decimal(\"0.20\"), \"USD\").\n Show a + b.\n", "## Main\n Let a be money(decimal(\"30.00\"), \"USD\").\n Let b be money(decimal(\"10.00\"), \"USD\").\n Show a - b.\n", "## Main\n Let m be money(10, \"USD\").\n Show m.\n", "## Main\n Let m be money(100, \"JPY\").\n Show m.\n", "## Main\n Let m be money(decimal(\"19.99\"), \"USD\").\n Show m.\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Money DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_quantity_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_quantity_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_quantity");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_quantity_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let d be 5 meters.\n Show d.\n", "## Main\n Show quantity(2, \"inch\").\n", "## Main\n Show 2 meters + 3 meters.\n", "## Main\n Show 5 meters - 2 meters.\n", "## Main\n Let d be 5 meters.\n Show d in feet.\n", "## Main\n Show 2 meters * 3 meters.\n", "## Main\n Show 100 meters / 4 meters.\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Quantity DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_rational_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_rational_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_rational");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_rational_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let x: Rational be 7 / 2.\n Show x.\n", "## Main\n Let x: Rational be 6 / 2.\n Show x.\n", "## Main\n Let a: Rational be 1 / 3.\n Let b: Rational be 1 / 6.\n Show a + b.\n", "## Main\n Let r: Rational be 1 / 2.\n Show r + 3.\n", "## Main\n Let r: Rational be 1 / 2.\n Show r * 4.\n", "## Main\n Let r: Rational be 3 / 4.\n Show r - 1 / 4.\n", "## Main\n Let a: Rational be 1 / 2.\n Let b: Rational be 1 / 3.\n Show a / b.\n", "## Main\n Let a: Rational be 1 / 1000000000000.\n Let b: Rational be 1 / 999999999999.\n Show a + b.\n",
"## Main\n Let r: Rational be 7 / 2.\n Show floor(r).\n", "## Main\n Let r: Rational be 7 / 2.\n Show ceil(r).\n", "## Main\n Let r: Rational be 7 / 2.\n Show round(r).\n", "## Main\n Let r: Rational be -7 / 2.\n Show floor(r).\n", "## Main\n Let r: Rational be -7 / 2.\n Show abs(r).\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Rational DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_uuid_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_uuid_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_uuid");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_uuid_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Let u be uuid(\"550e8400-e29b-41d4-a716-446655440000\").\n Show u.\n", "## Main\n Show uuid_nil().\n", "## Main\n Show uuid_max().\n", "## Main\n Show uuid_dns().\n", "## Main\n Show uuid_oid().\n",
"## Main\n Show uuid_version(uuid(\"550e8400-e29b-41d4-a716-446655440000\")).\n", "## Main\n Show uuid_nil() is equal to uuid_nil().\n", "## Main\n Show uuid_nil() is equal to uuid_max().\n", "## Main\n Let a be uuid(\"550e8400-e29b-41d4-a716-446655440000\").\n Let b be uuid(\"550E8400-E29B-41D4-A716-446655440000\").\n Show a is equal to b.\n", "## Main\n Show uuid_bytes(uuid(\"550e8400-e29b-41d4-a716-446655440000\")).\n", "## Main\n Show uuid_from_bytes(uuid_bytes(uuid(\"550e8400-e29b-41d4-a716-446655440000\"))).\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Uuid DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_span_calendar_arithmetic_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_span_calendar_arithmetic_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_span");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_span_calendar_arithmetic_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Show parse_timestamp(\"2024-01-31T00:00:00Z\") + 1 month.\n", "## Main\n Show parse_timestamp(\"2024-01-31T00:00:00Z\") - 1 month.\n", "## Main\n Show parse_timestamp(\"2023-01-31T00:00:00Z\") + 1 month.\n", "## Main\n Show parse_timestamp(\"2024-01-15T12:00:00Z\") + 45 days.\n", "## Main\n Show parse_timestamp(\"2020-02-29T00:00:00Z\") + 1 year.\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Span DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_extended_temporal_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_extended_temporal_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_exttemporal");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_extended_temporal_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Show format_timestamp(parse_timestamp(\"2024-03-10T07:30:00Z\")).\n", "## Main\n Let a be parse_timestamp(\"2024-03-10T07:30:00Z\").\n Show format_timestamp(add_seconds(a, 90)).\n", "## Main\n Let a be parse_timestamp(\"1969-12-31T23:30:00Z\").\n Show format_timestamp(a).\n", "## Main\n Let a be parse_timestamp(\"2024-03-10T00:00:00Z\").\n Let b be parse_timestamp(\"2025-05-10T00:00:00Z\").\n Show months_between(a, b).\n Show years_between(a, b).\n",
"## Main\n Show in_zone(parse_timestamp(\"2024-07-01T12:00:00Z\"), \"America/New_York\").\n",
"## Main\n Show in_zone(parse_timestamp(\"2024-01-01T12:00:00Z\"), \"America/New_York\").\n",
"## Main\n Show format_timestamp(local_instant(parse_timestamp(\"2024-07-01T12:00:00Z\"), \"Asia/Tokyo\")).\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked extended temporal DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_sha1_lane_intrinsics_match_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_sha1_lane_intrinsics_match_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_sha1");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_sha1_lane_intrinsics_match_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
let prelude = "## Main\n Let a be lanes4Of(word32(1), word32(2), word32(3), word32(4)).\n Let b be lanes4Of(word32(5), word32(6), word32(7), word32(8)).\n";
for tail in [
" Show seqOfLanes4W32(a).\n", " Show seqOfLanes4W32(sha1msg1(a, b)).\n",
" Show seqOfLanes4W32(sha1msg2(a, b)).\n",
" Show seqOfLanes4W32(sha1nexte(a, b)).\n",
" Show seqOfLanes4W32(sha1rnds4(a, b, 0)).\n",
" Show seqOfLanes4W32(sha1rnds4(a, b, 2)).\n",
" Show seqOfLanes4W32(a + b).\n", " Show seqOfLanes4W32(a xor b).\n", ] {
let src = format!("{prelude}{tail}");
let vm = crate::compile::vm_outcome(&src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(&src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked SHA-1 lane op DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_simd_lanes_match_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_simd_lanes_match_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_simdlanes");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_simd_lanes_match_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
let prelude = "## Main\n Let bytes be [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].\n Let idx be [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0].\n Let a be lanes16Word8(bytes).\n Let b be lanes16Word8(idx).\n";
for tail in [
" Show seqOfLanes16W8(a).\n", " Show seqOfLanes16W8(splat16Word8(7)).\n", " Show seqOfLanes16W8(shuffle16(a, b)).\n", " Show seqOfLanes16W8(byteAdd16(a, splat16Word8(1))).\n", " Show seqOfLanes16W8(interleaveLo16(a, b)).\n",
" Show seqOfLanes16W8(interleaveHi16(a, b)).\n",
" Show seqOfLanes16W8(shrBytes16(a, 4)).\n", " Show seqOfLanes16W8(packus16(maddubs16(a, b), maddubs16(a, b))).\n", " Show seqOfLanes8(lanes8Word32([1, 2, 3, 4, 5, 6, 7, 8])).\n", ] {
let src = format!("{prelude}{tail}");
let vm = crate::compile::vm_outcome(&src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(&src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked SIMD lane op DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_money_fx_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_money_fx_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_moneyfx");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_money_fx_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n\
Call set_rate with \"USD\" and 1.\n\
Call set_rate with \"EUR\" and decimal(\"1.10\").\n\
Call set_rate with \"GBP\" and decimal(\"1.25\").\n\
Show 10.00 EUR in USD.\n\
Show 11.00 USD in EUR.\n\
Show 10.00 GBP in EUR.\n\
Show 42.00 USD in USD.\n",
"## Main\n\
Let mut rates be a new Map of Text to Decimal.\n\
Set item \"USD\" of rates to decimal(\"1\").\n\
Set item \"EUR\" of rates to decimal(\"1.10\").\n\
Set item \"GBP\" of rates to decimal(\"1.25\").\n\
Call set_rates with rates.\n\
Show 10.00 EUR in USD.\n\
Show 10.00 GBP in EUR.\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Money FX DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_wire_bytes_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_wire_bytes_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_wirebytes");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_wire_bytes_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Show wireBytes(42).\n", "## Main\n Show wireBytes(-7).\n", "## Main\n Show wireBytes(true).\n", "## Main\n Show wireBytes(false).\n",
"## Main\n Show wireBytes(\"hello\").\n", "## Main\n Show wireBytes(3.5).\n", ] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked wireBytes DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_read_wire_program_and_run_accepted_match_the_known_value() {
use crate::concurrency::marshal::{encode_value_raw, GenExpr};
use crate::interpreter::{ClosureValue, RuntimeValue};
if !toolchain_available() {
eprintln!("SKIP linked_read_wire_program: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_readwire");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_read_wire_program: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
let assert_wire = |src: &str, frame: Vec<u8>, expected: &str| {
WIRE_FRAME.with(|f| *f.borrow_mut() = frame);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
assert_eq!(run_linked_capturing_text(&linked).trim(), expected, "wire-input result for:\n{src}");
};
assert_wire("## Main\n Show readWireProgram().\n", encode_value_raw(&RuntimeValue::Int(42)).expect("encode Int"), "42");
let shipped = RuntimeValue::Function(Box::new(ClosureValue {
body_index: usize::MAX,
captured_env: std::collections::HashMap::default(),
param_names: vec![logicaffeine_base::Symbol::from_index(0)],
generated: Some(std::rc::Rc::new(GenExpr::Add(
Box::new(GenExpr::Mul(Box::new(GenExpr::Index), Box::new(GenExpr::Const(3)))),
Box::new(GenExpr::Const(1)),
))),
}));
assert_wire(
"## Main\n Let f be readWireProgram().\n Show run_accepted(f, 5, 0, 1000).\n",
encode_value_raw(&shipped).expect("encode shipped fn"),
"16",
);
}
#[test]
fn linked_crypto_uuid_in_logos_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_crypto_uuid_in_logos_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_cryptouuid");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_crypto_uuid_in_logos_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\n Show sha1(text_bytes(\"abc\")).\n", "## Main\n Show uuid_v5(uuid_dns(), \"example.com\").\n", "## Main\n Show uuid_v3(uuid_dns(), \"example.com\").\n", "## Main\n Show uuid_v5(uuid_url(), \"https://logicaffeine.com\").\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("compile-reloc `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked crypto UUID DIVERGED from the VM on:\n{src}");
}
}
#[test]
fn linked_mixed_text_and_bigint_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_mixed_text_and_bigint_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_mixed");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_mixed_text_and_bigint_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\nShow \"x = \" + (2 to the power of 200).\n",
"## Main\nShow (2 to the power of 100) + \" is big\".\n",
"## Main\nShow \"\" + (99999999999 times 99999999999) + \"!\".\n",
"## Main\nShow \"first \" + (2 to the power of 64) + \" then \" + (3 to the power of 40).\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("assemble `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "mixed heap+BigInt output disagrees with the VM for:\n{src}");
}
}
#[test]
fn linked_repeat_loops_match_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_repeat_loops_match_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_repeat");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_repeat_loops_match_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\nRepeat for n from 1 to 5:\n Show n.\n",
"## Main\nRepeat for n from 1 to 4:\n Show n to the power of 50.\n",
"## Main\nLet total be 0.\nRepeat for n from 1 to 100:\n Set total to total + n.\nShow total.\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("assemble `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked Repeat output disagrees with the VM for:\n{src}");
}
}
#[test]
fn linked_large_heap_beyond_the_slab_matches_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_large_heap_beyond_the_slab_matches_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_bigheap");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_large_heap_beyond_the_slab_matches_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let src = "## Main\nLet s be \"x\".\nRepeat for n from 1 to 3500:\n Set s to s + \"y\".\nShow s.\n";
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored: {:?}", vm.error);
let reloc = crate::ui_bridge::with_parsed_program(src, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
.expect("compile the large-heap program");
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir).expect("link large-heap program");
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked >4MiB-heap output disagrees with the VM");
}
#[test]
fn linked_let_bindings_match_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_let_bindings_match_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_let");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_let_bindings_match_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\nLet x be 99999999999 times 99999999999.\nShow x.\n",
"## Main\nLet p be 2 to the power of 100.\nLet q be 3 to the power of 50.\nShow p times q.\n",
"## Main\nLet x be 2 to the power of 200.\nLet y be x plus 1.\nShow y.\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
match compile_reloc(src) {
Ok(reloc) => {
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked `Let`-binding output disagrees with the VM for:\n{src}");
}
Err(e) => eprintln!("NOTE: linked emitter declined `{src}` (Move-chain gap): {e}"),
}
}
}
#[test]
fn linked_closures_match_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_closures_match_the_vm: wasm link toolchain unavailable");
return;
}
let dir = std::env::temp_dir().join("logos_wasm_link_closure");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_closures_match_the_vm: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
let compile_reloc = |source: &str| -> Result<Vec<u8>, String> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
};
for src in [
"## Main\nLet f be (x: Int) -> x to the power of 100.\nShow f(2).\n",
"## Main\nLet g be (x: Int) -> x to the power of 90.\nLet f be g.\nShow f(2).\n",
"## Main\nLet g be (x: Int) -> x to the power of 90.\nLet h be (x: Int) -> x to the power of 60.\nLet f be g.\nLet f be h.\nShow f(2).\n",
"## Main\nLet base be 3.\nLet g be (e: Int) -> base to the power of e.\nShow g(50).\n",
] {
let vm = crate::compile::vm_outcome(src);
assert!(vm.error.is_none(), "the VM itself errored on `{src}`: {:?}", vm.error);
let reloc = compile_reloc(src).unwrap_or_else(|e| panic!("assemble `{src}`: {e}"));
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{src}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), vm.output.trim(), "linked closure output disagrees with the VM for:\n{src}");
}
}
#[test]
fn linked_program_computes_overflowing_power_as_bigint_matching_the_vm() {
if !toolchain_available() {
eprintln!("SKIP linked_program_computes_overflowing_power_as_bigint_matching_the_vm: wasm link toolchain unavailable");
return;
}
let compile_reloc = |source: &str| -> Vec<u8> {
crate::ui_bridge::with_parsed_program(source, |parsed, interner| -> Result<Vec<u8>, String> {
let (stmts, types, policies) = parsed?;
let oracle = crate::optimize::oracle_analyze_with(stmts, interner);
let program = crate::vm::Compiler::compile_with_oracle(stmts, interner, Some(types), Some(oracle))
.map_err(|e| format!("compile: {e}"))?;
let module = super::super::module::assemble_program_linked(&program, &policies, interner)
.map_err(|e| format!("assemble_linked: {e}"))?;
super::super::reloc::module_to_relocatable(&module).map_err(|e| format!("reloc: {e}"))
})
.unwrap_or_else(|e| panic!("compile `{source}` to relocatable: {e}"))
};
let dir = std::env::temp_dir().join("logos_wasm_link_prog_bigint");
let (runtime, mut rlibs) = match build_bigint_runtime(&dir) {
Ok(v) => v,
Err(_) => {
eprintln!("SKIP linked_program_...: base wasm32 build unavailable");
return;
}
};
rlibs.extend(wasm_all_sysroot_rlibs().expect("sysroot rlibs"));
for &(base, exp) in &[(2i64, 200u32), (10, 50), (7, 90), (3, 5), (2, 63), (2, 64)] {
let source = format!("## Main\nShow {base} to the power of {exp}.\n");
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let expected = logicaffeine_base::BigInt::from_i64(base).pow(exp).to_string();
assert_eq!(got.trim(), expected, "linked wasm must print the exact {base}^{exp} the base BigInt computes");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm output must equal the VM for {base}^{exp} (the WASM==VM lock, through the BigInt linker)"
);
}
for &(a, ae, b, be) in &[(2i64, 100u32, 3i64, 50u32), (10, 40, 7, 33)] {
let source = format!("## Main\nShow ({a} to the power of {ae}) times ({b} to the power of {be}).\n");
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let av = logicaffeine_base::BigInt::from_i64(a).pow(ae);
let bv = logicaffeine_base::BigInt::from_i64(b).pow(be);
let expected = av.mul(&bv).to_string();
assert_eq!(got.trim(), expected, "linked wasm must print the exact ({a}^{ae})*({b}^{be})");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm BigInt product must equal the VM for ({a}^{ae})*({b}^{be})"
);
}
for &(a, ae, k) in &[(2i64, 100u32, 3i64), (10, 45, 999)] {
let source = format!("## Main\nShow ({a} to the power of {ae}) times {k}.\n");
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let expected = logicaffeine_base::BigInt::from_i64(a).pow(ae).mul(&logicaffeine_base::BigInt::from_i64(k)).to_string();
assert_eq!(got.trim(), expected, "linked wasm must print the exact ({a}^{ae})*{k} (mixed BigInt*Int)");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm mixed BigInt*Int must equal the VM for ({a}^{ae})*{k}"
);
}
for &(a, ae, add, b, be) in
&[(2i64, 100u32, true, 3i64, 50u32), (3, 80, false, 2, 80), (2, 50, false, 3, 50)]
{
let word = if add { "plus" } else { "minus" };
let source = format!("## Main\nShow ({a} to the power of {ae}) {word} ({b} to the power of {be}).\n");
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let av = logicaffeine_base::BigInt::from_i64(a).pow(ae);
let bv = logicaffeine_base::BigInt::from_i64(b).pow(be);
let expected = if add { av.add(&bv) } else { av.sub(&bv) }.to_string();
assert_eq!(got.trim(), expected, "linked wasm must print the exact ({a}^{ae}) {word} ({b}^{be})");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm add/sub must equal the VM for ({a}^{ae}) {word} ({b}^{be})"
);
}
for &(a, ae, add, k) in &[(2i64, 100u32, true, 7i64), (2, 100, false, 7)] {
let word = if add { "plus" } else { "minus" };
let source = format!("## Main\nShow ({a} to the power of {ae}) {word} {k}.\n");
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let av = logicaffeine_base::BigInt::from_i64(a).pow(ae);
let kv = logicaffeine_base::BigInt::from_i64(k);
let expected = if add { av.add(&kv) } else { av.sub(&kv) }.to_string();
assert_eq!(got.trim(), expected, "linked wasm mixed ({a}^{ae}) {word} {k}");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm mixed add/sub must equal the VM for ({a}^{ae}) {word} {k}"
);
}
for &(a, ae, isdiv, k) in &[(2i64, 200u32, true, 7i64), (2, 200, false, 7), (10, 50, true, 3)] {
let source = if isdiv {
format!("## Main\nShow ({a} to the power of {ae}) divided by {k}.\n")
} else {
format!("## Main\nShow the remainder of ({a} to the power of {ae}) and {k}.\n")
};
let reloc = compile_reloc(&source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
let (q, r) = logicaffeine_base::BigInt::from_i64(a).pow(ae).div_rem(&logicaffeine_base::BigInt::from_i64(k)).unwrap();
let expected = if isdiv { q } else { r }.to_string();
let what = if isdiv { "quotient" } else { "remainder" };
assert_eq!(got.trim(), expected, "linked wasm must print the exact {what} of ({a}^{ae}) and {k}");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(&source).output.trim(),
"linked wasm div/mod must equal the VM for the {what} of ({a}^{ae}) and {k}"
);
}
let overflow_cases: [(&str, logicaffeine_base::BigInt); 3] = [
(
"## Main\nShow 99999999999 times 99999999999.\n",
logicaffeine_base::BigInt::from_i64(99999999999).mul(&logicaffeine_base::BigInt::from_i64(99999999999)),
),
(
"## Main\nShow 9223372036854775807 plus 1.\n",
logicaffeine_base::BigInt::from_i64(9223372036854775807).add(&logicaffeine_base::BigInt::from_i64(1)),
),
(
"## Main\nShow (99999999999 times 99999999999) plus 1.\n",
logicaffeine_base::BigInt::from_i64(99999999999)
.mul(&logicaffeine_base::BigInt::from_i64(99999999999))
.add(&logicaffeine_base::BigInt::from_i64(1)),
),
];
for (source, expected_big) in &overflow_cases {
let reloc = compile_reloc(source);
let linked = link_objects_with_rlibs(&[&reloc, &runtime], &rlibs, false, &dir)
.unwrap_or_else(|e| panic!("link `{source}`: {e}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), expected_big.to_string(), "linked wasm must promote the overflowing `{source}` to the exact BigInt");
assert_eq!(
got.trim(),
crate::compile::vm_outcome(source).output.trim(),
"linked wasm overflow promotion must equal the VM for `{source}`"
);
}
}
#[test]
fn public_compile_to_wasm_linked_entry_runs_a_bigint_program() {
if !toolchain_available() {
eprintln!("SKIP public_compile_to_wasm_linked_entry_runs_a_bigint_program: wasm link toolchain unavailable");
return;
}
if build_bigint_runtime(&std::env::temp_dir().join("logos_wasm_linked_bigint")).is_err() {
eprintln!("SKIP public_compile_to_wasm_linked_entry_...: base wasm32 build unavailable");
return;
}
for (source, expected) in [
(
"## Main\nShow (2 to the power of 128) times 3.\n",
logicaffeine_base::BigInt::from_i64(2).pow(128).mul(&logicaffeine_base::BigInt::from_i64(3)).to_string(),
),
(
"## Main\nShow 99999999999 times 99999999999.\n",
logicaffeine_base::BigInt::from_i64(99999999999).mul(&logicaffeine_base::BigInt::from_i64(99999999999)).to_string(),
),
] {
let linked = crate::compile::compile_to_wasm_linked(source)
.unwrap_or_else(|e| panic!("compile_to_wasm_linked(`{source}`) failed: {e:?}"));
let got = run_linked_capturing_text(&linked);
assert_eq!(got.trim(), expected, "public linked entry must print the exact BigInt for `{source}`");
assert_eq!(got.trim(), crate::compile::vm_outcome(source).output.trim(), "public linked entry == VM for `{source}`");
}
}
}