plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
380a51c
— Peter John
2026-07-20T11:43:17+05:30
feat(plum-wasm-codegen): compile general enum variant construction
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -3,7 +3,7 @@ use std::cell::RefCell;
|
|
|
3
3
|
use std::collections::HashMap;
|
|
4
4
|
use plum_core::ast;
|
|
5
5
|
use plum_checker::types::{PlumType, TypeEnv, TypeScheme};
|
|
6
|
-
use plum_checker::{ClassEnv, MethodEnv, EnumVariants};
|
|
6
|
+
use plum_checker::{ClassEnv, MethodEnv, EnumVariants, EnumVariantInfo};
|
|
7
7
|
|
|
8
8
|
/// Bump-allocated heap for class instances starts at the second 64KiB page so it can
|
|
9
9
|
/// never collide with the (small, compile-time-sized) string literal data area below it.
|
|
@@ -478,6 +478,14 @@ impl<'a> Collector<'a> {
|
|
|
478
478
|
self.walk_expr(&t.else_);
|
|
479
479
|
}
|
|
480
480
|
ast::Expr::FnCall(call) => {
|
|
481
|
+
let carries_payload = self.cctx.enum_variants.get(&call.name)
|
|
482
|
+
.map(|info| !info.field_types.is_empty())
|
|
483
|
+
.unwrap_or(false);
|
|
484
|
+
if carries_payload {
|
|
485
|
+
let idx = self.next_classcall_slot;
|
|
486
|
+
self.next_classcall_slot += 1;
|
|
487
|
+
self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
|
|
488
|
+
}
|
|
481
489
|
for arg in &call.args {
|
|
482
490
|
self.walk_arg(arg);
|
|
483
491
|
}
|
|
@@ -1038,19 +1046,23 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mu
|
|
|
1038
1046
|
Instruction::End.encode(body);
|
|
1039
1047
|
}
|
|
1040
1048
|
ast::Expr::FnCall(call) => {
|
|
1049
|
+
if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
1050
|
+
compile_variant_construction(info, call, expr, body, ctx, state)?;
|
|
1051
|
+
} else {
|
|
1041
|
-
|
|
1052
|
+
for arg in &call.args {
|
|
1042
|
-
|
|
1053
|
+
let arg_expr = match arg {
|
|
1043
|
-
|
|
1054
|
+
ast::Arg::Positional(e) => e,
|
|
1044
|
-
|
|
1055
|
+
ast::Arg::Keyword { value, .. } => value,
|
|
1045
|
-
|
|
1056
|
+
ast::Arg::Pair { value, .. } => value,
|
|
1046
|
-
|
|
1057
|
+
};
|
|
1047
|
-
|
|
1058
|
+
compile_expr(arg_expr, body, ctx, state)?;
|
|
1059
|
+
}
|
|
1060
|
+
let func_idx = ctx
|
|
1061
|
+
.func_ids
|
|
1062
|
+
.get(&call.name)
|
|
1063
|
+
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
1064
|
+
Instruction::Call(*func_idx).encode(body);
|
|
1048
1065
|
}
|
|
1049
|
-
let func_idx = ctx
|
|
1050
|
-
.func_ids
|
|
1051
|
-
.get(&call.name)
|
|
1052
|
-
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
1053
|
-
Instruction::Call(*func_idx).encode(body);
|
|
1054
1066
|
}
|
|
1055
1067
|
ast::Expr::Self_ => {
|
|
1056
1068
|
let idx = ctx
|
|
@@ -1060,10 +1072,12 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mu
|
|
|
1060
1072
|
.ok_or_else(|| "codegen: 'self' used outside a method".to_string())?;
|
|
1061
1073
|
Instruction::LocalGet(idx).encode(body);
|
|
1062
1074
|
}
|
|
1063
|
-
ast::Expr::TypeName(n) => match
|
|
1075
|
+
ast::Expr::TypeName(n) => match ctx.enum_variants.get(n) {
|
|
1076
|
+
Some(info) if info.field_types.is_empty() => {
|
|
1064
|
-
|
|
1077
|
+
Instruction::I32Const(info.tag).encode(body);
|
|
1078
|
+
}
|
|
1065
|
-
|
|
1079
|
+
Some(_) => return Err(format!("codegen: '{}' carries a payload — construct it with '{}(...)'", n, n)),
|
|
1066
|
-
|
|
1080
|
+
None => return Err(format!("codegen: type name '{}' is not yet supported as a value", n)),
|
|
1067
1081
|
},
|
|
1068
1082
|
ast::Expr::ClassCall(call) => {
|
|
1069
1083
|
let fields = ctx
|
|
@@ -1176,3 +1190,64 @@ fn compile_expr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mu
|
|
|
1176
1190
|
}
|
|
1177
1191
|
Ok(())
|
|
1178
1192
|
}
|
|
1193
|
+
|
|
1194
|
+
/// Compiles a variant-construction call. A payload-free variant (`None`, called as
|
|
1195
|
+
/// `None()` rather than used bare) is just its tag. A payload variant bump-allocates
|
|
1196
|
+
/// `[tag: i32][field0][field1]...` (8-byte stride per slot, matching class field
|
|
1197
|
+
/// layout) and leaves the base pointer on the stack.
|
|
1198
|
+
fn compile_variant_construction(
|
|
1199
|
+
info: &EnumVariantInfo,
|
|
1200
|
+
call: &ast::FnCall,
|
|
1201
|
+
expr: &ast::Expr,
|
|
1202
|
+
body: &mut Vec<u8>,
|
|
1203
|
+
ctx: &LocalCtx,
|
|
1204
|
+
state: &mut ModuleState,
|
|
1205
|
+
) -> Result<(), String> {
|
|
1206
|
+
if call.args.len() != info.field_types.len() {
|
|
1207
|
+
return Err(format!(
|
|
1208
|
+
"codegen: variant '{}' expects {} arg(s), got {}",
|
|
1209
|
+
call.name, info.field_types.len(), call.args.len()
|
|
1210
|
+
));
|
|
1211
|
+
}
|
|
1212
|
+
if info.field_types.is_empty() {
|
|
1213
|
+
Instruction::I32Const(info.tag).encode(body);
|
|
1214
|
+
return Ok(());
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
let size = (1 + info.field_types.len() as i32) * 8;
|
|
1218
|
+
let scratch_key = expr as *const ast::Expr as usize;
|
|
1219
|
+
let scratch_idx = *ctx
|
|
1220
|
+
.classcall_scratch
|
|
1221
|
+
.get(&scratch_key)
|
|
1222
|
+
.ok_or_else(|| "internal codegen error: missing variant-call scratch slot".to_string())?;
|
|
1223
|
+
let scratch_local = ctx.classcall_scratch_base + scratch_idx;
|
|
1224
|
+
|
|
1225
|
+
Instruction::GlobalGet(ctx.bump_global).encode(body);
|
|
1226
|
+
Instruction::LocalSet(scratch_local).encode(body);
|
|
1227
|
+
Instruction::GlobalGet(ctx.bump_global).encode(body);
|
|
1228
|
+
Instruction::I32Const(size).encode(body);
|
|
1229
|
+
Instruction::I32Add.encode(body);
|
|
1230
|
+
Instruction::GlobalSet(ctx.bump_global).encode(body);
|
|
1231
|
+
|
|
1232
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
1233
|
+
Instruction::I32Const(info.tag).encode(body);
|
|
1234
|
+
Instruction::I32Store(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
|
|
1235
|
+
|
|
1236
|
+
for (i, (arg, field_ty)) in call.args.iter().zip(info.field_types.iter()).enumerate() {
|
|
1237
|
+
let arg_expr = match arg {
|
|
1238
|
+
ast::Arg::Positional(e) => e,
|
|
1239
|
+
ast::Arg::Keyword { value, .. } => value,
|
|
1240
|
+
ast::Arg::Pair { value, .. } => value,
|
|
1241
|
+
};
|
|
1242
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
1243
|
+
compile_expr(arg_expr, body, ctx, state)?;
|
|
1244
|
+
let offset = ((i + 1) as u64) * 8;
|
|
1245
|
+
match plum_type_to_valtype(field_ty) {
|
|
1246
|
+
ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
|
|
1247
|
+
ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
|
|
1248
|
+
_ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
1252
|
+
Ok(())
|
|
1253
|
+
}
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -366,3 +366,59 @@ main() -> Int =
|
|
|
366
366
|
let err = main.call(&mut store, ()).expect_err("todo should trap");
|
|
367
367
|
assert_eq!(err.downcast_ref::<wasmtime::Trap>(), Some(&wasmtime::Trap::UnreachableCodeReached), "got: {}", err);
|
|
368
368
|
}
|
|
369
|
+
|
|
370
|
+
#[test]
|
|
371
|
+
fn payload_free_variant_construction_compiles() {
|
|
372
|
+
let src = "\
|
|
373
|
+
enum Color =
|
|
374
|
+
| Red
|
|
375
|
+
| Green
|
|
376
|
+
| Blue
|
|
377
|
+
|
|
378
|
+
main() -> Int =\n x = Green\n 0\n";
|
|
379
|
+
assert_valid(src);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn payload_variant_construction_compiles_and_runs() {
|
|
384
|
+
let src = "\
|
|
385
|
+
enum Option =
|
|
386
|
+
| Some(Int)
|
|
387
|
+
| None
|
|
388
|
+
|
|
389
|
+
unwrapOr(o: Option, default: Int) -> Int =
|
|
390
|
+
match o
|
|
391
|
+
Some(v) =>
|
|
392
|
+
return v
|
|
393
|
+
None =>
|
|
394
|
+
return default
|
|
395
|
+
|
|
396
|
+
main() -> Int =
|
|
397
|
+
unwrapOr(Some(7), 0)
|
|
398
|
+
";
|
|
399
|
+
let source = parse(src);
|
|
400
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
401
|
+
assert_eq!(run_main(&bytes), 7);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#[test]
|
|
405
|
+
fn multi_field_variant_construction_compiles_and_runs() {
|
|
406
|
+
let src = "\
|
|
407
|
+
enum Shape =
|
|
408
|
+
| Rect(Int, Int)
|
|
409
|
+
| Circle(Int)
|
|
410
|
+
|
|
411
|
+
area(s: Shape) -> Int =
|
|
412
|
+
match s
|
|
413
|
+
Rect(w, h) =>
|
|
414
|
+
return w * h
|
|
415
|
+
Circle(r) =>
|
|
416
|
+
return r * r
|
|
417
|
+
|
|
418
|
+
main() -> Int =
|
|
419
|
+
area(Rect(3, 4))
|
|
420
|
+
";
|
|
421
|
+
let source = parse(src);
|
|
422
|
+
let bytes = compile_source(&source).expect("compile failed");
|
|
423
|
+
assert_eq!(run_main(&bytes), 12);
|
|
424
|
+
}
|