plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
6c41056
— Peter John
2026-10-25T19:30:30+05:30
feat: trait names as types with typeId-header dyn dispatch
- plum-checker/src/lib.rs +83 -7
- plum-checker/tests/checker_tests.rs +70 -0
- plum-cli/src/main.rs +6 -5
- plum-examples/traits.plum +66 -0
- plum-runtime/src/main.rs +6 -6
- plum-wasm-codegen/src/lib.rs +507 -75
- plum-wasm-codegen/tests/codegen_tests.rs +115 -0
- plum-wasm-codegen/tests/examples_test.rs +2 -2
plum-checker/src/lib.rs
CHANGED
|
@@ -432,6 +432,43 @@ const GLOBAL_KEY: &str = "$global:";
|
|
|
432
432
|
const TRAIT_KEY: &str = "$trait:";
|
|
433
433
|
/// `TypeEnv` key prefix for a generic parameter's trait bound (`$bound:K:Hashable`).
|
|
434
434
|
const BOUND_KEY: &str = "$bound:";
|
|
435
|
+
/// `methods` method-name marking a receiver name as a trait (see `buildTables`).
|
|
436
|
+
pub const TRAIT_MARKER: &str = "$trait";
|
|
437
|
+
|
|
438
|
+
/// The name of the first method `trait_name` declares that `actual` lacks, or
|
|
439
|
+
/// `None` when `actual` implements the whole trait (or `trait_name` isn't one).
|
|
440
|
+
/// Reuses the per-receiver method table: a primitive/variant also counts when
|
|
441
|
+
/// its owning "union" enum (`Number` for `Int`) defines the method.
|
|
442
|
+
pub fn traitMissingMethod(
|
|
443
|
+
trait_name: &str,
|
|
444
|
+
actual: &PlumType,
|
|
445
|
+
ctx: &CheckCtx,
|
|
446
|
+
) -> Option<String> {
|
|
447
|
+
let key = (trait_name.to_string(), TRAIT_MARKER.to_string());
|
|
448
|
+
if !ctx.methods.contains_key(&key) {
|
|
449
|
+
return None;
|
|
450
|
+
}
|
|
451
|
+
let recv = methodReceiverName(actual);
|
|
452
|
+
let owner = recv.as_ref().and_then(|r| {
|
|
453
|
+
ctx.enum_variants.get(r.as_str()).map(|i| i.enum_name.clone())
|
|
454
|
+
});
|
|
455
|
+
ctx.methods
|
|
456
|
+
.range((trait_name.to_string(), String::new())..)
|
|
457
|
+
.take_while(|((r, _), _)| r == trait_name)
|
|
458
|
+
.map(|((_, m), _)| m)
|
|
459
|
+
.filter(|m| m.as_str() != TRAIT_MARKER)
|
|
460
|
+
.find(|m| {
|
|
461
|
+
let has = |r: &String| ctx.methods.contains_key(&(r.clone(), (*m).clone()));
|
|
462
|
+
!(recv.as_ref().is_some_and(has) || owner.as_ref().is_some_and(has))
|
|
463
|
+
})
|
|
464
|
+
.cloned()
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/// True if `name` is a declared trait.
|
|
468
|
+
pub fn isTraitName(name: &str, ctx: &CheckCtx) -> bool {
|
|
469
|
+
ctx.methods
|
|
470
|
+
.contains_key(&(name.to_string(), TRAIT_MARKER.to_string()))
|
|
471
|
+
}
|
|
435
472
|
|
|
436
473
|
pub fn plumTypeFromAst(ty: &ast::Type) -> PlumType {
|
|
437
474
|
plumTypeFromName(&ty.name)
|
|
@@ -590,6 +627,20 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
|
|
|
590
627
|
}
|
|
591
628
|
unify(r1, r2, ctx)
|
|
592
629
|
}
|
|
630
|
+
// A trait used as a type: any value implementing every trait method
|
|
631
|
+
// coerces to it (a bounded generic parameter is trusted).
|
|
632
|
+
(PlumType::TNamed(tr), actual) if isTraitName(tr, ctx) => {
|
|
633
|
+
if matches!(actual, PlumType::TParam(_)) {
|
|
634
|
+
return Ok(());
|
|
635
|
+
}
|
|
636
|
+
match traitMissingMethod(tr, actual, ctx) {
|
|
637
|
+
Some(m) => Err(format!(
|
|
638
|
+
"type '{}' does not implement trait '{}': missing '{}'",
|
|
639
|
+
actual, tr, m
|
|
640
|
+
)),
|
|
641
|
+
None => Ok(()),
|
|
642
|
+
}
|
|
643
|
+
}
|
|
593
644
|
_ => Err(format!("type mismatch: expected {}, found {}", t1, t2)),
|
|
594
645
|
}
|
|
595
646
|
}
|
|
@@ -887,15 +938,40 @@ pub fn buildTables(
|
|
|
887
938
|
};
|
|
888
939
|
global_env.insert(c.name.clone(), TypeScheme::mono(ty));
|
|
889
940
|
}
|
|
890
|
-
ast::Item::Trait(t)
|
|
941
|
+
ast::Item::Trait(t) => {
|
|
942
|
+
// A trait name is also usable as a type (`s: Shape`): register it
|
|
943
|
+
// as a receiver in `methods` (a `$trait` marker plus one entry per
|
|
944
|
+
// declared method) so calls on a trait-typed value resolve to the
|
|
891
|
-
//
|
|
945
|
+
// declared signature and `unify` can check implementers.
|
|
946
|
+
methods.insert(
|
|
947
|
+
(t.name.clone(), TRAIT_MARKER.to_string()),
|
|
948
|
+
PlumType::TFun(Vec::new(), Box::new(PlumType::TUnit)),
|
|
949
|
+
);
|
|
892
950
|
for m in &t.methods {
|
|
893
|
-
let params: Vec<PlumType> = m
|
|
951
|
+
let params: Vec<PlumType> = m
|
|
952
|
+
.params
|
|
953
|
+
.iter()
|
|
954
|
+
.map(|p| match &p.ty {
|
|
955
|
+
ast::ParamType::Type(ty) => conv(ty),
|
|
956
|
+
ast::ParamType::Variadic(ty) => {
|
|
957
|
+
PlumType::TVariadic(Box::new(conv(ty)))
|
|
958
|
+
}
|
|
959
|
+
ast::ParamType::Fn(ps, ret) => PlumType::TFun(
|
|
960
|
+
ps.iter().map(&conv).collect(),
|
|
961
|
+
Box::new(ret.as_ref().map(|r| conv(r)).unwrap_or(PlumType::TUnit)),
|
|
962
|
+
),
|
|
963
|
+
})
|
|
964
|
+
.collect();
|
|
894
965
|
let ret = m.returns.as_ref().map(&conv).unwrap_or(PlumType::TUnit);
|
|
966
|
+
let sig = PlumType::TFun(params, Box::new(ret));
|
|
967
|
+
methods.insert((t.name.clone(), m.name.clone()), sig.clone());
|
|
968
|
+
if generic {
|
|
969
|
+
// Looked up for calls on a bounded `TParam`.
|
|
895
|
-
|
|
970
|
+
global_env.insert(
|
|
896
|
-
|
|
971
|
+
format!("{}{}:{}", TRAIT_KEY, t.name, m.name),
|
|
897
|
-
|
|
972
|
+
TypeScheme::mono(sig),
|
|
898
|
-
|
|
973
|
+
);
|
|
974
|
+
}
|
|
899
975
|
}
|
|
900
976
|
}
|
|
901
977
|
_ => {}
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -2224,3 +2224,73 @@ fn probe_nested_generic_substitution() {
|
|
|
2224
2224
|
let m2 = "fun f() -> List[Int] =\n List(items: 1).map(|x| x + 1)\n";
|
|
2225
2225
|
assert!(checkSource(&parseClean(&format!("{}{}", GENERIC_DEFS, m2))).is_ok());
|
|
2226
2226
|
}
|
|
2227
|
+
|
|
2228
|
+
// ---- trait types (T3) ----
|
|
2229
|
+
|
|
2230
|
+
const SHAPE_DEFS: &str = "\
|
|
2231
|
+
trait Shape =
|
|
2232
|
+
area() -> Float
|
|
2233
|
+
|
|
2234
|
+
enum Circle(Shape) =
|
|
2235
|
+
| Circle(r: Float)
|
|
2236
|
+
|
|
2237
|
+
fun area(self) -> Float =
|
|
2238
|
+
self.r
|
|
2239
|
+
|
|
2240
|
+
enum Plain =
|
|
2241
|
+
| Plain(n: Int)
|
|
2242
|
+
|
|
2243
|
+
";
|
|
2244
|
+
|
|
2245
|
+
#[test]
|
|
2246
|
+
fn trait_type_accepts_implementer() {
|
|
2247
|
+
let src = format!(
|
|
2248
|
+
"{}fun total(s: Shape) -> Float =\n s.area()\n\nfun go() -> Float =\n total(Circle(r: 2.0))\n",
|
|
2249
|
+
SHAPE_DEFS
|
|
2250
|
+
);
|
|
2251
|
+
let r = checkSource(&parseClean(&src));
|
|
2252
|
+
assert!(r.is_ok(), "got {:?}", r.err());
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
#[test]
|
|
2256
|
+
fn missing_trait_method_rejected_in_checker() {
|
|
2257
|
+
let src = format!(
|
|
2258
|
+
"{}fun total(s: Shape) -> Float =\n s.area()\n\nfun go() -> Float =\n total(Plain(n: 1))\n",
|
|
2259
|
+
SHAPE_DEFS
|
|
2260
|
+
);
|
|
2261
|
+
let msg = errText(checkSource(&parseClean(&src)));
|
|
2262
|
+
assert!(
|
|
2263
|
+
msg.contains("type 'Plain' does not implement trait 'Shape': missing 'area'"),
|
|
2264
|
+
"got {}",
|
|
2265
|
+
msg
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
#[test]
|
|
2270
|
+
fn trait_method_call_has_declared_signature() {
|
|
2271
|
+
let src = format!("{}fun f(s: Shape) -> Int =\n s.area()\n", SHAPE_DEFS);
|
|
2272
|
+
assert!(checkSource(&parseClean(&src)).is_err());
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
#[test]
|
|
2276
|
+
fn primitive_coerces_to_trait_via_number() {
|
|
2277
|
+
let src = "\
|
|
2278
|
+
enum Number =
|
|
2279
|
+
| Int
|
|
2280
|
+
| Float
|
|
2281
|
+
|
|
2282
|
+
fun toStr(self) -> Str =
|
|
2283
|
+
\"n\"
|
|
2284
|
+
|
|
2285
|
+
trait ToStr =
|
|
2286
|
+
toStr() -> Str
|
|
2287
|
+
|
|
2288
|
+
fun show(x: ToStr) -> Str =
|
|
2289
|
+
x.toStr()
|
|
2290
|
+
|
|
2291
|
+
fun go() -> Str =
|
|
2292
|
+
show(5)
|
|
2293
|
+
";
|
|
2294
|
+
let r = checkSource(&parseClean(src));
|
|
2295
|
+
assert!(r.is_ok(), "got {:?}", r.err());
|
|
2296
|
+
}
|
plum-cli/src/main.rs
CHANGED
|
@@ -654,11 +654,11 @@ fn unwrapStrToArray(
|
|
|
654
654
|
s: &wasmtime::Rooted<wasmtime::AnyRef>,
|
|
655
655
|
) -> wasmtime::Result<wasmtime::Rooted<wasmtime::ArrayRef>> {
|
|
656
656
|
let str_struct = s.unwrap_struct(&store)?;
|
|
657
|
-
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut store,
|
|
657
|
+
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut store, 1)? else {
|
|
658
658
|
return Err(wasmtime::Error::msg("Str.data is not a Buffer reference"));
|
|
659
659
|
};
|
|
660
660
|
let buffer_struct = buffer_ref.unwrap_struct(&store)?;
|
|
661
|
-
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut store,
|
|
661
|
+
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut store, 1)? else {
|
|
662
662
|
return Err(wasmtime::Error::msg(
|
|
663
663
|
"Buffer.data is not a []Byte reference",
|
|
664
664
|
));
|
|
@@ -708,7 +708,7 @@ fn makeStrResult(
|
|
|
708
708
|
.ok_or_else(|| wasmtime::Error::msg("expected a concrete struct return type"))?
|
|
709
709
|
.clone();
|
|
710
710
|
let buffer_ty = str_ty
|
|
711
|
-
.field(
|
|
711
|
+
.field(1)
|
|
712
712
|
.and_then(|f| match f.element_type() {
|
|
713
713
|
wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => {
|
|
714
714
|
rt.heap_type().as_concrete_struct().cloned()
|
|
@@ -719,7 +719,7 @@ fn makeStrResult(
|
|
|
719
719
|
wasmtime::Error::msg("expected Str.data to be a concrete Buffer struct type")
|
|
720
720
|
})?;
|
|
721
721
|
let array_ty = buffer_ty
|
|
722
|
-
.field(
|
|
722
|
+
.field(1)
|
|
723
723
|
.and_then(|f| match f.element_type() {
|
|
724
724
|
wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => {
|
|
725
725
|
rt.heap_type().as_concrete_array().cloned()
|
|
@@ -738,6 +738,7 @@ fn makeStrResult(
|
|
|
738
738
|
&mut caller,
|
|
739
739
|
&buffer_pre,
|
|
740
740
|
&[
|
|
741
|
+
wasmtime::Val::I32(6), // Buffer typeId (see plum_wasm_codegen::TYPE_ID_BUFFER)
|
|
741
742
|
wasmtime::Val::AnyRef(Some(arr.to_anyref())),
|
|
742
743
|
wasmtime::Val::I64(bytes.len() as i64),
|
|
743
744
|
],
|
|
@@ -747,7 +748,7 @@ fn makeStrResult(
|
|
|
747
748
|
let str_val = wasmtime::StructRef::new(
|
|
748
749
|
&mut caller,
|
|
749
750
|
&str_pre,
|
|
750
|
-
&[wasmtime::Val::AnyRef(Some(buffer.to_anyref()))],
|
|
751
|
+
&[wasmtime::Val::I32(5), wasmtime::Val::AnyRef(Some(buffer.to_anyref()))], // 5 = Str typeId
|
|
751
752
|
)?;
|
|
752
753
|
Ok(str_val.to_anyref())
|
|
753
754
|
}
|
plum-examples/traits.plum
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import std/Bool
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Number
|
|
4
|
+
import std/Try
|
|
5
|
+
import std/List
|
|
6
|
+
|
|
7
|
+
# A trait name is a type. Any value whose type implements every method the
|
|
8
|
+
# trait declares can be passed where the trait is expected, and calling a
|
|
9
|
+
# method on it dispatches on the value's runtime type id.
|
|
10
|
+
trait Shape =
|
|
11
|
+
area() -> Float
|
|
12
|
+
name() -> Str
|
|
13
|
+
|
|
14
|
+
enum Circle(Shape) =
|
|
15
|
+
| Circle(r: Float)
|
|
16
|
+
|
|
17
|
+
fun area(self) -> Float =
|
|
18
|
+
self.r * self.r * 3.0
|
|
19
|
+
|
|
20
|
+
fun name(self) -> Str =
|
|
21
|
+
"circle"
|
|
22
|
+
|
|
23
|
+
enum Square(Shape) =
|
|
24
|
+
| Square(s: Float)
|
|
25
|
+
|
|
26
|
+
fun area(self) -> Float =
|
|
27
|
+
self.s * self.s
|
|
28
|
+
|
|
29
|
+
fun name(self) -> Str =
|
|
30
|
+
"square"
|
|
31
|
+
|
|
32
|
+
enum Tagged(ToStr) =
|
|
33
|
+
| Tagged(label: Str)
|
|
34
|
+
|
|
35
|
+
fun toStr(self) -> Str =
|
|
36
|
+
"<{self.label}>"
|
|
37
|
+
|
|
38
|
+
fun totalArea(shapes: List[Shape]) -> Float =
|
|
39
|
+
shapes.reduce(0.0, |acc, s| acc + s.area())
|
|
40
|
+
|
|
41
|
+
fun describe(s: Shape) -> Str =
|
|
42
|
+
"{s.name()}"
|
|
43
|
+
|
|
44
|
+
# Int, Float, Str and a user enum all flow through the same `ToStr` slot;
|
|
45
|
+
# the Int and Float are boxed on the way in.
|
|
46
|
+
fun show(x: ToStr) -> Str =
|
|
47
|
+
x.toStr()
|
|
48
|
+
|
|
49
|
+
test "a heterogeneous List[Shape] sums its areas through dynamic dispatch"
|
|
50
|
+
shapes := List[Shape]()
|
|
51
|
+
shapes.add(Circle(r: 1.5))
|
|
52
|
+
shapes.add(Square(s: 1.5))
|
|
53
|
+
shapes.add(Circle(r: 0.5))
|
|
54
|
+
# 6.75 + 2.25 + 0.75
|
|
55
|
+
assert totalArea(shapes) == 9.75
|
|
56
|
+
|
|
57
|
+
test "a method on a trait-typed value picks the implementor by runtime type"
|
|
58
|
+
assert describe(Circle(r: 1.0)) == "circle"
|
|
59
|
+
assert describe(Square(s: 1.0)) == "square"
|
|
60
|
+
|
|
61
|
+
test "ToStr on mixed values: boxed Int, boxed Float, Str and an enum"
|
|
62
|
+
assert show(5) == "5"
|
|
63
|
+
assert show(-12) == "-12"
|
|
64
|
+
assert show(2.5) == "2.5"
|
|
65
|
+
assert show("hi") == "hi"
|
|
66
|
+
assert show(Tagged(label: "x")) == "<x>"
|
plum-runtime/src/main.rs
CHANGED
|
@@ -282,11 +282,11 @@ fn unwrapStrToArray(
|
|
|
282
282
|
s: &wasmtime::Rooted<wasmtime::AnyRef>,
|
|
283
283
|
) -> wasmtime::Result<wasmtime::Rooted<wasmtime::ArrayRef>> {
|
|
284
284
|
let str_struct = s.unwrap_struct(&store)?;
|
|
285
|
-
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut store,
|
|
285
|
+
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut store, 1)? else {
|
|
286
286
|
return Err(wasmtime::Error::msg("Str.data is not a Buffer reference"));
|
|
287
287
|
};
|
|
288
288
|
let buffer_struct = buffer_ref.unwrap_struct(&store)?;
|
|
289
|
-
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut store,
|
|
289
|
+
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut store, 1)? else {
|
|
290
290
|
return Err(wasmtime::Error::msg("Buffer.data is not a []Byte reference"));
|
|
291
291
|
};
|
|
292
292
|
bytes_ref.unwrap_array(&store)
|
|
@@ -321,13 +321,13 @@ fn makeStrResult(
|
|
|
321
321
|
let str_ty = ref_ty.heap_type().as_concrete_struct()
|
|
322
322
|
.ok_or_else(|| wasmtime::Error::msg("expected a concrete struct return type"))?
|
|
323
323
|
.clone();
|
|
324
|
-
let buffer_ty = str_ty.field(
|
|
324
|
+
let buffer_ty = str_ty.field(1)
|
|
325
325
|
.and_then(|f| match f.element_type() {
|
|
326
326
|
wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => rt.heap_type().as_concrete_struct().cloned(),
|
|
327
327
|
_ => None,
|
|
328
328
|
})
|
|
329
329
|
.ok_or_else(|| wasmtime::Error::msg("expected Str.data to be a concrete Buffer struct type"))?;
|
|
330
|
-
let array_ty = buffer_ty.field(
|
|
330
|
+
let array_ty = buffer_ty.field(1)
|
|
331
331
|
.and_then(|f| match f.element_type() {
|
|
332
332
|
wasmtime::StorageType::ValType(wasmtime::ValType::Ref(rt)) => rt.heap_type().as_concrete_array().cloned(),
|
|
333
333
|
_ => None,
|
|
@@ -341,11 +341,11 @@ fn makeStrResult(
|
|
|
341
341
|
let buffer = wasmtime::StructRef::new(
|
|
342
342
|
&mut caller,
|
|
343
343
|
&buffer_pre,
|
|
344
|
-
&[wasmtime::Val::AnyRef(Some(arr.to_anyref())), wasmtime::Val::I64(bytes.len() as i64)],
|
|
344
|
+
&[wasmtime::Val::I32(6), wasmtime::Val::AnyRef(Some(arr.to_anyref())), wasmtime::Val::I64(bytes.len() as i64)], // 6 = Buffer typeId
|
|
345
345
|
)?;
|
|
346
346
|
|
|
347
347
|
let str_pre = wasmtime::StructRefPre::new(&mut caller, str_ty);
|
|
348
|
-
let str_val = wasmtime::StructRef::new(&mut caller, &str_pre, &[wasmtime::Val::AnyRef(Some(buffer.to_anyref()))])?;
|
|
348
|
+
let str_val = wasmtime::StructRef::new(&mut caller, &str_pre, &[wasmtime::Val::I32(5), wasmtime::Val::AnyRef(Some(buffer.to_anyref()))])?; // 5 = Str typeId
|
|
349
349
|
Ok(str_val.to_anyref())
|
|
350
350
|
}
|
|
351
351
|
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -629,20 +629,52 @@ fn collectTagNames(s: &str, out: &mut std::collections::BTreeSet<String>) {
|
|
|
629
629
|
/// reference values are stored as-is.
|
|
630
630
|
fn boxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
631
631
|
match plumTypeToValtype(ty) {
|
|
632
|
-
ValType::I64 => Instruction::
|
|
632
|
+
ValType::I64 => Instruction::Call(ctx.gc_types.box_i64_fn).encode(body),
|
|
633
|
-
ValType::F64 => Instruction::
|
|
633
|
+
ValType::F64 => Instruction::Call(ctx.gc_types.box_f64_fn).encode(body),
|
|
634
|
-
ValType::I32 => Instruction::
|
|
634
|
+
ValType::I32 => Instruction::Call(ctx.gc_types.box_i32_fn).encode(body),
|
|
635
635
|
_ => {}
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
/// Boxes the primitive on top of the stack (`ty`) into its `$Box*` struct when
|
|
640
|
+
/// it flows into a slot whose wasm type is `expected` and `ty` itself is not a
|
|
641
|
+
/// reference — i.e. an `Int`/`Float`/`Byte` entering a trait-typed (`$Obj`) slot.
|
|
642
|
+
fn boxIfPrimitiveIntoObj(ty: &PlumType, expected: ValType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
643
|
+
let ValType::Ref(rt) = expected else { return };
|
|
644
|
+
if rt.heap_type != HeapType::Concrete(ctx.gc_types.obj_type_idx) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if matches!(ty, PlumType::TInt | PlumType::TFloat | PlumType::TByte) {
|
|
648
|
+
boxTagPayload(ty, body, ctx);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/// Compiles `expr` and, if it is a primitive flowing into a trait-typed slot of
|
|
653
|
+
/// wasm type `expected`, boxes it.
|
|
654
|
+
fn compileExprAs(
|
|
655
|
+
expr: &ast::Expr,
|
|
656
|
+
expected: Option<ValType>,
|
|
657
|
+
body: &mut Vec<u8>,
|
|
658
|
+
ctx: &LocalCtx,
|
|
659
|
+
state: &mut ModuleState,
|
|
660
|
+
) -> Result<(), String> {
|
|
661
|
+
compileExpr(expr, body, ctx, state)?;
|
|
662
|
+
if let Some(vt @ ValType::Ref(rt)) = expected {
|
|
663
|
+
if rt.heap_type == HeapType::Concrete(ctx.gc_types.obj_type_idx) {
|
|
664
|
+
let ty = inferLocalType(expr, ctx);
|
|
665
|
+
boxIfPrimitiveIntoObj(&ty, vt, body, ctx);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
Ok(())
|
|
669
|
+
}
|
|
670
|
+
|
|
639
671
|
/// Inverse of `boxTagPayload`: an `anyref` payload on the stack becomes a `ty` value.
|
|
640
672
|
fn unboxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
641
673
|
let unbox = |idx: u32, body: &mut Vec<u8>| {
|
|
642
674
|
Instruction::RefCastNonNull(HeapType::Concrete(idx)).encode(body);
|
|
643
675
|
Instruction::StructGet {
|
|
644
676
|
struct_type_index: idx,
|
|
645
|
-
field_index: 0,
|
|
677
|
+
field_index: fld(0),
|
|
646
678
|
}
|
|
647
679
|
.encode(body);
|
|
648
680
|
};
|
|
@@ -668,6 +700,7 @@ fn compileTagLiteral(
|
|
|
668
700
|
ctx: &LocalCtx,
|
|
669
701
|
state: &mut ModuleState,
|
|
670
702
|
) -> Result<(), String> {
|
|
703
|
+
Instruction::I32Const(TYPE_ID_TAG).encode(body);
|
|
671
704
|
Instruction::I32Const(tagId(name)).encode(body);
|
|
672
705
|
match payload {
|
|
673
706
|
Some(e) => {
|
|
@@ -866,6 +899,46 @@ pub struct GcTypeRegistry {
|
|
|
866
899
|
pub box_i64_idx: u32,
|
|
867
900
|
pub box_f64_idx: u32,
|
|
868
901
|
pub box_i32_idx: u32,
|
|
902
|
+
/// The `$Obj {typeId: i32}` supertype of every class/enum/tag/box struct.
|
|
903
|
+
pub obj_type_idx: u32,
|
|
904
|
+
/// Enum name and variant name -> the runtime `typeId` stored in field 0 of
|
|
905
|
+
/// every value of that type (all variants of an enum share the enum's id).
|
|
906
|
+
pub type_ids: HashMap<String, i32>,
|
|
907
|
+
/// Box-helper function indices `(i64) -> $BoxI64`, `(f64) -> $BoxF64`,
|
|
908
|
+
/// `(i32) -> $BoxI32` (they push the header typeId themselves).
|
|
909
|
+
pub box_i64_fn: u32,
|
|
910
|
+
pub box_f64_fn: u32,
|
|
911
|
+
pub box_i32_fn: u32,
|
|
912
|
+
/// Helper `(arr: array<i8>, len: i64) -> Str` building `Str{Buffer{arr,len}}`
|
|
913
|
+
/// with correct headers; `None` when the program has no `Str`/`Buffer`.
|
|
914
|
+
pub mk_str_fn: Option<u32>,
|
|
915
|
+
/// `(trait, method)` -> its `$dyn$<Trait>$<method>` dispatcher function.
|
|
916
|
+
pub dyn_fns: HashMap<(String, String), u32>,
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/// Box type ids (see the plan's Global constraints): 0 is `$Obj` itself.
|
|
920
|
+
pub const TYPE_ID_BOX_I64: i32 = 1;
|
|
921
|
+
pub const TYPE_ID_BOX_F64: i32 = 2;
|
|
922
|
+
pub const TYPE_ID_BOX_I32: i32 = 3;
|
|
923
|
+
pub const TYPE_ID_TAG: i32 = 4;
|
|
924
|
+
/// `Str` and `Buffer` get fixed ids so hosts can build them without a lookup.
|
|
925
|
+
pub const TYPE_ID_STR: i32 = 5;
|
|
926
|
+
pub const TYPE_ID_BUFFER: i32 = 6;
|
|
927
|
+
/// First id handed to a declared class/enum, in declaration order.
|
|
928
|
+
pub const TYPE_ID_FIRST_DECLARED: i32 = 16;
|
|
929
|
+
|
|
930
|
+
/// Every class/enum/tag/box struct carries `typeId: i32` as field 0, so a
|
|
931
|
+
/// declared field `i` lives at struct field `i + OBJ_HEADER_FIELDS`.
|
|
932
|
+
const OBJ_HEADER_FIELDS: u32 = 1;
|
|
933
|
+
|
|
934
|
+
/// Struct field index of declared payload field `i` (skipping the typeId header).
|
|
935
|
+
fn fld(i: usize) -> u32 {
|
|
936
|
+
i as u32 + OBJ_HEADER_FIELDS
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/// The runtime typeId of a variant/enum, or 0 when unknown.
|
|
940
|
+
fn typeIdOf(r: &GcTypeRegistry, name: &str) -> i32 {
|
|
941
|
+
r.type_ids.get(name).copied().unwrap_or(0)
|
|
869
942
|
}
|
|
870
943
|
|
|
871
944
|
/// Whether `name` is `Array` itself or one of its monomorphized specializations
|
|
@@ -955,6 +1028,8 @@ fn buildGcTypeRegistry(
|
|
|
955
1028
|
) -> GcTypeRegistry {
|
|
956
1029
|
enum Slot {
|
|
957
1030
|
Str,
|
|
1031
|
+
/// `$Obj {typeId: i32}` — supertype of every struct below.
|
|
1032
|
+
Obj,
|
|
958
1033
|
EnumSuper(String),
|
|
959
1034
|
Variant(String),
|
|
960
1035
|
/// The single shared `array<anyref>` type every `Array[T]`
|
|
@@ -968,13 +1043,17 @@ fn buildGcTypeRegistry(
|
|
|
968
1043
|
|
|
969
1044
|
// Pass 1: assign every entry a slot (and therefore a type index) up front, before
|
|
970
1045
|
// any field list is built, so field-type resolution can reference ANY other entry.
|
|
971
|
-
let mut slots: Vec<Slot> = vec![Slot::Str];
|
|
1046
|
+
let mut slots: Vec<Slot> = vec![Slot::Str, Slot::Obj];
|
|
1047
|
+
let obj_type_idx: u32 = 1;
|
|
1048
|
+
let mut type_ids: HashMap<String, i32> = HashMap::new();
|
|
1049
|
+
let mut next_type_id = TYPE_ID_FIRST_DECLARED;
|
|
972
1050
|
let mut class_type_idx: HashMap<String, u32> = HashMap::new();
|
|
973
1051
|
let mut enum_super_type_idx: HashMap<String, u32> = HashMap::new();
|
|
974
1052
|
let mut variant_type_idx: HashMap<String, u32> = HashMap::new();
|
|
975
1053
|
let mut array_ref_slot: Option<u32> = None;
|
|
976
1054
|
let mut erased_enum_super: HashMap<String, u32> = HashMap::new();
|
|
977
1055
|
let mut erased_variant_idx: HashMap<String, u32> = HashMap::new();
|
|
1056
|
+
let mut erased_enum_id: HashMap<String, i32> = HashMap::new();
|
|
978
1057
|
|
|
979
1058
|
// `Bool` is an ordinary enum now (`type Bool = | True | False` in
|
|
980
1059
|
// `libs/std/Bool.plum`) — registered exactly like any other enum
|
|
@@ -988,6 +1067,12 @@ fn buildGcTypeRegistry(
|
|
|
988
1067
|
));
|
|
989
1068
|
}
|
|
990
1069
|
}
|
|
1070
|
+
// A trait name used as a type lowers to `ref null $Obj`.
|
|
1071
|
+
for item in &source.items {
|
|
1072
|
+
if let ast::Item::Trait(t) = item {
|
|
1073
|
+
class_type_idx.insert(t.name.clone(), obj_type_idx);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
991
1076
|
for (enum_name, variant_names) in &enum_decls {
|
|
992
1077
|
// `Array`/`Array$T` is a RECORD-shaped enum (single variant sharing the
|
|
993
1078
|
// enum's own name — the `type Array[T] = ...` replacement) whose values
|
|
@@ -1016,6 +1101,13 @@ fn buildGcTypeRegistry(
|
|
|
1016
1101
|
let erased_enum = plum_checker::erasedEnumName(enum_name);
|
|
1017
1102
|
if let Some(existing_super) = erased_enum_super.get(&erased_enum) {
|
|
1018
1103
|
enum_super_type_idx.insert(enum_name.clone(), *existing_super);
|
|
1104
|
+
// Shares the first specialization's wasm types, and so its typeId.
|
|
1105
|
+
if let Some(first_id) = erased_enum_id.get(&erased_enum).copied() {
|
|
1106
|
+
type_ids.insert(enum_name.clone(), first_id);
|
|
1107
|
+
for vname in variant_names {
|
|
1108
|
+
type_ids.insert(vname.clone(), first_id);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1019
1111
|
for vname in variant_names {
|
|
1020
1112
|
let ev = plum_checker::erasedEnumName(vname);
|
|
1021
1113
|
if let Some(idx) = erased_variant_idx.get(&ev) {
|
|
@@ -1028,6 +1120,20 @@ fn buildGcTypeRegistry(
|
|
|
1028
1120
|
}
|
|
1029
1121
|
continue;
|
|
1030
1122
|
}
|
|
1123
|
+
let this_id = match enum_name.as_str() {
|
|
1124
|
+
"Str" => TYPE_ID_STR,
|
|
1125
|
+
"Buffer" => TYPE_ID_BUFFER,
|
|
1126
|
+
_ => {
|
|
1127
|
+
let id = next_type_id;
|
|
1128
|
+
next_type_id += 1;
|
|
1129
|
+
id
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
type_ids.insert(enum_name.clone(), this_id);
|
|
1133
|
+
for vname in variant_names {
|
|
1134
|
+
type_ids.insert(vname.clone(), this_id);
|
|
1135
|
+
}
|
|
1136
|
+
erased_enum_id.insert(erased_enum.clone(), this_id);
|
|
1031
1137
|
enum_super_type_idx.insert(enum_name.clone(), slots.len() as u32);
|
|
1032
1138
|
erased_enum_super.insert(erased_enum, slots.len() as u32);
|
|
1033
1139
|
slots.push(Slot::EnumSuper(enum_name.clone()));
|
|
@@ -1072,6 +1178,13 @@ fn buildGcTypeRegistry(
|
|
|
1072
1178
|
box_i64_idx,
|
|
1073
1179
|
box_f64_idx,
|
|
1074
1180
|
box_i32_idx,
|
|
1181
|
+
obj_type_idx,
|
|
1182
|
+
type_ids,
|
|
1183
|
+
box_i64_fn: 0,
|
|
1184
|
+
box_f64_fn: 0,
|
|
1185
|
+
box_i32_fn: 0,
|
|
1186
|
+
mk_str_fn: None,
|
|
1187
|
+
dyn_fns: HashMap::new(),
|
|
1075
1188
|
};
|
|
1076
1189
|
|
|
1077
1190
|
// Pass 2: build the real SubType for every slot, now that every cross-reference
|
|
@@ -1101,16 +1214,14 @@ fn buildGcTypeRegistry(
|
|
|
1101
1214
|
// trap at runtime whenever `self` isn't actually THAT variant).
|
|
1102
1215
|
Slot::EnumSuper(enum_name) => {
|
|
1103
1216
|
let params = enum_params.get(enum_name).cloned().unwrap_or_default();
|
|
1104
|
-
let field_types: Vec<FieldType> =
|
|
1217
|
+
let mut field_types: Vec<FieldType> = vec![typeIdField()];
|
|
1105
|
-
.iter()
|
|
1106
|
-
|
|
1218
|
+
field_types.extend(params.iter().map(|(_, ty)| FieldType {
|
|
1107
|
-
|
|
1219
|
+
element_type: StorageType::Val(plumTypeToGcValtype(ty, ®istry)),
|
|
1108
|
-
|
|
1220
|
+
mutable: true,
|
|
1109
|
-
|
|
1221
|
+
}));
|
|
1110
|
-
.collect();
|
|
1111
1222
|
SubType {
|
|
1112
1223
|
is_final: false,
|
|
1113
|
-
supertype_idx:
|
|
1224
|
+
supertype_idx: Some(obj_type_idx),
|
|
1114
1225
|
composite_type: CompositeType {
|
|
1115
1226
|
inner: CompositeInnerType::Struct(StructType {
|
|
1116
1227
|
fields: field_types.into(),
|
|
@@ -1139,14 +1250,11 @@ fn buildGcTypeRegistry(
|
|
|
1139
1250
|
// supertype's own fields (see the `EnumSuper` arm above) — a variant with
|
|
1140
1251
|
// zero ADDED fields beyond its supertype is still valid wasm-gc
|
|
1141
1252
|
// subtyping, so no special-casing is needed here.
|
|
1142
|
-
let field_types: Vec<FieldType> =
|
|
1253
|
+
let mut field_types: Vec<FieldType> = vec![typeIdField()];
|
|
1143
|
-
.field_types
|
|
1144
|
-
.iter()
|
|
1145
|
-
|
|
1254
|
+
field_types.extend(info.field_types.iter().map(|ty| FieldType {
|
|
1146
|
-
|
|
1255
|
+
element_type: StorageType::Val(plumTypeToGcValtype(ty, ®istry)),
|
|
1147
|
-
|
|
1256
|
+
mutable: true,
|
|
1148
|
-
|
|
1257
|
+
}));
|
|
1149
|
-
.collect();
|
|
1150
1258
|
SubType {
|
|
1151
1259
|
is_final: true,
|
|
1152
1260
|
supertype_idx: Some(super_idx),
|
|
@@ -1158,12 +1266,23 @@ fn buildGcTypeRegistry(
|
|
|
1158
1266
|
},
|
|
1159
1267
|
}
|
|
1160
1268
|
}
|
|
1269
|
+
Slot::Obj => SubType {
|
|
1270
|
+
is_final: false,
|
|
1271
|
+
supertype_idx: None,
|
|
1272
|
+
composite_type: CompositeType {
|
|
1273
|
+
inner: CompositeInnerType::Struct(StructType {
|
|
1274
|
+
fields: vec![typeIdField()].into(),
|
|
1275
|
+
}),
|
|
1276
|
+
shared: false,
|
|
1277
|
+
},
|
|
1278
|
+
},
|
|
1161
1279
|
Slot::Tag => SubType {
|
|
1162
1280
|
is_final: true,
|
|
1163
|
-
supertype_idx:
|
|
1281
|
+
supertype_idx: Some(obj_type_idx),
|
|
1164
1282
|
composite_type: CompositeType {
|
|
1165
1283
|
inner: CompositeInnerType::Struct(StructType {
|
|
1166
1284
|
fields: vec![
|
|
1285
|
+
typeIdField(),
|
|
1167
1286
|
FieldType {
|
|
1168
1287
|
element_type: StorageType::Val(ValType::I32),
|
|
1169
1288
|
mutable: false,
|
|
@@ -1178,9 +1297,9 @@ fn buildGcTypeRegistry(
|
|
|
1178
1297
|
shared: false,
|
|
1179
1298
|
},
|
|
1180
1299
|
},
|
|
1181
|
-
Slot::BoxI64 => boxSubType(ValType::I64),
|
|
1300
|
+
Slot::BoxI64 => boxSubType(ValType::I64, obj_type_idx),
|
|
1182
|
-
Slot::BoxF64 => boxSubType(ValType::F64),
|
|
1301
|
+
Slot::BoxF64 => boxSubType(ValType::F64, obj_type_idx),
|
|
1183
|
-
Slot::BoxI32 => boxSubType(ValType::I32),
|
|
1302
|
+
Slot::BoxI32 => boxSubType(ValType::I32, obj_type_idx),
|
|
1184
1303
|
Slot::ArrayRef => SubType {
|
|
1185
1304
|
is_final: true,
|
|
1186
1305
|
supertype_idx: None,
|
|
@@ -1200,16 +1319,27 @@ fn buildGcTypeRegistry(
|
|
|
1200
1319
|
registry
|
|
1201
1320
|
}
|
|
1202
1321
|
|
|
1322
|
+
/// The immutable `typeId: i32` header field every `$Obj` subtype starts with.
|
|
1323
|
+
fn typeIdField() -> FieldType {
|
|
1324
|
+
FieldType {
|
|
1325
|
+
element_type: StorageType::Val(ValType::I32),
|
|
1326
|
+
mutable: false,
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1203
|
-
fn boxSubType(vt: ValType) -> SubType {
|
|
1330
|
+
fn boxSubType(vt: ValType, obj_type_idx: u32) -> SubType {
|
|
1204
1331
|
SubType {
|
|
1205
1332
|
is_final: true,
|
|
1206
|
-
supertype_idx:
|
|
1333
|
+
supertype_idx: Some(obj_type_idx),
|
|
1207
1334
|
composite_type: CompositeType {
|
|
1208
1335
|
inner: CompositeInnerType::Struct(StructType {
|
|
1209
|
-
fields: vec![
|
|
1336
|
+
fields: vec![
|
|
1337
|
+
typeIdField(),
|
|
1338
|
+
FieldType {
|
|
1210
|
-
|
|
1339
|
+
element_type: StorageType::Val(vt),
|
|
1211
|
-
|
|
1340
|
+
mutable: false,
|
|
1212
|
-
|
|
1341
|
+
},
|
|
1342
|
+
]
|
|
1213
1343
|
.into(),
|
|
1214
1344
|
}),
|
|
1215
1345
|
shared: false,
|
|
@@ -1647,13 +1777,43 @@ fn compileSourceInner(
|
|
|
1647
1777
|
let global_idx = module.addGlobal(gcRef(variant_idx), true, &init);
|
|
1648
1778
|
singleton_globals.insert(name.clone(), global_idx);
|
|
1649
1779
|
|
|
1780
|
+
// A payload-free variant is exactly `{typeId}`.
|
|
1781
|
+
Instruction::I32Const(typeIdOf(&gc_types, name)).encode(&mut start_body);
|
|
1650
|
-
Instruction::
|
|
1782
|
+
Instruction::StructNew(variant_idx).encode(&mut start_body);
|
|
1651
1783
|
Instruction::GlobalSet(global_idx).encode(&mut start_body);
|
|
1652
1784
|
}
|
|
1653
1785
|
let start_type_idx = module.addType(&[], &[]);
|
|
1654
1786
|
let start_func_idx = module.addFunction(start_type_idx, &[]);
|
|
1655
1787
|
module.setStartFunction(start_func_idx);
|
|
1656
1788
|
|
|
1789
|
+
// Box helpers (`i64 -> $BoxI64`, ...) and the `Str` builder push the header
|
|
1790
|
+
// typeId themselves, so callers only supply the payload.
|
|
1791
|
+
gc_types.box_i64_fn =
|
|
1792
|
+
registerBoxHelper(&mut module, ValType::I64, TYPE_ID_BOX_I64, gc_types.box_i64_idx);
|
|
1793
|
+
gc_types.box_f64_fn =
|
|
1794
|
+
registerBoxHelper(&mut module, ValType::F64, TYPE_ID_BOX_F64, gc_types.box_f64_idx);
|
|
1795
|
+
gc_types.box_i32_fn =
|
|
1796
|
+
registerBoxHelper(&mut module, ValType::I32, TYPE_ID_BOX_I32, gc_types.box_i32_idx);
|
|
1797
|
+
if let (Some(&buffer_idx), Some(&str_idx)) = (
|
|
1798
|
+
gc_types.class_type_idx.get("Buffer"),
|
|
1799
|
+
gc_types.class_type_idx.get("Str"),
|
|
1800
|
+
) {
|
|
1801
|
+
let mut b = vec![0u8];
|
|
1802
|
+
Instruction::I32Const(TYPE_ID_STR).encode(&mut b);
|
|
1803
|
+
Instruction::I32Const(TYPE_ID_BUFFER).encode(&mut b);
|
|
1804
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
1805
|
+
Instruction::LocalGet(1).encode(&mut b);
|
|
1806
|
+
Instruction::StructNew(buffer_idx).encode(&mut b);
|
|
1807
|
+
Instruction::StructNew(str_idx).encode(&mut b);
|
|
1808
|
+
Instruction::End.encode(&mut b);
|
|
1809
|
+
let t = module.addType(
|
|
1810
|
+
&[gcRef(gc_types.byte_array_type_idx), ValType::I64],
|
|
1811
|
+
&[gcRef(str_idx)],
|
|
1812
|
+
);
|
|
1813
|
+
gc_types.mk_str_fn = Some(module.addFunction(t, &b));
|
|
1814
|
+
}
|
|
1815
|
+
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1816
|
+
|
|
1657
1817
|
// Closure wasm signature -> function-type index, deduped so every closure/call site
|
|
1658
1818
|
// of the same shape shares one `call_indirect` type.
|
|
1659
1819
|
let mut closure_call_types: HashMap<ClosureSigKey, u32> = HashMap::new();
|
|
@@ -1706,6 +1866,17 @@ fn compileSourceInner(
|
|
|
1706
1866
|
}
|
|
1707
1867
|
}
|
|
1708
1868
|
|
|
1869
|
+
registerDynDispatchers(
|
|
1870
|
+
&mut module,
|
|
1871
|
+
source,
|
|
1872
|
+
&methods,
|
|
1873
|
+
&enum_variants,
|
|
1874
|
+
&func_ids,
|
|
1875
|
+
&func_sigs,
|
|
1876
|
+
&mut gc_types,
|
|
1877
|
+
);
|
|
1878
|
+
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1879
|
+
|
|
1709
1880
|
let fns: Vec<&ast::Fn> = source
|
|
1710
1881
|
.items
|
|
1711
1882
|
.iter()
|
|
@@ -1995,6 +2166,177 @@ fn compileSourceInner(
|
|
|
1995
2166
|
Ok(module.finish())
|
|
1996
2167
|
}
|
|
1997
2168
|
|
|
2169
|
+
/// `(payload) -> ref $Box*`: allocates a box with its header typeId set.
|
|
2170
|
+
fn registerBoxHelper(module: &mut WasmModule, vt: ValType, type_id: i32, box_idx: u32) -> u32 {
|
|
2171
|
+
let mut b = vec![0u8];
|
|
2172
|
+
Instruction::I32Const(type_id).encode(&mut b);
|
|
2173
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2174
|
+
Instruction::StructNew(box_idx).encode(&mut b);
|
|
2175
|
+
Instruction::End.encode(&mut b);
|
|
2176
|
+
let t = module.addType(&[vt], &[gcRef(box_idx)]);
|
|
2177
|
+
module.addFunction(t, &b)
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
/// Emits one `$dyn$<Trait>$<method>(self: ref null $Obj, args...)` dispatcher per
|
|
2181
|
+
/// trait method that has at least one implementor: it reads the receiver's
|
|
2182
|
+
/// `typeId` and calls the matching concrete method. Declared enums dispatch by
|
|
2183
|
+
/// their own id; boxed `Int`/`Float`/`Byte` unbox and call the primitive's method
|
|
2184
|
+
/// (or, for `Int`/`Float`, wrap into the "union" enum such as `Number` that
|
|
2185
|
+
/// defines it — the same fallback a direct `x.toStr()` call uses). Only methods
|
|
2186
|
+
/// present in `func_ids` (i.e. actually compiled) are ever referenced.
|
|
2187
|
+
fn registerDynDispatchers(
|
|
2188
|
+
module: &mut WasmModule,
|
|
2189
|
+
source: &ast::Source,
|
|
2190
|
+
methods: &MethodEnv,
|
|
2191
|
+
enum_variants: &EnumVariants,
|
|
2192
|
+
func_ids: &HashMap<String, u32>,
|
|
2193
|
+
func_sigs: &HashMap<String, FuncSig>,
|
|
2194
|
+
gc_types: &mut GcTypeRegistry,
|
|
2195
|
+
) {
|
|
2196
|
+
let obj_idx = gc_types.obj_type_idx;
|
|
2197
|
+
let enum_names: Vec<&str> = source
|
|
2198
|
+
.items
|
|
2199
|
+
.iter()
|
|
2200
|
+
.filter_map(|i| match i {
|
|
2201
|
+
ast::Item::Enum(e) if !isArraySpecialization(&e.name) => Some(e.name.as_str()),
|
|
2202
|
+
_ => None,
|
|
2203
|
+
})
|
|
2204
|
+
.collect();
|
|
2205
|
+
let mut registered: Vec<((String, String), u32)> = Vec::new();
|
|
2206
|
+
for item in &source.items {
|
|
2207
|
+
let ast::Item::Trait(t) = item else { continue };
|
|
2208
|
+
for m in &t.methods {
|
|
2209
|
+
let key = (t.name.clone(), m.name.clone());
|
|
2210
|
+
let Some(PlumType::TFun(ps, ret)) = methods.get(&key) else {
|
|
2211
|
+
continue;
|
|
2212
|
+
};
|
|
2213
|
+
if ps
|
|
2214
|
+
.iter()
|
|
2215
|
+
.any(|p| matches!(p, PlumType::TVariadic(_) | PlumType::TFun(..)))
|
|
2216
|
+
{
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
let params_vt: Vec<ValType> = ps.iter().map(plumTypeToValtype).collect();
|
|
2220
|
+
let ret_vt: Option<ValType> = if **ret == PlumType::TUnit {
|
|
2221
|
+
None
|
|
2222
|
+
} else {
|
|
2223
|
+
Some(plumTypeToValtype(ret))
|
|
2224
|
+
};
|
|
2225
|
+
let sig_matches = |fkey: &str| {
|
|
2226
|
+
func_sigs
|
|
2227
|
+
.get(fkey)
|
|
2228
|
+
.is_some_and(|s| s.params.get(1..) == Some(¶ms_vt[..]) && s.ret == ret_vt)
|
|
2229
|
+
};
|
|
2230
|
+
// (typeId, arm code) — code leaves the call result on the stack.
|
|
2231
|
+
let mut arms: Vec<(i32, Vec<u8>)> = Vec::new();
|
|
2232
|
+
let push_args = |b: &mut Vec<u8>| {
|
|
2233
|
+
for i in 0..params_vt.len() {
|
|
2234
|
+
Instruction::LocalGet(i as u32 + 1).encode(b);
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
let mut seen_ids = std::collections::HashSet::new();
|
|
2238
|
+
for en in &enum_names {
|
|
2239
|
+
let fkey = format!("{}::{}", en, m.name);
|
|
2240
|
+
let Some(&fidx) = func_ids.get(&fkey) else { continue };
|
|
2241
|
+
let id = typeIdOf(gc_types, en);
|
|
2242
|
+
if id == 0 || !sig_matches(&fkey) || !seen_ids.insert(id) {
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
let Some(ValType::Ref(self_rt)) = func_sigs.get(&fkey).and_then(|s| s.params.first().copied())
|
|
2246
|
+
else {
|
|
2247
|
+
continue;
|
|
2248
|
+
};
|
|
2249
|
+
let mut b = Vec::new();
|
|
2250
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2251
|
+
Instruction::RefCastNullable(self_rt.heap_type).encode(&mut b);
|
|
2252
|
+
push_args(&mut b);
|
|
2253
|
+
Instruction::Call(fidx).encode(&mut b);
|
|
2254
|
+
arms.push((id, b));
|
|
2255
|
+
}
|
|
2256
|
+
// Boxed primitives.
|
|
2257
|
+
for (prim, vt, box_idx, tid) in [
|
|
2258
|
+
("Int", ValType::I64, gc_types.box_i64_idx, TYPE_ID_BOX_I64),
|
|
2259
|
+
("Float", ValType::F64, gc_types.box_f64_idx, TYPE_ID_BOX_F64),
|
|
2260
|
+
("Byte", ValType::I32, gc_types.box_i32_idx, TYPE_ID_BOX_I32),
|
|
2261
|
+
] {
|
|
2262
|
+
let direct = format!("{}::{}", prim, m.name);
|
|
2263
|
+
let mut b = Vec::new();
|
|
2264
|
+
if func_ids.contains_key(&direct)
|
|
2265
|
+
&& sig_matches(&direct)
|
|
2266
|
+
&& func_sigs.get(&direct).and_then(|s| s.params.first().copied()) == Some(vt)
|
|
2267
|
+
{
|
|
2268
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2269
|
+
Instruction::RefCastNonNull(HeapType::Concrete(box_idx)).encode(&mut b);
|
|
2270
|
+
Instruction::StructGet {
|
|
2271
|
+
struct_type_index: box_idx,
|
|
2272
|
+
field_index: fld(0),
|
|
2273
|
+
}
|
|
2274
|
+
.encode(&mut b);
|
|
2275
|
+
push_args(&mut b);
|
|
2276
|
+
Instruction::Call(func_ids[&direct]).encode(&mut b);
|
|
2277
|
+
} else if let Some(info) = enum_variants.get(prim) {
|
|
2278
|
+
let wrap_key = format!("{}::{}", info.enum_name, m.name);
|
|
2279
|
+
let (Some(&fidx), Some(&variant_idx)) =
|
|
2280
|
+
(func_ids.get(&wrap_key), gc_types.variant_type_idx.get(prim))
|
|
2281
|
+
else {
|
|
2282
|
+
continue;
|
|
2283
|
+
};
|
|
2284
|
+
if !sig_matches(&wrap_key) {
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
Instruction::I32Const(typeIdOf(gc_types, prim)).encode(&mut b);
|
|
2288
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2289
|
+
Instruction::RefCastNonNull(HeapType::Concrete(box_idx)).encode(&mut b);
|
|
2290
|
+
Instruction::StructGet {
|
|
2291
|
+
struct_type_index: box_idx,
|
|
2292
|
+
field_index: fld(0),
|
|
2293
|
+
}
|
|
2294
|
+
.encode(&mut b);
|
|
2295
|
+
Instruction::StructNew(variant_idx).encode(&mut b);
|
|
2296
|
+
push_args(&mut b);
|
|
2297
|
+
Instruction::Call(fidx).encode(&mut b);
|
|
2298
|
+
} else {
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
arms.push((tid, b));
|
|
2302
|
+
}
|
|
2303
|
+
if arms.is_empty() {
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
let mut all_params = vec![gcRef(obj_idx)];
|
|
2307
|
+
all_params.extend(params_vt.iter().copied());
|
|
2308
|
+
let results: Vec<ValType> = ret_vt.into_iter().collect();
|
|
2309
|
+
let tid_local = all_params.len() as u32;
|
|
2310
|
+
let mut body = Vec::new();
|
|
2311
|
+
body.extend(encodeLeb128U32(1));
|
|
2312
|
+
body.extend(encodeLeb128U32(1));
|
|
2313
|
+
ValType::I32.encode(&mut body);
|
|
2314
|
+
Instruction::LocalGet(0).encode(&mut body);
|
|
2315
|
+
Instruction::StructGet {
|
|
2316
|
+
struct_type_index: obj_idx,
|
|
2317
|
+
field_index: 0,
|
|
2318
|
+
}
|
|
2319
|
+
.encode(&mut body);
|
|
2320
|
+
Instruction::LocalSet(tid_local).encode(&mut body);
|
|
2321
|
+
for (id, code) in &arms {
|
|
2322
|
+
Instruction::LocalGet(tid_local).encode(&mut body);
|
|
2323
|
+
Instruction::I32Const(*id).encode(&mut body);
|
|
2324
|
+
Instruction::I32Eq.encode(&mut body);
|
|
2325
|
+
Instruction::If(BlockType::Empty).encode(&mut body);
|
|
2326
|
+
body.extend(code);
|
|
2327
|
+
Instruction::Return.encode(&mut body);
|
|
2328
|
+
Instruction::End.encode(&mut body);
|
|
2329
|
+
}
|
|
2330
|
+
Instruction::Unreachable.encode(&mut body);
|
|
2331
|
+
Instruction::End.encode(&mut body);
|
|
2332
|
+
let t = module.addType(&all_params, &results);
|
|
2333
|
+
let fidx = module.addFunction(t, &body);
|
|
2334
|
+
registered.push((key, fidx));
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
gc_types.dyn_fns.extend(registered);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
1998
2340
|
/// Registers `__string_concat(a: ref Str, b: ref Str) -> ref Str`, a hand-written
|
|
1999
2341
|
/// runtime helper backing string interpolation. Unwraps both `Str` params down
|
|
2000
2342
|
/// to their raw `array<i8>`s (`unwrapStrToBytes`) — which each track their own
|
|
@@ -2451,9 +2793,10 @@ fn wrapBytesAsStr(body: &mut Vec<u8>, push_len: impl FnOnce(&mut Vec<u8>)) {
|
|
|
2451
2793
|
let Some((buffer_type_idx, str_type_idx)) = types else {
|
|
2452
2794
|
return;
|
|
2453
2795
|
};
|
|
2796
|
+
let _ = (buffer_type_idx, str_type_idx);
|
|
2454
2797
|
push_len(body);
|
|
2455
|
-
|
|
2798
|
+
let mk = withGcTypes(|r| r.mk_str_fn).expect("mk_str_fn registered with Str/Buffer");
|
|
2456
|
-
Instruction::
|
|
2799
|
+
Instruction::Call(mk).encode(body);
|
|
2457
2800
|
}
|
|
2458
2801
|
|
|
2459
2802
|
/// The inverse of `wrapBytesAsStr`: given a `Str` value already on the stack,
|
|
@@ -2474,12 +2817,12 @@ fn unwrapStrToBytes(body: &mut Vec<u8>) {
|
|
|
2474
2817
|
};
|
|
2475
2818
|
Instruction::StructGet {
|
|
2476
2819
|
struct_type_index: str_type_idx,
|
|
2477
|
-
field_index: 0,
|
|
2820
|
+
field_index: fld(0),
|
|
2478
2821
|
}
|
|
2479
2822
|
.encode(body);
|
|
2480
2823
|
Instruction::StructGet {
|
|
2481
2824
|
struct_type_index: buffer_type_idx,
|
|
2482
|
-
field_index: 0,
|
|
2825
|
+
field_index: fld(0),
|
|
2483
2826
|
}
|
|
2484
2827
|
.encode(body);
|
|
2485
2828
|
}
|
|
@@ -3879,7 +4222,7 @@ fn compileStmtInValuePosition(
|
|
|
3879
4222
|
state: &mut ModuleState,
|
|
3880
4223
|
) -> Result<(), String> {
|
|
3881
4224
|
match stmt {
|
|
3882
|
-
ast::Stmt::Expr(e, ..) =>
|
|
4225
|
+
ast::Stmt::Expr(e, ..) => compileExprAs(e, Some(result_vt), body, ctx, state),
|
|
3883
4226
|
ast::Stmt::Return(_) | ast::Stmt::Todo | ast::Stmt::Crash(_) => {
|
|
3884
4227
|
compileStmt(stmt, body, ctx, state)
|
|
3885
4228
|
}
|
|
@@ -4116,7 +4459,7 @@ fn compileStmt(
|
|
|
4116
4459
|
compileExpr(value, body, ctx, state)?;
|
|
4117
4460
|
Instruction::StructSet {
|
|
4118
4461
|
struct_type_index: super_type_idx,
|
|
4119
|
-
field_index: field_idx
|
|
4462
|
+
field_index: fld(field_idx),
|
|
4120
4463
|
}
|
|
4121
4464
|
.encode(body);
|
|
4122
4465
|
}
|
|
@@ -4147,7 +4490,7 @@ fn compileStmt(
|
|
|
4147
4490
|
compileExpr(value, body, ctx, state)?;
|
|
4148
4491
|
Instruction::StructSet {
|
|
4149
4492
|
struct_type_index: variant_type_idx,
|
|
4150
|
-
field_index: field_idx
|
|
4493
|
+
field_index: fld(field_idx),
|
|
4151
4494
|
}
|
|
4152
4495
|
.encode(body);
|
|
4153
4496
|
}
|
|
@@ -4157,7 +4500,7 @@ fn compileStmt(
|
|
|
4157
4500
|
}
|
|
4158
4501
|
}
|
|
4159
4502
|
ast::Stmt::Return(Some(e)) => {
|
|
4160
|
-
|
|
4503
|
+
compileExprAs(e, ctx.ret_type.as_ref().map(plumTypeToValtype), body, ctx, state)?;
|
|
4161
4504
|
Instruction::Return.encode(body);
|
|
4162
4505
|
}
|
|
4163
4506
|
ast::Stmt::Return(None) => {
|
|
@@ -4501,7 +4844,7 @@ fn compileTagPattern(
|
|
|
4501
4844
|
load(body);
|
|
4502
4845
|
Instruction::StructGet {
|
|
4503
4846
|
struct_type_index: tag_idx,
|
|
4504
|
-
field_index: 0,
|
|
4847
|
+
field_index: fld(0),
|
|
4505
4848
|
}
|
|
4506
4849
|
.encode(body);
|
|
4507
4850
|
Instruction::I32Const(tagId(name)).encode(body);
|
|
@@ -4516,7 +4859,7 @@ fn compileTagPattern(
|
|
|
4516
4859
|
load(body);
|
|
4517
4860
|
Instruction::StructGet {
|
|
4518
4861
|
struct_type_index: tag_idx,
|
|
4519
|
-
field_index: 1,
|
|
4862
|
+
field_index: fld(1),
|
|
4520
4863
|
}
|
|
4521
4864
|
.encode(body);
|
|
4522
4865
|
unboxTagPayload(pty, body, ctx);
|
|
@@ -5056,7 +5399,7 @@ fn compileFieldPatterns(
|
|
|
5056
5399
|
Instruction::LocalGet(container_local).encode(b);
|
|
5057
5400
|
Instruction::StructGet {
|
|
5058
5401
|
struct_type_index: container_type_idx,
|
|
5059
|
-
field_index: fpos
|
|
5402
|
+
field_index: fld(fpos),
|
|
5060
5403
|
}
|
|
5061
5404
|
.encode(b);
|
|
5062
5405
|
},
|
|
@@ -5125,7 +5468,7 @@ fn compileFieldPatterns(
|
|
|
5125
5468
|
Instruction::LocalGet(container_local).encode(body);
|
|
5126
5469
|
Instruction::StructGet {
|
|
5127
5470
|
struct_type_index: container_type_idx,
|
|
5128
|
-
field_index: fpos
|
|
5471
|
+
field_index: fld(fpos),
|
|
5129
5472
|
}
|
|
5130
5473
|
.encode(body);
|
|
5131
5474
|
Instruction::LocalSet(idx).encode(body);
|
|
@@ -5166,7 +5509,7 @@ fn compileFieldPatterns(
|
|
|
5166
5509
|
Instruction::LocalGet(container_local).encode(body);
|
|
5167
5510
|
Instruction::StructGet {
|
|
5168
5511
|
struct_type_index: container_type_idx,
|
|
5169
|
-
field_index: fpos
|
|
5512
|
+
field_index: fld(fpos),
|
|
5170
5513
|
}
|
|
5171
5514
|
.encode(body);
|
|
5172
5515
|
Instruction::RefTestNonNull(HeapType::Concrete(variant_idx)).encode(body);
|
|
@@ -5206,7 +5549,7 @@ fn compileFieldPatterns(
|
|
|
5206
5549
|
Instruction::LocalGet(container_local).encode(body);
|
|
5207
5550
|
Instruction::StructGet {
|
|
5208
5551
|
struct_type_index: container_type_idx,
|
|
5209
|
-
field_index: fpos
|
|
5552
|
+
field_index: fld(fpos),
|
|
5210
5553
|
}
|
|
5211
5554
|
.encode(body);
|
|
5212
5555
|
Instruction::I64Const(*n).encode(body);
|
|
@@ -5285,7 +5628,7 @@ fn compileFieldPatterns(
|
|
|
5285
5628
|
Instruction::LocalGet(container_local).encode(body);
|
|
5286
5629
|
Instruction::StructGet {
|
|
5287
5630
|
struct_type_index: container_type_idx,
|
|
5288
|
-
field_index: fpos
|
|
5631
|
+
field_index: fld(fpos),
|
|
5289
5632
|
}
|
|
5290
5633
|
.encode(body);
|
|
5291
5634
|
Instruction::RefTestNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
|
|
@@ -5294,7 +5637,7 @@ fn compileFieldPatterns(
|
|
|
5294
5637
|
Instruction::LocalGet(container_local).encode(body);
|
|
5295
5638
|
Instruction::StructGet {
|
|
5296
5639
|
struct_type_index: container_type_idx,
|
|
5297
|
-
field_index: fpos
|
|
5640
|
+
field_index: fld(fpos),
|
|
5298
5641
|
}
|
|
5299
5642
|
.encode(body);
|
|
5300
5643
|
Instruction::RefCastNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
|
|
@@ -5457,8 +5800,6 @@ fn compileExpr(
|
|
|
5457
5800
|
Instruction::LocalTee(scratch_local).encode(body);
|
|
5458
5801
|
Instruction::RefTestNonNull(HeapType::Concrete(failure_idx)).encode(body);
|
|
5459
5802
|
Instruction::If(BlockType::Result(success_vt)).encode(body);
|
|
5460
|
-
Instruction::LocalGet(scratch_local).encode(body);
|
|
5461
|
-
Instruction::RefCastNonNull(HeapType::Concrete(failure_idx)).encode(body);
|
|
5462
5803
|
// Failure branch: take the inner `Err` payload and build the OUTER
|
|
5463
5804
|
// function's `Err` variant from it (the Ok types may differ).
|
|
5464
5805
|
let outer_err = match &ctx.ret_type {
|
|
@@ -5468,6 +5809,7 @@ fn compileExpr(
|
|
|
5468
5809
|
}
|
|
5469
5810
|
_ => None,
|
|
5470
5811
|
};
|
|
5812
|
+
let mut rewrap: Option<(u32, i32)> = None;
|
|
5471
5813
|
if let Some(outer_err_name) = outer_err {
|
|
5472
5814
|
let outer_idx = *ctx
|
|
5473
5815
|
.gc_types
|
|
@@ -5480,14 +5822,22 @@ fn compileExpr(
|
|
|
5480
5822
|
)
|
|
5481
5823
|
})?;
|
|
5482
5824
|
if outer_idx != failure_idx {
|
|
5483
|
-
Instruction::StructGet {
|
|
5484
|
-
struct_type_index: failure_idx,
|
|
5485
|
-
field_index: 0,
|
|
5486
|
-
}
|
|
5487
|
-
.encode(body);
|
|
5488
|
-
|
|
5825
|
+
rewrap = Some((outer_idx, typeIdOf(ctx.gc_types, outer_err_name)));
|
|
5489
5826
|
}
|
|
5490
5827
|
}
|
|
5828
|
+
if let Some((_, tid)) = rewrap {
|
|
5829
|
+
Instruction::I32Const(tid).encode(body);
|
|
5830
|
+
}
|
|
5831
|
+
Instruction::LocalGet(scratch_local).encode(body);
|
|
5832
|
+
Instruction::RefCastNonNull(HeapType::Concrete(failure_idx)).encode(body);
|
|
5833
|
+
if let Some((outer_idx, _)) = rewrap {
|
|
5834
|
+
Instruction::StructGet {
|
|
5835
|
+
struct_type_index: failure_idx,
|
|
5836
|
+
field_index: fld(0),
|
|
5837
|
+
}
|
|
5838
|
+
.encode(body);
|
|
5839
|
+
Instruction::StructNew(outer_idx).encode(body);
|
|
5840
|
+
}
|
|
5491
5841
|
Instruction::Return.encode(body);
|
|
5492
5842
|
Instruction::Else.encode(body);
|
|
5493
5843
|
Instruction::LocalGet(scratch_local).encode(body);
|
|
@@ -5495,7 +5845,7 @@ fn compileExpr(
|
|
|
5495
5845
|
if !success_info.field_types.is_empty() {
|
|
5496
5846
|
Instruction::StructGet {
|
|
5497
5847
|
struct_type_index: success_idx,
|
|
5498
|
-
field_index: 0,
|
|
5848
|
+
field_index: fld(0),
|
|
5499
5849
|
}
|
|
5500
5850
|
.encode(body);
|
|
5501
5851
|
}
|
|
@@ -5576,7 +5926,7 @@ fn compileExpr(
|
|
|
5576
5926
|
if !success_info.field_types.is_empty() {
|
|
5577
5927
|
Instruction::StructGet {
|
|
5578
5928
|
struct_type_index: success_idx,
|
|
5579
|
-
field_index: 0,
|
|
5929
|
+
field_index: fld(0),
|
|
5580
5930
|
}
|
|
5581
5931
|
.encode(body);
|
|
5582
5932
|
}
|
|
@@ -5660,7 +6010,7 @@ fn compileExpr(
|
|
|
5660
6010
|
compileExpr(side, body, ctx, state)?;
|
|
5661
6011
|
Instruction::StructGet {
|
|
5662
6012
|
struct_type_index: tag_idx,
|
|
5663
|
-
field_index: 0,
|
|
6013
|
+
field_index: fld(0),
|
|
5664
6014
|
}
|
|
5665
6015
|
.encode(body);
|
|
5666
6016
|
}
|
|
@@ -5883,10 +6233,15 @@ fn compileExpr(
|
|
|
5883
6233
|
},
|
|
5884
6234
|
_ => None,
|
|
5885
6235
|
};
|
|
6236
|
+
let sig_params: Vec<ValType> = ctx
|
|
6237
|
+
.func_sigs
|
|
6238
|
+
.get(&call.name)
|
|
6239
|
+
.map(|s| s.params.clone())
|
|
6240
|
+
.unwrap_or_default();
|
|
5886
6241
|
match variadic_split {
|
|
5887
6242
|
Some((elem_ty, fixed_count)) => {
|
|
5888
|
-
for arg in call.args.iter().take(fixed_count) {
|
|
6243
|
+
for (i, arg) in call.args.iter().take(fixed_count).enumerate() {
|
|
5889
|
-
|
|
6244
|
+
compileExprAs(argExprOf(arg), sig_params.get(i).copied(), body, ctx, state)?;
|
|
5890
6245
|
}
|
|
5891
6246
|
let trailing: Vec<&ast::Expr> =
|
|
5892
6247
|
call.args.iter().skip(fixed_count).map(argExprOf).collect();
|
|
@@ -5897,7 +6252,7 @@ fn compileExpr(
|
|
|
5897
6252
|
.get(&elem_vt)
|
|
5898
6253
|
.ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
|
|
5899
6254
|
for arg_expr in &trailing {
|
|
5900
|
-
|
|
6255
|
+
compileExprAs(arg_expr, Some(elem_vt), body, ctx, state)?;
|
|
5901
6256
|
}
|
|
5902
6257
|
Instruction::ArrayNewFixed {
|
|
5903
6258
|
array_type_index: array_type_idx,
|
|
@@ -5912,8 +6267,8 @@ fn compileExpr(
|
|
|
5912
6267
|
Instruction::Call(*func_idx).encode(body);
|
|
5913
6268
|
}
|
|
5914
6269
|
None => {
|
|
5915
|
-
for arg in
|
|
6270
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
5916
|
-
|
|
6271
|
+
compileExprAs(argExprOf(arg), sig_params.get(i).copied(), body, ctx, state)?;
|
|
5917
6272
|
}
|
|
5918
6273
|
compileDefaultArgs(&call.name, call.args.len(), body, ctx, state)?;
|
|
5919
6274
|
let func_idx = ctx
|
|
@@ -6033,9 +6388,16 @@ fn compileExpr(
|
|
|
6033
6388
|
)
|
|
6034
6389
|
})?;
|
|
6035
6390
|
let field_names = info.field_names.clone();
|
|
6391
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, &call.type_name)).encode(body);
|
|
6036
6392
|
for (field_idx, field_name) in field_names.iter().enumerate() {
|
|
6037
6393
|
match call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
6038
|
-
Some(fa) =>
|
|
6394
|
+
Some(fa) => compileExprAs(
|
|
6395
|
+
&fa.value,
|
|
6396
|
+
info.field_types.get(field_idx).map(plumTypeToValtype),
|
|
6397
|
+
body,
|
|
6398
|
+
ctx,
|
|
6399
|
+
state,
|
|
6400
|
+
)?,
|
|
6039
6401
|
// `Circle(..c, radius: 10)` — a field not overridden is read
|
|
6040
6402
|
// straight off the spread source instead (checker only proved
|
|
6041
6403
|
// `c` is SOME value of the owning enum, restricted to a bare
|
|
@@ -6056,7 +6418,7 @@ fn compileExpr(
|
|
|
6056
6418
|
Instruction::RefCastNonNull(HeapType::Concrete(variant_idx)).encode(body);
|
|
6057
6419
|
Instruction::StructGet {
|
|
6058
6420
|
struct_type_index: variant_idx,
|
|
6059
|
-
field_index: field_idx
|
|
6421
|
+
field_index: fld(field_idx),
|
|
6060
6422
|
}
|
|
6061
6423
|
.encode(body);
|
|
6062
6424
|
}
|
|
@@ -6123,7 +6485,7 @@ fn compileExpr(
|
|
|
6123
6485
|
compileExpr(&attr.object, body, ctx, state)?;
|
|
6124
6486
|
Instruction::StructGet {
|
|
6125
6487
|
struct_type_index: super_type_idx,
|
|
6126
|
-
field_index: field_idx
|
|
6488
|
+
field_index: fld(field_idx),
|
|
6127
6489
|
}
|
|
6128
6490
|
.encode(body);
|
|
6129
6491
|
}
|
|
@@ -6172,7 +6534,7 @@ fn compileExpr(
|
|
|
6172
6534
|
.encode(body);
|
|
6173
6535
|
Instruction::StructGet {
|
|
6174
6536
|
struct_type_index: variant_type_idx,
|
|
6175
|
-
field_index: field_idx
|
|
6537
|
+
field_index: fld(field_idx),
|
|
6176
6538
|
}
|
|
6177
6539
|
.encode(body);
|
|
6178
6540
|
}
|
|
@@ -6198,6 +6560,59 @@ fn compileExpr(
|
|
|
6198
6560
|
))
|
|
6199
6561
|
}
|
|
6200
6562
|
};
|
|
6563
|
+
// A method call on a trait-typed value goes through the trait's
|
|
6564
|
+
// `$dyn$<Trait>$<method>` dispatcher (reads the receiver's typeId).
|
|
6565
|
+
let is_trait_recv = ctx.gc_types.class_type_idx.get(&class_name)
|
|
6566
|
+
== Some(&ctx.gc_types.obj_type_idx)
|
|
6567
|
+
&& ctx.methods.contains_key(&(
|
|
6568
|
+
class_name.clone(),
|
|
6569
|
+
plum_checker::TRAIT_MARKER.to_string(),
|
|
6570
|
+
));
|
|
6571
|
+
// A method the receiver's own trait doesn't declare (e.g. a
|
|
6572
|
+
// `T: ToStr` bound's `toStr` on a `Shape`-typed value inside a
|
|
6573
|
+
// specialized generic) dispatches through whichever trait does.
|
|
6574
|
+
let dyn_owner = if is_trait_recv {
|
|
6575
|
+
if ctx.gc_types.dyn_fns.contains_key(&(class_name.clone(), call.name.clone())) {
|
|
6576
|
+
Some(class_name.clone())
|
|
6577
|
+
} else {
|
|
6578
|
+
ctx.gc_types
|
|
6579
|
+
.dyn_fns
|
|
6580
|
+
.keys()
|
|
6581
|
+
.find(|(_, m)| *m == call.name)
|
|
6582
|
+
.map(|(t, _)| t.clone())
|
|
6583
|
+
}
|
|
6584
|
+
} else {
|
|
6585
|
+
None
|
|
6586
|
+
};
|
|
6587
|
+
if is_trait_recv && dyn_owner.is_none() {
|
|
6588
|
+
// No implementor of this method exists at all: unreachable at
|
|
6589
|
+
// runtime, but the call must still type-check structurally.
|
|
6590
|
+
Instruction::Unreachable.encode(body);
|
|
6591
|
+
return Ok(());
|
|
6592
|
+
}
|
|
6593
|
+
if let Some(&dyn_idx) = dyn_owner.as_ref().and_then(|t| {
|
|
6594
|
+
ctx.gc_types
|
|
6595
|
+
.dyn_fns
|
|
6596
|
+
.get(&(t.clone(), call.name.clone()))
|
|
6597
|
+
}) {
|
|
6598
|
+
let owner = dyn_owner.clone().unwrap_or_default();
|
|
6599
|
+
let param_tys: Vec<PlumType> =
|
|
6600
|
+
match ctx.methods.get(&(owner, call.name.clone())) {
|
|
6601
|
+
Some(PlumType::TFun(ps, _)) => ps.clone(),
|
|
6602
|
+
_ => Vec::new(),
|
|
6603
|
+
};
|
|
6604
|
+
compileExpr(&attr.object, body, ctx, state)?;
|
|
6605
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
6606
|
+
let e = match arg {
|
|
6607
|
+
ast::Arg::Positional(e) => e,
|
|
6608
|
+
ast::Arg::Keyword { value, .. } => value,
|
|
6609
|
+
ast::Arg::Pair { value, .. } => value,
|
|
6610
|
+
};
|
|
6611
|
+
compileExprAs(e, param_tys.get(i).map(plumTypeToValtype), body, ctx, state)?;
|
|
6612
|
+
}
|
|
6613
|
+
Instruction::Call(dyn_idx).encode(body);
|
|
6614
|
+
return Ok(());
|
|
6615
|
+
}
|
|
6201
6616
|
let direct_key = format!("{}::{}", class_name, call.name);
|
|
6202
6617
|
// Fallback: a primitive/class receiver's method might be
|
|
6203
6618
|
// defined once on the "union" enum that bare-wraps it
|
|
@@ -6254,6 +6669,10 @@ fn compileExpr(
|
|
|
6254
6669
|
let vt = astTypeToWasm(&dispatch_name).unwrap_or(ValType::I32);
|
|
6255
6670
|
pushSelfPlaceholder(vt, body);
|
|
6256
6671
|
} else {
|
|
6672
|
+
if box_variant_idx.is_some() {
|
|
6673
|
+
// The wrap-enum variant's header typeId goes below its payload.
|
|
6674
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, &class_name)).encode(body);
|
|
6675
|
+
}
|
|
6257
6676
|
compileExpr(&attr.object, body, ctx, state)?; // push self
|
|
6258
6677
|
if let Some(variant_idx) = box_variant_idx {
|
|
6259
6678
|
// Dispatching to the wrap-enum's method (see
|
|
@@ -6285,10 +6704,15 @@ fn compileExpr(
|
|
|
6285
6704
|
},
|
|
6286
6705
|
_ => None,
|
|
6287
6706
|
};
|
|
6707
|
+
let sig_params: Vec<ValType> = ctx
|
|
6708
|
+
.func_sigs
|
|
6709
|
+
.get(&key)
|
|
6710
|
+
.map(|s| s.params.clone())
|
|
6711
|
+
.unwrap_or_default();
|
|
6288
6712
|
match variadic_split {
|
|
6289
6713
|
Some((elem_ty, fixed_count)) => {
|
|
6290
|
-
for arg in call.args.iter().take(fixed_count) {
|
|
6714
|
+
for (i, arg) in call.args.iter().take(fixed_count).enumerate() {
|
|
6291
|
-
|
|
6715
|
+
compileExprAs(argExprOf(arg), sig_params.get(i + 1).copied(), body, ctx, state)?;
|
|
6292
6716
|
}
|
|
6293
6717
|
let trailing: Vec<&ast::Expr> =
|
|
6294
6718
|
call.args.iter().skip(fixed_count).map(argExprOf).collect();
|
|
@@ -6299,7 +6723,7 @@ fn compileExpr(
|
|
|
6299
6723
|
.get(&elem_vt)
|
|
6300
6724
|
.ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
|
|
6301
6725
|
for arg_expr in &trailing {
|
|
6302
|
-
|
|
6726
|
+
compileExprAs(arg_expr, Some(elem_vt), body, ctx, state)?;
|
|
6303
6727
|
}
|
|
6304
6728
|
Instruction::ArrayNewFixed {
|
|
6305
6729
|
array_type_index: array_type_idx,
|
|
@@ -6308,8 +6732,8 @@ fn compileExpr(
|
|
|
6308
6732
|
.encode(body);
|
|
6309
6733
|
}
|
|
6310
6734
|
None => {
|
|
6311
|
-
for arg in
|
|
6735
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
6312
|
-
|
|
6736
|
+
compileExprAs(argExprOf(arg), sig_params.get(i + 1).copied(), body, ctx, state)?;
|
|
6313
6737
|
}
|
|
6314
6738
|
compileDefaultArgs(&key, call.args.len(), body, ctx, state)?;
|
|
6315
6739
|
}
|
|
@@ -6421,13 +6845,20 @@ fn compileVariantConstruction(
|
|
|
6421
6845
|
// single construction instruction — positional args already match field
|
|
6422
6846
|
// declaration order (unlike named class-field construction), so no reordering
|
|
6423
6847
|
// is needed here.
|
|
6848
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, &call.name)).encode(body);
|
|
6424
|
-
for arg in
|
|
6849
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
6425
6850
|
let arg_expr = match arg {
|
|
6426
6851
|
ast::Arg::Positional(e) => e,
|
|
6427
6852
|
ast::Arg::Keyword { value, .. } => value,
|
|
6428
6853
|
ast::Arg::Pair { value, .. } => value,
|
|
6429
6854
|
};
|
|
6855
|
+
compileExprAs(
|
|
6856
|
+
arg_expr,
|
|
6430
|
-
|
|
6857
|
+
info.field_types.get(i).map(plumTypeToValtype),
|
|
6858
|
+
body,
|
|
6859
|
+
ctx,
|
|
6860
|
+
state,
|
|
6861
|
+
)?;
|
|
6431
6862
|
}
|
|
6432
6863
|
Instruction::StructNew(variant_type_idx).encode(body);
|
|
6433
6864
|
Ok(())
|
|
@@ -6503,6 +6934,7 @@ fn compileInterpolatedString(
|
|
|
6503
6934
|
// `Float` at all already must `import std/Number` (see
|
|
6504
6935
|
// `plum-core::loader::checkBuiltinImports`), so these
|
|
6505
6936
|
// lookups are always present.
|
|
6937
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, "Float")).encode(body);
|
|
6506
6938
|
compileExpr(expr, body, ctx, state)?;
|
|
6507
6939
|
let variant_idx = *ctx.gc_types.variant_type_idx.get("Float")
|
|
6508
6940
|
.ok_or_else(|| "codegen: interpolating a Float value requires `Number` (import std/Number)".to_string())?;
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -1557,3 +1557,118 @@ fun main() -> Int =
|
|
|
1557
1557
|
);
|
|
1558
1558
|
assert_eq!(got, 9);
|
|
1559
1559
|
}
|
|
1560
|
+
|
|
1561
|
+
// ---- trait types + typeId (T3) ----
|
|
1562
|
+
|
|
1563
|
+
fn repoRoot() -> std::path::PathBuf {
|
|
1564
|
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/// Compiles `src` as a real file (so `import std/...` resolves against the repo's
|
|
1568
|
+
/// `plum-std`) and returns the wasm instance's `main` export plus its store.
|
|
1569
|
+
fn runMerged<R: wasmtime::WasmResults>(name: &str, src: &str) -> R {
|
|
1570
|
+
let dir = std::env::temp_dir().join(format!("plum_t3_{}_{}", name, std::process::id()));
|
|
1571
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
1572
|
+
let path = dir.join("main.plum");
|
|
1573
|
+
std::fs::write(&path, src).unwrap();
|
|
1574
|
+
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
1575
|
+
plum_checker::checkSource(&source).unwrap_or_else(|e| {
|
|
1576
|
+
panic!("check errors: {:?}", e.iter().map(|x| x.message.clone()).collect::<Vec<_>>())
|
|
1577
|
+
});
|
|
1578
|
+
let bytes = compileSource(&source).expect("compile failed");
|
|
1579
|
+
wasmparser::validate(&bytes).expect("valid wasm");
|
|
1580
|
+
let engine = gcEngine();
|
|
1581
|
+
let module = wasmtime::Module::new(&engine, &bytes).unwrap();
|
|
1582
|
+
let mut store = wasmtime::Store::new(&engine, ());
|
|
1583
|
+
// std pulls in host externs (printLn, ...) the test never calls: stub them.
|
|
1584
|
+
let mut linker = wasmtime::Linker::new(&engine);
|
|
1585
|
+
linker
|
|
1586
|
+
.define_unknown_imports_as_default_values(&mut store, &module)
|
|
1587
|
+
.unwrap();
|
|
1588
|
+
let instance = linker.instantiate(&mut store, &module).unwrap();
|
|
1589
|
+
let main = instance.get_typed_func::<(), R>(&mut store, "main").unwrap();
|
|
1590
|
+
let r = main.call(&mut store, ()).unwrap();
|
|
1591
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1592
|
+
r
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
#[test]
|
|
1596
|
+
fn heterogeneous_list_dispatch() {
|
|
1597
|
+
let src = "\
|
|
1598
|
+
import std/Bool
|
|
1599
|
+
import std/Str
|
|
1600
|
+
import std/Number
|
|
1601
|
+
import std/Try
|
|
1602
|
+
import std/List
|
|
1603
|
+
|
|
1604
|
+
trait Shape =
|
|
1605
|
+
area() -> Float
|
|
1606
|
+
|
|
1607
|
+
enum Circle(Shape) =
|
|
1608
|
+
| Circle(r: Float)
|
|
1609
|
+
|
|
1610
|
+
fun area(self) -> Float =
|
|
1611
|
+
self.r * self.r * 3.0
|
|
1612
|
+
|
|
1613
|
+
enum Square(Shape) =
|
|
1614
|
+
| Square(s: Float)
|
|
1615
|
+
|
|
1616
|
+
fun area(self) -> Float =
|
|
1617
|
+
self.s * self.s
|
|
1618
|
+
|
|
1619
|
+
fun total(xs: List[Shape]) -> Float =
|
|
1620
|
+
xs.reduce(0.0, |acc, x| acc + x.area())
|
|
1621
|
+
|
|
1622
|
+
fun main() -> Float =
|
|
1623
|
+
xs := List[Shape]()
|
|
1624
|
+
xs.add(Circle(r: 1.5))
|
|
1625
|
+
xs.add(Square(s: 1.5))
|
|
1626
|
+
xs.add(Circle(r: 0.5))
|
|
1627
|
+
total(xs)
|
|
1628
|
+
";
|
|
1629
|
+
// 6.75 + 2.25 + 0.75, exact in binary floating point.
|
|
1630
|
+
assert_eq!(runMerged::<f64>("hetero", src), 9.75);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
#[test]
|
|
1634
|
+
fn missing_trait_method_rejected() {
|
|
1635
|
+
let src = "\
|
|
1636
|
+
trait Shape =
|
|
1637
|
+
area() -> Float
|
|
1638
|
+
|
|
1639
|
+
enum Plain =
|
|
1640
|
+
| Plain(n: Int)
|
|
1641
|
+
|
|
1642
|
+
fun total(s: Shape) -> Float =
|
|
1643
|
+
s.area()
|
|
1644
|
+
|
|
1645
|
+
fun main() -> Float =
|
|
1646
|
+
total(Plain(n: 1))
|
|
1647
|
+
";
|
|
1648
|
+
let errs = plum_checker::checkSource(&parse(src)).unwrap_err();
|
|
1649
|
+
assert!(
|
|
1650
|
+
errs.iter().any(|e| e
|
|
1651
|
+
.message
|
|
1652
|
+
.contains("type 'Plain' does not implement trait 'Shape': missing 'area'")),
|
|
1653
|
+
"got {:?}",
|
|
1654
|
+
errs.iter().map(|e| e.message.clone()).collect::<Vec<_>>()
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
#[test]
|
|
1659
|
+
fn boxed_int_dispatch() {
|
|
1660
|
+
let src = "\
|
|
1661
|
+
import std/Bool
|
|
1662
|
+
import std/Str
|
|
1663
|
+
import std/Number
|
|
1664
|
+
|
|
1665
|
+
fun show(x: ToStr) -> Str =
|
|
1666
|
+
x.toStr()
|
|
1667
|
+
|
|
1668
|
+
fun main() -> Int =
|
|
1669
|
+
if show(5) == \"5\" && show(2.5) == \"2.5\" && show(\"hi\") == \"hi\"
|
|
1670
|
+
return 1
|
|
1671
|
+
return 0
|
|
1672
|
+
";
|
|
1673
|
+
assert_eq!(runMerged::<i64>("boxed", src), 1);
|
|
1674
|
+
}
|
plum-wasm-codegen/tests/examples_test.rs
CHANGED
|
@@ -135,11 +135,11 @@ fn ioExampleCompilesAndRunsCorrectly() {
|
|
|
135
135
|
return Err(wasmtime::Error::msg("printLn expects a Str argument"));
|
|
136
136
|
};
|
|
137
137
|
let str_struct = s.unwrap_struct(&caller)?;
|
|
138
|
-
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut caller,
|
|
138
|
+
let wasmtime::Val::AnyRef(Some(buffer_ref)) = str_struct.field(&mut caller, 1)? else {
|
|
139
139
|
return Err(wasmtime::Error::msg("Str.data is not a Buffer reference"));
|
|
140
140
|
};
|
|
141
141
|
let buffer_struct = buffer_ref.unwrap_struct(&caller)?;
|
|
142
|
-
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut caller,
|
|
142
|
+
let wasmtime::Val::AnyRef(Some(bytes_ref)) = buffer_struct.field(&mut caller, 1)?
|
|
143
143
|
else {
|
|
144
144
|
return Err(wasmtime::Error::msg(
|
|
145
145
|
"Buffer.data is not a []Byte reference",
|