split SelectDefault -> SelectStatic & Jump...

This commit is contained in:
2026-04-19 13:49:58 +08:00
parent 74866ec1d3
commit f66752afa5
3 changed files with 123 additions and 200 deletions
+17 -12
View File
@@ -255,9 +255,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
("MakePatternClosure", arg_str) ("MakePatternClosure", arg_str)
} }
Op::Call => { Op::Call => ("Call", String::new()),
("Call", String::new())
},
Op::MakeAttrs => { Op::MakeAttrs => {
let count = self.read_u32(); let count = self.read_u32();
@@ -265,17 +263,24 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
} }
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()), Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
Op::Select => { Op::SelectStatic => {
let path_len = self.read_u16();
let span_id = self.read_u32();
("Select", format!("path_len={} span={}", path_len, span_id))
}
Op::SelectDefault => {
let path_len = self.read_u16();
let span_id = self.read_u32(); let span_id = self.read_u32();
let key_id = self.read_u32();
( (
"SelectDefault", "SelectStatic",
format!("path_len={} span={}", path_len, span_id), format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
)
}
Op::SelectDynamic => {
let span_id = self.read_u32();
("SelectDynamic", format!("span={}", span_id))
}
Op::JumpIfSelectSucceeded => {
let offset = self.read_i32();
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
(
"JumpIfSelectSucceeded",
format!("-> {:04x} offset={}", target, offset),
) )
} }
Op::HasAttr => { Op::HasAttr => {
+29 -29
View File
@@ -45,8 +45,9 @@ pub enum Op {
MakeAttrs, MakeAttrs,
MakeEmptyAttrs, MakeEmptyAttrs,
Select, SelectStatic,
SelectDefault, SelectDynamic,
JumpIfSelectSucceeded,
HasAttr, HasAttr,
MakeList, MakeList,
@@ -114,13 +115,6 @@ pub enum OperandType {
BigInt, BigInt,
} }
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum Const { pub enum Const {
Smi(i32), Smi(i32),
Float(f64), Float(f64),
@@ -130,6 +124,13 @@ pub enum Const {
Null, Null,
} }
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum InlineOperand { pub enum InlineOperand {
Const(Const), Const(Const),
Local { layer: u16, local: u32 }, Local { layer: u16, local: u32 },
@@ -626,6 +627,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_with(namespace, body, thunks); self.emit_with(namespace, body, thunks);
} }
&Ir::WithLookup(name) => { &Ir::WithLookup(name) => {
// TODO: specialize shallow with lookups
self.emit_op(Op::PrepareWith); self.emit_op(Op::PrepareWith);
self.emit_op(Op::LookupWith); self.emit_op(Op::LookupWith);
self.emit_str_id(name); self.emit_str_id(name);
@@ -819,34 +821,32 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
span: TextRange, span: TextRange,
) { ) {
self.emit_expr(expr); self.emit_expr(expr);
for attr in attrpath.iter() {
if let Attr::Dynamic(expr, _) = *attr {
self.emit_expr(expr);
}
}
if let Some(default) = default {
self.emit_expr(default);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::SelectDefault);
self.emit_u16(attrpath.len() as u16);
self.emit_u32(span_id);
} else {
let span_id = self.ctx.register_span(span);
self.emit_op(Op::Select);
self.emit_u16(attrpath.len() as u16);
self.emit_u32(span_id);
}
for attr in attrpath.iter() { for attr in attrpath.iter() {
match *attr { match *attr {
Attr::Str(sym, _) => { Attr::Str(sym, _) => {
self.emit_u8(AttrKeyType::Static as u8); let span_id = self.ctx.register_span(span);
self.emit_op(Op::SelectStatic);
self.emit_u32(span_id);
self.emit_str_id(sym); self.emit_str_id(sym);
} }
Attr::Dynamic(_, _) => { Attr::Dynamic(key_expr, _) => {
self.emit_u8(AttrKeyType::Dynamic as u8); self.emit_expr(key_expr);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::SelectDynamic);
self.emit_u32(span_id);
} }
} }
} }
if let Some(default) = default {
self.emit_op(Op::JumpIfSelectSucceeded);
let placeholder = self.emit_i32_placeholder();
let before: i32 = self.ctx.get_code().len().try_into().unwrap();
self.emit_expr(default);
let after: i32 = self.ctx.get_code().len().try_into().unwrap();
self.patch_i32(placeholder, after - before);
}
} }
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) { fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
+78 -160
View File
@@ -215,11 +215,13 @@ impl<'gc> Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn pop_stack(&mut self) -> Value<'gc> { fn pop_stack(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow") self.stack.pop().expect("stack underflow")
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn peek_stack(&mut self, depth: usize) -> Value<'gc> { fn peek_stack(&mut self, depth: usize) -> Value<'gc> {
*self *self
.stack .stack
@@ -264,7 +266,7 @@ struct CallFrame<'gc> {
} }
pub(crate) enum Action { pub(crate) enum Action {
Continue, Continue { pc: usize },
Done(Result<fix_common::Value>), Done(Result<fix_common::Value>),
} }
@@ -308,11 +310,6 @@ struct AttrEntry {
val: OperandData, val: OperandData,
} }
enum SelectKeyData {
Static(StringId),
Dynamic,
}
macro_rules! try_vm { macro_rules! try_vm {
($self:ident; $expr:expr) => { ($self:ident; $expr:expr) => {
match $expr { match $expr {
@@ -335,8 +332,9 @@ impl Vm<'_> {
let mut pc = ip.0; let mut pc = ip.0;
loop { loop {
match arena.mutate_root(|mc, root| root.execute_batch(&mut ctx, &mut pc, mc)) { match arena.mutate_root(|mc, root| root.execute_batch(&mut ctx, pc, mc)) {
Action::Continue => { Action::Continue { pc: new_pc } => {
pc = new_pc;
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY { if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
if arena.collection_phase() == CollectionPhase::Sweeping { if arena.collection_phase() == CollectionPhase::Sweeping {
arena.collect_debt(); arena.collect_debt();
@@ -356,7 +354,7 @@ impl<'gc> Vm<'gc> {
fn execute_batch( fn execute_batch(
&mut self, &mut self,
ctx: &mut impl VmContext, ctx: &mut impl VmContext,
pc: &mut usize, mut pc: usize,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Action { ) -> Action {
use fix_codegen::Op::{self, *}; use fix_codegen::Op::{self, *};
@@ -397,21 +395,8 @@ impl<'gc> Vm<'gc> {
} }
} }
}}; }};
(SelectKeyData, $n:expr) => {{
let mut keys = SmallVec::<[SelectKeyData; 4]>::with_capacity($n as usize);
for _ in 0..$n {
let tag = read!(u8);
let Ok(ty) = AttrKeyType::try_from_primitive(tag)
.map_err(|err| panic!("unknown key tag: {:#04x}", err.number));
match ty {
AttrKeyType::Static => keys.push(SelectKeyData::Static(read!(StringId))),
AttrKeyType::Dynamic => keys.push(SelectKeyData::Dynamic),
};
}
keys
}};
($type:ty) => { ($type:ty) => {
<$type>::from_le_bytes(read_array(ctx.bytecode(), pc)) <$type>::from_le_bytes(read_array(ctx.bytecode(), &mut pc))
}; };
} }
@@ -433,7 +418,7 @@ impl<'gc> Vm<'gc> {
with_env: self.with_env, with_env: self.with_env,
}); });
*pc = ip; pc = ip;
self.env = env; self.env = env;
self.with_env = with_env; self.with_env = with_env;
*state = ThunkState::Blackhole; *state = ThunkState::Blackhole;
@@ -454,18 +439,18 @@ impl<'gc> Vm<'gc> {
} }
if fuel == 0 { if fuel == 0 {
return Action::Continue; return Action::Continue { pc };
} }
fuel -= 1; fuel -= 1;
// Save PC for Instruction Retry // Save PC for Instruction Retry
let inst_start_pc = *pc; let inst_start_pc = pc;
let byte = ctx.bytecode()[*pc]; let byte = ctx.bytecode()[pc];
if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) { if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) {
panic!("unknown opcode: {byte:#04x}") panic!("unknown opcode: {byte:#04x}")
} }
let op = unsafe { std::mem::transmute::<u8, Op>(byte) }; let op = unsafe { std::mem::transmute::<u8, Op>(byte) };
*pc += 1; pc += 1;
match op { match op {
PushSmi => { PushSmi => {
@@ -606,13 +591,13 @@ impl<'gc> Vm<'gc> {
let new_env = let new_env =
Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame { self.call_stack.push(CallFrame {
pc: *pc, pc,
stack_depth: 0, stack_depth: 0,
thunk: None, thunk: None,
env: self.env, env: self.env,
with_env: self.with_env, with_env: self.with_env,
}); });
*pc = ip as usize; pc = ip as usize;
self.env = new_env; self.env = new_env;
} }
} else { } else {
@@ -656,148 +641,81 @@ impl<'gc> Vm<'gc> {
self.push_stack(self.empty_attrs); self.push_stack(self.empty_attrs);
} }
Select => { SelectStatic => {
let n = read!(u16) as usize;
let _span_id = read!(u32); let _span_id = read!(u32);
let key = read!(StringId);
let keys = read!(SelectKeyData, n); try_force!(0, inst_start_pc);
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
// Force target (at depth `dyn_count`) and all dynamic keys on top of it. let attrs = self.peek_stack(0).restrict().expect("forced");
for i in 0..=dyn_count { let Some(attrset) = attrs.as_gc::<AttrSet>() else {
try_force!(i, inst_start_pc); return Action::Done(Err(Error::eval_error("value is not a set while a set was expected")));
}
// Stack Layout: [..., target, dyn1, dyn2, ..., dyn_m]
let target_idx = self.stack.len() - dyn_count - 1;
let mut current_dyn_key_idx = target_idx + 1;
let mut current_val = self.stack[target_idx].restrict().expect("forced");
let mut result_val = None;
let mut error = None;
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
SelectKeyData::Static(sid) => *sid,
SelectKeyData::Dynamic => {
let v = self.stack[current_dyn_key_idx]
.restrict()
.expect("dynamic key must be forced");
current_dyn_key_idx += 1;
if let Some(sid) = v.as_inline::<StringId>() {
sid
} else if let Some(ns) = v.as_gc::<NixString>() {
ctx.intern_string(ns.as_str())
} else {
panic!("dynamic select key must be a string")
}
}
}; };
let Some(attrset) = current_val.as_gc::<AttrSet>() else { match attrset.lookup(key) {
error = Some(vm_err("value is not a set while a set was expected")); Some(v) => {
self.replace_stack(0, v);
}
None => {
loop {
let byte = ctx.bytecode()[pc];
if byte == SelectStatic as u8 {
pc += 1 + 4 + 4;
} else if byte == SelectDynamic as u8 {
pc += 1 + 4;
} else if byte == JumpIfSelectSucceeded as u8 {
pc += 1 + 4;
let _ = self.pop_stack();
break; break;
} else {
let name = ctx.resolve_string(key);
return Action::Done(Err(Error::eval_error(format!("attribute '{name}' missing"))));
}
}
}
}
}
SelectDynamic => {
let _span_id = read!(u32);
try_force!(0, inst_start_pc);
try_force!(1, inst_start_pc);
let key_val = self.stack[self.stack.len() - 1]
.restrict()
.expect("dynamic key must be forced");
let key_sid = if let Some(sid) = key_val.as_inline::<StringId>() {
sid
} else if let Some(ns) = key_val.as_gc::<NixString>() {
ctx.intern_string(ns.as_str())
} else {
return self.handle_vm_error(vm_err("dynamic select key must be a string"));
};
let attrset_val = self.stack[self.stack.len() - 2].restrict().expect("forced");
let Some(attrset) = attrset_val.as_gc::<AttrSet>() else {
return self.handle_vm_error(vm_err(
"value is not a set while a set was expected",
));
}; };
match attrset.lookup(key_sid) { match attrset.lookup(key_sid) {
Some(v) => { Some(v) => {
if i < n - 1 { self.stack.truncate(self.stack.len() - 2);
// FIXME: Proper async force hook inside select chain for nested thunks self.push_stack(v);
current_val = v.restrict().unwrap_or_else(|_| {
panic!("intermediate select values must be forced")
});
} else {
result_val = Some(v);
}
} }
None => { None => {
let name = ctx.resolve_string(key_sid); let name = ctx.resolve_string(key_sid);
error = Some(vm_err(format!("attribute '{name}' missing"))); return self
break; .handle_vm_error(vm_err(format!("attribute '{name}' missing")));
} }
} }
} }
JumpIfSelectSucceeded => {
let offset = read!(i32);
pc = ((pc as isize) + (offset as isize)) as usize;
}
// Clean up the target and all dynamic keys
self.stack.truncate(target_idx);
if let Some(e) = error {
return self.handle_vm_error(e);
}
if let Some(v) = result_val {
self.push_stack(v);
}
}
SelectDefault => {
let n = read!(u16) as usize;
let _span_id = read!(u32);
let keys = read!(SelectKeyData, n);
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
// Stack layout: [..., target, default_val, dyn1, dyn2, ..., dyn_m]
for i in 0..=dyn_count + 1 {
try_force!(i, inst_start_pc);
}
let target_idx = self.stack.len() - dyn_count - 2;
let default_idx = target_idx + 1;
let mut current_dyn_key_idx = default_idx + 1;
let mut current_val = self.stack[target_idx].restrict().expect("forced");
let mut result_val = None;
let mut use_default = false;
for (i, key) in keys.iter().enumerate() {
let key_sid = match key {
SelectKeyData::Static(sid) => *sid,
SelectKeyData::Dynamic => {
let v = self.stack[current_dyn_key_idx]
.restrict()
.expect("dynamic key must be forced");
current_dyn_key_idx += 1;
if let Some(sid) = v.as_inline::<StringId>() {
sid
} else if let Some(ns) = v.as_gc::<NixString>() {
ctx.intern_string(ns.as_str())
} else {
panic!("dynamic select key must be a string")
}
}
};
if let Some(attrset) = current_val.as_gc::<AttrSet>()
&& let Some(v) = attrset.lookup(key_sid)
{
if i < n - 1 {
current_val = v.restrict().unwrap_or_else(|_| {
panic!("intermediate select values must be forced")
});
} else {
result_val = Some(v);
}
continue;
}
use_default = true;
break;
}
let def = self.stack[default_idx];
self.stack.truncate(target_idx);
if use_default {
self.push_stack(def);
} else if let Some(v) = result_val {
self.push_stack(v);
}
}
HasAttr => { HasAttr => {
let _n = read!(u16) as usize; let _n = read!(u16) as usize;
todo!("HasAttr"); todo!("HasAttr");
@@ -946,7 +864,7 @@ impl<'gc> Vm<'gc> {
try_force!(0, inst_start_pc); try_force!(0, inst_start_pc);
let cond = self.pop_stack(); let cond = self.pop_stack();
if cond.as_inline::<bool>() == Some(false) { if cond.as_inline::<bool>() == Some(false) {
*pc = ((*pc as isize) + (offset as isize)) as usize; pc = ((pc as isize) + (offset as isize)) as usize;
} }
} }
JumpIfTrue => { JumpIfTrue => {
@@ -954,12 +872,12 @@ impl<'gc> Vm<'gc> {
try_force!(0, inst_start_pc); try_force!(0, inst_start_pc);
let cond = self.pop_stack(); let cond = self.pop_stack();
if cond.as_inline::<bool>() == Some(true) { if cond.as_inline::<bool>() == Some(true) {
*pc = ((*pc as isize) + (offset as isize)) as usize; pc = ((pc as isize) + (offset as isize)) as usize;
} }
} }
Jump => { Jump => {
let offset = read!(i32); let offset = read!(i32);
*pc = ((*pc as isize) + (offset as isize)) as usize; pc = ((pc as isize) + (offset as isize)) as usize;
} }
ConcatStrings => { ConcatStrings => {
@@ -1027,7 +945,7 @@ impl<'gc> Vm<'gc> {
let env = self.pop_stack().as_gc::<AttrSet>().unwrap(); let env = self.pop_stack().as_gc::<AttrSet>().unwrap();
let Some(val) = env.lookup(name) else { let Some(val) = env.lookup(name) else {
*pc = inst_start_pc; pc = inst_start_pc;
self.with_env = prev; self.with_env = prev;
continue 'dispatch; continue 'dispatch;
}; };
@@ -1066,7 +984,7 @@ impl<'gc> Vm<'gc> {
} }
Return => { Return => {
if let Some(result) = self.handle_return(pc, ctx, mc) { if let Some(result) = self.handle_return(&mut pc, ctx, mc) {
return Action::Done(result); return Action::Done(result);
} }
} }