# Wasm "index out of bounds" only in debug mode

**URL:** <https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705>\
**Category:** Help\
**Tags:** wasm, compiler\
**Created:** [June 26, 2025, 5:51pm UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705 "2025-06-26T17:51:53Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Zemogus](https://ziggit.dev/user_avatar/ziggit.dev/zemogus/32/1421_2.png) [@Zemogus](https://ziggit.dev/u/Zemogus)\
**Post date:** [June 26, 2025, 5:51pm UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/1 "2025-06-26T17:51:53Z")

</div>

Hi all, I was trying to compile Zig to WASM with some sample code (see below), when I got hit with this error:

```zig
Uncaught RuntimeError: index out of bounds
    add http://127.0.0.1:57391/zig-out/bin/main.wasm:126
    <anonymous> http://127.0.0.1:57391/src/index.html:14
    InterpretGeneratorResume self-hosted:1425
    AsyncFunctionNext self-hosted:800

```

Oddly, this only happens when compiling in debug mode. Compiling in any of the release modes produces the expected output:

```zig
5
a + b = 5

```

> **Source code**
>
> build.zig:
> 
> ```zig
> const std = @import("std");
> 
> pub fn build(b: *std.Build) void {
> const optimize = b.standardOptimizeOption(.{});
> 
> const wasm = b.addLibrary(.{
> .name = "wasm",
> .root_module = b.createModule(.{
> .root_source_file = b.path("src/main.zig"),
> .target = b.resolveTargetQuery(.{
> .cpu_arch = .wasm32,
> .ofmt = .wasm,
> .os_tag = .freestanding,
> }),
> .optimize = optimize,
> }),
> .use_lld = false,
> });
> wasm.rdynamic = true;
> 
> const install_wasm = b.addInstallArtifact(wasm, .{
> .dest_dir = .{ .override = .bin },
> .dest_sub_path = "main.wasm",
> });
> b.getInstallStep().dependOn(&install_wasm.step);
> }
> 
> ```
> 
> src/main.zig
> 
> ```zig
> extern fn print(a: i32) void;
> 
> export fn add(a: i32, b: i32) i32 {
> print(a + b);
> return a + b;
> }
> 
> ```
> 
> src/index.html:
> 
> ```html
> <!doctype html>
> <script type="module">
> const importObject = {
> env: {
> print: function (x) {
> console.log(x);
> },
> },
> };
> const { instance } = await WebAssembly.instantiateStreaming(
> fetch("../zig-out/bin/main.wasm"), // Compiled by zig
> importObject,
> );
> console.log(`a + b = ${instance.exports.add(2, 3)}`); // Error occurs on this line
> </script>
> 
> ```

My first thought was that this was a regression in the new self-hosted x86 backend, but nope, setting `.use_llvm = true` didn’t change anything. I tried setting `use_lld` to `true`, but still nothing. I tried out more Zig versions and surprisingly, 0.13.0 worked.

| zig version | works |
| --- | --- |
| 0.13.0 | ✅ |
| 0.14.0 | ❌ |
| 0.14.1 | ❌ |
| master | ❌ |

Digging a bit deeper, I found out that the error goes away if I remove all arguments from the `add` function (perhaps the function arguments are passed as an array?).

I didn’t manage to find an issue in the github repo documenting this. Is this a regression or am I doing something wrong (what could even go wrong with such a small piece of code)?

---

<div class="post-metadata">

**Author:** ![Southporter](https://ziggit.dev/user_avatar/ziggit.dev/southporter/32/2500_2.png) [@Southporter](https://ziggit.dev/u/Southporter)\
**Post date:** [June 26, 2025, 7:42pm UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/2 "2025-06-26T19:42:57Z")

</div>

This feels like a miscompilation to me. Can you use [wasm-tools](https://github.com/bytecodealliance/wasm-tools) or similar to print out the `wat` version of the wasm and see what it gives you? You could even compare between release and debug modes. This will likely produce a big output so you may have to filter it down to your add function.  
You could even compare the output from 0.13 to one of the others and see what changed.

---

<div class="post-metadata">

**Author:** ![castholm](https://ziggit.dev/user_avatar/ziggit.dev/castholm/32/1330_2.png) [@castholm](https://ziggit.dev/u/castholm)\
**Post date:** [June 26, 2025, 8:57pm UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/3 "2025-06-26T20:57:43Z")

</div>

```diff
- const wasm = b.addLibrary(.{
+ const wasm = b.addExecutable(.{
         .name = "wasm",
         .root_module = b.createModule(.{
             .root_source_file = b.path("src/main.zig"),
             .target = b.resolveTargetQuery(.{
                 .cpu_arch = .wasm32,
                 .ofmt = .wasm,
                 .os_tag = .freestanding,
             }),
             .optimize = optimize,
         }),
         .use_lld = false,
     });
+ wasm.entry = .disabled;
     wasm.rdynamic = true;

```

The problem is that you’re building a static library, which might not always result in a legal and working Wasm binary. Since Zig 0.12.0, when compiling for Wasm you need to build your code as an executable without an entry point (`-fno-entry` (CLI) or `exe.entry = .disabled` (build system)).

---

<div class="post-metadata">

**Author:** ![Zemogus](https://ziggit.dev/user_avatar/ziggit.dev/zemogus/32/1421_2.png) [@Zemogus](https://ziggit.dev/u/Zemogus)\
**Post date:** [June 27, 2025, 1:09am UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/4 "2025-06-27T01:09:15Z")

</div>

Thanks, that fixed it for Zig 0.14.1! Oddly enough, it doesn’t work on master (0.15.0-dev.875+75d6d4c3f), not that I mind too much though.

Edit: never mind turns out the code didn’t actually compile because of `.use_lld = false`. Re-compiled it and now it works on both 0.14.1 and master.

In case anyone else has the same issue, this is my final build.zig:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});

    const wasm = b.addExecutable(.{
        .name = "wasm",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = b.resolveTargetQuery(.{
                .cpu_arch = .wasm32,
                .os_tag = .freestanding,
            }),
            .optimize = optimize,
        }),
    });
    wasm.entry = .disabled;
    wasm.rdynamic = true;

    const install_wasm = b.addInstallArtifact(wasm, .{
        .dest_dir = .{ .override = .bin },
        .dest_sub_path = "main.wasm",
    });
    b.getInstallStep().dependOn(&install_wasm.step);
}

```

---

<div class="post-metadata">

**Author:** ![KilianHanich](https://ziggit.dev/user_avatar/ziggit.dev/kilianhanich/32/8254_2.png) [@KilianHanich](https://ziggit.dev/u/KilianHanich)\
**Post date:** [June 27, 2025, 9:33pm UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/6 "2025-06-27T21:33:40Z")

</div>

It would actually be nice to know why that is necessary.

---

<div class="post-metadata">

**Author:** ![TUSF](https://ziggit.dev/user_avatar/ziggit.dev/tusf/32/456_2.png) [@TUSF](https://ziggit.dev/u/TUSF)\
**Post date:** [June 28, 2025, 12:13am UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/7 "2025-06-28T00:13:53Z")

</div>

A static library is basically an incomplete object file that expects to be compiled in with other object files. Usually this means that many functions have pointers and offsets that are not yet correct, and which a linker is meant to correct when combining with other objects.

A look at the wasm text format, and I assume that in this case, the problem lay in the `__stack_pointer` global. The static library assumes that the stack (the linear memory stack used by most compiled languages, not WebAssembly’s internal stack) was setup by the entry point, and tries to write data into it. ~~But there is no memory allocated to the module, and hence the `index out of bounds` error.~~ (Edit Correction:) It’s not that the memory for the stack isn’t set up, but that when compiled to a static library, the `__stack_pointer` global is set to a negative number for its placeholder. So when trying to write to the stack, it’s trying to write to a negative index, which is out of bounds.

The error only happens in Debug mode, because the other modes optimize out the interaction with the non-existent stack memory. Meanwhile, making the artifact into an executable, and without a default entry point, means that the compiler knows that it needs to sets up the stack pointer on its own, so even though Debug mode is still using the stack, it now works.

---

<div class="post-metadata">

**Author:** ![KilianHanich](https://ziggit.dev/user_avatar/ziggit.dev/kilianhanich/32/8254_2.png) [@KilianHanich](https://ziggit.dev/u/KilianHanich)\
**Post date:** [June 29, 2025, 12:20am UTC](https://ziggit.dev/t/wasm-index-out-of-bounds-only-in-debug-mode/10705/8 "2025-06-29T00:20:54Z")

</div>

For a static library that is understandable, but shouldn’t a dynamic library work?
