Debugging Wasm in Safari Web Inspector
This page answers one task: a WebAssembly feature works in Chrome and Firefox but fails or performs badly in Safari — on macOS or iOS — and you need to debug it in Safari’s own tools. You want to know what Web Inspector shows for WebAssembly, how to use breakpoints and profiling there, and how to work around the features it lacks compared with Chromium’s DevTools.
Prerequisites
- [ ] Safari on macOS (and, for iOS, a device connected to a Mac).
- [ ] The Develop menu enabled (Safari Settings → Advanced → “Show features for web developers”).
- [ ] A build of the module that keeps function names (the name section).
What Web Inspector offers for WebAssembly
Safari runs WebAssembly in JavaScriptCore, with its own tiers (an interpreter and the BBQ and OMG compilers). Web Inspector exposes modules in the Sources tab, where each module appears as a resource whose functions are shown as disassembled text. You can set breakpoints on instructions, step, and inspect locals and the operand values the inspector exposes; the call stack shows Wasm frames with function names when the name section is present. The Timelines tab records JavaScript and Wasm execution for profiling. Console errors from compilation, linking and traps appear with JavaScriptCore’s wording.
What Safari does not offer is Chromium’s DWARF-based source-level debugging (the C/C++ DevTools extension) — you debug at the Wasm instruction level, mapped back to your source through function names and careful reasoning, or reproduce the issue elsewhere for source-level work. Capabilities change between Safari releases, so check what your version provides.
Step 1 — reproduce and read the console first
Open Web Inspector (Develop → Show Web Inspector, or ⌥⌘I), reload, and reproduce. Many Safari-only Wasm failures are diagnosable from the console alone:
CompileErrormentioning an unknown opcode or feature — the module uses a proposal this Safari version does not support (check feature detection and build flags).RangeErrorwhen creating or growing memory — Safari’s memory limits, notably on iOS, are lower than desktop Chrome’s.TypeErrorabout MIME types orinstantiateStreaming— server configuration or a cached response missing headers.RuntimeErrorwith a trap message — a bug or undefined behaviour that other engines happened to tolerate, or different floating-point edge cases.
Step 2 — find the function in Sources
In the Sources tab, locate the Wasm module (listed by URL or as an anonymous module) and open the function from the call stack of the error. With a name section, functions show their source-level names (mangled for C++ and Rust unless demangled at build time), which tells you where in your code you are. Keep a symbol-rich build for debugging even if production strips names.
Step 3 — set breakpoints and step
Set a breakpoint on an instruction in the function, trigger the code path, and step through. Inspect the values Web Inspector shows for locals and parameters; compare with what the same function receives in Chrome (where you can debug at source level) to find where behaviour diverges. Conditional breakpoints help when a function runs thousands of times before the failing call. Enable “Pause on exceptions” so traps stop in the debugger at the failing instruction.
Step 4 — profile with Timelines
For performance problems, record in the Timelines tab with the JavaScript & Events timeline, reproduce the slow operation, and inspect the call tree. Wasm functions appear with their names; look for functions much heavier in Safari than in Chrome profiles of the same operation. Common Safari-specific culprits include code paths that depend on features handled differently by JavaScriptCore’s tiers, very large functions that take longer to optimise, and frequent calls between JavaScript and Wasm.
Step 5 — debug on iOS devices
Connect the iPhone or iPad to a Mac with a cable (or trusted network pairing), enable Web Inspector on the device (Settings → Safari → Advanced), and choose the device and page from Safari’s Develop menu on the Mac. The same Sources, Console and Timelines tools work against the device. iOS-specific issues — memory limits, background tab suspension, slower devices — show up only there. See debugging Wasm on Android and iOS devices.
Strategies without source-level debugging
When instruction-level debugging is too slow, add instrumentation instead: a debug build that logs function entry with key parameters through an imported logging function, assertion checks compiled into debug builds, or a “trace mode” that records calls (see recording Wasm call traces for bug reports). Record a trace in Safari and replay it in an environment with source-level debugging; if the bug reproduces there, it is in your code, and if not, it is engine-specific behaviour worth reporting with a reduced test case.
Reporting engine bugs
If the investigation points to JavaScriptCore — a crash, a miscompilation, a spec deviation — reduce it to a minimal module and page, check against Safari Technology Preview (which runs newer engine builds), and file a report with the WebKit bug tracker including the module, the page and expected versus actual behaviour.
Inspecting memory from the console
Without a dedicated memory inspector, the console is the tool for looking at linear memory. When paused at a breakpoint, or after an error, evaluate
expressions against the module’s exported memory: new Uint8Array(instance.exports.memory.buffer, ptr, 64) shows bytes at an address, new DataView(...)
reads typed fields, and a small helper that formats a hex dump makes this comfortable. Expose the instance on window in debug builds (window.__wasm = instance) so it is reachable from the console. Comparing the same region in Safari and Chrome at the same point in a reproducible scenario is often the fastest
way to find where state diverges — a corrupted header, an unexpected length, a value computed differently due to floating-point or integer-width assumptions.
Common Safari-specific root causes
Investigations in Safari tend to end at a handful of causes. Feature gaps in older versions — exception handling, tail calls, GC, relaxed SIMD — produce compile errors and need feature detection with fallback builds. Lower memory ceilings on iOS produce allocation failures that never happen on desktop. Differences in how background tabs and page lifecycle events are handled (Safari freezes or discards background pages more aggressively on iOS) produce errors after the user returns to a tab whose worker was terminated. Undefined behaviour in C or C++ code — reading uninitialised memory, relying on unspecified evaluation order — surfaces differently because JavaScriptCore’s compilers optimise differently. Checking these four first saves a lot of instruction-level stepping.
Keeping Safari in the test matrix
Most Safari-only bugs are found by users because teams develop in Chromium. Running end-to-end tests in WebKit (Playwright’s WebKit build) and periodically in real Safari on macOS and iOS catches the majority before release.
Expected output
The Safari-only failure is classified from the console as a memory RangeError on iOS; the module’s initial memory is reduced and growth enabled; a separate
performance issue is traced in Timelines to a function called per pixel from JavaScript, fixed by batching; and a remaining miscompilation is reduced to a
40-line WAT test case and reported to WebKit.
Gotchas
- Stripped names. Functions show as indices. Keep names in debug builds.
- Expecting DWARF source maps. Safari debugs at instruction level. Compare with Chrome for source-level views.
- Testing only desktop Safari. iOS limits differ. Debug on devices.
- Recording profiles with Web Inspector open for everything. Overhead skews timing. Profile focused operations.
- Assuming engine bugs. Most Safari-only bugs are undefined behaviour or feature gaps. Reduce before reporting.
- Developing only in Chromium. Safari bugs reach users first. Run WebKit tests in CI.
Performance note
Batching per-pixel JavaScript-to-Wasm calls into one call per row cut a Safari-specific hot spot from 210 ms to 14 ms for a 2-megapixel image; the same change helped Chrome far less, because its call overhead was already lower.
Frequently Asked Questions
Does Safari support DWARF debugging for Wasm? Not in the way Chromium’s extension does; debug at the instruction level or reproduce in Chrome.
Can I inspect linear memory? Through the console by reading the exported memory with typed arrays; Safari lacks a dedicated memory inspector.
Why do iOS errors not appear on macOS? iOS has different memory limits and background behaviour; debug on the device.
Should I test in Safari Technology Preview? Yes — it shows whether newer engine builds fix or change behaviour.
How do I look at linear memory in Safari?
Expose the instance in debug builds and read memory.buffer from the console with typed arrays or a small hex-dump helper.
Related
- Debugging Wasm in Firefox DevTools — another engine.
- Debugging Wasm with DWARF source maps — source-level in Chrome.
- How Safari runs Wasm — JavaScriptCore’s tiers.
- Supporting older Safari versions with Wasm — feature gaps.
← Back to Debugging & Profiling Wasm Modules