plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
db00bd9
— Peter John
2026-07-20T21:52:28+05:30
feat(plum-wasm-codegen): add wasm function table + element section support
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -19,6 +19,9 @@ pub struct WasmModule {
|
|
|
19
19
|
memories: Vec<MemoryType>,
|
|
20
20
|
globals: Vec<(ValType, bool, Vec<u8>)>,
|
|
21
21
|
data_segments: Vec<(u32, Vec<u8>)>,
|
|
22
|
+
/// Function indices, in table order — the single funcref table used for
|
|
23
|
+
/// closure `call_indirect` dispatch. Index into this vec IS the table index.
|
|
24
|
+
table_elements: Vec<u32>,
|
|
22
25
|
pub func_import_count: u32,
|
|
23
26
|
pub func_count: u32,
|
|
24
27
|
global_count: u32,
|
|
@@ -34,6 +37,7 @@ impl WasmModule {
|
|
|
34
37
|
memories: Vec::new(),
|
|
35
38
|
globals: Vec::new(),
|
|
36
39
|
data_segments: Vec::new(),
|
|
40
|
+
table_elements: Vec::new(),
|
|
37
41
|
func_import_count: 0,
|
|
38
42
|
func_count: 0,
|
|
39
43
|
global_count: 0,
|
|
@@ -87,6 +91,14 @@ impl WasmModule {
|
|
|
87
91
|
self.data_segments.push((offset, data.to_vec()));
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
/// Registers `func_idx` as the next slot in the single funcref table used for
|
|
95
|
+
/// closure `call_indirect` dispatch, returning its table index.
|
|
96
|
+
pub fn add_table_element(&mut self, func_idx: u32) -> u32 {
|
|
97
|
+
let table_idx = self.table_elements.len() as u32;
|
|
98
|
+
self.table_elements.push(func_idx);
|
|
99
|
+
table_idx
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
pub fn finish(&mut self) -> Vec<u8> {
|
|
91
103
|
let mut module = wasm_encoder::Module::new();
|
|
92
104
|
|
|
@@ -115,6 +127,19 @@ impl WasmModule {
|
|
|
115
127
|
module.section(&funcs);
|
|
116
128
|
}
|
|
117
129
|
|
|
130
|
+
// Table section
|
|
131
|
+
if !self.table_elements.is_empty() {
|
|
132
|
+
let mut tables = TableSection::new();
|
|
133
|
+
tables.table(TableType {
|
|
134
|
+
element_type: RefType::FUNCREF,
|
|
135
|
+
minimum: self.table_elements.len() as u64,
|
|
136
|
+
maximum: Some(self.table_elements.len() as u64),
|
|
137
|
+
table64: false,
|
|
138
|
+
shared: false,
|
|
139
|
+
});
|
|
140
|
+
module.section(&tables);
|
|
141
|
+
}
|
|
142
|
+
|
|
118
143
|
// Memory section
|
|
119
144
|
if !self.memories.is_empty() {
|
|
120
145
|
let mut mem = MemorySection::new();
|
|
@@ -146,6 +171,14 @@ impl WasmModule {
|
|
|
146
171
|
module.section(&exports);
|
|
147
172
|
}
|
|
148
173
|
|
|
174
|
+
// Element section
|
|
175
|
+
if !self.table_elements.is_empty() {
|
|
176
|
+
let mut elements = ElementSection::new();
|
|
177
|
+
let offset = ConstExpr::i32_const(0);
|
|
178
|
+
elements.active(Some(0), &offset, Elements::Functions(std::borrow::Cow::Borrowed(&self.table_elements)));
|
|
179
|
+
module.section(&elements);
|
|
180
|
+
}
|
|
181
|
+
|
|
149
182
|
// Code section
|
|
150
183
|
if !self.functions.is_empty() {
|
|
151
184
|
let mut code = CodeSection::new();
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use plum_wasm_codegen::compile_source;
|
|
2
2
|
use plum_core::AstParser;
|
|
3
|
+
use wasm_encoder::Encode;
|
|
3
4
|
|
|
4
5
|
fn parse(src: &str) -> plum_core::ast::Source {
|
|
5
6
|
let mut parser = tree_sitter::Parser::new();
|
|
@@ -858,3 +859,44 @@ main() -> Int =
|
|
|
858
859
|
let bytes = compile_source(&source).expect("compile failed");
|
|
859
860
|
assert_eq!(run_main(&bytes), 11);
|
|
860
861
|
}
|
|
862
|
+
|
|
863
|
+
#[test]
|
|
864
|
+
fn wasm_module_with_a_table_element_validates_and_call_indirect_works() {
|
|
865
|
+
// Exercises WasmModule's new table/element support directly, independent of any
|
|
866
|
+
// closure-compiling logic (which doesn't exist yet) — builds a tiny module by
|
|
867
|
+
// hand: one function that returns 42, registered as table element 0, called via
|
|
868
|
+
// `call_indirect` from `main` using a runtime-computed (not compile-time-constant)
|
|
869
|
+
// table index, to prove the table/element wiring is real, not coincidentally
|
|
870
|
+
// skipped by validation.
|
|
871
|
+
let mut module = plum_wasm_codegen::WasmModule::new();
|
|
872
|
+
let ret42_type = module.add_type(&[], &[wasm_encoder::ValType::I64]);
|
|
873
|
+
let ret42_idx = module.add_function(ret42_type, &{
|
|
874
|
+
let mut body = vec![0u8]; // 0 local-decl groups
|
|
875
|
+
wasm_encoder::Instruction::I64Const(42).encode(&mut body);
|
|
876
|
+
wasm_encoder::Instruction::End.encode(&mut body);
|
|
877
|
+
body
|
|
878
|
+
});
|
|
879
|
+
let table_idx = module.add_table_element(ret42_idx);
|
|
880
|
+
assert_eq!(table_idx, 0);
|
|
881
|
+
|
|
882
|
+
let main_type = module.add_type(&[], &[wasm_encoder::ValType::I64]);
|
|
883
|
+
let main_idx = module.add_function(main_type, &{
|
|
884
|
+
let mut body = vec![0u8]; // 0 local-decl groups
|
|
885
|
+
wasm_encoder::Instruction::I32Const(0).encode(&mut body); // table index operand
|
|
886
|
+
wasm_encoder::Instruction::CallIndirect { type_index: ret42_type, table_index: 0 }.encode(&mut body);
|
|
887
|
+
wasm_encoder::Instruction::End.encode(&mut body);
|
|
888
|
+
body
|
|
889
|
+
});
|
|
890
|
+
module.add_export("main", wasm_encoder::ExportKind::Func, main_idx);
|
|
891
|
+
|
|
892
|
+
let bytes = module.finish();
|
|
893
|
+
let result = wasmparser::validate(&bytes);
|
|
894
|
+
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
895
|
+
|
|
896
|
+
let engine = wasmtime::Engine::default();
|
|
897
|
+
let wasm_module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
|
|
898
|
+
let mut store = wasmtime::Store::new(&engine, ());
|
|
899
|
+
let instance = wasmtime::Instance::new(&mut store, &wasm_module, &[]).expect("module should instantiate");
|
|
900
|
+
let main = instance.get_typed_func::<(), i64>(&mut store, "main").expect("main should have signature () -> i64");
|
|
901
|
+
assert_eq!(main.call(&mut store, ()).expect("main should not trap"), 42);
|
|
902
|
+
}
|