plum

#treesitter#compiler#wasm

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

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


d6b1f95Peter 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
- param_types.push(ast_type_to_wasm(param_type_name(&p.ty)).unwrap_or(ValType::I32));
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
- for arg in &call.args {
2758
+ fn arg_expr_of(arg: &ast::Arg) -> &ast::Expr {
2751
- let arg_expr = match arg {
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
- compile_expr(arg_expr, body, ctx, state)?;
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
+ }