plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
d6b1f95
— Peter John
2026-07-23T19:57:54+05:30
feat(plum-wasm-codegen): compile variadic call sites into a length-prefixed buffer
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -441,7 +441,11 @@ pub fn compile_source(source: &ast::Source) -> Result<Vec<u8>, String> {
|
|
|
441
441
|
param_types.push(ValType::I32);
|
|
442
442
|
}
|
|
443
443
|
for p in &f.params {
|
|
444
|
+
let vt = match &p.ty {
|
|
445
|
+
ast::ParamType::Variadic(_) => ValType::I32,
|
|
444
|
-
|
|
446
|
+
other => ast_type_to_wasm(param_type_name(other)).unwrap_or(ValType::I32),
|
|
447
|
+
};
|
|
448
|
+
param_types.push(vt);
|
|
445
449
|
}
|
|
446
450
|
let ret = ret_type_to_wasm(f.returns.as_ref());
|
|
447
451
|
let results_vec: Vec<ValType> = ret.into_iter().collect();
|
|
@@ -1692,7 +1696,11 @@ impl<'a> Collector<'a> {
|
|
|
1692
1696
|
let carries_payload = self.cctx.enum_variants.get(&call.name)
|
|
1693
1697
|
.map(|info| !info.field_types.is_empty())
|
|
1694
1698
|
.unwrap_or(false);
|
|
1699
|
+
let is_variadic_call = matches!(
|
|
1700
|
+
plum_checker::lookup(&self.env, &call.name),
|
|
1701
|
+
Ok(PlumType::TFun(params, _)) if matches!(params.last(), Some(PlumType::TVariadic(_)))
|
|
1702
|
+
);
|
|
1695
|
-
if carries_payload {
|
|
1703
|
+
if carries_payload || is_variadic_call {
|
|
1696
1704
|
let idx = self.next_classcall_slot;
|
|
1697
1705
|
self.next_classcall_slot += 1;
|
|
1698
1706
|
self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
|
|
@@ -2747,19 +2755,73 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mu
|
|
|
2747
2755
|
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
2748
2756
|
compile_variant_construction(info, call, expr, body, ctx, state)?;
|
|
2749
2757
|
} else {
|
|
2750
|
-
|
|
2758
|
+
fn arg_expr_of(arg: &ast::Arg) -> &ast::Expr {
|
|
2751
|
-
|
|
2759
|
+
match arg {
|
|
2752
2760
|
ast::Arg::Positional(e) => e,
|
|
2753
2761
|
ast::Arg::Keyword { value, .. } => value,
|
|
2754
2762
|
ast::Arg::Pair { value, .. } => value,
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
let callee_sig = infer_local_type(&ast::Expr::Var(call.name.clone()), ctx);
|
|
2766
|
+
let variadic_split = match &callee_sig {
|
|
2767
|
+
PlumType::TFun(params, _) => match params.last() {
|
|
2768
|
+
Some(PlumType::TVariadic(elem)) => Some(((**elem).clone(), params.len() - 1)),
|
|
2769
|
+
_ => None,
|
|
2770
|
+
},
|
|
2771
|
+
_ => None,
|
|
2755
|
-
|
|
2772
|
+
};
|
|
2773
|
+
match variadic_split {
|
|
2774
|
+
Some((elem_ty, fixed_count)) => {
|
|
2775
|
+
for arg in call.args.iter().take(fixed_count) {
|
|
2776
|
+
compile_expr(arg_expr_of(arg), body, ctx, state)?;
|
|
2777
|
+
}
|
|
2778
|
+
let trailing: Vec<&ast::Expr> = call.args.iter().skip(fixed_count).map(arg_expr_of).collect();
|
|
2779
|
+
let count = trailing.len() as i32;
|
|
2780
|
+
let size = 8 * (count + 1);
|
|
2781
|
+
|
|
2782
|
+
let scratch_key = expr as *const ast::Expr as usize;
|
|
2783
|
+
let scratch_idx = *ctx
|
|
2784
|
+
.classcall_scratch
|
|
2785
|
+
.get(&scratch_key)
|
|
2786
|
+
.ok_or_else(|| "internal codegen error: missing variadic-call scratch slot".to_string())?;
|
|
2787
|
+
let scratch_local = ctx.classcall_scratch_base + scratch_idx;
|
|
2788
|
+
|
|
2789
|
+
Instruction::GlobalGet(ctx.bump_global).encode(body);
|
|
2790
|
+
Instruction::LocalSet(scratch_local).encode(body);
|
|
2791
|
+
Instruction::GlobalGet(ctx.bump_global).encode(body);
|
|
2792
|
+
Instruction::I32Const(size).encode(body);
|
|
2793
|
+
Instruction::I32Add.encode(body);
|
|
2794
|
+
Instruction::GlobalSet(ctx.bump_global).encode(body);
|
|
2795
|
+
|
|
2796
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
2797
|
+
Instruction::I64Const(count as i64).encode(body);
|
|
2798
|
+
emit_store(ValType::I64, 0, body);
|
|
2799
|
+
|
|
2800
|
+
let elem_vt = plum_type_to_valtype(&elem_ty);
|
|
2801
|
+
for (i, arg_expr) in trailing.iter().enumerate() {
|
|
2802
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
2756
|
-
|
|
2803
|
+
compile_expr(arg_expr, body, ctx, state)?;
|
|
2804
|
+
emit_store(elem_vt, 8 * (i as u64 + 1), body);
|
|
2805
|
+
}
|
|
2806
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
2807
|
+
|
|
2808
|
+
let func_idx = ctx
|
|
2809
|
+
.func_ids
|
|
2810
|
+
.get(&call.name)
|
|
2811
|
+
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
2812
|
+
Instruction::Call(*func_idx).encode(body);
|
|
2813
|
+
}
|
|
2814
|
+
None => {
|
|
2815
|
+
for arg in &call.args {
|
|
2816
|
+
compile_expr(arg_expr_of(arg), body, ctx, state)?;
|
|
2817
|
+
}
|
|
2818
|
+
let func_idx = ctx
|
|
2819
|
+
.func_ids
|
|
2820
|
+
.get(&call.name)
|
|
2821
|
+
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
2822
|
+
Instruction::Call(*func_idx).encode(body);
|
|
2823
|
+
}
|
|
2757
2824
|
}
|
|
2758
|
-
let func_idx = ctx
|
|
2759
|
-
.func_ids
|
|
2760
|
-
.get(&call.name)
|
|
2761
|
-
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
2762
|
-
Instruction::Call(*func_idx).encode(body);
|
|
2763
2825
|
}
|
|
2764
2826
|
}
|
|
2765
2827
|
ast::Expr::Self_ => {
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -1426,3 +1426,20 @@ main() -> Int =
|
|
|
1426
1426
|
let bytes = compile_source(&source).expect("compile failed");
|
|
1427
1427
|
assert_eq!(run_main(&bytes), 107);
|
|
1428
1428
|
}
|
|
1429
|
+
|
|
1430
|
+
#[test]
|
|
1431
|
+
fn variadic_call_with_varying_trailing_arg_counts_runs_correctly() {
|
|
1432
|
+
let src = "\
|
|
1433
|
+
combine(prefix: Int, rest: ...Int) -> Int =
|
|
1434
|
+
prefix
|
|
1435
|
+
|
|
1436
|
+
main() -> Int =
|
|
1437
|
+
a = combine(10)
|
|
1438
|
+
b = combine(20, 1)
|
|
1439
|
+
c = combine(30, 1, 2, 3)
|
|
1440
|
+
a + b + c
|
|
1441
|
+
";
|
|
1442
|
+
let source = parse(src);
|
|
1443
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
1444
|
+
assert_eq!(run_main(&bytes), 60);
|
|
1445
|
+
}
|