From fcfc274a4bdc44a5b4d73a6267a3fd945e5364b9 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:19:02 +0000 Subject: [PATCH] perf(ast): assume `serde_json` output is valid UTF8 string (#8928) `serde_json` produces valid UTF8 output, so when converting serialized JSON to string, there's no need to check the output for UTF8 validity. Skipping that check is a speed-up. Additionally, add a method to test the serializer without producing any output, and use it in conformance. This will hopefully speed up conformance a little. --- crates/oxc_ast/src/serialize.rs | 58 ++++++++++++++++++++++++++++----- tasks/coverage/src/driver.rs | 2 +- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 6ea9973f64808..8c7946b0a8bf7 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use cow_utils::CowUtils; use num_bigint::BigInt; use num_traits::Num; @@ -90,18 +92,56 @@ impl serde_json::ser::Formatter for EcmaFormatter { } impl Program<'_> { - /// # Panics + /// Serialize AST to JSON. + // + // Should not panic if everything is working correctly. + // Serializing into a `Vec` should be infallible. + #[expect(clippy::missing_panics_doc)] pub fn to_json(&self) -> String { - let ser = self.serializer(); - String::from_utf8(ser.into_inner()).unwrap() + let buf = Vec::new(); + let ser = self.to_json_into_writer(buf).unwrap(); + let buf = ser.into_inner(); + // SAFETY: `serde_json` outputs valid UTF-8. + // `serde_json::to_string` also uses `from_utf8_unchecked`. + // https://github.com/serde-rs/json/blob/1174c5f57db44c26460951b525c6ede50984b655/src/ser.rs#L2209-L2219 + unsafe { String::from_utf8_unchecked(buf) } } - /// # Panics - pub fn serializer(&self) -> serde_json::Serializer, EcmaFormatter> { - let buf = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter(buf, EcmaFormatter); - self.serialize(&mut ser).unwrap(); - ser + /// Serialize AST into a "black hole" writer. + /// + /// Only useful for testing, to make sure serialization completes successfully. + /// Should be faster than [`Program::to_json`], as does not actually produce any output. + /// + /// # Errors + /// Returns `Err` if serialization fails. + #[doc(hidden)] + pub fn test_to_json(&self) -> Result<(), serde_json::Error> { + struct BlackHole; + + #[expect(clippy::inline_always)] + impl Write for BlackHole { + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> Result { + Ok(buf.len()) + } + + #[inline(always)] + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } + } + + self.to_json_into_writer(BlackHole).map(|_| ()) + } + + /// Serialize AST into the provided writer. + fn to_json_into_writer( + &self, + writer: W, + ) -> Result, serde_json::Error> { + let mut ser = serde_json::Serializer::with_formatter(writer, EcmaFormatter); + self.serialize(&mut ser)?; + Ok(ser) } } diff --git a/tasks/coverage/src/driver.rs b/tasks/coverage/src/driver.rs index 2946f9b599051..bf9e093969cb1 100644 --- a/tasks/coverage/src/driver.rs +++ b/tasks/coverage/src/driver.rs @@ -76,7 +76,7 @@ impl CompilerInterface for Driver { self.errors.push(OxcDiagnostic::error("SourceType must not be unambiguous.")); } // Make sure serialization doesn't crash; also for code coverage. - let _serializer = program.serializer(); + program.test_to_json().unwrap(); ControlFlow::Continue(()) }