Skip to content

Commit fd02baa

Browse files
author
Arnaud Riess
committed
feat(codegen): implement module-level code generation
- Added `generate_module_with_info` function to handle both standalone and module wrapper generation based on `ModuleInfo`. - Implemented `generate_standalone_module` for generating standalone functions and `generate_wrapper_module` for module wrappers with constructors and export methods. - Introduced `generate_host_traits` to create Rust trait definitions for imported functions and globals, organized by module name. - Added utility functions for type conversion from WebAssembly types to Rust types in `types.rs`. - Created general-purpose utility functions in `utils.rs` for building call arguments with conditionally included globals, memory, and tables.
1 parent 95f6ddb commit fd02baa

9 files changed

Lines changed: 1353 additions & 1073 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//! Constructor and initialization code generation.
2+
//!
3+
//! Handles generation of:
4+
//! - Module/LibraryModule constructors
5+
//! - Const items for immutable globals
6+
//! - Element segment initialization
7+
//! - Data segment initialization
8+
9+
use crate::backend::Backend;
10+
use crate::ir::*;
11+
12+
/// Emit preamble for generated Rust files.
13+
pub fn rust_code_preamble() -> String {
14+
let mut code = String::new();
15+
code.push_str("// Generated by herkos\n");
16+
code.push_str("// DO NOT EDIT\n\n");
17+
code.push_str("use herkos_runtime::*;\n\n");
18+
code
19+
}
20+
21+
/// Generate const items for immutable globals.
22+
pub fn emit_const_globals<B: Backend>(_backend: &B, info: &ModuleInfo) -> String {
23+
let mut code = String::new();
24+
for (idx, g) in info.globals.iter().enumerate() {
25+
if !g.mutable {
26+
let (rust_ty, value_str) =
27+
crate::codegen::types::global_init_to_rust(&g.init_value, &g.wasm_type);
28+
code.push_str(&format!("pub const G{idx}: {rust_ty} = {value_str};\n"));
29+
}
30+
}
31+
if info.globals.iter().any(|g| !g.mutable) {
32+
code.push('\n');
33+
}
34+
code
35+
}
36+
37+
/// Generate element segment initialization code for a table.
38+
///
39+
/// `table_receiver` is the expression to access the table (e.g., "module.table" or "table").
40+
pub fn emit_element_segments(info: &ModuleInfo, table_receiver: &str) -> String {
41+
let mut code = String::new();
42+
for seg in &info.element_segments {
43+
for (i, func_idx) in seg.func_indices.iter().enumerate() {
44+
let table_idx = seg.offset + i;
45+
let local_func_idx = *func_idx - info.num_imported_functions();
46+
let type_idx = info
47+
.func_signatures
48+
.get(local_func_idx)
49+
.map(|s| s.type_idx)
50+
.unwrap_or(0);
51+
code.push_str(&format!(
52+
" {}.set({}, Some(FuncRef {{ type_index: {}, func_index: {} }})).unwrap();\n",
53+
table_receiver, table_idx, type_idx, local_func_idx
54+
));
55+
}
56+
}
57+
code
58+
}
59+
60+
/// Generate the `pub fn new() -> WasmModule` or `pub fn new() -> WasmResult<WasmModule>` constructor.
61+
pub fn generate_constructor<B: Backend>(
62+
_backend: &B,
63+
info: &ModuleInfo,
64+
has_mut_globals: bool,
65+
) -> String {
66+
let mut code = String::new();
67+
68+
// Simple constructor for modules with no initialization
69+
if !info.has_memory
70+
&& !has_mut_globals
71+
&& info.data_segments.is_empty()
72+
&& info.element_segments.is_empty()
73+
{
74+
code.push_str("pub fn new() -> Result<WasmModule, herkos_runtime::ConstructionError> {\n");
75+
code.push_str(" Ok(WasmModule(LibraryModule::new((), Table::try_new(0)?)))\n");
76+
code.push_str("}\n");
77+
return code;
78+
}
79+
80+
code.push_str("pub fn new() -> WasmResult<WasmModule> {\n");
81+
82+
// Build globals initializer
83+
let globals_init = if has_mut_globals {
84+
let mut fields = String::from("Globals { ");
85+
let mut first = true;
86+
for (idx, g) in info.globals.iter().enumerate() {
87+
if g.mutable {
88+
if !first {
89+
fields.push_str(", ");
90+
}
91+
let (_, value_str) =
92+
crate::codegen::types::global_init_to_rust(&g.init_value, &g.wasm_type);
93+
fields.push_str(&format!("g{idx}: {value_str}"));
94+
first = false;
95+
}
96+
}
97+
fields.push_str(" }");
98+
fields
99+
} else {
100+
"()".to_string()
101+
};
102+
103+
// Table initialization
104+
let table_init = if info.has_table() {
105+
format!("Table::try_new({})?", info.table_initial)
106+
} else {
107+
"Table::try_new(0)?".to_string()
108+
};
109+
110+
if info.has_memory {
111+
let needs_mut = !info.data_segments.is_empty() || !info.element_segments.is_empty();
112+
let binding = if needs_mut {
113+
"let mut module"
114+
} else {
115+
"let module"
116+
};
117+
code.push_str(&format!(
118+
" {} = Module::try_new({}, {}, {}).map_err(|_| WasmTrap::OutOfBounds)?;\n",
119+
binding, info.initial_pages, globals_init, table_init
120+
));
121+
122+
// Data segment initialization (byte-by-byte)
123+
for seg in &info.data_segments {
124+
for (i, byte) in seg.data.iter().enumerate() {
125+
let addr = seg.offset as usize + i;
126+
code.push_str(&format!(
127+
" module.memory.store_u8({}, {})?;\n",
128+
addr, byte
129+
));
130+
}
131+
}
132+
133+
// Element segment initialization
134+
code.push_str(&emit_element_segments(info, "module.table"));
135+
136+
code.push_str(" Ok(WasmModule(module))\n");
137+
} else if !info.element_segments.is_empty() {
138+
// Need mutable table for element initialization
139+
code.push_str(&format!(" let mut table = {};\n", table_init));
140+
code.push_str(&emit_element_segments(info, "table"));
141+
code.push_str(&format!(
142+
" Ok(WasmModule(LibraryModule::new({}, table)))\n",
143+
globals_init
144+
));
145+
} else {
146+
code.push_str(&format!(
147+
" Ok(WasmModule(LibraryModule::new({}, {})))\n",
148+
globals_init, table_init
149+
));
150+
}
151+
152+
code.push_str("}\n");
153+
code
154+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! Export implementation generation.
2+
//!
3+
//! Generates the `impl WasmModule { ... }` block with exported function methods
4+
//! that forward calls to internal functions while managing shared state.
5+
6+
use crate::backend::Backend;
7+
use crate::ir::*;
8+
9+
/// Generate the `impl WasmModule { ... }` block with export methods.
10+
pub fn generate_export_impl<B: Backend>(_backend: &B, info: &ModuleInfo) -> String {
11+
let mut code = String::new();
12+
let has_mut_globals = info.has_mutable_globals();
13+
14+
code.push_str("impl WasmModule {\n");
15+
16+
for export in &info.func_exports {
17+
let func_idx = export.func_index;
18+
let sig = &info.func_signatures[func_idx];
19+
20+
// Determine trait bounds for this export
21+
let trait_bounds_opt = if sig.needs_host {
22+
crate::codegen::traits::build_trait_bounds(info)
23+
} else {
24+
None
25+
};
26+
let has_multiple_bounds = trait_bounds_opt.as_ref().is_some_and(|b| b.contains(" + "));
27+
28+
// Build generics: handle both H (host) and MP (imported memory size)
29+
let mut generics: Vec<String> = Vec::new();
30+
if info.has_memory_import {
31+
generics.push("const MP: usize".to_string());
32+
}
33+
if has_multiple_bounds {
34+
generics.push(format!("H: {}", trait_bounds_opt.as_ref().unwrap()));
35+
}
36+
37+
// Method signature with optional generic parameter
38+
let mut param_parts: Vec<String> = Vec::new();
39+
param_parts.push("&mut self".to_string());
40+
for (i, ty) in sig.params.iter().enumerate() {
41+
let rust_ty = crate::codegen::types::wasm_type_to_rust(ty);
42+
param_parts.push(format!("v{i}: {rust_ty}"));
43+
}
44+
45+
// Add memory parameter if imported
46+
if info.has_memory_import {
47+
param_parts.push("memory: &mut IsolatedMemory<MP>".to_string());
48+
}
49+
50+
// Add host parameter if function needs it
51+
if sig.needs_host {
52+
if let Some(trait_bounds) = &trait_bounds_opt {
53+
if has_multiple_bounds {
54+
// Use generic parameter H
55+
param_parts.push("host: &mut H".to_string());
56+
} else {
57+
// Single trait bound - use impl directly
58+
param_parts.push(format!("host: &mut impl {trait_bounds}"));
59+
}
60+
} else {
61+
// Fallback for backwards compatibility
62+
param_parts.push("host: &mut impl Host".to_string());
63+
}
64+
}
65+
66+
let return_type = crate::codegen::types::format_return_type(sig.return_type.as_ref());
67+
68+
// Generate method signature (with generics if needed)
69+
let generic_part = if generics.is_empty() {
70+
String::new()
71+
} else {
72+
format!("<{}>", generics.join(", "))
73+
};
74+
75+
code.push_str(&format!(
76+
" pub fn {}{generic_part}({}) -> {} {{\n",
77+
export.name,
78+
param_parts.join(", "),
79+
return_type
80+
));
81+
82+
// Forward call to internal function
83+
let mut call_args: Vec<String> = (0..sig.params.len()).map(|i| format!("v{i}")).collect();
84+
85+
// Forward host parameter if needed
86+
if sig.needs_host {
87+
call_args.push("host".to_string());
88+
}
89+
90+
if has_mut_globals {
91+
call_args.push("&mut self.0.globals".to_string());
92+
}
93+
if info.has_memory {
94+
call_args.push("&mut self.0.memory".to_string());
95+
} else if info.has_memory_import {
96+
call_args.push("memory".to_string());
97+
}
98+
if info.has_table() {
99+
call_args.push("&self.0.table".to_string());
100+
}
101+
102+
code.push_str(&format!(
103+
" func_{}({})\n",
104+
export.func_index,
105+
call_args.join(", ")
106+
));
107+
code.push_str(" }\n");
108+
}
109+
110+
code.push_str("}\n");
111+
code
112+
}

0 commit comments

Comments
 (0)