use fix_builtins::{BUILTINS, BuiltinId}; use fix_common::StringId; use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind}; use hashbrown::HashMap; use num_enum::TryFromPrimitive; use rnix::TextRange; use string_interner::Symbol as _; pub mod disassembler; pub struct InstructionPtr(pub usize); pub trait BytecodeContext { fn intern_string(&mut self, s: &str) -> StringId; fn register_span(&mut self, range: TextRange) -> u32; fn get_code(&self) -> &[u8]; fn get_code_mut(&mut self) -> &mut Vec; fn add_constant(&mut self, val: Const) -> u32; } #[repr(u8)] #[derive(Debug, Clone, Copy, TryFromPrimitive)] #[allow(clippy::enum_variant_names)] pub enum Op { PushSmi, PushBigInt, PushFloat, PushString, PushNull, PushTrue, PushFalse, LoadLocal, LoadOuter, StoreLocal, AllocLocals, MakeThunk, MakeClosure, MakePatternClosure, Call, DispatchPrimOp, MakeAttrs, MakeEmptyAttrs, SelectStatic, SelectDynamic, HasAttrPathStatic, HasAttrPathDynamic, HasAttrStatic, HasAttrDynamic, HasAttrResolve, JumpIfSelectSucceeded, JumpIfSelectFailed, MakeList, MakeEmptyList, OpAdd, OpSub, OpMul, OpDiv, OpEq, OpNeq, OpLt, OpGt, OpLeq, OpGeq, OpConcat, OpUpdate, OpNeg, OpNot, JumpIfFalse, JumpIfTrue, Jump, ConcatStrings, ResolvePath, Assert, PushWith, PopWith, LookupWith, PrepareWith, LoadBuiltins, LoadBuiltin, MkPos, LoadReplBinding, LoadScopedBinding, Return, Illegal, } struct ScopeInfo { depth: u8, thunk_map: HashMap, } struct BytecodeEmitter<'a, Ctx: BytecodeContext> { ctx: &'a mut Ctx, scope_stack: Vec, } #[repr(u8)] #[derive(Debug, Clone, Copy, TryFromPrimitive)] pub enum OperandType { Const, BigInt, Local, BuiltinConst, Builtins, ReplBinding, ScopedImportBinding, WithLookup, } pub enum Const { Smi(i32), Float(f64), Bool(bool), String(StringId), Path(StringId), PrimOp { id: BuiltinId, arity: u8, dispatch_ip: u32, }, Null, } #[repr(u8)] #[derive(Debug, Clone, Copy, TryFromPrimitive)] pub enum AttrKeyType { Static, Dynamic, } pub enum InlineOperand { Const(Const), BigInt(i64), Local { layer: u8, local: u32 }, BuiltinConst(StringId), Builtins, ReplBinding(StringId), ScopedImportBinding(StringId), WithLookup(StringId), } pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr { let ip = ctx.get_code().len(); let mut emitter = BytecodeEmitter::new(ctx); emitter.emit_toplevel(ir); InstructionPtr(ip) } impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { fn new(ctx: &'a mut Ctx) -> Self { Self { ctx, scope_stack: Vec::with_capacity(32), } } #[must_use] fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand { use MaybeThunk::*; match *val { Int(x) => { if let Ok(x) = x.try_into() { InlineOperand::Const(Const::Smi(x)) } else { InlineOperand::BigInt(x) } } Float(x) => InlineOperand::Const(Const::Float(x)), Bool(b) => InlineOperand::Const(Const::Bool(b)), Null => InlineOperand::Const(Const::Null), Str(id) => InlineOperand::Const(Const::String(id)), Path(id) => InlineOperand::Const(Const::String(id)), Thunk(id) => { let (layer, local) = self.resolve_thunk(id); InlineOperand::Local { layer, local } } Arg { layer } => InlineOperand::Local { layer, local: 0 }, Builtin(id) => { let (_, arity) = BUILTINS[id as usize]; InlineOperand::Const(Const::PrimOp { id, arity, dispatch_ip: id.entry_phase().ip(), }) } BuiltinConst(id) => InlineOperand::BuiltinConst(id), Builtins => InlineOperand::Builtins, ReplBinding(id) => InlineOperand::ReplBinding(id), ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id), WithLookup(id) => InlineOperand::WithLookup(id), } } fn emit_maybe_thunk(&mut self, val: &MaybeThunk) { use InlineOperand::*; let operand = self.inline_maybe_thunk(val); match operand { Const(val) => { let idx = self.ctx.add_constant(val); self.emit_u8(OperandType::Const as u8); self.emit_u32(idx); } BigInt(val) => { self.emit_u8(OperandType::BigInt as u8); self.emit_i64(val); } Local { layer, local } => { self.emit_u8(OperandType::Local as u8); self.emit_u8(layer); self.emit_u32(local); } BuiltinConst(id) => { self.emit_u8(OperandType::BuiltinConst as u8); self.emit_str_id(id); } Builtins => { self.emit_u8(OperandType::Builtins as u8); } ReplBinding(id) => { self.emit_u8(OperandType::ReplBinding as u8); self.emit_str_id(id); } ScopedImportBinding(id) => { self.emit_u8(OperandType::ScopedImportBinding as u8); self.emit_str_id(id); } WithLookup(id) => { self.emit_u8(OperandType::WithLookup as u8); self.emit_str_id(id); } } } #[inline] fn emit_op(&mut self, op: Op) { self.ctx.get_code_mut().push(op as u8); } #[inline] fn emit_u8(&mut self, val: u8) { self.ctx.get_code_mut().push(val); } #[inline] fn emit_u16(&mut self, val: u16) { self.ctx .get_code_mut() .extend_from_slice(&val.to_le_bytes()); } #[inline] fn emit_u32(&mut self, val: u32) { self.ctx .get_code_mut() .extend_from_slice(&val.to_le_bytes()); } #[inline] fn emit_i32(&mut self, val: i32) { self.ctx .get_code_mut() .extend_from_slice(&val.to_le_bytes()); } #[inline] fn emit_i64(&mut self, val: i64) { self.ctx .get_code_mut() .extend_from_slice(&val.to_le_bytes()); } #[inline] fn emit_f64(&mut self, val: f64) { self.ctx .get_code_mut() .extend_from_slice(&val.to_le_bytes()); } #[inline] fn emit_i32_placeholder(&mut self) -> usize { let offset = self.ctx.get_code_mut().len(); self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]); offset } #[inline] fn patch_i32(&mut self, offset: usize, val: i32) { self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes()); } #[inline] fn emit_jump_placeholder(&mut self) -> usize { self.emit_op(Op::Jump); self.emit_i32_placeholder() } #[inline] fn patch_jump_target(&mut self, placeholder_offset: usize) { let current_pos = self.ctx.get_code_mut().len(); let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4; self.patch_i32(placeholder_offset, relative_offset); } #[inline] fn emit_str_id(&mut self, id: StringId) { self.ctx .get_code_mut() .extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes()); } fn current_depth(&self) -> u8 { self.scope_stack.last().map_or(0, |s| s.depth) } fn resolve_thunk(&self, id: ThunkId) -> (u8, u32) { for scope in self.scope_stack.iter().rev() { if let Some(&local_idx) = scope.thunk_map.get(&id) { let layer = self.current_depth() - scope.depth; return (layer, local_idx); } } panic!("ThunkId {:?} not found in any scope", id); } fn emit_load(&mut self, layer: u8, local: u32) { if layer == 0 { self.emit_op(Op::LoadLocal); self.emit_u32(local); } else { self.emit_op(Op::LoadOuter); self.emit_u8(layer); self.emit_u32(local); } } fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize { match ir { Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body), Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body), Ir::If { cond, consq, alter } => { self.count_with_thunks(*cond) + self.count_with_thunks(*consq) + self.count_with_thunks(*alter) } Ir::BinOp { lhs, rhs, .. } => { self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs) } Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs), Ir::Call { func, .. } => self.count_with_thunks(*func), Ir::Assert { assertion, expr, .. } => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr), Ir::Select { expr, .. } => self.count_with_thunks(*expr), Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs), Ir::ConcatStrings { parts, .. } => { parts.iter().map(|p| self.count_with_thunks(*p)).sum() } _ => 0, } } fn collect_all_thunks<'ir>( &self, own_thunks: &[(ThunkId, RawIrRef<'ir>)], body: RawIrRef<'ir>, ) -> Vec<(ThunkId, RawIrRef<'ir>)> { let mut all = Vec::from(own_thunks); self.collect_with_thunks_recursive(body, &mut all); let mut i = 0; while i < all.len() { let thunk_body = all[i].1; self.collect_with_thunks_recursive(thunk_body, &mut all); i += 1; } all } fn collect_with_thunks_recursive<'ir>( &self, ir: RawIrRef<'ir>, out: &mut Vec<(ThunkId, RawIrRef<'ir>)>, ) { match ir { Ir::With { thunks, body, .. } => { for &(id, inner) in thunks.iter() { out.push((id, inner)); } self.collect_with_thunks_recursive(*body, out); } Ir::TopLevel { thunks, body } => { for &(id, inner) in thunks.iter() { out.push((id, inner)); } self.collect_with_thunks_recursive(*body, out); } Ir::If { cond, consq, alter } => { self.collect_with_thunks_recursive(*cond, out); self.collect_with_thunks_recursive(*consq, out); self.collect_with_thunks_recursive(*alter, out); } Ir::BinOp { lhs, rhs, .. } => { self.collect_with_thunks_recursive(*lhs, out); self.collect_with_thunks_recursive(*rhs, out); } Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out), Ir::Call { func, .. } => { self.collect_with_thunks_recursive(*func, out); } Ir::Assert { assertion, expr, .. } => { self.collect_with_thunks_recursive(*assertion, out); self.collect_with_thunks_recursive(*expr, out); } Ir::Select { expr, .. } => { self.collect_with_thunks_recursive(*expr, out); } Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out), Ir::ConcatStrings { parts, .. } => { for p in parts.iter() { self.collect_with_thunks_recursive(*p, out); } } _ => (), } } fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) { let depth = self.scope_stack.len().try_into().expect("scope too deep!"); let thunk_base = if has_arg { 1u32 } else { 0u32 }; let thunk_map = thunk_ids .iter() .enumerate() .map(|(i, &id)| (id, thunk_base + i as u32)) .collect(); self.scope_stack.push(ScopeInfo { depth, thunk_map }); } fn pop_scope(&mut self) { self.scope_stack.pop(); } fn emit_toplevel(&mut self, ir: RawIrRef<'_>) { match ir { &Ir::TopLevel { body, ref thunks } => { let with_thunk_count = self.count_with_thunks(body); let total_slots = thunks.len() + with_thunk_count; let all_thunks = self.collect_all_thunks(thunks, body); let thunk_ids: Vec = all_thunks.iter().map(|&(id, _)| id).collect(); self.push_scope(false, &thunk_ids); if total_slots > 0 { self.emit_op(Op::AllocLocals); self.emit_u32(total_slots as u32); } self.emit_scope_thunks(thunks); self.emit_expr(body); self.emit_op(Op::Return); self.pop_scope(); } _ => { self.push_scope(false, &[]); self.emit_expr(ir); self.emit_op(Op::Return); self.pop_scope(); } } } fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) { for &(id, inner) in thunks { let skip_patch = self.emit_jump_placeholder(); let entry_point = self.ctx.get_code_mut().len() as u32; self.emit_expr(inner); self.emit_op(Op::Return); self.patch_jump_target(skip_patch); self.emit_op(Op::MakeThunk); self.emit_u32(entry_point); let (_, local_idx) = self.resolve_thunk(id); self.emit_op(Op::StoreLocal); self.emit_u32(local_idx); } } fn emit_expr(&mut self, ir: RawIrRef<'_>) { match ir { &Ir::Int(x) => { if let Ok(x) = x.try_into() { self.emit_op(Op::PushSmi); self.emit_i32(x); } else { self.emit_op(Op::PushBigInt); self.emit_i64(x); } } &Ir::Float(x) => { self.emit_op(Op::PushFloat); self.emit_f64(x); } &Ir::Bool(true) => self.emit_op(Op::PushTrue), &Ir::Bool(false) => self.emit_op(Op::PushFalse), Ir::Null => self.emit_op(Op::PushNull), &Ir::Str(id) => { self.emit_op(Op::PushString); self.emit_str_id(id); } &Ir::Path(p) => { self.emit_expr(p); self.emit_op(Op::ResolvePath); } &Ir::If { cond, consq, alter } => { self.emit_expr(cond); self.emit_op(Op::JumpIfFalse); let else_placeholder = self.emit_i32_placeholder(); let after_jif = self.ctx.get_code_mut().len(); self.emit_expr(consq); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); let else_offset = (after_jump as i32) - (after_jif as i32); self.patch_i32(else_placeholder, else_offset); self.emit_expr(alter); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32); self.patch_i32(end_placeholder, end_offset); } &Ir::BinOp { lhs, rhs, kind } => { self.emit_binop(lhs, rhs, kind); } &Ir::UnOp { rhs, kind } => match kind { UnOpKind::Neg => { self.emit_expr(rhs); self.emit_op(Op::OpNeg); } UnOpKind::Not => { self.emit_expr(rhs); self.emit_op(Op::OpNot); } }, &Ir::Func { body, ref param, ref thunks, } => { self.emit_func(thunks, param, body); } Ir::AttrSet { stcs, dyns } => { self.emit_attrset(stcs, dyns); } Ir::List { items } => { if items.is_empty() { self.emit_op(Op::MakeEmptyList); } else { self.emit_op(Op::MakeList); self.emit_u32(items.len() as u32); for &item in items.iter() { self.emit_maybe_thunk(item); } } } &Ir::Call { func, arg, .. } => { self.emit_expr(func); self.emit_op(Op::Call); self.emit_maybe_thunk(arg); } &Ir::Arg { layer } => { self.emit_load(layer, 0); } &Ir::TopLevel { body, ref thunks } => { self.emit_toplevel_inner(body, thunks); } &Ir::Select { expr, ref attrpath, default, span, } => { self.emit_select(expr, attrpath, default, span); } Ir::Builtins => { self.emit_op(Op::LoadBuiltins); } &Ir::Builtin(id) => { self.emit_op(Op::LoadBuiltin); self.emit_u8(id as u8); } &Ir::BuiltinConst(id) => { self.emit_select( &Ir::Builtins, &[Attr::Str(id, TextRange::default())], None, TextRange::default(), ); } &Ir::ConcatStrings { parts: _, force_string: _, } => { todo!("redesign ConcatStrings"); // self.emit_op(Op::ConcatStrings); // self.emit_u16(parts.len() as u16); // self.emit_u8(if force_string { 1 } else { 0 }); // for &part in parts.iter() { // let operand = self.inline_maybe_thunk(part); // self.emit_inline_operand(operand); // } } &Ir::HasAttr { lhs, ref rhs } => { self.emit_has_attr(lhs, rhs); } Ir::Assert { assertion, expr, assertion_raw, span, } => { let raw_idx = self.ctx.intern_string(assertion_raw); let span_id = self.ctx.register_span(*span); self.emit_expr(*assertion); self.emit_expr(*expr); self.emit_op(Op::Assert); self.emit_str_id(raw_idx); self.emit_u32(span_id); } &Ir::CurPos(span) => { let span_id = self.ctx.register_span(span); self.emit_op(Op::MkPos); self.emit_u32(span_id); } &Ir::ReplBinding(name) => { self.emit_op(Op::LoadReplBinding); self.emit_str_id(name); } &Ir::ScopedImportBinding(name) => { self.emit_op(Op::LoadScopedBinding); self.emit_str_id(name); } &Ir::With { namespace, body, ref thunks, } => { 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); } &Ir::MaybeThunk(thunk) => { use MaybeThunk::*; match *thunk { Int(x) => { if let Ok(x) = x.try_into() { self.emit_op(Op::PushSmi); self.emit_i32(x); } else { self.emit_op(Op::PushBigInt); self.emit_i64(x); } } Float(x) => { self.emit_op(Op::PushFloat); self.emit_f64(x); } Bool(true) => self.emit_op(Op::PushTrue), Bool(false) => self.emit_op(Op::PushFalse), Null => self.emit_op(Op::PushNull), Str(id) => { self.emit_op(Op::PushString); self.emit_str_id(id); } Path(id) => { self.emit_op(Op::PushString); self.emit_str_id(id); self.emit_op(Op::ResolvePath); } Thunk(id) => { let (layer, local) = self.resolve_thunk(id); self.emit_load(layer, local); } Arg { layer } => self.emit_load(layer, 0), Builtin(id) => { self.emit_op(Op::LoadBuiltin); self.emit_u8(id as u8); } BuiltinConst(id) => self.emit_select( &Ir::Builtins, &[Attr::Str(id, TextRange::default())], None, TextRange::default(), ), Builtins => self.emit_op(Op::LoadBuiltins), ReplBinding(name) => { self.emit_op(Op::LoadReplBinding); self.emit_str_id(name); } ScopedImportBinding(name) => { self.emit_op(Op::LoadScopedBinding); self.emit_str_id(name); } WithLookup(name) => { // TODO: specialize shallow with lookups self.emit_op(Op::PrepareWith); self.emit_op(Op::LookupWith); self.emit_str_id(name); } } } } } fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) { use BinOpKind::*; match kind { And => { self.emit_expr(lhs); self.emit_op(Op::JumpIfFalse); let skip_placeholder = self.emit_i32_placeholder(); let after_jif = self.ctx.get_code_mut().len(); self.emit_expr(rhs); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); let false_offset = (after_jump as i32) - (after_jif as i32); self.patch_i32(skip_placeholder, false_offset); self.emit_op(Op::PushFalse); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32); self.patch_i32(end_placeholder, end_offset); } Or => { self.emit_expr(lhs); self.emit_op(Op::JumpIfTrue); let skip_placeholder = self.emit_i32_placeholder(); let after_jit = self.ctx.get_code_mut().len(); self.emit_expr(rhs); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); let true_offset = (after_jump as i32) - (after_jit as i32); self.patch_i32(skip_placeholder, true_offset); self.emit_op(Op::PushTrue); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32); self.patch_i32(end_placeholder, end_offset); } Impl => { self.emit_expr(lhs); self.emit_op(Op::JumpIfFalse); let skip_placeholder = self.emit_i32_placeholder(); let after_jif = self.ctx.get_code_mut().len(); self.emit_expr(rhs); self.emit_op(Op::Jump); let end_placeholder = self.emit_i32_placeholder(); let after_jump = self.ctx.get_code_mut().len(); let true_offset = (after_jump as i32) - (after_jif as i32); self.patch_i32(skip_placeholder, true_offset); self.emit_op(Op::PushTrue); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32); self.patch_i32(end_placeholder, end_offset); } PipeL => { todo!("new call"); // self.emit_expr(rhs); // self.emit_expr(lhs); // self.emit_op(Op::Call); } PipeR => { todo!("new call"); // self.emit_expr(lhs); // self.emit_expr(rhs); // self.emit_op(Op::Call); } _ => { self.emit_expr(lhs); self.emit_expr(rhs); self.emit_op(match kind { Add => Op::OpAdd, Sub => Op::OpSub, Mul => Op::OpMul, Div => Op::OpDiv, Eq => Op::OpEq, Neq => Op::OpNeq, Lt => Op::OpLt, Gt => Op::OpGt, Leq => Op::OpLeq, Geq => Op::OpGeq, Con => Op::OpConcat, Upd => Op::OpUpdate, _ => unreachable!(), }); } } } fn emit_func<'ir>( &mut self, thunks: &[(ThunkId, RawIrRef<'ir>)], param: &Option>, body: RawIrRef<'ir>, ) { let with_thunk_count = self.count_with_thunks(body); let total_slots = thunks.len() + with_thunk_count; let all_thunks = self.collect_all_thunks(thunks, body); let thunk_ids: Vec = all_thunks.iter().map(|&(id, _)| id).collect(); let skip_patch = self.emit_jump_placeholder(); let entry_point = self.ctx.get_code().len() as u32; self.push_scope(true, &thunk_ids); self.emit_scope_thunks(thunks); self.emit_expr(body); self.emit_op(Op::Return); self.pop_scope(); self.patch_jump_target(skip_patch); if let Some(Param { required, optional, ellipsis, }) = param { self.emit_op(Op::MakePatternClosure); self.emit_u32(entry_point); self.emit_u32(total_slots as u32); self.emit_u16(required.len() as u16); self.emit_u16(optional.len() as u16); self.emit_u8(if *ellipsis { 1 } else { 0 }); for &(sym, _) in required.iter() { self.emit_str_id(sym); } for &(sym, _) in optional.iter() { self.emit_str_id(sym); } for &(sym, span) in required.iter().chain(optional.iter()) { let span_id = self.ctx.register_span(span); self.emit_str_id(sym); self.emit_u32(span_id); } } else { self.emit_op(Op::MakeClosure); self.emit_u32(entry_point); self.emit_u32(total_slots as u32); } } fn emit_attrset( &mut self, stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>, dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)], ) { if stcs.is_empty() && dyns.is_empty() { self.emit_op(Op::MakeEmptyAttrs); return; } let total = stcs.len() + dyns.len(); self.emit_op(Op::MakeAttrs); self.emit_u32(total as u32); for (&sym, &(val, span)) in stcs.iter() { self.emit_u8(AttrKeyType::Static as u8); self.emit_str_id(sym); self.emit_maybe_thunk(val); let span_id = self.ctx.register_span(span); self.emit_u32(span_id); } for &(_key, val, span) in dyns.iter() { self.emit_u8(AttrKeyType::Dynamic as u8); self.emit_maybe_thunk(val); let span_id = self.ctx.register_span(span); self.emit_u32(span_id); } } fn emit_select( &mut self, expr: RawIrRef<'_>, attrpath: &[Attr>], default: Option>, span: TextRange, ) { self.emit_expr(expr); let mut dynamic_patches = Vec::new(); for attr in attrpath.iter() { match *attr { Attr::Str(sym, _) => { 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(key_expr, _) => { self.emit_op(Op::JumpIfSelectFailed); dynamic_patches.push(self.emit_i32_placeholder()); 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 { let before: i32 = self.ctx.get_code().len().try_into().unwrap(); for patch in dynamic_patches { self.patch_jump_target(patch); } self.emit_op(Op::JumpIfSelectSucceeded); let placeholder = self.emit_i32_placeholder(); self.emit_expr(default); let after: i32 = self.ctx.get_code().len().try_into().unwrap(); // Offset is relative to after the placeholder, so subtract the // size of JumpIfSelectSucceeded (1) + placeholder (4). self.patch_i32(placeholder, after - before - 5); } else { for patch in dynamic_patches { self.patch_jump_target(patch); } } } fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr>]) { self.emit_expr(lhs); let mut dynamic_patches = Vec::new(); let [attrs @ .., last] = rhs else { panic!("attrpath is empty"); }; for attr in attrs { match *attr { Attr::Str(sym, span) => { let span_id = self.ctx.register_span(span); self.emit_op(Op::HasAttrPathStatic); self.emit_u32(span_id); self.emit_str_id(sym); } Attr::Dynamic(key_expr, span) => { self.emit_op(Op::JumpIfSelectFailed); dynamic_patches.push(self.emit_i32_placeholder()); self.emit_expr(key_expr); let span_id = self.ctx.register_span(span); self.emit_op(Op::HasAttrPathDynamic); self.emit_u32(span_id); } } } match *last { Attr::Str(sym, _) => { self.emit_op(Op::HasAttrStatic); self.emit_str_id(sym); } Attr::Dynamic(key_expr, _) => { self.emit_op(Op::JumpIfSelectFailed); dynamic_patches.push(self.emit_i32_placeholder()); self.emit_expr(key_expr); self.emit_op(Op::HasAttrDynamic); } } for patch in dynamic_patches { self.patch_jump_target(patch); } self.emit_op(Op::HasAttrResolve); } fn emit_with( &mut self, namespace: &MaybeThunk, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)], ) { self.emit_op(Op::PushWith); self.emit_maybe_thunk(namespace); self.emit_scope_thunks(thunks); self.emit_expr(body); self.emit_op(Op::PopWith); } fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) { self.emit_scope_thunks(thunks); self.emit_expr(body); } }