plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-runtime/src/main.rs
a271f34 1
// Functions are named camelCase across this project (matching plum-cli, which
a271f34 2
// mirrors this file's host-import logic), not Rust's idiomatic snake_case.
a271f34 3
#![allow(non_snake_case)]
a271f34 4
a271f34 5
// Embeds a Plum-compiled wasm module (path supplied via the PLUM_WASM_PATH
a271f34 6
// env var at build time by `plum build`) and runs its `main` export under
a271f34 7
// wasmtime, so the result is a single native executable with no runtime
a271f34 8
// dependency on a `wasmtime` install or a separate .wasm file.
a271f34 9
const WASM_BYTES: &[u8] = include_bytes!(env!("PLUM_WASM_PATH"));
a271f34 10
a271f34 11
/// Builds the `Extern`s that satisfy a compiled Plum module's host imports (today,
a271f34 12
/// only `plum::printLn`) in declaration order, matching each one's exact `FuncType`
a271f34 13
/// (its `Str` param is a concrete wasm-gc array type, so the host function must be
a271f34 14
/// built from the module's own reported type rather than a generic `Rooted<ArrayRef>`
a271f34 15
/// wrapper, which would carry the wrong, unrelated top-array type and fail to
a271f34 16
/// instantiate).
a271f34 17
fn hostImports(store: &mut wasmtime::Store<()>, module: &wasmtime::Module) -> Vec<wasmtime::Extern> {
a271f34 18
    module.imports().map(|imp| {
a271f34 19
        match (imp.module(), imp.name()) {
a271f34 20
            ("plum", "printLn") => {
a271f34 21
                let func_ty = imp.ty().func().cloned().expect("plum::printLn import should be a function");
a271f34 22
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, _results| {
a271f34 23
                    let wasmtime::Val::AnyRef(Some(s)) = &params[0] else {
a271f34 24
                        return Err(wasmtime::Error::msg("printLn expects a Str argument"));
a271f34 25
                    };
73e4ff1 26
                    let arr = unwrapStrToArray(&mut caller, s)?;
a271f34 27
                    let len = arr.len(&caller)?;
a271f34 28
                    let mut buf = vec![0u8; len as usize];
a271f34 29
                    arr.copy_to_i8_slice(&mut caller, &mut buf)?;
a271f34 30
                    println!("{}", String::from_utf8_lossy(&buf));
a271f34 31
                    Ok(())
a271f34 32
                }))
a271f34 33
            }
a271f34 34
            ("plum", "rawRandomInt") => {
a271f34 35
                let func_ty = imp.ty().func().cloned().expect("plum::rawRandomInt import should be a function");
a271f34 36
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |_caller, _params, results| {
a271f34 37
                    results[0] = wasmtime::Val::I64(randomI64());
a271f34 38
                    Ok(())
a271f34 39
                }))
a271f34 40
            }
a271f34 41
            ("plum", "rawNowMillis") => {
a271f34 42
                let func_ty = imp.ty().func().cloned().expect("plum::rawNowMillis import should be a function");
a271f34 43
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |_caller, _params, results| {
a271f34 44
                    results[0] = wasmtime::Val::I64(nowMillis());
a271f34 45
                    Ok(())
a271f34 46
                }))
a271f34 47
            }
2ec05c8 48
            ("plum", "rawReadFile") => {
2ec05c8 49
                let func_ty = imp.ty().func().cloned().expect("plum::rawReadFile import should be a function");
2ec05c8 50
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty.clone(), move |mut caller, params, results| {
2ec05c8 51
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 52
                    let bytes = std::fs::read(&path).unwrap_or_default();
2ec05c8 53
                    results[0] = wasmtime::Val::AnyRef(Some(makeStrResult(&mut caller, &func_ty, &bytes)?));
2ec05c8 54
                    Ok(())
2ec05c8 55
                }))
2ec05c8 56
            }
2ec05c8 57
            ("plum", "rawWriteFile") => {
2ec05c8 58
                let func_ty = imp.ty().func().cloned().expect("plum::rawWriteFile import should be a function");
2ec05c8 59
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 60
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 61
                    let data = readStrArg(&mut caller, &params[1])?;
2ec05c8 62
                    results[0] = wasmtime::Val::I64(std::fs::write(&path, &data).is_ok() as i64);
2ec05c8 63
                    Ok(())
2ec05c8 64
                }))
2ec05c8 65
            }
2ec05c8 66
            ("plum", "rawExists") => {
2ec05c8 67
                let func_ty = imp.ty().func().cloned().expect("plum::rawExists import should be a function");
2ec05c8 68
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 69
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 70
                    // See the identical `rawExists` in plum-cli for why this uses
2ec05c8 71
                    // `symlink_metadata` rather than `Path::exists`.
2ec05c8 72
                    results[0] = wasmtime::Val::I64(std::fs::symlink_metadata(&path).is_ok() as i64);
2ec05c8 73
                    Ok(())
2ec05c8 74
                }))
2ec05c8 75
            }
2ec05c8 76
            ("plum", "rawMkdir") => {
2ec05c8 77
                let func_ty = imp.ty().func().cloned().expect("plum::rawMkdir import should be a function");
2ec05c8 78
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 79
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 80
                    results[0] = wasmtime::Val::I64(std::fs::create_dir_all(&path).is_ok() as i64);
2ec05c8 81
                    Ok(())
2ec05c8 82
                }))
2ec05c8 83
            }
2ec05c8 84
            ("plum", "rawRemove") => {
2ec05c8 85
                let func_ty = imp.ty().func().cloned().expect("plum::rawRemove import should be a function");
2ec05c8 86
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 87
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 88
                    results[0] = wasmtime::Val::I64(std::fs::remove_file(&path).is_ok() as i64);
2ec05c8 89
                    Ok(())
2ec05c8 90
                }))
2ec05c8 91
            }
2ec05c8 92
            ("plum", "rawRemoveDir") => {
2ec05c8 93
                let func_ty = imp.ty().func().cloned().expect("plum::rawRemoveDir import should be a function");
2ec05c8 94
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 95
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 96
                    results[0] = wasmtime::Val::I64(std::fs::remove_dir(&path).is_ok() as i64);
2ec05c8 97
                    Ok(())
2ec05c8 98
                }))
2ec05c8 99
            }
2ec05c8 100
            ("plum", "rawRename") => {
2ec05c8 101
                let func_ty = imp.ty().func().cloned().expect("plum::rawRename import should be a function");
2ec05c8 102
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 103
                    let old_path = readStrArg(&mut caller, &params[0])?;
2ec05c8 104
                    let new_path = readStrArg(&mut caller, &params[1])?;
2ec05c8 105
                    results[0] = wasmtime::Val::I64(std::fs::rename(&old_path, &new_path).is_ok() as i64);
2ec05c8 106
                    Ok(())
2ec05c8 107
                }))
2ec05c8 108
            }
2ec05c8 109
            ("plum", "rawTruncate") => {
2ec05c8 110
                let func_ty = imp.ty().func().cloned().expect("plum::rawTruncate import should be a function");
2ec05c8 111
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 112
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 113
                    let wasmtime::Val::I64(len) = params[1] else {
2ec05c8 114
                        return Err(wasmtime::Error::msg("rawTruncate expects an Int length argument"));
2ec05c8 115
                    };
2ec05c8 116
                    let ok = std::fs::OpenOptions::new().write(true).open(&path)
2ec05c8 117
                        .and_then(|f| f.set_len(len.max(0) as u64)).is_ok();
2ec05c8 118
                    results[0] = wasmtime::Val::I64(ok as i64);
2ec05c8 119
                    Ok(())
2ec05c8 120
                }))
2ec05c8 121
            }
2ec05c8 122
            ("plum", "rawLink") => {
2ec05c8 123
                let func_ty = imp.ty().func().cloned().expect("plum::rawLink import should be a function");
2ec05c8 124
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 125
                    let existing_path = readStrArg(&mut caller, &params[0])?;
2ec05c8 126
                    let new_path = readStrArg(&mut caller, &params[1])?;
2ec05c8 127
                    results[0] = wasmtime::Val::I64(std::fs::hard_link(&existing_path, &new_path).is_ok() as i64);
2ec05c8 128
                    Ok(())
2ec05c8 129
                }))
2ec05c8 130
            }
2ec05c8 131
            ("plum", "rawSymlink") => {
2ec05c8 132
                let func_ty = imp.ty().func().cloned().expect("plum::rawSymlink import should be a function");
2ec05c8 133
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 134
                    let target = readStrArg(&mut caller, &params[0])?;
2ec05c8 135
                    let path = readStrArg(&mut caller, &params[1])?;
2ec05c8 136
                    #[cfg(unix)]
2ec05c8 137
                    let ok = std::os::unix::fs::symlink(&target, &path).is_ok();
2ec05c8 138
                    #[cfg(not(unix))]
2ec05c8 139
                    let ok = false;
2ec05c8 140
                    results[0] = wasmtime::Val::I64(ok as i64);
2ec05c8 141
                    Ok(())
2ec05c8 142
                }))
2ec05c8 143
            }
2ec05c8 144
            ("plum", "rawReadlink") => {
2ec05c8 145
                let func_ty = imp.ty().func().cloned().expect("plum::rawReadlink import should be a function");
2ec05c8 146
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty.clone(), move |mut caller, params, results| {
2ec05c8 147
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 148
                    let target = std::fs::read_link(&path).ok()
2ec05c8 149
                        .map(|p| p.to_string_lossy().into_owned()).unwrap_or_default();
2ec05c8 150
                    results[0] = wasmtime::Val::AnyRef(Some(makeStrResult(&mut caller, &func_ty, target.as_bytes())?));
2ec05c8 151
                    Ok(())
2ec05c8 152
                }))
2ec05c8 153
            }
2ec05c8 154
            ("plum", "rawRealpath") => {
2ec05c8 155
                let func_ty = imp.ty().func().cloned().expect("plum::rawRealpath import should be a function");
2ec05c8 156
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty.clone(), move |mut caller, params, results| {
2ec05c8 157
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 158
                    let resolved = std::fs::canonicalize(&path).ok()
2ec05c8 159
                        .map(|p| p.to_string_lossy().into_owned()).unwrap_or_default();
2ec05c8 160
                    results[0] = wasmtime::Val::AnyRef(Some(makeStrResult(&mut caller, &func_ty, resolved.as_bytes())?));
2ec05c8 161
                    Ok(())
2ec05c8 162
                }))
2ec05c8 163
            }
2ec05c8 164
            ("plum", "rawChmod") => {
2ec05c8 165
                let func_ty = imp.ty().func().cloned().expect("plum::rawChmod import should be a function");
2ec05c8 166
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty, |mut caller, params, results| {
2ec05c8 167
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 168
                    let wasmtime::Val::I64(mode) = params[1] else {
2ec05c8 169
                        return Err(wasmtime::Error::msg("rawChmod expects an Int mode argument"));
2ec05c8 170
                    };
2ec05c8 171
                    #[cfg(unix)]
2ec05c8 172
                    let ok = {
2ec05c8 173
                        use std::os::unix::fs::PermissionsExt;
2ec05c8 174
                        std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode as u32)).is_ok()
2ec05c8 175
                    };
2ec05c8 176
                    #[cfg(not(unix))]
2ec05c8 177
                    let ok = false;
2ec05c8 178
                    results[0] = wasmtime::Val::I64(ok as i64);
2ec05c8 179
                    Ok(())
2ec05c8 180
                }))
2ec05c8 181
            }
2ec05c8 182
            ("plum", "rawReadDir") => {
2ec05c8 183
                let func_ty = imp.ty().func().cloned().expect("plum::rawReadDir import should be a function");
2ec05c8 184
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty.clone(), move |mut caller, params, results| {
2ec05c8 185
                    let path = readStrArg(&mut caller, &params[0])?;
2ec05c8 186
                    let joined = std::fs::read_dir(&path).ok().map(|entries| {
2ec05c8 187
                        entries.filter_map(|e| e.ok())
2ec05c8 188
                            .map(|e| e.file_name().to_string_lossy().into_owned())
2ec05c8 189
                            .collect::<Vec<_>>()
2ec05c8 190
                            .join("\n")
2ec05c8 191
                    }).unwrap_or_default();
2ec05c8 192
                    results[0] = wasmtime::Val::AnyRef(Some(makeStrResult(&mut caller, &func_ty, joined.as_bytes())?));
2ec05c8 193
                    Ok(())
2ec05c8 194
                }))
2ec05c8 195
            }
bf629a2 196
            ("plum", "rawHttpRequest") => {
bf629a2 197
                let func_ty = imp.ty().func().cloned().expect("plum::rawHttpRequest import should be a function");
bf629a2 198
                wasmtime::Extern::Func(wasmtime::Func::new(&mut *store, func_ty.clone(), move |mut caller, params, results| {
bf629a2 199
                    let method = readStrArg(&mut caller, &params[0])?;
bf629a2 200
                    let url = readStrArg(&mut caller, &params[1])?;
bf629a2 201
                    let headers = readStrArg(&mut caller, &params[2])?;
bf629a2 202
                    let body = readStrArg(&mut caller, &params[3])?;
bf629a2 203
                    let raw = performHttpRequest(&method, &url, &headers, &body);
bf629a2 204
                    results[0] = wasmtime::Val::AnyRef(Some(makeStrResult(&mut caller, &func_ty, raw.as_bytes())?));
bf629a2 205
                    Ok(())
bf629a2 206
                }))
bf629a2 207
            }
a271f34 208
            (m, n) => panic!("unsupported host import {m}::{n}"),
a271f34 209
        }
a271f34 210
    }).collect()
a271f34 211
}
a271f34 212
bf629a2 213
/// See the identical helper in `plum-cli/src/main.rs` — this crate is built
bf629a2 214
/// fresh per `plum build` invocation, not linked against `plum-cli`, so the
bf629a2 215
/// logic is duplicated rather than shared.
bf629a2 216
fn performHttpRequest(method: &str, url: &str, headers: &str, body: &str) -> String {
bf629a2 217
    let agent = ureq::Agent::new();
bf629a2 218
    let mut req = agent.request(method, url);
bf629a2 219
    for line in headers.split('\n') {
bf629a2 220
        if let Some((k, v)) = line.split_once(": ") {
bf629a2 221
            req = req.set(k, v);
bf629a2 222
        }
bf629a2 223
    }
bf629a2 224
    let result = if body.is_empty() { req.call() } else { req.send_string(body) };
bf629a2 225
    match result {
bf629a2 226
        Ok(resp) | Err(ureq::Error::Status(_, resp)) => {
bf629a2 227
            let status = resp.status();
bf629a2 228
            let mut header_lines = String::new();
bf629a2 229
            for name in resp.headers_names() {
bf629a2 230
                if let Some(v) = resp.header(&name) {
bf629a2 231
                    if !header_lines.is_empty() {
bf629a2 232
                        header_lines.push('\n');
bf629a2 233
                    }
bf629a2 234
                    header_lines.push_str(&format!("{name}: {v}"));
bf629a2 235
                }
bf629a2 236
            }
bf629a2 237
            let body_text = resp.into_string().unwrap_or_default();
bf629a2 238
            format!("1\x01{status}\x01{header_lines}\x00{body_text}")
bf629a2 239
        }
bf629a2 240
        Err(e) => format!("0\x01\x01{e}\x00"),
bf629a2 241
    }
bf629a2 242
}
bf629a2 243
a271f34 244
/// See the identical helper in `plum-cli/src/main.rs` — this crate is built
a271f34 245
/// fresh per `plum build` invocation, not linked against `plum-cli`, so the
a271f34 246
/// logic is duplicated rather than shared.
a271f34 247
fn randomI64() -> i64 {
a271f34 248
    let nanos = std::time::SystemTime::now()
a271f34 249
        .duration_since(std::time::UNIX_EPOCH)
a271f34 250
        .map(|d| d.as_nanos() as u64)
a271f34 251
        .unwrap_or(0);
a271f34 252
    let mut x = nanos ^ 0x9E3779B97F4A7C15;
a271f34 253
    x ^= x << 13;
a271f34 254
    x ^= x >> 7;
a271f34 255
    x ^= x << 17;
a271f34 256
    x as i64
a271f34 257
}
a271f34 258
2ec05c8 259
/// See the identical helpers in `plum-cli/src/main.rs` — this crate is built
2ec05c8 260
/// fresh per `plum build` invocation, not linked against `plum-cli`, so the
2ec05c8 261
/// logic is duplicated rather than shared.
73e4ff1 262
///
73e4ff1 263
/// `Str` is a real two-level wasm-gc struct — `Str{data: Buffer{data: []Byte,
73e4ff1 264
/// len: Int}}` (see `libs/std/str.plum`'s header comment) — not a raw
73e4ff1 265
/// `array<i8>` directly. Unwraps a `Str` `AnyRef` down to that raw array.
73e4ff1 266
fn unwrapStrToArray(
73e4ff1 267
    mut store: impl wasmtime::AsContextMut,
73e4ff1 268
    s: &wasmtime::Rooted<wasmtime::AnyRef>,
73e4ff1 269
) -> wasmtime::Result<wasmtime::Rooted<wasmtime::ArrayRef>> {
73e4ff1 270
    let str_struct = s.unwrap_struct(&store)?;
73e4ff1 271
    let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut store, 0)? else {
73e4ff1 272
        return Err(wasmtime::Error::msg("Str.data is not a Buffer reference"));
73e4ff1 273
    };
73e4ff1 274
    let buffer_struct = buffer_ref.unwrap_struct(&store)?;
73e4ff1 275
    let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut store, 0)? else {
73e4ff1 276
        return Err(wasmtime::Error::msg("Buffer.data is not a []Byte reference"));
73e4ff1 277
    };
73e4ff1 278
    bytes_ref.unwrap_array(&store)
73e4ff1 279
}
73e4ff1 280
2ec05c8 281
fn readStrArg(caller: &mut wasmtime::Caller<'_, ()>, val: &wasmtime::Val) -> wasmtime::Result<String> {
2ec05c8 282
    let wasmtime::Val::AnyRef(Some(s)) = val else {
2ec05c8 283
        return Err(wasmtime::Error::msg("expected a Str argument"));
2ec05c8 284
    };
73e4ff1 285
    let arr = unwrapStrToArray(&mut *caller, s)?;
2ec05c8 286
    let len = arr.len(&caller)?;
2ec05c8 287
    let mut buf = vec![0u8; len as usize];
2ec05c8 288
    arr.copy_to_i8_slice(caller, &mut buf)?;
2ec05c8 289
    Ok(String::from_utf8_lossy(&buf).into_owned())
2ec05c8 290
}
2ec05c8 291
73e4ff1 292
/// Builds a new `Str` value from raw bytes — allocates the raw `array<i8>`
73e4ff1 293
/// (needs the import's own reported `FuncType` to find the concrete nested
73e4ff1 294
/// array type — a generic `ArrayRef` type would be the wrong, unrelated top
73e4ff1 295
/// array type), then wraps it in a fresh `Buffer` struct and a `Str` struct
73e4ff1 296
/// around that, mirroring `str.plum`'s real shape.
2ec05c8 297
fn makeStrResult(
2ec05c8 298
    mut caller: impl wasmtime::AsContextMut,
2ec05c8 299
    func_ty: &wasmtime::FuncType,
2ec05c8 300
    bytes: &[u8],
2ec05c8 301
) -> wasmtime::Result<wasmtime::Rooted<wasmtime::AnyRef>> {
2ec05c8 302
    let result_ty = func_ty.results().next()
2ec05c8 303
        .ok_or_else(|| wasmtime::Error::msg("expected a Str-returning function"))?;
2ec05c8 304
    let wasmtime::ValType::Ref(ref_ty) = result_ty else {
2ec05c8 305
        return Err(wasmtime::Error::msg("expected a Str (ref) return type"));
2ec05c8 306
    };
73e4ff1 307
    let str_ty = ref_ty.heap_type().as_concrete_struct()
73e4ff1 308
        .ok_or_else(|| wasmtime::Error::msg("expected a concrete struct return type"))?
2ec05c8 309
        .clone();
73e4ff1 310
    let buffer_ty = str_ty.field(0)
73e4ff1 311
        .and_then(|f| match f.element_type() {
73e4ff1 312
            wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => rt.heap_type().as_concrete_struct().cloned(),
73e4ff1 313
            _ => None,
73e4ff1 314
        })
73e4ff1 315
        .ok_or_else(|| wasmtime::Error::msg("expected Str.data to be a concrete Buffer struct type"))?;
73e4ff1 316
    let array_ty = buffer_ty.field(0)
73e4ff1 317
        .and_then(|f| match f.element_type() {
73e4ff1 318
            wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => rt.heap_type().as_concrete_array().cloned(),
73e4ff1 319
            _ => None,
73e4ff1 320
        })
73e4ff1 321
        .ok_or_else(|| wasmtime::Error::msg("expected Buffer.data to be a concrete []Byte array type"))?;
73e4ff1 322
73e4ff1 323
    let array_pre = wasmtime::ArrayRefPre::new(&mut caller, array_ty);
73e4ff1 324
    let arr = wasmtime::ArrayRef::new_from_i8_slice(&mut caller, &array_pre, bytes)?;
73e4ff1 325
73e4ff1 326
    let buffer_pre = wasmtime::StructRefPre::new(&mut caller, buffer_ty);
73e4ff1 327
    let buffer = wasmtime::StructRef::new(
73e4ff1 328
        &mut caller,
73e4ff1 329
        &buffer_pre,
73e4ff1 330
        &[wasmtime::Val::AnyRef(Some(arr.to_anyref())), wasmtime::Val::I64(bytes.len() as i64)],
73e4ff1 331
    )?;
73e4ff1 332
73e4ff1 333
    let str_pre = wasmtime::StructRefPre::new(&mut caller, str_ty);
73e4ff1 334
    let str_val = wasmtime::StructRef::new(&mut caller, &str_pre, &[wasmtime::Val::AnyRef(Some(buffer.to_anyref()))])?;
73e4ff1 335
    Ok(str_val.to_anyref())
2ec05c8 336
}
2ec05c8 337
a271f34 338
fn nowMillis() -> i64 {
a271f34 339
    std::time::SystemTime::now()
a271f34 340
        .duration_since(std::time::UNIX_EPOCH)
a271f34 341
        .map(|d| d.as_millis() as i64)
a271f34 342
        .unwrap_or(0)
a271f34 343
}
a271f34 344
a271f34 345
fn main() {
a271f34 346
    // GC + typed function references are required to load modules containing
a271f34 347
    // Plum's structs/enums/strings/closures (see docs/superpowers/plans/2026-07-25-wasm-gc-migration.md).
a271f34 348
    let mut config = wasmtime::Config::new();
a271f34 349
    config.wasm_gc(true);
a271f34 350
    config.wasm_function_references(true);
a271f34 351
    let engine = wasmtime::Engine::new(&config).expect("engine with GC config should construct");
a271f34 352
a271f34 353
    let module = wasmtime::Module::new(&engine, WASM_BYTES).expect("embedded wasm should be valid");
a271f34 354
    let mut store = wasmtime::Store::new(&engine, ());
a271f34 355
    let imports = hostImports(&mut store, &module);
a271f34 356
    let instance = wasmtime::Instance::new(&mut store, &module, &imports).expect("module should instantiate");
a271f34 357
a271f34 358
    // `main` returns `i64` unless it's declared `Unit` (no return value), in
a271f34 359
    // which case codegen gives it an empty wasm result type instead.
a271f34 360
    if let Ok(main) = instance.get_typed_func::<(), i64>(&mut store, "main") {
a271f34 361
        match main.call(&mut store, ()) {
a271f34 362
            Ok(result) => println!("{result}"),
a271f34 363
            Err(trap) => {
a271f34 364
                eprintln!("error: {trap}");
a271f34 365
                std::process::exit(1);
a271f34 366
            }
a271f34 367
        }
a271f34 368
        return;
a271f34 369
    }
a271f34 370
a271f34 371
    let main = instance
a271f34 372
        .get_typed_func::<(), ()>(&mut store, "main")
a271f34 373
        .expect("module has no `main` export with signature () -> i64 or () -> ()");
a271f34 374
    if let Err(trap) = main.call(&mut store, ()) {
a271f34 375
        eprintln!("error: {trap}");
a271f34 376
        std::process::exit(1);
a271f34 377
    }
a271f34 378
}