Commit 1fe1e4d292

Luuk de Gram <luuk@degram.dev>
2022-01-18 21:43:20
wasm: Split funcgen and declgen
This allows us to get rid of unused fields when generating code for non-function decls. We can now create seperate instances of `DeclGen` which in turn can then be used to generate the code for a constant. Besides those reasons, it will be much easier to switch to the generic purpose `codegen.zig` that any backend should use. Allowing us to deduplicate this code.
1 parent 28acbdb
Changed files (3)
src
arch
link
src/arch/wasm/CodeGen.zig
@@ -542,10 +542,9 @@ locals: std.ArrayListUnmanaged(u8),
 target: std.Target,
 /// Represents the wasm binary file that is being linked.
 bin_file: *link.File.Wasm,
-/// Table with the global error set. Consists of every error found in
-/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
-/// during codegen to determine the error value.
-global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
+/// Reference to the Module that this decl is part of.
+/// Used to find the error value.
+module: *Module,
 /// List of MIR Instructions
 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
 /// Contains extra data for MIR
@@ -581,7 +580,7 @@ pub fn deinit(self: *Self) void {
     self.* = undefined;
 }
 
-/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
+/// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
 fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
     const src: LazySrcLoc = .{ .node_offset = 0 };
     const src_loc = src.toSrcLoc(self.decl);
@@ -674,50 +673,41 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
     return result;
 }
 
-/// Using a given `Type`, returns the corresponding wasm Valtype
-fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
+/// Using a given `Type`, returns the corresponding type
+fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype {
     return switch (ty.zigTypeTag()) {
         .Float => blk: {
-            const bits = ty.floatBits(self.target);
+            const bits = ty.floatBits(target);
             if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
             if (bits == 64) break :blk wasm.Valtype.f64;
-            return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
+            return wasm.Valtype.i32; // represented as pointer to stack
         },
         .Int => blk: {
-            const info = ty.intInfo(self.target);
+            const info = ty.intInfo(target);
             if (info.bits <= 32) break :blk wasm.Valtype.i32;
             if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
             break :blk wasm.Valtype.i32; // represented as pointer to stack
         },
         .Enum => switch (ty.tag()) {
             .enum_simple => wasm.Valtype.i32,
-            else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
+            else => typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty, target),
         },
-        .Bool,
-        .Pointer,
-        .ErrorSet,
-        .Struct,
-        .ErrorUnion,
-        .Optional,
-        .Fn,
-        .Array,
-        => wasm.Valtype.i32,
-        else => self.fail("TODO - Wasm typeToValtype for type '{}'", .{ty}),
+        else => wasm.Valtype.i32, // all represented as reference/immediate
     };
 }
 
 /// Using a given `Type`, returns the byte representation of its wasm value type
-fn genValtype(self: *Self, ty: Type) InnerError!u8 {
-    return wasm.valtype(try self.typeToValtype(ty));
+fn genValtype(ty: Type, target: std.Target) u8 {
+    return wasm.valtype(typeToValtype(ty, target));
 }
 
 /// Using a given `Type`, returns the corresponding wasm value type
 /// Differently from `genValtype` this also allows `void` to create a block
 /// with no return type
-fn genBlockType(self: *Self, ty: Type) InnerError!u8 {
+fn genBlockType(ty: Type, target: std.Target) u8 {
     return switch (ty.tag()) {
         .void, .noreturn => wasm.block_empty,
-        else => self.genValtype(ty),
+        else => genValtype(ty, target),
     };
 }
 
@@ -739,7 +729,7 @@ fn emitWValue(self: *Self, value: WValue) InnerError!void {
 /// Returns a corresponding `Wvalue` with `local` as active tag
 fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
     const initial_index = self.local_index;
-    const valtype = try self.genValtype(ty);
+    const valtype = genValtype(ty, self.target);
     try self.locals.append(self.gpa, valtype);
     self.local_index += 1;
     return WValue{ .local = initial_index };
@@ -747,33 +737,33 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
 
 /// Generates a `wasm.Type` from a given function type.
 /// Memory is owned by the caller.
-fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
-    var params = std.ArrayList(wasm.Valtype).init(self.gpa);
+fn genFunctype(gpa: Allocator, fn_ty: Type, target: std.Target) !wasm.Type {
+    var params = std.ArrayList(wasm.Valtype).init(gpa);
     defer params.deinit();
-    var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
+    var returns = std.ArrayList(wasm.Valtype).init(gpa);
     defer returns.deinit();
     const return_type = fn_ty.fnReturnType();
 
-    const want_sret = self.isByRef(return_type);
+    const want_sret = isByRef(return_type, target);
 
     if (want_sret) {
-        try params.append(try self.typeToValtype(Type.usize));
+        try params.append(typeToValtype(return_type, target));
     }
 
     // param types
     if (fn_ty.fnParamLen() != 0) {
-        const fn_params = try self.gpa.alloc(Type, fn_ty.fnParamLen());
-        defer self.gpa.free(fn_params);
+        const fn_params = try gpa.alloc(Type, fn_ty.fnParamLen());
+        defer gpa.free(fn_params);
         fn_ty.fnParamTypes(fn_params);
         for (fn_params) |param_type| {
             if (!param_type.hasCodeGenBits()) continue;
-            try params.append(try self.typeToValtype(param_type));
+            try params.append(typeToValtype(param_type, target));
         }
     }
 
     // return type
     if (!want_sret and return_type.hasCodeGenBits()) {
-        try returns.append(try self.typeToValtype(return_type));
+        try returns.append(typeToValtype(return_type, target));
     }
 
     return wasm.Type{
@@ -782,8 +772,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
     };
 }
 
-pub fn genFunc(self: *Self) InnerError!Result {
-    var func_type = try self.genFunctype(self.decl.ty);
+pub fn genFunc(self: *Self) InnerError!void {
+    var func_type = try genFunctype(self.gpa, self.decl.ty, self.target);
     defer func_type.deinit(self.gpa);
     self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
 
@@ -828,240 +818,260 @@ pub fn genFunc(self: *Self) InnerError!Result {
         },
         else => |e| return e,
     };
-
-    // codegen data has been appended to `code`
-    return Result.appended;
 }
 
-pub fn genDecl(self: *Self) InnerError!Result {
-    const decl = self.decl;
-    assert(decl.has_tv);
-
-    log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
-
-    if (decl.val.castTag(.function)) |func_payload| {
-        _ = func_payload;
-        return self.fail("TODO wasm backend genDecl function pointer", .{});
-    } else if (decl.val.castTag(.extern_fn)) |extern_fn| {
-        const ext_decl = extern_fn.data;
-        var func_type = try self.genFunctype(ext_decl.ty);
-        func_type.deinit(self.gpa);
-        ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
-        return Result.appended;
-    } else {
-        const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
-            break :init_val payload.data.init;
-        } else decl.val;
-        if (init_val.tag() != .unreachable_value) {
-            return try self.genTypedValue(decl.ty, init_val);
+pub const DeclGen = struct {
+    /// The decl we are generating code for.
+    decl: *Decl,
+    /// The symbol we're generating code for.
+    /// This can either be the symbol of the Decl itself,
+    /// or one of its locals.
+    symbol_index: u32,
+    gpa: Allocator,
+    /// A reference to the linker, that will process the decl's
+    /// code and create any relocations it deems neccesary.
+    bin_file: *link.File.Wasm,
+    /// This will be set when `InnerError` has been returned.
+    /// In any other case, this will be 'undefined'.
+    err_msg: *Module.ErrorMsg,
+    /// Reference to the Module that is being compiled.
+    /// Used to find the error value of an error.
+    module: *Module,
+    /// The list of bytes that have been generated so far,
+    /// can be used to calculate the offset into a section.
+    code: *std.ArrayList(u8),
+
+    /// Sets `err_msg` on `DeclGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
+    fn fail(self: *DeclGen, comptime fmt: []const u8, args: anytype) InnerError {
+        const src: LazySrcLoc = .{ .node_offset = 0 };
+        const src_loc = src.toSrcLoc(self.decl);
+        self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
+        return error.CodegenFail;
+    }
+
+    fn target(self: *const DeclGen) std.Target {
+        return self.bin_file.base.options.target;
+    }
+
+    pub fn genDecl(self: *DeclGen) InnerError!Result {
+        const decl = self.decl;
+        assert(decl.has_tv);
+
+        log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
+
+        if (decl.val.castTag(.function)) |func_payload| {
+            _ = func_payload;
+            return self.fail("TODO wasm backend genDecl function pointer", .{});
+        } else if (decl.val.castTag(.extern_fn)) |extern_fn| {
+            const ext_decl = extern_fn.data;
+            var func_type = try genFunctype(self.gpa, ext_decl.ty, self.target());
+            func_type.deinit(self.gpa);
+            ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
+            return Result.appended;
+        } else {
+            const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
+                break :init_val payload.data.init;
+            } else decl.val;
+            if (init_val.tag() != .unreachable_value) {
+                return try self.genTypedValue(decl.ty, init_val, self.code.writer());
+            }
+            return Result.appended;
         }
-        return Result.appended;
     }
-}
 
-/// Generates the wasm bytecode for the declaration belonging to `Context`
-fn genTypedValue(self: *Self, ty: Type, val: Value) InnerError!Result {
-    if (val.isUndef()) {
-        try self.code.appendNTimes(0xaa, @intCast(usize, ty.abiSize(self.target)));
-        return Result.appended;
-    }
-    switch (ty.zigTypeTag()) {
-        .Fn => {
-            const fn_decl = switch (val.tag()) {
-                .extern_fn => val.castTag(.extern_fn).?.data,
-                .function => val.castTag(.function).?.data.owner_decl,
-                else => unreachable,
-            };
-            return try self.lowerDeclRef(ty, val, fn_decl);
-        },
-        .Optional => {
-            var opt_buf: Type.Payload.ElemType = undefined;
-            const payload_type = ty.optionalChild(&opt_buf);
-            if (ty.isPtrLikeOptional()) {
-                if (val.castTag(.opt_payload)) |payload| {
-                    return try self.genTypedValue(payload_type, payload.data);
-                } else if (!val.isNull()) {
-                    return try self.genTypedValue(payload_type, val);
-                } else {
-                    try self.code.appendNTimes(0, @intCast(usize, ty.abiSize(self.target)));
+    /// Generates the wasm bytecode for the declaration belonging to `Context`
+    fn genTypedValue(self: *DeclGen, ty: Type, val: Value, writer: anytype) InnerError!Result {
+        if (val.isUndef()) {
+            try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
+            return Result.appended;
+        }
+        switch (ty.zigTypeTag()) {
+            .Fn => {
+                const fn_decl = switch (val.tag()) {
+                    .extern_fn => val.castTag(.extern_fn).?.data,
+                    .function => val.castTag(.function).?.data.owner_decl,
+                    else => unreachable,
+                };
+                return try self.lowerDeclRef(ty, val, fn_decl, writer);
+            },
+            .Optional => {
+                var opt_buf: Type.Payload.ElemType = undefined;
+                const payload_type = ty.optionalChild(&opt_buf);
+                const is_pl = !val.isNull();
+
+                if (!payload_type.hasCodeGenBits()) {
+                    try writer.writeByteNTimes(@boolToInt(is_pl), 4);
                     return Result.appended;
                 }
-            }
-            // `null-tag` byte
-            try self.code.appendNTimes(@boolToInt(!val.isNull()), 4);
-            const pl_result = try self.genTypedValue(
-                payload_type,
-                if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
-            );
-            switch (pl_result) {
-                .appended => {},
-                .externally_managed => |payload| try self.code.appendSlice(payload),
-            }
-            return Result.appended;
-        },
-        .Array => switch (val.tag()) {
-            .bytes => {
-                const payload = val.castTag(.bytes).?;
-                return Result{ .externally_managed = payload.data };
+
+                if (ty.isPtrLikeOptional()) {
+                    if (val.castTag(.opt_payload)) |payload| {
+                        return try self.genTypedValue(payload_type, payload.data, writer);
+                    } else if (!val.isNull()) {
+                        return try self.genTypedValue(payload_type, val, writer);
+                    } else {
+                        try writer.writeByteNTimes(0, @intCast(usize, ty.abiSize(self.target())));
+                        return Result.appended;
+                    }
+                }
+
+                // `null-tag` bytes
+                try writer.writeByteNTimes(@boolToInt(is_pl), 4);
+                const pl_result = try self.genTypedValue(
+                    payload_type,
+                    if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
+                    writer,
+                );
+                switch (pl_result) {
+                    .appended => {},
+                    .externally_managed => |payload| try writer.writeAll(payload),
+                }
+                return Result.appended;
             },
-            .array => {
-                const elem_vals = val.castTag(.array).?.data;
-                const elem_ty = ty.childType();
-                for (elem_vals) |elem_val| {
-                    switch (try self.genTypedValue(elem_ty, elem_val)) {
+            .Array => switch (val.tag()) {
+                .bytes => {
+                    const payload = val.castTag(.bytes).?;
+                    return Result{ .externally_managed = payload.data };
+                },
+                .array => {
+                    const elem_vals = val.castTag(.array).?.data;
+                    const elem_ty = ty.childType();
+                    for (elem_vals) |elem_val| {
+                        switch (try self.genTypedValue(elem_ty, elem_val, writer)) {
+                            .appended => {},
+                            .externally_managed => |data| try writer.writeAll(data),
+                        }
+                    }
+                    return Result.appended;
+                },
+                else => return self.fail("TODO implement genTypedValue for array type value: {s}", .{@tagName(val.tag())}),
+            },
+            .Int => {
+                const info = ty.intInfo(self.target());
+                const abi_size = @intCast(usize, ty.abiSize(self.target()));
+                // todo: Implement integer sizes larger than 64bits
+                if (info.bits > 64) return self.fail("TODO: Implement genTypedValue for integer bit size: {d}", .{info.bits});
+                var buf: [8]u8 = undefined;
+                if (info.signedness == .unsigned) {
+                    std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt());
+                } else std.mem.writeIntLittle(i64, &buf, val.toSignedInt());
+                try writer.writeAll(buf[0..abi_size]);
+                return Result.appended;
+            },
+            .Enum => {
+                try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
+                return Result.appended;
+            },
+            .Bool => {
+                try writer.writeByte(@boolToInt(val.toBool()));
+                return Result.appended;
+            },
+            .Struct => {
+                const field_vals = val.castTag(.@"struct").?.data;
+                for (field_vals) |field_val, index| {
+                    const field_ty = ty.structFieldType(index);
+                    if (!field_ty.hasCodeGenBits()) continue;
+                    switch (try self.genTypedValue(field_ty, field_val, writer)) {
                         .appended => {},
-                        .externally_managed => |data| try self.code.appendSlice(data),
+                        .externally_managed => |payload| try writer.writeAll(payload),
                     }
                 }
                 return Result.appended;
             },
-            else => return self.fail("TODO implement genTypedValue for array type value: {s}", .{@tagName(val.tag())}),
-        },
-        .Int => {
-            const info = ty.intInfo(self.target);
-            const abi_size = @intCast(usize, ty.abiSize(self.target));
-            // todo: Implement integer sizes larger than 64bits
-            if (info.bits > 64) return self.fail("TODO: Implement genTypedValue for integer bit size: {d}", .{info.bits});
-            var buf: [8]u8 = undefined;
-            if (info.signedness == .unsigned) {
-                std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt());
-            } else std.mem.writeIntLittle(i64, &buf, val.toSignedInt());
-            try self.code.appendSlice(buf[0..abi_size]);
-            return Result.appended;
-        },
-        .Enum => {
-            const size = @intCast(usize, ty.abiSize(self.target));
-            try self.code.appendNTimes(0xaa, size);
-            return Result.appended;
-        },
-        .Bool => {
-            const int_byte: u8 = @boolToInt(val.toBool());
-            try self.code.append(int_byte);
-            return Result.appended;
-        },
-        .Struct => {
-            const field_vals = val.castTag(.@"struct").?.data;
-            for (field_vals) |field_val, index| {
-                const field_ty = ty.structFieldType(index);
-                if (!field_ty.hasCodeGenBits()) continue;
-                switch (try self.genTypedValue(field_ty, field_val)) {
-                    .appended => {},
-                    .externally_managed => |payload| try self.code.appendSlice(payload),
-                }
-            }
-            return Result.appended;
-        },
-        .Union => {
-            // TODO: Implement Union declarations
-            const abi_size = @intCast(usize, ty.abiSize(self.target));
-            try self.code.appendNTimes(0xaa, abi_size);
-            return Result.appended;
-        },
-        .Pointer => switch (val.tag()) {
-            .variable => {
-                const decl = val.castTag(.variable).?.data.owner_decl;
-                return try self.lowerDeclRef(ty, val, decl);
+            .Union => {
+                // TODO: Implement Union declarations
+                try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
+                return Result.appended;
             },
-            .decl_ref => {
-                const decl = val.castTag(.decl_ref).?.data;
-                return try self.lowerDeclRef(ty, val, decl);
+            .Pointer => switch (val.tag()) {
+                .variable => {
+                    const decl = val.castTag(.variable).?.data.owner_decl;
+                    return try self.lowerDeclRef(ty, val, decl, writer);
+                },
+                .decl_ref => {
+                    const decl = val.castTag(.decl_ref).?.data;
+                    return try self.lowerDeclRef(ty, val, decl, writer);
+                },
+                .slice => {
+                    const slice = val.castTag(.slice).?.data;
+                    var buf: Type.SlicePtrFieldTypeBuffer = undefined;
+                    const ptr_ty = ty.slicePtrFieldType(&buf);
+                    switch (try self.genTypedValue(ptr_ty, slice.ptr, writer)) {
+                        .externally_managed => |data| try writer.writeAll(data),
+                        .appended => {},
+                    }
+                    switch (try self.genTypedValue(Type.usize, slice.len, writer)) {
+                        .externally_managed => |data| try writer.writeAll(data),
+                        .appended => {},
+                    }
+                    return Result.appended;
+                },
+                else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
             },
-            .slice => {
-                const slice = val.castTag(.slice).?.data;
-                var buf: Type.SlicePtrFieldTypeBuffer = undefined;
-                const ptr_ty = ty.slicePtrFieldType(&buf);
-                switch (try self.genTypedValue(ptr_ty, slice.ptr)) {
-                    .externally_managed => |data| try self.code.appendSlice(data),
+            .ErrorUnion => {
+                const error_ty = ty.errorUnionSet();
+                const payload_ty = ty.errorUnionPayload();
+                const is_pl = val.errorUnionIsPayload();
+
+                const err_val = if (!is_pl) val else Value.initTag(.zero);
+                switch (try self.genTypedValue(error_ty, err_val, writer)) {
+                    .externally_managed => |data| try writer.writeAll(data),
                     .appended => {},
                 }
-                switch (try self.genTypedValue(Type.usize, slice.len)) {
-                    .externally_managed => |data| try self.code.appendSlice(data),
-                    .appended => {},
+
+                if (payload_ty.hasCodeGenBits()) {
+                    const pl_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
+                    switch (try self.genTypedValue(payload_ty, pl_val, writer)) {
+                        .externally_managed => |data| try writer.writeAll(data),
+                        .appended => {},
+                    }
                 }
+
                 return Result.appended;
             },
-            else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
-        },
-        .ErrorUnion => {
-            const error_ty = ty.errorUnionSet();
-            const payload_ty = ty.errorUnionPayload();
-            const is_pl = val.errorUnionIsPayload();
-
-            const err_val = if (!is_pl) val else Value.initTag(.zero);
-            switch (try self.genTypedValue(error_ty, err_val)) {
-                .externally_managed => |data| try self.code.appendSlice(data),
-                .appended => {},
-            }
-
-            if (payload_ty.hasCodeGenBits()) {
-                const pl_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
-                switch (try self.genTypedValue(payload_ty, pl_val)) {
-                    .externally_managed => |data| try self.code.appendSlice(data),
-                    .appended => {},
+            .ErrorSet => {
+                switch (val.tag()) {
+                    .@"error" => {
+                        const name = val.castTag(.@"error").?.data.name;
+                        const kv = try self.module.getErrorValue(name);
+                        try writer.writeIntLittle(u32, kv.value);
+                    },
+                    else => {
+                        try writer.writeByteNTimes(0, @intCast(usize, ty.abiSize(self.target())));
+                    },
                 }
-            }
-
-            return Result.appended;
-        },
-        .ErrorSet => {
-            switch (val.tag()) {
-                .@"error" => {
-                    const name = val.castTag(.@"error").?.data.name;
-                    const value = self.global_error_set.get(name).?;
-                    try self.code.writer().writeIntLittle(u32, value);
-                },
-                else => {
-                    const abi_size = @intCast(usize, ty.abiSize(self.target));
-                    try self.code.appendNTimes(0, abi_size);
-                },
-            }
-            return Result.appended;
-        },
-        else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
+                return Result.appended;
+            },
+            else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
+        }
     }
-}
 
-fn lowerDeclRef(self: *Self, ty: Type, val: Value, decl: *Module.Decl) InnerError!Result {
-    if (ty.isSlice()) {
-        var buf: Type.SlicePtrFieldTypeBuffer = undefined;
-        const slice_ty = ty.slicePtrFieldType(&buf);
-        switch (try self.genTypedValue(slice_ty, val)) {
-            .appended => {},
-            .externally_managed => |payload| try self.code.appendSlice(payload),
+    fn lowerDeclRef(self: *DeclGen, ty: Type, val: Value, decl: *Module.Decl, writer: anytype) InnerError!Result {
+        if (ty.isSlice()) {
+            var buf: Type.SlicePtrFieldTypeBuffer = undefined;
+            const slice_ty = ty.slicePtrFieldType(&buf);
+            switch (try self.genTypedValue(slice_ty, val, writer)) {
+                .appended => {},
+                .externally_managed => |payload| try writer.writeAll(payload),
+            }
+            var slice_len: Value.Payload.U64 = .{
+                .base = .{ .tag = .int_u64 },
+                .data = val.sliceLen(),
+            };
+            return try self.genTypedValue(Type.usize, Value.initPayload(&slice_len.base), writer);
         }
-        var slice_len: Value.Payload.U64 = .{
-            .base = .{ .tag = .int_u64 },
-            .data = val.sliceLen(),
-        };
-        return try self.genTypedValue(Type.usize, Value.initPayload(&slice_len.base));
-    }
-
-    const offset = @intCast(u32, self.code.items.len);
-    const atom = &self.decl.link.wasm;
-    const target_sym_index = decl.link.wasm.sym_index;
-    decl.markAlive();
-    if (decl.ty.zigTypeTag() == .Fn) {
-        // We found a function pointer, so add it to our table,
-        // as function pointers are not allowed to be stored inside the data section,
-        // but rather in a function table which are called by index
-        try self.bin_file.addTableFunction(target_sym_index);
-        try atom.relocs.append(self.gpa, .{
-            .index = target_sym_index,
-            .offset = offset,
-            .relocation_type = .R_WASM_TABLE_INDEX_I32,
-        });
-    } else {
-        try atom.relocs.append(self.gpa, .{
-            .index = target_sym_index,
-            .offset = offset,
-            .relocation_type = .R_WASM_MEMORY_ADDR_I32,
-        });
-    }
-    const ptr_width = @intCast(usize, self.target.cpu.arch.ptrBitWidth() / 8);
-    try self.code.appendNTimes(0xaa, ptr_width);
 
-    return Result.appended;
-}
+        decl.markAlive();
+        try writer.writeIntLittle(u32, try self.bin_file.getDeclVAddr(
+            self.decl, // The decl containing the source symbol index
+            decl.ty, // type we generate the address of
+            self.symbol_index, // source symbol index
+            decl.link.wasm.sym_index, // target symbol index
+            @intCast(u32, self.code.items.len), // offset
+        ));
+        return Result.appended;
+    }
+};
 
 const CallWValues = struct {
     args: []WValue,
@@ -1086,7 +1096,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
     const ret_ty = fn_ty.fnReturnType();
     // Check if we store the result as a pointer to the stack rather than
     // by value
-    if (self.isByRef(ret_ty)) {
+    if (isByRef(ret_ty, self.target)) {
         // the sret arg will be passed as first argument, therefore we
         // set the `return_value` before allocating locals for regular args.
         result.return_value = .{ .local = self.local_index };
@@ -1213,8 +1223,8 @@ fn arch(self: *const Self) std.Target.Cpu.Arch {
 }
 
 /// For a given `Type`, will return true when the type will be passed
-/// by reference, rather than by value.
-fn isByRef(self: Self, ty: Type) bool {
+/// by reference, rather than by value
+fn isByRef(ty: Type, target: std.Target) bool {
     switch (ty.zigTypeTag()) {
         .Type,
         .ComptimeInt,
@@ -1242,7 +1252,7 @@ fn isByRef(self: Self, ty: Type) bool {
         .Frame,
         .Union,
         => return ty.hasCodeGenBits(),
-        .Int => return if (ty.intInfo(self.target).bits > 64) true else false,
+        .Int => return if (ty.intInfo(target).bits > 64) true else false,
         .ErrorUnion => {
             const has_tag = ty.errorUnionSet().hasCodeGenBits();
             const has_pl = ty.errorUnionPayload().hasCodeGenBits();
@@ -1470,7 +1480,7 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const child_type = self.air.typeOfIndex(inst).childType();
     if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
 
-    if (self.isByRef(child_type)) {
+    if (isByRef(child_type, self.target)) {
         return self.return_value;
     }
 
@@ -1487,7 +1497,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ret_ty = self.air.typeOf(un_op).childType();
     if (!ret_ty.hasCodeGenBits()) return WValue.none;
 
-    if (!self.isByRef(ret_ty)) {
+    if (!isByRef(ret_ty, self.target)) {
         const result = try self.load(operand, ret_ty, 0);
         try self.emitWValue(result);
     }
@@ -1509,7 +1519,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
         else => unreachable,
     };
     const ret_ty = fn_ty.fnReturnType();
-    const first_param_sret = self.isByRef(ret_ty);
+    const first_param_sret = isByRef(ret_ty, self.target);
 
     const target: ?*Decl = blk: {
         const func_val = self.air.value(pl_op.operand) orelse break :blk null;
@@ -1546,7 +1556,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
         const operand = try self.resolveInst(pl_op.operand);
         try self.emitWValue(operand);
 
-        var fn_type = try self.genFunctype(fn_ty);
+        var fn_type = try genFunctype(self.gpa, fn_ty, self.target);
         defer fn_type.deinit(self.gpa);
 
         const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);
@@ -1642,7 +1652,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
     }
     try self.emitWValue(lhs);
     try self.emitWValue(rhs);
-    const valtype = try self.typeToValtype(ty);
+    const valtype = typeToValtype(ty, self.target);
     // check if we should pass by pointer or value based on ABI size
     // TODO: Implement a way to get ABI values from a given type,
     // that is portable across the backend, rather than copying logic.
@@ -1675,7 +1685,7 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     if (!ty.hasCodeGenBits()) return WValue{ .none = {} };
 
-    if (self.isByRef(ty)) {
+    if (isByRef(ty, self.target)) {
         const new_local = try self.allocStack(ty);
         try self.store(new_local, operand, ty, 0);
         return new_local;
@@ -1712,7 +1722,7 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
     };
 
     const opcode = buildOpcode(.{
-        .valtype1 = try self.typeToValtype(ty),
+        .valtype1 = typeToValtype(ty, self.target),
         .width = abi_size * 8, // use bitsize instead of byte size
         .op = .load,
         .signedness = signedness,
@@ -1743,7 +1753,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
     const rhs = try self.resolveInst(bin_op.rhs);
     const operand_ty = self.air.typeOfIndex(inst);
 
-    if (self.isByRef(operand_ty)) {
+    if (isByRef(operand_ty, self.target)) {
         return self.fail("TODO: Implement binary operation for type: {}", .{operand_ty});
     }
 
@@ -1753,7 +1763,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
     const bin_ty = self.air.typeOf(bin_op.lhs);
     const opcode: wasm.Opcode = buildOpcode(.{
         .op = op,
-        .valtype1 = try self.typeToValtype(bin_ty),
+        .valtype1 = typeToValtype(bin_ty, self.target),
         .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
     });
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
@@ -1775,7 +1785,7 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
     const bin_ty = self.air.typeOf(bin_op.lhs);
     const opcode: wasm.Opcode = buildOpcode(.{
         .op = op,
-        .valtype1 = try self.typeToValtype(bin_ty),
+        .valtype1 = typeToValtype(bin_ty, self.target),
         .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
     });
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
@@ -1924,8 +1934,8 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
         },
         .ErrorSet => switch (val.tag()) {
             .@"error" => {
-                const error_index = self.global_error_set.get(val.getError().?).?;
-                return WValue{ .imm32 = error_index };
+                const kv = try self.module.getErrorValue(val.getError().?);
+                return WValue{ .imm32 = kv.value };
             },
             else => return WValue{ .imm32 = 0 },
         },
@@ -1945,7 +1955,7 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
                 if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef),
                 payload_type,
             );
-            const pl_ptr = if (self.isByRef(payload_type))
+            const pl_ptr = if (isByRef(payload_type, self.target))
                 try self.buildPointerOffset(result, error_type.abiSize(self.target), .new)
             else
                 result;
@@ -2159,8 +2169,8 @@ fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
             .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
         },
         .ErrorSet => {
-            const error_index = self.global_error_set.get(val.getError().?).?;
-            return @bitCast(i32, error_index);
+            const kv = self.module.getErrorValue(val.getError().?) catch unreachable; // passed invalid `Value` to function
+            return @bitCast(i32, kv.value);
         },
         else => unreachable, // Programmer called this function for an illegal type
     }
@@ -2168,7 +2178,7 @@ fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
 
 fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
-    const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
+    const block_ty = genBlockType(self.air.getRefType(ty_pl.ty), self.target);
     const extra = self.air.extraData(Air.Block, ty_pl.payload);
     const body = self.air.extra[extra.end..][0..extra.data.body_len];
 
@@ -2265,7 +2275,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
             // both lhs and rhs, as well as checking the payload are matching of lhs and rhs
             return self.cmpOptionals(lhs, rhs, operand_ty, op);
         }
-    } else if (self.isByRef(operand_ty)) {
+    } else if (isByRef(operand_ty, self.target)) {
         return self.cmpBigInt(lhs, rhs, operand_ty, op);
     }
 
@@ -2280,7 +2290,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
         break :blk operand_ty.intInfo(self.target).signedness;
     };
     const opcode: wasm.Opcode = buildOpcode(.{
-        .valtype1 = try self.typeToValtype(operand_ty),
+        .valtype1 = typeToValtype(operand_ty, self.target),
         .op = switch (op) {
             .lt => .lt,
             .lte => .le,
@@ -2353,13 +2363,6 @@ fn airUnreachable(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 fn airBitcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const operand = try self.resolveInst(ty_op.operand);
-    // if (operand == .constant) {
-    //     std.debug.print("Let's take a look at this!!!!!!\n--------------------\n", .{});
-    //     const result = try self.allocLocal(self.air.typeOfIndex(inst));
-    //     try self.emitWValue(operand);
-    //     try self.addLabel(.local_set, result.local);
-    //     return result;
-    // }
     return operand;
 }
 
@@ -2407,7 +2410,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
         return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});
     };
 
-    if (self.isByRef(field_ty)) {
+    if (isByRef(field_ty, self.target)) {
         return self.buildPointerOffset(operand, offset, .new);
     }
 
@@ -2527,7 +2530,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
                 const val = try self.lowerConstant(case.values[0].value, target_ty);
                 try self.emitWValue(val);
                 const opcode = buildOpcode(.{
-                    .valtype1 = try self.typeToValtype(target_ty),
+                    .valtype1 = typeToValtype(target_ty, self.target),
                     .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
                     .signedness = signedness,
                 });
@@ -2541,7 +2544,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
                     const val = try self.lowerConstant(value.value, target_ty);
                     try self.emitWValue(val);
                     const opcode = buildOpcode(.{
-                        .valtype1 = try self.typeToValtype(target_ty),
+                        .valtype1 = typeToValtype(target_ty, self.target),
                         .op = .eq,
                         .signedness = signedness,
                     });
@@ -2596,7 +2599,7 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
     const payload_ty = err_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) return WValue{ .none = {} };
     const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target));
-    if (self.isByRef(payload_ty)) {
+    if (isByRef(payload_ty, self.target)) {
         return self.buildPointerOffset(operand, offset, .new);
     }
     return try self.load(operand, payload_ty, offset);
@@ -2725,7 +2728,7 @@ fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
 
-    if (self.isByRef(payload_ty)) {
+    if (isByRef(payload_ty, self.target)) {
         return self.buildPointerOffset(operand, offset, .new);
     }
 
@@ -2856,7 +2859,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const result = try self.allocLocal(elem_ty);
     try self.addLabel(.local_set, result.local);
 
-    if (self.isByRef(elem_ty)) {
+    if (isByRef(elem_ty, self.target)) {
         return result;
     }
     return try self.load(result, elem_ty, 0);
@@ -3017,7 +3020,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     const result = try self.allocLocal(elem_ty);
     try self.addLabel(.local_set, result.local);
-    if (self.isByRef(elem_ty)) {
+    if (isByRef(elem_ty, self.target)) {
         return result;
     }
     return try self.load(result, elem_ty, 0);
@@ -3064,7 +3067,7 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
         else => ptr_ty.childType(),
     };
 
-    const valtype = try self.typeToValtype(Type.usize);
+    const valtype = typeToValtype(Type.usize, self.target);
     const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul });
     const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op });
 
@@ -3167,7 +3170,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const result = try self.allocLocal(elem_ty);
     try self.addLabel(.local_set, result.local);
 
-    if (self.isByRef(elem_ty)) {
+    if (isByRef(elem_ty, self.target)) {
         return result;
     }
     return try self.load(result, elem_ty, 0);
@@ -3184,8 +3187,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     try self.emitWValue(operand);
     const op = buildOpcode(.{
         .op = .trunc,
-        .valtype1 = try self.typeToValtype(dest_ty),
-        .valtype2 = try self.typeToValtype(op_ty),
+        .valtype1 = typeToValtype(dest_ty, self.target),
+        .valtype2 = typeToValtype(op_ty, self.target),
         .signedness = if (dest_ty.isSignedInt()) .signed else .unsigned,
     });
     try self.addTag(Mir.Inst.Tag.fromOpcode(op));
@@ -3249,7 +3252,7 @@ fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std
 
     try self.emitWValue(lhs_pl);
     try self.emitWValue(rhs_pl);
-    const opcode = buildOpcode(.{ .op = .ne, .valtype1 = try self.typeToValtype(payload_ty) });
+    const opcode = buildOpcode(.{ .op = .ne, .valtype1 = typeToValtype(payload_ty, self.target) });
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
     try self.addLabel(.br_if, 0);
 
src/link/Wasm/Atom.zig
@@ -31,6 +31,10 @@ next: ?*Atom,
 /// is null when this atom is the first in its order
 prev: ?*Atom,
 
+/// Contains atoms local to a decl, all managed by this `Atom`.
+/// When the parent atom is being freed, it will also do so for all local atoms.
+locals: std.ArrayListUnmanaged(Atom) = .{},
+
 /// Represents a default empty wasm `Atom`
 pub const empty: Atom = .{
     .alignment = 0,
@@ -45,6 +49,11 @@ pub const empty: Atom = .{
 pub fn deinit(self: *Atom, gpa: Allocator) void {
     self.relocs.deinit(gpa);
     self.code.deinit(gpa);
+
+    while (self.locals.popOrNull()) |*local| {
+        local.deinit(gpa);
+    }
+    self.locals.deinit(gpa);
 }
 
 /// Sets the length of relocations and code to '0',
@@ -72,6 +81,15 @@ pub fn getFirst(self: *Atom) *Atom {
     return tmp;
 }
 
+/// Returns the atom for the given `symbol_index`.
+/// This can be either the `Atom` itself, or one of its locals.
+pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom {
+    if (self.sym_index == symbol_index) return self;
+    return for (self.locals.items) |*local_atom| {
+        if (local_atom.sym_index == symbol_index) break local_atom;
+    } else unreachable; // Used a symbol index not present in this atom or its children.
+}
+
 /// Resolves the relocations within the atom, writing the new value
 /// at the calculated offset.
 pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
src/link/Wasm.zig
@@ -234,12 +234,12 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
         .locals = .{},
         .target = self.base.options.target,
         .bin_file = self,
-        .global_error_set = self.base.options.module.?.global_error_set,
+        .module = module,
     };
     defer codegen.deinit();
 
     // generate the 'code' section for the function declaration
-    const result = codegen.genFunc() catch |err| switch (err) {
+    codegen.genFunc() catch |err| switch (err) {
         error.CodegenFail => {
             decl.analysis = .codegen_failure;
             try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
@@ -247,7 +247,7 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
         },
         else => |e| return e,
     };
-    return self.finishUpdateDecl(decl, result, &codegen);
+    return self.finishUpdateDecl(decl, codegen.code.items);
 }
 
 // Generate code for the Decl, storing it in memory to be later written to
@@ -264,40 +264,37 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
 
     decl.link.wasm.clear();
 
-    var codegen: CodeGen = .{
+    var code_writer = std.ArrayList(u8).init(self.base.allocator);
+    defer code_writer.deinit();
+    var decl_gen: CodeGen.DeclGen = .{
         .gpa = self.base.allocator,
-        .air = undefined,
-        .liveness = undefined,
-        .values = .{},
-        .code = std.ArrayList(u8).init(self.base.allocator),
         .decl = decl,
-        .err_msg = undefined,
-        .locals = .{},
-        .target = self.base.options.target,
+        .symbol_index = decl.link.wasm.sym_index,
         .bin_file = self,
-        .global_error_set = self.base.options.module.?.global_error_set,
+        .err_msg = undefined,
+        .code = &code_writer,
+        .module = module,
     };
-    defer codegen.deinit();
 
     // generate the 'code' section for the function declaration
-    const result = codegen.genDecl() catch |err| switch (err) {
+    const result = decl_gen.genDecl() catch |err| switch (err) {
         error.CodegenFail => {
             decl.analysis = .codegen_failure;
-            try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
+            try module.failed_decls.put(module.gpa, decl, decl_gen.err_msg);
             return;
         },
         else => |e| return e,
     };
 
-    return self.finishUpdateDecl(decl, result, &codegen);
-}
-
-fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {
-    const code: []const u8 = switch (result) {
-        .appended => @as([]const u8, codegen.code.items),
-        .externally_managed => |payload| payload,
+    const code = switch (result) {
+        .externally_managed => |data| data,
+        .appended => code_writer.items,
     };
 
+    return self.finishUpdateDecl(decl, code);
+}
+
+fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void {
     if (decl.isExtern()) {
         try self.addOrUpdateImport(decl);
         return;
@@ -313,7 +310,7 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod
 
 /// Creates a new local symbol for a given type (and its bytes it's represented by)
 /// and then append it as a 'contained' atom onto the Decl.
-pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []const u8) !u32 {
+pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type) !u32 {
     assert(ty.zigTypeTag() != .Fn); // cannot create local symbols for functions
     var symbol: Symbol = .{
         .name = "unnamed_local",
@@ -325,9 +322,7 @@ pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []cons
     symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
 
     var atom = Atom.empty;
-    atom.size = @intCast(u32, code.len);
     atom.alignment = ty.abiAlignment(self.base.options.target);
-    try atom.code.appendSlice(self.base.allocator, code);
 
     if (self.symbols_free_list.popOrNull()) |index| {
         atom.sym_index = index;
@@ -341,6 +336,41 @@ pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []cons
     return atom.sym_index;
 }
 
+pub fn updateLocalSymbolCode(self: *Wasm, decl: *Module.Decl, symbol_index: u32, code: []const u8) !void {
+    const atom = decl.link.wasm.symbolAtom(symbol_index);
+    atom.size = @intCast(u32, code.len);
+    try atom.code.appendSlice(self.base.allocator, code);
+}
+
+/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
+/// Returns the given pointer address
+pub fn getDeclVAddr(self: *Wasm, decl: *Module.Decl, ty: Type, symbol_index: u32, target_symbol_index: u32, offset: u32) !u32 {
+    const atom = decl.link.wasm.symbolAtom(symbol_index);
+    const is_wasm32 = self.base.options.target.cpu.arch == .wasm32;
+    if (ty.zigTypeTag() == .Fn) {
+        // We found a function pointer, so add it to our table,
+        // as function pointers are not allowed to be stored inside the data section.
+        // They are instead stored in a function table which are called by index.
+        try self.addTableFunction(target_symbol_index);
+        try atom.relocs.append(self.base.allocator, .{
+            .index = target_symbol_index,
+            .offset = offset,
+            .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
+        });
+    } else {
+        try atom.relocs.append(self.base.allocator, .{
+            .index = target_symbol_index,
+            .offset = offset,
+            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
+        });
+    }
+    // we do not know the final address at this point,
+    // as atom allocation will determine the address and relocations
+    // will calculate and rewrite this. Therefore, we simply return the symbol index
+    // that was targeted.
+    return target_symbol_index;
+}
+
 pub fn updateDeclExports(
     self: *Wasm,
     module: *Module,