From 1849da2367c35f5aa8988a77aa7b540b0156474d Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:02:37 +0000 Subject: [PATCH 1/4] macros: log "[macro] call" through a scoped logger The debug-only print of every macro call was a plain prettyln! gated on IS_DEBUG, so BUN_DEBUG_QUIET_LOGS did not silence it and it landed on stdout of every macro test. It is now scoped_log!(Macro, ...): same text by default in debug builds, off under BUN_DEBUG_QUIET_LOGS, and BUN_DEBUG_Macro=1 turns it on alone. Release builds are unchanged. The 39900 test asserts the child's exact stdout instead of a substring. --- src/js_parser_jsc/Macro.rs | 9 +++------ test/regression/issue/39900.test.ts | 7 +++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/js_parser_jsc/Macro.rs b/src/js_parser_jsc/Macro.rs index ade58abe1a5f..abae2f5af3f1 100644 --- a/src/js_parser_jsc/Macro.rs +++ b/src/js_parser_jsc/Macro.rs @@ -30,6 +30,8 @@ use bun_jsc::{ }; use bun_jsc::{BuildMessage, ResolveMessage}; +bun_core::declare_scope!(Macro, visible); + const NAMESPACE_WITH_COLON: &[u8] = b"macro:"; fn is_macro_path(str: &[u8]) -> bool { @@ -899,12 +901,7 @@ impl Runner { id: i32, javascript_object: JSValue, ) -> Result { - if bun_core::env::IS_DEBUG { - bun_core::prettyln!( - "[macro] call {}", - bstr::BStr::new(function_name) - ); - } + bun_core::scoped_log!(Macro, "call {}", bstr::BStr::new(function_name)); // The exception holder is never read in this body (legacy from an earlier // exception-reporting path); a thread-local sentinel suffices. diff --git a/test/regression/issue/39900.test.ts b/test/regression/issue/39900.test.ts index c1ff77b514b4..970eb534d2aa 100644 --- a/test/regression/issue/39900.test.ts +++ b/test/regression/issue/39900.test.ts @@ -27,8 +27,7 @@ test.concurrent("macro that awaits crypto.subtle.digest resolves under bun run", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); expect(stderr).toBe(""); - // A debug build also logs "[macro] call sha" to stdout. - expect(stdout).toContain(`${expected}\n`); + expect(stdout).toBe(`${expected}\n`); expect(exitCode).toBe(0); }); @@ -54,8 +53,8 @@ console.log(start()); }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); expect(stderr).toBe(""); - expect(stdout).toContain("1\n"); - expect(stdout).toContain("settled\n"); + // The unawaited digest settles before or after the entry module's output. + expect(stdout.trim().split("\n").sort()).toEqual(["1", "settled"]); expect(exitCode).toBe(0); }); From c200d01d0118084d9621ae3c0fae10c1b1030e52 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 27 Aug 2026 18:49:18 +0000 Subject: [PATCH 2/4] macros: name the log scope macros, not Macro, which is already the struct in this module --- src/js_parser_jsc/Macro.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js_parser_jsc/Macro.rs b/src/js_parser_jsc/Macro.rs index abae2f5af3f1..59688479a735 100644 --- a/src/js_parser_jsc/Macro.rs +++ b/src/js_parser_jsc/Macro.rs @@ -30,7 +30,7 @@ use bun_jsc::{ }; use bun_jsc::{BuildMessage, ResolveMessage}; -bun_core::declare_scope!(Macro, visible); +bun_core::declare_scope!(macros, visible); const NAMESPACE_WITH_COLON: &[u8] = b"macro:"; @@ -901,7 +901,7 @@ impl Runner { id: i32, javascript_object: JSValue, ) -> Result { - bun_core::scoped_log!(Macro, "call {}", bstr::BStr::new(function_name)); + bun_core::scoped_log!(macros, "call {}", bstr::BStr::new(function_name)); // The exception holder is never read in this body (legacy from an earlier // exception-reporting path); a thread-local sentinel suffices. From 203b7bce6b4368df742e0a4831ff04ce8e231820 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:59:47 +0000 Subject: [PATCH 3/4] test: drop the "[macro] call" stdout workarounds the scoped logger makes dead macro-test.test.ts asserted a stdout tail, a last line, and filtered "[macro]" lines out because debug builds printed the macro call log to stdout. With the log behind BUN_DEBUG_QUIET_LOGS the three tests assert the exact stdout. The unawaited case of 39900.test.ts goes back to main's two toContain checks, since whether "settled" prints is timing. --- test/bundler/transpiler/macro-test.test.ts | 16 ++++++---------- test/regression/issue/39900.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/test/bundler/transpiler/macro-test.test.ts b/test/bundler/transpiler/macro-test.test.ts index fc28497df537..f925362fa695 100644 --- a/test/bundler/transpiler/macro-test.test.ts +++ b/test/bundler/transpiler/macro-test.test.ts @@ -150,10 +150,10 @@ test("object argument with a sparse numeric key", async () => { }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); // One combined assertion so stderr (where JSC prints the exception check failure) shows up in - // the diff if the child aborts. Debug builds print "[macro] call take" to stdout before the - // script's own output, so only the tail of stdout is matched. - expect({ stdout, stderr, exitCode, signalCode: proc.signalCode }).toMatchObject({ - stdout: expect.stringMatching(/200000\n$/), + // the diff if the child aborts. + expect({ stdout, stderr, exitCode, signalCode: proc.signalCode }).toEqual({ + stdout: "200000\n", + stderr: "", exitCode: 0, signalCode: null, }); @@ -178,7 +178,7 @@ test("object destructuring of a macro result keeps every bound property regardle stderr: "pipe", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect({ lastLine: stdout.trim().split("\n").pop(), stderr }).toEqual({ lastLine: "[2,1,1,1,2]", stderr: "" }); + expect({ stdout, stderr }).toEqual({ stdout: "[2,1,1,1,2]\n", stderr: "" }); expect(exitCode).toBe(0); }); @@ -199,11 +199,7 @@ describe("event loop routing around macros", () => { stderr: "pipe", }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - // Debug builds also print "[macro] call " to stdout. - const lines = stdout - .trim() - .split("\n") - .filter(line => !line.startsWith("[macro]")); + const lines = stdout.trim().split("\n"); return { lines, stderr, exitCode }; } diff --git a/test/regression/issue/39900.test.ts b/test/regression/issue/39900.test.ts index 970eb534d2aa..40caa4afae77 100644 --- a/test/regression/issue/39900.test.ts +++ b/test/regression/issue/39900.test.ts @@ -53,8 +53,8 @@ console.log(start()); }); const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); expect(stderr).toBe(""); - // The unawaited digest settles before or after the entry module's output. - expect(stdout.trim().split("\n").sort()).toEqual(["1", "settled"]); + expect(stdout).toContain("1\n"); + expect(stdout).toContain("settled\n"); expect(exitCode).toBe(0); }); From 010f058f8fb05f99182e37880656a0c068c13ded Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:37:06 +0000 Subject: [PATCH 4/4] verify-baseline-static: allowlist the llint_op_jmp_wide32 decode false positive The linear-sweep scan of the linux-x64 baseline build decoded one RDPMC out of the opcode id embedded at the start of that LLInt handler. The layout shift that moved the desync onto this symbol is this branch's Rust change. Same class as the two llint_op_* entries already listed. --- scripts/verify-baseline-static/allowlist-x64.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/verify-baseline-static/allowlist-x64.txt b/scripts/verify-baseline-static/allowlist-x64.txt index 13819585cd13..b4395e1969d7 100644 --- a/scripts/verify-baseline-static/allowlist-x64.txt +++ b/scripts/verify-baseline-static/allowlist-x64.txt @@ -1349,7 +1349,11 @@ jsimd_ycc_rgb_convert_avx2.return [AVX, AVX2] # single-digit hit here is decode noise. Ceiling [AVX] so a multi-feature leak # still fails. On ELF each opcode handler has its own symbol, so layout shifts # can move the desync between llint_op_* siblings; add them here as they trip. -# (2 symbols) +# llint_op_jmp_wide32 decoded a stray RDPMC (0F 33) out of the same garbage. +# That is not a SIMD feature and the bytes move with layout, so it is a blanket +# pass; a real -march leak would show up in the other llint_op_* symbols. +# (3 symbols) # ---------------------------------------------------------------------------- llint_op_enter_wide32 [AVX] llint_op_wide16_wide16 [AVX] +llint_op_jmp_wide32