use fix_common::StringId; use fix_error::Error; use gc_arena::{Gc, RefLock}; use smallvec::SmallVec; use crate::value::NixType; use crate::{ AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, }; impl<'gc> crate::Vm<'gc> { #[inline(always)] pub(crate) fn op_make_attrs( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let count = reader.read_u32() as usize; let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); for _ in 0..count { let key = reader.read_attr_key_data(ctx); let val = reader.read_operand_data(ctx); let _span_id = reader.read_u32(); entries.push(AttrEntry { key, val }); } let mut kv: SmallVec<[(crate::StringId, Value); 4]> = SmallVec::with_capacity(count); for entry in &entries { let key_sid = match &entry.key { AttrKeyData::Static(sid) => *sid, AttrKeyData::Dynamic(op) => { let v = op.resolve(mc, self); v.as_inline::() .expect("dynamic attr key must be a string") } }; let val = entry.val.resolve(mc, self); kv.push((key_sid, val)); } kv.sort_by_key(|(k, _)| *k); let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); self.push(Value::new_gc(attrs)); Step::Continue(()) } #[inline(always)] pub(crate) fn op_make_empty_attrs(&mut self) -> Step { self.push(self.empty_attrs); Step::Continue(()) } #[inline(always)] pub(crate) fn op_select_static( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let _span_id = reader.read_u32(); let key = reader.read_string_id(); let attrset = self.try_force::>(reader, mc)?; match attrset.lookup(key) { Some(v) => { self.push(v); } None => return self.select_skip(key, ctx, reader), } Step::Continue(()) } #[inline(always)] pub(crate) fn op_select_dynamic( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let _span_id = reader.read_u32(); let (attrset, key_val) = self.try_force::<(Gc, StrictValue)>(reader, mc)?; let key_sid = match ctx.get_string_id(key_val) { Ok(id) => id, Err(got) => return self.finish_type_err(NixType::String, got), }; match attrset.lookup(key_sid) { Some(v) => { self.push(v); } None => return self.select_skip(key_sid, ctx, reader), } Step::Continue(()) } /// Skip the rest of a **Select** attrpath after a missing attribute. /// Only recognises Select opcodes and jumps; encountering any other /// opcode means we've reached the end of the select sequence and /// should report the missing-attribute error. fn select_skip( &mut self, key: StringId, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, ) -> Step { use fix_codegen::Op::*; loop { match reader.read_op() { SelectStatic => { reader.set_pc(reader.pc() + 4 + 4); } SelectDynamic => { reader.set_pc(reader.pc() + 4); } JumpIfSelectSucceeded => { reader.set_pc(reader.pc() + 4); break Step::Continue(()); } JumpIfSelectFailed => { let offset = reader.read_i32(); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); } _ => { let name = ctx.resolve_string(key); return self .finish_err(Error::eval_error(format!("attribute '{name}' missing"))); } } } } /// Skip the rest of a **HasAttr** attrpath after an intermediate /// lookup failed. Only recognises HasAttr opcodes and jumps. fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step { use fix_codegen::Op::*; loop { match reader.read_op() { HasAttrPathStatic => { reader.set_pc(reader.pc() + 4 + 4); } HasAttrPathDynamic => { reader.set_pc(reader.pc() + 4); } HasAttrStatic => { reader.set_pc(reader.pc() + 4); break Step::Continue(()); } JumpIfSelectFailed => { let offset = reader.read_i32(); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); } HasAttrDynamic => { break Step::Continue(()); } HasAttrResolve => { reader.set_pc(reader.pc() - 1); break Step::Continue(()); } other => { unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8) } } } } #[inline(always)] pub(crate) fn op_has_attr_path_static( &mut self, _ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let _span_id = reader.read_u32(); let key = reader.read_string_id(); let current = self.try_force::(reader, mc)?; match current .as_gc::() .and_then(|attrs| attrs.lookup(key)) { Some(v) => { self.push(v); } None => return self.has_attr_skip(reader), } Step::Continue(()) } #[inline(always)] pub(crate) fn op_has_attr_path_dynamic( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let _span_id = reader.read_u32(); let (current, key_val) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let key_sid = match ctx.get_string_id(key_val) { Ok(id) => id, Err(got) => return self.finish_type_err(NixType::String, got), }; match current .as_gc::() .and_then(|attrs| attrs.lookup(key_sid)) { Some(v) => { self.push(v); } None => return self.has_attr_skip(reader), } Step::Continue(()) } #[inline(always)] pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step { // No-op let _offset = reader.read_i32(); Step::Continue(()) } #[inline(always)] pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step { let offset = reader.read_i32(); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); Step::Continue(()) } #[inline(always)] pub(crate) fn op_has_attr_static( &mut self, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let key = reader.read_string_id(); let current = self.try_force::(reader, mc)?; self.push(Value::new_inline( current .as_gc::() .and_then(|attrs| attrs.lookup(key)) .is_some(), )); // Skip HasAttrResolve reader.set_pc(reader.pc() + 1); Step::Continue(()) } #[inline(always)] pub(crate) fn op_has_attr_dynamic( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let (current, dyn_key) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let key_sid = match ctx.get_string_id(dyn_key) { Ok(id) => id, Err(got) => return self.finish_type_err(NixType::String, got), }; self.push(Value::new_inline( current .as_gc::() .and_then(|attrs| attrs.lookup(key_sid)) .is_some(), )); // Skip HasAttrResolve reader.set_pc(reader.pc() + 1); Step::Continue(()) } #[inline(always)] pub(crate) fn op_has_attr_resolve(&mut self) -> Step { // If we reach here, has_attr check has failed, push false (AttrSet is already popped) self.push(Value::new_inline(false)); Step::Continue(()) } #[inline(always)] pub(crate) fn op_make_list( &mut self, ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &gc_arena::Mutation<'gc>, ) -> Step { let count = reader.read_u32() as usize; let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); for _ in 0..count { items.push(reader.read_operand_data(ctx).resolve(mc, self)); } let list = Gc::new( mc, List { inner: RefLock::new(items), }, ); self.push(Value::new_gc(list)); Step::Continue(()) } #[inline(always)] pub(crate) fn op_make_empty_list(&mut self) -> Step { self.push(self.empty_list); Step::Continue(()) } } pub(crate) struct AttrEntry { pub(crate) key: AttrKeyData, pub(crate) val: OperandData, }