plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
3f7586e
— Peter John
2026-10-26T11:44:55+05:30
feat(codegen)!: one compiled copy per generic instantiation, unboxed
- plum-checker/src/lib.rs +77 -9
- plum-cli/src/main.rs +5 -2
- plum-wasm-codegen/src/lib.rs +998 -746
- plum-wasm-codegen/tests/codegen_tests.rs +207 -15
plum-checker/src/lib.rs
CHANGED
|
@@ -23,6 +23,13 @@ thread_local! {
|
|
|
23
23
|
static INST_MODE: Cell<bool> = const { Cell::new(false) };
|
|
24
24
|
/// Line of the declaration currently being checked (the "called at" line of recorded calls).
|
|
25
25
|
static CUR_LINE: Cell<usize> = const { Cell::new(0) };
|
|
26
|
+
/// Address of the expression `inferExprWith` is currently inferring (the innermost one).
|
|
27
|
+
static CUR_EXPR: Cell<usize> = const { Cell::new(0) };
|
|
28
|
+
/// While checking the last statement of a value-producing tail (an fn body's
|
|
29
|
+
/// final `if`/`match`/expression): the type that tail value must have, so its
|
|
30
|
+
/// generic arguments are pinned like a directly-returned expression's.
|
|
31
|
+
static TAIL: RefCell<Option<PlumType>> = const { RefCell::new(None) };
|
|
32
|
+
static MATCH_TAIL: RefCell<Option<PlumType>> = const { RefCell::new(None) };
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
/// A call to a generic fn/method seen in some body: callee identity plus the
|
|
@@ -33,9 +40,23 @@ struct CallSite {
|
|
|
33
40
|
name: String,
|
|
34
41
|
args: BTreeMap<String, PlumType>,
|
|
35
42
|
line: usize,
|
|
43
|
+
/// Address of the call expression (or, for an implicit call, of the expression it is made on).
|
|
44
|
+
expr: usize,
|
|
45
|
+
/// A call codegen makes on its own (string interpolation's `toStr`), not written in source.
|
|
46
|
+
implicit: bool,
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
fn recordCall(recv: Option<&str>, name: &str, args: BTreeMap<String, PlumType>) {
|
|
50
|
+
recordCallAt(CUR_EXPR.with(|e| e.get()), false, recv, name, args);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn recordCallAt(
|
|
54
|
+
expr: usize,
|
|
55
|
+
implicit: bool,
|
|
56
|
+
recv: Option<&str>,
|
|
57
|
+
name: &str,
|
|
58
|
+
args: BTreeMap<String, PlumType>,
|
|
59
|
+
) {
|
|
39
60
|
if args.is_empty() {
|
|
40
61
|
return;
|
|
41
62
|
}
|
|
@@ -46,6 +67,8 @@ fn recordCall(recv: Option<&str>, name: &str, args: BTreeMap<String, PlumType>)
|
|
|
46
67
|
name: name.to_string(),
|
|
47
68
|
args,
|
|
48
69
|
line: CUR_LINE.with(|l| l.get()),
|
|
70
|
+
expr,
|
|
71
|
+
implicit,
|
|
49
72
|
});
|
|
50
73
|
}
|
|
51
74
|
});
|
|
@@ -69,6 +92,10 @@ pub struct TypedInfo {
|
|
|
69
92
|
pub instantiations: Vec<Instantiation>,
|
|
70
93
|
pub inst_expr_types: HashMap<(usize, usize), PlumType>,
|
|
71
94
|
pub inst_index: HashMap<(String, Vec<PlumType>), usize>,
|
|
95
|
+
/// `(calling instantiation (None = a root fn), call expression address, implicit)` ->
|
|
96
|
+
/// the instantiation it calls. `implicit` marks calls codegen makes itself (the
|
|
97
|
+
/// `toStr` of a `"{x}"` interpolation, keyed by the interpolated expression).
|
|
98
|
+
pub call_inst: HashMap<(Option<usize>, usize, bool), usize>,
|
|
72
99
|
}
|
|
73
100
|
|
|
74
101
|
/// One reached instantiation of a generic fn or method of a generic type.
|
|
@@ -460,7 +487,15 @@ fn parseTagUnion(s: &str) -> Option<PlumType> {
|
|
|
460
487
|
Some(p) => {
|
|
461
488
|
let name = seg[..p].trim().to_string();
|
|
462
489
|
let payload = seg[p + 1..].strip_suffix(')')?;
|
|
490
|
+
let payload = payload.trim();
|
|
491
|
+
let pty = if isGenericName(payload) {
|
|
492
|
+
TSUBST
|
|
493
|
+
.with(|s| s.borrow().as_ref().and_then(|m| m.get(payload).cloned()))
|
|
494
|
+
.unwrap_or_else(|| PlumType::TParam(payload.to_string()))
|
|
495
|
+
} else {
|
|
496
|
+
plumTypeFromName(payload)
|
|
497
|
+
};
|
|
463
|
-
tags.insert(name, Some(Box::new(
|
|
498
|
+
tags.insert(name, Some(Box::new(pty)));
|
|
464
499
|
}
|
|
465
500
|
None => {
|
|
466
501
|
tags.insert(seg.to_string(), None);
|
|
@@ -1455,7 +1490,7 @@ fn drainCalls() -> Vec<CallSite> {
|
|
|
1455
1490
|
|
|
1456
1491
|
/// Generic parameter names of `f`: its owner type's generics, then the free
|
|
1457
1492
|
/// type parameters of its own signature. Empty = not generic.
|
|
1458
|
-
fn fnGenericNames(f: &ast::Fn, ctx: &CheckCtx) -> Vec<String> {
|
|
1493
|
+
pub fn fnGenericNames(f: &ast::Fn, ctx: &CheckCtx) -> Vec<String> {
|
|
1459
1494
|
let mut names: Vec<String> = match &f.type_param {
|
|
1460
1495
|
Some(r) => enumGenericsOf(r, ctx).to_vec(),
|
|
1461
1496
|
None => Vec::new(),
|
|
@@ -1521,7 +1556,8 @@ fn registerCalls(
|
|
|
1521
1556
|
None => cs.name.clone(),
|
|
1522
1557
|
};
|
|
1523
1558
|
let key = (item.clone(), args.clone());
|
|
1524
|
-
if st.info.inst_index.
|
|
1559
|
+
if let Some(&existing) = st.info.inst_index.get(&key) {
|
|
1560
|
+
st.info.call_inst.insert((parent, cs.expr, cs.implicit), existing);
|
|
1525
1561
|
continue;
|
|
1526
1562
|
}
|
|
1527
1563
|
let id = st.info.instantiations.len();
|
|
@@ -1545,6 +1581,7 @@ fn registerCalls(
|
|
|
1545
1581
|
called_at_line: cs.line,
|
|
1546
1582
|
});
|
|
1547
1583
|
st.info.inst_index.insert(key, id);
|
|
1584
|
+
st.info.call_inst.insert((parent, cs.expr, cs.implicit), id);
|
|
1548
1585
|
st.queue.push_back(id);
|
|
1549
1586
|
}
|
|
1550
1587
|
}
|
|
@@ -1686,7 +1723,11 @@ fn checkFn(
|
|
|
1686
1723
|
Some((ast::Stmt::Expr(e, line, column), init)) => (Some((e, *line, *column)), init),
|
|
1687
1724
|
_ => (None, &block.stmts[..]),
|
|
1688
1725
|
};
|
|
1726
|
+
let n_init = init.len();
|
|
1689
|
-
for stmt in init {
|
|
1727
|
+
for (i, stmt) in init.iter().enumerate() {
|
|
1728
|
+
if tail.is_none() && i + 1 == n_init && !matches!(declared_ret, PlumType::TUnit) {
|
|
1729
|
+
TAIL.with(|c| *c.borrow_mut() = Some(declared_ret.clone()));
|
|
1730
|
+
}
|
|
1690
1731
|
errors.append(&mut checkStmt(
|
|
1691
1732
|
stmt,
|
|
1692
1733
|
&mut env,
|
|
@@ -1730,7 +1771,11 @@ fn checkBlock(
|
|
|
1730
1771
|
ctx: &CheckCtx,
|
|
1731
1772
|
) -> Vec<CheckError> {
|
|
1732
1773
|
let mut errors = Vec::new();
|
|
1774
|
+
let tail = TAIL.with(|t| t.borrow_mut().take());
|
|
1775
|
+
for (i, stmt) in block.stmts.iter().enumerate() {
|
|
1733
|
-
|
|
1776
|
+
if i + 1 == block.stmts.len() {
|
|
1777
|
+
TAIL.with(|t| *t.borrow_mut() = tail.clone());
|
|
1778
|
+
}
|
|
1734
1779
|
let mut stmt_errors = checkStmt(stmt, env, declared_ret, fn_name, fn_pos, ctx);
|
|
1735
1780
|
errors.append(&mut stmt_errors);
|
|
1736
1781
|
}
|
|
@@ -1761,6 +1806,8 @@ fn checkStmt(
|
|
|
1761
1806
|
ctx: &CheckCtx,
|
|
1762
1807
|
) -> Vec<CheckError> {
|
|
1763
1808
|
let mut errors = Vec::new();
|
|
1809
|
+
let tail = TAIL.with(|t| t.borrow_mut().take());
|
|
1810
|
+
let set_tail = |t: &Option<PlumType>| TAIL.with(|c| *c.borrow_mut() = t.clone());
|
|
1764
1811
|
match stmt {
|
|
1765
1812
|
ast::Stmt::Assign(a) => {
|
|
1766
1813
|
for (target, value) in a.targets.iter().zip(a.values.iter()) {
|
|
@@ -1881,6 +1928,7 @@ fn checkStmt(
|
|
|
1881
1928
|
format!("fn '{}': if condition: {}", fn_name, msg),
|
|
1882
1929
|
)),
|
|
1883
1930
|
}
|
|
1931
|
+
set_tail(&tail);
|
|
1884
1932
|
errors.append(&mut checkBlock(
|
|
1885
1933
|
&if_.body,
|
|
1886
1934
|
env,
|
|
@@ -1909,7 +1957,8 @@ fn checkStmt(
|
|
|
1909
1957
|
format!("fn '{}': else if condition: {}", fn_name, msg),
|
|
1910
1958
|
)),
|
|
1911
1959
|
}
|
|
1960
|
+
set_tail(&tail);
|
|
1912
|
-
|
|
1961
|
+
errors.append(&mut checkBlock(
|
|
1913
1962
|
&ei.body,
|
|
1914
1963
|
env,
|
|
1915
1964
|
declared_ret,
|
|
@@ -1919,7 +1968,8 @@ fn checkStmt(
|
|
|
1919
1968
|
));
|
|
1920
1969
|
}
|
|
1921
1970
|
if let Some(else_block) = &if_.else_ {
|
|
1971
|
+
set_tail(&tail);
|
|
1922
|
-
|
|
1972
|
+
errors.append(&mut checkBlock(
|
|
1923
1973
|
else_block,
|
|
1924
1974
|
env,
|
|
1925
1975
|
declared_ret,
|
|
@@ -1993,7 +2043,7 @@ fn checkStmt(
|
|
|
1993
2043
|
));
|
|
1994
2044
|
}
|
|
1995
2045
|
ast::Stmt::Expr(e, line, column) => {
|
|
1996
|
-
if let Err(msg) =
|
|
2046
|
+
if let Err(msg) = inferExprWith(e, env, ctx, tail.as_ref()) {
|
|
1997
2047
|
errors.push(CheckError::at(
|
|
1998
2048
|
*line,
|
|
1999
2049
|
*column,
|
|
@@ -2018,6 +2068,7 @@ fn checkStmt(
|
|
|
2018
2068
|
)),
|
|
2019
2069
|
},
|
|
2020
2070
|
ast::Stmt::Match(m) => {
|
|
2071
|
+
MATCH_TAIL.with(|c| *c.borrow_mut() = tail.clone());
|
|
2021
2072
|
errors.append(&mut checkMatch(m, env, declared_ret, fn_name, fn_pos, ctx));
|
|
2022
2073
|
}
|
|
2023
2074
|
ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {}
|
|
@@ -2212,6 +2263,7 @@ fn checkMatch(
|
|
|
2212
2263
|
fn_pos: (usize, usize),
|
|
2213
2264
|
ctx: &CheckCtx,
|
|
2214
2265
|
) -> Vec<CheckError> {
|
|
2266
|
+
let match_tail = MATCH_TAIL.with(|c| c.borrow_mut().take());
|
|
2215
2267
|
let mut errors = Vec::new();
|
|
2216
2268
|
|
|
2217
2269
|
let mut subject_types: Vec<PlumType> = Vec::new();
|
|
@@ -2297,6 +2349,7 @@ fn checkMatch(
|
|
|
2297
2349
|
)),
|
|
2298
2350
|
}
|
|
2299
2351
|
}
|
|
2352
|
+
TAIL.with(|c| *c.borrow_mut() = match_tail.clone());
|
|
2300
2353
|
errors.append(&mut checkBlock(
|
|
2301
2354
|
&case.body,
|
|
2302
2355
|
&mut case_env,
|
|
@@ -3055,7 +3108,9 @@ pub fn inferExprWith(
|
|
|
3055
3108
|
ctx: &CheckCtx,
|
|
3056
3109
|
expected: Option<&PlumType>,
|
|
3057
3110
|
) -> Result<PlumType, String> {
|
|
3111
|
+
let prev = CUR_EXPR.with(|c| c.replace(expr as *const ast::Expr as usize));
|
|
3058
3112
|
let r = inferExprInner(expr, env, ctx, expected);
|
|
3113
|
+
CUR_EXPR.with(|c| c.set(prev));
|
|
3059
3114
|
if let Ok(t) = &r {
|
|
3060
3115
|
recordType(expr, t);
|
|
3061
3116
|
}
|
|
@@ -3079,7 +3134,20 @@ fn inferExprInner(
|
|
|
3079
3134
|
// were never part of this check.
|
|
3080
3135
|
for part in &s.parts {
|
|
3081
3136
|
if let ast::StringPart::Interp(e) = part {
|
|
3082
|
-
let
|
|
3137
|
+
if let Ok(PlumType::TApp(n, targs)) = inferExprWith(e, env, ctx, None) {
|
|
3138
|
+
let gens = enumGenericsOf(&n, ctx);
|
|
3139
|
+
if gens.len() == targs.len()
|
|
3140
|
+
&& ctx.methods.contains_key(&(n.clone(), "toStr".to_string()))
|
|
3141
|
+
{
|
|
3142
|
+
recordCallAt(
|
|
3143
|
+
e as *const ast::Expr as usize,
|
|
3144
|
+
true,
|
|
3145
|
+
Some(&n),
|
|
3146
|
+
"toStr",
|
|
3147
|
+
appSubst(gens, &targs),
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3083
3151
|
}
|
|
3084
3152
|
}
|
|
3085
3153
|
Ok(PlumType::TStr)
|
plum-cli/src/main.rs
CHANGED
|
@@ -774,7 +774,7 @@ fn cmdRun(file: std::path::PathBuf, lib_path: std::path::PathBuf) -> Result<()>
|
|
|
774
774
|
.map_err(|e| anyhow::anyhow!("failed to construct wasmtime engine: {e}"))?;
|
|
775
775
|
|
|
776
776
|
let module = wasmtime::Module::new(&engine, &wasm_bytes)
|
|
777
|
-
.map_err(|e| anyhow::anyhow!("compiled wasm failed to load: {e}"))?;
|
|
777
|
+
.map_err(|e| anyhow::anyhow!("compiled wasm failed to load: {e:#}"))?;
|
|
778
778
|
let mut store = wasmtime::Store::new(&engine, ());
|
|
779
779
|
let imports = hostImports(&mut store, &module)?;
|
|
780
780
|
let instance = wasmtime::Instance::new(&mut store, &module, &imports)
|
|
@@ -913,6 +913,9 @@ fn cmdTest(paths: Vec<std::path::PathBuf>, lib_path: std::path::PathBuf) -> Resu
|
|
|
913
913
|
/// the single-file and directory-recursive paths of `cmdTest`.
|
|
914
914
|
fn runTestFile(file: &std::path::Path, lib_path: &std::path::Path) -> Result<(usize, usize)> {
|
|
915
915
|
let (wasm_bytes, names) = compileToTestWasm(file, lib_path)?;
|
|
916
|
+
if let Ok(path) = std::env::var("PLUM_DUMP_WASM") {
|
|
917
|
+
let _ = std::fs::write(path, &wasm_bytes);
|
|
918
|
+
}
|
|
916
919
|
|
|
917
920
|
let mut config = wasmtime::Config::new();
|
|
918
921
|
config.wasm_gc(true);
|
|
@@ -921,7 +924,7 @@ fn runTestFile(file: &std::path::Path, lib_path: &std::path::Path) -> Result<(us
|
|
|
921
924
|
.map_err(|e| anyhow::anyhow!("failed to construct wasmtime engine: {e}"))?;
|
|
922
925
|
|
|
923
926
|
let module = wasmtime::Module::new(&engine, &wasm_bytes)
|
|
924
|
-
.map_err(|e| anyhow::anyhow!("compiled wasm failed to load: {e}"))?;
|
|
927
|
+
.map_err(|e| anyhow::anyhow!("compiled wasm failed to load: {e:#}"))?;
|
|
925
928
|
let mut store = wasmtime::Store::new(&engine, ());
|
|
926
929
|
let imports = hostImports(&mut store, &module)?;
|
|
927
930
|
let instance = wasmtime::Instance::new(&mut store, &module, &imports)
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -48,6 +48,7 @@ pub struct WasmModule {
|
|
|
48
48
|
pub func_count: u32,
|
|
49
49
|
global_count: u32,
|
|
50
50
|
start_function: Option<u32>,
|
|
51
|
+
func_names: Vec<(u32, String)>,
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
impl WasmModule {
|
|
@@ -65,6 +66,7 @@ impl WasmModule {
|
|
|
65
66
|
func_count: 0,
|
|
66
67
|
global_count: 0,
|
|
67
68
|
start_function: None,
|
|
69
|
+
func_names: Vec::new(),
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -107,6 +109,11 @@ impl WasmModule {
|
|
|
107
109
|
idx
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/// Records a debug name for function `idx` (wasm `name` section).
|
|
113
|
+
pub fn nameFunction(&mut self, idx: u32, name: &str) {
|
|
114
|
+
self.func_names.push((idx, name.to_string()));
|
|
115
|
+
}
|
|
116
|
+
|
|
110
117
|
pub fn addExport(&mut self, name: &str, kind: ExportKind, idx: u32) {
|
|
111
118
|
self.exports.push((name.to_string(), kind, idx));
|
|
112
119
|
}
|
|
@@ -294,6 +301,19 @@ impl WasmModule {
|
|
|
294
301
|
module.section(&data);
|
|
295
302
|
}
|
|
296
303
|
|
|
304
|
+
if !self.func_names.is_empty() {
|
|
305
|
+
let mut names = wasm_encoder::NameSection::new();
|
|
306
|
+
let mut map = wasm_encoder::NameMap::new();
|
|
307
|
+
let mut sorted = self.func_names.clone();
|
|
308
|
+
sorted.sort_by_key(|(i, _)| *i);
|
|
309
|
+
sorted.dedup_by_key(|(i, _)| *i);
|
|
310
|
+
for (i, n) in &sorted {
|
|
311
|
+
map.append(*i, n);
|
|
312
|
+
}
|
|
313
|
+
names.functions(&map);
|
|
314
|
+
module.section(&names);
|
|
315
|
+
}
|
|
316
|
+
|
|
297
317
|
module.finish()
|
|
298
318
|
}
|
|
299
319
|
}
|
|
@@ -356,33 +376,37 @@ pub struct ClosureInfo {
|
|
|
356
376
|
/// `Unit` result is a null `anyref`. This keeps one wasm function per closure
|
|
357
377
|
/// literal and needs no per-instantiation thunk: the "thunk" work is done in the
|
|
358
378
|
/// closure's own prologue and the call site's epilogue.
|
|
359
|
-
type ClosureSigKey =
|
|
379
|
+
type ClosureSigKey = (Vec<ValType>, Option<ValType>);
|
|
360
380
|
|
|
361
381
|
/// Global, read-only lookup tables shared by every function body being compiled.
|
|
362
382
|
pub struct CompileCtx<'a> {
|
|
363
383
|
pub func_ids: HashMap<String, u32>,
|
|
364
384
|
pub func_sigs: HashMap<String, FuncSig>,
|
|
385
|
+
/// Instantiation id -> its wasm function and signature.
|
|
386
|
+
pub inst_funcs: HashMap<usize, (u32, FuncSig)>,
|
|
387
|
+
/// See `plum_checker::TypedInfo::call_inst`.
|
|
388
|
+
pub call_inst: HashMap<(Option<usize>, usize, bool), usize>,
|
|
389
|
+
/// Every free (non-method) fn name, generic or not.
|
|
390
|
+
pub known_fns: HashSet<String>,
|
|
391
|
+
pub eg: EnumGenerics,
|
|
365
392
|
pub methods: MethodEnv,
|
|
366
393
|
pub enum_variants: EnumVariants,
|
|
367
394
|
pub enum_params: EnumParams,
|
|
368
395
|
pub min_required: plum_checker::MinRequiredArgs,
|
|
369
396
|
pub global_env: TypeEnv,
|
|
370
397
|
/// Closure literal (keyed by `&Expr::Closure` pointer identity) -> its `ClosureInfo`.
|
|
371
|
-
pub closures: HashMap<usize, ClosureInfo>,
|
|
398
|
+
pub closures: HashMap<(usize, usize), ClosureInfo>,
|
|
372
399
|
/// The AST of each discovered closure literal, keyed the same way, so its body can
|
|
373
400
|
/// be compiled in a second pass after all closures are registered.
|
|
374
|
-
pub closure_asts: HashMap<usize, &'a ast::Closure>,
|
|
401
|
+
pub closure_asts: HashMap<(usize, usize), &'a ast::Closure>,
|
|
375
402
|
/// Closure arity -> the erased `call_indirect` function-type index.
|
|
376
403
|
pub closure_call_types: HashMap<ClosureSigKey, u32>,
|
|
377
|
-
/// Instantiated type of every expression of the original source,
|
|
378
|
-
/// keyed by expression address — from `plum_checker::typedSource`.
|
|
379
|
-
pub types: HashMap<usize, PlumType>,
|
|
380
404
|
/// Top-level function name -> global index holding its zero-capture "trampoline"
|
|
381
405
|
/// closure struct `{table_idx, env=null}`, for using a plain named function
|
|
382
406
|
/// wherever a `fn(...)`-typed value is expected (e.g. `each(double)`). Since the
|
|
383
407
|
/// struct has no captures it never changes, so it's built once by the shared
|
|
384
408
|
/// `start` function instead of being reconstructed per reference.
|
|
385
|
-
pub named_fn_values: HashMap<String, u32>,
|
|
409
|
+
pub named_fn_values: HashMap<(String, PlumType), u32>,
|
|
386
410
|
/// Shared runtime helper `(a: ref Str, b: ref Str) -> ref Str`: allocates a new
|
|
387
411
|
/// `array<i8>` exactly long enough to hold `a`'s bytes followed by `b`'s, for
|
|
388
412
|
/// lowering string interpolation (`"{expr}"`).
|
|
@@ -403,7 +427,7 @@ pub struct CompileCtx<'a> {
|
|
|
403
427
|
pub gc_types: GcTypeRegistry,
|
|
404
428
|
/// Payload-free variant name (True/False/End/...) -> the global index holding
|
|
405
429
|
/// its one pre-allocated instance (see this migration plan's Decision 2).
|
|
406
|
-
pub singleton_globals: HashMap<String, u32>,
|
|
430
|
+
pub singleton_globals: HashMap<(PlumType, String), u32>,
|
|
407
431
|
}
|
|
408
432
|
|
|
409
433
|
/// Per-module state that accumulates as function bodies are compiled: every static
|
|
@@ -435,14 +459,20 @@ struct LocalCtx<'a> {
|
|
|
435
459
|
variadic_for_scratch: HashMap<usize, u32>,
|
|
436
460
|
func_ids: &'a HashMap<String, u32>,
|
|
437
461
|
func_sigs: &'a HashMap<String, FuncSig>,
|
|
462
|
+
inst_funcs: &'a HashMap<usize, (u32, FuncSig)>,
|
|
463
|
+
call_inst: &'a HashMap<(Option<usize>, usize, bool), usize>,
|
|
464
|
+
known_fns: &'a HashSet<String>,
|
|
465
|
+
/// Index of the unit being compiled (closures are keyed by it) and its instantiation.
|
|
466
|
+
unit_idx: usize,
|
|
467
|
+
unit_inst: Option<usize>,
|
|
438
468
|
types: &'a HashMap<usize, PlumType>,
|
|
439
469
|
/// Name -> the plum type each local was DECLARED with (so its wasm
|
|
440
470
|
/// representation is `plumTypeToValtype` of this), as opposed to the
|
|
441
471
|
/// type of a particular use of it.
|
|
442
472
|
local_decl_types: HashMap<String, PlumType>,
|
|
443
|
-
closures: &'a HashMap<usize, ClosureInfo>,
|
|
473
|
+
closures: &'a HashMap<(usize, usize), ClosureInfo>,
|
|
444
474
|
closure_call_types: &'a HashMap<ClosureSigKey, u32>,
|
|
445
|
-
named_fn_values: &'a HashMap<String, u32>,
|
|
475
|
+
named_fn_values: &'a HashMap<(String, PlumType), u32>,
|
|
446
476
|
string_concat_func: u32,
|
|
447
477
|
string_eq_func: u32,
|
|
448
478
|
dyn_eq_func: u32,
|
|
@@ -452,7 +482,7 @@ struct LocalCtx<'a> {
|
|
|
452
482
|
enum_params: &'a EnumParams,
|
|
453
483
|
min_required: &'a plum_checker::MinRequiredArgs,
|
|
454
484
|
gc_types: &'a GcTypeRegistry,
|
|
455
|
-
singleton_globals: &'a HashMap<String, u32>,
|
|
485
|
+
singleton_globals: &'a HashMap<(PlumType, String), u32>,
|
|
456
486
|
/// Tracks each binding's inferred type as compilation proceeds through
|
|
457
487
|
/// statements in order, mirroring `plum-checker`'s own env evolution — needed
|
|
458
488
|
/// to resolve `Attribute`/`ClassCall` targets and pick the right load/store width.
|
|
@@ -847,27 +877,17 @@ fn withGcTypes<R>(f: impl FnOnce(&GcTypeRegistry) -> R) -> R {
|
|
|
847
877
|
})
|
|
848
878
|
}
|
|
849
879
|
|
|
850
|
-
/// A constructor-pattern name (`nested_class_scratch_types`'s recorded name, or any
|
|
851
|
-
///
|
|
880
|
+
/// GC type index of a pattern/`?` scratch local: variant `vname` of enum type `ty`,
|
|
852
|
-
/// variant — falls back to `class_type_idx` only for the record-shaped alias case
|
|
853
|
-
/// (see `buildGcTypeRegistry`'s own alias comment), which `variant_type_idx` should
|
|
854
|
-
///
|
|
881
|
+
/// or (empty `vname`) the enum's own supertype.
|
|
855
|
-
fn
|
|
882
|
+
fn scratchTypeIdx(r: &GcTypeRegistry, ty: &PlumType, vname: &str) -> u32 {
|
|
883
|
+
let found = if vname.is_empty() {
|
|
884
|
+
r.superIdx(ty).or_else(|| r.classIdx(ty))
|
|
885
|
+
} else {
|
|
856
|
-
|
|
886
|
+
r.variantIdx(ty, vname)
|
|
857
|
-
|
|
887
|
+
};
|
|
858
|
-
.or_else(|| r.class_type_idx.get(name))
|
|
859
|
-
// A scratch slot narrowed to a MULTI-variant enum's own wide supertype
|
|
860
|
-
// (not any one variant) — the `?` operator's scratch local holds the
|
|
861
|
-
// subject at its natural (possibly multi-variant, e.g. `Try`)
|
|
862
|
-
// type, never narrower, since it ref.casts to whichever of the two
|
|
863
|
-
// variants applies only at the point of use, not up front.
|
|
864
|
-
.or_else(|| r.enum_super_type_idx.get(name))
|
|
865
|
-
|
|
888
|
+
found.unwrap_or_else(|| {
|
|
866
|
-
panic!(
|
|
867
|
-
|
|
889
|
+
panic!("internal codegen error: '{}' of {} missing from the GC type registry", vname, ty)
|
|
868
|
-
name
|
|
869
|
-
)
|
|
870
|
-
|
|
890
|
+
})
|
|
871
891
|
}
|
|
872
892
|
|
|
873
893
|
/// Resolves an `ast::Type`/`ast::ParamType`'s bare name (e.g. from a function
|
|
@@ -927,54 +947,7 @@ fn pushSelfPlaceholder(vt: ValType, body: &mut Vec<u8>) {
|
|
|
927
947
|
}
|
|
928
948
|
|
|
929
949
|
fn plumTypeToValtype(t: &PlumType) -> ValType {
|
|
930
|
-
match t {
|
|
931
|
-
PlumType::TApp(n, _) => plumTypeToValtype(&PlumType::TNamed(n.clone())),
|
|
932
|
-
PlumType::TInt => ValType::I64,
|
|
933
|
-
PlumType::TFloat => ValType::F64,
|
|
934
|
-
PlumType::TBool => ValType::I32,
|
|
935
|
-
// `Str` is an ORDINARY class (`type Str(...) = data: Buffer ...` in
|
|
936
|
-
// `str.plum`) — its own real struct type, registered exactly like any
|
|
937
|
-
// other class (see `buildGcTypeRegistry`'s `Slot::Class` handling),
|
|
938
|
-
// NOT the raw `byte_array_type_idx` (that's `Buffer`'s own `data`
|
|
939
|
-
// field's type, two levels down) — except when `Str` was never
|
|
940
|
-
// actually declared at all (an isolated snippet/unit test that never
|
|
941
|
-
// went through `plum-core::loader::loadAndMerge`'s `std/str` prelude),
|
|
942
|
-
// where `strGcValtypeOrFallback` falls back to treating `Str` as
|
|
943
|
-
// that raw array directly, matching this function's pre-refactor
|
|
944
|
-
// behavior.
|
|
945
|
-
PlumType::TStr => withGcTypes(strGcValtypeOrFallback),
|
|
946
|
-
PlumType::TByte => ValType::I32,
|
|
947
|
-
// `[]Byte` is represented by the EXACT SAME wasm-gc array type as `Str`
|
|
948
|
-
// (a mutable `array<i8>`) — they're structurally identical, and nothing
|
|
949
|
-
// in this codegen needs to distinguish them at the wasm-type level
|
|
950
|
-
// (no runtime `ref.test`/dynamic dispatch keys off it), so reusing
|
|
951
|
-
// `byte_array_type_idx` avoids a second, redundant GC type-section entry.
|
|
952
|
-
PlumType::TByteSlice => withGcTypes(|r| gcRef(r.byte_array_type_idx)),
|
|
953
|
-
PlumType::TNamed(name) => withGcTypes(|r| {
|
|
954
|
-
match r
|
|
955
|
-
.class_type_idx
|
|
956
|
-
.get(name)
|
|
957
|
-
.or_else(|| r.enum_super_type_idx.get(name))
|
|
958
|
-
{
|
|
959
|
-
Some(idx) => gcRef(*idx),
|
|
960
|
-
// Genuinely unmodeled type name (not a real class/enum) — permissive
|
|
961
|
-
// fallback, matching this function's pre-wasm-gc "unmodeled type:
|
|
962
|
-
// pointer" behavior; codegen sites that actually need a concrete
|
|
963
|
-
// struct type still resolve it themselves and error clearly if absent.
|
|
964
|
-
None => ValType::Ref(RefType::ANYREF),
|
|
965
|
-
}
|
|
966
|
-
}),
|
|
967
|
-
// A closure value is a 2-field `{table_index: i32, env: anyref}` struct —
|
|
968
|
-
// its OWN concrete GC type, registered per Task 2e (closures), not through
|
|
969
|
-
// this general resolver; `anyref` here is a safe placeholder used only
|
|
970
|
-
// where a closure's exact struct type isn't being constructed/destructured
|
|
971
|
-
// directly (e.g. deciding a local's storage class), matching `TVariadic`'s
|
|
972
|
-
// existing "not modeled as a concrete shape here" treatment.
|
|
973
|
-
PlumType::TTags(_) => withGcTypes(|r| gcRef(r.tag_type_idx)),
|
|
974
|
-
PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
|
|
975
|
-
PlumType::TParam(_) => withGcTypes(|r| gcRef(r.obj_type_idx)),
|
|
976
|
-
|
|
950
|
+
withGcTypes(|r| plumTypeToGcValtype(t, r))
|
|
977
|
-
}
|
|
978
951
|
}
|
|
979
952
|
|
|
980
953
|
/// Type-section indices for every wasm-gc composite type this program's
|
|
@@ -992,6 +965,17 @@ pub struct GcTypeRegistry {
|
|
|
992
965
|
/// Variant name (flat namespace, matching `EnumVariants`) -> its concrete
|
|
993
966
|
/// subtype `struct` type index.
|
|
994
967
|
pub variant_type_idx: HashMap<String, u32>,
|
|
968
|
+
/// Concrete instance of a generic enum (`List[Int]`) -> its supertype index
|
|
969
|
+
/// (for `Array[T]`: the native array type of `T`'s representation).
|
|
970
|
+
pub app_super: HashMap<PlumType, u32>,
|
|
971
|
+
/// Record-shaped generic enum instance -> its one variant's struct index.
|
|
972
|
+
pub app_class: HashMap<PlumType, u32>,
|
|
973
|
+
/// (generic enum instance, variant name) -> variant struct index.
|
|
974
|
+
pub app_variant: HashMap<(PlumType, String), u32>,
|
|
975
|
+
/// Every registered enum instance (`TNamed` for non-generic, `TApp` for generic).
|
|
976
|
+
pub instances: Vec<PlumType>,
|
|
977
|
+
/// Names of every declared enum.
|
|
978
|
+
pub known_enums: HashSet<String>,
|
|
995
979
|
/// The single shared `array<i8>` type index `[]Byte`/`Buffer`'s own
|
|
996
980
|
/// `data` field use — NOT `Str`'s own representation (`Str` is an
|
|
997
981
|
/// ordinary class, `data: Buffer`; see `libs/std/Str.plum`'s header).
|
|
@@ -1039,6 +1023,36 @@ pub struct GcTypeRegistry {
|
|
|
1039
1023
|
pub dyn_fns: HashMap<(String, String), u32>,
|
|
1040
1024
|
}
|
|
1041
1025
|
|
|
1026
|
+
impl GcTypeRegistry {
|
|
1027
|
+
/// Supertype struct index of enum type `ty` (`TNamed` or `TApp`).
|
|
1028
|
+
pub fn superIdx(&self, ty: &PlumType) -> Option<u32> {
|
|
1029
|
+
match ty {
|
|
1030
|
+
PlumType::TNamed(n) => self
|
|
1031
|
+
.enum_super_type_idx
|
|
1032
|
+
.get(n)
|
|
1033
|
+
.or_else(|| self.class_type_idx.get(n))
|
|
1034
|
+
.copied(),
|
|
1035
|
+
PlumType::TApp(..) => self.app_super.get(ty).copied(),
|
|
1036
|
+
_ => None,
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/// Struct index of the class-like (record-shaped) type `ty`.
|
|
1040
|
+
pub fn classIdx(&self, ty: &PlumType) -> Option<u32> {
|
|
1041
|
+
match ty {
|
|
1042
|
+
PlumType::TNamed(n) => self.class_type_idx.get(n).copied(),
|
|
1043
|
+
PlumType::TApp(..) => self.app_class.get(ty).copied(),
|
|
1044
|
+
_ => None,
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/// Struct index of variant `vname` of enum type `ty`.
|
|
1048
|
+
pub fn variantIdx(&self, ty: &PlumType, vname: &str) -> Option<u32> {
|
|
1049
|
+
match ty {
|
|
1050
|
+
PlumType::TApp(..) => self.app_variant.get(&(ty.clone(), vname.to_string())).copied(),
|
|
1051
|
+
_ => self.variant_type_idx.get(vname).copied(),
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1042
1056
|
/// Box type ids (see the plan's Global constraints): 0 is `$Obj` itself.
|
|
1043
1057
|
pub const TYPE_ID_BOX_I64: i32 = 1;
|
|
1044
1058
|
pub const TYPE_ID_BOX_F64: i32 = 2;
|
|
@@ -1082,45 +1096,27 @@ fn isArraySpecialization(name: &str) -> bool {
|
|
|
1082
1096
|
/// structure: this function is only ever called during its second pass).
|
|
1083
1097
|
fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
1084
1098
|
match t {
|
|
1085
|
-
PlumType::TApp(n, _) =>
|
|
1099
|
+
PlumType::TApp(n, _) => match registry.classIdx(t).or_else(|| registry.superIdx(t)) {
|
|
1100
|
+
Some(idx) => gcRef(idx),
|
|
1101
|
+
None if hasFreeTy(t) || !registry.known_enums.contains(n) => {
|
|
1102
|
+
ValType::Ref(RefType::ANYREF)
|
|
1103
|
+
}
|
|
1104
|
+
None => panic!("internal codegen error: type {} was not registered", t),
|
|
1105
|
+
},
|
|
1086
1106
|
PlumType::TInt => ValType::I64,
|
|
1087
1107
|
PlumType::TFloat => ValType::F64,
|
|
1088
1108
|
PlumType::TBool => ValType::I32,
|
|
1089
|
-
// See the matching comment in `plumTypeToValtype` — `Str` is an
|
|
1090
|
-
// ordinary class's own real struct type, not the raw
|
|
1091
|
-
// `byte_array_type_idx`, except when `Str` was never actually
|
|
1092
|
-
// declared at all (`strGcValtypeOrFallback`'s fallback case).
|
|
1093
1109
|
PlumType::TStr => strGcValtypeOrFallback(registry),
|
|
1094
1110
|
PlumType::TByte => ValType::I32,
|
|
1095
|
-
// See the matching comment in `plumTypeToValtype` — `[]Byte` reuses
|
|
1096
|
-
// `Buffer`'s own `array<i8>` GC type index rather than getting its own.
|
|
1097
|
-
PlumType::TByteSlice => ValType::Ref(RefType {
|
|
1098
|
-
nullable: true,
|
|
1099
|
-
|
|
1111
|
+
PlumType::TByteSlice => gcRef(registry.byte_array_type_idx),
|
|
1100
|
-
}),
|
|
1101
1112
|
PlumType::TNamed(name) => match registry
|
|
1102
1113
|
.class_type_idx
|
|
1103
1114
|
.get(name)
|
|
1104
1115
|
.or_else(|| registry.enum_super_type_idx.get(name))
|
|
1105
1116
|
{
|
|
1106
|
-
Some(idx) =>
|
|
1117
|
+
Some(idx) => gcRef(*idx),
|
|
1107
|
-
nullable: true,
|
|
1108
|
-
heap_type: HeapType::Concrete(*idx),
|
|
1109
|
-
}),
|
|
1110
|
-
// A field typed as a generic enum (e.g. `Node.next: Link[Node]`)
|
|
1111
|
-
// resolves here to the BARE generic name — `plumTypeFromAst`
|
|
1112
|
-
// has no representation for type arguments, so it can't know this means
|
|
1113
|
-
// `Link[Int]` rather than bare `Link` (erased generics keep one
|
|
1114
|
-
// compiled `Link`) — same permissive `anyref` fallback as
|
|
1115
|
-
// `plumTypeToValtype` above, for the same "genuinely unmodeled" reason:
|
|
1116
|
-
// `struct.get`/`ref.test`/`ref.cast` all work against `anyref` operands
|
|
1117
|
-
// fine, so a field merely being STORED as `anyref` instead of the exact
|
|
1118
|
-
// concrete type costs nothing but static precision.
|
|
1119
1118
|
None => ValType::Ref(RefType::ANYREF),
|
|
1120
1119
|
},
|
|
1121
|
-
// TFun (closures) and TVariadic get their own concrete representation once
|
|
1122
|
-
// Task 2 (closures/variadic calls) lands — `anyref` is a safe, valid-but-not-
|
|
1123
|
-
// yet-meaningful placeholder in the meantime, since nothing consumes it yet.
|
|
1124
1120
|
PlumType::TTags(_) => gcRef(registry.tag_type_idx),
|
|
1125
1121
|
PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
|
|
1126
1122
|
PlumType::TParam(_) => gcRef(registry.obj_type_idx),
|
|
@@ -1128,86 +1124,92 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
1128
1124
|
}
|
|
1129
1125
|
}
|
|
1130
1126
|
|
|
1127
|
+
/// Free type variables / generic parameters anywhere inside `t`.
|
|
1128
|
+
fn hasFreeTy(t: &PlumType) -> bool {
|
|
1129
|
+
match t {
|
|
1130
|
+
PlumType::TVar(_) | PlumType::TParam(_) => true,
|
|
1131
|
+
PlumType::TApp(_, a) => a.iter().any(hasFreeTy),
|
|
1132
|
+
PlumType::TFun(ps, r) => ps.iter().any(hasFreeTy) || hasFreeTy(r),
|
|
1133
|
+
PlumType::TVariadic(i) => hasFreeTy(i),
|
|
1134
|
+
PlumType::TTags(m) => m.values().flatten().any(|t| hasFreeTy(t)),
|
|
1135
|
+
_ => false,
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
fn arrayDepth(t: &PlumType) -> usize {
|
|
1140
|
+
match t {
|
|
1141
|
+
PlumType::TApp(n, a) => {
|
|
1142
|
+
usize::from(isArraySpecialization(n)) + a.iter().map(arrayDepth).max().unwrap_or(0)
|
|
1143
|
+
}
|
|
1144
|
+
_ => 0,
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1131
|
-
/// Builds the wasm-gc type registry
|
|
1148
|
+
/// Builds the wasm-gc type registry: one struct family per non-generic enum and per
|
|
1132
|
-
/// `source` (`Bool`/`Str`/etc included, but only if imported — no builtin is
|
|
1133
|
-
/// hardcoded here), plus the shared raw byte-array type, and declares them all as
|
|
1134
|
-
/// ONE `rec` group via `module.addGcTypes` — a single group sidesteps every ordering
|
|
1135
|
-
/// question about mutual/self-references (a record-shaped enum's field naming
|
|
1136
|
-
/// another such enum's type, an enum variant field referencing its own enum,
|
|
1137
|
-
/// `
|
|
1149
|
+
/// distinct concrete instance (`apps`, e.g. `List[Int]`) of a generic enum, keyed by
|
|
1150
|
+
/// the structural `PlumType`; `Array[T]` instances become native wasm arrays of `T`'s
|
|
1138
|
-
///
|
|
1151
|
+
/// representation. Everything is declared as ONE `rec` group.
|
|
1139
1152
|
fn buildGcTypeRegistry(
|
|
1140
1153
|
module: &mut WasmModule,
|
|
1141
1154
|
source: &ast::Source,
|
|
1142
1155
|
enum_variants: &EnumVariants,
|
|
1143
1156
|
enum_params: &EnumParams,
|
|
1157
|
+
apps: &[PlumType],
|
|
1144
1158
|
) -> GcTypeRegistry {
|
|
1145
1159
|
enum Slot {
|
|
1146
1160
|
Str,
|
|
1147
|
-
/// `$Obj {typeId: i32}` — supertype of every struct below.
|
|
1148
1161
|
Obj,
|
|
1149
|
-
EnumSuper(
|
|
1162
|
+
EnumSuper(PlumType),
|
|
1150
|
-
Variant(String),
|
|
1163
|
+
Variant(PlumType, String),
|
|
1151
|
-
/// The single shared `array<anyref>` type every `Array[T]`
|
|
1152
|
-
/// aliases — see `isArraySpecialization`.
|
|
1153
|
-
ArrayRef,
|
|
1164
|
+
ArrayRef(ValType),
|
|
1154
1165
|
Tag,
|
|
1155
1166
|
BoxI64,
|
|
1156
1167
|
BoxF64,
|
|
1157
1168
|
BoxI32,
|
|
1158
1169
|
BoxBool,
|
|
1159
1170
|
}
|
|
1160
|
-
|
|
1161
|
-
// Pass 1: assign every entry a slot (and therefore a type index) up front, before
|
|
1162
|
-
// any field list is built, so field-type resolution can reference ANY other entry.
|
|
1163
1171
|
let mut slots: Vec<Slot> = vec![Slot::Str, Slot::Obj];
|
|
1164
1172
|
let obj_type_idx: u32 = 1;
|
|
1173
|
+
let mut reg = GcTypeRegistry {
|
|
1174
|
+
class_type_idx: HashMap::new(),
|
|
1175
|
+
enum_super_type_idx: HashMap::new(),
|
|
1165
|
-
|
|
1176
|
+
variant_type_idx: HashMap::new(),
|
|
1177
|
+
app_super: HashMap::new(),
|
|
1178
|
+
app_class: HashMap::new(),
|
|
1179
|
+
app_variant: HashMap::new(),
|
|
1180
|
+
instances: Vec::new(),
|
|
1181
|
+
known_enums: HashSet::new(),
|
|
1182
|
+
byte_array_type_idx: 0,
|
|
1183
|
+
closure_type_idx: 0,
|
|
1184
|
+
variadic_array_type_idx: HashMap::new(),
|
|
1185
|
+
array_type_idx: 0,
|
|
1186
|
+
tag_type_idx: 0,
|
|
1187
|
+
box_i64_idx: 0,
|
|
1188
|
+
box_f64_idx: 0,
|
|
1189
|
+
box_i32_idx: 0,
|
|
1190
|
+
box_bool_idx: 0,
|
|
1191
|
+
obj_type_idx,
|
|
1192
|
+
type_ids: HashMap::new(),
|
|
1193
|
+
box_i64_fn: 0,
|
|
1194
|
+
box_f64_fn: 0,
|
|
1195
|
+
box_i32_fn: 0,
|
|
1196
|
+
box_bool_fn: 0,
|
|
1197
|
+
mk_str_fn: None,
|
|
1198
|
+
dyn_fns: HashMap::new(),
|
|
1199
|
+
};
|
|
1166
1200
|
let mut next_type_id = TYPE_ID_FIRST_DECLARED;
|
|
1167
|
-
let mut
|
|
1201
|
+
let mut enum_decls: HashMap<String, &ast::Enum> = HashMap::new();
|
|
1168
|
-
let mut enum_super_type_idx: HashMap<String, u32> = HashMap::new();
|
|
1169
|
-
let mut variant_type_idx: HashMap<String, u32> = HashMap::new();
|
|
1170
|
-
let mut array_ref_slot: Option<u32> = None;
|
|
1171
|
-
|
|
1172
|
-
// `Bool` is an ordinary enum now (`type Bool = | True | False` in
|
|
1173
|
-
// `libs/std/Bool.plum`) — registered exactly like any other enum
|
|
1174
|
-
// declared in `source.items`, present only if actually imported.
|
|
1175
|
-
let mut
|
|
1202
|
+
let mut instances: Vec<PlumType> = Vec::new();
|
|
1176
1203
|
for item in &source.items {
|
|
1177
|
-
if let ast::Item::
|
|
1204
|
+
if let ast::Item::Trait(t) = item {
|
|
1178
|
-
enum_decls.push((
|
|
1179
|
-
e.name.clone(),
|
|
1180
|
-
|
|
1205
|
+
reg.class_type_idx.insert(t.name.clone(), obj_type_idx);
|
|
1181
|
-
));
|
|
1182
1206
|
}
|
|
1183
1207
|
}
|
|
1184
|
-
// A trait name used as a type lowers to `ref null $Obj`.
|
|
1185
1208
|
for item in &source.items {
|
|
1186
|
-
|
|
1209
|
+
let ast::Item::Enum(e) = item else { continue };
|
|
1187
|
-
class_type_idx.insert(t.name.clone(), obj_type_idx);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
for (enum_name, variant_names) in &enum_decls {
|
|
1191
|
-
// `Array`/`Array$T` is a RECORD-shaped enum (single variant sharing the
|
|
1192
|
-
// enum's own name — the `type Array[T] = ...` replacement) whose values
|
|
1193
|
-
// are ALL represented by the one shared `array<anyref>` type regardless
|
|
1194
|
-
// of `T` (see `isArraySpecialization`'s own doc comment) — alias its
|
|
1195
|
-
// enum/variant/class names onto that single existing slot instead of
|
|
1196
|
-
// giving it its own real struct type.
|
|
1197
|
-
if isArraySpecialization(enum_name) {
|
|
1198
|
-
let idx = *array_ref_slot.get_or_insert_with(|| {
|
|
1199
|
-
let idx = slots.len() as u32;
|
|
1200
|
-
slots.push(Slot::ArrayRef);
|
|
1201
|
-
idx
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1210
|
+
enum_decls.insert(e.name.clone(), e);
|
|
1204
|
-
class_type_idx.insert(enum_name.clone(), idx);
|
|
1205
|
-
for vname in variant_names {
|
|
1206
|
-
|
|
1211
|
+
reg.known_enums.insert(e.name.clone());
|
|
1207
|
-
}
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
let this_id = match
|
|
1212
|
+
let this_id = match e.name.as_str() {
|
|
1211
1213
|
"Str" => TYPE_ID_STR,
|
|
1212
1214
|
"Buffer" => TYPE_ID_BUFFER,
|
|
1213
1215
|
_ => {
|
|
@@ -1216,67 +1218,101 @@ fn buildGcTypeRegistry(
|
|
|
1216
1218
|
id
|
|
1217
1219
|
}
|
|
1218
1220
|
};
|
|
1219
|
-
type_ids.insert(
|
|
1221
|
+
reg.type_ids.insert(e.name.clone(), this_id);
|
|
1220
|
-
for
|
|
1222
|
+
for v in &e.variants {
|
|
1221
|
-
type_ids.insert(
|
|
1223
|
+
reg.type_ids.insert(v.name.clone(), this_id);
|
|
1222
1224
|
}
|
|
1223
|
-
|
|
1225
|
+
if e.generics.is_empty() && !isArraySpecialization(&e.name) {
|
|
1224
|
-
slots.push(Slot::EnumSuper(enum_name.clone()));
|
|
1225
|
-
for vname in variant_names {
|
|
1226
|
-
variant_type_idx.insert(vname.clone(), slots.len() as u32);
|
|
1227
|
-
|
|
1226
|
+
instances.push(PlumType::TNamed(e.name.clone()));
|
|
1228
1227
|
}
|
|
1229
|
-
// A RECORD-shaped enum (the `type X = ...` replacement, e.g. `enum Cat =
|
|
1230
|
-
|
|
1228
|
+
}
|
|
1231
|
-
// variant's real struct slot — `plumTypeToValtype`'s `TNamed(name)` arm
|
|
1232
|
-
// resolves a value of this type via `class_type_idx` (checked first,
|
|
1233
|
-
// falling back to `enum_super_type_idx`), and every OTHER codegen site
|
|
1234
|
-
|
|
1229
|
+
let mut arrays: Vec<PlumType> = Vec::new();
|
|
1230
|
+
for a in apps {
|
|
1235
|
-
|
|
1231
|
+
if let PlumType::TApp(n, _) = a {
|
|
1232
|
+
if isArraySpecialization(n) {
|
|
1233
|
+
arrays.push(a.clone());
|
|
1236
|
-
|
|
1234
|
+
} else if enum_decls.contains_key(n) && !instances.contains(a) {
|
|
1237
|
-
|
|
1235
|
+
instances.push(a.clone());
|
|
1236
|
+
}
|
|
1238
1237
|
}
|
|
1239
1238
|
}
|
|
1240
|
-
|
|
1239
|
+
for ty in &instances {
|
|
1240
|
+
let n = plum_checker::nominalName(ty).unwrap().to_string();
|
|
1241
|
+
let e = enum_decls[&n];
|
|
1242
|
+
let super_idx = slots.len() as u32;
|
|
1243
|
+
slots.push(Slot::EnumSuper(ty.clone()));
|
|
1244
|
+
match ty {
|
|
1245
|
+
PlumType::TNamed(_) => {
|
|
1246
|
+
reg.enum_super_type_idx.insert(n.clone(), super_idx);
|
|
1247
|
+
}
|
|
1248
|
+
_ => {
|
|
1249
|
+
reg.app_super.insert(ty.clone(), super_idx);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for v in &e.variants {
|
|
1253
|
+
let idx = slots.len() as u32;
|
|
1254
|
+
slots.push(Slot::Variant(ty.clone(), v.name.clone()));
|
|
1255
|
+
match ty {
|
|
1256
|
+
PlumType::TNamed(_) => {
|
|
1257
|
+
reg.variant_type_idx.insert(v.name.clone(), idx);
|
|
1258
|
+
}
|
|
1259
|
+
_ => {
|
|
1260
|
+
reg.app_variant.insert((ty.clone(), v.name.clone()), idx);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
if e.variants.len() == 1 && v.name == n {
|
|
1264
|
+
match ty {
|
|
1265
|
+
PlumType::TNamed(_) => {
|
|
1266
|
+
reg.class_type_idx.insert(n.clone(), idx);
|
|
1267
|
+
}
|
|
1268
|
+
_ => {
|
|
1269
|
+
reg.app_class.insert(ty.clone(), idx);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
reg.instances = instances.clone();
|
|
1241
|
-
|
|
1276
|
+
reg.tag_type_idx = slots.len() as u32;
|
|
1242
1277
|
slots.push(Slot::Tag);
|
|
1243
|
-
|
|
1278
|
+
reg.box_i64_idx = slots.len() as u32;
|
|
1244
1279
|
slots.push(Slot::BoxI64);
|
|
1245
|
-
|
|
1280
|
+
reg.box_f64_idx = slots.len() as u32;
|
|
1246
1281
|
slots.push(Slot::BoxF64);
|
|
1247
|
-
|
|
1282
|
+
reg.box_i32_idx = slots.len() as u32;
|
|
1248
1283
|
slots.push(Slot::BoxI32);
|
|
1249
|
-
|
|
1284
|
+
reg.box_bool_idx = slots.len() as u32;
|
|
1250
1285
|
slots.push(Slot::BoxBool);
|
|
1251
1286
|
|
|
1252
|
-
//
|
|
1287
|
+
// Native arrays: one wasm array type per distinct element representation,
|
|
1253
|
-
// slot) even though no field lists exist yet — safe to hand to `plumTypeToGcValtype`
|
|
1254
|
-
//
|
|
1288
|
+
// innermost (least nested) element types first.
|
|
1255
|
-
|
|
1289
|
+
arrays.sort_by_key(arrayDepth);
|
|
1256
|
-
class_type_idx,
|
|
1257
|
-
enum_super_type_idx,
|
|
1258
|
-
variant_type_idx,
|
|
1259
|
-
byte_array_type_idx: 0,
|
|
1260
|
-
closure_type_idx: 0,
|
|
1261
|
-
|
|
1290
|
+
let mut by_vt: HashMap<ValType, u32> = HashMap::new();
|
|
1291
|
+
for a in &arrays {
|
|
1292
|
+
let PlumType::TApp(_, args) = a else { continue };
|
|
1293
|
+
let vt = plumTypeToGcValtype(&args[0], ®);
|
|
1262
|
-
|
|
1294
|
+
let idx = *by_vt.entry(vt).or_insert_with(|| {
|
|
1263
|
-
tag_type_idx,
|
|
1264
|
-
box_i64_idx,
|
|
1265
|
-
box_f64_idx,
|
|
1266
|
-
box_i32_idx,
|
|
1267
|
-
box_bool_idx,
|
|
1268
|
-
obj_type_idx,
|
|
1269
|
-
type_ids,
|
|
1270
|
-
box_i64_fn: 0,
|
|
1271
|
-
box_f64_fn: 0,
|
|
1272
|
-
box_i32_fn: 0,
|
|
1273
|
-
box_bool_fn: 0,
|
|
1274
|
-
|
|
1295
|
+
let idx = slots.len() as u32;
|
|
1275
|
-
|
|
1296
|
+
slots.push(Slot::ArrayRef(vt));
|
|
1297
|
+
idx
|
|
1276
|
-
|
|
1298
|
+
});
|
|
1299
|
+
reg.app_super.insert(a.clone(), idx);
|
|
1300
|
+
reg.app_class.insert(a.clone(), idx);
|
|
1301
|
+
reg.app_variant.insert((a.clone(), "Array".to_string()), idx);
|
|
1302
|
+
}
|
|
1277
1303
|
|
|
1304
|
+
let enum_generics = |n: &str| -> Vec<String> {
|
|
1305
|
+
enum_decls
|
|
1306
|
+
.get(n)
|
|
1307
|
+
.map(|e| e.generics.iter().map(|g| g.name.clone()).collect())
|
|
1308
|
+
.unwrap_or_default()
|
|
1309
|
+
};
|
|
1278
|
-
|
|
1310
|
+
let subst_for = |ty: &PlumType| -> std::collections::BTreeMap<String, PlumType> {
|
|
1311
|
+
match ty {
|
|
1312
|
+
PlumType::TApp(n, args) => enum_generics(n).into_iter().zip(args.iter().cloned()).collect(),
|
|
1279
|
-
|
|
1313
|
+
_ => Default::default(),
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1280
1316
|
let subtypes: Vec<SubType> = slots
|
|
1281
1317
|
.iter()
|
|
1282
1318
|
.map(|slot| match slot {
|
|
@@ -1291,20 +1327,16 @@ fn buildGcTypeRegistry(
|
|
|
1291
1327
|
shared: false,
|
|
1292
1328
|
},
|
|
1293
1329
|
},
|
|
1294
|
-
// An ORDINARY enum's supertype declares zero fields (each variant adds its
|
|
1295
|
-
// own distinct payload fields below it). A DISCRIMINANT enum (`enum
|
|
1296
|
-
// Foo(n: Int) = ...`) is different: every variant shares the EXACT SAME
|
|
1297
|
-
// field list (`plum-checker::buildGlobalTables` already gives every variant
|
|
1298
|
-
// of such an enum identical `field_types`, equal to the shared params), so
|
|
1299
|
-
// the supertype declares those fields directly — this is what lets `self.n`
|
|
1300
|
-
// field access work on the plain supertype-typed reference with a
|
|
1301
|
-
// `struct.get`, no `ref.cast` to one arbitrary variant required (which would
|
|
1302
|
-
// trap at runtime whenever `self` isn't actually THAT variant).
|
|
1303
|
-
Slot::EnumSuper(
|
|
1330
|
+
Slot::EnumSuper(ty) => {
|
|
1331
|
+
let n = plum_checker::nominalName(ty).unwrap();
|
|
1332
|
+
let s = subst_for(ty);
|
|
1304
|
-
let params = enum_params.get(
|
|
1333
|
+
let params = enum_params.get(n).cloned().unwrap_or_default();
|
|
1305
1334
|
let mut field_types: Vec<FieldType> = vec![typeIdField()];
|
|
1306
|
-
field_types.extend(params.iter().map(|(_,
|
|
1335
|
+
field_types.extend(params.iter().map(|(_, fty)| FieldType {
|
|
1307
|
-
element_type: StorageType::Val(plumTypeToGcValtype(
|
|
1336
|
+
element_type: StorageType::Val(plumTypeToGcValtype(
|
|
1337
|
+
&plum_checker::types::substitute(fty, &s),
|
|
1338
|
+
®,
|
|
1339
|
+
)),
|
|
1308
1340
|
mutable: true,
|
|
1309
1341
|
}));
|
|
1310
1342
|
SubType {
|
|
@@ -1318,29 +1350,14 @@ fn buildGcTypeRegistry(
|
|
|
1318
1350
|
},
|
|
1319
1351
|
}
|
|
1320
1352
|
}
|
|
1321
|
-
Slot::Variant(vname) => {
|
|
1353
|
+
Slot::Variant(ty, vname) => {
|
|
1322
1354
|
let info = enum_variants.get(vname).unwrap_or_else(|| {
|
|
1323
|
-
panic!(
|
|
1324
|
-
|
|
1355
|
+
panic!("internal codegen error: variant '{}' missing from EnumVariants", vname)
|
|
1325
|
-
vname
|
|
1326
|
-
)
|
|
1327
1356
|
});
|
|
1328
|
-
let super_idx = *registry
|
|
1329
|
-
.enum_super_type_idx
|
|
1330
|
-
.get(&info.enum_name)
|
|
1331
|
-
.unwrap_or_else(|| {
|
|
1332
|
-
panic!(
|
|
1333
|
-
|
|
1357
|
+
let super_idx = reg.superIdx(ty).expect("enum supertype slot");
|
|
1334
|
-
info.enum_name
|
|
1335
|
-
)
|
|
1336
|
-
});
|
|
1337
|
-
// For a discriminant enum, `info.field_types` is ALREADY identical to the
|
|
1338
|
-
// supertype's own fields (see the `EnumSuper` arm above) — a variant with
|
|
1339
|
-
// zero ADDED fields beyond its supertype is still valid wasm-gc
|
|
1340
|
-
// subtyping, so no special-casing is needed here.
|
|
1341
1358
|
let mut field_types: Vec<FieldType> = vec![typeIdField()];
|
|
1342
|
-
field_types.extend(info.
|
|
1359
|
+
field_types.extend(plum_checker::variantFields(info, ty).iter().map(|fty| FieldType {
|
|
1343
|
-
element_type: StorageType::Val(plumTypeToGcValtype(
|
|
1360
|
+
element_type: StorageType::Val(plumTypeToGcValtype(fty, ®)),
|
|
1344
1361
|
mutable: true,
|
|
1345
1362
|
}));
|
|
1346
1363
|
SubType {
|
|
@@ -1389,12 +1406,12 @@ fn buildGcTypeRegistry(
|
|
|
1389
1406
|
Slot::BoxF64 => boxSubType(ValType::F64, obj_type_idx),
|
|
1390
1407
|
Slot::BoxI32 => boxSubType(ValType::I32, obj_type_idx),
|
|
1391
1408
|
Slot::BoxBool => boxSubType(ValType::I32, obj_type_idx),
|
|
1392
|
-
Slot::ArrayRef => SubType {
|
|
1409
|
+
Slot::ArrayRef(vt) => SubType {
|
|
1393
1410
|
is_final: true,
|
|
1394
1411
|
supertype_idx: None,
|
|
1395
1412
|
composite_type: CompositeType {
|
|
1396
1413
|
inner: CompositeInnerType::Array(ArrayType(FieldType {
|
|
1397
|
-
element_type: StorageType::Val(
|
|
1414
|
+
element_type: StorageType::Val(*vt),
|
|
1398
1415
|
mutable: true,
|
|
1399
1416
|
})),
|
|
1400
1417
|
shared: false,
|
|
@@ -1402,10 +1419,8 @@ fn buildGcTypeRegistry(
|
|
|
1402
1419
|
},
|
|
1403
1420
|
})
|
|
1404
1421
|
.collect();
|
|
1405
|
-
|
|
1406
1422
|
module.addGcTypes(subtypes);
|
|
1407
|
-
|
|
1408
|
-
|
|
1423
|
+
reg
|
|
1409
1424
|
}
|
|
1410
1425
|
|
|
1411
1426
|
/// The immutable `typeId: i32` header field every `$Obj` subtype starts with.
|
|
@@ -1440,9 +1455,6 @@ fn blockTypeFor(result_vt: Option<ValType>) -> BlockType {
|
|
|
1440
1455
|
result_vt.map(BlockType::Result).unwrap_or(BlockType::Empty)
|
|
1441
1456
|
}
|
|
1442
1457
|
|
|
1443
|
-
fn retTypeToWasm(ret: Option<&ast::Type>) -> Option<ValType> {
|
|
1444
|
-
ret.and_then(|r| astTypeToWasm(&r.name))
|
|
1445
|
-
}
|
|
1446
1458
|
|
|
1447
1459
|
fn encodeLeb128U32(mut val: u32) -> Vec<u8> {
|
|
1448
1460
|
let mut bytes = Vec::new();
|
|
@@ -1752,18 +1764,305 @@ fn desugarAssertStmt(stmt: &ast::Stmt) -> ast::Stmt {
|
|
|
1752
1764
|
}
|
|
1753
1765
|
}
|
|
1754
1766
|
|
|
1767
|
+
/// One function body to compile: a non-generic fn (`inst: None`) or one concrete
|
|
1768
|
+
/// instantiation of a generic fn / method of a generic type.
|
|
1769
|
+
struct UnitDef<'a> {
|
|
1770
|
+
f: &'a ast::Fn,
|
|
1771
|
+
inst: Option<usize>,
|
|
1772
|
+
subst: std::collections::BTreeMap<String, PlumType>,
|
|
1773
|
+
/// Debug name in the wasm name section.
|
|
1774
|
+
name: String,
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
type Subst = std::collections::BTreeMap<String, PlumType>;
|
|
1778
|
+
type EnumGenerics = HashMap<String, Vec<String>>;
|
|
1779
|
+
|
|
1780
|
+
/// The type of `self` for a method of `recv`, with the owner's generics substituted.
|
|
1781
|
+
fn recvPlumType(recv: &str, subst: &Subst, eg: &EnumGenerics) -> PlumType {
|
|
1782
|
+
if recv == "ByteSlice" {
|
|
1783
|
+
return PlumType::TByteSlice;
|
|
1784
|
+
}
|
|
1785
|
+
match eg.get(recv) {
|
|
1786
|
+
Some(g) if !g.is_empty() => PlumType::TApp(
|
|
1787
|
+
recv.to_string(),
|
|
1788
|
+
g.iter()
|
|
1789
|
+
.map(|n| subst.get(n).cloned().unwrap_or_else(|| PlumType::TParam(n.clone())))
|
|
1790
|
+
.collect(),
|
|
1791
|
+
),
|
|
1792
|
+
_ => plum_checker::plumTypeFromName(recv),
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/// `(self type, declared param types (variadic as TVariadic), return type)`, generics substituted.
|
|
1797
|
+
fn unitDeclTypes(f: &ast::Fn, subst: &Subst, eg: &EnumGenerics) -> (Option<PlumType>, Vec<PlumType>, PlumType) {
|
|
1798
|
+
let sub = |t: PlumType| plum_checker::types::substitute(&t, subst);
|
|
1799
|
+
let self_ty = f.type_param.as_deref().map(|r| recvPlumType(r, subst, eg));
|
|
1800
|
+
let params = f.params.iter().map(|p| sub(paramPlumType(&p.ty))).collect();
|
|
1801
|
+
let ret = sub(f
|
|
1802
|
+
.returns
|
|
1803
|
+
.as_ref()
|
|
1804
|
+
.map(plum_checker::plumTypeFromAstG)
|
|
1805
|
+
.unwrap_or(PlumType::TUnit));
|
|
1806
|
+
(self_ty, params, ret)
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
fn unitFuncSig(f: &ast::Fn, subst: &Subst, eg: &EnumGenerics, g: &GcTypeRegistry) -> FuncSig {
|
|
1810
|
+
let (self_ty, params, ret) = unitDeclTypes(f, subst, eg);
|
|
1811
|
+
let mut vts: Vec<ValType> = Vec::new();
|
|
1812
|
+
if let Some(s) = &self_ty {
|
|
1813
|
+
vts.push(plumTypeToGcValtype(s, g));
|
|
1814
|
+
}
|
|
1815
|
+
for p in ¶ms {
|
|
1816
|
+
vts.push(match p {
|
|
1817
|
+
PlumType::TVariadic(e) => {
|
|
1818
|
+
let ev = plumTypeToGcValtype(e, g);
|
|
1819
|
+
gcRef(*g.variadic_array_type_idx.get(&ev).unwrap_or_else(|| {
|
|
1820
|
+
panic!("internal codegen error: no variadic array type for element {}", e)
|
|
1821
|
+
}))
|
|
1822
|
+
}
|
|
1823
|
+
other => plumTypeToGcValtype(other, g),
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
let ret_vt = if matches!(ret, PlumType::TUnit) { None } else { Some(plumTypeToGcValtype(&ret, g)) };
|
|
1827
|
+
let param_defaults = f
|
|
1828
|
+
.params
|
|
1829
|
+
.iter()
|
|
1830
|
+
.map(|p| match &p.ty {
|
|
1831
|
+
ast::ParamType::Type(_) => p.default.clone(),
|
|
1832
|
+
_ => None,
|
|
1833
|
+
})
|
|
1834
|
+
.collect();
|
|
1835
|
+
FuncSig {
|
|
1836
|
+
params: vts,
|
|
1837
|
+
ret: ret_vt,
|
|
1838
|
+
param_defaults,
|
|
1839
|
+
decl_params: params,
|
|
1840
|
+
decl_ret: ret,
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/// Collects every concrete generic-enum instance, function type and variadic element
|
|
1845
|
+
/// type mentioned by the types it is shown.
|
|
1846
|
+
struct TypeCollector<'a> {
|
|
1847
|
+
apps: Vec<PlumType>,
|
|
1848
|
+
seen: HashSet<PlumType>,
|
|
1849
|
+
funs: Vec<PlumType>,
|
|
1850
|
+
funs_seen: HashSet<PlumType>,
|
|
1851
|
+
variadics: Vec<PlumType>,
|
|
1852
|
+
variadics_seen: HashSet<PlumType>,
|
|
1853
|
+
enum_variants: &'a EnumVariants,
|
|
1854
|
+
enum_params: &'a EnumParams,
|
|
1855
|
+
eg: &'a EnumGenerics,
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
impl<'a> TypeCollector<'a> {
|
|
1859
|
+
fn visit(&mut self, t: &PlumType) {
|
|
1860
|
+
if hasFreeTy(t) {
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
match t {
|
|
1864
|
+
PlumType::TApp(n, args) => {
|
|
1865
|
+
for a in args {
|
|
1866
|
+
self.visit(a);
|
|
1867
|
+
}
|
|
1868
|
+
if !self.seen.insert(t.clone()) {
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
self.apps.push(t.clone());
|
|
1872
|
+
if isArraySpecialization(n) {
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
let infos: Vec<&EnumVariantInfo> = self
|
|
1876
|
+
.enum_variants
|
|
1877
|
+
.values()
|
|
1878
|
+
.filter(|i| i.enum_name == *n)
|
|
1879
|
+
.collect();
|
|
1880
|
+
for info in infos {
|
|
1881
|
+
for f in plum_checker::variantFields(info, t) {
|
|
1882
|
+
self.visit(&f);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if let (Some(params), Some(g)) = (self.enum_params.get(n), self.eg.get(n)) {
|
|
1886
|
+
let s: Subst = g.iter().cloned().zip(args.iter().cloned()).collect();
|
|
1887
|
+
for (_, fty) in params.clone() {
|
|
1888
|
+
self.visit(&plum_checker::types::substitute(&fty, &s));
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
PlumType::TFun(ps, r) => {
|
|
1893
|
+
for p in ps {
|
|
1894
|
+
self.visit(p);
|
|
1895
|
+
}
|
|
1896
|
+
self.visit(r);
|
|
1897
|
+
if self.funs_seen.insert(t.clone()) {
|
|
1898
|
+
self.funs.push(t.clone());
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
PlumType::TVariadic(e) => {
|
|
1902
|
+
self.visit(e);
|
|
1903
|
+
if self.variadics_seen.insert((**e).clone()) {
|
|
1904
|
+
self.variadics.push((**e).clone());
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
PlumType::TTags(m) => {
|
|
1908
|
+
for v in m.values().flatten() {
|
|
1909
|
+
self.visit(v);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
_ => {}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/// Everything a unit body needs to resolve calls, closures and expression types.
|
|
1918
|
+
struct UnitRef<'a> {
|
|
1919
|
+
idx: usize,
|
|
1920
|
+
inst: Option<usize>,
|
|
1921
|
+
subst: &'a Subst,
|
|
1922
|
+
types: &'a HashMap<usize, PlumType>,
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1755
1925
|
fn compileSourceInner(
|
|
1756
1926
|
source: &ast::Source,
|
|
1757
1927
|
extra_exports: &[(String, String)],
|
|
1758
1928
|
typed: plum_checker::TypedInfo,
|
|
1759
1929
|
) -> Result<(Vec<u8>, Vec<String>), String> {
|
|
1760
|
-
|
|
1930
|
+
let plum_checker::TypedInfo {
|
|
1931
|
+
expr_types,
|
|
1932
|
+
instantiations,
|
|
1933
|
+
inst_expr_types,
|
|
1934
|
+
call_inst,
|
|
1935
|
+
..
|
|
1936
|
+
} = typed;
|
|
1761
|
-
//
|
|
1937
|
+
// Per-instantiation expression types, split out by instantiation id.
|
|
1938
|
+
let mut inst_types: Vec<HashMap<usize, PlumType>> = vec![HashMap::new(); instantiations.len()];
|
|
1762
|
-
|
|
1939
|
+
for ((id, ptr), t) in inst_expr_types {
|
|
1940
|
+
inst_types[id].insert(ptr, t);
|
|
1941
|
+
}
|
|
1763
1942
|
// `Bool.toStr` is a checker/codegen builtin: compiled as one extra method, never added to the source.
|
|
1764
1943
|
let bool_to_str_fn = plum_checker::needsBuiltinBoolToStr(source).then(plum_checker::builtinBoolToStr);
|
|
1765
1944
|
let (global_env, methods, enum_variants, enum_params) = plum_checker::buildGlobalTables(source);
|
|
1766
1945
|
let min_required = plum_checker::buildMinRequiredArgs(source);
|
|
1946
|
+
let mut eg: EnumGenerics = HashMap::new();
|
|
1947
|
+
for item in &source.items {
|
|
1948
|
+
if let ast::Item::Enum(e) = item {
|
|
1949
|
+
eg.insert(e.name.clone(), e.generics.iter().map(|g| g.name.clone()).collect());
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// ---- units: every non-generic fn, plus one per reached instantiation ----
|
|
1954
|
+
let all_fns: Vec<&ast::Fn> = source
|
|
1955
|
+
.items
|
|
1956
|
+
.iter()
|
|
1957
|
+
.filter_map(|item| match item {
|
|
1958
|
+
ast::Item::Fn(f) if !f.is_extern => Some(f),
|
|
1959
|
+
_ => None,
|
|
1960
|
+
})
|
|
1961
|
+
.chain(bool_to_str_fn.iter())
|
|
1962
|
+
.collect();
|
|
1963
|
+
let mut units: Vec<UnitDef> = Vec::new();
|
|
1964
|
+
let mut known_fns: HashSet<String> = HashSet::new();
|
|
1965
|
+
{
|
|
1966
|
+
let cctx = checkCtxOf(&methods, &enum_variants, &enum_params, &min_required);
|
|
1967
|
+
for f in &all_fns {
|
|
1968
|
+
if f.type_param.is_none() {
|
|
1969
|
+
known_fns.insert(f.name.clone());
|
|
1970
|
+
}
|
|
1971
|
+
if plum_checker::fnGenericNames(f, &cctx).is_empty() {
|
|
1972
|
+
units.push(UnitDef {
|
|
1973
|
+
f,
|
|
1974
|
+
inst: None,
|
|
1975
|
+
subst: Subst::new(),
|
|
1976
|
+
name: fnKey(f).replace("::", "."),
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
for inst in &instantiations {
|
|
1981
|
+
let Some(f) = all_fns
|
|
1982
|
+
.iter()
|
|
1983
|
+
.find(|f| f.type_param == inst.receiver && f.name == inst.name)
|
|
1984
|
+
else {
|
|
1985
|
+
continue;
|
|
1986
|
+
};
|
|
1987
|
+
let subst: Subst = inst
|
|
1988
|
+
.generics
|
|
1989
|
+
.iter()
|
|
1990
|
+
.cloned()
|
|
1991
|
+
.zip(inst.type_args.iter().cloned())
|
|
1992
|
+
.collect();
|
|
1993
|
+
let owner_n = inst.receiver.as_ref().and_then(|r| eg.get(r)).map(|g| g.len()).unwrap_or(0);
|
|
1994
|
+
let show = |a: &[PlumType]| a.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
|
1995
|
+
let name = match &inst.receiver {
|
|
1996
|
+
Some(r) => {
|
|
1997
|
+
let mut n = format!("{}[{}].{}", r, show(&inst.type_args[..owner_n.min(inst.type_args.len())]), inst.name);
|
|
1998
|
+
if inst.type_args.len() > owner_n {
|
|
1999
|
+
n.push_str(&format!("[{}]", show(&inst.type_args[owner_n..])));
|
|
2000
|
+
}
|
|
2001
|
+
if owner_n == 0 {
|
|
2002
|
+
n = format!("{}.{}", r, inst.name);
|
|
2003
|
+
}
|
|
2004
|
+
n
|
|
2005
|
+
}
|
|
2006
|
+
None => format!("{}[{}]", inst.name, show(&inst.type_args)),
|
|
2007
|
+
};
|
|
2008
|
+
units.push(UnitDef {
|
|
2009
|
+
f,
|
|
2010
|
+
inst: Some(inst.id),
|
|
2011
|
+
subst,
|
|
2012
|
+
name,
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// ---- collect the concrete generic types the program uses ----
|
|
2018
|
+
let mut tc = TypeCollector {
|
|
2019
|
+
apps: Vec::new(),
|
|
2020
|
+
seen: HashSet::new(),
|
|
2021
|
+
funs: Vec::new(),
|
|
2022
|
+
funs_seen: HashSet::new(),
|
|
2023
|
+
variadics: Vec::new(),
|
|
2024
|
+
variadics_seen: HashSet::new(),
|
|
2025
|
+
enum_variants: &enum_variants,
|
|
2026
|
+
enum_params: &enum_params,
|
|
2027
|
+
eg: &eg,
|
|
2028
|
+
};
|
|
2029
|
+
for info in enum_variants.values() {
|
|
2030
|
+
if info.generics.is_empty() {
|
|
2031
|
+
for t in &info.field_types {
|
|
2032
|
+
tc.visit(t);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
for (name, params) in enum_params.iter() {
|
|
2037
|
+
if eg.get(name).is_none_or(|g| g.is_empty()) {
|
|
2038
|
+
for (_, t) in params {
|
|
2039
|
+
tc.visit(t);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
for t in expr_types.values() {
|
|
2044
|
+
tc.visit(t);
|
|
2045
|
+
}
|
|
2046
|
+
for m in &inst_types {
|
|
2047
|
+
for t in m.values() {
|
|
2048
|
+
tc.visit(t);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
for u in &units {
|
|
2052
|
+
let (s, ps, r) = unitDeclTypes(u.f, &u.subst, &eg);
|
|
2053
|
+
if let Some(s) = s {
|
|
2054
|
+
tc.visit(&s);
|
|
2055
|
+
}
|
|
2056
|
+
for p in &ps {
|
|
2057
|
+
tc.visit(p);
|
|
2058
|
+
}
|
|
2059
|
+
tc.visit(&r);
|
|
2060
|
+
}
|
|
2061
|
+
for inst in &instantiations {
|
|
2062
|
+
for t in &inst.type_args {
|
|
2063
|
+
tc.visit(t);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
1767
2066
|
|
|
1768
2067
|
// Intern every tag name the program's types mention, in sorted order.
|
|
1769
2068
|
{
|
|
@@ -1779,9 +2078,19 @@ fn compileSourceInner(
|
|
|
1779
2078
|
for t in methods.values() {
|
|
1780
2079
|
collectTagNamesTy(t, &mut names);
|
|
1781
2080
|
}
|
|
1782
|
-
for t in
|
|
2081
|
+
for t in expr_types.values() {
|
|
1783
2082
|
collectTagNamesTy(t, &mut names);
|
|
1784
2083
|
}
|
|
2084
|
+
for m in &inst_types {
|
|
2085
|
+
for t in m.values() {
|
|
2086
|
+
collectTagNamesTy(t, &mut names);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
for inst in &instantiations {
|
|
2090
|
+
for t in &inst.type_args {
|
|
2091
|
+
collectTagNamesTy(t, &mut names);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
1785
2094
|
CURRENT_TAG_IDS.with(|c| {
|
|
1786
2095
|
*c.borrow_mut() = names
|
|
1787
2096
|
.into_iter()
|
|
@@ -1793,7 +2102,7 @@ fn compileSourceInner(
|
|
|
1793
2102
|
|
|
1794
2103
|
let mut module = WasmModule::new();
|
|
1795
2104
|
|
|
1796
|
-
let mut gc_types = buildGcTypeRegistry(&mut module, source, &enum_variants, &enum_params);
|
|
2105
|
+
let mut gc_types = buildGcTypeRegistry(&mut module, source, &enum_variants, &enum_params, &tc.apps);
|
|
1797
2106
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1798
2107
|
|
|
1799
2108
|
CURRENT_CONSTS.with(|c| {
|
|
@@ -1805,22 +2114,12 @@ fn compileSourceInner(
|
|
|
1805
2114
|
}
|
|
1806
2115
|
});
|
|
1807
2116
|
|
|
1808
|
-
//
|
|
2117
|
+
// One immutable `array<T>` per distinct wasm value type used by a `...T` variadic.
|
|
1809
|
-
// `...T` variadic parameter anywhere in the program (almost always just one, e.g.
|
|
1810
|
-
// `Int...`) — replaces the old bump-allocated `[count][elem...]` blob (`array.len`
|
|
1811
|
-
// replaces the explicit count word). Must happen before function signatures are
|
|
1812
|
-
// registered below, since a variadic param's wasm type is this array's `ref`.
|
|
1813
2118
|
let mut variadic_elem_vts: Vec<ValType> = Vec::new();
|
|
1814
|
-
for item in &source.items {
|
|
1815
|
-
if let ast::Item::Fn(f) = item {
|
|
1816
|
-
|
|
2119
|
+
for e in &tc.variadics {
|
|
1817
|
-
if let ast::ParamType::Variadic(t) = &p.ty {
|
|
1818
|
-
|
|
2120
|
+
let vt = plumTypeToGcValtype(e, &gc_types);
|
|
1819
|
-
|
|
2121
|
+
if !variadic_elem_vts.contains(&vt) {
|
|
1820
|
-
|
|
2122
|
+
variadic_elem_vts.push(vt);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
2123
|
}
|
|
1825
2124
|
}
|
|
1826
2125
|
let variadic_array_subtypes: Vec<SubType> = variadic_elem_vts
|
|
@@ -1845,13 +2144,7 @@ fn compileSourceInner(
|
|
|
1845
2144
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1846
2145
|
}
|
|
1847
2146
|
|
|
1848
|
-
// Register every `extern fun`
|
|
2147
|
+
// Register every `extern fun` as a genuine wasm import BEFORE any function.
|
|
1849
|
-
// genuine wasm import BEFORE any function (including the `start` function
|
|
1850
|
-
// set up right below) — imports must occupy the low end of the function
|
|
1851
|
-
// index space for every later `addFunction`'s index arithmetic to stay
|
|
1852
|
-
// correct. `plum-checker` has already confirmed every extern fn has no
|
|
1853
|
-
// receiver and no body, so `f.name` alone (no `fnKey` receiver-qualification) is
|
|
1854
|
-
// always its unique key.
|
|
1855
2148
|
let mut func_ids: HashMap<String, u32> = HashMap::new();
|
|
1856
2149
|
let mut func_sigs: HashMap<String, FuncSig> = HashMap::new();
|
|
1857
2150
|
let mut extern_wrappers: Vec<(String, u32, Vec<ValType>, Option<ValType>)> = Vec::new();
|
|
@@ -1860,38 +2153,28 @@ fn compileSourceInner(
|
|
|
1860
2153
|
if !f.is_extern {
|
|
1861
2154
|
continue;
|
|
1862
2155
|
}
|
|
2156
|
+
known_fns.insert(f.name.clone());
|
|
2157
|
+
let sig = unitFuncSig(f, &Subst::new(), &eg, &gc_types);
|
|
1863
|
-
let param_types =
|
|
2158
|
+
let param_types = sig.params.clone();
|
|
1864
|
-
let ret =
|
|
2159
|
+
let ret = sig.ret;
|
|
1865
|
-
// `Int` in an extern signature is narrowed to wasm i32 at the host
|
|
2160
|
+
// `Int` in an extern signature is narrowed to wasm i32 at the host boundary.
|
|
1866
|
-
// boundary (the browser host never sees a BigInt). The import gets the
|
|
1867
|
-
// narrowed type; a same-named wrapper with the plain i64 signature (added
|
|
1868
|
-
// below, once all imports exist) range-checks/wraps args and sign-extends
|
|
1869
|
-
// the result, so every Plum call site is unchanged.
|
|
1870
2161
|
let narrow = |v: &ValType| if *v == ValType::I64 { ValType::I32 } else { *v };
|
|
1871
2162
|
let imp_params: Vec<ValType> = param_types.iter().map(narrow).collect();
|
|
1872
2163
|
let imp_results: Vec<ValType> = ret.iter().map(narrow).collect();
|
|
1873
2164
|
let type_idx = module.addType(&imp_params, &imp_results);
|
|
1874
2165
|
let func_idx = module.addImport("plum", &f.name, type_idx);
|
|
1875
|
-
extern_wrappers.push((f.name.clone(), func_idx, param_types
|
|
2166
|
+
extern_wrappers.push((f.name.clone(), func_idx, param_types, ret));
|
|
1876
2167
|
func_sigs.insert(
|
|
1877
2168
|
f.name.clone(),
|
|
1878
2169
|
FuncSig {
|
|
1879
|
-
params: param_types,
|
|
1880
|
-
ret,
|
|
1881
2170
|
param_defaults: Vec::new(),
|
|
1882
|
-
decl_params: f.params.iter().map(|p| paramPlumType(&p.ty)).collect(),
|
|
1883
|
-
decl_ret: f
|
|
1884
|
-
.returns
|
|
1885
|
-
|
|
2171
|
+
..sig
|
|
1886
|
-
.map(plum_checker::plumTypeFromAstG)
|
|
1887
|
-
.unwrap_or(PlumType::TUnit),
|
|
1888
2172
|
},
|
|
1889
2173
|
);
|
|
1890
2174
|
}
|
|
1891
2175
|
}
|
|
1892
2176
|
|
|
1893
|
-
// `plum::crash(Str)`: same wasm type as `printLn`; imported only when the
|
|
1894
|
-
//
|
|
2177
|
+
// `plum::crash(Str)`
|
|
1895
2178
|
{
|
|
1896
2179
|
let uses_crash = sourceUsesCrash(&format!("{:?}", source.items));
|
|
1897
2180
|
if uses_crash {
|
|
@@ -1902,9 +2185,7 @@ fn compileSourceInner(
|
|
|
1902
2185
|
}
|
|
1903
2186
|
}
|
|
1904
2187
|
|
|
1905
|
-
// Extern `Int` narrowing wrappers
|
|
2188
|
+
// Extern `Int` narrowing wrappers.
|
|
1906
|
-
// check is unconditional (one wrap/extend/compare per Int arg; the CLI has no
|
|
1907
|
-
// separate debug mode): an `Int` that doesn't fit in i32 traps.
|
|
1908
2189
|
for (name, imp_idx, params, ret) in &extern_wrappers {
|
|
1909
2190
|
let mut b = vec![0u8];
|
|
1910
2191
|
for (i, vt) in params.iter().enumerate() {
|
|
@@ -1933,54 +2214,39 @@ fn compileSourceInner(
|
|
|
1933
2214
|
let results_vec: Vec<ValType> = ret.iter().cloned().collect();
|
|
1934
2215
|
let t = module.addType(params, &results_vec);
|
|
1935
2216
|
let w = module.addFunction(t, &b);
|
|
2217
|
+
module.nameFunction(w, name);
|
|
1936
2218
|
func_ids.insert(name.clone(), w);
|
|
1937
2219
|
}
|
|
1938
2220
|
|
|
1939
|
-
// Pre-allocate one instance of every payload-free variant
|
|
2221
|
+
// Pre-allocate one instance of every payload-free variant of every enum instance.
|
|
1940
|
-
// as a global, populated once by a `start` function rather than reconstructed on
|
|
1941
|
-
// every reference — see this migration plan's Decision 2. `struct.new` isn't
|
|
1942
|
-
// allowed inside a global's own const-expr initializer (confirmed empirically in
|
|
1943
|
-
// Task 1), so each global starts `ref.null` and a `start` function fills it in
|
|
1944
|
-
// before any export is callable. The zero-capture "trampoline" closures
|
|
1945
|
-
// registered below (once closures are discovered) append to this SAME start
|
|
1946
|
-
// function, so its body isn't finalized/patched into the module until then.
|
|
1947
|
-
let mut singleton_globals: HashMap<String, u32> = HashMap::new();
|
|
2222
|
+
let mut singleton_globals: HashMap<(PlumType, String), u32> = HashMap::new();
|
|
1948
2223
|
let mut start_body = Vec::new();
|
|
2224
|
+
for ty in gc_types.instances.clone() {
|
|
2225
|
+
let en = plum_checker::nominalName(&ty).unwrap().to_string();
|
|
2226
|
+
let mut vnames: Vec<(&String, &EnumVariantInfo)> =
|
|
2227
|
+
enum_variants.iter().filter(|(_, i)| i.enum_name == en).collect();
|
|
2228
|
+
vnames.sort_by_key(|(_, i)| i.tag);
|
|
1949
|
-
|
|
2229
|
+
for (name, info) in vnames {
|
|
1950
|
-
|
|
2230
|
+
if !info.field_types.is_empty() {
|
|
1951
|
-
|
|
2231
|
+
continue;
|
|
1952
|
-
|
|
2232
|
+
}
|
|
2233
|
+
let variant_idx = gc_types.variantIdx(&ty, name).unwrap_or_else(|| {
|
|
1953
|
-
|
|
2234
|
+
panic!("internal codegen error: payload-free variant '{}' missing from GC type registry", name)
|
|
2235
|
+
});
|
|
1954
|
-
|
|
2236
|
+
let mut init = Vec::new();
|
|
1955
|
-
// `buildGcTypeRegistry`'s `isArraySpecialization` handling), not a
|
|
1956
|
-
|
|
2237
|
+
Instruction::RefNull(HeapType::Concrete(variant_idx)).encode(&mut init);
|
|
1957
|
-
|
|
2238
|
+
let global_idx = module.addGlobal(gcRef(variant_idx), true, &init);
|
|
1958
|
-
|
|
2239
|
+
singleton_globals.insert((ty.clone(), name.clone()), global_idx);
|
|
2240
|
+
Instruction::I32Const(typeIdOf(&gc_types, name)).encode(&mut start_body);
|
|
1959
|
-
|
|
2241
|
+
Instruction::StructNew(variant_idx).encode(&mut start_body);
|
|
1960
|
-
|
|
2242
|
+
Instruction::GlobalSet(global_idx).encode(&mut start_body);
|
|
1961
2243
|
}
|
|
1962
|
-
let variant_idx = *gc_types.variant_type_idx.get(name).unwrap_or_else(|| {
|
|
1963
|
-
panic!(
|
|
1964
|
-
"internal codegen error: payload-free variant '{}' missing from GC type registry",
|
|
1965
|
-
name
|
|
1966
|
-
)
|
|
1967
|
-
});
|
|
1968
|
-
let mut init = Vec::new();
|
|
1969
|
-
Instruction::RefNull(HeapType::Concrete(variant_idx)).encode(&mut init);
|
|
1970
|
-
let global_idx = module.addGlobal(gcRef(variant_idx), true, &init);
|
|
1971
|
-
singleton_globals.insert(name.clone(), global_idx);
|
|
1972
|
-
|
|
1973
|
-
// A payload-free variant is exactly `{typeId}`.
|
|
1974
|
-
Instruction::I32Const(typeIdOf(&gc_types, name)).encode(&mut start_body);
|
|
1975
|
-
Instruction::StructNew(variant_idx).encode(&mut start_body);
|
|
1976
|
-
Instruction::GlobalSet(global_idx).encode(&mut start_body);
|
|
1977
2244
|
}
|
|
1978
2245
|
let start_type_idx = module.addType(&[], &[]);
|
|
1979
2246
|
let start_func_idx = module.addFunction(start_type_idx, &[]);
|
|
2247
|
+
module.nameFunction(start_func_idx, "start");
|
|
1980
2248
|
module.setStartFunction(start_func_idx);
|
|
1981
2249
|
|
|
1982
|
-
// Box helpers (`i64 -> $BoxI64`, ...) and the `Str` builder push the header
|
|
1983
|
-
// typeId themselves, so callers only supply the payload.
|
|
1984
2250
|
gc_types.box_i64_fn =
|
|
1985
2251
|
registerBoxHelper(&mut module, ValType::I64, TYPE_ID_BOX_I64, gc_types.box_i64_idx);
|
|
1986
2252
|
gc_types.box_f64_fn =
|
|
@@ -1989,6 +2255,14 @@ fn compileSourceInner(
|
|
|
1989
2255
|
registerBoxHelper(&mut module, ValType::I32, TYPE_ID_BOX_I32, gc_types.box_i32_idx);
|
|
1990
2256
|
gc_types.box_bool_fn =
|
|
1991
2257
|
registerBoxHelper(&mut module, ValType::I32, TYPE_ID_BOX_BOOL, gc_types.box_bool_idx);
|
|
2258
|
+
for (f, n) in [
|
|
2259
|
+
(gc_types.box_i64_fn, "box.i64"),
|
|
2260
|
+
(gc_types.box_f64_fn, "box.f64"),
|
|
2261
|
+
(gc_types.box_i32_fn, "box.i32"),
|
|
2262
|
+
(gc_types.box_bool_fn, "box.bool"),
|
|
2263
|
+
] {
|
|
2264
|
+
module.nameFunction(f, n);
|
|
2265
|
+
}
|
|
1992
2266
|
if let (Some(&buffer_idx), Some(&str_idx)) = (
|
|
1993
2267
|
gc_types.class_type_idx.get("Buffer"),
|
|
1994
2268
|
gc_types.class_type_idx.get("Str"),
|
|
@@ -2009,59 +2283,32 @@ fn compileSourceInner(
|
|
|
2009
2283
|
}
|
|
2010
2284
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
2011
2285
|
|
|
2012
|
-
// Closure ABI
|
|
2286
|
+
// Closure ABI: one `call_indirect` type per Plum function signature,
|
|
2013
|
-
// `(env
|
|
2287
|
+
// `(env: anyref, params...) -> result`, with every value in its own representation.
|
|
2014
2288
|
let mut closure_call_types: HashMap<ClosureSigKey, u32> = HashMap::new();
|
|
2015
|
-
for
|
|
2289
|
+
for t in &tc.funs {
|
|
2016
|
-
let params = vec![ValType::Ref(RefType::ANYREF); arity + 1];
|
|
2017
|
-
let t = module.addType(¶ms, &[ValType::Ref(RefType::ANYREF)]);
|
|
2018
|
-
|
|
2290
|
+
if let PlumType::TFun(ps, r) = t {
|
|
2291
|
+
getClosureCallType(&mut module, &mut closure_call_types, &closureSigKey(ps, r, &gc_types));
|
|
2292
|
+
}
|
|
2019
2293
|
}
|
|
2020
2294
|
|
|
2021
|
-
// Register every
|
|
2295
|
+
// Register every unit's wasm function up front.
|
|
2022
|
-
|
|
2296
|
+
let mut inst_funcs: HashMap<usize, (u32, FuncSig)> = HashMap::new();
|
|
2023
|
-
for
|
|
2297
|
+
for u in &units {
|
|
2024
|
-
.
|
|
2298
|
+
let sig = unitFuncSig(u.f, &u.subst, &eg, &gc_types);
|
|
2025
|
-
.
|
|
2299
|
+
let results_vec: Vec<ValType> = sig.ret.into_iter().collect();
|
|
2300
|
+
let type_idx = module.addType(&sig.params, &results_vec);
|
|
2026
|
-
|
|
2301
|
+
let func_idx = module.addFunction(type_idx, &[]);
|
|
2027
|
-
|
|
2302
|
+
module.nameFunction(func_idx, &u.name);
|
|
2303
|
+
match u.inst {
|
|
2028
|
-
|
|
2304
|
+
Some(id) => {
|
|
2305
|
+
inst_funcs.insert(id, (func_idx, sig));
|
|
2029
|
-
|
|
2306
|
+
}
|
|
2030
|
-
.chain(bool_to_str_fn.iter())
|
|
2031
|
-
{
|
|
2032
|
-
{
|
|
2033
|
-
if f.is_extern {
|
|
2034
|
-
|
|
2307
|
+
None => {
|
|
2308
|
+
let key = fnKey(u.f);
|
|
2309
|
+
func_ids.insert(key.clone(), func_idx);
|
|
2310
|
+
func_sigs.insert(key, sig);
|
|
2035
2311
|
}
|
|
2036
|
-
let param_types = fnWasmParamTypes(f, &gc_types);
|
|
2037
|
-
let ret = retTypeToWasm(f.returns.as_ref());
|
|
2038
|
-
let results_vec: Vec<ValType> = ret.into_iter().collect();
|
|
2039
|
-
let type_idx = module.addType(¶m_types, &results_vec);
|
|
2040
|
-
let func_idx = module.addFunction(type_idx, &[]);
|
|
2041
|
-
let key = fnKey(f);
|
|
2042
|
-
func_ids.insert(key.clone(), func_idx);
|
|
2043
|
-
let param_defaults: Vec<Option<ast::Expr>> = f
|
|
2044
|
-
.params
|
|
2045
|
-
.iter()
|
|
2046
|
-
.map(|p| match &p.ty {
|
|
2047
|
-
ast::ParamType::Type(_) => p.default.clone(),
|
|
2048
|
-
_ => None,
|
|
2049
|
-
})
|
|
2050
|
-
.collect();
|
|
2051
|
-
func_sigs.insert(
|
|
2052
|
-
key,
|
|
2053
|
-
FuncSig {
|
|
2054
|
-
params: param_types,
|
|
2055
|
-
ret,
|
|
2056
|
-
param_defaults,
|
|
2057
|
-
decl_params: f.params.iter().map(|p| paramPlumType(&p.ty)).collect(),
|
|
2058
|
-
decl_ret: f
|
|
2059
|
-
.returns
|
|
2060
|
-
.as_ref()
|
|
2061
|
-
.map(plum_checker::plumTypeFromAstG)
|
|
2062
|
-
.unwrap_or(PlumType::TUnit),
|
|
2063
|
-
},
|
|
2064
|
-
);
|
|
2065
2312
|
}
|
|
2066
2313
|
}
|
|
2067
2314
|
|
|
@@ -2076,44 +2323,30 @@ fn compileSourceInner(
|
|
|
2076
2323
|
);
|
|
2077
2324
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
2078
2325
|
|
|
2079
|
-
let fns: Vec<&ast::Fn> = source
|
|
2080
|
-
.items
|
|
2081
|
-
.iter()
|
|
2082
|
-
.filter_map(|item| match item {
|
|
2083
|
-
ast::Item::Fn(f) if !f.is_extern => Some(f),
|
|
2084
|
-
_ => None,
|
|
2085
|
-
})
|
|
2086
|
-
.chain(bool_to_str_fn.iter())
|
|
2087
|
-
.collect();
|
|
2088
|
-
|
|
2089
|
-
// ---- Discovery pre-pass:
|
|
2326
|
+
// ---- Discovery pre-pass: every closure literal in every unit body. ----
|
|
2090
|
-
// (Runs on the original, erased source.) Registers each closure as its own wasm function + table
|
|
2091
|
-
// element and records the free variables it must capture.
|
|
2092
|
-
// Only genuine free functions (`type_param.is_none()`) — a bare `FnCall`
|
|
2093
|
-
// (`each(...)`) can only ever resolve to a free function, never a method
|
|
2094
|
-
// (those always dispatch through `Attribute`/`AttrKind::Method`, keyed by
|
|
2095
|
-
// `(receiver, name)` instead — see that arm's own comment below). Keying
|
|
2096
|
-
// this by bare name across free functions AND methods would let a
|
|
2097
|
-
// method silently SHADOW a same-named free function here
|
|
2098
|
-
// (e.g. a user's own top-level `each` losing to `List.each` once
|
|
2099
|
-
// `List` happens to be in scope) — same-named methods on unrelated
|
|
2100
|
-
// classes are already ambiguous by bare name, so methods have no business
|
|
2101
|
-
// being in a bare-name lookup table at all.
|
|
2102
|
-
let fn_decls: HashMap<String, &ast::Fn> =
|
|
2327
|
+
let fn_decls: HashMap<String, &ast::Fn> = units
|
|
2103
2328
|
.iter()
|
|
2104
|
-
.filter(|
|
|
2329
|
+
.filter(|u| u.inst.is_none() && u.f.type_param.is_none())
|
|
2105
|
-
.map(|
|
|
2330
|
+
.map(|u| (u.f.name.clone(), u.f))
|
|
2106
2331
|
.collect();
|
|
2332
|
+
let unit_types = |u: &UnitDef| -> &HashMap<usize, PlumType> {
|
|
2333
|
+
match u.inst {
|
|
2334
|
+
Some(id) => &inst_types[id],
|
|
2335
|
+
None => &expr_types,
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2107
|
-
let mut raw_closures: Vec<RawClosure> = Vec::new();
|
|
2338
|
+
let mut raw_closures: Vec<(usize, RawClosure)> = Vec::new();
|
|
2108
|
-
let mut named_fn_refs: HashSet<String> = HashSet::new();
|
|
2339
|
+
let mut named_fn_refs: HashSet<(String, PlumType)> = HashSet::new();
|
|
2340
|
+
for (ui, u) in units.iter().enumerate() {
|
|
2109
|
-
|
|
2341
|
+
let f = u.f;
|
|
2110
2342
|
let mut locals: HashSet<String> = HashSet::new();
|
|
2111
2343
|
for p in &f.params {
|
|
2112
2344
|
locals.insert(p.name.clone());
|
|
2113
2345
|
}
|
|
2114
2346
|
let mut walker = ClosureWalker {
|
|
2115
|
-
types:
|
|
2347
|
+
types: unit_types(u),
|
|
2116
2348
|
fn_decls: &fn_decls,
|
|
2349
|
+
func_sigs: &func_sigs,
|
|
2117
2350
|
found: Vec::new(),
|
|
2118
2351
|
locals,
|
|
2119
2352
|
named_fn_refs: HashSet::new(),
|
|
@@ -2121,18 +2354,12 @@ fn compileSourceInner(
|
|
|
2121
2354
|
match &f.body {
|
|
2122
2355
|
ast::FnBody::Block(block) => walker.walkBlock(block),
|
|
2123
2356
|
ast::FnBody::Expr(e) => walker.walkExpr(e),
|
|
2124
|
-
// `fns` excludes every `extern fun` (no body to discover closures in).
|
|
2125
|
-
ast::FnBody::Extern => unreachable!("extern fns are excluded from
|
|
2357
|
+
ast::FnBody::Extern => unreachable!("extern fns are excluded from units"),
|
|
2126
2358
|
}
|
|
2127
|
-
raw_closures.extend(walker.found);
|
|
2359
|
+
raw_closures.extend(walker.found.into_iter().map(|c| (ui, c)));
|
|
2128
2360
|
named_fn_refs.extend(walker.named_fn_refs);
|
|
2129
2361
|
}
|
|
2130
2362
|
|
|
2131
|
-
// Register every closure literal's own env struct type, plus the ONE shared
|
|
2132
|
-
// closure-value struct `{table_idx: i32, env: anyref}` every closure (and
|
|
2133
|
-
// trampoline, below) wraps its env in — declared together as a single `rec`
|
|
2134
|
-
// group now that every closure's free-variable types are known. The shared
|
|
2135
|
-
// struct is always slot 0; closure `i`'s env type is slot `i + 1`.
|
|
2136
2363
|
let mut closure_subtypes: Vec<SubType> = vec![SubType {
|
|
2137
2364
|
is_final: true,
|
|
2138
2365
|
supertype_idx: None,
|
|
@@ -2153,7 +2380,7 @@ fn compileSourceInner(
|
|
|
2153
2380
|
shared: false,
|
|
2154
2381
|
},
|
|
2155
2382
|
}];
|
|
2156
|
-
for rc in &raw_closures {
|
|
2383
|
+
for (_, rc) in &raw_closures {
|
|
2157
2384
|
let field_types: Vec<FieldType> = rc
|
|
2158
2385
|
.free_vars
|
|
2159
2386
|
.iter()
|
|
@@ -2177,17 +2404,20 @@ fn compileSourceInner(
|
|
|
2177
2404
|
gc_types.closure_type_idx = closure_type_indices[0];
|
|
2178
2405
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
2179
2406
|
|
|
2180
|
-
let mut closures: HashMap<usize, ClosureInfo> = HashMap::new();
|
|
2407
|
+
let mut closures: HashMap<(usize, usize), ClosureInfo> = HashMap::new();
|
|
2181
|
-
let mut closure_asts: HashMap<usize, &ast::Closure> = HashMap::new();
|
|
2408
|
+
let mut closure_asts: HashMap<(usize, usize), &ast::Closure> = HashMap::new();
|
|
2182
|
-
for (i, rc) in raw_closures.iter().enumerate() {
|
|
2409
|
+
for (i, (ui, rc)) in raw_closures.iter().enumerate() {
|
|
2183
|
-
let arity = rc.closure.params.len();
|
|
2184
|
-
let
|
|
2410
|
+
let key = closureSigKey(
|
|
2411
|
+
&rc.param_ptypes,
|
|
2412
|
+
&rc.ret_ptype,
|
|
2185
|
-
|
|
2413
|
+
&gc_types,
|
|
2414
|
+
);
|
|
2186
|
-
|
|
2415
|
+
let type_idx = getClosureCallType(&mut module, &mut closure_call_types, &key);
|
|
2187
2416
|
let func_idx = module.addFunction(type_idx, &[]);
|
|
2417
|
+
module.nameFunction(func_idx, &format!("{}.closure", units[*ui].name));
|
|
2188
2418
|
let table_idx = module.addTableElement(func_idx);
|
|
2189
2419
|
closures.insert(
|
|
2190
|
-
rc.ptr,
|
|
2420
|
+
(*ui, rc.ptr),
|
|
2191
2421
|
ClosureInfo {
|
|
2192
2422
|
func_idx,
|
|
2193
2423
|
table_idx,
|
|
@@ -2197,57 +2427,35 @@ fn compileSourceInner(
|
|
|
2197
2427
|
free_vars: rc.free_vars.clone(),
|
|
2198
2428
|
},
|
|
2199
2429
|
);
|
|
2200
|
-
closure_asts.insert(rc.ptr, rc.closure);
|
|
2430
|
+
closure_asts.insert((*ui, rc.ptr), rc.closure);
|
|
2201
|
-
}
|
|
2431
|
+
}
|
|
2202
|
-
|
|
2432
|
+
|
|
2203
|
-
//
|
|
2433
|
+
// Zero-capture trampolines for top-level functions used as values.
|
|
2204
|
-
// referenced as a bare value (e.g. `each(double)`): a real wasm function with the
|
|
2205
|
-
// erased closure ABI `(env, anyref...) -> anyref` that ignores its env, unboxes
|
|
2206
|
-
// each argument to the real function's parameter representation, forwards to the
|
|
2207
|
-
// real function and boxes its result, plus a funcref-table entry for it. Since
|
|
2208
|
-
// it never captures anything, its `{table_idx, env=null}` closure struct is a
|
|
2209
|
-
// compile-time constant — like every payload-free enum variant, built once by
|
|
2210
|
-
// the shared `start` function and stored in its own global.
|
|
2211
|
-
let mut named_fn_values: HashMap<String, u32> = HashMap::new();
|
|
2434
|
+
let mut named_fn_values: HashMap<(String, PlumType), u32> = HashMap::new();
|
|
2212
|
-
for name in &named_fn_refs {
|
|
2435
|
+
for (name, fty) in &named_fn_refs {
|
|
2213
2436
|
let sig = func_sigs
|
|
2214
2437
|
.get(name)
|
|
2215
2438
|
.expect("named fn ref must be a registered top-level function");
|
|
2216
2439
|
let real_func_idx = *func_ids
|
|
2217
2440
|
.get(name)
|
|
2218
2441
|
.expect("named fn ref must be a registered top-level function");
|
|
2219
|
-
let
|
|
2442
|
+
let PlumType::TFun(ps, r) = fty else { continue };
|
|
2220
|
-
let
|
|
2443
|
+
let key = closureSigKey(ps, r, &gc_types);
|
|
2221
|
-
.get(&arity)
|
|
2222
|
-
|
|
2444
|
+
let type_idx = getClosureCallType(&mut module, &mut closure_call_types, &key);
|
|
2223
|
-
|
|
2224
|
-
let mut tbody =
|
|
2445
|
+
let mut tbody = vec![0u8];
|
|
2225
|
-
tbody.push(0u8); // no locals beyond the params already in the signature
|
|
2226
|
-
for
|
|
2446
|
+
for i in 0..sig.params.len() {
|
|
2227
|
-
Instruction::LocalGet((i + 1) as u32).encode(&mut tbody);
|
|
2447
|
+
Instruction::LocalGet((i + 1) as u32).encode(&mut tbody);
|
|
2228
|
-
match sig.decl_params.get(i) {
|
|
2229
|
-
Some(PlumType::TBool) => {
|
|
2230
|
-
coerceTy(&anyTy(), &PlumType::TBool, &mut tbody, &gc_types)
|
|
2231
|
-
}
|
|
2232
|
-
_ => coerceVt(ValType::Ref(RefType::ANYREF), *vt, &mut tbody, &gc_types),
|
|
2233
|
-
}
|
|
2234
2448
|
}
|
|
2235
2449
|
Instruction::Call(real_func_idx).encode(&mut tbody);
|
|
2236
|
-
match sig.ret {
|
|
2237
|
-
Some(_) if matches!(sig.decl_ret, PlumType::TBool) => {
|
|
2238
|
-
coerceTy(&PlumType::TBool, &anyTy(), &mut tbody, &gc_types)
|
|
2239
|
-
}
|
|
2240
|
-
Some(rvt) => coerceVt(rvt, ValType::Ref(RefType::ANYREF), &mut tbody, &gc_types),
|
|
2241
|
-
None => Instruction::RefNull(HeapType::ANY).encode(&mut tbody),
|
|
2242
|
-
}
|
|
2243
2450
|
Instruction::End.encode(&mut tbody);
|
|
2244
2451
|
let func_idx = module.addFunction(type_idx, &tbody);
|
|
2452
|
+
module.nameFunction(func_idx, &format!("{}.trampoline", name));
|
|
2245
2453
|
let table_idx = module.addTableElement(func_idx);
|
|
2246
2454
|
|
|
2247
2455
|
let mut init = Vec::new();
|
|
2248
2456
|
Instruction::RefNull(HeapType::Concrete(gc_types.closure_type_idx)).encode(&mut init);
|
|
2249
2457
|
let global_idx = module.addGlobal(gcRef(gc_types.closure_type_idx), true, &init);
|
|
2250
|
-
named_fn_values.insert(name.clone(), global_idx);
|
|
2458
|
+
named_fn_values.insert((name.clone(), fty.clone()), global_idx);
|
|
2251
2459
|
|
|
2252
2460
|
Instruction::I32Const(table_idx as i32).encode(&mut start_body);
|
|
2253
2461
|
Instruction::RefNull(HeapType::ANY).encode(&mut start_body);
|
|
@@ -2255,19 +2463,12 @@ fn compileSourceInner(
|
|
|
2255
2463
|
Instruction::GlobalSet(global_idx).encode(&mut start_body);
|
|
2256
2464
|
}
|
|
2257
2465
|
|
|
2258
|
-
// The `start` function is now complete — every payload-free variant singleton
|
|
2259
|
-
// (above) and every trampoline closure (above) has appended its own init code.
|
|
2260
|
-
// It declares no locals of its own, but a function body's raw bytes must still
|
|
2261
|
-
// start with the (empty) locals-declaration vector — a single `0x00` — before
|
|
2262
|
-
// the instruction stream, exactly like every other compiled function body below.
|
|
2263
2466
|
Instruction::End.encode(&mut start_body);
|
|
2264
2467
|
let mut final_start_body = vec![0u8];
|
|
2265
2468
|
final_start_body.extend(start_body);
|
|
2266
2469
|
let start_slot = (start_func_idx - module.func_import_count) as usize;
|
|
2267
2470
|
module.functions[start_slot].1 = final_start_body;
|
|
2268
2471
|
|
|
2269
|
-
// Runtime helpers backing string interpolation (`"{expr}"`): always registered
|
|
2270
|
-
// (unconditionally, for simplicity) since they're cheap and self-contained.
|
|
2271
2472
|
let string_concat_func = registerStringConcatHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2272
2473
|
let string_eq_func = registerStringEqHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2273
2474
|
let dyn_eq_func = registerDynEqHelper(&mut module, &gc_types, string_eq_func);
|
|
@@ -2276,6 +2477,10 @@ fn compileSourceInner(
|
|
|
2276
2477
|
let ctx = CompileCtx {
|
|
2277
2478
|
func_ids,
|
|
2278
2479
|
func_sigs,
|
|
2480
|
+
inst_funcs,
|
|
2481
|
+
call_inst,
|
|
2482
|
+
known_fns,
|
|
2483
|
+
eg,
|
|
2279
2484
|
methods,
|
|
2280
2485
|
enum_variants,
|
|
2281
2486
|
enum_params,
|
|
@@ -2284,7 +2489,6 @@ fn compileSourceInner(
|
|
|
2284
2489
|
closures,
|
|
2285
2490
|
closure_asts,
|
|
2286
2491
|
closure_call_types,
|
|
2287
|
-
types,
|
|
2288
2492
|
named_fn_values,
|
|
2289
2493
|
string_concat_func,
|
|
2290
2494
|
string_eq_func,
|
|
@@ -2298,35 +2502,45 @@ fn compileSourceInner(
|
|
|
2298
2502
|
passive_segments: Vec::new(),
|
|
2299
2503
|
};
|
|
2300
2504
|
|
|
2301
|
-
let mut compiled_bodies: Vec<(
|
|
2505
|
+
let mut compiled_bodies: Vec<(u32, Vec<u8>)> = Vec::new();
|
|
2506
|
+
for (ui, u) in units.iter().enumerate() {
|
|
2507
|
+
let f = u.f;
|
|
2508
|
+
let uref = UnitRef {
|
|
2509
|
+
idx: ui,
|
|
2510
|
+
inst: u.inst,
|
|
2302
|
-
|
|
2511
|
+
subst: &u.subst,
|
|
2512
|
+
types: unit_types(u),
|
|
2513
|
+
};
|
|
2303
|
-
let body = match compileIntrinsicFnBody(f) {
|
|
2514
|
+
let body = match compileIntrinsicFnBody(f, &u.subst) {
|
|
2304
2515
|
Some(body) => body,
|
|
2305
|
-
None => compileFnBody(f, &ctx, &mut state).map_err(|e| {
|
|
2516
|
+
None => compileFnBody(f, &ctx, &mut state, &uref).map_err(|e| format!("{e} (in '{}')", u.name))?,
|
|
2306
|
-
format!("{e} (in '{}{}')", f.type_param.as_ref().map(|r| format!("{r}.")).unwrap_or_default(), f.name)
|
|
2307
|
-
|
|
2517
|
+
};
|
|
2518
|
+
let idx = match u.inst {
|
|
2519
|
+
Some(id) => ctx.inst_funcs[&id].0,
|
|
2520
|
+
None => ctx.func_ids[&fnKey(f)],
|
|
2308
2521
|
};
|
|
2309
|
-
compiled_bodies.push((
|
|
2522
|
+
compiled_bodies.push((idx, body));
|
|
2310
2523
|
}
|
|
2311
|
-
|
|
2312
|
-
// Patch compiled bodies back into their pre-registered function slots.
|
|
2313
|
-
for (
|
|
2524
|
+
for (idx, body) in &compiled_bodies {
|
|
2314
|
-
let idx = *ctx
|
|
2315
|
-
.func_ids
|
|
2316
|
-
.get(name)
|
|
2317
|
-
.expect("function was registered in the first pass");
|
|
2318
|
-
let slot = (idx - module.func_import_count) as usize;
|
|
2525
|
+
let slot = (*idx - module.func_import_count) as usize;
|
|
2319
2526
|
module.functions[slot].1 = body.clone();
|
|
2320
2527
|
}
|
|
2321
2528
|
|
|
2322
2529
|
// Compile each closure literal's own body into its reserved function slot.
|
|
2323
2530
|
let mut closure_bodies: Vec<(u32, Vec<u8>)> = Vec::new();
|
|
2324
|
-
for (ptr, info) in &ctx.closures {
|
|
2531
|
+
for ((ui, ptr), info) in &ctx.closures {
|
|
2325
2532
|
let cl = ctx
|
|
2326
2533
|
.closure_asts
|
|
2327
|
-
.get(ptr)
|
|
2534
|
+
.get(&(*ui, *ptr))
|
|
2328
2535
|
.expect("every registered closure has its AST recorded");
|
|
2536
|
+
let u = &units[*ui];
|
|
2537
|
+
let uref = UnitRef {
|
|
2538
|
+
idx: *ui,
|
|
2539
|
+
inst: u.inst,
|
|
2540
|
+
subst: &u.subst,
|
|
2541
|
+
types: unit_types(u),
|
|
2542
|
+
};
|
|
2329
|
-
let body = compileClosureBody(cl, info, &ctx, &mut state)?;
|
|
2543
|
+
let body = compileClosureBody(cl, info, &ctx, &mut state, &uref)?;
|
|
2330
2544
|
closure_bodies.push((info.func_idx, body));
|
|
2331
2545
|
}
|
|
2332
2546
|
for (func_idx, body) in &closure_bodies {
|
|
@@ -2334,10 +2548,6 @@ fn compileSourceInner(
|
|
|
2334
2548
|
module.functions[slot].1 = body.clone();
|
|
2335
2549
|
}
|
|
2336
2550
|
|
|
2337
|
-
// Flush every staged string literal into the module in the SAME order they were
|
|
2338
|
-
// staged — `compileStaticString` already baked each one's index (its position in
|
|
2339
|
-
// `state.passive_segments` at staging time) into an `array.new_data` instruction,
|
|
2340
|
-
// so that order must be preserved exactly for those indices to still be correct.
|
|
2341
2551
|
for bytes in &state.passive_segments {
|
|
2342
2552
|
module.addPassiveDataSegment(bytes);
|
|
2343
2553
|
}
|
|
@@ -2360,10 +2570,31 @@ fn compileSourceInner(
|
|
|
2360
2570
|
.filter(|k| k.as_str() != CRASH_FN_KEY)
|
|
2361
2571
|
.cloned()
|
|
2362
2572
|
.collect();
|
|
2573
|
+
fn_keys.extend(units.iter().filter(|u| u.inst.is_some()).map(|u| u.name.clone()));
|
|
2363
2574
|
fn_keys.sort();
|
|
2364
2575
|
Ok((module.finish(), fn_keys))
|
|
2365
2576
|
}
|
|
2366
2577
|
|
|
2578
|
+
/// `(param value types, result value type)` of a closure of Plum type `fn(ps) -> r`.
|
|
2579
|
+
fn closureSigKey(ps: &[PlumType], r: &PlumType, g: &GcTypeRegistry) -> ClosureSigKey {
|
|
2580
|
+
(
|
|
2581
|
+
ps.iter().map(|p| plumTypeToGcValtype(p, g)).collect(),
|
|
2582
|
+
if matches!(r, PlumType::TUnit) { None } else { Some(plumTypeToGcValtype(r, g)) },
|
|
2583
|
+
)
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
fn getClosureCallType(module: &mut WasmModule, m: &mut HashMap<ClosureSigKey, u32>, key: &ClosureSigKey) -> u32 {
|
|
2587
|
+
if let Some(&t) = m.get(key) {
|
|
2588
|
+
return t;
|
|
2589
|
+
}
|
|
2590
|
+
let mut params = vec![ValType::Ref(RefType::ANYREF)];
|
|
2591
|
+
params.extend(key.0.iter().copied());
|
|
2592
|
+
let results: Vec<ValType> = key.1.into_iter().collect();
|
|
2593
|
+
let t = module.addType(¶ms, &results);
|
|
2594
|
+
m.insert(key.clone(), t);
|
|
2595
|
+
t
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2367
2598
|
/// `(payload) -> ref $Box*`: allocates a box with its header typeId set.
|
|
2368
2599
|
fn registerBoxHelper(module: &mut WasmModule, vt: ValType, type_id: i32, box_idx: u32) -> u32 {
|
|
2369
2600
|
let mut b = vec![0u8];
|
|
@@ -2956,10 +3187,10 @@ fn registerIntToStringHelper(module: &mut WasmModule, byte_array_type_idx: u32)
|
|
|
2956
3187
|
/// registers their real signature and validates call sites), then intercepted
|
|
2957
3188
|
/// here — by `fnKey` — instead of compiling their `todo` body to `unreachable`.
|
|
2958
3189
|
/// Returns `None` for any other function, meaning "compile it normally."
|
|
2959
|
-
fn compileIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
|
|
3190
|
+
fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
|
|
2960
3191
|
if let Some(recv) = f.type_param.as_deref() {
|
|
2961
3192
|
if isArraySpecialization(recv) {
|
|
2962
|
-
return compileArrayIntrinsicFnBody(f);
|
|
3193
|
+
return compileArrayIntrinsicFnBody(f, subst);
|
|
2963
3194
|
}
|
|
2964
3195
|
}
|
|
2965
3196
|
let byte_array_type_idx = withGcTypes(|r| r.byte_array_type_idx);
|
|
@@ -3129,8 +3360,10 @@ fn unwrapStrToBytes(body: &mut Vec<u8>) {
|
|
|
3129
3360
|
/// `ref.cast` back from the raw stored `anyref` down to `T`'s own concrete
|
|
3130
3361
|
/// type; `set`/`init` need no such cast the other way, since any concrete ref
|
|
3131
3362
|
/// is already a valid subtype wherever `anyref` is expected.
|
|
3132
|
-
fn compileArrayIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
|
|
3363
|
+
fn compileArrayIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
|
|
3364
|
+
let elem = subst.get("T")?.clone();
|
|
3365
|
+
let array_ty = PlumType::TApp("Array".to_string(), vec![elem]);
|
|
3133
|
-
let array_type_idx = withGcTypes(|r| r.
|
|
3366
|
+
let array_type_idx = withGcTypes(|r| r.superIdx(&array_ty))?;
|
|
3134
3367
|
match f.name.as_str() {
|
|
3135
3368
|
// init() -> Array[T] — always starts empty; see `push` for how a
|
|
3136
3369
|
// specific length is actually built up.
|
|
@@ -3244,15 +3477,6 @@ fn compileArrayIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
|
|
|
3244
3477
|
Instruction::LocalGet(1).encode(&mut body); // i
|
|
3245
3478
|
Instruction::I32WrapI64.encode(&mut body);
|
|
3246
3479
|
Instruction::ArrayGet(array_type_idx).encode(&mut body);
|
|
3247
|
-
// Narrow the raw `anyref` element back to `T`'s own concrete type —
|
|
3248
|
-
// skipped when `T` itself resolves to a bare `anyref` (nothing to cast to).
|
|
3249
|
-
if let Some(ValType::Ref(RefType {
|
|
3250
|
-
heap_type: HeapType::Concrete(idx),
|
|
3251
|
-
..
|
|
3252
|
-
})) = f.returns.as_ref().and_then(|t| astTypeToWasm(&t.name))
|
|
3253
|
-
{
|
|
3254
|
-
Instruction::RefCastNullable(HeapType::Concrete(idx)).encode(&mut body);
|
|
3255
|
-
}
|
|
3256
3480
|
Instruction::End.encode(&mut body);
|
|
3257
3481
|
Some(body)
|
|
3258
3482
|
}
|
|
@@ -3280,6 +3504,15 @@ fn compileArrayIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
|
|
|
3280
3504
|
|
|
3281
3505
|
/// The `PlumType` of a declared parameter, including `fn(...)`-typed params as `TFun`;
|
|
3282
3506
|
/// a single-letter generic name is a `TParam`.
|
|
3507
|
+
/// The enum type (`TNamed`/`TApp`) a variant-constructing expression produces.
|
|
3508
|
+
fn enumTypeOfExpr(expr: &ast::Expr, info: &EnumVariantInfo, ctx: &LocalCtx) -> PlumType {
|
|
3509
|
+
let t = inferLocalType(expr, ctx);
|
|
3510
|
+
match &t {
|
|
3511
|
+
PlumType::TNamed(n) | PlumType::TApp(n, _) if *n == info.enum_name => t,
|
|
3512
|
+
_ => PlumType::TNamed(info.enum_name.clone()),
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3283
3516
|
fn paramPlumType(pt: &ast::ParamType) -> PlumType {
|
|
3284
3517
|
match pt {
|
|
3285
3518
|
ast::ParamType::Type(t) => plum_checker::plumTypeFromAstG(t),
|
|
@@ -3307,6 +3540,22 @@ struct RawClosure<'a> {
|
|
|
3307
3540
|
free_vars: Vec<(String, PlumType)>,
|
|
3308
3541
|
}
|
|
3309
3542
|
|
|
3543
|
+
/// The `fn(...)` type a bare reference to top-level fn `name` is used at.
|
|
3544
|
+
fn namedRefType(
|
|
3545
|
+
name: &str,
|
|
3546
|
+
expr: &ast::Expr,
|
|
3547
|
+
types: &HashMap<usize, PlumType>,
|
|
3548
|
+
sigs: &HashMap<String, FuncSig>,
|
|
3549
|
+
) -> PlumType {
|
|
3550
|
+
match types.get(&(expr as *const ast::Expr as usize)) {
|
|
3551
|
+
Some(t @ PlumType::TFun(..)) if !hasFreeTy(t) => t.clone(),
|
|
3552
|
+
_ => match sigs.get(name) {
|
|
3553
|
+
Some(s) => PlumType::TFun(s.decl_params.clone(), Box::new(s.decl_ret.clone())),
|
|
3554
|
+
None => PlumType::TFun(vec![], Box::new(PlumType::TUnit)),
|
|
3555
|
+
},
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3310
3559
|
fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
3311
3560
|
match arg {
|
|
3312
3561
|
ast::Arg::Positional(e) => e,
|
|
@@ -3322,6 +3571,7 @@ fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
|
3322
3571
|
struct ClosureWalker<'a, 't> {
|
|
3323
3572
|
types: &'t HashMap<usize, PlumType>,
|
|
3324
3573
|
fn_decls: &'a HashMap<String, &'a ast::Fn>,
|
|
3574
|
+
func_sigs: &'t HashMap<String, FuncSig>,
|
|
3325
3575
|
found: Vec<RawClosure<'a>>,
|
|
3326
3576
|
/// Local names bound in the function currently being walked (params, assign
|
|
3327
3577
|
/// targets, for-loop vars) — used to tell a local variable reference apart from a
|
|
@@ -3332,7 +3582,7 @@ struct ClosureWalker<'a, 't> {
|
|
|
3332
3582
|
/// `Expr::FnCall` and never reaches here). Each one needs a zero-capture
|
|
3333
3583
|
/// "trampoline" closure so it can be used wherever a `fn(...)`-typed value is
|
|
3334
3584
|
/// expected.
|
|
3335
|
-
named_fn_refs: HashSet<String>,
|
|
3585
|
+
named_fn_refs: HashSet<(String, PlumType)>,
|
|
3336
3586
|
}
|
|
3337
3587
|
|
|
3338
3588
|
impl<'a, 't> ClosureWalker<'a, 't> {
|
|
@@ -3456,7 +3706,8 @@ impl<'a, 't> ClosureWalker<'a, 't> {
|
|
|
3456
3706
|
if !self.locals.contains(name) {
|
|
3457
3707
|
if let Some(f) = self.fn_decls.get(name) {
|
|
3458
3708
|
if f.type_param.is_none() {
|
|
3709
|
+
let ty = namedRefType(name, expr, self.types, self.func_sigs);
|
|
3459
|
-
self.named_fn_refs.insert(name.clone());
|
|
3710
|
+
self.named_fn_refs.insert((name.clone(), ty));
|
|
3460
3711
|
}
|
|
3461
3712
|
}
|
|
3462
3713
|
}
|
|
@@ -3765,7 +4016,7 @@ struct Collector<'a> {
|
|
|
3765
4016
|
/// Slot number (the `u32` values in `nested_class_scratch`) -> the variant name
|
|
3766
4017
|
/// it narrows to, so its scratch local can be declared with that variant's exact
|
|
3767
4018
|
/// concrete ref type instead of a uniform placeholder type.
|
|
3768
|
-
nested_class_scratch_types: Vec<String>,
|
|
4019
|
+
nested_class_scratch_types: Vec<(PlumType, String)>,
|
|
3769
4020
|
next_nested_class_slot: u32,
|
|
3770
4021
|
/// `For` stmt identity (pointer address) -> a slot number; each slot reserves 2
|
|
3771
4022
|
/// consecutive `i32` scratch locals for variadic iteration (`for v in nums`):
|
|
@@ -3821,7 +4072,7 @@ impl<'a> Collector<'a> {
|
|
|
3821
4072
|
let slot = self.next_nested_class_slot;
|
|
3822
4073
|
self.next_nested_class_slot += 1;
|
|
3823
4074
|
self.nested_class_scratch.insert(key, slot);
|
|
3824
|
-
self.nested_class_scratch_types.push(name.clone());
|
|
4075
|
+
self.nested_class_scratch_types.push((ty.clone(), name.clone()));
|
|
3825
4076
|
if let Some(info) = self.enum_variants.get(name) {
|
|
3826
4077
|
// The payload types as instantiated by the matched value's type
|
|
3827
4078
|
// arguments (`Ok(v)` against `Try[Int, E]` binds `v: Int`).
|
|
@@ -3953,11 +4204,10 @@ impl<'a> Collector<'a> {
|
|
|
3953
4204
|
ast::Expr::Try(inner) => {
|
|
3954
4205
|
let key = expr as *const ast::Expr as usize;
|
|
3955
4206
|
let ty = self.ty(inner);
|
|
3956
|
-
let name = plum_checker::nominalName(&ty).unwrap_or("").to_string();
|
|
3957
4207
|
let slot = self.next_nested_class_slot;
|
|
3958
4208
|
self.next_nested_class_slot += 1;
|
|
3959
4209
|
self.nested_class_scratch.insert(key, slot);
|
|
3960
|
-
self.nested_class_scratch_types.push(
|
|
4210
|
+
self.nested_class_scratch_types.push((ty, String::new()));
|
|
3961
4211
|
self.walkExpr(inner);
|
|
3962
4212
|
}
|
|
3963
4213
|
ast::Expr::Unary(u) => self.walkExpr(&u.operand),
|
|
@@ -3974,11 +4224,10 @@ impl<'a> Collector<'a> {
|
|
|
3974
4224
|
ast::Expr::Elvis(e) => {
|
|
3975
4225
|
let key = expr as *const ast::Expr as usize;
|
|
3976
4226
|
let ty = self.ty(&e.left);
|
|
3977
|
-
let name = plum_checker::nominalName(&ty).unwrap_or("").to_string();
|
|
3978
4227
|
let slot = self.next_nested_class_slot;
|
|
3979
4228
|
self.next_nested_class_slot += 1;
|
|
3980
4229
|
self.nested_class_scratch.insert(key, slot);
|
|
3981
|
-
self.nested_class_scratch_types.push(
|
|
4230
|
+
self.nested_class_scratch_types.push((ty, String::new()));
|
|
3982
4231
|
self.walkExpr(&e.left);
|
|
3983
4232
|
self.walkExpr(&e.right);
|
|
3984
4233
|
}
|
|
@@ -4029,26 +4278,26 @@ fn compileFnBody(
|
|
|
4029
4278
|
f: &ast::Fn,
|
|
4030
4279
|
ctx: &CompileCtx,
|
|
4031
4280
|
state: &mut ModuleState,
|
|
4281
|
+
unit: &UnitRef,
|
|
4032
4282
|
) -> Result<Vec<u8>, String> {
|
|
4033
4283
|
let mut body = Vec::new();
|
|
4034
4284
|
|
|
4035
4285
|
// Fallback type env (only consulted for synthesized nodes the checker never
|
|
4036
|
-
// saw): globals, `self`, and the params under their
|
|
4286
|
+
// saw): globals, `self`, and the params under their (instantiated) declared types.
|
|
4037
4287
|
let mut base_env = ctx.global_env.clone();
|
|
4038
4288
|
let mut decl_types: HashMap<String, PlumType> = HashMap::new();
|
|
4289
|
+
let (self_decl, param_decls, ret_decl) = unitDeclTypes(f, unit.subst, &ctx.eg);
|
|
4039
|
-
if let Some(
|
|
4290
|
+
if let Some(self_ty) = self_decl {
|
|
4040
|
-
let self_ty = plum_checker::plumTypeFromName(recv);
|
|
4041
4291
|
base_env.insert("self".to_string(), TypeScheme::mono(self_ty.clone()));
|
|
4042
4292
|
decl_types.insert("self".to_string(), self_ty);
|
|
4043
4293
|
}
|
|
4044
|
-
for p in &f.params {
|
|
4045
|
-
|
|
4294
|
+
for (p, ty) in f.params.iter().zip(param_decls.iter()) {
|
|
4046
4295
|
base_env.insert(p.name.clone(), TypeScheme::mono(ty.clone()));
|
|
4047
|
-
decl_types.insert(p.name.clone(), ty);
|
|
4296
|
+
decl_types.insert(p.name.clone(), ty.clone());
|
|
4048
4297
|
}
|
|
4049
4298
|
|
|
4050
4299
|
let mut collector = Collector {
|
|
4051
|
-
types:
|
|
4300
|
+
types: unit.types,
|
|
4052
4301
|
enum_variants: &ctx.enum_variants,
|
|
4053
4302
|
named: Vec::new(),
|
|
4054
4303
|
named_set: Default::default(),
|
|
@@ -4104,8 +4353,8 @@ fn compileFnBody(
|
|
|
4104
4353
|
// placeholder) — `struct.get` on a constructor-pattern match requires the local
|
|
4105
4354
|
// holding the narrowed (`ref.cast`) value to be statically typed as that exact
|
|
4106
4355
|
// variant, and different slots very likely narrow to different variants.
|
|
4107
|
-
for vname in &collector.nested_class_scratch_types {
|
|
4356
|
+
for (sty, vname) in &collector.nested_class_scratch_types {
|
|
4108
|
-
let variant_idx = withGcTypes(|r|
|
|
4357
|
+
let variant_idx = withGcTypes(|r| scratchTypeIdx(r, sty, vname));
|
|
4109
4358
|
groups.push(gcRef(variant_idx));
|
|
4110
4359
|
idx += 1;
|
|
4111
4360
|
}
|
|
@@ -4144,7 +4393,12 @@ fn compileFnBody(
|
|
|
4144
4393
|
variadic_for_scratch: collector.variadic_for_scratch,
|
|
4145
4394
|
func_ids: &ctx.func_ids,
|
|
4146
4395
|
func_sigs: &ctx.func_sigs,
|
|
4396
|
+
inst_funcs: &ctx.inst_funcs,
|
|
4397
|
+
call_inst: &ctx.call_inst,
|
|
4398
|
+
known_fns: &ctx.known_fns,
|
|
4399
|
+
unit_idx: unit.idx,
|
|
4400
|
+
unit_inst: unit.inst,
|
|
4147
|
-
types:
|
|
4401
|
+
types: unit.types,
|
|
4148
4402
|
local_decl_types: decl_types,
|
|
4149
4403
|
closures: &ctx.closures,
|
|
4150
4404
|
closure_call_types: &ctx.closure_call_types,
|
|
@@ -4162,12 +4416,16 @@ fn compileFnBody(
|
|
|
4162
4416
|
type_env: RefCell::new(base_env),
|
|
4163
4417
|
loop_extra_depth: Cell::new(0),
|
|
4164
4418
|
in_closure: false,
|
|
4165
|
-
ret_type: f.returns.as_ref().map(
|
|
4419
|
+
ret_type: f.returns.as_ref().map(|_| ret_decl.clone()),
|
|
4166
4420
|
match_subject_types: RefCell::new(HashMap::new()),
|
|
4167
4421
|
subject_ptypes: RefCell::new(HashMap::new()),
|
|
4168
4422
|
};
|
|
4169
4423
|
|
|
4424
|
+
let result_vt = if f.returns.is_none() || matches!(ret_decl, PlumType::TUnit) {
|
|
4425
|
+
None
|
|
4426
|
+
} else {
|
|
4170
|
-
|
|
4427
|
+
Some(plumTypeToValtype(&ret_decl))
|
|
4428
|
+
};
|
|
4171
4429
|
|
|
4172
4430
|
match &f.body {
|
|
4173
4431
|
ast::FnBody::Expr(e) => {
|
|
@@ -4425,18 +4683,27 @@ fn compileStmt(
|
|
|
4425
4683
|
field_name, class_name
|
|
4426
4684
|
)
|
|
4427
4685
|
})?;
|
|
4428
|
-
let super_type_idx =
|
|
4686
|
+
let super_type_idx = ctx
|
|
4429
4687
|
.gc_types
|
|
4430
|
-
.enum_super_type_idx
|
|
4431
|
-
.
|
|
4688
|
+
.superIdx(&obj_ty)
|
|
4432
4689
|
.ok_or_else(|| {
|
|
4433
4690
|
format!(
|
|
4434
4691
|
"codegen: enum '{}' missing from the GC type registry",
|
|
4435
4692
|
class_name
|
|
4436
4693
|
)
|
|
4437
4694
|
})?;
|
|
4695
|
+
let sub_s: Subst = ctx
|
|
4696
|
+
.enum_variants
|
|
4697
|
+
.values()
|
|
4698
|
+
.find(|i| i.enum_name == class_name)
|
|
4699
|
+
.map(|i| match &obj_ty {
|
|
4700
|
+
PlumType::TApp(_, a) => i.generics.iter().cloned().zip(a.iter().cloned()).collect(),
|
|
4701
|
+
_ => Subst::new(),
|
|
4702
|
+
})
|
|
4703
|
+
.unwrap_or_default();
|
|
4704
|
+
let fty = plum_checker::types::substitute(¶ms[field_idx].1, &sub_s);
|
|
4438
4705
|
compileExpr(object, body, ctx, state)?;
|
|
4439
|
-
compileExprInto(value, &
|
|
4706
|
+
compileExprInto(value, &fty, body, ctx, state)?;
|
|
4440
4707
|
Instruction::StructSet {
|
|
4441
4708
|
struct_type_index: super_type_idx,
|
|
4442
4709
|
field_index: fld(field_idx),
|
|
@@ -4462,12 +4729,13 @@ fn compileStmt(
|
|
|
4462
4729
|
.iter()
|
|
4463
4730
|
.position(|n| n == field_name)
|
|
4464
4731
|
.expect("just found by this field name");
|
|
4465
|
-
let variant_type_idx =
|
|
4732
|
+
let variant_type_idx = ctx.gc_types.variantIdx(&obj_ty, variant_name)
|
|
4466
|
-
.ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", variant_name))?;
|
|
4733
|
+
.ok_or_else(|| format!("codegen: variant '{}' of {} missing from the GC type registry", variant_name, obj_ty))?;
|
|
4734
|
+
let fty = plum_checker::variantFields(info, &obj_ty)[field_idx].clone();
|
|
4467
4735
|
compileExpr(object, body, ctx, state)?;
|
|
4468
4736
|
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))
|
|
4469
4737
|
.encode(body);
|
|
4470
|
-
compileExprInto(value, &
|
|
4738
|
+
compileExprInto(value, &fty, body, ctx, state)?;
|
|
4471
4739
|
Instruction::StructSet {
|
|
4472
4740
|
struct_type_index: variant_type_idx,
|
|
4473
4741
|
field_index: fld(field_idx),
|
|
@@ -4484,23 +4752,9 @@ fn compileStmt(
|
|
|
4484
4752
|
Some(rt) => compileExprInto(e, rt, body, ctx, state)?,
|
|
4485
4753
|
None => compileExpr(e, body, ctx, state)?,
|
|
4486
4754
|
}
|
|
4487
|
-
if ctx.in_closure {
|
|
4488
|
-
// Closure ABI: the result leaves as an `anyref`.
|
|
4489
|
-
let rt_vt = ctx.ret_type.as_ref().map(plumTypeToValtype);
|
|
4490
|
-
if let Some(vt) = rt_vt {
|
|
4491
|
-
if let Some(rt @ PlumType::TBool) = ctx.ret_type.as_ref() {
|
|
4492
|
-
coerceTy(rt, &anyTy(), body, ctx.gc_types);
|
|
4493
|
-
} else {
|
|
4494
|
-
coerceVt(vt, ValType::Ref(RefType::ANYREF), body, ctx.gc_types);
|
|
4495
|
-
}
|
|
4496
|
-
}
|
|
4497
|
-
}
|
|
4498
4755
|
Instruction::Return.encode(body);
|
|
4499
4756
|
}
|
|
4500
4757
|
ast::Stmt::Return(None) => {
|
|
4501
|
-
if ctx.in_closure {
|
|
4502
|
-
Instruction::RefNull(HeapType::ANY).encode(body);
|
|
4503
|
-
}
|
|
4504
4758
|
Instruction::Return.encode(body);
|
|
4505
4759
|
}
|
|
4506
4760
|
ast::Stmt::If(if_) => {
|
|
@@ -4686,13 +4940,15 @@ fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
|
|
|
4686
4940
|
{
|
|
4687
4941
|
return !closureCallReturnsUnit(&call.name, ctx);
|
|
4688
4942
|
}
|
|
4689
|
-
ctx.func_sigs
|
|
4690
|
-
|
|
4943
|
+
resolveCallee(ctx, expr, false, &call.name)
|
|
4691
|
-
.map(|s| s.ret.is_some())
|
|
4944
|
+
.map(|(_, s)| s.ret.is_some())
|
|
4692
4945
|
.unwrap_or(true)
|
|
4693
4946
|
}
|
|
4694
4947
|
ast::Expr::Attribute(attr) => match &attr.attr {
|
|
4695
4948
|
ast::AttrKind::Method(call) => {
|
|
4949
|
+
if let Some((_, s)) = resolveCallee(ctx, expr, false, "") {
|
|
4950
|
+
return s.ret.is_some();
|
|
4951
|
+
}
|
|
4696
4952
|
let obj_ty = inferLocalType(&attr.object, ctx);
|
|
4697
4953
|
// A call on an erased or trait-typed receiver dispatches through a
|
|
4698
4954
|
// trait method whose declared return type decides.
|
|
@@ -5318,10 +5574,16 @@ fn compileVariantEqArm(
|
|
|
5318
5574
|
name
|
|
5319
5575
|
));
|
|
5320
5576
|
}
|
|
5577
|
+
let sty = ctx
|
|
5578
|
+
.subject_ptypes
|
|
5579
|
+
.borrow()
|
|
5580
|
+
.get(&scratch_local)
|
|
5581
|
+
.cloned()
|
|
5582
|
+
.unwrap_or_else(|| PlumType::TVar("_".to_string()));
|
|
5321
|
-
let variant_idx =
|
|
5583
|
+
let variant_idx = ctx.gc_types.variantIdx(&sty, name).ok_or_else(|| {
|
|
5322
5584
|
format!(
|
|
5323
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
5585
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
5324
|
-
name
|
|
5586
|
+
name, sty
|
|
5325
5587
|
)
|
|
5326
5588
|
})?;
|
|
5327
5589
|
// A single ref.test against the variant's exact concrete type replaces the old
|
|
@@ -5391,18 +5653,21 @@ fn compileVariantConstructorArm(
|
|
|
5391
5653
|
fields.len()
|
|
5392
5654
|
));
|
|
5393
5655
|
}
|
|
5656
|
+
let sty = ctx
|
|
5657
|
+
.subject_ptypes
|
|
5658
|
+
.borrow()
|
|
5659
|
+
.get(&scratch_local)
|
|
5660
|
+
.cloned()
|
|
5661
|
+
.unwrap_or_else(|| PlumType::TVar("_".to_string()));
|
|
5394
|
-
let variant_idx =
|
|
5662
|
+
let variant_idx = ctx.gc_types.variantIdx(&sty, name).ok_or_else(|| {
|
|
5395
5663
|
format!(
|
|
5396
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
5664
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
5397
|
-
name
|
|
5665
|
+
name, sty
|
|
5398
5666
|
)
|
|
5399
5667
|
})?;
|
|
5400
|
-
let field_types = info.field_types.clone();
|
|
5401
5668
|
// Payload types instantiated by the subject's type arguments.
|
|
5402
|
-
let inst_types = match ctx.subject_ptypes.borrow().get(&scratch_local) {
|
|
5403
|
-
|
|
5669
|
+
let inst_types = instFields(info, &sty);
|
|
5404
|
-
|
|
5670
|
+
let field_types = inst_types.clone();
|
|
5405
|
-
};
|
|
5406
5671
|
|
|
5407
5672
|
// This pattern's own narrowly-typed scratch local (declared with variant's exact
|
|
5408
5673
|
// concrete ref type — see `nested_class_scratch_types`), distinct from
|
|
@@ -5498,7 +5763,7 @@ fn compileFieldPatterns(
|
|
|
5498
5763
|
let pat = &fields[fpos];
|
|
5499
5764
|
// The field's DECLARED type fixes its stored representation (an erased `T` slot
|
|
5500
5765
|
// is a `ref null $Obj`); its INSTANTIATED type is what a binding/pattern sees.
|
|
5501
|
-
let decl_ty = &
|
|
5766
|
+
let decl_ty = &inst_types[fpos];
|
|
5502
5767
|
let field_ty = &inst_types[fpos];
|
|
5503
5768
|
let decl_vt = plumTypeToValtype(decl_ty);
|
|
5504
5769
|
let inst_vt = plumTypeToValtype(field_ty);
|
|
@@ -5663,8 +5928,8 @@ fn compileFieldPatterns(
|
|
|
5663
5928
|
n
|
|
5664
5929
|
));
|
|
5665
5930
|
}
|
|
5666
|
-
let variant_idx =
|
|
5931
|
+
let variant_idx = ctx.gc_types.variantIdx(field_ty, n).ok_or_else(|| {
|
|
5667
|
-
format!("codegen: variant '{}' missing from the GC type registry", n)
|
|
5932
|
+
format!("codegen: variant '{}' of {} missing from the GC type registry", n, field_ty)
|
|
5668
5933
|
})?;
|
|
5669
5934
|
Instruction::LocalGet(container_local).encode(body);
|
|
5670
5935
|
Instruction::StructGet {
|
|
@@ -5774,10 +6039,10 @@ fn compileFieldPatterns(
|
|
|
5774
6039
|
inner_fields.len()
|
|
5775
6040
|
));
|
|
5776
6041
|
}
|
|
5777
|
-
let inner_variant_idx =
|
|
6042
|
+
let inner_variant_idx = ctx.gc_types.variantIdx(field_ty, name).ok_or_else(|| {
|
|
5778
6043
|
format!(
|
|
5779
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
6044
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
5780
|
-
name
|
|
6045
|
+
name, field_ty
|
|
5781
6046
|
)
|
|
5782
6047
|
})?;
|
|
5783
6048
|
let key = pat as *const ast::CasePattern as usize;
|
|
@@ -5786,8 +6051,8 @@ fn compileFieldPatterns(
|
|
|
5786
6051
|
.to_string()
|
|
5787
6052
|
})?;
|
|
5788
6053
|
let nested_local = ctx.nested_class_scratch_base + slot;
|
|
5789
|
-
let inner_field_types = info.field_types.clone();
|
|
5790
6054
|
let inner_inst_types = instFields(info, field_ty);
|
|
6055
|
+
let inner_field_types = inner_inst_types.clone();
|
|
5791
6056
|
|
|
5792
6057
|
Instruction::LocalGet(container_local).encode(body);
|
|
5793
6058
|
Instruction::StructGet {
|
|
@@ -5891,9 +6156,10 @@ fn compileExpr(
|
|
|
5891
6156
|
// value (e.g. `each(double)`) — push its zero-capture trampoline
|
|
5892
6157
|
// closure's (pre-built by the `start` function) global.
|
|
5893
6158
|
None => {
|
|
6159
|
+
let fty = namedRefType(name, expr, ctx.types, ctx.func_sigs);
|
|
5894
6160
|
let global_idx = *ctx
|
|
5895
6161
|
.named_fn_values
|
|
5896
|
-
.get(name.
|
|
6162
|
+
.get(&(name.clone(), fty))
|
|
5897
6163
|
.ok_or_else(|| format!("undeclared variable '{}'", name))?;
|
|
5898
6164
|
Instruction::GlobalGet(global_idx).encode(body);
|
|
5899
6165
|
}
|
|
@@ -5935,34 +6201,16 @@ fn compileExpr(
|
|
|
5935
6201
|
enum_name
|
|
5936
6202
|
)
|
|
5937
6203
|
})?;
|
|
5938
|
-
let success_idx =
|
|
5939
|
-
*ctx.gc_types
|
|
5940
|
-
.variant_type_idx
|
|
5941
|
-
|
|
6204
|
+
let success_idx = ctx.gc_types.variantIdx(&inner_ty, success_name).ok_or_else(|| {
|
|
5942
|
-
.ok_or_else(|| {
|
|
5943
|
-
format!(
|
|
5944
|
-
|
|
6205
|
+
format!("codegen: variant '{}' of {} missing from the GC type registry", success_name, inner_ty)
|
|
5945
|
-
success_name
|
|
5946
|
-
)
|
|
5947
|
-
|
|
6206
|
+
})?;
|
|
5948
|
-
let failure_idx =
|
|
5949
|
-
*ctx.gc_types
|
|
5950
|
-
.variant_type_idx
|
|
5951
|
-
|
|
6207
|
+
let failure_idx = ctx.gc_types.variantIdx(&inner_ty, failure_name).ok_or_else(|| {
|
|
5952
|
-
.ok_or_else(|| {
|
|
5953
|
-
format!(
|
|
5954
|
-
|
|
6208
|
+
format!("codegen: variant '{}' of {} missing from the GC type registry", failure_name, inner_ty)
|
|
5955
|
-
failure_name
|
|
5956
|
-
)
|
|
5957
|
-
|
|
6209
|
+
})?;
|
|
5958
6210
|
// The payload slot is declared at its generic type (`T` -> `$Obj`); the
|
|
5959
6211
|
// value this expression yields is at its instantiated type.
|
|
5960
|
-
let raw_success_ty = success_info
|
|
5961
|
-
.field_types
|
|
5962
|
-
.first()
|
|
5963
|
-
.cloned()
|
|
5964
|
-
.unwrap_or(PlumType::TUnit);
|
|
5965
6212
|
let success_ty = inferLocalType(expr, ctx);
|
|
6213
|
+
let raw_success_ty = success_ty.clone();
|
|
5966
6214
|
let success_vt = plumTypeToValtype(&success_ty);
|
|
5967
6215
|
|
|
5968
6216
|
let key = expr as *const ast::Expr as usize;
|
|
@@ -5979,22 +6227,20 @@ fn compileExpr(
|
|
|
5979
6227
|
// Failure branch: take the inner `Err` payload and build the OUTER
|
|
5980
6228
|
// function's `Err` variant from it (the Ok types may differ).
|
|
5981
6229
|
let outer_err = match ctx.ret_type.as_ref().and_then(plum_checker::nominalName) {
|
|
5982
|
-
Some(outer_enum) if outer_enum != enum_name => {
|
|
5983
|
-
|
|
6230
|
+
Some(outer_enum) => plum_checker::tryOperatorVariants(outer_enum, &cctx)
|
|
5984
|
-
|
|
6231
|
+
.map(|(_, _, fname, _)| fname),
|
|
5985
|
-
}
|
|
5986
6232
|
_ => None,
|
|
5987
6233
|
};
|
|
5988
6234
|
let mut rewrap: Option<(u32, i32)> = None;
|
|
5989
6235
|
if let Some(outer_err_name) = outer_err {
|
|
6236
|
+
let outer_ty = ctx.ret_type.clone().unwrap_or(PlumType::TUnit);
|
|
5990
|
-
let outer_idx =
|
|
6237
|
+
let outer_idx = ctx
|
|
5991
6238
|
.gc_types
|
|
5992
|
-
.variant_type_idx
|
|
5993
|
-
.
|
|
6239
|
+
.variantIdx(&outer_ty, outer_err_name)
|
|
5994
6240
|
.ok_or_else(|| {
|
|
5995
6241
|
format!(
|
|
5996
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
6242
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
5997
|
-
outer_err_name
|
|
6243
|
+
outer_err_name, outer_ty
|
|
5998
6244
|
)
|
|
5999
6245
|
})?;
|
|
6000
6246
|
if outer_idx != failure_idx {
|
|
@@ -6057,32 +6303,14 @@ fn compileExpr(
|
|
|
6057
6303
|
enum_name
|
|
6058
6304
|
)
|
|
6059
6305
|
})?;
|
|
6060
|
-
let success_idx =
|
|
6061
|
-
*ctx.gc_types
|
|
6062
|
-
.variant_type_idx
|
|
6063
|
-
|
|
6306
|
+
let success_idx = ctx.gc_types.variantIdx(&left_ty, success_name).ok_or_else(|| {
|
|
6064
|
-
.ok_or_else(|| {
|
|
6065
|
-
format!(
|
|
6066
|
-
|
|
6307
|
+
format!("codegen: variant '{}' of {} missing from the GC type registry", success_name, left_ty)
|
|
6067
|
-
success_name
|
|
6068
|
-
)
|
|
6069
|
-
|
|
6308
|
+
})?;
|
|
6070
|
-
let failure_idx =
|
|
6071
|
-
*ctx.gc_types
|
|
6072
|
-
.variant_type_idx
|
|
6073
|
-
|
|
6309
|
+
let failure_idx = ctx.gc_types.variantIdx(&left_ty, failure_name).ok_or_else(|| {
|
|
6074
|
-
.ok_or_else(|| {
|
|
6075
|
-
format!(
|
|
6076
|
-
|
|
6310
|
+
format!("codegen: variant '{}' of {} missing from the GC type registry", failure_name, left_ty)
|
|
6077
|
-
failure_name
|
|
6078
|
-
)
|
|
6079
|
-
|
|
6311
|
+
})?;
|
|
6080
|
-
let raw_success_ty = success_info
|
|
6081
|
-
.field_types
|
|
6082
|
-
.first()
|
|
6083
|
-
.cloned()
|
|
6084
|
-
.unwrap_or(PlumType::TUnit);
|
|
6085
6312
|
let success_ty = inferLocalType(expr, ctx);
|
|
6313
|
+
let raw_success_ty = success_ty.clone();
|
|
6086
6314
|
let success_vt = plumTypeToValtype(&success_ty);
|
|
6087
6315
|
|
|
6088
6316
|
let key = expr as *const ast::Expr as usize;
|
|
@@ -6235,6 +6463,17 @@ fn compileExpr(
|
|
|
6235
6463
|
}
|
|
6236
6464
|
// `TVar`/`TUnit` share `Int`'s `i64` wasm representation (see
|
|
6237
6465
|
// `plumTypeToValtype`) — an unresolved generic defaults the same way.
|
|
6466
|
+
PlumType::TByte => {
|
|
6467
|
+
match c.op {
|
|
6468
|
+
ast::CmpOp::Lt => Instruction::I32LtU,
|
|
6469
|
+
ast::CmpOp::Lte => Instruction::I32LeU,
|
|
6470
|
+
ast::CmpOp::Eq => Instruction::I32Eq,
|
|
6471
|
+
ast::CmpOp::Neq | ast::CmpOp::NotEq2 => Instruction::I32Ne,
|
|
6472
|
+
ast::CmpOp::Gte => Instruction::I32GeU,
|
|
6473
|
+
ast::CmpOp::Gt => Instruction::I32GtU,
|
|
6474
|
+
}
|
|
6475
|
+
.encode(body);
|
|
6476
|
+
}
|
|
6238
6477
|
PlumType::TInt | PlumType::TVar(_) | PlumType::TUnit => {
|
|
6239
6478
|
match c.op {
|
|
6240
6479
|
ast::CmpOp::Lt => Instruction::I64LtS,
|
|
@@ -6336,7 +6575,7 @@ fn compileExpr(
|
|
|
6336
6575
|
.is_some_and(|info| info.field_types == [ty.clone()])
|
|
6337
6576
|
});
|
|
6338
6577
|
if (call.name == "Int" || call.name == "Float" || call.name == "Byte")
|
|
6339
|
-
&& !ctx.
|
|
6578
|
+
&& !ctx.known_fns.contains(&call.name)
|
|
6340
6579
|
&& !is_same_type_variant_wrap
|
|
6341
6580
|
&& single_arg_ty.is_some()
|
|
6342
6581
|
{
|
|
@@ -6362,7 +6601,7 @@ fn compileExpr(
|
|
|
6362
6601
|
compileClosureCall(call, expr, body, ctx, state)?;
|
|
6363
6602
|
// `Bad(x)` / `Bad()` — an uppercase name the checker typed as a tag
|
|
6364
6603
|
// union is a tag literal, even when a declared variant shares its name.
|
|
6365
|
-
} else if !ctx.
|
|
6604
|
+
} else if !ctx.known_fns.contains(&call.name)
|
|
6366
6605
|
&& call.args.len() <= 1
|
|
6367
6606
|
&& call
|
|
6368
6607
|
.name
|
|
@@ -6404,6 +6643,7 @@ fn compileExpr(
|
|
|
6404
6643
|
&init_call,
|
|
6405
6644
|
&PlumType::TNamed(call.name.clone()),
|
|
6406
6645
|
expr,
|
|
6646
|
+
false,
|
|
6407
6647
|
body,
|
|
6408
6648
|
ctx,
|
|
6409
6649
|
state,
|
|
@@ -6411,16 +6651,10 @@ fn compileExpr(
|
|
|
6411
6651
|
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
6412
6652
|
compileVariantConstruction(info, call, expr, body, ctx, state)?;
|
|
6413
6653
|
} else {
|
|
6414
|
-
let sig = ctx
|
|
6415
|
-
.func_sigs
|
|
6416
|
-
|
|
6654
|
+
let (func_idx, sig) = resolveCallee(ctx, expr, false, &call.name)
|
|
6417
6655
|
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
6418
6656
|
compileCallArgs(&call.name, &call.args, sig, body, ctx, state)?;
|
|
6419
|
-
let func_idx = ctx
|
|
6420
|
-
.func_ids
|
|
6421
|
-
.get(&call.name)
|
|
6422
|
-
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
6423
|
-
Instruction::Call(
|
|
6657
|
+
Instruction::Call(func_idx).encode(body);
|
|
6424
6658
|
coerceCallResult(&sig.decl_ret, expr, body, ctx);
|
|
6425
6659
|
}
|
|
6426
6660
|
}
|
|
@@ -6443,7 +6677,8 @@ fn compileExpr(
|
|
|
6443
6677
|
compileTagLiteral(n, None, body, ctx, state)?
|
|
6444
6678
|
}
|
|
6445
6679
|
Some(info) if info.field_types.is_empty() => {
|
|
6680
|
+
let ety = enumTypeOfExpr(expr, info, ctx);
|
|
6446
|
-
let global_idx = *ctx.singleton_globals.get(n)
|
|
6681
|
+
let global_idx = *ctx.singleton_globals.get(&(ety, n.clone()))
|
|
6447
6682
|
.unwrap_or_else(|| panic!("internal codegen error: payload-free variant '{}' has no singleton global", n));
|
|
6448
6683
|
Instruction::GlobalGet(global_idx).encode(body);
|
|
6449
6684
|
}
|
|
@@ -6511,6 +6746,7 @@ fn compileExpr(
|
|
|
6511
6746
|
&init_call,
|
|
6512
6747
|
&PlumType::TNamed(call.type_name.clone()),
|
|
6513
6748
|
expr,
|
|
6749
|
+
false,
|
|
6514
6750
|
body,
|
|
6515
6751
|
ctx,
|
|
6516
6752
|
state,
|
|
@@ -6535,21 +6771,22 @@ fn compileExpr(
|
|
|
6535
6771
|
.enum_variants
|
|
6536
6772
|
.get(&call.type_name)
|
|
6537
6773
|
.expect("just checked is_some");
|
|
6774
|
+
let ety = enumTypeOfExpr(expr, info, ctx);
|
|
6775
|
+
let field_tys = plum_checker::variantFields(info, &ety);
|
|
6538
|
-
let variant_idx =
|
|
6776
|
+
let variant_idx = ctx
|
|
6539
6777
|
.gc_types
|
|
6540
|
-
.variant_type_idx
|
|
6541
|
-
.
|
|
6778
|
+
.variantIdx(&ety, &call.type_name)
|
|
6542
6779
|
.ok_or_else(|| {
|
|
6543
6780
|
format!(
|
|
6544
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
6781
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
6545
|
-
call.type_name
|
|
6782
|
+
call.type_name, ety
|
|
6546
6783
|
)
|
|
6547
6784
|
})?;
|
|
6548
6785
|
let field_names = info.field_names.clone();
|
|
6549
6786
|
Instruction::I32Const(typeIdOf(ctx.gc_types, &call.type_name)).encode(body);
|
|
6550
6787
|
for (field_idx, field_name) in field_names.iter().enumerate() {
|
|
6551
6788
|
match call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
6552
|
-
Some(fa) => compileExprInto(&fa.value, &
|
|
6789
|
+
Some(fa) => compileExprInto(&fa.value, &field_tys[field_idx], body, ctx, state)?,
|
|
6553
6790
|
// `Circle(..c, radius: 10)` — a field not overridden is read
|
|
6554
6791
|
// straight off the spread source instead (checker only proved
|
|
6555
6792
|
// `c` is SOME value of the owning enum, restricted to a bare
|
|
@@ -6595,7 +6832,7 @@ fn compileExpr(
|
|
|
6595
6832
|
compileFieldRead(&attr.object, field_name, &obj_ty, expr, body, ctx, state)?
|
|
6596
6833
|
}
|
|
6597
6834
|
ast::AttrKind::Method(call) => {
|
|
6598
|
-
compileMethodCall(&attr.object, call, &obj_ty, expr, body, ctx, state)?
|
|
6835
|
+
compileMethodCall(&attr.object, call, &obj_ty, expr, false, body, ctx, state)?
|
|
6599
6836
|
}
|
|
6600
6837
|
}
|
|
6601
6838
|
}
|
|
@@ -6644,6 +6881,24 @@ fn coerceCallResultTo(decl_ret: &PlumType, want: &PlumType, body: &mut Vec<u8>,
|
|
|
6644
6881
|
/// argument converted to its DECLARED param type (boxing into erased `T` slots,
|
|
6645
6882
|
/// wrapping a bare `Int`/`Float` for a `Number` param), a trailing `...T` pack
|
|
6646
6883
|
/// built into one GC array, and the declared defaults for omitted trailing params.
|
|
6884
|
+
/// The wasm function (and signature) a call expression invokes: its checked
|
|
6885
|
+
/// instantiation if the callee is generic, else the plain function `plain_key`.
|
|
6886
|
+
fn resolveCallee<'c>(
|
|
6887
|
+
ctx: &'c LocalCtx,
|
|
6888
|
+
call_expr: &ast::Expr,
|
|
6889
|
+
implicit: bool,
|
|
6890
|
+
plain_key: &str,
|
|
6891
|
+
) -> Option<(u32, &'c FuncSig)> {
|
|
6892
|
+
let ptr = call_expr as *const ast::Expr as usize;
|
|
6893
|
+
if let Some(id) = ctx.call_inst.get(&(ctx.unit_inst, ptr, implicit)) {
|
|
6894
|
+
return ctx.inst_funcs.get(id).map(|(f, s)| (*f, s));
|
|
6895
|
+
}
|
|
6896
|
+
match (ctx.func_ids.get(plain_key), ctx.func_sigs.get(plain_key)) {
|
|
6897
|
+
(Some(f), Some(s)) => Some((*f, s)),
|
|
6898
|
+
_ => None,
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
|
|
6647
6902
|
fn compileCallArgs(
|
|
6648
6903
|
func_key: &str,
|
|
6649
6904
|
args: &[ast::Arg],
|
|
@@ -6682,7 +6937,7 @@ fn compileCallArgs(
|
|
|
6682
6937
|
None => compileExpr(argExprOf(arg), body, ctx, state)?,
|
|
6683
6938
|
}
|
|
6684
6939
|
}
|
|
6685
|
-
compileDefaultArgs(func_key, args.len(), body, ctx, state)?;
|
|
6940
|
+
compileDefaultArgs(func_key, sig, args.len(), body, ctx, state)?;
|
|
6686
6941
|
}
|
|
6687
6942
|
}
|
|
6688
6943
|
Ok(())
|
|
@@ -6714,6 +6969,10 @@ fn compileFieldRead(
|
|
|
6714
6969
|
}
|
|
6715
6970
|
};
|
|
6716
6971
|
let want = &inferLocalType(expr, ctx);
|
|
6972
|
+
let ety = match obj_ty {
|
|
6973
|
+
PlumType::TStr => PlumType::TNamed("Str".to_string()),
|
|
6974
|
+
t => t.clone(),
|
|
6975
|
+
};
|
|
6717
6976
|
// A discriminant enum's shared params are declared directly on the enum's
|
|
6718
6977
|
// SUPERTYPE (see `buildGcTypeRegistry`'s `EnumSuper` arm) — no `ref.cast` to
|
|
6719
6978
|
// any one variant needed.
|
|
@@ -6722,20 +6981,15 @@ fn compileFieldRead(
|
|
|
6722
6981
|
.iter()
|
|
6723
6982
|
.position(|(n, _)| n == field_name)
|
|
6724
6983
|
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
6725
|
-
let super_type_idx =
|
|
6984
|
+
let super_type_idx = ctx.gc_types.superIdx(&ety).ok_or_else(|| {
|
|
6726
|
-
.gc_types
|
|
6727
|
-
.enum_super_type_idx
|
|
6728
|
-
.get(&class_name)
|
|
6729
|
-
.ok_or_else(|| {
|
|
6730
|
-
|
|
6985
|
+
format!("codegen: enum '{}' missing from the GC type registry", class_name)
|
|
6731
|
-
|
|
6986
|
+
})?;
|
|
6732
6987
|
compileExpr(object, body, ctx, state)?;
|
|
6733
6988
|
Instruction::StructGet {
|
|
6734
6989
|
struct_type_index: super_type_idx,
|
|
6735
6990
|
field_index: fld(field_idx),
|
|
6736
6991
|
}
|
|
6737
6992
|
.encode(body);
|
|
6738
|
-
coerceTy(¶ms[field_idx].1, want, body, ctx.gc_types);
|
|
6739
6993
|
return Ok(());
|
|
6740
6994
|
}
|
|
6741
6995
|
// Not a discriminant enum: `class_name` is an ordinary enum, and the checker
|
|
@@ -6755,16 +7009,16 @@ fn compileFieldRead(
|
|
|
6755
7009
|
.iter()
|
|
6756
7010
|
.position(|n| n == field_name)
|
|
6757
7011
|
.expect("just found by this field name");
|
|
6758
|
-
let variant_type_idx =
|
|
7012
|
+
let variant_type_idx = ctx
|
|
6759
7013
|
.gc_types
|
|
6760
|
-
.variant_type_idx
|
|
6761
|
-
.
|
|
7014
|
+
.variantIdx(&ety, variant_name)
|
|
6762
7015
|
.ok_or_else(|| {
|
|
6763
7016
|
format!(
|
|
6764
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
7017
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
6765
|
-
variant_name
|
|
7018
|
+
variant_name, ety
|
|
6766
7019
|
)
|
|
6767
7020
|
})?;
|
|
7021
|
+
let _ = (info, want);
|
|
6768
7022
|
compileExpr(object, body, ctx, state)?;
|
|
6769
7023
|
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
6770
7024
|
Instruction::StructGet {
|
|
@@ -6772,7 +7026,6 @@ fn compileFieldRead(
|
|
|
6772
7026
|
field_index: fld(field_idx),
|
|
6773
7027
|
}
|
|
6774
7028
|
.encode(body);
|
|
6775
|
-
coerceTy(&info.field_types[field_idx], want, body, ctx.gc_types);
|
|
6776
7029
|
Ok(())
|
|
6777
7030
|
}
|
|
6778
7031
|
|
|
@@ -6824,10 +7077,29 @@ fn compileMethodCall(
|
|
|
6824
7077
|
call: &ast::FnCall,
|
|
6825
7078
|
obj_ty: &PlumType,
|
|
6826
7079
|
result_expr: &ast::Expr,
|
|
7080
|
+
implicit: bool,
|
|
6827
7081
|
body: &mut Vec<u8>,
|
|
6828
7082
|
ctx: &LocalCtx,
|
|
6829
7083
|
state: &mut ModuleState,
|
|
6830
7084
|
) -> Result<(), String> {
|
|
7085
|
+
// A call the checker resolved to a generic instantiation.
|
|
7086
|
+
if let Some((func_idx, sig)) = ctx
|
|
7087
|
+
.call_inst
|
|
7088
|
+
.get(&(ctx.unit_inst, result_expr as *const ast::Expr as usize, implicit))
|
|
7089
|
+
.and_then(|id| ctx.inst_funcs.get(id))
|
|
7090
|
+
.map(|(f, s)| (*f, s))
|
|
7091
|
+
{
|
|
7092
|
+
let recv = plum_checker::methodReceiverName(obj_ty).unwrap_or_default();
|
|
7093
|
+
if matches!(object, ast::Expr::TypeName(n) if *n == recv) {
|
|
7094
|
+
pushSelfPlaceholder(sig.params[0], body);
|
|
7095
|
+
} else {
|
|
7096
|
+
compileExpr(object, body, ctx, state)?;
|
|
7097
|
+
}
|
|
7098
|
+
compileCallArgs(&call.name, &call.args, sig, body, ctx, state)?;
|
|
7099
|
+
Instruction::Call(func_idx).encode(body);
|
|
7100
|
+
coerceCallResult(&sig.decl_ret, result_expr, body, ctx);
|
|
7101
|
+
return Ok(());
|
|
7102
|
+
}
|
|
6831
7103
|
// A method on an erased generic parameter (`K: Hashable` -> `key.hash()`)
|
|
6832
7104
|
// dispatches dynamically through the declaring trait's `$dyn$` dispatcher.
|
|
6833
7105
|
if matches!(obj_ty, PlumType::TParam(_)) {
|
|
@@ -6892,7 +7164,12 @@ fn compileMethodCall(
|
|
|
6892
7164
|
// the right type must still be pushed — the body simply never reads it.
|
|
6893
7165
|
let is_static_call = matches!(object, ast::Expr::TypeName(n) if *n == class_name);
|
|
6894
7166
|
if is_static_call {
|
|
6895
|
-
let
|
|
7167
|
+
let _ = &dispatch_name;
|
|
7168
|
+
let vt = ctx
|
|
7169
|
+
.func_sigs
|
|
7170
|
+
.get(&key)
|
|
7171
|
+
.and_then(|s| s.params.first().copied())
|
|
7172
|
+
.unwrap_or(ValType::I32);
|
|
6896
7173
|
pushSelfPlaceholder(vt, body);
|
|
6897
7174
|
} else {
|
|
6898
7175
|
if let Some(variant) = &box_variant {
|
|
@@ -6971,14 +7248,12 @@ fn compileDynCall(
|
|
|
6971
7248
|
/// same as before this feature existed.
|
|
6972
7249
|
fn compileDefaultArgs(
|
|
6973
7250
|
func_key: &str,
|
|
7251
|
+
sig: &FuncSig,
|
|
6974
7252
|
supplied_count: usize,
|
|
6975
7253
|
body: &mut Vec<u8>,
|
|
6976
7254
|
ctx: &LocalCtx,
|
|
6977
7255
|
state: &mut ModuleState,
|
|
6978
7256
|
) -> Result<(), String> {
|
|
6979
|
-
let Some(sig) = ctx.func_sigs.get(func_key) else {
|
|
6980
|
-
return Ok(());
|
|
6981
|
-
};
|
|
6982
7257
|
for (i, default) in sig.param_defaults.iter().enumerate().skip(supplied_count) {
|
|
6983
7258
|
let default_expr = default.as_ref().ok_or_else(|| {
|
|
6984
7259
|
format!(
|
|
@@ -6997,11 +7272,13 @@ fn compileDefaultArgs(
|
|
|
6997
7272
|
fn compileVariantConstruction(
|
|
6998
7273
|
info: &EnumVariantInfo,
|
|
6999
7274
|
call: &ast::FnCall,
|
|
7000
|
-
|
|
7275
|
+
expr: &ast::Expr,
|
|
7001
7276
|
body: &mut Vec<u8>,
|
|
7002
7277
|
ctx: &LocalCtx,
|
|
7003
7278
|
state: &mut ModuleState,
|
|
7004
7279
|
) -> Result<(), String> {
|
|
7280
|
+
let ety = enumTypeOfExpr(expr, info, ctx);
|
|
7281
|
+
let field_tys = plum_checker::variantFields(info, &ety);
|
|
7005
7282
|
if call.args.len() != info.field_types.len() {
|
|
7006
7283
|
return Err(format!(
|
|
7007
7284
|
"codegen: variant '{}' expects {} arg(s), got {}",
|
|
@@ -7010,20 +7287,19 @@ fn compileVariantConstruction(
|
|
|
7010
7287
|
call.args.len()
|
|
7011
7288
|
));
|
|
7012
7289
|
}
|
|
7013
|
-
let variant_type_idx =
|
|
7290
|
+
let variant_type_idx = ctx
|
|
7014
7291
|
.gc_types
|
|
7015
|
-
.variant_type_idx
|
|
7016
|
-
.
|
|
7292
|
+
.variantIdx(&ety, &call.name)
|
|
7017
7293
|
.ok_or_else(|| {
|
|
7018
7294
|
format!(
|
|
7019
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
7295
|
+
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
7020
|
-
call.name
|
|
7296
|
+
call.name, ety
|
|
7021
7297
|
)
|
|
7022
7298
|
})?;
|
|
7023
7299
|
if info.field_types.is_empty() {
|
|
7024
7300
|
// Pre-allocated singleton (this migration plan's Decision 2) — not a fresh
|
|
7025
7301
|
// struct.new per reference.
|
|
7026
|
-
let global_idx = *ctx.singleton_globals.get(&call.name).ok_or_else(|| {
|
|
7302
|
+
let global_idx = *ctx.singleton_globals.get(&(ety.clone(), call.name.clone())).ok_or_else(|| {
|
|
7027
7303
|
format!(
|
|
7028
7304
|
"codegen: payload-free variant '{}' has no singleton global",
|
|
7029
7305
|
call.name
|
|
@@ -7044,7 +7320,7 @@ fn compileVariantConstruction(
|
|
|
7044
7320
|
ast::Arg::Keyword { value, .. } => value,
|
|
7045
7321
|
ast::Arg::Pair { value, .. } => value,
|
|
7046
7322
|
};
|
|
7047
|
-
match
|
|
7323
|
+
match field_tys.get(i) {
|
|
7048
7324
|
Some(d) => compileExprInto(arg_expr, d, body, ctx, state)?,
|
|
7049
7325
|
None => compileExpr(arg_expr, body, ctx, state)?,
|
|
7050
7326
|
}
|
|
@@ -7155,7 +7431,7 @@ fn compileInterpolatedString(
|
|
|
7155
7431
|
} else if plum_checker::nominalName(&other)
|
|
7156
7432
|
.is_some_and(|n| ctx.func_ids.contains_key(&format!("{}::toStr", n)))
|
|
7157
7433
|
{
|
|
7158
|
-
compileMethodCall(expr, &to_str, &other, expr, body, ctx, state)?;
|
|
7434
|
+
compileMethodCall(expr, &to_str, &other, expr, true, body, ctx, state)?;
|
|
7159
7435
|
} else {
|
|
7160
7436
|
return Err(format!(
|
|
7161
7437
|
"codegen: interpolating a value of type {} is not yet supported",
|
|
@@ -7184,7 +7460,7 @@ fn compileClosureLiteral(
|
|
|
7184
7460
|
ctx: &LocalCtx,
|
|
7185
7461
|
_state: &mut ModuleState,
|
|
7186
7462
|
) -> Result<(), String> {
|
|
7187
|
-
let key = cl as *const ast::Closure as usize;
|
|
7463
|
+
let key = (ctx.unit_idx, cl as *const ast::Closure as usize);
|
|
7188
7464
|
let info = ctx.closures.get(&key).ok_or_else(|| {
|
|
7189
7465
|
"internal codegen error: closure literal was not discovered by the discovery pre-pass"
|
|
7190
7466
|
.to_string()
|
|
@@ -7210,18 +7486,12 @@ fn compileClosureLiteral(
|
|
|
7210
7486
|
Ok(())
|
|
7211
7487
|
}
|
|
7212
7488
|
|
|
7213
|
-
/// Compiles a call to a closure-typed local via `call_indirect`
|
|
7489
|
+
/// Compiles a call to a closure-typed local via `call_indirect` with the typed
|
|
7214
|
-
/// closure ABI
|
|
7490
|
+
/// closure ABI: push the env, each argument in its own representation, then the
|
|
7215
|
-
/// (primitives boxed), the result is converted from `anyref` to the call's own
|
|
7216
|
-
/// instantiated type (or dropped when the closure's declared result is `Unit`).
|
|
7217
|
-
/// The local is statically `anyref` (closures share that placeholder type — see
|
|
7218
|
-
/// `plumTypeToValtype`), so every read of its fields `ref.cast`s down to the shared
|
|
7219
|
-
/// concrete closure struct type first. Stack order matches the closure function's
|
|
7220
|
-
/// signature `(env, ...args)`: push the env, then each argument, then the table
|
|
7221
|
-
/// index (the `call_indirect` operand).
|
|
7491
|
+
/// table index (the `call_indirect` operand).
|
|
7222
7492
|
fn compileClosureCall(
|
|
7223
7493
|
call: &ast::FnCall,
|
|
7224
|
-
|
|
7494
|
+
_call_expr: &ast::Expr,
|
|
7225
7495
|
body: &mut Vec<u8>,
|
|
7226
7496
|
ctx: &LocalCtx,
|
|
7227
7497
|
state: &mut ModuleState,
|
|
@@ -7231,16 +7501,17 @@ fn compileClosureCall(
|
|
|
7231
7501
|
.get(&call.name)
|
|
7232
7502
|
.ok_or_else(|| format!("codegen: closure '{}' is not a local", call.name))?;
|
|
7233
7503
|
let closure_type_idx = ctx.gc_types.closure_type_idx;
|
|
7504
|
+
let Some(PlumType::TFun(ps, r)) = ctx.local_decl_types.get(&call.name) else {
|
|
7505
|
+
return Err(format!("codegen: '{}' is not a function-typed local", call.name));
|
|
7506
|
+
};
|
|
7507
|
+
let key = closureSigKey(ps, r, ctx.gc_types);
|
|
7234
|
-
let type_index = *ctx.closure_call_types.get(&
|
|
7508
|
+
let type_index = *ctx.closure_call_types.get(&key).ok_or_else(|| {
|
|
7235
7509
|
format!(
|
|
7236
|
-
"internal codegen error: no call_indirect type for closure '{}'
|
|
7510
|
+
"internal codegen error: no call_indirect type for closure '{}' of type fn{:?}",
|
|
7237
|
-
call.name,
|
|
7511
|
+
call.name, key
|
|
7238
|
-
call.args.len()
|
|
7239
7512
|
)
|
|
7240
7513
|
})?;
|
|
7241
|
-
let any = ValType::Ref(RefType::ANYREF);
|
|
7242
7514
|
|
|
7243
|
-
// env (closure struct field 1)
|
|
7244
7515
|
Instruction::LocalGet(closure_local).encode(body);
|
|
7245
7516
|
Instruction::RefCastNonNull(HeapType::Concrete(closure_type_idx)).encode(body);
|
|
7246
7517
|
Instruction::StructGet {
|
|
@@ -7248,11 +7519,12 @@ fn compileClosureCall(
|
|
|
7248
7519
|
field_index: 1,
|
|
7249
7520
|
}
|
|
7250
7521
|
.encode(body);
|
|
7251
|
-
// real arguments, boxed to the erased ABI
|
|
7252
|
-
for arg in
|
|
7522
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
7523
|
+
match ps.get(i) {
|
|
7524
|
+
Some(p) => compileExprInto(argExprOf(arg), p, body, ctx, state)?,
|
|
7253
|
-
|
|
7525
|
+
None => compileExpr(argExprOf(arg), body, ctx, state)?,
|
|
7526
|
+
}
|
|
7254
7527
|
}
|
|
7255
|
-
// table index (closure struct field 0) — the call_indirect operand
|
|
7256
7528
|
Instruction::LocalGet(closure_local).encode(body);
|
|
7257
7529
|
Instruction::RefCastNonNull(HeapType::Concrete(closure_type_idx)).encode(body);
|
|
7258
7530
|
Instruction::StructGet {
|
|
@@ -7265,13 +7537,6 @@ fn compileClosureCall(
|
|
|
7265
7537
|
table_index: 0,
|
|
7266
7538
|
}
|
|
7267
7539
|
.encode(body);
|
|
7268
|
-
// The result: `Unit` closures produce a null anyref that is dropped here, so
|
|
7269
|
-
// a Unit-typed call leaves nothing on the stack (see `exprHasResult`).
|
|
7270
|
-
if closureCallReturnsUnit(&call.name, ctx) {
|
|
7271
|
-
Instruction::Drop.encode(body);
|
|
7272
|
-
} else {
|
|
7273
|
-
coerceTy(&anyTy(), &inferLocalType(call_expr, ctx), body, ctx.gc_types);
|
|
7274
|
-
}
|
|
7275
7540
|
Ok(())
|
|
7276
7541
|
}
|
|
7277
7542
|
|
|
@@ -7287,22 +7552,19 @@ fn closureCallReturnsUnit(name: &str, ctx: &LocalCtx) -> bool {
|
|
|
7287
7552
|
}
|
|
7288
7553
|
|
|
7289
7554
|
/// Compiles a closure literal's own body into a standalone wasm function with the
|
|
7290
|
-
///
|
|
7555
|
+
/// typed closure ABI `(env: anyref, p1: T1, ..., pn: Tn) -> R` (see `ClosureSigKey`).
|
|
7291
|
-
/// (see `ClosureSigKey`). Local 0 is the env pointer; locals `1..=n` are the raw
|
|
7292
|
-
///
|
|
7556
|
+
/// Local 0 is the env; locals `1..=n` are the params in their own representation;
|
|
7293
|
-
/// raw param unboxed/cast to its real Plum type by the prologue), one per
|
|
7294
|
-
/// captured free
|
|
7557
|
+
/// captured free variables (restored from the env struct) and the body's own named
|
|
7295
|
-
/// named locals and scratch slots. The tail value is boxed back to `anyref` (a
|
|
7296
|
-
///
|
|
7558
|
+
/// locals and scratch slots follow.
|
|
7297
7559
|
fn compileClosureBody(
|
|
7298
7560
|
cl: &ast::Closure,
|
|
7299
7561
|
info: &ClosureInfo,
|
|
7300
7562
|
ctx: &CompileCtx,
|
|
7301
7563
|
state: &mut ModuleState,
|
|
7564
|
+
unit: &UnitRef,
|
|
7302
7565
|
) -> Result<Vec<u8>, String> {
|
|
7303
7566
|
let mut body = Vec::new();
|
|
7304
7567
|
|
|
7305
|
-
// Fallback type env (synthesized nodes only) + declared local types.
|
|
7306
7568
|
let mut base_env = ctx.global_env.clone();
|
|
7307
7569
|
let mut decl_types: HashMap<String, PlumType> = HashMap::new();
|
|
7308
7570
|
for (name, ty) in &info.free_vars {
|
|
@@ -7315,7 +7577,7 @@ fn compileClosureBody(
|
|
|
7315
7577
|
}
|
|
7316
7578
|
|
|
7317
7579
|
let mut collector = Collector {
|
|
7318
|
-
types:
|
|
7580
|
+
types: unit.types,
|
|
7319
7581
|
enum_variants: &ctx.enum_variants,
|
|
7320
7582
|
named: Vec::new(),
|
|
7321
7583
|
named_set: Default::default(),
|
|
@@ -7328,19 +7590,12 @@ fn compileClosureBody(
|
|
|
7328
7590
|
};
|
|
7329
7591
|
collector.walkBlock(&cl.body);
|
|
7330
7592
|
|
|
7331
|
-
// ---- local index layout ----
|
|
7332
|
-
// [
|
|
7593
|
+
// ---- local index layout: [env][params][free vars][named...][match][scratch] ----
|
|
7333
7594
|
let mut locals: HashMap<String, u32> = HashMap::new();
|
|
7334
7595
|
let mut groups: Vec<ValType> = Vec::new();
|
|
7335
|
-
let mut idx =
|
|
7596
|
+
let mut idx = 1u32;
|
|
7336
|
-
|
|
7337
|
-
idx += 1 + cl.params.len() as u32; // env + raw params (all in the signature)
|
|
7338
|
-
let mut param_prologue: Vec<(u32, u32, PlumType)> = Vec::new(); // (raw, typed, ptype)
|
|
7339
|
-
for (i, (name, pty)) in cl.params.iter().zip(info.param_ptypes.iter()).enumerate() {
|
|
7340
|
-
|
|
7597
|
+
for name in cl.params.iter() {
|
|
7341
7598
|
locals.insert(name.clone(), idx);
|
|
7342
|
-
groups.push(vt);
|
|
7343
|
-
param_prologue.push((i as u32 + 1, idx, pty.clone()));
|
|
7344
7599
|
idx += 1;
|
|
7345
7600
|
}
|
|
7346
7601
|
for (name, ty) in &info.free_vars {
|
|
@@ -7369,8 +7624,8 @@ fn compileClosureBody(
|
|
|
7369
7624
|
}
|
|
7370
7625
|
|
|
7371
7626
|
let nested_class_scratch_base = idx;
|
|
7372
|
-
for vname in &collector.nested_class_scratch_types {
|
|
7627
|
+
for (sty, vname) in &collector.nested_class_scratch_types {
|
|
7373
|
-
let variant_idx = withGcTypes(|r|
|
|
7628
|
+
let variant_idx = withGcTypes(|r| scratchTypeIdx(r, sty, vname));
|
|
7374
7629
|
groups.push(gcRef(variant_idx));
|
|
7375
7630
|
idx += 1;
|
|
7376
7631
|
}
|
|
@@ -7388,6 +7643,7 @@ fn compileClosureBody(
|
|
|
7388
7643
|
groups.push(ValType::I32); // loop index
|
|
7389
7644
|
idx += 2;
|
|
7390
7645
|
}
|
|
7646
|
+
let _ = idx;
|
|
7391
7647
|
|
|
7392
7648
|
if groups.is_empty() {
|
|
7393
7649
|
body.push(0);
|
|
@@ -7399,17 +7655,10 @@ fn compileClosureBody(
|
|
|
7399
7655
|
}
|
|
7400
7656
|
}
|
|
7401
7657
|
|
|
7402
|
-
// Prologue: convert each raw `anyref` param to its real representation.
|
|
7403
|
-
for (raw, typed, pty) in ¶m_prologue {
|
|
7404
|
-
Instruction::LocalGet(*raw).encode(&mut body);
|
|
7405
|
-
coerceTy(&anyTy(), pty, &mut body, &ctx.gc_types);
|
|
7406
|
-
Instruction::LocalSet(*typed).encode(&mut body);
|
|
7407
|
-
}
|
|
7408
|
-
// Restore each captured free variable from the env struct
|
|
7658
|
+
// Restore each captured free variable from the env struct at entry.
|
|
7409
|
-
// `anyref` — `ref.cast` down to THIS closure's own concrete env type) at entry.
|
|
7410
7659
|
for (i, (name, _)) in info.free_vars.iter().enumerate() {
|
|
7411
7660
|
let local_idx = *locals.get(name).expect("free var local was assigned above");
|
|
7412
|
-
Instruction::LocalGet(0).encode(&mut body);
|
|
7661
|
+
Instruction::LocalGet(0).encode(&mut body);
|
|
7413
7662
|
Instruction::RefCastNonNull(HeapType::Concrete(info.env_type_idx)).encode(&mut body);
|
|
7414
7663
|
Instruction::StructGet {
|
|
7415
7664
|
struct_type_index: info.env_type_idx,
|
|
@@ -7429,7 +7678,12 @@ fn compileClosureBody(
|
|
|
7429
7678
|
variadic_for_scratch: collector.variadic_for_scratch,
|
|
7430
7679
|
func_ids: &ctx.func_ids,
|
|
7431
7680
|
func_sigs: &ctx.func_sigs,
|
|
7681
|
+
inst_funcs: &ctx.inst_funcs,
|
|
7682
|
+
call_inst: &ctx.call_inst,
|
|
7683
|
+
known_fns: &ctx.known_fns,
|
|
7684
|
+
unit_idx: unit.idx,
|
|
7685
|
+
unit_inst: unit.inst,
|
|
7432
|
-
types:
|
|
7686
|
+
types: unit.types,
|
|
7433
7687
|
local_decl_types: decl_types,
|
|
7434
7688
|
closures: &ctx.closures,
|
|
7435
7689
|
closure_call_types: &ctx.closure_call_types,
|
|
@@ -7454,11 +7708,9 @@ fn compileClosureBody(
|
|
|
7454
7708
|
|
|
7455
7709
|
if matches!(info.ret_ptype, PlumType::TUnit) {
|
|
7456
7710
|
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, None)?;
|
|
7457
|
-
Instruction::RefNull(HeapType::ANY).encode(&mut body);
|
|
7458
7711
|
} else {
|
|
7459
7712
|
let ret_vt = plumTypeToValtype(&info.ret_ptype);
|
|
7460
7713
|
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, Some(ret_vt))?;
|
|
7461
|
-
coerceTy(&info.ret_ptype, &anyTy(), &mut body, &ctx.gc_types);
|
|
7462
7714
|
}
|
|
7463
7715
|
|
|
7464
7716
|
Instruction::End.encode(&mut body);
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -1221,7 +1221,7 @@ fn countTypes(bytes: &[u8]) -> usize {
|
|
|
1221
1221
|
}
|
|
1222
1222
|
|
|
1223
1223
|
#[test]
|
|
1224
|
-
fn
|
|
1224
|
+
fn distinct_instances_get_distinct_gc_types() {
|
|
1225
1225
|
let prog = |second: &str| {
|
|
1226
1226
|
format!(
|
|
1227
1227
|
"{}\
|
|
@@ -1238,7 +1238,7 @@ fun main() -> Int = 1
|
|
|
1238
1238
|
};
|
|
1239
1239
|
let same = assertValid(&prog("[A]"));
|
|
1240
1240
|
let wider = assertValid(&prog("[A, B]"));
|
|
1241
|
-
|
|
1241
|
+
assert!(countTypes(&same) < countTypes(&wider));
|
|
1242
1242
|
}
|
|
1243
1243
|
|
|
1244
1244
|
#[test]
|
|
@@ -1742,29 +1742,221 @@ fun main() -> Int =
|
|
|
1742
1742
|
assert_eq!(runMerged::<i64>("trylist", src), 41);
|
|
1743
1743
|
}
|
|
1744
1744
|
|
|
1745
|
+
/// Compiles `src` (merged with the real std) and returns every compiled function key/name.
|
|
1746
|
+
fn compiledKeys(name: &str, src: &str) -> (Vec<u8>, Vec<String>) {
|
|
1747
|
+
let dir = std::env::temp_dir().join(format!("plum_t3k_{}_{}", name, std::process::id()));
|
|
1745
|
-
|
|
1748
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
1749
|
+
let path = dir.join("main.plum");
|
|
1746
|
-
|
|
1750
|
+
std::fs::write(&path, src).unwrap();
|
|
1751
|
+
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
1752
|
+
let r = plum_wasm_codegen::compileSourceWithFunctions(&source).expect("compile");
|
|
1753
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1754
|
+
wasmparser::validate(&r.0).expect("valid wasm");
|
|
1755
|
+
r
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1747
|
-
|
|
1758
|
+
const STD_HEAD: &str = "\
|
|
1748
1759
|
import std/Str
|
|
1749
1760
|
import std/Number
|
|
1750
1761
|
import std/Try
|
|
1751
1762
|
import std/List
|
|
1763
|
+
";
|
|
1752
1764
|
|
|
1765
|
+
#[test]
|
|
1766
|
+
fn one_copy_per_distinct_type() {
|
|
1767
|
+
let src = format!("{}\
|
|
1753
1768
|
fun main() -> Int =
|
|
1754
1769
|
a := List(1, 2).map(|n| n + 1)
|
|
1755
1770
|
b := List(\"x\", \"y\").map(|s| s + \"!\")
|
|
1756
1771
|
a.length() + b.length()
|
|
1757
|
-
";
|
|
1772
|
+
", STD_HEAD);
|
|
1758
|
-
let dir = std::env::temp_dir().join(format!("plum_t4_copy_{}", std::process::id()));
|
|
1759
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
1760
|
-
let
|
|
1773
|
+
let (_, keys) = compiledKeys("copy2", &src);
|
|
1761
|
-
std::fs::write(&path, src).unwrap();
|
|
1762
|
-
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
1763
|
-
let (_, keys) = plum_wasm_codegen::compileSourceWithFunctions(&source).expect("compile");
|
|
1764
|
-
let _ = std::fs::remove_dir_all(&dir);
|
|
1765
|
-
let maps: Vec<&String> = keys.iter().filter(|k| k.
|
|
1774
|
+
let maps: Vec<&String> = keys.iter().filter(|k| k.starts_with("List[") && k.contains("].map")).collect();
|
|
1766
|
-
assert_eq!(maps.len(),
|
|
1775
|
+
assert_eq!(maps.len(), 2, "got {:?}", maps);
|
|
1776
|
+
assert!(keys.iter().any(|k| k.starts_with("List[Int].map")), "{:?}", maps);
|
|
1777
|
+
assert!(keys.iter().any(|k| k.starts_with("List[Str].map")), "{:?}", maps);
|
|
1767
1778
|
assert!(!keys.iter().any(|k| k.contains('$')), "mangled names remain");
|
|
1779
|
+
|
|
1780
|
+
let src = format!("{}\
|
|
1781
|
+
fun main() -> Int =
|
|
1782
|
+
a := List(1, 2).map(|n| n + 1)
|
|
1783
|
+
b := List(3).map(|n| n * 2)
|
|
1784
|
+
a.length() + b.length()
|
|
1785
|
+
", STD_HEAD);
|
|
1786
|
+
let (_, keys) = compiledKeys("copy1", &src);
|
|
1787
|
+
let maps: Vec<&String> = keys.iter().filter(|k| k.starts_with("List[") && k.contains("].map")).collect();
|
|
1788
|
+
assert_eq!(maps.len(), 1, "got {:?}", maps);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
#[test]
|
|
1792
|
+
fn unreachable_generic_not_emitted() {
|
|
1793
|
+
let src = format!("{}\
|
|
1794
|
+
fun neverUsed(x: T) -> T =
|
|
1795
|
+
x
|
|
1796
|
+
|
|
1797
|
+
fun main() -> Int =
|
|
1798
|
+
List(1, 2).length()
|
|
1799
|
+
", STD_HEAD);
|
|
1800
|
+
let (_, keys) = compiledKeys("unreach", &src);
|
|
1801
|
+
assert!(!keys.iter().any(|k| k.contains("neverUsed")), "{:?}", keys);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/// `(function name, body operators as debug strings)` for every function with a name.
|
|
1805
|
+
fn namedBodies(bytes: &[u8]) -> Vec<(String, Vec<String>)> {
|
|
1806
|
+
let mut names: std::collections::HashMap<u32, String> = Default::default();
|
|
1807
|
+
let mut bodies = Vec::new();
|
|
1808
|
+
let mut imports = 0u32;
|
|
1809
|
+
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
|
|
1810
|
+
match payload.unwrap() {
|
|
1811
|
+
wasmparser::Payload::ImportSection(s) => {
|
|
1812
|
+
imports = s.count();
|
|
1813
|
+
}
|
|
1814
|
+
wasmparser::Payload::CustomSection(c) => {
|
|
1815
|
+
if let wasmparser::KnownCustom::Name(r) = c.as_known() {
|
|
1816
|
+
for n in r {
|
|
1817
|
+
if let wasmparser::Name::Function(m) = n.unwrap() {
|
|
1818
|
+
for e in m {
|
|
1819
|
+
let e = e.unwrap();
|
|
1820
|
+
names.insert(e.index, e.name.to_string());
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
wasmparser::Payload::CodeSectionEntry(b) => {
|
|
1827
|
+
let mut ops = Vec::new();
|
|
1828
|
+
let mut r = b.get_operators_reader().unwrap();
|
|
1829
|
+
while !r.eof() {
|
|
1830
|
+
ops.push(format!("{:?}", r.read().unwrap()));
|
|
1831
|
+
}
|
|
1832
|
+
bodies.push(ops);
|
|
1833
|
+
}
|
|
1834
|
+
_ => {}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
bodies
|
|
1838
|
+
.into_iter()
|
|
1839
|
+
.enumerate()
|
|
1840
|
+
.map(|(i, ops)| (names.get(&(imports + i as u32)).cloned().unwrap_or_default(), ops))
|
|
1841
|
+
.collect()
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
#[test]
|
|
1845
|
+
fn no_boxes_in_list_int_map() {
|
|
1846
|
+
let src = format!("{}\
|
|
1847
|
+
fun main() -> Int =
|
|
1848
|
+
List(1, 2).map(|n| n + 1).length()
|
|
1849
|
+
", STD_HEAD);
|
|
1850
|
+
let (bytes, _) = compiledKeys("nobox", &src);
|
|
1851
|
+
let bodies = namedBodies(&bytes);
|
|
1852
|
+
let (name, ops) = bodies
|
|
1853
|
+
.iter()
|
|
1854
|
+
.find(|(n, _)| n.starts_with("List[Int].map"))
|
|
1855
|
+
.expect("List[Int].map compiled");
|
|
1856
|
+
assert!(!ops.is_empty(), "{name}");
|
|
1857
|
+
let box_idx: Vec<u32> = {
|
|
1858
|
+
// function indices of the box helpers
|
|
1859
|
+
let mut v = Vec::new();
|
|
1860
|
+
let mut names: Vec<(u32, String)> = Vec::new();
|
|
1861
|
+
for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
|
|
1862
|
+
if let wasmparser::Payload::CustomSection(c) = payload.unwrap() {
|
|
1863
|
+
if let wasmparser::KnownCustom::Name(r) = c.as_known() {
|
|
1864
|
+
for n in r {
|
|
1865
|
+
if let wasmparser::Name::Function(m) = n.unwrap() {
|
|
1866
|
+
for e in m {
|
|
1867
|
+
let e = e.unwrap();
|
|
1868
|
+
names.push((e.index, e.name.to_string()));
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
for (i, n) in names {
|
|
1876
|
+
if n.starts_with("box.") {
|
|
1877
|
+
v.push(i);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
v
|
|
1881
|
+
};
|
|
1882
|
+
assert!(!box_idx.is_empty());
|
|
1883
|
+
for op in ops {
|
|
1884
|
+
for b in &box_idx {
|
|
1885
|
+
assert_ne!(*op, format!("Call {{ function_index: {} }}", b), "{name} boxes: {ops:?}");
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
#[test]
|
|
1891
|
+
fn array_int_native() {
|
|
1892
|
+
let src = format!("{}\
|
|
1893
|
+
fun main() -> Int =
|
|
1894
|
+
List(1, 2).length()
|
|
1895
|
+
", STD_HEAD);
|
|
1896
|
+
let (bytes, _) = compiledKeys("arrint", &src);
|
|
1897
|
+
let mut found_i64 = false;
|
|
1898
|
+
let mut found_anyref = false;
|
|
1899
|
+
for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
|
|
1900
|
+
if let wasmparser::Payload::TypeSection(s) = payload.unwrap() {
|
|
1901
|
+
for g in s {
|
|
1902
|
+
for t in g.unwrap().types() {
|
|
1903
|
+
if let wasmparser::CompositeInnerType::Array(a) = &t.composite_type.inner {
|
|
1904
|
+
if a.0.mutable && a.0.element_type == wasmparser::StorageType::Val(wasmparser::ValType::I64) {
|
|
1905
|
+
found_i64 = true;
|
|
1906
|
+
}
|
|
1907
|
+
if a.0.element_type == wasmparser::StorageType::Val(wasmparser::ValType::Ref(wasmparser::RefType::ANYREF)) {
|
|
1908
|
+
found_anyref = true;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
assert!(found_i64, "no `array (mut i64)` declared");
|
|
1916
|
+
assert!(!found_anyref, "an anyref array remains");
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
#[test]
|
|
1920
|
+
fn generic_tag_payload() {
|
|
1921
|
+
let src = format!("{}\
|
|
1922
|
+
fun fail(x: T) -> Try[Int, [Bad(T)]] =
|
|
1923
|
+
Err(Bad(x))
|
|
1924
|
+
|
|
1925
|
+
fun main() -> Int =
|
|
1926
|
+
match fail(7)
|
|
1927
|
+
Ok(v) => v
|
|
1928
|
+
Err(Bad(n)) => n
|
|
1929
|
+
", STD_HEAD);
|
|
1930
|
+
assert_eq!(runMerged::<i64>("gentag", &src), 7);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
#[test]
|
|
1934
|
+
fn closure_typed_abi() {
|
|
1935
|
+
let src = format!("{}\
|
|
1936
|
+
fun apply(f: fn(Int) -> Int, x: Int) -> Int =
|
|
1937
|
+
f(x)
|
|
1938
|
+
|
|
1939
|
+
fun main() -> Int =
|
|
1940
|
+
apply(|n| n + 1, 41)
|
|
1941
|
+
", STD_HEAD);
|
|
1942
|
+
let (bytes, _) = compiledKeys("clabi", &src);
|
|
1943
|
+
let mut found = false;
|
|
1944
|
+
for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
|
|
1945
|
+
if let wasmparser::Payload::TypeSection(s) = payload.unwrap() {
|
|
1946
|
+
for g in s {
|
|
1947
|
+
for t in g.unwrap().types() {
|
|
1948
|
+
if let wasmparser::CompositeInnerType::Func(f) = &t.composite_type.inner {
|
|
1949
|
+
if f.params() == [wasmparser::ValType::Ref(wasmparser::RefType::ANYREF), wasmparser::ValType::I64]
|
|
1950
|
+
&& f.results() == [wasmparser::ValType::I64]
|
|
1951
|
+
{
|
|
1952
|
+
found = true;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
assert!(found, "no (anyref, i64) -> i64 closure type");
|
|
1768
1960
|
}
|
|
1769
1961
|
|
|
1770
1962
|
// ---- built-in Bool (i32; no `import std/Bool`) ----
|