ruka_codegen_wasm/
entry.rs

1use super::*;
2
3/// Configuration switches for direct WASM code generation.
4#[derive(Debug, Clone)]
5pub struct EmitOptions {
6    /// Emit `run_main` with a runtime leak assertion call after `main`.
7    ///
8    /// Defaults to `true`.
9    pub assert_no_leaks_at_run_main: bool,
10    /// Best-effort map of source function names to 1-based declaration lines.
11    pub function_lines: BTreeMap<String, usize>,
12}
13
14impl Default for EmitOptions {
15    fn default() -> Self {
16        Self {
17            assert_no_leaks_at_run_main: true,
18            function_lines: BTreeMap::new(),
19        }
20    }
21}
22
23/// Emit WASM and matching WAT from MIR.
24///
25/// WAT is rendered from validated `wasm_bytes` via `wasmprinter`.
26/// If lowering or validation fails, no WASM/WAT is returned and diagnostics
27/// describe the failure.
28pub fn emit_program_best_effort(program: &ruka_mir::MirProgram) -> WasmArtifacts {
29    emit_program_best_effort_with_options(program, EmitOptions::default())
30}
31
32/// Emit WASM and matching WAT from MIR with explicit backend options.
33pub fn emit_program_best_effort_with_options(
34    program: &ruka_mir::MirProgram,
35    options: EmitOptions,
36) -> WasmArtifacts {
37    let mut diagnostics = Vec::<WasmDiagnostic>::new();
38
39    if let Err(error) = validate_owned_function_returns(program) {
40        diagnostics.push(WasmDiagnostic {
41            phase: "codegen_wasm",
42            message: error.to_string(),
43        });
44        return WasmArtifacts {
45            wat_source: String::new(),
46            wasm_bytes: None,
47            diagnostics,
48        };
49    }
50
51    for (_, function) in program.functions.iter() {
52        function.assert_valid();
53    }
54
55    let string_literals = collect_string_literals(program).0;
56    let function_order = program
57        .functions
58        .iter()
59        .map(|(id, function)| (id, function.name.clone()))
60        .collect::<Vec<_>>();
61    let callee_param_runtime_mask = program
62        .functions
63        .iter()
64        .map(|(id, function)| {
65            let mask = function
66                .params
67                .iter()
68                .map(|local| !matches!(function.locals[*local].ty, Ty::Unit))
69                .collect::<Vec<_>>();
70            (id.as_u32(), mask)
71        })
72        .collect::<BTreeMap<u32, Vec<bool>>>();
73    let callee_returns_runtime = program
74        .functions
75        .iter()
76        .map(|(id, function)| {
77            (
78                id.as_u32(),
79                !matches!(function.return_ty, Ty::Unit)
80                    && !function_returns_via_out_slot(&function.return_ty),
81            )
82        })
83        .collect::<BTreeMap<u32, bool>>();
84    let callee_param_inout_mask = program
85        .functions
86        .iter()
87        .map(|(id, function)| {
88            let mask = function
89                .param_bindings()
90                .map(borrowed_param_uses_inout)
91                .collect::<Vec<_>>();
92            (id.as_u32(), mask)
93        })
94        .collect::<BTreeMap<u32, Vec<bool>>>();
95    let callee_returns_via_out_slot = program
96        .functions
97        .iter()
98        .map(|(id, function)| {
99            (
100                id.as_u32(),
101                function_returns_via_out_slot(&function.return_ty),
102            )
103        })
104        .collect::<BTreeMap<u32, bool>>();
105    let callee_param_runtime_types = program
106        .functions
107        .iter()
108        .map(|(id, function)| {
109            let types = function
110                .param_bindings()
111                .filter_map(|binding| {
112                    if matches!(binding.local.ty, Ty::Unit) {
113                        None
114                    } else {
115                        Some(if borrowed_param_uses_inout(binding) {
116                            ty_to_valtype(binding.semantic_ty())
117                        } else {
118                            param_binding_valtype(binding)
119                        })
120                    }
121                })
122                .collect::<Vec<_>>();
123            (id.as_u32(), types)
124        })
125        .collect::<BTreeMap<u32, Vec<ValType>>>();
126
127    let linked = match link_runtime_with_literals(&string_literals) {
128        Ok(linked) => linked,
129        Err(error) => {
130            diagnostics.push(WasmDiagnostic {
131                phase: "codegen_wasm",
132                message: error.to_string(),
133            });
134            return WasmArtifacts {
135                wat_source: String::new(),
136                wasm_bytes: None,
137                diagnostics,
138            };
139        }
140    };
141    let mut module = linked.module;
142    let mut used_function_names = module
143        .funcs
144        .iter()
145        .filter_map(|item| item.name.clone())
146        .collect::<BTreeSet<_>>();
147    let runtime = linked.runtime;
148    let memory_id = linked.memory_id;
149    let string_literal_offsets = linked.string_literal_offsets;
150    let pointer_drop_functions = emit_pointer_drop_functions(
151        &mut module,
152        &runtime,
153        memory_id,
154        &program.structs,
155        &program.enums,
156        program,
157    )
158    .map_err(|error| WasmDiagnostic {
159        phase: "codegen_wasm",
160        message: error.to_string(),
161    })
162    .map_or_else(
163        |diagnostic| {
164            diagnostics.push(diagnostic);
165            BTreeMap::<String, FunctionId>::new()
166        },
167        |functions| functions,
168    );
169
170    let mut func_id_by_mir = BTreeMap::<u32, FunctionId>::new();
171    let mut main_function_id = None::<FunctionId>;
172    let mut main_returns_runtime_value = false;
173    for (func_id, _) in &function_order {
174        let function_ref = &program.functions[*func_id];
175        let (params, results) = signature_types(function_ref);
176        let mut builder = FunctionBuilder::new(&mut module.types, &params, &results);
177        if let Some(result_ty) = results.first().copied() {
178            match result_ty {
179                ValType::I32 => {
180                    builder.func_body().i32_const(0);
181                }
182                ValType::I64 => {
183                    builder.func_body().i64_const(0);
184                }
185                _ => {}
186            }
187        }
188
189        let mut args = Vec::<LocalId>::new();
190        for param_ty in &params {
191            args.push(module.locals.add(*param_ty));
192        }
193
194        let function_id = builder.finish(args, &mut module.funcs);
195        let readable_name = format!("rk::{}", readable_symbol_fragment(&function_ref.name));
196        assign_unique_function_name(
197            &mut module,
198            &mut used_function_names,
199            function_id,
200            &readable_name,
201        );
202        if function_ref.name == "main" {
203            main_function_id = Some(function_id);
204            main_returns_runtime_value = !matches!(function_ref.return_ty, Ty::Unit)
205                && !function_returns_via_out_slot(&function_ref.return_ty);
206        }
207        let _ = func_id_by_mir.insert(func_id.as_u32(), function_id);
208    }
209
210    if let Some(main_function_id) = main_function_id {
211        module.exports.add("main", main_function_id);
212        let mut builder = FunctionBuilder::new(&mut module.types, &[], &[]);
213        let mut body = builder.func_body();
214        body.call(main_function_id);
215        if main_returns_runtime_value {
216            body.drop();
217        }
218        if options.assert_no_leaks_at_run_main {
219            let assert_no_leaks = runtime_function(&runtime, wasm_api::RT_ASSERT_NO_LEAKS_SYMBOL)
220                .map_err(|error| WasmDiagnostic {
221                    phase: "codegen_wasm",
222                    message: error.to_string(),
223                })
224                .map_or_else(
225                    |diagnostic| {
226                        diagnostics.push(diagnostic);
227                        None
228                    },
229                    |function| Some(function.function_id),
230                );
231            if let Some(assert_no_leaks) = assert_no_leaks {
232                body.call(assert_no_leaks);
233            }
234        }
235        let run_main_id = builder.finish(Vec::new(), &mut module.funcs);
236        assign_unique_function_name(
237            &mut module,
238            &mut used_function_names,
239            run_main_id,
240            "rk::run_main",
241        );
242        module.exports.add("run_main", run_main_id);
243    }
244
245    for (type_key, function_id) in &pointer_drop_functions {
246        let readable_name = format!("rk::drop::{}", readable_symbol_fragment(type_key));
247        assign_unique_function_name(
248            &mut module,
249            &mut used_function_names,
250            *function_id,
251            &readable_name,
252        );
253    }
254
255    for (func_id, _) in &function_order {
256        let function_ref = &program.functions[*func_id];
257        let Some(wasm_func_id) = func_id_by_mir.get(&func_id.as_u32()).copied() else {
258            diagnostics.push(WasmDiagnostic {
259                phase: "codegen_wasm",
260                message: LowerError::UnknownCallee.to_string(),
261            });
262            return WasmArtifacts {
263                wat_source: String::new(),
264                wasm_bytes: None,
265                diagnostics,
266            };
267        };
268        let lower_ctx = ModuleLowerCtx {
269            func_id_by_mir: &func_id_by_mir,
270            callee_param_runtime_mask: &callee_param_runtime_mask,
271            callee_param_runtime_types: &callee_param_runtime_types,
272            callee_param_inout_mask: &callee_param_inout_mask,
273            callee_returns_via_out_slot: &callee_returns_via_out_slot,
274            callee_returns_runtime: &callee_returns_runtime,
275            runtime: &runtime,
276            pointer_drop_functions: &pointer_drop_functions,
277            memory_id,
278            string_literal_offsets: &string_literal_offsets,
279            structs: &program.structs,
280            enums: &program.enums,
281            function_lines: &options.function_lines,
282        };
283        match lower_function(&mut module, wasm_func_id, function_ref, &lower_ctx) {
284            Ok(()) => {}
285            Err(error) => {
286                diagnostics.push(WasmDiagnostic {
287                    phase: "codegen_wasm",
288                    message: format!("function `{}`: {error}", function_ref.name),
289                });
290            }
291        }
292    }
293
294    let wasm_bytes = if diagnostics.is_empty() {
295        Some(module.emit_wasm())
296    } else {
297        None
298    };
299
300    let wat_source = match &wasm_bytes {
301        Some(bytes) => {
302            let mut validator = Validator::new();
303            if let Err(error) = validator.validate_all(bytes) {
304                diagnostics.push(WasmDiagnostic {
305                    phase: "codegen_wasm",
306                    message: format!("emitted wasm failed validation: {error}"),
307                });
308                String::new()
309            } else {
310                let mut wat = String::new();
311                let mut printer = WatPrinterConfig::new();
312                printer.name_unnamed(true);
313                match printer.print(bytes, &mut PrintFmtWrite(&mut wat)) {
314                    Ok(()) => wat,
315                    Err(error) => {
316                        diagnostics.push(WasmDiagnostic {
317                            phase: "codegen_wasm",
318                            message: format!("failed to render WAT from emitted WASM: {error}"),
319                        });
320                        String::new()
321                    }
322                }
323            }
324        }
325        None => String::new(),
326    };
327
328    let wasm_bytes = if diagnostics.is_empty() {
329        wasm_bytes
330    } else {
331        None
332    };
333
334    WasmArtifacts {
335        wat_source,
336        wasm_bytes,
337        diagnostics,
338    }
339}
340
341/// Reject borrowed/reference returns before ABI planning.
342fn validate_owned_function_returns(program: &ruka_mir::MirProgram) -> Result<(), LowerError> {
343    for (_, function) in program.functions.iter() {
344        if is_borrowed_return_ty(&function.return_ty) {
345            return Err(LowerError::BorrowedReturnType {
346                function: function.name.clone(),
347                return_ty: function.return_ty.to_string(),
348            });
349        }
350    }
351    Ok(())
352}
353
354/// Return whether one return type denotes a borrowed/reference value.
355fn is_borrowed_return_ty(return_ty: &Ty) -> bool {
356    matches!(return_ty, Ty::RefRo(_) | Ty::RefMut(_))
357}
358
359/// Build the WASM function signature for a MIR function.
360pub(crate) fn signature_types(function: &ruka_mir::MirFunction) -> (Vec<ValType>, Vec<ValType>) {
361    let mut params = function
362        .param_bindings()
363        .filter_map(|binding| {
364            if matches!(binding.local.ty, Ty::Unit) {
365                None
366            } else {
367                Some(if borrowed_param_uses_inout(binding) {
368                    ty_to_valtype(binding.semantic_ty())
369                } else {
370                    param_binding_valtype(binding)
371                })
372            }
373        })
374        .collect::<Vec<_>>();
375    if function_returns_via_out_slot(&function.return_ty) {
376        params.insert(0, ValType::I32);
377    }
378    let mut results = function
379        .param_bindings()
380        .filter_map(|binding| {
381            if borrowed_param_uses_inout(binding) {
382                Some(ty_to_valtype(binding.semantic_ty()))
383            } else {
384                None
385            }
386        })
387        .collect::<Vec<_>>();
388    if !matches!(function.return_ty, Ty::Unit)
389        && !function_returns_via_out_slot(&function.return_ty)
390    {
391        results.push(ty_to_valtype(&function.return_ty));
392    }
393    (params, results)
394}
395
396pub(crate) fn borrowed_param_uses_inout(binding: ruka_mir::MirParamBinding<'_>) -> bool {
397    binding.expects_mut_borrow() && !is_shadow_stack_aggregate_ty(binding.semantic_ty())
398}
399
400pub(crate) fn function_returns_via_out_slot(return_ty: &Ty) -> bool {
401    is_shadow_stack_aggregate_ty(return_ty)
402}
403
404/// Map one MIR parameter binding to the incoming WASM value type.
405pub(crate) fn param_binding_valtype(binding: ruka_mir::MirParamBinding<'_>) -> ValType {
406    if binding.expects_mut_borrow() {
407        ty_to_valtype(&binding.local.ty)
408    } else {
409        ty_to_valtype(binding.semantic_ty())
410    }
411}
412
413/// Map a checker type to the backend runtime value type.
414pub(crate) fn ty_to_valtype(ty: &Ty) -> ValType {
415    match ty {
416        Ty::U8 | Ty::U16 | Ty::U32 | Ty::I8 | Ty::I16 | Ty::I32 | Ty::Bool => ValType::I32,
417        Ty::U64 | Ty::I64 => ValType::I64,
418        Ty::F32 => ValType::F32,
419        Ty::F64 => ValType::F64,
420        Ty::String
421        | Ty::Unit
422        | Ty::Option(_)
423        | Ty::Pointer(_)
424        | Ty::Array { .. }
425        | Ty::Tuple(_)
426        | Ty::Struct { .. }
427        | Ty::Enum { .. } => ValType::I32,
428        Ty::Slice(_) => ValType::I32,
429        Ty::RefRo(_) | Ty::RefMut(_) => ValType::I32,
430    }
431}
432
433pub(crate) fn is_shadow_stack_aggregate_ty(ty: &Ty) -> bool {
434    matches!(ty, Ty::Tuple(_) | Ty::Struct { .. } | Ty::Slice(_))
435}
436
437pub(crate) fn align_up_u32(value: u32, alignment: u32) -> u32 {
438    if alignment == 0 {
439        return value;
440    }
441    let mask = alignment - 1;
442    (value + mask) & !mask
443}
444
445pub(crate) fn should_shadow_stack_local(info: &ruka_mir::LocalInfo) -> bool {
446    if !info.repr.is_place() {
447        return is_shadow_stack_aggregate_ty(&info.ty);
448    }
449    matches!(
450        &info.ty,
451        Ty::RefRo(inner) | Ty::RefMut(inner) if matches!(inner.as_ref(), Ty::Slice(_))
452    )
453}
454
455pub(crate) fn ty_key(ty: &Ty) -> String {
456    format!("{ty:?}")
457}
458
459pub(crate) fn readable_symbol_fragment(value: &str) -> String {
460    let mut out = String::with_capacity(value.len());
461    let mut last_was_sep = false;
462    for ch in value.chars() {
463        let is_kept = ch.is_ascii_alphanumeric()
464            || matches!(
465                ch,
466                '_' | ':' | '<' | '>' | '[' | ']' | ',' | '.' | '*' | '&' | '-'
467            );
468        let next = if is_kept { ch } else { '_' };
469        if next == '_' {
470            if last_was_sep {
471                continue;
472            }
473            last_was_sep = true;
474        } else {
475            last_was_sep = false;
476        }
477        out.push(next);
478    }
479    let trimmed = out.trim_matches('_');
480    if trimmed.is_empty() {
481        "anon".to_owned()
482    } else {
483        trimmed.to_owned()
484    }
485}
486
487pub(crate) fn assign_unique_function_name(
488    module: &mut walrus::Module,
489    used_names: &mut BTreeSet<String>,
490    function_id: FunctionId,
491    preferred_name: &str,
492) {
493    let mut candidate = preferred_name.to_owned();
494    let mut suffix = 1_u32;
495    while used_names.contains(&candidate) {
496        suffix += 1;
497        candidate = format!("{preferred_name}::{suffix}");
498    }
499    let _ = used_names.insert(candidate.clone());
500    module.funcs.get_mut(function_id).name = Some(candidate);
501}
502
503pub(crate) fn enum_decl_by_name<'a>(
504    enums: &'a [ruka_mir::MirEnumDecl],
505    enum_name: &str,
506) -> Result<&'a ruka_mir::MirEnumDecl, LowerError> {
507    enums
508        .iter()
509        .find(|decl| decl.name == enum_name)
510        .ok_or(LowerError::UnsupportedInstruction(
511            "missing enum declaration",
512        ))
513}
514
515pub(crate) fn enum_variant_index(
516    enums: &[ruka_mir::MirEnumDecl],
517    enum_name: &str,
518    variant: &str,
519) -> Result<u32, LowerError> {
520    let decl = enum_decl_by_name(enums, enum_name)?;
521    let index = decl
522        .variants
523        .iter()
524        .position(|item| item.name == variant)
525        .ok_or(LowerError::UnsupportedInstruction("unknown enum variant"))?;
526    u32::try_from(index)
527        .map_err(|_| LowerError::UnsupportedInstruction("enum variant index overflow"))
528}
529
530pub(crate) fn mir_type_expr_to_ty_with_subst(
531    ty: &ruka_mir::MirTypeExpr,
532    bindings: &BTreeMap<String, Ty>,
533) -> Result<Ty, LowerError> {
534    match ty {
535        ruka_mir::MirTypeExpr::Named(name) => match name.as_str() {
536            "Unit" => Ok(Ty::Unit),
537            "u8" => Ok(Ty::U8),
538            "u16" => Ok(Ty::U16),
539            "u32" => Ok(Ty::U32),
540            "u64" => Ok(Ty::U64),
541            "i8" => Ok(Ty::I8),
542            "i16" => Ok(Ty::I16),
543            "i32" => Ok(Ty::I32),
544            "i64" => Ok(Ty::I64),
545            "f32" => Ok(Ty::F32),
546            "f64" => Ok(Ty::F64),
547            "String" => Ok(Ty::String),
548            "Bool" => Ok(Ty::Bool),
549            _ => {
550                if let Some(actual) = bindings.get(name) {
551                    Ok(actual.clone())
552                } else {
553                    Ok(Ty::Struct {
554                        name: name.to_owned(),
555                        args: Vec::new(),
556                    })
557                }
558            }
559        },
560        ruka_mir::MirTypeExpr::Pointer { item } => Ok(Ty::Pointer(Box::new(
561            mir_type_expr_to_ty_with_subst(item, bindings)?,
562        ))),
563        ruka_mir::MirTypeExpr::Array { item, len } => Ok(Ty::Array {
564            item: Box::new(mir_type_expr_to_ty_with_subst(item, bindings)?),
565            len: *len,
566        }),
567        ruka_mir::MirTypeExpr::Slice { item } => Ok(Ty::Slice(Box::new(
568            mir_type_expr_to_ty_with_subst(item, bindings)?,
569        ))),
570        ruka_mir::MirTypeExpr::Tuple(items) => Ok(Ty::Tuple(
571            items
572                .iter()
573                .map(|item| mir_type_expr_to_ty_with_subst(item, bindings))
574                .collect::<Result<Vec<_>, _>>()?,
575        )),
576        ruka_mir::MirTypeExpr::Apply {
577            callee,
578            args: type_args,
579        } => Ok(Ty::Struct {
580            name: callee.clone(),
581            args: type_args
582                .iter()
583                .map(|item| mir_type_expr_to_ty_with_subst(item, bindings))
584                .collect::<Result<Vec<_>, _>>()?,
585        }),
586    }
587}
588
589pub(crate) fn collect_pointer_types(
590    ty: &Ty,
591    structs: &[ruka_mir::MirStructDecl],
592    enums: &[ruka_mir::MirEnumDecl],
593    out: &mut BTreeMap<String, Ty>,
594    visiting_structs: &mut BTreeSet<String>,
595) {
596    match ty {
597        Ty::Pointer(item) => {
598            let pointer_ty = Ty::Pointer(item.clone());
599            let _ = out.insert(ty_key(&pointer_ty), pointer_ty);
600            collect_pointer_types(item, structs, enums, out, visiting_structs);
601        }
602        Ty::Option(item) => {
603            collect_pointer_types(item, structs, enums, out, visiting_structs);
604        }
605        Ty::Array { item, .. } | Ty::Slice(item) | Ty::RefRo(item) | Ty::RefMut(item) => {
606            collect_pointer_types(item, structs, enums, out, visiting_structs)
607        }
608        Ty::Tuple(items) => {
609            for item in items {
610                collect_pointer_types(item, structs, enums, out, visiting_structs);
611            }
612        }
613        Ty::Struct { name, .. } => {
614            if !visiting_structs.insert(name.clone()) {
615                return;
616            }
617            if let Some(decl) = structs.iter().find(|decl| decl.name == *name) {
618                for field in &decl.fields {
619                    if let Ok(field_ty) =
620                        aggregate::aggregate_field_ty(ty, field.name.as_str(), structs, enums)
621                    {
622                        collect_pointer_types(&field_ty, structs, enums, out, visiting_structs);
623                    }
624                }
625            }
626            let _ = visiting_structs.remove(name);
627        }
628        Ty::Enum { name, args } => {
629            if let Some(decl) = enums.iter().find(|decl| decl.name == *name) {
630                let bindings = decl
631                    .type_params
632                    .iter()
633                    .cloned()
634                    .zip(args.iter().cloned())
635                    .collect::<BTreeMap<_, _>>();
636                for variant in &decl.variants {
637                    for payload in &variant.payload {
638                        if let Ok(payload_ty) = mir_type_expr_to_ty_with_subst(payload, &bindings) {
639                            collect_pointer_types(
640                                &payload_ty,
641                                structs,
642                                enums,
643                                out,
644                                visiting_structs,
645                            );
646                        }
647                    }
648                }
649            }
650            for arg in args {
651                collect_pointer_types(arg, structs, enums, out, visiting_structs);
652            }
653        }
654        Ty::Unit
655        | Ty::U8
656        | Ty::U16
657        | Ty::U32
658        | Ty::U64
659        | Ty::I8
660        | Ty::I16
661        | Ty::I32
662        | Ty::I64
663        | Ty::F32
664        | Ty::F64
665        | Ty::Bool
666        | Ty::String => {}
667    }
668}
669
670pub(crate) fn emit_pointer_drop_functions(
671    module: &mut walrus::Module,
672    runtime: &RuntimeFunctions,
673    memory_id: MemoryId,
674    structs: &[ruka_mir::MirStructDecl],
675    enums: &[ruka_mir::MirEnumDecl],
676    program: &ruka_mir::MirProgram,
677) -> Result<BTreeMap<String, FunctionId>, LowerError> {
678    let mut pointer_types = BTreeMap::<String, Ty>::new();
679    let mut visiting_structs = BTreeSet::<String>::new();
680    for (_, function) in program.functions.iter() {
681        for param in function.params.iter().copied() {
682            collect_pointer_types(
683                &function.locals[param].ty,
684                structs,
685                enums,
686                &mut pointer_types,
687                &mut visiting_structs,
688            );
689        }
690        for (_, local) in function.locals.iter() {
691            collect_pointer_types(
692                &local.ty,
693                structs,
694                enums,
695                &mut pointer_types,
696                &mut visiting_structs,
697            );
698        }
699    }
700
701    let mut pointer_drop_functions = BTreeMap::<String, FunctionId>::new();
702    for key in pointer_types.keys() {
703        let builder = FunctionBuilder::new(&mut module.types, &[ValType::I32], &[]);
704        let function_id = builder.finish(vec![module.locals.add(ValType::I32)], &mut module.funcs);
705        let _ = pointer_drop_functions.insert(key.clone(), function_id);
706    }
707
708    let free = runtime_function(runtime, wasm_api::RT_FREE_BYTES_SYMBOL)?;
709    for (key, pointer_ty) in pointer_types {
710        let function_id = *pointer_drop_functions
711            .get(&key)
712            .expect("pointer drop function id should exist");
713        let Ty::Pointer(pointee_ty) = pointer_ty else {
714            continue;
715        };
716        let local_function = module.funcs.get_mut(function_id).kind.unwrap_local_mut();
717        let pointer_local = local_function.args[0];
718        let entry_block = local_function.entry_block();
719        local_function.block_mut(entry_block).instrs.clear();
720        let scratch_ptr_local = module.locals.add(ValType::I32);
721        let scratch_count_local = module.locals.add(ValType::I32);
722        let scratch_size_local = module.locals.add(ValType::I32);
723        let scratch_value_local = module.locals.add(ValType::I32);
724        let scratch_i64_local = module.locals.add(ValType::I64);
725        let mut body = local_function.builder_mut().instr_seq(entry_block);
726        emit_pointer_release_with_free(
727            &mut body,
728            memory_id,
729            pointer_local,
730            &build_release_plan(pointee_ty.as_ref(), structs, enums)?,
731            &pointer_drop_functions,
732            scratch_ptr_local,
733            scratch_count_local,
734            scratch_size_local,
735            scratch_value_local,
736            scratch_i64_local,
737            free.function_id,
738        );
739    }
740
741    Ok(pointer_drop_functions)
742}
743
744#[cfg(test)]
745mod tests {
746    use super::*;
747
748    #[test]
749    fn detects_borrowed_return_types() {
750        assert!(is_borrowed_return_ty(&Ty::RefRo(Box::new(Ty::I64))));
751        assert!(is_borrowed_return_ty(&Ty::RefMut(Box::new(Ty::I64))));
752    }
753
754    #[test]
755    fn accepts_owned_return_types() {
756        assert!(!is_borrowed_return_ty(&Ty::Slice(Box::new(Ty::I64))));
757        assert!(!is_borrowed_return_ty(&Ty::Array {
758            item: Box::new(Ty::I64),
759            len: 4,
760        }));
761    }
762}