diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs index 45d0b6a..3087ce1 100644 --- a/fix-codegen/src/disassembler.rs +++ b/fix-codegen/src/disassembler.rs @@ -291,7 +291,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { Op::Call => { self.read_operand_data(); ("Call", "arg=?".into()) - }, + } Op::DispatchPrimOp => { let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id"); ("DispatchPrimOp", format!("id={id:?}")) @@ -419,6 +419,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { let force = self.read_u8(); ("ConcatStrings", format!("count={} force={}", count, force)) } + Op::CoerceToString => ("CoerceToString", String::new()), Op::ResolvePath => ("ResolvePath", String::new()), Op::Assert => { let raw_idx = self.read_u32(); diff --git a/fix-codegen/src/lib.rs b/fix-codegen/src/lib.rs index 659d138..4e64238 100644 --- a/fix-codegen/src/lib.rs +++ b/fix-codegen/src/lib.rs @@ -77,6 +77,8 @@ pub enum Op { JumpIfTrue, Jump, + CoerceToString, + ConcatStrings, ResolvePath, @@ -250,6 +252,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.ctx.get_code_mut().push(op as u8); } + #[inline] + fn emit_bool(&mut self, val: bool) { + self.emit_u8(u8::from(val)); + } + #[inline] fn emit_u8(&mut self, val: u8) { self.ctx.get_code_mut().push(val); @@ -615,17 +622,16 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { ); } &Ir::ConcatStrings { - parts: _, - force_string: _, + ref 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); - // } + for &part in parts.iter() { + self.emit_expr(part); + self.emit_op(Op::CoerceToString); + } + self.emit_op(Op::ConcatStrings); + self.emit_u16(parts.len() as u16); + self.emit_bool(force_string); } &Ir::HasAttr { lhs, ref rhs } => { self.emit_has_attr(lhs, rhs); @@ -842,7 +848,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { 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 }); + self.emit_bool(*ellipsis); for &(sym, _) in required.iter() { self.emit_str_id(sym); diff --git a/fix-vm/src/dispatch_tailcall.rs b/fix-vm/src/dispatch_tailcall.rs index e6ebad1..5fb45b8 100644 --- a/fix-vm/src/dispatch_tailcall.rs +++ b/fix-vm/src/dispatch_tailcall.rs @@ -180,6 +180,8 @@ tail_fn!(op_jump_if_false, (reader, mc)); tail_fn!(op_jump_if_true, (reader, mc)); tail_fn!(op_jump, (reader)); +tail_fn!(op_coerce_to_string, (reader, mc)); + tail_fn!(op_concat_strings, (ctx, reader, mc)); tail_fn!(op_resolve_path, (ctx)); @@ -272,6 +274,8 @@ table! { JumpIfTrue => op_jump_if_true, Jump => op_jump, + CoerceToString => op_coerce_to_string, + ConcatStrings => op_concat_strings, ResolvePath => op_resolve_path, diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs index cb735de..9c719a3 100644 --- a/fix-vm/src/instructions/arithmetic.rs +++ b/fix-vm/src/instructions/arithmetic.rs @@ -326,6 +326,9 @@ fn numeric_binop<'gc>( (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { Ok(Value::new_float(float_op(a, b as f64))) } - _ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")), + _ => Err(crate::vm_err(format!( + "cannot perform arithmetic on non-numbers: {:?}", + (lhs.ty(), rhs.ty()) + ))), } } diff --git a/fix-vm/src/instructions/misc.rs b/fix-vm/src/instructions/misc.rs index 61377b2..258c58c 100644 --- a/fix-vm/src/instructions/misc.rs +++ b/fix-vm/src/instructions/misc.rs @@ -1,6 +1,8 @@ use fix_builtins::BuiltinId; +use fix_common::StringId; use num_enum::TryFromPrimitive; +use crate::value::{NixString, StrictValue}; use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx}; impl<'gc> crate::Vm<'gc> { @@ -34,6 +36,21 @@ impl<'gc> crate::Vm<'gc> { todo!("LoadScopedBinding"); } + #[inline(always)] + pub(crate) fn op_coerce_to_string( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, + ) -> Step { + let val = self.try_force::(reader, mc)?; + if val.is::() || val.is::() { + self.push(val.relax()); + } else { + todo!("coerce other types to string: {:?}", val.ty()); + } + Step::Continue(()) + } + #[inline(always)] pub(crate) fn op_concat_strings( &mut self, @@ -41,14 +58,30 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, _mc: &gc_arena::Mutation<'gc>, ) -> Step { - let _parts_count = reader.read_u16() as usize; + use crate::VmRuntimeCtxExt; + + let count = reader.read_u16() as usize; let _force_string = reader.read_u8() != 0; - let mut _operands: smallvec::SmallVec<[crate::OperandData; 4]> = - smallvec::SmallVec::with_capacity(_parts_count); - for _ in 0.._parts_count { - _operands.push(reader.read_operand_data(ctx)); + + let mut total_len = 0; + for i in 0..count { + let val = self.peek_forced(count - 1 - i); + let s = ctx.get_string(val).expect("coerced"); + total_len += s.len(); } - todo!("implement ConcatStrings (force parts, coerce to string, concatenate)"); + + let mut result = String::with_capacity(total_len); + for i in 0..count { + let val = self.peek_forced(count - 1 - i); + let s = ctx.get_string(val).expect("coerced"); + result.push_str(s); + } + + self.stack.truncate(self.stack.len() - count); + + let sid = ctx.intern_string(result); + self.push(Value::new_inline(sid)); + Step::Continue(()) } #[inline(always)] diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index d6c1ee7..5ba903a 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -492,7 +492,7 @@ impl<'gc> Vm<'gc> { }); self.push(func); self.call(reader, mc, arg, resume_pc) - }, + } ThunkState::Blackhole => { self.finish_err(Error::eval_error("infinite recursion encountered")) } @@ -666,6 +666,7 @@ impl<'gc> Vm<'gc> { Jump => self.op_jump(&mut reader), ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc), + CoerceToString => self.op_coerce_to_string(&mut reader, mc), ResolvePath => self.op_resolve_path(ctx), Assert => self.op_assert(&mut reader),