submilli_engine/codegen/function_emitter/
virtual_call.rs1use wasm_encoder::{BlockType, Instruction};
13
14use crate::codegen::CodegenCtx;
15use crate::codegen::function_emitter::FunctionEmitter;
16use crate::codegen::function_emitter::expr::{emit_expr, emit_vtable_dispatch_on_object_stack};
17use crate::codegen::function_emitter::json::emit_inline_const_raw_string;
18use crate::{ExprId, VirtualDispatch};
19
20pub(super) fn emit_virtual_call(
21 emitter: &mut FunctionEmitter,
22 ctx: &CodegenCtx,
23 target: &VirtualDispatch,
24 tool: &str,
25 args: &[ExprId],
26) -> Result<(), crate::compiler_error::CompilerFailure> {
27 if args.len() > 1 {
28 return Err(crate::codegen::internal_failure(
29 "virtual-package calls require a single arguments object",
30 ));
31 }
32 if let VirtualDispatch::Mcp { server } = target {
36 emit_inline_const_string(emitter, ctx, server)?;
37 }
38 emit_inline_const_string(emitter, ctx, tool)?;
39 if let Some(&arg) = args.first() {
40 emit_expr(emitter, ctx, arg)?;
41 let ty = &ctx
42 .ta
43 .try_expr(arg)
44 .map_err(crate::codegen::arena_failure)?
45 .ty;
46 let local = emitter.add_anonymous_local(ctx.symbols.value_type(ty)?)?;
47 emitter.instruction(Instruction::LocalTee(local));
48 super::expr::emit_is_undefined(emitter, ctx)?;
49 emitter.emit_if(BlockType::Result(
50 ctx.symbols.value_type(&crate::Type::String)?,
51 ));
52 emit_inline_const_string(emitter, ctx, "{}")?;
53 emitter.emit_else();
54 emitter.instruction(Instruction::LocalGet(local));
55 emit_vtable_dispatch_on_object_stack(emitter, ctx, 1);
56 emitter.emit_end();
57 } else {
58 emit_inline_const_string(emitter, ctx, "{}")?;
59 }
60
61 let call_idx = ctx
62 .symbols
63 .func_idx(&crate::mangle::host(target.host_module(), "call"))
64 .ok_or_else(|| {
65 crate::codegen::internal_failure("virtual-package call imported during codegen")
66 })?;
67 emitter.instruction(Instruction::Call(call_idx));
68
69 Ok(())
70}
71
72fn emit_inline_const_string(
75 emitter: &mut FunctionEmitter,
76 ctx: &CodegenCtx,
77 text: &str,
78) -> Result<(), crate::compiler_error::CompilerFailure> {
79 let intrinsics = ctx
80 .symbols
81 .intrinsic_type_indices()
82 .ok_or_else(|| crate::codegen::internal_failure("MCP intrinsic types are missing"))?;
83 let string_vtable_idx = ctx
84 .symbols
85 .prelude_global_idx("string_vtable")
86 .ok_or_else(|| crate::codegen::internal_failure("MCP string vtable is missing"))?;
87 emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
88 emit_inline_const_raw_string(emitter, ctx, text)?;
89 emitter.instruction(Instruction::I64Const(0));
90 emitter.instruction(Instruction::StructNew(intrinsics.string));
91 Ok(())
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::TypedAst;
98 use crate::codegen::invariant_tests::{assert_internal, with_context};
99 use crate::codegen::{SymbolTable, tests::mock_symbols_with_intrinsics};
100
101 #[test]
102 fn mcp_import_failures_are_compiler_failures() {
103 with_context(&TypedAst::new(), &mock_symbols_with_intrinsics(), |ctx| {
104 let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
105 let server = VirtualDispatch::Mcp {
106 server: "server".to_string(),
107 };
108 assert_internal(
109 emit_virtual_call(&mut emitter, ctx, &server, "tool", &[]).unwrap_err(),
110 );
111 assert_internal(
112 emit_virtual_call(&mut emitter, ctx, &VirtualDispatch::Tools, "tool", &[])
113 .unwrap_err(),
114 );
115 assert_internal(emit_inline_const_string(&mut emitter, ctx, "server").unwrap_err());
116 });
117 with_context(&TypedAst::new(), &SymbolTable::default(), |ctx| {
118 assert_internal(
119 emit_inline_const_string(
120 &mut FunctionEmitter::new(ctx, &[]).unwrap(),
121 ctx,
122 "server",
123 )
124 .unwrap_err(),
125 );
126 let invalid = crate::ExprId(u32::MAX);
127 assert_internal(
128 emit_virtual_call(
129 &mut FunctionEmitter::new(ctx, &[]).unwrap(),
130 ctx,
131 &VirtualDispatch::Tools,
132 "tool",
133 &[invalid; 2],
134 )
135 .unwrap_err(),
136 );
137 });
138 }
139}