-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunction.rs
More file actions
338 lines (301 loc) · 11.8 KB
/
Copy pathfunction.rs
File metadata and controls
338 lines (301 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
//! Function code generation from IR.
//!
//! Converts IR functions to complete Rust functions,
//! including signature generation, variable declarations,
//! and block-to-code translation.
use crate::backend::Backend;
use crate::ir::*;
use anyhow::Result;
/// Generate a complete Rust function from IR with module info.
///
/// `is_public` controls whether the function is `pub fn` or `fn`.
pub fn generate_function_with_info<B: Backend>(
backend: &B,
ir_func: &IrFunction,
func_name: &str,
info: &ModuleInfo,
is_public: bool,
) -> Result<String> {
let mut output = String::new();
// Suppress warnings for generated code patterns that are hard to avoid
output.push_str("#[allow(unused_mut, unused_variables, unused_assignments, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)]\n");
// Generate function signature
output.push_str(&generate_signature_with_info(
backend, ir_func, func_name, info, is_public,
));
output.push_str(" {\n");
// Create mapping from BlockId to vector index
let mut block_id_to_index = std::collections::HashMap::new();
for (idx, block) in ir_func.blocks.iter().enumerate() {
block_id_to_index.insert(block.id, idx);
}
// Collect all variables and their types from instructions.
let mut var_types: std::collections::HashMap<VarId, WasmType> =
std::collections::HashMap::new();
// Seed with parameter types
for (var, ty) in &ir_func.params {
var_types.insert(*var, *ty);
}
// Seed with declared local variable types
for (var, ty) in &ir_func.locals {
var_types.insert(*var, *ty);
}
// Infer types from instructions
for block in &ir_func.blocks {
for instr in &block.instructions {
match instr {
IrInstr::Const { dest, value } => {
var_types.insert(*dest, value.wasm_type());
}
IrInstr::BinOp { dest, op, .. } => {
var_types.insert(*dest, op.result_type());
}
IrInstr::UnOp { dest, op, .. } => {
var_types.insert(*dest, op.result_type());
}
IrInstr::Load { dest, ty, .. } => {
var_types.insert(*dest, *ty);
}
IrInstr::Call {
dest: Some(dest),
func_idx,
..
} => {
// func_idx is in local space (imports already excluded)
let ty = info
.ir_function(*func_idx)
.and_then(|f| f.return_type)
.unwrap_or(WasmType::I32);
var_types.insert(*dest, ty);
}
IrInstr::CallImport {
dest: Some(dest),
import_idx,
..
} => {
// Look up import signature from func_imports
let ty = info
.func_import(import_idx.clone())
.and_then(|imp| imp.return_type)
.unwrap_or(WasmType::I32);
var_types.insert(*dest, ty);
}
IrInstr::Assign { dest, src } => {
if let Some(ty) = var_types.get(src) {
var_types.insert(*dest, *ty);
} else {
var_types.insert(*dest, WasmType::I32);
}
}
IrInstr::GlobalGet { dest, index } => {
let ty = match info.resolve_global(*index) {
ResolvedGlobal::Imported(_idx, g) => g.wasm_type,
ResolvedGlobal::Local(_idx, g) => g.init_value.ty(),
};
var_types.insert(*dest, ty);
}
IrInstr::CallIndirect {
dest: Some(dest),
type_idx,
..
} => {
let ty = info
.type_signature(type_idx.clone())
.and_then(|s| s.return_type)
.unwrap_or(WasmType::I32);
var_types.insert(*dest, ty);
}
IrInstr::MemorySize { dest } | IrInstr::MemoryGrow { dest, .. } => {
var_types.insert(*dest, WasmType::I32);
}
IrInstr::Select { dest, val1, .. } => {
// Result type matches the operand type
let ty = var_types.get(val1).copied().unwrap_or(WasmType::I32);
var_types.insert(*dest, ty);
}
_ => {}
}
}
// Also scan terminators for variable references (needed for
// dead-code blocks after `unreachable` where the variable
// was never assigned by an instruction).
match &block.terminator {
IrTerminator::Return { value: Some(var) } => {
var_types
.entry(*var)
.or_insert(ir_func.return_type.unwrap_or(WasmType::I32));
}
IrTerminator::BranchIf { condition, .. } => {
var_types.entry(*condition).or_insert(WasmType::I32);
}
IrTerminator::BranchTable { index, .. } => {
var_types.entry(*index).or_insert(WasmType::I32);
}
_ => {}
}
}
// Declare all SSA variables with their inferred types
let mut sorted_vars: Vec<_> = var_types
.iter()
.filter(|(var, _)| !ir_func.params.iter().any(|(p, _)| p == *var))
.collect();
sorted_vars.sort_by_key(|(var, _)| var.0);
for (var, ty) in sorted_vars {
let rust_ty = crate::codegen::types::wasm_type_to_rust(ty);
let default = ty.default_value_literal();
output.push_str(&format!(" let mut {var}: {rust_ty} = {default};\n"));
}
// Multi-block: state machine with per-function Block enum
output.push_str(" #[derive(Clone, Copy)]\n #[allow(dead_code)]\n");
output.push_str(" enum Block { ");
for idx in 0..ir_func.blocks.len() {
if idx > 0 {
output.push_str(", ");
}
output.push_str(&format!("B{}", idx));
}
output.push_str(" }\n");
output.push_str(" let mut __current_block = Block::B0;\n");
output.push_str(" loop {\n");
output.push_str(" match __current_block {\n");
// Compute whether this function has a host parameter in scope
// (same conditions as in generate_signature_with_info)
let has_call_indirect_with_imports =
has_call_indirect(ir_func) && !info.func_imports.is_empty();
let caller_has_host = has_import_calls(ir_func)
|| has_global_import_access(ir_func, info.imported_globals.len())
|| has_call_indirect_with_imports;
for (idx, block) in ir_func.blocks.iter().enumerate() {
output.push_str(&format!(" Block::B{} => {{\n", idx));
for instr in &block.instructions {
let code = crate::codegen::instruction::generate_instruction_with_info(
backend,
instr,
info,
caller_has_host,
)?;
output.push_str(&code);
output.push('\n');
}
let term_code = crate::codegen::instruction::generate_terminator_with_mapping(
backend,
&block.terminator,
&block_id_to_index,
ir_func.return_type,
);
output.push_str(&term_code);
output.push('\n');
output.push_str(" }\n");
}
// No catch-all needed — match is exhaustive over Block enum
output.push_str(" }\n");
output.push_str(" }\n");
output.push_str("}\n");
Ok(output)
}
/// Generate function signature with module info.
fn generate_signature_with_info<B: Backend>(
_backend: &B,
ir_func: &IrFunction,
func_name: &str,
info: &ModuleInfo,
is_public: bool,
) -> String {
let visibility = if is_public { "pub " } else { "" };
// Check if function needs host parameter (imports, global imports, or call_indirect with imports)
let has_call_indirect_with_imports =
has_call_indirect(ir_func) && !info.func_imports.is_empty();
let needs_host = has_import_calls(ir_func)
|| has_global_import_access(ir_func, info.imported_globals.len())
|| has_call_indirect_with_imports;
let trait_bounds_opt = if needs_host {
crate::codegen::traits::build_trait_bounds(info)
} else {
None
};
let has_multiple_bounds = trait_bounds_opt.as_ref().is_some_and(|b| b.contains(" + "));
// Build generics: handle both H (host) and MP (imported memory size)
let mut generics: Vec<String> = Vec::new();
if info.has_memory_import {
generics.push("const MP: usize".to_string());
}
if has_multiple_bounds {
generics.push(format!("H: {}", trait_bounds_opt.as_ref().unwrap()));
}
let generic_part = if generics.is_empty() {
String::new()
} else {
format!("<{}>", generics.join(", "))
};
let mut sig = format!("{visibility}fn {func_name}{generic_part}(");
// Parameters (mutable, as in WebAssembly all locals are mutable)
let mut param_parts: Vec<String> = ir_func
.params
.iter()
.map(|(var_id, ty)| {
let rust_ty = crate::codegen::types::wasm_type_to_rust(ty);
format!("mut {}: {}", var_id, rust_ty)
})
.collect();
// Add host parameter if function needs imports or global imports
if let Some(trait_bounds) = trait_bounds_opt {
if has_multiple_bounds {
// Use generic parameter H
param_parts.push("host: &mut H".to_string());
} else {
// Single trait bound - use impl directly
param_parts.push(format!("host: &mut impl {trait_bounds}"));
}
}
// Add globals parameter if module has mutable globals
if info.has_mutable_globals() {
param_parts.push("globals: &mut Globals".to_string());
}
// Add memory parameter — either const MAX_PAGES or generic MP
if info.has_memory {
param_parts.push("memory: &mut IsolatedMemory<MAX_PAGES>".to_string());
} else if info.has_memory_import {
param_parts.push("memory: &mut IsolatedMemory<MP>".to_string());
}
// Add table parameter if module has a table
if info.has_table() {
param_parts.push("table: &Table<TABLE_MAX>".to_string());
}
sig.push_str(¶m_parts.join(", "));
sig.push(')');
// Return type
sig.push_str(&format!(
" -> {}",
crate::codegen::types::format_return_type(ir_func.return_type.as_ref())
));
sig
}
/// Check if an IR function has any import calls.
fn has_import_calls(ir_func: &IrFunction) -> bool {
ir_func.blocks.iter().any(|block| {
block
.instructions
.iter()
.any(|instr| matches!(instr, IrInstr::CallImport { .. }))
})
}
/// Check if an IR function has any call_indirect instructions.
fn has_call_indirect(ir_func: &IrFunction) -> bool {
ir_func.blocks.iter().any(|block| {
block
.instructions
.iter()
.any(|instr| matches!(instr, IrInstr::CallIndirect { .. }))
})
}
/// Check if an IR function accesses any imported globals.
fn has_global_import_access(ir_func: &IrFunction, num_imported_globals: usize) -> bool {
if num_imported_globals == 0 {
return false;
}
ir_func.blocks.iter().any(|block| {
block.instructions.iter().any(|instr| {
matches!(instr, IrInstr::GlobalGet { index, .. } | IrInstr::GlobalSet { index, .. } if index.as_usize() < num_imported_globals)
})
})
}