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.

Safari Web Inspector versus Chromium DevTools for Wasm Safari's Web Inspector shows Wasm modules as disassembled functions with instruction-level breakpoints, stepping and a timeline profiler, but lacks DWARF source-level debugging. Chromium DevTools adds source-level C, C++ and Rust debugging through DWARF and an extension, plus memory inspection tools. Safari Web Inspector disassembled functions instruction-level breakpoints Timelines profiler Safari-only bugs Chromium DevTools DWARF source-level debugging memory inspector Performance panel everyday debugging

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:

  • CompileError mentioning an unknown opcode or feature — the module uses a proposal this Safari version does not support (check feature detection and build flags).
  • RangeError when creating or growing memory — Safari’s memory limits, notably on iOS, are lower than desktop Chrome’s.
  • TypeError about MIME types or instantiateStreaming — server configuration or a cached response missing headers.
  • RuntimeError with 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.

Narrowing down a Safari-only Wasm bug Start with the console error to classify the failure. Open the failing function from the call stack in the Sources tab. Set breakpoints or pause on exceptions and step through the disassembly, recording parameter values. Compare those values with the same call in Chrome debugged at source level to find the first divergence. console error classify failure open function in Sources name section helps break + step instruction level record values params, locals compare with Chrome first divergence

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.

Processing a 2-megapixel image in Safari Milliseconds to process a 2-megapixel image in Safari with one JavaScript to Wasm call per pixel and with one call per row of pixels. ms per image call per pixel 210 ms call per row 14 ms

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.

← Back to Debugging & Profiling Wasm Modules