@@ -592,9 +592,12 @@ fn native_inline_leaf_calls(unit: &RegUnit, func: &RegFunction) -> Option<(Vec<R
592592 dst,
593593 function,
594594 args,
595+ mut_args,
595596 } => {
596597 let callee = unit.functions.get(*function)?;
597- if !native_callee_inlinable(callee, args.len()) {
598+ // Calls with `mut` args need a write-back at return; don't inline
599+ // them (native-inlinable callees are side-effect-free anyway).
600+ if !mut_args.is_empty() || !native_callee_inlinable(callee, args.len()) {
598601 return None;
599602 }
600603 let base = next_reg;
@@ -1676,6 +1679,12 @@ enum RegInstr {
16761679 dst: Reg,
16771680 function: usize,
16781681 args: Vec<Reg>,
1682+ /// Argument positions passed with `mut` (the callee's `mut` params). After
1683+ /// the call returns, each such argument's (possibly mutated) value is
1684+ /// written back to the caller's argument register, so a `mut` parameter's
1685+ /// field/element mutations propagate to the caller — matching AOT's
1686+ /// `&mut` semantics.
1687+ mut_args: Vec<usize>,
16791688 },
16801689 /// `spawn f(args)` / `async let`: start `function` as a new concurrent task
16811690 /// and put a Task handle in `dst` (the spawning task keeps running).
@@ -3733,10 +3742,12 @@ impl RegLowerer<'_> {
37333742 // the generics before the lookup — otherwise a generic *function*
37343743 // call falls through and is mis-lowered as a struct construction.
37353744 if let Some(function) = self.function_ids.get(type_root_name(name)).copied() {
3745+ let mut_args = self.user_mut_arg_positions(name);
37363746 self.emit(RegInstr::CallKnown {
37373747 dst,
37383748 function,
37393749 args: arg_regs,
3750+ mut_args,
37403751 });
37413752 } else if self.is_native_function(None, name) {
37423753 let mut_args = self.native_mut_arg_positions(None, name);
@@ -5038,10 +5049,13 @@ impl RegLowerer<'_> {
50385049 return Ok(dst);
50395050 }
50405051 if let Some(function) = self.function_ids.get(&qualified_key).copied() {
5052+ let mut_args =
5053+ self.native_mut_arg_positions(Some(namespace_root), name_root);
50415054 self.emit(RegInstr::CallKnown {
50425055 dst,
50435056 function,
50445057 args: arg_regs,
5058+ mut_args,
50455059 });
50465060 return Ok(dst);
50475061 }
@@ -5106,6 +5120,12 @@ impl RegLowerer<'_> {
51065120 .unwrap_or_default()
51075121 }
51085122
5123+ /// `mut` parameter positions of a user function, so a `CallKnown` can write
5124+ /// the mutated arguments back to the caller (matching AOT's `&mut` params).
5125+ fn user_mut_arg_positions(&self, name: &str) -> Vec<usize> {
5126+ self.native_mut_arg_positions(None, name)
5127+ }
5128+
51095129 fn variant_match(&mut self, value: &HirExpr, arms: &[HirMatchArm]) -> Result<bool, EvalError> {
51105130 if arms.is_empty()
51115131 || !arms
@@ -5842,6 +5862,11 @@ struct Frame {
58425862 /// Absolute register in the caller that receives this frame's return value.
58435863 /// `usize::MAX` marks a driver root (its value is returned out of `run_frame`).
58445864 ret_dst: usize,
5865+ /// `mut`-argument write-backs to perform when this frame completes:
5866+ /// `(caller_abs_reg, this_frame_abs_reg)` pairs. The caller register receives
5867+ /// the parameter's final (possibly mutated) value, so `mut` params propagate.
5868+ /// Empty for the overwhelmingly common no-`mut`-arg call (then a no-op).
5869+ mut_writeback: Vec<(usize, usize)>,
58455870}
58465871
58475872/// Result of driving a task's call stack one slice at a time.
@@ -6448,6 +6473,7 @@ impl RegVm {
64486473 dst,
64496474 function: callee_id,
64506475 args,
6476+ mut_args,
64516477 } = instr
64526478 {
64536479 let callee = Rc::clone(&unit.functions[*callee_id]);
@@ -6458,6 +6484,11 @@ impl RegVm {
64586484 self.set_reg(next_base + index, value);
64596485 }
64606486 let result = self.run_frame(unit, callee, next_base)?;
6487+ // Propagate `mut` parameters back to the caller's argument regs.
6488+ for &pos in mut_args {
6489+ let value = self.reg(next_base + pos).clone();
6490+ self.set_reg(base + args[pos], value);
6491+ }
64616492 self.set_reg(base + *dst, result);
64626493 continue;
64636494 }
@@ -7022,6 +7053,16 @@ impl RegVm {
70227053 self.written[index] = true;
70237054 }
70247055
7056+ /// Propagate a completing frame's `mut` parameters back to the caller: each
7057+ /// `(caller_reg, callee_reg)` copies the parameter's final value out. A no-op
7058+ /// for the common call with no `mut` args (empty `mut_writeback`).
7059+ fn apply_mut_writeback(&mut self, frame: &Frame) {
7060+ for &(caller_reg, callee_reg) in &frame.mut_writeback {
7061+ let value = self.reg(callee_reg).clone();
7062+ self.set_reg(caller_reg, value);
7063+ }
7064+ }
7065+
70257066 #[inline(always)]
70267067 fn take_reg(&mut self, index: usize) -> VmValue {
70277068 assert!(
@@ -7055,6 +7096,7 @@ impl RegVm {
70557096 ip: 0,
70567097 base,
70577098 ret_dst: usize::MAX,
7099+ mut_writeback: Vec::new(),
70587100 });
70597101 match self.drive(unit, floor)? {
70607102 Outcome::Completed(value) => Ok(value),
@@ -7095,6 +7137,7 @@ impl RegVm {
70957137 ip: 0,
70967138 base: 0,
70977139 ret_dst: usize::MAX,
7140+ mut_writeback: Vec::new(),
70987141 }];
70997142 self.tasks.insert(
71007143 tid,
@@ -7415,6 +7458,7 @@ impl RegVm {
74157458 && let Some(value) = self.try_native(&func, base)
74167459 {
74177460 let frame = self.frames.pop().expect("active frame");
7461+ self.apply_mut_writeback(&frame);
74187462 if self.frames.len() == floor {
74197463 return Ok(Outcome::Completed(value));
74207464 }
@@ -7429,6 +7473,7 @@ impl RegVm {
74297473 if self.jit_enabled && ip == 0 && self.is_jit_eligible(&func) {
74307474 let value = self.run_jit(unit, &func, base)?;
74317475 let frame = self.frames.pop().expect("active frame");
7476+ self.apply_mut_writeback(&frame);
74327477 if self.frames.len() == floor {
74337478 return Ok(Outcome::Completed(value));
74347479 }
@@ -7447,6 +7492,7 @@ impl RegVm {
74477492 PureStep::Next => {}
74487493 PureStep::Return(value) => {
74497494 let frame = self.frames.pop().expect("active frame");
7495+ self.apply_mut_writeback(&frame);
74507496 if self.frames.len() == floor {
74517497 return Ok(Outcome::Completed(value));
74527498 }
@@ -7462,13 +7508,21 @@ impl RegVm {
74627508 dst,
74637509 function: callee_id,
74647510 args,
7511+ mut_args,
74657512 } => {
74667513 let callee = Rc::clone(&unit.functions[*callee_id]);
74677514 self.prepare_frame(next_base, callee.regs);
74687515 for (index, reg) in args.iter().enumerate() {
74697516 let value = self.reg(base + *reg).clone();
74707517 self.set_reg(next_base + index, value);
74717518 }
7519+ // `mut` args: when this frame completes, write each
7520+ // parameter's final value back to the caller's register
7521+ // so mutations propagate (caller_abs_reg, callee_abs_reg).
7522+ let mut_writeback = mut_args
7523+ .iter()
7524+ .map(|&pos| (base + args[pos], next_base + pos))
7525+ .collect();
74727526 // Stackless call: save our resume point, push the callee, and
74737527 // re-enter the driver loop instead of recursing on the host
74747528 // stack — so an `await` deep in this chain can later suspend it.
@@ -7478,6 +7532,7 @@ impl RegVm {
74787532 ip: 0,
74797533 base: next_base,
74807534 ret_dst: base + *dst,
7535+ mut_writeback,
74817536 });
74827537 continue 'frames;
74837538 }
@@ -7839,6 +7894,7 @@ impl RegVm {
78397894 // whole stackless driver.
78407895 let err_value = value_err(error);
78417896 let frame = self.frames.pop().expect("active frame");
7897+ self.apply_mut_writeback(&frame);
78427898 if self.frames.len() == floor {
78437899 return Ok(Outcome::Completed(err_value));
78447900 }
@@ -7867,6 +7923,7 @@ impl RegVm {
78677923 // Fell off the end of the function body without an explicit `Return`.
78687924 // Lowering always appends one, so this is a defensive `Unit` return.
78697925 let frame = self.frames.pop().expect("active frame");
7926+ self.apply_mut_writeback(&frame);
78707927 if self.frames.len() == floor {
78717928 return Ok(Outcome::Completed(VmValue::Unit));
78727929 }
0 commit comments