From 0516e8088c46e977ab0317d8362e188c3818cc8d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 2 Sep 2022 23:45:25 +0800 Subject: [PATCH] Add fast-ops feature. --- .github/workflows/benchmark.yml | 1 + Cargo.toml | 3 ++- src/ast/expr.rs | 6 ++++++ src/eval/expr.rs | 28 ++++++++++++++++++++++++++++ src/parser.rs | 5 ++++- tests/arrays.rs | 16 ++++++++++++++++ tests/optimizer.rs | 25 ++++++++++++++----------- 7 files changed, 71 insertions(+), 13 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 98309937..267fccfa 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,6 +3,7 @@ on: push: branches: - master + - fast-ops jobs: benchmark: diff --git a/Cargo.toml b/Cargo.toml index 54331e51..effb4b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,10 @@ serde_bytes = "0.11" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } [features] -default = ["std"] +default = ["std", "fast_ops"] std = ["ahash/std", "ahash/runtime-rng", "num-traits/std", "smartstring/std"] unchecked = [] # unchecked arithmetic +fast_ops = [] # ignore overloaded standard operators sync = [] # restrict to only types that implement Send + Sync no_position = [] # do not track position in the parser no_optimize = [] # no script optimizer diff --git a/src/ast/expr.rs b/src/ast/expr.rs index e2f66fd0..b648d7c4 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -190,6 +190,8 @@ pub struct FnCallExpr { pub args: StaticVec, /// Does this function call capture the parent scope? pub capture_parent_scope: bool, + /// Is this function call a simple symbol-based operator? + pub is_standard_operator: bool, /// [Position] of the function name. pub pos: Position, } @@ -204,6 +206,9 @@ impl fmt::Debug for FnCallExpr { if self.capture_parent_scope { ff.field("capture_parent_scope", &self.capture_parent_scope); } + if self.is_standard_operator { + ff.field("is_standard_operator", &self.is_standard_operator); + } ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); @@ -662,6 +667,7 @@ impl Expr { hashes: calc_fn_hash(f.fn_name(), 1).into(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, + is_standard_operator: false, pos, } .into(), diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 9419e9e9..7fd7027b 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -222,11 +222,39 @@ impl Engine { #[cfg(not(feature = "no_module"))] namespace, capture_parent_scope: capture, + is_standard_operator, hashes, args, .. } = expr; + #[cfg(feature = "fast_ops")] + if *is_standard_operator { + let mut lhs = self + .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? + .0 + .flatten(); + + let mut rhs = self + .get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)? + .0 + .flatten(); + + let arg_values = &mut [&mut lhs, &mut rhs]; + + return if let Some(f) = + crate::func::get_builtin_binary_op_fn(&name, arg_values[0], arg_values[1]) + { + let context = (self, name, None, &*global, lib, pos, level).into(); + (f)(context, arg_values) + } else { + self.exec_fn_call( + None, global, caches, lib, name, *hashes, arg_values, false, false, pos, level, + ) + .map(|(v, ..)| v) + }; + } + #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { // Qualified function call diff --git a/src/parser.rs b/src/parser.rs index 43df65a1..4142cd68 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -614,8 +614,9 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: id, + name: state.get_interned_string(id), capture_parent_scope, + is_standard_operator: false, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -687,6 +688,7 @@ impl Engine { return Ok(FnCallExpr { name: state.get_interned_string(id), capture_parent_scope, + is_standard_operator: false, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -2339,6 +2341,7 @@ impl Engine { name: state.get_interned_string(op.as_ref()), hashes: FnCallHashes::from_native(hash), pos, + is_standard_operator: op_token.is_standard_symbol(), ..Default::default() }; diff --git a/tests/arrays.rs b/tests/arrays.rs index d29d062b..a5fc59ce 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -169,6 +169,22 @@ fn test_arrays() -> Result<(), Box> { " )?); + let value = vec![ + String::from("hello"), + String::from("world"), + String::from("foo"), + String::from("bar"), + ]; + + let array: Dynamic = value.into(); + + assert_eq!(array.type_name(), "array"); + + let array = array.cast::(); + + assert_eq!(array[0].type_name(), "string"); + assert_eq!(array.len(), 4); + Ok(()) } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index d231540b..93a429ce 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -49,21 +49,24 @@ fn test_optimizer_run() -> Result<(), Box> { run_test(&mut engine)?; // Override == operator - engine.register_fn("==", |_x: INT, _y: INT| false); + #[cfg(not(feature = "fast_ops"))] + { + engine.register_fn("==", |_x: INT, _y: INT| false); - engine.set_optimization_level(OptimizationLevel::Simple); + engine.set_optimization_level(OptimizationLevel::Simple); - assert_eq!( - engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, - 123 - ); + assert_eq!( + engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, + 123 + ); - engine.set_optimization_level(OptimizationLevel::Full); + engine.set_optimization_level(OptimizationLevel::Full); - assert_eq!( - engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, - 123 - ); + assert_eq!( + engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, + 123 + ); + } Ok(()) }