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