From b0ea71e104bc5ad95818371a1441118d921b4a68 Mon Sep 17 00:00:00 2001 From: Duhan BALCI Date: Mon, 6 Apr 2026 00:04:40 +0300 Subject: [PATCH] performance improvements --- benches/my_benchmark.rs | 244 ++++++++++++++++++++- src/ast/value.rs | 32 +-- src/bytecode.rs | 12 +- src/language_info.rs | 2 +- src/vm/methods.rs | 435 ++++++++++++++----------------------- src/vm/vm.rs | 65 +++--- tests/data_driven_tests.rs | 2 +- tests/integration_tests.rs | 20 +- 8 files changed, 469 insertions(+), 343 deletions(-) diff --git a/benches/my_benchmark.rs b/benches/my_benchmark.rs index 4c857f0..a404b3e 100644 --- a/benches/my_benchmark.rs +++ b/benches/my_benchmark.rs @@ -1,11 +1,29 @@ use criterion::{criterion_group, criterion_main, Criterion}; use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; +use indexmap::IndexMap; +use rust_decimal::Decimal; use rust_decimal_macros::dec; +use smol_str::SmolStr; +/// Helper: compile source to bytecode +fn compile(source: &str) -> Vec { + let ast = parser::program(source).unwrap(); + let mut compiler = Compiler::new(); + compiler.compile(ast).unwrap() +} +/// Helper: build a sample object with N fields +fn sample_object(n: usize) -> Value { + let mut map = IndexMap::new(); + for i in 0..n { + map.insert(SmolStr::new(format!("field{}", i)), Value::Number(Decimal::from(i))); + } + Value::Object(Box::new(map)) +} pub fn criterion_benchmark(c: &mut Criterion) { - // 1. Parser Benchmark + // ── Existing benchmarks ────────────────────────────────────────────── + c.bench_function("parser_long", |b| { let input = include_str!("../examples/bench_long.dexpr"); b.iter(|| { @@ -13,7 +31,6 @@ pub fn criterion_benchmark(c: &mut Criterion) { }) }); - // 2. Compiler Benchmark c.bench_function("compiler_long", |b| { let input = include_str!("../examples/bench_long.dexpr"); let ast = parser::program(input).unwrap(); @@ -23,9 +40,6 @@ pub fn criterion_benchmark(c: &mut Criterion) { }) }); - // 3. VM Benchmarks - - // basic_long.dexpr benchmark c.bench_function("vm_basic_long", |b| { let input = include_str!("../examples/basic_long.dexpr"); let ast = parser::program(input).unwrap(); @@ -38,7 +52,6 @@ pub fn criterion_benchmark(c: &mut Criterion) { }) }); - // Long code benchmark (using bench_long.dexpr) c.bench_function("vm_long", |b| { let input = include_str!("../examples/bench_long.dexpr"); let ast = parser::program(input).unwrap(); @@ -50,12 +63,9 @@ pub fn criterion_benchmark(c: &mut Criterion) { }) }); - // Short code benchmark c.bench_function("vm_short", |b| { let input = "5.12 + test * 1.5"; - let ast = parser::program(input).unwrap(); - let mut compiler = Compiler::new(); - let bytecode = compiler.compile(ast).unwrap(); + let bytecode = compile(input); let test_val = dec!(100); b.iter(|| { let mut vm = VM::new(&bytecode); @@ -63,6 +73,220 @@ pub fn criterion_benchmark(c: &mut Criterion) { let _ = vm.execute().unwrap(); }) }); + + // ── #1: Method dispatch clone overhead ─────────────────────────────── + + // Object method — clone entire IndexMap per call + c.bench_function("vm_object_method_keys", |b| { + let bytecode = compile("obj.keys()"); + let obj = sample_object(20); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("obj", obj.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_object_method_length", |b| { + let bytecode = compile("obj.length()"); + let obj = sample_object(20); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("obj", obj.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_object_method_contains", |b| { + let bytecode = compile(r#"obj.contains("field10")"#); + let obj = sample_object(20); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("obj", obj.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + // StringList method — clone entire Vec per call + c.bench_function("vm_strlist_method_length", |b| { + let bytecode = compile("items.length()"); + let items = Value::StringList(Box::new((0..50).map(|i| SmolStr::new(format!("item{}", i))).collect())); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("items", items.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_numlist_method_sum", |b| { + let bytecode = compile("nums.sum()"); + let nums = Value::NumberList(Box::new((0..100).map(Decimal::from).collect())); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("nums", nums.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + // String method — lighter clone (SmolStr) + c.bench_function("vm_string_method_upper", |b| { + let bytecode = compile(r#"s.upper()"#); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("s", Value::String(SmolStr::new("hello world this is a test string"))); + let _ = vm.execute().unwrap(); + }) + }); + + // ── #2 & #3: Vec alloc in method/external calls ───────────────────── + + c.bench_function("vm_method_call_with_args", |b| { + let bytecode = compile(r#"s.replace("hello", "world")"#); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("s", Value::String(SmolStr::new("hello world hello"))); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_external_fn_call", |b| { + let bytecode = compile("getRate(a, b)"); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("a", Value::Number(dec!(10))); + vm.set_global("b", Value::Number(dec!(20))); + vm.register_function("getRate", |_args| Ok(Value::Number(dec!(34.5)))); + let _ = vm.execute().unwrap(); + }) + }); + + // ── #4: Value enum size (cache pressure on register ops) ───────────── + + c.bench_function("vm_arithmetic_chain", |b| { + // Pure arithmetic — measures register read/write cache performance + let bytecode = compile( + "a = 1.5\nb = 2.3\nc = a + b\nd = c * a\ne = d - b\nf = e / c\ng = f + d\ng * 2.0", + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_comparison_chain", |b| { + let bytecode = compile( + "a = 10\nb = 20\nc = a < b\nd = b >= a\ne = a == 10\nf = b != 15\nc && d && e && f", + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + // ── #5: SmolStr alloc per opcode (string table missing) ────────────── + + c.bench_function("vm_global_read_heavy", |b| { + // Many LoadGlobal ops → SmolStr alloc per read (split to stay within register limit) + let bytecode = compile( + "r1 = x1 + x2 + x3\nr2 = x4 + x5 + x6\nr = r1 + r2\nr", + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + for i in 1..=6 { + vm.set_global(&format!("x{}", i), Value::Number(Decimal::from(i))); + } + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_global_write_heavy", |b| { + // Many StoreGlobal ops + let bytecode = compile( + "a = 1\nb = 2\nc = 3\nd = 4\ne = 5\nf = 6\nr = a + b + c\ns = d + e + f\nr + s", + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_property_access_chain", |b| { + // GetProperty → read_string per access + let bytecode = compile("obj.field0 + obj.field1 + obj.field2 + obj.field3 + obj.field4"); + let obj = sample_object(10); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("obj", obj.clone()); + let _ = vm.execute().unwrap(); + }) + }); + + // ── #8: String concat with format! ─────────────────────────────────── + + c.bench_function("vm_string_concat", |b| { + let bytecode = compile( + r#"a = "hello" + " " + "world" +b = a + " " + "this" +c = b + " " + "test" +c"#, + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + c.bench_function("vm_string_number_coerce", |b| { + let bytecode = compile( + r#"a = "value: " + 42 +b = a + " and " + 3.14 +b"#, + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + // ── #6: Opcode dispatch (overall loop throughput) ──────────────────── + + c.bench_function("vm_opcode_throughput", |b| { + // Many simple ops to stress the dispatch loop + let bytecode = compile( + "a = 1\nb = 2\nc = a + b\nd = c * 2\ne = d - 1\nf = e / 3\n\ + g = f + a\nh = g * b\ni = h - c\nj = i + d\n\ + k = j * 2\nl = k - 1\nm = l + 3\nn = m / 2\n\ + o = n + a\np = o * b\np", + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + // ── Combined: realistic expression with multiple issue areas ───────── + + c.bench_function("vm_realistic_mixed", |b| { + let bytecode = compile( + r#" + price = 100.50 + tax = 18 + discount = 5.5 + net = price * (1 + tax / 100) - discount + label = "Total: " + net + if net > 100 then + result = label + " (high)" + else + result = label + " (low)" + end + result + "#, + ); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/src/ast/value.rs b/src/ast/value.rs index 652e961..31ae253 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -11,9 +11,9 @@ pub enum Value { Number(Decimal), String(SmolStr), Boolean(bool), - NumberList(Vec), - StringList(Vec), - Object(IndexMap), + NumberList(Box>), + StringList(Box>), + Object(Box>), } /// Type tag constants for serialization @@ -120,7 +120,7 @@ impl Value { bytes.push((list.len() >> 8) as u8); bytes.push(list.len() as u8); // List items - for n in list { + for n in list.iter() { bytes.extend_from_slice(&n.serialize()); } } @@ -129,7 +129,7 @@ impl Value { bytes.push((list.len() >> 8) as u8); bytes.push(list.len() as u8); // List items - for s in list { + for s in list.iter() { // String length (2 bytes) bytes.push((s.len() >> 8) as u8); bytes.push(s.len() as u8); @@ -142,7 +142,7 @@ impl Value { bytes.push((map.len() >> 8) as u8); bytes.push(map.len() as u8); // Entries: key (string) + value (recursive) - for (key, val) in map { + for (key, val) in map.iter() { bytes.push((key.len() >> 8) as u8); bytes.push(key.len() as u8); bytes.extend_from_slice(key.as_bytes()); @@ -209,19 +209,19 @@ impl From for Value { impl From> for Value { fn from(v: Vec) -> Self { - Value::NumberList(v) + Value::NumberList(Box::new(v)) } } impl From> for Value { fn from(v: Vec) -> Self { - Value::StringList(v) + Value::StringList(Box::new(v)) } } impl From> for Value { fn from(m: IndexMap) -> Self { - Value::Object(m) + Value::Object(Box::new(m)) } } @@ -292,7 +292,7 @@ impl Value { list.push(Decimal::deserialize(decimal_bytes)); } - Ok((Value::NumberList(list), pos)) + Ok((Value::NumberList(Box::new(list)), pos)) } TYPE_STRING_LIST => { if bytes.len() < pos + 2 { @@ -321,7 +321,7 @@ impl Value { list.push(s.into()); } - Ok((Value::StringList(list), pos)) + Ok((Value::StringList(Box::new(list)), pos)) } TYPE_OBJECT => { if bytes.len() < pos + 2 { @@ -354,7 +354,7 @@ impl Value { map.insert(key.into(), val); } - Ok((Value::Object(map), pos)) + Ok((Value::Object(Box::new(map)), pos)) } _ => Err(format!("Unknown type tag: {}", type_tag)), } @@ -407,7 +407,7 @@ impl Value { serde_json::Value::String(s) => Ok(Value::String(SmolStr::new(s))), serde_json::Value::Array(arr) => { if arr.is_empty() { - return Ok(Value::StringList(Vec::new())); + return Ok(Value::StringList(Box::new(Vec::new()))); } // Check if all elements are the same type let first = &arr[0]; @@ -418,12 +418,12 @@ impl Value { nums.push(n); } } - Ok(Value::NumberList(nums)) + Ok(Value::NumberList(Box::new(nums))) } else if first.is_string() && arr.iter().all(|v| v.is_string()) { let strings: Vec = arr.iter() .filter_map(|v| v.as_str().map(SmolStr::new)) .collect(); - Ok(Value::StringList(strings)) + Ok(Value::StringList(Box::new(strings))) } else { Err("Arrays must contain all numbers or all strings".to_string()) } @@ -433,7 +433,7 @@ impl Value { for (k, v) in obj { map.insert(SmolStr::new(k), Self::from_json_value(v)?); } - Ok(Value::Object(map)) + Ok(Value::Object(Box::new(map))) } } } diff --git a/src/bytecode.rs b/src/bytecode.rs index f956cd2..22c5db2 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -126,9 +126,9 @@ impl<'a> BytecodeReader<'a> { self.read_byte() } - /// Read a string + /// Read a string as a borrowed slice (zero-copy from bytecode) #[inline(always)] - pub fn read_string(&mut self) -> Result { + pub fn read_str(&mut self) -> Result<&'a str, String> { let length = self.read_u16()? as usize; if self.position + length > self.bytecode.len() { @@ -139,7 +139,13 @@ impl<'a> BytecodeReader<'a> { .map_err(|_| "Invalid UTF-8 string".to_string())?; self.position += length; - Ok(s.into()) + Ok(s) + } + + /// Read a string as SmolStr (allocating — use read_str when possible) + #[inline(always)] + pub fn read_string(&mut self) -> Result { + self.read_str().map(SmolStr::from) } /// Read a value diff --git a/src/language_info.rs b/src/language_info.rs index 4c568b0..142fc13 100644 --- a/src/language_info.rs +++ b/src/language_info.rs @@ -24,7 +24,7 @@ //! let mut customer = IndexMap::new(); //! customer.insert(SmolStr::new("name"), Value::String("Alice".into())); //! customer.insert(SmolStr::new("age"), Value::Number(dec!(30))); -//! info.add_value("customer", &Value::Object(customer), None); +//! info.add_value("customer", &Value::Object(Box::new(customer)), None); //! info.add_value("price", &Value::Number(dec!(100)), None); //! //! let json = info.to_json(); diff --git a/src/vm/methods.rs b/src/vm/methods.rs index 495ad04..8dbabd6 100644 --- a/src/vm/methods.rs +++ b/src/vm/methods.rs @@ -6,66 +6,63 @@ use super::error::VMError; use super::vm::VM; impl<'a> VM<'a> { - /// Dispatch a method call on a value + /// Dispatch a method call on a value. + /// + /// Uses `std::mem::take` to temporarily move the value out of the register, + /// avoiding clones when dispatching to type-specific handlers. pub(super) fn dispatch_method( &mut self, dest: usize, obj: usize, - method: &SmolStr, + method: &str, args: &[Value], ) -> Result<(), VMError> { - match &self.registers[obj] { - Value::String(_) => self.dispatch_string_method(dest, obj, method, args), - Value::StringList(_) => self.dispatch_string_list_method(dest, obj, method, args), - Value::NumberList(_) => self.dispatch_number_list_method(dest, obj, method, args), - Value::Object(_) => self.dispatch_object_method(dest, obj, method, args), + // Take the value out to avoid borrow conflicts (register read + write). + let obj_val = std::mem::take(&mut self.registers[obj]); + + let result = match &obj_val { + Value::String(_) => self.dispatch_string_method_inner(&obj_val, method, args), + Value::StringList(_) => self.dispatch_string_list_method_inner(&obj_val, method, args), + Value::NumberList(_) => self.dispatch_number_list_method_inner(&obj_val, method, args), + Value::Object(_) => self.dispatch_object_method_inner(&obj_val, method, args), _ => { // Try external methods for any type - let obj_val = &self.registers[obj]; let type_name: SmolStr = obj_val.type_name().into(); - let key = (type_name.clone(), method.clone()); + let key = (type_name, SmolStr::from(method)); if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { - let result = ext_method(obj_val, args).map_err(VMError::RuntimeError)?; - self.registers[dest] = result; - Ok(()) + ext_method(&obj_val, args).map_err(VMError::RuntimeError) } else { Err(VMError::MethodNotFound { type_name: obj_val.type_name(), - method: method.clone(), + method: SmolStr::from(method), }) } } - } + }; + + // Put the object back, then set dest (if dest == obj, result overwrites it — that's fine). + self.registers[obj] = obj_val; + self.registers[dest] = result?; + Ok(()) } - fn dispatch_string_method( - &mut self, - dest: usize, - obj: usize, - method: &SmolStr, + fn dispatch_string_method_inner( + &self, + obj_val: &Value, + method: &str, args: &[Value], - ) -> Result<(), VMError> { - let s = match &self.registers[obj] { - Value::String(s) => s.clone(), + ) -> Result { + let s = match obj_val { + Value::String(s) => s, _ => unreachable!(), }; - match method.as_str() { - "upper" => { - self.registers[dest] = Value::String(s.to_uppercase_smolstr()); - } - "lower" => { - self.registers[dest] = Value::String(s.to_lowercase_smolstr()); - } - "trim" => { - self.registers[dest] = Value::String(SmolStr::new(s.trim())); - } - "trimStart" => { - self.registers[dest] = Value::String(SmolStr::new(s.trim_start())); - } - "trimEnd" => { - self.registers[dest] = Value::String(SmolStr::new(s.trim_end())); - } + match method { + "upper" => Ok(Value::String(s.to_uppercase_smolstr())), + "lower" => Ok(Value::String(s.to_lowercase_smolstr())), + "trim" => Ok(Value::String(SmolStr::new(s.trim()))), + "trimStart" => Ok(Value::String(SmolStr::new(s.trim_start()))), + "trimEnd" => Ok(Value::String(SmolStr::new(s.trim_end()))), "split" => { if args.is_empty() { return Err(VMError::RuntimeError( @@ -75,13 +72,11 @@ impl<'a> VM<'a> { match &args[0] { Value::String(delim) => { let parts: Vec = s.split(delim.as_str()).map(SmolStr::new).collect(); - self.registers[dest] = Value::StringList(parts); - } - _ => { - return Err(VMError::RuntimeError( - "split() requires a string delimiter".to_string(), - )); + Ok(Value::StringList(Box::new(parts))) } + _ => Err(VMError::RuntimeError( + "split() requires a string delimiter".to_string(), + )), } } "replace" => { @@ -92,14 +87,11 @@ impl<'a> VM<'a> { } match (&args[0], &args[1]) { (Value::String(old), Value::String(new)) => { - let result = SmolStr::new(s.replace(old.as_str(), new.as_str())); - self.registers[dest] = Value::String(result); - } - _ => { - return Err(VMError::RuntimeError( - "replace() requires string arguments".to_string(), - )); + Ok(Value::String(SmolStr::new(s.replace(old.as_str(), new.as_str())))) } + _ => Err(VMError::RuntimeError( + "replace() requires string arguments".to_string(), + )), } } "startsWith" => { @@ -109,14 +101,10 @@ impl<'a> VM<'a> { )); } match &args[0] { - Value::String(prefix) => { - self.registers[dest] = Value::Boolean(s.starts_with(prefix.as_str())); - } - _ => { - return Err(VMError::RuntimeError( - "startsWith() requires a string prefix".to_string(), - )); - } + Value::String(prefix) => Ok(Value::Boolean(s.starts_with(prefix.as_str()))), + _ => Err(VMError::RuntimeError( + "startsWith() requires a string prefix".to_string(), + )), } } "endsWith" => { @@ -126,14 +114,10 @@ impl<'a> VM<'a> { )); } match &args[0] { - Value::String(suffix) => { - self.registers[dest] = Value::Boolean(s.ends_with(suffix.as_str())); - } - _ => { - return Err(VMError::RuntimeError( - "endsWith() requires a string suffix".to_string(), - )); - } + Value::String(suffix) => Ok(Value::Boolean(s.ends_with(suffix.as_str()))), + _ => Err(VMError::RuntimeError( + "endsWith() requires a string suffix".to_string(), + )), } } "contains" => { @@ -143,19 +127,13 @@ impl<'a> VM<'a> { )); } match &args[0] { - Value::String(substr) => { - self.registers[dest] = Value::Boolean(s.contains(substr.as_str())); - } - _ => { - return Err(VMError::RuntimeError( - "contains() requires a string substring".to_string(), - )); - } + Value::String(substr) => Ok(Value::Boolean(s.contains(substr.as_str()))), + _ => Err(VMError::RuntimeError( + "contains() requires a string substring".to_string(), + )), } } - "length" => { - self.registers[dest] = Value::Number(Decimal::from(s.len())); - } + "length" => Ok(Value::Number(Decimal::from(s.len()))), "charAt" => { if args.is_empty() { return Err(VMError::RuntimeError( @@ -166,19 +144,13 @@ impl<'a> VM<'a> { Value::Number(idx) => { let index = idx.to_u64().unwrap_or(u64::MAX) as usize; match s.chars().nth(index) { - Some(c) => { - self.registers[dest] = Value::String(SmolStr::new(c.to_string())); - } - None => { - self.registers[dest] = Value::Null; - } + Some(c) => Ok(Value::String(SmolStr::new(c.to_string()))), + None => Ok(Value::Null), } } - _ => { - return Err(VMError::RuntimeError( - "charAt() requires a number index".to_string(), - )); - } + _ => Err(VMError::RuntimeError( + "charAt() requires a number index".to_string(), + )), } } "substring" => { @@ -201,68 +173,48 @@ impl<'a> VM<'a> { }; if start >= chars.len() || start >= end { - self.registers[dest] = Value::String(SmolStr::new("")); + Ok(Value::String(SmolStr::new(""))) } else { let end = end.min(chars.len()); let result: String = chars[start..end].iter().collect(); - self.registers[dest] = Value::String(SmolStr::new(result)); + Ok(Value::String(SmolStr::new(result))) } } - _ => { - return Err(VMError::RuntimeError( - "substring() requires a number start index".to_string(), - )); - } + _ => Err(VMError::RuntimeError( + "substring() requires a number start index".to_string(), + )), } } _ => { - let key = (SmolStr::new_static("String"), method.clone()); + let key = (SmolStr::new_static("String"), SmolStr::from(method)); if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { - let obj_val = &self.registers[obj]; - let result = ext_method(obj_val, args).map_err(VMError::RuntimeError)?; - self.registers[dest] = result; + ext_method(obj_val, args).map_err(VMError::RuntimeError) } else { - return Err(VMError::MethodNotFound { + Err(VMError::MethodNotFound { type_name: "String", - method: method.clone(), - }); + method: SmolStr::from(method), + }) } } } - Ok(()) } - fn dispatch_string_list_method( - &mut self, - dest: usize, - obj: usize, - method: &SmolStr, + fn dispatch_string_list_method_inner( + &self, + obj_val: &Value, + method: &str, args: &[Value], - ) -> Result<(), VMError> { - let list = match &self.registers[obj] { - Value::StringList(l) => l.clone(), + ) -> Result { + let list = match obj_val { + Value::StringList(l) => l, _ => unreachable!(), }; - match method.as_str() { - "length" | "len" => { - self.registers[dest] = Value::Number(Decimal::from(list.len())); - } - "isEmpty" => { - self.registers[dest] = Value::Boolean(list.is_empty()); - } - "first" => { - self.registers[dest] = list - .first() - .map(|s| Value::String(s.clone())) - .unwrap_or(Value::Null); - } - "last" => { - self.registers[dest] = list - .last() - .map(|s| Value::String(s.clone())) - .unwrap_or(Value::Null); - } + match method { + "length" | "len" => Ok(Value::Number(Decimal::from(list.len()))), + "isEmpty" => Ok(Value::Boolean(list.is_empty())), + "first" => Ok(list.first().map(|s| Value::String(s.clone())).unwrap_or(Value::Null)), + "last" => Ok(list.last().map(|s| Value::String(s.clone())).unwrap_or(Value::Null)), "get" => { if args.is_empty() { return Err(VMError::RuntimeError("get() requires an index".to_string())); @@ -270,12 +222,9 @@ impl<'a> VM<'a> { match &args[0] { Value::Number(idx) => { let index = idx.to_usize().unwrap_or(usize::MAX); - self.registers[dest] = list - .get(index) - .map(|s| Value::String(s.clone())) - .unwrap_or(Value::Null); + Ok(list.get(index).map(|s| Value::String(s.clone())).unwrap_or(Value::Null)) } - _ => return Err(VMError::RuntimeError("get() requires a number index".to_string())), + _ => Err(VMError::RuntimeError("get() requires a number index".to_string())), } } "contains" => { @@ -283,10 +232,8 @@ impl<'a> VM<'a> { return Err(VMError::RuntimeError("contains() requires an argument".to_string())); } match &args[0] { - Value::String(s) => { - self.registers[dest] = Value::Boolean(list.contains(s)); - } - _ => return Err(VMError::RuntimeError("contains() requires a string argument".to_string())), + Value::String(s) => Ok(Value::Boolean(list.contains(s))), + _ => Err(VMError::RuntimeError("contains() requires a string argument".to_string())), } } "indexOf" => { @@ -296,11 +243,9 @@ impl<'a> VM<'a> { match &args[0] { Value::String(s) => { let idx = list.iter().position(|item| item == s); - self.registers[dest] = idx - .map(|i| Value::Number(Decimal::from(i))) - .unwrap_or(Value::Number(Decimal::from(-1))); + Ok(idx.map(|i| Value::Number(Decimal::from(i))).unwrap_or(Value::Number(Decimal::from(-1)))) } - _ => return Err(VMError::RuntimeError("indexOf() requires a string argument".to_string())), + _ => Err(VMError::RuntimeError("indexOf() requires a string argument".to_string())), } } "slice" => { @@ -318,20 +263,20 @@ impl<'a> VM<'a> { } else { list.len() }; - self.registers[dest] = Value::StringList(list[start..end].to_vec()); + Ok(Value::StringList(Box::new(list[start..end].to_vec()))) } - _ => return Err(VMError::RuntimeError("slice() requires a number index".to_string())), + _ => Err(VMError::RuntimeError("slice() requires a number index".to_string())), } } "reverse" => { - let mut reversed = list; + let mut reversed = list.to_vec(); reversed.reverse(); - self.registers[dest] = Value::StringList(reversed); + Ok(Value::StringList(Box::new(reversed))) } "sort" => { - let mut sorted = list; + let mut sorted = list.to_vec(); sorted.sort(); - self.registers[dest] = Value::StringList(sorted); + Ok(Value::StringList(Box::new(sorted))) } "join" => { let delim = if args.is_empty() { @@ -343,56 +288,38 @@ impl<'a> VM<'a> { } }; let result: String = list.iter().map(|s| s.as_str()).collect::>().join(delim); - self.registers[dest] = Value::String(SmolStr::new(result)); + Ok(Value::String(SmolStr::new(result))) } _ => { - let key = (SmolStr::new_static("StringList"), method.clone()); + let key = (SmolStr::new_static("StringList"), SmolStr::from(method)); if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { - let obj_val = &self.registers[obj]; - let result = ext_method(obj_val, args).map_err(VMError::RuntimeError)?; - self.registers[dest] = result; + ext_method(obj_val, args).map_err(VMError::RuntimeError) } else { - return Err(VMError::MethodNotFound { + Err(VMError::MethodNotFound { type_name: "StringList", - method: method.clone(), - }); + method: SmolStr::from(method), + }) } } } - Ok(()) } - fn dispatch_number_list_method( - &mut self, - dest: usize, - obj: usize, - method: &SmolStr, + fn dispatch_number_list_method_inner( + &self, + obj_val: &Value, + method: &str, args: &[Value], - ) -> Result<(), VMError> { - let list = match &self.registers[obj] { - Value::NumberList(l) => l.clone(), + ) -> Result { + let list = match obj_val { + Value::NumberList(l) => l, _ => unreachable!(), }; - match method.as_str() { - "length" | "len" => { - self.registers[dest] = Value::Number(Decimal::from(list.len())); - } - "isEmpty" => { - self.registers[dest] = Value::Boolean(list.is_empty()); - } - "first" => { - self.registers[dest] = list - .first() - .map(|n| Value::Number(*n)) - .unwrap_or(Value::Null); - } - "last" => { - self.registers[dest] = list - .last() - .map(|n| Value::Number(*n)) - .unwrap_or(Value::Null); - } + match method { + "length" | "len" => Ok(Value::Number(Decimal::from(list.len()))), + "isEmpty" => Ok(Value::Boolean(list.is_empty())), + "first" => Ok(list.first().map(|n| Value::Number(*n)).unwrap_or(Value::Null)), + "last" => Ok(list.last().map(|n| Value::Number(*n)).unwrap_or(Value::Null)), "get" => { if args.is_empty() { return Err(VMError::RuntimeError("get() requires an index".to_string())); @@ -400,12 +327,9 @@ impl<'a> VM<'a> { match &args[0] { Value::Number(idx) => { let index = idx.to_usize().unwrap_or(usize::MAX); - self.registers[dest] = list - .get(index) - .map(|n| Value::Number(*n)) - .unwrap_or(Value::Null); + Ok(list.get(index).map(|n| Value::Number(*n)).unwrap_or(Value::Null)) } - _ => return Err(VMError::RuntimeError("get() requires a number index".to_string())), + _ => Err(VMError::RuntimeError("get() requires a number index".to_string())), } } "contains" => { @@ -413,10 +337,8 @@ impl<'a> VM<'a> { return Err(VMError::RuntimeError("contains() requires an argument".to_string())); } match &args[0] { - Value::Number(n) => { - self.registers[dest] = Value::Boolean(list.contains(n)); - } - _ => return Err(VMError::RuntimeError("contains() requires a number argument".to_string())), + Value::Number(n) => Ok(Value::Boolean(list.contains(n))), + _ => Err(VMError::RuntimeError("contains() requires a number argument".to_string())), } } "indexOf" => { @@ -426,11 +348,9 @@ impl<'a> VM<'a> { match &args[0] { Value::Number(n) => { let idx = list.iter().position(|item| item == n); - self.registers[dest] = idx - .map(|i| Value::Number(Decimal::from(i))) - .unwrap_or(Value::Number(Decimal::from(-1))); + Ok(idx.map(|i| Value::Number(Decimal::from(i))).unwrap_or(Value::Number(Decimal::from(-1)))) } - _ => return Err(VMError::RuntimeError("indexOf() requires a number argument".to_string())), + _ => Err(VMError::RuntimeError("indexOf() requires a number argument".to_string())), } } "slice" => { @@ -448,86 +368,70 @@ impl<'a> VM<'a> { } else { list.len() }; - self.registers[dest] = Value::NumberList(list[start..end].to_vec()); + Ok(Value::NumberList(Box::new(list[start..end].to_vec()))) } - _ => return Err(VMError::RuntimeError("slice() requires a number index".to_string())), + _ => Err(VMError::RuntimeError("slice() requires a number index".to_string())), } } "reverse" => { - let mut reversed = list; + let mut reversed = list.to_vec(); reversed.reverse(); - self.registers[dest] = Value::NumberList(reversed); + Ok(Value::NumberList(Box::new(reversed))) } "sort" => { - let mut sorted = list; + let mut sorted = list.to_vec(); sorted.sort(); - self.registers[dest] = Value::NumberList(sorted); + Ok(Value::NumberList(Box::new(sorted))) } "sum" => { let sum: Decimal = list.iter().sum(); - self.registers[dest] = Value::Number(sum); + Ok(Value::Number(sum)) } "avg" => { if list.is_empty() { - self.registers[dest] = Value::Null; + Ok(Value::Null) } else { let sum: Decimal = list.iter().sum(); let avg = sum / Decimal::from(list.len()); - self.registers[dest] = Value::Number(avg); + Ok(Value::Number(avg)) } } - "min" => { - self.registers[dest] = list - .iter() - .min() - .map(|n| Value::Number(*n)) - .unwrap_or(Value::Null); - } - "max" => { - self.registers[dest] = list - .iter() - .max() - .map(|n| Value::Number(*n)) - .unwrap_or(Value::Null); - } + "min" => Ok(list.iter().min().map(|n| Value::Number(*n)).unwrap_or(Value::Null)), + "max" => Ok(list.iter().max().map(|n| Value::Number(*n)).unwrap_or(Value::Null)), _ => { - let key = (SmolStr::new_static("NumberList"), method.clone()); + let key = (SmolStr::new_static("NumberList"), SmolStr::from(method)); if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { - let obj_val = &self.registers[obj]; - let result = ext_method(obj_val, args).map_err(VMError::RuntimeError)?; - self.registers[dest] = result; + ext_method(obj_val, args).map_err(VMError::RuntimeError) } else { - return Err(VMError::MethodNotFound { + Err(VMError::MethodNotFound { type_name: "NumberList", - method: method.clone(), - }); + method: SmolStr::from(method), + }) } } } - Ok(()) } - fn dispatch_object_method( - &mut self, - dest: usize, - obj: usize, - method: &SmolStr, + fn dispatch_object_method_inner( + &self, + obj_val: &Value, + method: &str, args: &[Value], - ) -> Result<(), VMError> { - let map = match &self.registers[obj] { - Value::Object(m) => m.clone(), + ) -> Result { + let map = match obj_val { + Value::Object(m) => m, _ => unreachable!(), }; - match method.as_str() { + match method { "keys" => { let keys: Vec = map.keys().cloned().collect(); - self.registers[dest] = Value::StringList(keys); + Ok(Value::StringList(Box::new(keys))) } "values" => { let vals: Vec = map.values().cloned().collect(); if vals.is_empty() { - self.registers[dest] = Value::StringList(Vec::new()); + Ok(Value::StringList(Box::new(Vec::new()))) } else if vals.iter().all(|v| matches!(v, Value::String(_))) { let strings: Vec = vals .into_iter() @@ -536,7 +440,7 @@ impl<'a> VM<'a> { _ => unreachable!(), }) .collect(); - self.registers[dest] = Value::StringList(strings); + Ok(Value::StringList(Box::new(strings))) } else if vals.iter().all(|v| matches!(v, Value::Number(_))) { let numbers: Vec = vals .into_iter() @@ -545,16 +449,14 @@ impl<'a> VM<'a> { _ => unreachable!(), }) .collect(); - self.registers[dest] = Value::NumberList(numbers); + Ok(Value::NumberList(Box::new(numbers))) } else { - return Err(VMError::RuntimeError( + Err(VMError::RuntimeError( "values() only works when all values are the same type (String or Number)".to_string(), - )); + )) } } - "length" | "len" => { - self.registers[dest] = Value::Number(Decimal::from(map.len())); - } + "length" | "len" => Ok(Value::Number(Decimal::from(map.len()))), "contains" => { if args.is_empty() { return Err(VMError::RuntimeError( @@ -562,14 +464,10 @@ impl<'a> VM<'a> { )); } match &args[0] { - Value::String(key) => { - self.registers[dest] = Value::Boolean(map.contains_key(key)); - } - _ => { - return Err(VMError::RuntimeError( - "contains() requires a string key".to_string(), - )) - } + Value::String(key) => Ok(Value::Boolean(map.contains_key(key))), + _ => Err(VMError::RuntimeError( + "contains() requires a string key".to_string(), + )), } } "get" => { @@ -579,30 +477,23 @@ impl<'a> VM<'a> { )); } match &args[0] { - Value::String(key) => { - self.registers[dest] = map.get(key).cloned().unwrap_or(Value::Null); - } - _ => { - return Err(VMError::RuntimeError( - "get() requires a string key".to_string(), - )) - } + Value::String(key) => Ok(map.get(key).cloned().unwrap_or(Value::Null)), + _ => Err(VMError::RuntimeError( + "get() requires a string key".to_string(), + )), } } _ => { - let key = (SmolStr::new_static("Object"), method.clone()); + let key = (SmolStr::new_static("Object"), SmolStr::from(method)); if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { - let obj_val = &self.registers[obj]; - let result = ext_method(obj_val, args).map_err(VMError::RuntimeError)?; - self.registers[dest] = result; + ext_method(obj_val, args).map_err(VMError::RuntimeError) } else { - return Err(VMError::MethodNotFound { + Err(VMError::MethodNotFound { type_name: "Object", - method: method.clone(), - }); + method: SmolStr::from(method), + }) } } } - Ok(()) } } diff --git a/src/vm/vm.rs b/src/vm/vm.rs index 5268ac7..36487d0 100644 --- a/src/vm/vm.rs +++ b/src/vm/vm.rs @@ -1,8 +1,8 @@ use crate::{ast::value::Value, bytecode::BytecodeReader, opcodes::OpCodeByte}; -use bumpalo::Bump; use micromap::Map; use rust_decimal::{Decimal, MathematicalOps}; use rustc_hash::FxHashMap; +use smallvec::SmallVec; use smol_str::SmolStr; /// Type alias for external (host) functions @@ -47,8 +47,6 @@ pub struct VM<'a> { pub(super) external_methods: Option>, - heap: Bump, - debug_info: Option<&'a DebugInfo>, #[cfg(debug_assertions)] @@ -70,7 +68,6 @@ impl<'a> VM<'a> { last_result: Value::Null, external_functions: None, external_methods: None, - heap: Bump::new(), debug_info: None, #[cfg(debug_assertions)] debug: false, @@ -144,7 +141,6 @@ impl<'a> VM<'a> { self.registers = [const { Value::Null }; MAX_REGISTERS]; self.last_result = Value::Null; // Preserve globals - self.heap = Bump::new(); } /// Execute the bytecode program and return the last expression result @@ -402,12 +398,12 @@ impl<'a> VM<'a> { #[inline] fn handle_load_global(&mut self) -> Result<(), VMError> { let reg = self.read_register_checked()?; - let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + let name = self.reader.read_str().map_err(VMError::BytecodeError)?; let value = self .globals - .get(&name) - .ok_or_else(|| VMError::UndefinedVariable(name.clone()))?; + .get(name) + .ok_or_else(|| VMError::UndefinedVariable(SmolStr::from(name)))?; self.registers[reg] = value.clone(); log_debug!(self, @@ -420,12 +416,12 @@ impl<'a> VM<'a> { /// Handle StoreGlobal opcode - store register to global variable #[inline] fn handle_store_global(&mut self) -> Result<(), VMError> { - let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + let name = self.reader.read_str().map_err(VMError::BytecodeError)?; let reg = self.read_register_checked()?; self .globals - .insert(name.clone(), self.registers[reg].clone()); + .insert(SmolStr::from(name), self.registers[reg].clone()); log_debug!(self, "StoreGlobal global.{} = r{} ({})", @@ -476,15 +472,23 @@ impl<'a> VM<'a> { self.registers[dest] = Value::Number(*a_num + *b_num); } (Value::String(a_str), Value::String(b_str)) => { - let result = format!("{}{}", a_str, b_str); + let mut result = String::with_capacity(a_str.len() + b_str.len()); + result.push_str(a_str); + result.push_str(b_str); self.registers[dest] = Value::String(result.into()); } (Value::String(a_str), other) => { - let result = format!("{}{}", a_str, value_to_string(other)); + let b_cow = value_to_string(other); + let mut result = String::with_capacity(a_str.len() + b_cow.len()); + result.push_str(a_str); + result.push_str(&b_cow); self.registers[dest] = Value::String(result.into()); } (other, Value::String(b_str)) => { - let result = format!("{}{}", value_to_string(other), b_str); + let a_cow = value_to_string(other); + let mut result = String::with_capacity(a_cow.len() + b_str.len()); + result.push_str(&a_cow); + result.push_str(b_str); self.registers[dest] = Value::String(result.into()); } (a_val, b_val) => { @@ -657,11 +661,11 @@ impl<'a> VM<'a> { let a = self.read_register_checked()?; let b = self.read_register_checked()?; - let result = format!( - "{}{}", - value_to_string(&self.registers[a]), - value_to_string(&self.registers[b]) - ); + let a_cow = value_to_string(&self.registers[a]); + let b_cow = value_to_string(&self.registers[b]); + let mut result = String::with_capacity(a_cow.len() + b_cow.len()); + result.push_str(&a_cow); + result.push_str(&b_cow); self.registers[dest] = Value::String(result.into()); log_debug!(self, "Concat r{} = r{} + r{}", dest, a, b); @@ -672,11 +676,11 @@ impl<'a> VM<'a> { fn handle_get_property(&mut self) -> Result<(), VMError> { let dest = self.read_register_checked()?; let obj = self.read_register_checked()?; - let prop = self.reader.read_string().map_err(VMError::BytecodeError)?; + let prop = self.reader.read_str().map_err(VMError::BytecodeError)?; match &self.registers[obj] { Value::Object(map) => { - let value = map.get(&prop).cloned().unwrap_or(Value::Null); + let value = map.get(prop).cloned().unwrap_or(Value::Null); self.registers[dest] = value; } other => { @@ -694,13 +698,13 @@ impl<'a> VM<'a> { /// Handle SetProperty opcode - set a field on an Object (in-place on register) fn handle_set_property(&mut self) -> Result<(), VMError> { let obj = self.read_register_checked()?; - let prop = self.reader.read_string().map_err(VMError::BytecodeError)?; + let prop = self.reader.read_str().map_err(VMError::BytecodeError)?; let val = self.read_register_checked()?; let value = self.registers[val].clone(); match &mut self.registers[obj] { Value::Object(map) => { - map.insert(prop.clone(), value); + map.insert(SmolStr::from(prop), value); } other => { return Err(VMError::RuntimeError(format!( @@ -718,21 +722,21 @@ impl<'a> VM<'a> { fn handle_method_call(&mut self) -> Result<(), VMError> { let dest = self.read_register_checked()?; let obj = self.read_register_checked()?; - let method = self.reader.read_string().map_err(VMError::BytecodeError)?; + let method = self.reader.read_str().map_err(VMError::BytecodeError)?; // Read argument registers let arg_count = self .reader .read_byte() .map_err(VMError::BytecodeError)? as usize; - let mut args = Vec::with_capacity(arg_count); + let mut args: SmallVec<[Value; 4]> = SmallVec::with_capacity(arg_count); for _ in 0..arg_count { let reg = self.read_register_checked()?; args.push(self.registers[reg].clone()); } - self.dispatch_method(dest, obj, &method, &args)?; + self.dispatch_method(dest, obj, method, &args)?; log_debug!(self, "MethodCall r{} = r{}.{}(...)", dest, obj, method); Ok(()) @@ -748,7 +752,7 @@ impl<'a> VM<'a> { let fn_id = self.reader.read_byte().map_err(VMError::BytecodeError)?; let arg_count = self.reader.read_byte().map_err(VMError::BytecodeError)? as usize; - let mut arg_regs = Vec::with_capacity(arg_count); + let mut arg_regs: SmallVec<[usize; 4]> = SmallVec::with_capacity(arg_count); for _ in 0..arg_count { arg_regs.push(self.read_register_checked()?); } @@ -762,20 +766,21 @@ impl<'a> VM<'a> { /// Handle CallExternal opcode - call host function by name fn handle_call_external(&mut self) -> Result<(), VMError> { let dest = self.read_register_checked()?; - let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + let name = self.reader.read_str().map_err(VMError::BytecodeError)?; let arg_count = self.reader.read_byte().map_err(VMError::BytecodeError)? as usize; - let mut args = Vec::with_capacity(arg_count); + let mut args: SmallVec<[Value; 4]> = SmallVec::with_capacity(arg_count); for _ in 0..arg_count { let reg = self.read_register_checked()?; args.push(self.registers[reg].clone()); } + let name_key = SmolStr::from(name); let func = self .external_functions .as_ref() - .and_then(|m| m.get(&name)) - .ok_or_else(|| VMError::RuntimeError(format!("Undefined function: {}", name)))?; + .and_then(|m| m.get(&name_key)) + .ok_or_else(|| VMError::RuntimeError(format!("Undefined function: {}", name_key)))?; let result = func(&args).map_err(VMError::RuntimeError)?; self.registers[dest] = result; diff --git a/tests/data_driven_tests.rs b/tests/data_driven_tests.rs index ef61a6f..b4e1201 100644 --- a/tests/data_driven_tests.rs +++ b/tests/data_driven_tests.rs @@ -55,7 +55,7 @@ fn value_def_to_value(def: &ValueDef) -> Value { }; map.insert(SmolStr::from(k.as_str()), val); } - Value::Object(map) + Value::Object(Box::new(map)) } let obj = def.value.as_ref().unwrap().as_object().unwrap(); json_obj_to_value(obj) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ecf275d..f2445db 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -628,7 +628,7 @@ fn test_numberlist_contains() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3), dec!(4)])); + vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3), dec!(4)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); } @@ -639,7 +639,7 @@ fn test_numberlist_indexof() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3)])); + vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(2))); } @@ -650,7 +650,7 @@ fn test_numberlist_slice() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![dec!(10), dec!(20), dec!(30), dec!(40)])); + vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(10), dec!(20), dec!(30), dec!(40)]))); vm.execute().expect("execute"); // slice(1,3) = [20, 30], sum = 50 assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(50))); @@ -662,7 +662,7 @@ fn test_numberlist_reverse() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3)])); + vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(1), dec!(2), dec!(3)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(3))); } @@ -673,7 +673,7 @@ fn test_numberlist_sort() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![dec!(30), dec!(10), dec!(20)])); + vm.set_global("nums", Value::NumberList(Box::new(vec![dec!(30), dec!(10), dec!(20)]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(10))); } @@ -684,7 +684,7 @@ fn test_numberlist_isempty() { let mut compiler = Compiler::new(); let bytecode = compiler.compile(ast).expect("compile"); let mut vm = VM::new(&bytecode); - vm.set_global("nums", Value::NumberList(vec![])); + vm.set_global("nums", Value::NumberList(Box::new(vec![]))); vm.execute().expect("execute"); assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); } @@ -1028,7 +1028,7 @@ fn make_object(entries: Vec<(&str, Value)>) -> Value { for (k, v) in entries { map.insert(SmolStr::new(k), v); } - Value::Object(map) + Value::Object(Box::new(map)) } #[test] @@ -1113,7 +1113,7 @@ fn test_object_method_keys() { let result = run_expr_with_globals("person.keys()", vec![("person", obj)]); assert_eq!( result, - Value::StringList(vec![SmolStr::new("name"), SmolStr::new("age")]) + Value::StringList(Box::new(vec![SmolStr::new("name"), SmolStr::new("age")])) ); } @@ -1177,7 +1177,7 @@ fn test_object_method_values_strings() { let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); assert_eq!( result, - Value::StringList(vec![SmolStr::new("x"), SmolStr::new("y")]) + Value::StringList(Box::new(vec![SmolStr::new("x"), SmolStr::new("y")])) ); } @@ -1188,7 +1188,7 @@ fn test_object_method_values_numbers() { ("b", Value::Number(dec!(2))), ]); let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); - assert_eq!(result, Value::NumberList(vec![dec!(1), dec!(2)])); + assert_eq!(result, Value::NumberList(Box::new(vec![dec!(1), dec!(2)]))); } #[test]