diff --git a/src/engine.rs b/src/engine.rs index 8ad61089..e4068fc7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -369,8 +369,15 @@ impl Engine { } /// Imitation of std::hints::black_box which requires nightly. + #[cfg(not(target_family = "wasm"))] #[inline(never)] pub(crate) fn black_box() -> usize { unsafe { core::ptr::read_volatile(&0_usize as *const usize) } } + /// Imitation of std::hints::black_box which requires nightly. + #[cfg(target_family = "wasm")] + #[inline(always)] + pub(crate) fn black_box() -> usize { + 0 + } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 91d944cf..c2aaf79d 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -254,8 +254,6 @@ impl Engine { } // Then variable access. - // We shouldn't do this for too many variants because, soon or later, the added comparisons - // will cost more than the mis-predicted `match` branch. if let Expr::Variable(x, index, var_pos) = expr { return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS { this_ptr @@ -267,41 +265,27 @@ impl Engine { }; } - // Stop merging branches here! - Self::black_box(); - - // Constants + // Then integer constants. if let Expr::IntegerConstant(x, ..) = expr { return Ok((*x).into()); } - if let Expr::StringConstant(x, ..) = expr { - return Ok(x.clone().into()); - } - if let Expr::BoolConstant(x, ..) = expr { - return Ok((*x).into()); - } - - // Stop merging branches here! - Self::black_box(); - - #[cfg(not(feature = "no_float"))] - if let Expr::FloatConstant(x, ..) = expr { - return Ok((*x).into()); - } - if let Expr::CharConstant(x, ..) = expr { - return Ok((*x).into()); - } - if let Expr::Unit(..) = expr { - return Ok(Dynamic::UNIT); - } - if let Expr::DynamicConstant(x, ..) = expr { - return Ok(x.as_ref().clone()); - } // Stop merging branches here! + // We shouldn't lift out too many variants because, soon or later, the added comparisons + // will cost more than the mis-predicted `match` branch. Self::black_box(); match expr { + // Constants + Expr::IntegerConstant(..) => unreachable!(), + Expr::StringConstant(x, ..) => Ok(x.clone().into()), + Expr::BoolConstant(x, ..) => Ok((*x).into()), + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(x, ..) => Ok((*x).into()), + Expr::CharConstant(x, ..) => Ok((*x).into()), + Expr::Unit(..) => Ok(Dynamic::UNIT), + Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()), + // `... ${...} ...` Expr::InterpolatedString(x, _) => { let mut concat = SmartString::new_const(); @@ -445,8 +429,13 @@ impl Engine { .and_then(|r| self.check_data_size(r, expr.start_position())) } - Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), - Expr::Stmt(x) => self.eval_stmt_block(global, caches, scope, this_ptr, x, true), + Expr::Stmt(x) => { + if x.is_empty() { + Ok(Dynamic::UNIT) + } else { + self.eval_stmt_block(global, caches, scope, this_ptr, x, true) + } + } #[cfg(not(feature = "no_index"))] Expr::Index(..) => { diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 4b863701..1f4ae51d 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -277,7 +277,6 @@ impl Engine { // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. - // Hopefully the compiler won't undo all this work! // Function calls should account for a relatively larger portion of statements. if let Stmt::FnCall(x, pos) = stmt { @@ -285,8 +284,6 @@ impl Engine { } // Then assignments. - // We shouldn't do this for too many variants because, soon or later, the added comparisons - // will cost more than the mis-predicted `match` branch. if let Stmt::Assignment(x, ..) = stmt { let (op_info, BinaryExpr { lhs, rhs }) = &**x; @@ -353,22 +350,12 @@ impl Engine { } } - // Stop merging branches here! - Self::black_box(); - - // Block scope - if let Stmt::Block(statements, ..) = stmt { - return if statements.is_empty() { - Ok(Dynamic::UNIT) - } else { - self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) - }; - } - + // Then variable definitions. if let Stmt::Var(x, options, pos) = stmt { if !self.allow_shadowing() && scope.contains(&x.0) { return Err(ERR::ErrorVariableExists(x.0.to_string(), *pos).into()); } + // Let/const statement let (var_name, expr, index) = &**x; @@ -451,48 +438,52 @@ impl Engine { } // Stop merging branches here! - Self::black_box(); - - // If statement - if let Stmt::If(x, ..) = stmt { - let FlowControl { - expr, - body: if_block, - branch: else_block, - } = &**x; - - let guard_val = self - .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? - .as_bool() - .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; - - return match guard_val { - true if !if_block.is_empty() => { - self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true) - } - false if !else_block.is_empty() => { - self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true) - } - _ => Ok(Dynamic::UNIT), - }; - } - - // Expression as statement - if let Stmt::Expr(expr) = stmt { - return self - .eval_expr(global, caches, scope, this_ptr, expr) - .map(Dynamic::flatten); - } - - // No-op - if let Stmt::Noop(..) = stmt { - return Ok(Dynamic::UNIT); - } - - // Stop merging branches here! + // We shouldn't lift out too many variants because, soon or later, the added comparisons + // will cost more than the mis-predicted `match` branch. Self::black_box(); match stmt { + // No-op + Stmt::Noop(..) => Ok(Dynamic::UNIT), + + // Expression as statement + Stmt::Expr(expr) => self + .eval_expr(global, caches, scope, this_ptr, expr) + .map(Dynamic::flatten), + + // Block scope + Stmt::Block(statements, ..) => { + if statements.is_empty() { + Ok(Dynamic::UNIT) + } else { + self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) + } + } + + // If statement + Stmt::If(x, ..) => { + let FlowControl { + expr, + body: if_block, + branch: else_block, + } = &**x; + + let guard_val = self + .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; + + match guard_val { + true if !if_block.is_empty() => { + self.eval_stmt_block(global, caches, scope, this_ptr, if_block, true) + } + false if !else_block.is_empty() => { + self.eval_stmt_block(global, caches, scope, this_ptr, else_block, true) + } + _ => Ok(Dynamic::UNIT), + } + } + // Switch statement Stmt::Switch(x, ..) => { let (