@@ -1708,6 +1708,17 @@ enum RegInstr {
17081708 /// `&mut` semantics.
17091709 mut_args: Vec<usize>,
17101710 },
1711+ /// Dynamic protocol dispatch: a `Protocol.method(self: x, ...)` call whose
1712+ /// concrete impl is chosen at runtime by `args[0]`'s struct type name. This is
1713+ /// how capability objects and generic protocol bounds dispatch in the VM,
1714+ /// mirroring the compiled backend's closed-world enum dispatch. `dispatch`
1715+ /// maps each implementing struct name to the impl's target function id.
1716+ CallDynamic {
1717+ dst: Reg,
1718+ dispatch: Vec<(String, usize)>,
1719+ args: Vec<Reg>,
1720+ mut_args: Vec<usize>,
1721+ },
17111722 /// `spawn f(args)` / `async let`: start `function` as a new concurrent task
17121723 /// and put a Task handle in `dst` (the spawning task keeps running).
17131724 SpawnTask {
@@ -5125,6 +5136,36 @@ impl RegLowerer<'_> {
51255136 });
51265137 return Ok(dst);
51275138 }
5139+ // Dynamic protocol dispatch: `Protocol.method(self: x, ...)`
5140+ // where `Protocol` is a protocol with impls. The concrete
5141+ // function is selected at runtime by `args[0]`'s struct type
5142+ // (capability objects + generic bounds) — the VM equivalent
5143+ // of the compiled backend's closed-world enum dispatch.
5144+ // Checked before the `function_ids` lookup because a protocol
5145+ // method also appears there as a bodyless stub (which would
5146+ // wrongly return `Unit`).
5147+ let dispatch: Vec<(String, usize)> = self
5148+ .hir
5149+ .protocol_method_targets(namespace_root, name_root)
5150+ .into_iter()
5151+ .filter_map(|(type_name, target)| {
5152+ self.function_ids
5153+ .get(type_root_name(&target))
5154+ .copied()
5155+ .map(|function| (type_name, function))
5156+ })
5157+ .collect();
5158+ if !dispatch.is_empty() {
5159+ let mut_args =
5160+ self.native_mut_arg_positions(Some(namespace_root), name_root);
5161+ self.emit(RegInstr::CallDynamic {
5162+ dst,
5163+ dispatch,
5164+ args: arg_regs,
5165+ mut_args,
5166+ });
5167+ return Ok(dst);
5168+ }
51285169 if let Some(function) = self.function_ids.get(&qualified_key).copied() {
51295170 let mut_args =
51305171 self.native_mut_arg_positions(Some(namespace_root), name_root);
@@ -5363,9 +5404,7 @@ impl RegLowerer<'_> {
53635404 .is_none_or(|pattern| self.is_supported_match_pattern(pattern))
53645405 })
53655406 }
5366- MatchPattern::List {
5367- prefix, suffix, ..
5368- } => prefix
5407+ MatchPattern::List { prefix, suffix, .. } => prefix
53695408 .iter()
53705409 .chain(suffix)
53715410 .all(|pattern| self.is_supported_match_pattern(pattern)),
@@ -5422,7 +5461,10 @@ impl RegLowerer<'_> {
54225461 let mut failures = Vec::new();
54235462 let required = (prefix.len() + suffix.len()) as i64;
54245463 let len = self.temp();
5425- self.emit(RegInstr::ListLen { dst: len, list: src });
5464+ self.emit(RegInstr::ListLen {
5465+ dst: len,
5466+ list: src,
5467+ });
54265468 let bound = self.temp();
54275469 self.emit(RegInstr::LoadInt {
54285470 dst: bound,
@@ -7814,6 +7856,51 @@ impl RegVm {
78147856 });
78157857 continue 'frames;
78167858 }
7859+ RegInstr::CallDynamic {
7860+ dst,
7861+ dispatch,
7862+ args,
7863+ mut_args,
7864+ } => {
7865+ // Select the concrete impl by the runtime struct type of
7866+ // the receiver (args[0]), then call it like `CallKnown`.
7867+ let receiver = self.reg(base + args[0]).clone();
7868+ let type_name = match &receiver {
7869+ VmValue::Struct(data) => Some(data.name.clone()),
7870+ _ => None,
7871+ };
7872+ let callee_id = type_name.as_ref().and_then(|name| {
7873+ dispatch
7874+ .iter()
7875+ .find(|(struct_name, _)| struct_name.as_str() == &**name)
7876+ .map(|(_, id)| *id)
7877+ });
7878+ let Some(callee_id) = callee_id else {
7879+ return Err(EvalError::Runtime(format!(
7880+ "reg VM dynamic protocol dispatch found no impl for receiver `{}`.",
7881+ type_name.as_deref().unwrap_or("<non-struct value>")
7882+ )));
7883+ };
7884+ let callee = Rc::clone(&unit.functions[callee_id]);
7885+ self.prepare_frame(next_base, callee.regs);
7886+ for (index, reg) in args.iter().enumerate() {
7887+ let value = self.reg(base + *reg).clone();
7888+ self.set_reg(next_base + index, value);
7889+ }
7890+ let mut_writeback = mut_args
7891+ .iter()
7892+ .map(|&pos| (base + args[pos], next_base + pos))
7893+ .collect();
7894+ self.frames.last_mut().expect("active frame").ip = ip;
7895+ self.frames.push(Frame {
7896+ func: callee,
7897+ ip: 0,
7898+ base: next_base,
7899+ ret_dst: base + *dst,
7900+ mut_writeback,
7901+ });
7902+ continue 'frames;
7903+ }
78177904 RegInstr::SpawnTask {
78187905 dst,
78197906 function: callee_id,
0 commit comments