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)
}
Op::Call => {
("Call", String::new())
},
Op::Call => ("Call", String::new()),
Op::MakeAttrs => {
let count = self.read_u32();
@@ -265,17 +263,24 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
}
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
Op::Select => {
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();
Op::SelectStatic => {
let span_id = self.read_u32();
let key_id = self.read_u32();
(
"SelectDefault",
format!("path_len={} span={}", path_len, span_id),
"SelectStatic",
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 => {
+29 -29
View File
@@ -45,8 +45,9 @@ pub enum Op {
MakeAttrs,
MakeEmptyAttrs,
Select,
SelectDefault,
SelectStatic,
SelectDynamic,
JumpIfSelectSucceeded,
HasAttr,
MakeList,
@@ -114,13 +115,6 @@ pub enum OperandType {
BigInt,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum Const {
Smi(i32),
Float(f64),
@@ -130,6 +124,13 @@ pub enum Const {
Null,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum InlineOperand {
Const(Const),
Local { layer: u16, local: u32 },
@@ -626,6 +627,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_with(namespace, body, thunks);
}
&Ir::WithLookup(name) => {
// TODO: specialize shallow with lookups
self.emit_op(Op::PrepareWith);
self.emit_op(Op::LookupWith);
self.emit_str_id(name);
@@ -819,34 +821,32 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
span: TextRange,
) {
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() {
match *attr {
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);
}
Attr::Dynamic(_, _) => {
self.emit_u8(AttrKeyType::Dynamic as u8);
Attr::Dynamic(key_expr, _) => {
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<'_>>]) {
+77 -159
View File
@@ -215,11 +215,13 @@ impl<'gc> Vm<'gc> {
}
#[inline(always)]
#[must_use]
fn pop_stack(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow")
}
#[inline(always)]
#[must_use]
fn peek_stack(&mut self, depth: usize) -> Value<'gc> {
*self
.stack
@@ -264,7 +266,7 @@ struct CallFrame<'gc> {
}
pub(crate) enum Action {
Continue,
Continue { pc: usize },
Done(Result<fix_common::Value>),
}
@@ -308,11 +310,6 @@ struct AttrEntry {
val: OperandData,
}
enum SelectKeyData {
Static(StringId),
Dynamic,
}
macro_rules! try_vm {
($self:ident; $expr:expr) => {
match $expr {
@@ -335,8 +332,9 @@ impl Vm<'_> {
let mut pc = ip.0;
loop {
match arena.mutate_root(|mc, root| root.execute_batch(&mut ctx, &mut pc, mc)) {
Action::Continue => {
match arena.mutate_root(|mc, root| root.execute_batch(&mut ctx, pc, mc)) {
Action::Continue { pc: new_pc } => {
pc = new_pc;
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
if arena.collection_phase() == CollectionPhase::Sweeping {
arena.collect_debt();
@@ -356,7 +354,7 @@ impl<'gc> Vm<'gc> {
fn execute_batch(
&mut self,
ctx: &mut impl VmContext,
pc: &mut usize,
mut pc: usize,
mc: &Mutation<'gc>,
) -> Action {
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>::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,
});
*pc = ip;
pc = ip;
self.env = env;
self.with_env = with_env;
*state = ThunkState::Blackhole;
@@ -454,18 +439,18 @@ impl<'gc> Vm<'gc> {
}
if fuel == 0 {
return Action::Continue;
return Action::Continue { pc };
}
fuel -= 1;
// Save PC for Instruction Retry
let inst_start_pc = *pc;
let byte = ctx.bytecode()[*pc];
let inst_start_pc = pc;
let byte = ctx.bytecode()[pc];
if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) {
panic!("unknown opcode: {byte:#04x}")
}
let op = unsafe { std::mem::transmute::<u8, Op>(byte) };
*pc += 1;
pc += 1;
match op {
PushSmi => {
@@ -606,13 +591,13 @@ impl<'gc> Vm<'gc> {
let new_env =
Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame {
pc: *pc,
pc,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
*pc = ip as usize;
pc = ip as usize;
self.env = new_env;
}
} else {
@@ -656,148 +641,81 @@ impl<'gc> Vm<'gc> {
self.push_stack(self.empty_attrs);
}
Select => {
let n = read!(u16) as usize;
SelectStatic => {
let _span_id = read!(u32);
let key = read!(StringId);
let keys = read!(SelectKeyData, n);
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
try_force!(0, inst_start_pc);
// Force target (at depth `dyn_count`) and all dynamic keys on top of it.
for i in 0..=dyn_count {
try_force!(i, inst_start_pc);
}
let attrs = self.peek_stack(0).restrict().expect("forced");
let Some(attrset) = attrs.as_gc::<AttrSet>() else {
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())
match attrset.lookup(key) {
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;
} else {
panic!("dynamic select key must be a string")
let name = ctx.resolve_string(key);
return Action::Done(Err(Error::eval_error(format!("attribute '{name}' missing"))));
}
}
};
let Some(attrset) = current_val.as_gc::<AttrSet>() else {
error = Some(vm_err("value is not a set while a set was expected"));
break;
};
match attrset.lookup(key_sid) {
Some(v) => {
if i < n - 1 {
// FIXME: Proper async force hook inside select chain for nested thunks
current_val = v.restrict().unwrap_or_else(|_| {
panic!("intermediate select values must be forced")
});
} else {
result_val = Some(v);
}
}
None => {
let name = ctx.resolve_string(key_sid);
error = Some(vm_err(format!("attribute '{name}' missing")));
break;
}
}
}
// 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;
SelectDynamic => {
let _span_id = read!(u32);
let keys = read!(SelectKeyData, n);
let dyn_count = keys
.iter()
.filter(|k| matches!(k, SelectKeyData::Dynamic))
.count();
try_force!(0, inst_start_pc);
try_force!(1, inst_start_pc);
// Stack layout: [..., target, default_val, dyn1, dyn2, ..., dyn_m]
for i in 0..=dyn_count + 1 {
try_force!(i, 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 target_idx = self.stack.len() - dyn_count - 2;
let default_idx = target_idx + 1;
let mut current_dyn_key_idx = default_idx + 1;
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",
));
};
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;
match attrset.lookup(key_sid) {
Some(v) => {
self.stack.truncate(self.stack.len() - 2);
self.push_stack(v);
}
None => {
let name = ctx.resolve_string(key_sid);
return self
.handle_vm_error(vm_err(format!("attribute '{name}' missing")));
}
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);
}
}
JumpIfSelectSucceeded => {
let offset = read!(i32);
pc = ((pc as isize) + (offset as isize)) as usize;
}
HasAttr => {
let _n = read!(u16) as usize;
todo!("HasAttr");
@@ -946,7 +864,7 @@ impl<'gc> Vm<'gc> {
try_force!(0, inst_start_pc);
let cond = self.pop_stack();
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 => {
@@ -954,12 +872,12 @@ impl<'gc> Vm<'gc> {
try_force!(0, inst_start_pc);
let cond = self.pop_stack();
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 => {
let offset = read!(i32);
*pc = ((*pc as isize) + (offset as isize)) as usize;
pc = ((pc as isize) + (offset as isize)) as usize;
}
ConcatStrings => {
@@ -1027,7 +945,7 @@ impl<'gc> Vm<'gc> {
let env = self.pop_stack().as_gc::<AttrSet>().unwrap();
let Some(val) = env.lookup(name) else {
*pc = inst_start_pc;
pc = inst_start_pc;
self.with_env = prev;
continue 'dispatch;
};
@@ -1066,7 +984,7 @@ impl<'gc> Vm<'gc> {
}
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);
}
}