From 19b9efe88e6c5bb5b2e4e65ab322343ebffdf63a Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 25 May 2020 14:45:10 +0100 Subject: [PATCH 01/15] rebased Profiler --- .gitignore | 6 ++ Cargo.lock | 89 +++++++++++++++++++ boa/Cargo.toml | 2 + boa/src/lib.rs | 14 ++- boa/src/profiler.rs | 77 ++++++++++++++++ boa/src/realm.rs | 2 + boa/src/syntax/lexer/mod.rs | 10 ++- .../expression/assignment/arrow_function.rs | 24 ++--- 8 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 boa/src/profiler.rs diff --git a/.gitignore b/.gitignore index 42c96f2e8c5..9f85a70ad04 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,9 @@ yarn-error.log # tests/js/test.js is used for testing changes locally tests/js/test.js + +# Profiling +*.string_data +*.string_index +*.events +chrome_profiler.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3bc70dc1544..486605160d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,8 +8,10 @@ dependencies = [ "criterion", "gc", "jemallocator", + "measureme", "num-bigint", "num-traits", + "once_cell", "rand", "regex", "rustc-hash", @@ -136,6 +138,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "criterion" version = "0.3.2" @@ -359,6 +370,15 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.8" @@ -374,12 +394,34 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measureme" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef709d3257013bba7cff14fc504e07e80631d3fe0f6d38ce63b8f6510ccb932" +dependencies = [ + "byteorder", + "memmap", + "parking_lot", + "rustc-hash", +] + [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "memoffset" version = "0.5.4" @@ -430,12 +472,44 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + [[package]] name = "oorandom" version = "11.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af325bc33c7f60191be4e2c984d48aaa21e2854f473b85398344b60c9b6358" +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core", + "rustc_version", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec", + "winapi", +] + [[package]] name = "plotters" version = "0.2.14" @@ -563,6 +637,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + [[package]] name = "regex" version = "1.3.7" @@ -672,6 +752,15 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "strsim" version = "0.8.0" diff --git a/boa/Cargo.toml b/boa/Cargo.toml index aebe579223f..8f87939e2c6 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -22,6 +22,8 @@ num-bigint = { version = "0.2.6", features = ["serde"] } # Optional Dependencies serde = { version = "1.0.110", features = ["derive"], optional = true } bitflags = "1.2.1" +measureme = { version = "0.7.1" } +once_cell = "1.4.0" [dev-dependencies] criterion = "0.3.2" diff --git a/boa/src/lib.rs b/boa/src/lib.rs index ed75621262d..9e8f610cb5b 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -36,12 +36,14 @@ pub mod builtins; pub mod environment; pub mod exec; +pub mod profiler; pub mod realm; pub mod syntax; use crate::{builtins::value::ResultValue, syntax::ast::node::StatementList}; pub use crate::{ exec::{Executable, Interpreter}, + profiler::BoaProfiler, realm::Realm, syntax::{lexer::Lexer, parser::Parser}, }; @@ -72,14 +74,22 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { /// Similar to `forward`, except the current value is returned instad of the string /// If the interpreter fails parsing an error value is returned instead (error object) pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { + BoaProfiler::init(); + let main_timer = BoaProfiler::global().start_event("Main", "Main"); // Setup executor - match parser_expr(src) { + let result = match parser_expr(src) { Ok(expr) => expr.run(engine), Err(e) => { eprintln!("{}", e); std::process::exit(1); } - } + }; + + // The main_timer needs to be dropped before the BoaProfiler is. + drop(main_timer); + BoaProfiler::global().drop(); + + result } /// Create a clean Interpreter and execute the code diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs new file mode 100644 index 00000000000..9187972d2e7 --- /dev/null +++ b/boa/src/profiler.rs @@ -0,0 +1,77 @@ +#![allow(missing_copy_implementations, missing_debug_implementations)] + +use measureme::{EventId, Profiler, TimingGuard}; +use once_cell::sync::OnceCell; +use std::fmt::{self, Debug}; +use std::{ + path::Path, + thread::{current, ThreadId}, +}; + +/// MmapSerializatioSink is faster on macOS and Linux +/// but FileSerializationSink is faster on Windows +#[cfg(not(windows))] +type SerializationSink = measureme::MmapSerializationSink; +#[cfg(windows)] +type SerializationSink = measureme::FileSerializationSink; + +pub struct BoaProfiler { + profiler: Profiler, +} + +pub static mut INSTANCE: OnceCell = OnceCell::new(); + +impl BoaProfiler { + pub fn start_event(&self, label: &str, category: &str) -> TimingGuard<'_, SerializationSink> { + let kind = self.profiler.alloc_string(category); + let id = EventId::from_label(self.profiler.alloc_string(label)); + let thread_id = Self::thread_id_to_u32(current().id()); + self.profiler + .start_recording_interval_event(kind, id, thread_id) + } + + pub fn default() -> BoaProfiler { + let profiler = Profiler::new(Path::new("./my_trace")).unwrap(); + BoaProfiler { profiler } + } + + // init creates a global instance of BoaProfiler which can be used across the whole application + pub fn init() { + let profiler = Self::default(); + unsafe { + INSTANCE + .set(profiler) + .expect("Failed to set BoaProfiler globally"); + } + } + + pub fn global() -> &'static BoaProfiler { + unsafe { INSTANCE.get().expect("Profiler is not initialized") } + } + + pub fn drop(&self) { + // In order to drop the INSTANCE we need to get ownership of it, which isn't possible on a static unless you make it a mutable static + // mutating statics is unsafe, so we need to wrap it as so. + // This is actually safe though because init and drop are only called at the beginning and end of the application + unsafe { + INSTANCE + .take() + .expect("Could not take back profiler instance"); + } + } + + // Sadly we need to use the unsafe method until this is resolved: + // https://github.com/rust-lang/rust/issues/67939 + // Once `as_64()` is in stable we can do this: + // https://github.com/rust-lang/rust/pull/68531/commits/ea42b1c5b85f649728e3a3b334489bac6dce890a + // Until then our options are: use rust-nightly or use unsafe {} + fn thread_id_to_u32(tid: ThreadId) -> u32 { + unsafe { std::mem::transmute::(tid) as u32 } + } +} + +impl Debug for BoaProfiler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt("no debug implemented", f) + } +} diff --git a/boa/src/realm.rs b/boa/src/realm.rs index 44c9ac952ac..945b0987eeb 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -16,6 +16,7 @@ use crate::{ lexical_environment::LexicalEnvironment, object_environment_record::ObjectEnvironmentRecord, }, + BoaProfiler, }; use gc::{Gc, GcCell}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -32,6 +33,7 @@ pub struct Realm { impl Realm { pub fn create() -> Self { + let _timer = BoaProfiler::global().start_event("Realm::create", "Realm"); // Create brand new global object // Global has no prototype to pass None to new_obj let global = Value::new_object(None); diff --git a/boa/src/syntax/lexer/mod.rs b/boa/src/syntax/lexer/mod.rs index 25431ed4819..3d370d40298 100644 --- a/boa/src/syntax/lexer/mod.rs +++ b/boa/src/syntax/lexer/mod.rs @@ -7,9 +7,12 @@ mod tests; use crate::syntax::ast::bigint::BigInt; -use crate::syntax::ast::{ - token::{NumericLiteral, Token, TokenKind}, - Position, Punctuator, Span, +use crate::{ + syntax::ast::{ + token::{NumericLiteral, Token, TokenKind}, + Position, Punctuator, Span, + }, + BoaProfiler, }; use std::{ char::{decode_utf16, from_u32}, @@ -486,6 +489,7 @@ impl<'a> Lexer<'a> { /// } /// ``` pub fn lex(&mut self) -> Result<(), LexerError> { + let _timer = BoaProfiler::global().start_event("lex", "lexing"); loop { // Check if we've reached the end if self.preview_next().is_none() { diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index ccab9a104bc..608817cf00a 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -8,17 +8,20 @@ //! [spec]: https://tc39.es/ecma262/#sec-arrow-function-definitions use super::AssignmentExpression; -use crate::syntax::{ - ast::{ - node::{ArrowFunctionDecl, FormalParameter, Node, StatementList}, - Punctuator, TokenKind, - }, - parser::{ - error::{ErrorContext, ParseError, ParseResult}, - function::{FormalParameters, FunctionBody}, - statement::BindingIdentifier, - AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{ArrowFunctionDecl, FormalParameter, Node, StatementList}, + Punctuator, TokenKind, + }, + parser::{ + error::{ErrorContext, ParseError, ParseResult}, + function::{FormalParameters, FunctionBody}, + statement::BindingIdentifier, + AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, + }, }, + BoaProfiler, }; /// Arrow function parsing. @@ -60,6 +63,7 @@ impl TokenParser for ArrowFunction { type Output = ArrowFunctionDecl; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("ArrowFunction", "Parsing"); let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind { // CoverParenthesizedExpressionAndArrowParameterList From 45da5700834dc7512183747e478d986384544c0b Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 25 May 2020 22:17:50 +0100 Subject: [PATCH 02/15] Adding _timer to parser functions --- boa/src/realm.rs | 2 -- .../expression/assignment/conditional.rs | 14 +++++++---- .../expression/assignment/exponentiation.rs | 22 ++++++++++------- .../parser/expression/assignment/mod.rs | 14 +++++++---- .../expression/left_hand_side/arguments.rs | 13 ++++++---- .../parser/expression/left_hand_side/call.rs | 20 +++++++++------- .../expression/left_hand_side/member.rs | 20 +++++++++------- .../parser/expression/left_hand_side/mod.rs | 10 +++++--- boa/src/syntax/parser/expression/mod.rs | 10 +++++--- .../primary/array_initializer/mod.rs | 19 +++++++++------ .../expression/primary/function_expression.rs | 16 ++++++++----- .../primary/object_initializer/mod.rs | 24 +++++++++++-------- boa/src/syntax/parser/statement/block/mod.rs | 10 +++++--- .../syntax/parser/statement/break_stm/mod.rs | 10 +++++--- .../parser/statement/continue_stm/mod.rs | 10 +++++--- .../parser/statement/declaration/hoistable.rs | 14 +++++++---- .../parser/statement/declaration/lexical.rs | 20 +++++++++------- .../parser/statement/declaration/mod.rs | 10 +++++--- boa/src/syntax/parser/statement/if_stm/mod.rs | 14 +++++++---- .../statement/iteration/do_while_statement.rs | 14 +++++++---- .../statement/iteration/for_statement.rs | 24 +++++++++++-------- .../statement/iteration/while_statement.rs | 14 +++++++---- boa/src/syntax/parser/statement/mod.rs | 10 +++++++- .../syntax/parser/statement/return_stm/mod.rs | 12 +++++++--- boa/src/syntax/parser/statement/switch/mod.rs | 14 +++++++---- boa/src/syntax/parser/statement/throw/mod.rs | 12 +++++++--- .../syntax/parser/statement/try_stm/catch.rs | 20 +++++++++------- .../parser/statement/try_stm/finally.rs | 14 +++++++---- .../syntax/parser/statement/try_stm/mod.rs | 10 +++++--- boa/src/syntax/parser/statement/variable.rs | 20 +++++++++------- 30 files changed, 280 insertions(+), 156 deletions(-) diff --git a/boa/src/realm.rs b/boa/src/realm.rs index 945b0987eeb..44c9ac952ac 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -16,7 +16,6 @@ use crate::{ lexical_environment::LexicalEnvironment, object_environment_record::ObjectEnvironmentRecord, }, - BoaProfiler, }; use gc::{Gc, GcCell}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -33,7 +32,6 @@ pub struct Realm { impl Realm { pub fn create() -> Self { - let _timer = BoaProfiler::global().start_event("Realm::create", "Realm"); // Create brand new global object // Global has no prototype to pass None to new_obj let global = Value::new_object(None); diff --git a/boa/src/syntax/parser/expression/assignment/conditional.rs b/boa/src/syntax/parser/expression/assignment/conditional.rs index 2bea95cc9bb..c2584020170 100644 --- a/boa/src/syntax/parser/expression/assignment/conditional.rs +++ b/boa/src/syntax/parser/expression/assignment/conditional.rs @@ -7,12 +7,15 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator //! [spec]: https://tc39.es/ecma262/#sec-conditional-operator -use crate::syntax::{ - ast::{Node, Punctuator, TokenKind}, - parser::{ - expression::{AssignmentExpression, LogicalORExpression}, - AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{Node, Punctuator, TokenKind}, + parser::{ + expression::{AssignmentExpression, LogicalORExpression}, + AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Conditional expression parsing. @@ -54,6 +57,7 @@ impl TokenParser for ConditionalExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("Conditional", "Parsing"); // TODO: coalesce expression let lhs = LogicalORExpression::new(self.allow_in, self.allow_yield, self.allow_await) .parse(cursor)?; diff --git a/boa/src/syntax/parser/expression/assignment/exponentiation.rs b/boa/src/syntax/parser/expression/assignment/exponentiation.rs index fdc54114a3d..811bde4537d 100644 --- a/boa/src/syntax/parser/expression/assignment/exponentiation.rs +++ b/boa/src/syntax/parser/expression/assignment/exponentiation.rs @@ -7,16 +7,19 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation //! [spec]: https://tc39.es/ecma262/#sec-exp-operator -use crate::syntax::{ - ast::{ - node::{BinOp, Node}, - op::NumOp, - Keyword, Punctuator, TokenKind, - }, - parser::{ - expression::{unary::UnaryExpression, update::UpdateExpression}, - AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{BinOp, Node}, + op::NumOp, + Keyword, Punctuator, TokenKind, + }, + parser::{ + expression::{unary::UnaryExpression, update::UpdateExpression}, + AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Parses an exponentiation expression. @@ -71,6 +74,7 @@ impl TokenParser for ExponentiationExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ExponentiationExpression", "Parsing"); if Self::is_unary_expression(cursor) { return UnaryExpression::new(self.allow_yield, self.allow_await).parse(cursor); } diff --git a/boa/src/syntax/parser/expression/assignment/mod.rs b/boa/src/syntax/parser/expression/assignment/mod.rs index b45f35880db..4f71f217361 100644 --- a/boa/src/syntax/parser/expression/assignment/mod.rs +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -12,12 +12,15 @@ mod conditional; mod exponentiation; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; -use crate::syntax::{ - ast::{ - node::{Assign, BinOp, Node}, - Keyword, Punctuator, TokenKind, +use crate::{ + syntax::{ + ast::{ + node::{Assign, BinOp, Node}, + Keyword, Punctuator, TokenKind, + }, + parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }, - parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + BoaProfiler, }; pub(super) use exponentiation::ExponentiationExpression; @@ -70,6 +73,7 @@ impl TokenParser for AssignmentExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("AssignmentExpression", "Parsing"); // Arrow function let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; match next_token.kind { diff --git a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs index a5caf3a9383..4ccdde463bf 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs @@ -7,11 +7,15 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Argument //! [spec]: https://tc39.es/ecma262/#prod-Arguments -use crate::syntax::{ - ast::{Node, Punctuator, TokenKind}, - parser::{ - expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{Node, Punctuator, TokenKind}, + parser::{ + expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, + TokenParser, + }, }, + BoaProfiler, }; /// Parses a list of arguments. @@ -46,6 +50,7 @@ impl TokenParser for Arguments { type Output = Box<[Node]>; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("Arguments", "Parsing"); cursor.expect(Punctuator::OpenParen, "arguments")?; let mut args = Vec::new(); loop { diff --git a/boa/src/syntax/parser/expression/left_hand_side/call.rs b/boa/src/syntax/parser/expression/left_hand_side/call.rs index 899b1cc5490..8f32a8a8b63 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/call.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/call.rs @@ -8,15 +8,18 @@ //! [spec]: https://tc39.es/ecma262/#prod-CallExpression use super::arguments::Arguments; -use crate::syntax::{ - ast::{ - node::{Call, Node}, - Punctuator, TokenKind, - }, - parser::{ - expression::Expression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, - TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{Call, Node}, + Punctuator, TokenKind, + }, + parser::{ + expression::Expression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, + }, }, + BoaProfiler, }; /// Parses a call expression. @@ -51,6 +54,7 @@ impl TokenParser for CallExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("CallExpression", "Parsing"); let mut lhs = match cursor.peek(0) { Some(tk) if tk.kind == TokenKind::Punctuator(Punctuator::OpenParen) => { let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor)?; diff --git a/boa/src/syntax/parser/expression/left_hand_side/member.rs b/boa/src/syntax/parser/expression/left_hand_side/member.rs index de13301eaf3..842434740ae 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/member.rs @@ -6,15 +6,18 @@ //! [spec]: https://tc39.es/ecma262/#prod-MemberExpression use super::arguments::Arguments; -use crate::syntax::{ - ast::{ - node::{Call, New, Node}, - Keyword, Punctuator, TokenKind, - }, - parser::{ - expression::{primary::PrimaryExpression, Expression}, - AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{Call, New, Node}, + Keyword, Punctuator, TokenKind, + }, + parser::{ + expression::{primary::PrimaryExpression, Expression}, + AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Parses a member expression. @@ -47,6 +50,7 @@ impl TokenParser for MemberExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("MemberExpression", "Parsing"); let mut lhs = if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind == TokenKind::Keyword(Keyword::New) { diff --git a/boa/src/syntax/parser/expression/left_hand_side/mod.rs b/boa/src/syntax/parser/expression/left_hand_side/mod.rs index 737dfdbe85c..1be558b9ec9 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/mod.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/mod.rs @@ -12,9 +12,12 @@ mod call; mod member; use self::{call::CallExpression, member::MemberExpression}; -use crate::syntax::{ - ast::{Node, Punctuator, TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Node, Punctuator, TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, + }, + BoaProfiler, }; /// Parses a left hand side expression. @@ -49,6 +52,7 @@ impl TokenParser for LeftHandSideExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("LeftHandSIdeExpression", "Parsing"); // TODO: Implement NewExpression: new MemberExpression let lhs = MemberExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; match cursor.peek(0) { diff --git a/boa/src/syntax/parser/expression/mod.rs b/boa/src/syntax/parser/expression/mod.rs index 87317a6df56..0b4137bc150 100644 --- a/boa/src/syntax/parser/expression/mod.rs +++ b/boa/src/syntax/parser/expression/mod.rs @@ -18,9 +18,12 @@ mod update; use self::assignment::ExponentiationExpression; pub(super) use self::{assignment::AssignmentExpression, primary::Initializer}; use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser}; -use crate::syntax::ast::{ - node::{BinOp, Node}, - Keyword, Punctuator, TokenKind, +use crate::{ + profiler::BoaProfiler, + syntax::ast::{ + node::{BinOp, Node}, + Keyword, Punctuator, TokenKind, + }, }; // For use in the expression! macro to allow for both Punctuator and Keyword parameters. @@ -51,6 +54,7 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("Expression", "Parsing"); let mut lhs = $lower::new($( self.$low_param ),*).parse(cursor)?; while let Some(tok) = cursor.peek(0) { match tok.kind { diff --git a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs index 999af9a8c17..8bc1e74ace6 100644 --- a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -10,14 +10,18 @@ #[cfg(test)] mod tests; -use crate::syntax::{ - ast::{ - node::{ArrayDecl, Node}, - Const, Punctuator, - }, - parser::{ - expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{ArrayDecl, Node}, + Const, Punctuator, + }, + parser::{ + expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, + TokenParser, + }, }, + BoaProfiler, }; /// Parses an array literal. @@ -52,6 +56,7 @@ impl TokenParser for ArrayLiteral { type Output = ArrayDecl; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("ArrayLiteral", "Parsing"); let mut elements = Vec::new(); loop { diff --git a/boa/src/syntax/parser/expression/primary/function_expression.rs b/boa/src/syntax/parser/expression/primary/function_expression.rs index f27bcab26b8..8ebbf7347e4 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression.rs @@ -7,13 +7,16 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function //! [spec]: https://tc39.es/ecma262/#prod-FunctionExpression -use crate::syntax::{ - ast::{node::FunctionExpr, Punctuator}, - parser::{ - function::{FormalParameters, FunctionBody}, - statement::BindingIdentifier, - Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{node::FunctionExpr, Punctuator}, + parser::{ + function::{FormalParameters, FunctionBody}, + statement::BindingIdentifier, + Cursor, ParseError, TokenParser, + }, }, + BoaProfiler, }; /// Function expression parsing. @@ -31,6 +34,7 @@ impl TokenParser for FunctionExpression { type Output = FunctionExpr; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("FunctionExpression", "Parsing"); let name = BindingIdentifier::new(false, false).try_parse(cursor); cursor.expect(Punctuator::OpenParen, "function expression")?; diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs index f1c6785400f..eaeded52d51 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -10,17 +10,20 @@ #[cfg(test)] mod tests; -use crate::syntax::{ - ast::{ - node::{self, FunctionExpr, MethodDefinitionKind, Node}, - token::{Token, TokenKind}, - Punctuator, - }, - parser::{ - expression::AssignmentExpression, - function::{FormalParameters, FunctionBody}, - AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{self, FunctionExpr, MethodDefinitionKind, Node}, + token::{Token, TokenKind}, + Punctuator, + }, + parser::{ + expression::AssignmentExpression, + function::{FormalParameters, FunctionBody}, + AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Parses an object literal. @@ -55,6 +58,7 @@ impl TokenParser for ObjectLiteral { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ObjectLiteral", "Parsing"); let mut elements = Vec::new(); loop { diff --git a/boa/src/syntax/parser/statement/block/mod.rs b/boa/src/syntax/parser/statement/block/mod.rs index 3db21706cb9..843cb0341ea 100644 --- a/boa/src/syntax/parser/statement/block/mod.rs +++ b/boa/src/syntax/parser/statement/block/mod.rs @@ -11,9 +11,12 @@ mod tests; use super::StatementList; -use crate::syntax::{ - ast::{node, Punctuator, TokenKind}, - parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, +use crate::{ + profiler::BoaProfiler, + syntax::{ + ast::{node, Punctuator, TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, + }, }; /// A `BlockStatement` is equivalent to a `Block`. @@ -59,6 +62,7 @@ impl TokenParser for Block { type Output = node::Block; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("Block", "Parsing"); cursor.expect(Punctuator::OpenBlock, "block")?; if let Some(tk) = cursor.peek(0) { if tk.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { diff --git a/boa/src/syntax/parser/statement/break_stm/mod.rs b/boa/src/syntax/parser/statement/break_stm/mod.rs index d203097b059..9c7b9e14627 100644 --- a/boa/src/syntax/parser/statement/break_stm/mod.rs +++ b/boa/src/syntax/parser/statement/break_stm/mod.rs @@ -11,9 +11,12 @@ mod tests; use super::LabelIdentifier; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, + }, + BoaProfiler, }; /// Break statement parsing @@ -48,6 +51,7 @@ impl TokenParser for BreakStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("BreakStatement", "Parsing"); cursor.expect(Keyword::Break, "break statement")?; let label = if let (true, tok) = cursor.peek_semicolon(false) { diff --git a/boa/src/syntax/parser/statement/continue_stm/mod.rs b/boa/src/syntax/parser/statement/continue_stm/mod.rs index 7da0b5414cb..8614a43c7e3 100644 --- a/boa/src/syntax/parser/statement/continue_stm/mod.rs +++ b/boa/src/syntax/parser/statement/continue_stm/mod.rs @@ -11,9 +11,12 @@ mod tests; use super::LabelIdentifier; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, + }, + BoaProfiler, }; /// For statement parsing @@ -48,6 +51,7 @@ impl TokenParser for ContinueStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ContinueStatement", "Parsing"); cursor.expect(Keyword::Continue, "continue statement")?; let label = if let (true, tok) = cursor.peek_semicolon(false) { diff --git a/boa/src/syntax/parser/statement/declaration/hoistable.rs b/boa/src/syntax/parser/statement/declaration/hoistable.rs index 44a942a908e..37185e6377f 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable.rs @@ -5,12 +5,15 @@ //! //! [spec]: https://tc39.es/ecma262/#prod-HoistableDeclaration -use crate::syntax::{ - ast::{node::FunctionDecl, Keyword, Node, Punctuator}, - parser::{ - function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, - AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{node::FunctionDecl, Keyword, Node, Punctuator}, + parser::{ + function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Hoistable declaration parsing. @@ -46,6 +49,7 @@ impl TokenParser for HoistableDeclaration { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("HoistableDeclaration", "Parsing"); // TODO: check for generators and async functions + generators FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default) .parse(cursor) diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index ee9b3fd3f11..25dfec3a271 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -7,15 +7,18 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations -use crate::syntax::{ - ast::{ - node::{ConstDecl, ConstDeclList, LetDecl, LetDeclList, Node}, - Keyword, Punctuator, TokenKind, - }, - parser::{ - expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, - Cursor, ParseError, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{ConstDecl, ConstDeclList, LetDecl, LetDeclList, Node}, + Keyword, Punctuator, TokenKind, + }, + parser::{ + expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, + Cursor, ParseError, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Parses a lexical declaration. @@ -51,6 +54,7 @@ impl TokenParser for LexicalDeclaration { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("LexicalDeclaration", "Parsing"); let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; match tok.kind { diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index c8f44aee9f7..c17cebe90e1 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/mod.rs @@ -13,9 +13,12 @@ mod lexical; mod tests; use self::{hoistable::HoistableDeclaration, lexical::LexicalDeclaration}; -use crate::syntax::{ - ast::{Keyword, Node, TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Keyword, Node, TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + }, + BoaProfiler, }; /// Parses a declaration. @@ -47,6 +50,7 @@ impl TokenParser for Declaration { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("Declaration", "Parsing"); let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; match tok.kind { diff --git a/boa/src/syntax/parser/statement/if_stm/mod.rs b/boa/src/syntax/parser/statement/if_stm/mod.rs index 7d7f95785bb..68a2f065b8e 100644 --- a/boa/src/syntax/parser/statement/if_stm/mod.rs +++ b/boa/src/syntax/parser/statement/if_stm/mod.rs @@ -2,12 +2,15 @@ mod tests; use super::Statement; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{ - expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, - TokenParser, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{ + expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, + TokenParser, + }, }, + BoaProfiler, }; /// If statement parsing. @@ -47,6 +50,7 @@ impl TokenParser for IfStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("IfStatement", "Parsing"); cursor.expect(Keyword::If, "if statement")?; cursor.expect(Punctuator::OpenParen, "if statement")?; diff --git a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs index 5b71869af4e..c42c37a0ab5 100644 --- a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs @@ -7,12 +7,15 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while //! [spec]: https://tc39.es/ecma262/#sec-do-while-statement -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{ - expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, - ParseError, ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{ + expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, + Cursor, ParseError, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Do...while statement parsing @@ -54,6 +57,7 @@ impl TokenParser for DoWhileStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("DoWhileStatement", "Parsing"); cursor.expect(Keyword::Do, "do while statement")?; let body = diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index c5d2b340a06..347c7ad17d7 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -7,17 +7,20 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for //! [spec]: https://tc39.es/ecma262/#sec-for-statement -use crate::syntax::{ - ast::{ - node::{ForLoop, Node}, - Const, Keyword, Punctuator, TokenKind, - }, - parser::{ - expression::Expression, - statement::declaration::Declaration, - statement::{variable::VariableDeclarationList, Statement}, - AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{ForLoop, Node}, + Const, Keyword, Punctuator, TokenKind, + }, + parser::{ + expression::Expression, + statement::declaration::Declaration, + statement::{variable::VariableDeclarationList, Statement}, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, + }, }, + BoaProfiler, }; /// For statement parsing @@ -59,6 +62,7 @@ impl TokenParser for ForStatement { type Output = ForLoop; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing"); cursor.expect(Keyword::For, "for statement")?; cursor.expect(Punctuator::OpenParen, "for statement")?; diff --git a/boa/src/syntax/parser/statement/iteration/while_statement.rs b/boa/src/syntax/parser/statement/iteration/while_statement.rs index dd8040ae248..e1e3cb0307c 100644 --- a/boa/src/syntax/parser/statement/iteration/while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/while_statement.rs @@ -1,9 +1,12 @@ -use crate::syntax::{ - ast::{Keyword, Node, Punctuator}, - parser::{ - expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, - ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator}, + parser::{ + expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, + Cursor, ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// While statement parsing @@ -45,6 +48,7 @@ impl TokenParser for WhileStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("WhileStatement", "Parsing"); cursor.expect(Keyword::While, "while statement")?; cursor.expect(Punctuator::OpenParen, "while statement")?; diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index cf845e8a410..a4dd078fc5c 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -36,7 +36,10 @@ use super::{ expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }; -use crate::syntax::ast::{node, Keyword, Node, Punctuator, TokenKind}; +use crate::{ + syntax::ast::{node, Keyword, Node, Punctuator, TokenKind}, + BoaProfiler, +}; /// Statement parsing. /// @@ -90,6 +93,7 @@ impl TokenParser for Statement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("Statement", "Parsing"); // TODO: add BreakableStatement and divide Whiles, fors and so on to another place. let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; @@ -197,6 +201,7 @@ impl TokenParser for StatementList { type Output = node::StatementList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("StatementList", "Parsing"); let mut items = Vec::new(); loop { @@ -270,6 +275,7 @@ impl TokenParser for StatementListItem { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("StatementListItem", "Parsing"); let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; match tok.kind { @@ -315,6 +321,7 @@ impl TokenParser for ExpressionStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ExpressionStatement", "Parsing"); // TODO: lookahead let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; @@ -364,6 +371,7 @@ impl TokenParser for BindingIdentifier { type Output = Box; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("BindingIdentifier", "Parsing"); // TODO: strict mode. let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; diff --git a/boa/src/syntax/parser/statement/return_stm/mod.rs b/boa/src/syntax/parser/statement/return_stm/mod.rs index 096b3be17ae..e3700ca8720 100644 --- a/boa/src/syntax/parser/statement/return_stm/mod.rs +++ b/boa/src/syntax/parser/statement/return_stm/mod.rs @@ -1,9 +1,14 @@ #[cfg(test)] mod tests; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{ + expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + }, + }, + BoaProfiler, }; /// Return statement parsing @@ -38,6 +43,7 @@ impl TokenParser for ReturnStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ReturnStatement", "Parsing"); cursor.expect(Keyword::Return, "return statement")?; if let (true, tok) = cursor.peek_semicolon(false) { diff --git a/boa/src/syntax/parser/statement/switch/mod.rs b/boa/src/syntax/parser/statement/switch/mod.rs index c8416149a03..75fd9209793 100644 --- a/boa/src/syntax/parser/statement/switch/mod.rs +++ b/boa/src/syntax/parser/statement/switch/mod.rs @@ -1,12 +1,15 @@ #[cfg(test)] mod tests; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator}, - parser::{ - expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, - ParseResult, TokenParser, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator}, + parser::{ + expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, + ParseResult, TokenParser, + }, }, + BoaProfiler, }; /// Switch statement parsing. @@ -44,6 +47,7 @@ impl TokenParser for SwitchStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("SwitchStatement", "Parsing"); cursor.expect(Keyword::Switch, "switch statement")?; cursor.expect(Punctuator::OpenParen, "switch statement")?; diff --git a/boa/src/syntax/parser/statement/throw/mod.rs b/boa/src/syntax/parser/statement/throw/mod.rs index 4d788424e7e..aacc3d2216e 100644 --- a/boa/src/syntax/parser/statement/throw/mod.rs +++ b/boa/src/syntax/parser/statement/throw/mod.rs @@ -1,9 +1,14 @@ #[cfg(test)] mod tests; -use crate::syntax::{ - ast::{Keyword, Node, Punctuator, TokenKind}, - parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +use crate::{ + syntax::{ + ast::{Keyword, Node, Punctuator, TokenKind}, + parser::{ + expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + }, + }, + BoaProfiler, }; /// For statement parsing @@ -38,6 +43,7 @@ impl TokenParser for ThrowStatement { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let _timer = BoaProfiler::global().start_event("ThrowStatement", "Parsing"); cursor.expect(Keyword::Throw, "throw statement")?; cursor.peek_expect_no_lineterminator(0)?; diff --git a/boa/src/syntax/parser/statement/try_stm/catch.rs b/boa/src/syntax/parser/statement/try_stm/catch.rs index 816db052adc..1e818400a0b 100644 --- a/boa/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa/src/syntax/parser/statement/try_stm/catch.rs @@ -1,12 +1,15 @@ -use crate::syntax::{ - ast::{ - node::{self, Identifier}, - Keyword, Punctuator, - }, - parser::{ - statement::{block::Block, BindingIdentifier}, - AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{self, Identifier}, + Keyword, Punctuator, + }, + parser::{ + statement::{block::Block, BindingIdentifier}, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, + }, }, + BoaProfiler, }; /// Catch parsing @@ -44,6 +47,7 @@ impl TokenParser for Catch { type Output = node::Catch; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("Catch", "Parsing"); cursor.expect(Keyword::Catch, "try statement")?; let catch_param = if cursor.next_if(Punctuator::OpenParen).is_some() { let catch_param = diff --git a/boa/src/syntax/parser/statement/try_stm/finally.rs b/boa/src/syntax/parser/statement/try_stm/finally.rs index a6c9bebbda9..ac4f39a395f 100644 --- a/boa/src/syntax/parser/statement/try_stm/finally.rs +++ b/boa/src/syntax/parser/statement/try_stm/finally.rs @@ -1,9 +1,12 @@ -use crate::syntax::{ - ast::{node, Keyword}, - parser::{ - statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, - TokenParser, +use crate::{ + syntax::{ + ast::{node, Keyword}, + parser::{ + statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, + TokenParser, + }, }, + BoaProfiler, }; /// Finally parsing @@ -41,6 +44,7 @@ impl TokenParser for Finally { type Output = node::Finally; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("Finally", "Parsing"); cursor.expect(Keyword::Finally, "try statement")?; Ok( Block::new(self.allow_yield, self.allow_await, self.allow_return) diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index 2e7c4902477..041148f329b 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -7,9 +7,12 @@ mod tests; use self::catch::Catch; use self::finally::Finally; use super::block::Block; -use crate::syntax::{ - ast::{node::Try, Keyword, TokenKind}, - parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, +use crate::{ + syntax::{ + ast::{node::Try, Keyword, TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, + }, + BoaProfiler, }; /// Try...catch statement parsing @@ -47,6 +50,7 @@ impl TokenParser for TryStatement { type Output = Try; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("TryStatement", "Parsing"); // TRY cursor.expect(Keyword::Try, "try statement")?; diff --git a/boa/src/syntax/parser/statement/variable.rs b/boa/src/syntax/parser/statement/variable.rs index a5f750140d9..904b012f0ce 100644 --- a/boa/src/syntax/parser/statement/variable.rs +++ b/boa/src/syntax/parser/statement/variable.rs @@ -1,13 +1,16 @@ // use super::lexical_declaration_continuation; -use crate::syntax::{ - ast::{ - node::{VarDecl, VarDeclList}, - Keyword, Punctuator, TokenKind, - }, - parser::{ - expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, - Cursor, ParseError, TokenParser, +use crate::{ + syntax::{ + ast::{ + node::{VarDecl, VarDeclList}, + Keyword, Punctuator, TokenKind, + }, + parser::{ + expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, + Cursor, ParseError, TokenParser, + }, }, + BoaProfiler, }; /// Variable statement parsing. @@ -44,6 +47,7 @@ impl TokenParser for VariableStatement { type Output = VarDeclList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let _timer = BoaProfiler::global().start_event("VariableStatement", "Parsing"); cursor.expect(Keyword::Var, "variable statement")?; let decl_list = From 1c4ffd38f68526108c5725ed6485a61f397119ce Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 25 May 2020 22:58:29 +0100 Subject: [PATCH 03/15] timing more places --- boa/src/builtins/array/mod.rs | 2 ++ boa/src/builtins/bigint/mod.rs | 2 ++ boa/src/builtins/boolean/mod.rs | 2 ++ boa/src/builtins/console/mod.rs | 2 ++ boa/src/builtins/error/mod.rs | 2 ++ boa/src/builtins/error/range.rs | 2 ++ boa/src/builtins/function/mod.rs | 2 ++ boa/src/builtins/json/mod.rs | 3 ++- boa/src/builtins/math/mod.rs | 2 ++ boa/src/builtins/number/mod.rs | 2 ++ boa/src/builtins/object/mod.rs | 2 ++ boa/src/builtins/regexp/mod.rs | 2 ++ boa/src/builtins/string/mod.rs | 2 ++ boa/src/builtins/symbol/mod.rs | 2 ++ boa/src/exec/array/mod.rs | 2 ++ boa/src/exec/block/mod.rs | 2 ++ boa/src/exec/declaration/mod.rs | 2 ++ boa/src/exec/expression/mod.rs | 2 ++ boa/src/exec/iteration/mod.rs | 2 ++ boa/src/exec/mod.rs | 2 ++ boa/src/exec/operator/mod.rs | 2 ++ boa/src/exec/statement_list.rs | 2 ++ boa/src/exec/try_node/mod.rs | 2 ++ boa/src/lib.rs | 1 - boa/src/profiler.rs | 2 +- boa/src/realm.rs | 3 +++ 26 files changed, 50 insertions(+), 3 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 4cf03d37901..9fa9ca73ee8 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -21,6 +21,7 @@ use crate::{ value::{same_value_zero, ResultValue, Value, ValueData}, }, exec::Interpreter, + BoaProfiler, }; use std::{ borrow::Borrow, @@ -1042,6 +1043,7 @@ impl Array { /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("array", "init"); global.set_field("Array", Self::create(global)); } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index f01b4573c6a..6e4ac8f5310 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -20,6 +20,7 @@ use crate::{ }, exec::Interpreter, syntax::ast::bigint::BigInt as AstBigInt, + BoaProfiler, }; #[cfg(test)] @@ -130,6 +131,7 @@ impl BigInt { /// Initialise the `BigInt` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("bigint", "init"); global.set_field("BigInt", Self::create(global)); } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 40b7de056dd..76012a40e7d 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -19,6 +19,7 @@ use crate::{ value::{ResultValue, Value, ValueData}, }, exec::Interpreter, + BoaProfiler, }; use std::{borrow::Borrow, ops::Deref}; @@ -128,6 +129,7 @@ impl Boolean { /// Initialise the `Boolean` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("boolean", "init"); global.set_field("Boolean", Self::create(global)); } } diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index b181e217458..2d678217ac7 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -23,6 +23,7 @@ use crate::{ value::{display_obj, ResultValue, Value}, }, exec::Interpreter, + BoaProfiler, }; use rustc_hash::FxHashMap; use std::time::SystemTime; @@ -520,5 +521,6 @@ pub fn create(global: &Value) -> Value { /// Initialise the `console` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("console", "init"); global.set_field("console", create(global)); } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 328edc50bfb..32eda95b3af 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -17,6 +17,7 @@ use crate::{ value::{ResultValue, Value}, }, exec::Interpreter, + profiler::BoaProfiler, }; // mod eval; @@ -80,6 +81,7 @@ impl Error { /// Initialise the global object with the `Error` object. pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("error", "init"); global.set_field("Error", Self::create(global)); } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 34e4c88ff77..ce0c4552b90 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -17,6 +17,7 @@ use crate::{ value::{ResultValue, Value}, }, exec::Interpreter, + profiler::BoaProfiler, }; /// JavaScript `RangeError` impleentation. @@ -91,6 +92,7 @@ impl RangeError { /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("rangeerror", "init"); global.set_field("RangeError", Self::create(global)); } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 2886b97051f..999f6ab61e7 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -22,6 +22,7 @@ use crate::{ environment::lexical_environment::{new_function_environment, Environment}, exec::{Executable, Interpreter}, syntax::ast::node::{FormalParameter, StatementList}, + BoaProfiler, }; use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt::{self, Debug}; @@ -447,5 +448,6 @@ where /// Initialise the `Function` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("function", "init"); global.set_field("Function", create(global)); } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index ad7cd378fd0..673e9f1b85c 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -19,7 +19,7 @@ use crate::builtins::{ property::Property, value::{ResultValue, Value}, }; -use crate::exec::Interpreter; +use crate::{exec::Interpreter, BoaProfiler}; use serde_json::{self, Value as JSONValue}; #[cfg(test)] @@ -142,5 +142,6 @@ pub fn create(global: &Value) -> Value { /// Initialise the `JSON` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("json", "init"); global.set_field("JSON", create(global)); } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 04591cad309..cc432ba20dc 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -17,6 +17,7 @@ use crate::{ value::{ResultValue, Value}, }, exec::Interpreter, + BoaProfiler, }; use rand::random; use std::f64; @@ -553,5 +554,6 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Math` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("math", "init"); global.set_field("Math", create(global)); } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 05cde1dcf13..99f8b38cddc 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -27,6 +27,7 @@ use crate::{ RangeError, }, exec::Interpreter, + BoaProfiler, }; use num_traits::float::FloatCore; use std::{borrow::Borrow, f64, ops::Deref}; @@ -421,6 +422,7 @@ impl Number { /// Initialise the `Number` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("number", "init"); global.set_field("Number", Self::create(global)); } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 912d15203cd..b3e8cd57b62 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -20,6 +20,7 @@ use crate::{ value::{same_value, ResultValue, Value, ValueData}, }, exec::Interpreter, + BoaProfiler, }; use gc::{unsafe_empty_trace, Finalize, Trace}; use rustc_hash::FxHashMap; @@ -633,5 +634,6 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Object` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("object", "init"); global.set_field("Object", create(global)); } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index af82d3bfe80..64c88eda8a4 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -21,6 +21,7 @@ use crate::{ value::{ResultValue, Value, ValueData}, }, exec::Interpreter, + BoaProfiler, }; #[cfg(test)] @@ -491,6 +492,7 @@ impl RegExp { /// Initialise the `RegExp` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("regexp", "init"); global.set_field("RegExp", Self::create(global)); } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 06ba55460b1..4197e3dfda5 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -21,6 +21,7 @@ use crate::{ RegExp, }, exec::Interpreter, + BoaProfiler, }; use regex::Regex; use std::string::String as StdString; @@ -1077,6 +1078,7 @@ impl String { /// Initialise the `String` object on the global object. #[inline] pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("string", "init"); global.set_field("String", Self::create(global)); } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index e71a5dc09c3..bbf915bfb44 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -28,6 +28,7 @@ use crate::{ value::{ResultValue, Value, ValueData}, }, exec::Interpreter, + BoaProfiler, }; use gc::{Gc, GcCell}; use rand::random; @@ -100,5 +101,6 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Symbol` object on the global object. #[inline] pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("symbol", "init"); global.set_field("Symbol", create(global)); } diff --git a/boa/src/exec/array/mod.rs b/boa/src/exec/array/mod.rs index 548acd461f3..1f0374f9bb9 100644 --- a/boa/src/exec/array/mod.rs +++ b/boa/src/exec/array/mod.rs @@ -4,10 +4,12 @@ use super::{Executable, Interpreter}; use crate::{ builtins::{Array, ResultValue}, syntax::ast::node::{ArrayDecl, Node}, + BoaProfiler, }; impl Executable for ArrayDecl { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("ArrayDecl", "exec"); let array = Array::new_array(interpreter)?; let mut elements = Vec::new(); for elem in self.as_ref() { diff --git a/boa/src/exec/block/mod.rs b/boa/src/exec/block/mod.rs index c592a05ab92..deec6e432bc 100644 --- a/boa/src/exec/block/mod.rs +++ b/boa/src/exec/block/mod.rs @@ -5,10 +5,12 @@ use crate::{ builtins::value::{ResultValue, Value}, environment::lexical_environment::new_declarative_environment, syntax::ast::node::Block, + BoaProfiler, }; impl Executable for Block { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("Block", "exec"); { let env = &mut interpreter.realm_mut().environment; env.push(new_declarative_environment(Some( diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs index 0ab755872ef..192a8a168fa 100644 --- a/boa/src/exec/declaration/mod.rs +++ b/boa/src/exec/declaration/mod.rs @@ -10,10 +10,12 @@ use crate::{ syntax::ast::node::{ ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList, }, + BoaProfiler, }; impl Executable for FunctionDecl { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec"); let val = interpreter.create_function( self.parameters().to_vec(), self.body().to_vec(), diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 57fc661ca7a..7087dfe14e0 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -7,10 +7,12 @@ use crate::{ value::{ResultValue, Value, ValueData}, }, syntax::ast::node::{Call, New, Node}, + BoaProfiler, }; impl Executable for Call { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("Call", "exec"); let (mut this, func) = match self.expr() { Node::GetConstField(ref obj, ref field) => { let mut obj = obj.run(interpreter)?; diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index ab1f8308d2f..dd4f728629e 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -5,11 +5,13 @@ use crate::{ builtins::value::{ResultValue, Value}, environment::lexical_environment::new_declarative_environment, syntax::ast::node::ForLoop, + BoaProfiler, }; impl Executable for ForLoop { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { // Create the block environment. + let _timer = BoaProfiler::global().start_event("ForLoop", "exec"); { let env = &mut interpreter.realm_mut().environment; env.push(new_declarative_environment(Some( diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 4f0c8266818..d92e2a8f2e9 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -26,6 +26,7 @@ use crate::{ constant::Const, node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition, StatementList}, }, + BoaProfiler, }; use std::{borrow::Borrow, ops::Deref}; @@ -373,6 +374,7 @@ impl Interpreter { impl Executable for Node { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("Executable", "exec"); match *self { Node::Const(Const::Null) => Ok(Value::null()), Node::Const(Const::Undefined) => Ok(Value::undefined()), diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index d4cb97495cb..29cef4bb504 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -8,11 +8,13 @@ use crate::{ node::{Assign, BinOp, Node, UnaryOp}, op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, }, + BoaProfiler, }; use std::borrow::BorrowMut; impl Executable for Assign { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("Assign", "exec"); let val = self.rhs().run(interpreter)?; match self.lhs() { Node::Identifier(ref name) => { diff --git a/boa/src/exec/statement_list.rs b/boa/src/exec/statement_list.rs index 31558e383f1..4863232171e 100644 --- a/boa/src/exec/statement_list.rs +++ b/boa/src/exec/statement_list.rs @@ -4,10 +4,12 @@ use super::{Executable, Interpreter}; use crate::{ builtins::value::{ResultValue, Value}, syntax::ast::node::StatementList, + BoaProfiler, }; impl Executable for StatementList { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("StatementList", "exec"); let mut obj = Value::null(); for (i, item) in self.statements().iter().enumerate() { let val = item.run(interpreter)?; diff --git a/boa/src/exec/try_node/mod.rs b/boa/src/exec/try_node/mod.rs index 8b45e46e1a8..cb6b25a43f6 100644 --- a/boa/src/exec/try_node/mod.rs +++ b/boa/src/exec/try_node/mod.rs @@ -5,6 +5,7 @@ use crate::{ builtins::value::ResultValue, environment::lexical_environment::{new_declarative_environment, VariableScope}, syntax::ast::node::Try, + BoaProfiler, }; #[cfg(test)] @@ -12,6 +13,7 @@ mod tests; impl Executable for Try { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let _timer = BoaProfiler::global().start_event("Try", "exec"); let res = self.block().run(interpreter).map_or_else( |err| { if let Some(catch) = self.catch() { diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 9e8f610cb5b..7c9b70ac9db 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -74,7 +74,6 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { /// Similar to `forward`, except the current value is returned instad of the string /// If the interpreter fails parsing an error value is returned instead (error object) pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { - BoaProfiler::init(); let main_timer = BoaProfiler::global().start_event("Main", "Main"); // Setup executor let result = match parser_expr(src) { diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 9187972d2e7..4341d2fb41a 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -46,7 +46,7 @@ impl BoaProfiler { } pub fn global() -> &'static BoaProfiler { - unsafe { INSTANCE.get().expect("Profiler is not initialized") } + unsafe { INSTANCE.get_or_init(Self::default) } } pub fn drop(&self) { diff --git a/boa/src/realm.rs b/boa/src/realm.rs index 44c9ac952ac..b6cfc4decf3 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -16,6 +16,7 @@ use crate::{ lexical_environment::LexicalEnvironment, object_environment_record::ObjectEnvironmentRecord, }, + BoaProfiler, }; use gc::{Gc, GcCell}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -32,6 +33,7 @@ pub struct Realm { impl Realm { pub fn create() -> Self { + let _timer = BoaProfiler::global().start_event("Realm::create", "realm"); // Create brand new global object // Global has no prototype to pass None to new_obj let global = Value::new_object(None); @@ -53,6 +55,7 @@ impl Realm { // Sets up the default global objects within Global fn create_instrinsics(&self) { + let _timer = BoaProfiler::global().start_event("create_instrinsics", "realm"); let global = &self.global_obj; // Create intrinsics, add global objects here builtins::init(global); From 159fddc9fe74ca5005ff9de33b82e499027c4da0 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 25 May 2020 23:14:43 +0100 Subject: [PATCH 04/15] add empty implementation --- boa/src/environment/lexical_environment.rs | 3 +++ boa/src/profiler.rs | 23 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 65919b446ec..8e32c099457 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -14,6 +14,7 @@ use crate::{ global_environment_record::GlobalEnvironmentRecord, object_environment_record::ObjectEnvironmentRecord, }, + BoaProfiler, }; use gc::{Gc, GcCell}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -79,6 +80,7 @@ impl error::Error for EnvironmentError { impl LexicalEnvironment { pub fn new(global: Value) -> Self { + let _timer = BoaProfiler::global().start_event("LexicalEnvironment::new", "env"); let global_env = new_global_environment(global.clone(), global); let mut lexical_env = Self { environment_stack: VecDeque::new(), @@ -219,6 +221,7 @@ impl LexicalEnvironment { } pub fn new_declarative_environment(env: Option) -> Environment { + let _timer = BoaProfiler::global().start_event("new_declarative_environment", "env"); let boxed_env = Box::new(DeclarativeEnvironmentRecord { env_rec: FxHashMap::default(), outer_env: env, diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 4341d2fb41a..18bb764d5f8 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -14,13 +14,13 @@ use std::{ type SerializationSink = measureme::MmapSerializationSink; #[cfg(windows)] type SerializationSink = measureme::FileSerializationSink; - +#[cfg(feature = "profiler")] pub struct BoaProfiler { profiler: Profiler, } pub static mut INSTANCE: OnceCell = OnceCell::new(); - +#[cfg(feature = "profiler")] impl BoaProfiler { pub fn start_event(&self, label: &str, category: &str) -> TimingGuard<'_, SerializationSink> { let kind = self.profiler.alloc_string(category); @@ -75,3 +75,22 @@ impl Debug for BoaProfiler { Debug::fmt("no debug implemented", f) } } + +#[cfg(not(feature = "profiler"))] +pub struct BoaProfiler; + +#[allow(clippy::unused_unit)] +#[cfg(not(feature = "profiler"))] +impl BoaProfiler { + pub fn start_event(&self, _label: &str, _category: &str) -> () { + () + } + + pub fn drop(&self) { + () + } + + pub fn global() -> BoaProfiler { + BoaProfiler + } +} From de2b7a1b133310316a59d2ce7783d6ff6121c660 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Tue, 26 May 2020 09:07:03 +0100 Subject: [PATCH 05/15] let_unit_value will need to be allowed --- boa/src/lib.rs | 1 + boa/src/profiler.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 7c9b70ac9db..baf747f80a0 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -30,6 +30,7 @@ clippy::must_use_candidate, clippy::missing_errors_doc, clippy::as_conversions, + clippy::let_unit_value, missing_doc_code_examples )] diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 18bb764d5f8..043ddaf7e87 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -20,6 +20,7 @@ pub struct BoaProfiler { } pub static mut INSTANCE: OnceCell = OnceCell::new(); + #[cfg(feature = "profiler")] impl BoaProfiler { pub fn start_event(&self, label: &str, category: &str) -> TimingGuard<'_, SerializationSink> { From 74e52dcd186c456c614274cc7fd2f6176ce86bba Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 11:39:32 +0100 Subject: [PATCH 06/15] only run on profiler feature --- boa/Cargo.toml | 7 +++++-- boa/src/profiler.rs | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 8f87939e2c6..6343ed6f9df 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -10,6 +10,9 @@ license = "Unlicense/MIT" exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"] edition = "2018" +[features] +profiler = ["measureme", "once_cell"] + [dependencies] gc = { version = "0.3.5", features = ["derive"] } serde_json = "1.0.53" @@ -21,9 +24,9 @@ num-bigint = { version = "0.2.6", features = ["serde"] } # Optional Dependencies serde = { version = "1.0.110", features = ["derive"], optional = true } +measureme = { version = "0.7.1", optional = true } +once_cell = { version = "1.4.0", optional = true } bitflags = "1.2.1" -measureme = { version = "0.7.1" } -once_cell = "1.4.0" [dev-dependencies] criterion = "0.3.2" diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 043ddaf7e87..4204a628ae1 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -1,8 +1,11 @@ #![allow(missing_copy_implementations, missing_debug_implementations)] +#[cfg(feature = "profiler")] use measureme::{EventId, Profiler, TimingGuard}; +#[cfg(feature = "profiler")] use once_cell::sync::OnceCell; use std::fmt::{self, Debug}; +#[cfg(feature = "profiler")] use std::{ path::Path, thread::{current, ThreadId}, @@ -11,14 +14,17 @@ use std::{ /// MmapSerializatioSink is faster on macOS and Linux /// but FileSerializationSink is faster on Windows #[cfg(not(windows))] +#[cfg(feature = "profiler")] type SerializationSink = measureme::MmapSerializationSink; #[cfg(windows)] +#[cfg(feature = "profiler")] type SerializationSink = measureme::FileSerializationSink; #[cfg(feature = "profiler")] pub struct BoaProfiler { profiler: Profiler, } +#[cfg(feature = "profiler")] pub static mut INSTANCE: OnceCell = OnceCell::new(); #[cfg(feature = "profiler")] From abd0179e875a1f85763be2bd425deedfcae52074 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 12:21:04 +0100 Subject: [PATCH 07/15] measure make_builtin_fn --- .vscode/tasks.json | 18 ++++++++++++++++++ boa/src/builtins/function/mod.rs | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 49d85082624..aec1c2cd7b1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -20,6 +20,24 @@ "clear": true } }, + { + "type": "process", + "label": "Cargo Run (Profiler)", + "command": "cargo", + "args": ["run", "--features", "Boa/profiler", "../tests/js/test.js"], + "problemMatcher": ["$rustc"], + "group": { + "kind": "build", + "isDefault": true + }, + "options": { + "env": { "RUST_BACKTRACE": "full" }, + "cwd": "${workspaceFolder}/boa_cli" + }, + "presentation": { + "clear": true + } + }, { "type": "process", "label": "Get Tokens", diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 999f6ab61e7..9869bd9dd9e 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -434,6 +434,9 @@ pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, where N: Into, { + let name_copy: String = name.into(); + let label = format!("{}{}", String::from("make_builtin_fn: "), &name_copy); + let _timer = BoaProfiler::global().start_event(&label, "init"); let func = Function::builtin(Vec::new(), function); let mut new_func = Object::function(); @@ -442,7 +445,7 @@ where let new_func_obj = Value::from(new_func); new_func_obj.set_field("length", length); - parent.set_field(name.into(), new_func_obj); + parent.set_field(Value::from(name_copy), new_func_obj); } /// Initialise the `Function` object on the global object. From f1ce8f7bdbfa0b65cb0074400cd74c9040e5fb37 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 19:36:51 +0100 Subject: [PATCH 08/15] adding more timers --- boa/src/builtins/function/mod.rs | 2 ++ boa/src/builtins/math/mod.rs | 1 + boa/src/builtins/object/internal_methods_trait.rs | 13 +++++++++---- boa/src/builtins/object/mod.rs | 3 +++ boa/src/builtins/value/conversions.rs | 2 ++ boa/src/builtins/value/mod.rs | 7 ++++++- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 9869bd9dd9e..f719bd064fd 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -143,6 +143,7 @@ impl Function { where P: Into>, { + let _timer = BoaProfiler::global().start_event("function::builtin", "function"); Self::new( parameter_list.into(), None, @@ -164,6 +165,7 @@ impl Function { interpreter: &mut Interpreter, this_obj: &mut Value, ) -> ResultValue { + let _timer = BoaProfiler::global().start_event("function::call", "function"); if self.callable { match self.body { FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index cc432ba20dc..775642ae299 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -508,6 +508,7 @@ pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue /// Create a new `Math` object pub fn create(global: &Value) -> Value { + let _timer = BoaProfiler::global().start_event("math:create", "init"); let math = Value::new_object(Some(global)); math.set_field("E", Value::from(f64::consts::E)); diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods_trait.rs index d99531bed85..47b21dd2827 100644 --- a/boa/src/builtins/object/internal_methods_trait.rs +++ b/boa/src/builtins/object/internal_methods_trait.rs @@ -5,10 +5,13 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -use crate::builtins::{ - object::{Object, INSTANCE_PROTOTYPE}, - property::Property, - value::{same_value, Value, ValueData}, +use crate::{ + builtins::{ + object::{Object, INSTANCE_PROTOTYPE}, + property::Property, + value::{same_value, Value, ValueData}, + }, + BoaProfiler, }; use std::borrow::Borrow; use std::ops::Deref; @@ -131,6 +134,7 @@ pub trait ObjectInternalMethods { /// [[Set]] /// fn set(&mut self, field: Value, val: Value) -> bool { + let _timer = BoaProfiler::global().start_event("Object::set", "object"); // [1] debug_assert!(Property::is_property_key(&field)); @@ -168,6 +172,7 @@ pub trait ObjectInternalMethods { } fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); let mut current = self.get_own_property(&Value::from(property_key.to_string())); let extensible = self.is_extensible(); diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index b3e8cd57b62..a366fe5c034 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -129,6 +129,7 @@ impl ObjectInternalMethods for Object { /// Helper function to get an immutable internal slot or Null fn get_internal_slot(&self, name: &str) -> Value { + let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); match self.internal_slots.get(name) { Some(v) => v.clone(), None => Value::null(), @@ -144,6 +145,7 @@ impl ObjectInternalMethods for Object { /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p fn get_own_property(&self, prop: &Value) -> Property { + let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); debug_assert!(Property::is_property_key(prop)); // Prop could either be a String or Symbol match *(*prop) { @@ -350,6 +352,7 @@ impl Object { /// Return a new ObjectData struct, with `kind` set to Ordinary pub fn function() -> Self { + let _timer = BoaProfiler::global().start_event("Object::Function", "object"); let mut object = Self { kind: ObjectKind::Function, internal_slots: FxHashMap::default(), diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index b2459cf40f0..be23230289a 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -9,6 +9,7 @@ impl From<&Value> for Value { impl From for Value { fn from(value: String) -> Self { + let _timer = BoaProfiler::global().start_event("From", "value"); Self::string(value) } } @@ -170,6 +171,7 @@ where impl From for Value { fn from(object: Object) -> Self { + let _timer = BoaProfiler::global().start_event("From", "value"); Value::object(object) } } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index ec682c7b09e..77b5de02efd 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -13,7 +13,7 @@ use crate::builtins::{ }, property::Property, }; -use crate::syntax::ast::bigint::BigInt; +use crate::{syntax::ast::bigint::BigInt, BoaProfiler}; use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ @@ -122,6 +122,7 @@ impl Value { /// Returns a new empty object pub fn new_object(global: Option<&Value>) -> Self { + let _timer = BoaProfiler::global().start_event("new_object", "value"); if let Some(global) = global { let object_prototype = global.get_field("Object").get_field(PROTOTYPE); @@ -488,6 +489,7 @@ impl ValueData { where F: Into, { + let _timer = BoaProfiler::global().start_event("get_field", "value"); match *field.into() { // Our field will either be a String or a Symbol Self::String(ref s) => { @@ -586,6 +588,7 @@ impl ValueData { /// Check to see if the Value has the field, mainly used by environment records pub fn has_field(&self, field: &str) -> bool { + let _timer = BoaProfiler::global().start_event("has_field", "value"); self.get_property(field).is_some() } @@ -596,6 +599,7 @@ impl ValueData { F: Into, V: Into, { + let _timer = BoaProfiler::global().start_event("set_field", "value"); let field = field.into(); let val = val.into(); @@ -625,6 +629,7 @@ impl ValueData { /// Set the private field in the value pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { + let _timer = BoaProfiler::global().start_event("set_internal_slot", "exec"); if let Self::Object(ref obj) = *self { obj.borrow_mut() .internal_slots From 9f1e76be3119aef93a1d6c53e065c18489dad1f4 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 23:17:31 +0100 Subject: [PATCH 09/15] allow unit arg for profiler --- boa/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/boa/src/lib.rs b/boa/src/lib.rs index baf747f80a0..5a2b2b543fe 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -74,6 +74,7 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { /// The str is consumed and the state of the Interpreter is changed /// Similar to `forward`, except the current value is returned instad of the string /// If the interpreter fails parsing an error value is returned instead (error object) +#[allow(clippy::unit_arg)] pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { let main_timer = BoaProfiler::global().start_event("Main", "Main"); // Setup executor From a92caf05a054c97441f9df33b7bfce10cacf111c Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 23:29:44 +0100 Subject: [PATCH 10/15] add documentation --- boa/Cargo.toml | 2 +- boa/src/lib.rs | 2 +- docs/img/profiler.png | Bin 0 -> 100512 bytes docs/profiling.md | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 docs/img/profiler.png create mode 100644 docs/profiling.md diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 6343ed6f9df..a92820c5585 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -21,12 +21,12 @@ num-traits = "0.2.11" regex = "1.3.7" rustc-hash = "1.1.0" num-bigint = { version = "0.2.6", features = ["serde"] } +bitflags = "1.2.1" # Optional Dependencies serde = { version = "1.0.110", features = ["derive"], optional = true } measureme = { version = "0.7.1", optional = true } once_cell = { version = "1.4.0", optional = true } -bitflags = "1.2.1" [dev-dependencies] criterion = "0.3.2" diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 5a2b2b543fe..4314d77f794 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -74,7 +74,7 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { /// The str is consumed and the state of the Interpreter is changed /// Similar to `forward`, except the current value is returned instad of the string /// If the interpreter fails parsing an error value is returned instead (error object) -#[allow(clippy::unit_arg)] +#[allow(clippy::unit_arg, clippy::drop_copy)] pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { let main_timer = BoaProfiler::global().start_event("Main", "Main"); // Setup executor diff --git a/docs/img/profiler.png b/docs/img/profiler.png new file mode 100644 index 0000000000000000000000000000000000000000..edd8360f218087d80165d2aa1d8adf481fce0d70 GIT binary patch literal 100512 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 literal 0 HcmV?d00001 diff --git a/docs/profiling.md b/docs/profiling.md new file mode 100644 index 00000000000..ff9234f22c6 --- /dev/null +++ b/docs/profiling.md @@ -0,0 +1,18 @@ +# Profiling + +![Example](img/profiler.png) + +It's possible to get a full profile of Boa in action. +Sometimes this is needed to figure out where it is spending most of it's time. + +We use a crate called [measureme](https://github.com/rust-lang/measureme), which helps us keep track of timing functions during runtime. + +## How To Use + +You can run boa using the "profiler" feature flag to enable profiling. Seeing as you'll most likely be using boa_cli you can pass this through, like so: + +`cargo run --features Boa/profiler ../tests/js/test.js` + +## More Info + +https://blog.rust-lang.org/inside-rust/2020/02/25/intro-rustc-self-profile.html From 580210f5adc093eeb2407ada35beacbd80bfafbc Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 30 May 2020 23:39:44 +0100 Subject: [PATCH 11/15] updates --- .gitignore | 2 +- README.md | 4 ++++ docs/profiling.md | 11 ++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9f85a70ad04..972d1eb0f84 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ tests/js/test.js *.string_data *.string_index *.events -chrome_profiler.json \ No newline at end of file +chrome_profiler.json diff --git a/README.md b/README.md index 86b5db41497..44089762643 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,10 @@ See [CHANGELOG.md](./CHANGELOG.md). - Run with `cargo run -- test.js` where `test.js` is an existing JS file. - If any JS doesn't work then it's a bug. Please raise an issue! +## Profiling + +See [Profiling](./docs/profiling.md) + ## Command-line Options ``` diff --git a/docs/profiling.md b/docs/profiling.md index ff9234f22c6..fee3c135fae 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -9,10 +9,19 @@ We use a crate called [measureme](https://github.com/rust-lang/measureme), which ## How To Use +### Prerequesites + +- [Crox](https://github.com/rust-lang/measureme/blob/master/crox/Readme.md) installed + You can run boa using the "profiler" feature flag to enable profiling. Seeing as you'll most likely be using boa_cli you can pass this through, like so: `cargo run --features Boa/profiler ../tests/js/test.js` +Once finished you should see some trace files left in the directory (boa_cli in this case). +In the same directory as the `.events, string_data, string_index` files run `crox my_trace` or whatever the name of the files are. This will generate a chrome_profiler.json file, you can load this into Chrome Dev tools. + ## More Info -https://blog.rust-lang.org/inside-rust/2020/02/25/intro-rustc-self-profile.html +- https://blog.rust-lang.org/inside-rust/2020/02/25/intro-rustc-self-profile.html +- https://github.com/rust-lang/measureme +- https://github.com/rust-lang/measureme/blob/master/crox/Readme.md From 5b51ed0f8850763a0022cf84df0cfb145e710288 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sun, 31 May 2020 13:26:49 +0100 Subject: [PATCH 12/15] more timers --- boa/src/builtins/value/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 77b5de02efd..807b4fe9b27 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -398,6 +398,7 @@ impl ValueData { /// /// A copy of the Property is returned. pub fn get_property(&self, field: &str) -> Option { + let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 // This is only for primitive strings, String() objects have their lengths calculated in string.rs if self.is_string() && field == "length" { @@ -444,6 +445,7 @@ impl ValueData { writable: Option, configurable: Option, ) { + let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); let obj: Option = match self { Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), _ => None, @@ -464,6 +466,7 @@ impl ValueData { /// /// Returns a copy of the Property. pub fn get_internal_slot(&self, field: &str) -> Value { + let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); let obj: Object = match *self { Self::Object(ref obj) => { let hash = obj.clone(); @@ -489,7 +492,7 @@ impl ValueData { where F: Into, { - let _timer = BoaProfiler::global().start_event("get_field", "value"); + let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); match *field.into() { // Our field will either be a String or a Symbol Self::String(ref s) => { @@ -588,7 +591,7 @@ impl ValueData { /// Check to see if the Value has the field, mainly used by environment records pub fn has_field(&self, field: &str) -> bool { - let _timer = BoaProfiler::global().start_event("has_field", "value"); + let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); self.get_property(field).is_some() } @@ -599,7 +602,7 @@ impl ValueData { F: Into, V: Into, { - let _timer = BoaProfiler::global().start_event("set_field", "value"); + let _timer = BoaProfiler::global().start_event("Value::set_field", "value"); let field = field.into(); let val = val.into(); @@ -629,7 +632,7 @@ impl ValueData { /// Set the private field in the value pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { - let _timer = BoaProfiler::global().start_event("set_internal_slot", "exec"); + let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec"); if let Self::Object(ref obj) = *self { obj.borrow_mut() .internal_slots @@ -769,6 +772,7 @@ impl ValueData { /// /// https://tc39.es/ecma262/#sec-typeof-operator pub fn get_type(&self) -> &'static str { + let _timer = BoaProfiler::global().start_event("Value::get_type", "value"); match *self { Self::Rational(_) | Self::Integer(_) => "number", Self::String(_) => "string", From d64d67529b8994de7fa5965c0f9f5ed1d76baceb Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sun, 31 May 2020 17:59:00 +0100 Subject: [PATCH 13/15] * INSTANCE does not need to be public * init() is not needed * Re-order doc --- boa/src/profiler.rs | 12 +----------- docs/profiling.md | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 4204a628ae1..72f2cc114df 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -25,7 +25,7 @@ pub struct BoaProfiler { } #[cfg(feature = "profiler")] -pub static mut INSTANCE: OnceCell = OnceCell::new(); +static mut INSTANCE: OnceCell = OnceCell::new(); #[cfg(feature = "profiler")] impl BoaProfiler { @@ -42,16 +42,6 @@ impl BoaProfiler { BoaProfiler { profiler } } - // init creates a global instance of BoaProfiler which can be used across the whole application - pub fn init() { - let profiler = Self::default(); - unsafe { - INSTANCE - .set(profiler) - .expect("Failed to set BoaProfiler globally"); - } - } - pub fn global() -> &'static BoaProfiler { unsafe { INSTANCE.get_or_init(Self::default) } } diff --git a/docs/profiling.md b/docs/profiling.md index fee3c135fae..97def3b8822 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -7,11 +7,11 @@ Sometimes this is needed to figure out where it is spending most of it's time. We use a crate called [measureme](https://github.com/rust-lang/measureme), which helps us keep track of timing functions during runtime. -## How To Use +## Prerequesites -### Prerequesites +- [Crox](https://github.com/rust-lang/measureme/blob/master/crox/Readme.md) -- [Crox](https://github.com/rust-lang/measureme/blob/master/crox/Readme.md) installed +## How To Use You can run boa using the "profiler" feature flag to enable profiling. Seeing as you'll most likely be using boa_cli you can pass this through, like so: From 9f975491136534b1265e1e19d1b8632770cda52f Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sun, 31 May 2020 18:05:05 +0100 Subject: [PATCH 14/15] note about performance --- docs/profiling.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/profiling.md b/docs/profiling.md index 97def3b8822..ab59d9d20b7 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -7,6 +7,9 @@ Sometimes this is needed to figure out where it is spending most of it's time. We use a crate called [measureme](https://github.com/rust-lang/measureme), which helps us keep track of timing functions during runtime. +When the "profiler" flag is enabled, you compile with the profiler and it is called throughout the interpreter. +when the feature flag is not enabled, you have an empty dummy implementation that is just no ops. rustc should completely optimize that away. So there should be no performance downgrade from these changes + ## Prerequesites - [Crox](https://github.com/rust-lang/measureme/blob/master/crox/Readme.md) From ab08c75ea7ceb1f778b20a2734d9daff2233e965 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Thu, 4 Jun 2020 18:38:21 +0100 Subject: [PATCH 15/15] update messaging on INSTANCE --- boa/src/profiler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boa/src/profiler.rs b/boa/src/profiler.rs index 72f2cc114df..43c7ccbf1f2 100644 --- a/boa/src/profiler.rs +++ b/boa/src/profiler.rs @@ -24,6 +24,8 @@ pub struct BoaProfiler { profiler: Profiler, } +/// This static instance should never be public, and its only access should be done through the `global()` and `drop()` methods +/// This is because `get_or_init` manages synchronisation and the case of an empty value #[cfg(feature = "profiler")] static mut INSTANCE: OnceCell = OnceCell::new();