Commit b4369dfbda

antlilja <liljaanton2001@gmail.com>
2023-08-13 16:04:18
LLVM: Add toBitcode to Builder
1 parent 5bd2a7c
Changed files (1)
src
codegen
src/codegen/llvm/Builder.zig
@@ -1121,7 +1121,7 @@ pub const Attribute = union(Kind) {
                 => |kind| {
                     const field = comptime blk: {
                         @setEvalBranchQuota(10_000);
-                        inline for (@typeInfo(Attribute).Union.fields) |field| {
+                        for (@typeInfo(Attribute).Union.fields) |field| {
                             if (std.mem.eql(u8, field.name, @tagName(kind))) break :blk field;
                         }
                         unreachable;
@@ -12042,6 +12042,1476 @@ fn constantExtraData(self: *const Builder, comptime T: type, index: Constant.Ite
     return self.constantExtraDataTrail(T, index).data;
 }
 
+pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]const u32 {
+    const BitcodeWriter = bitcode_writer.BitcodeWriter(&.{ Type, FunctionAttributes });
+    var bitcode = BitcodeWriter.init(allocator, &.{
+        std.math.log2_int_ceil(usize, self.type_items.items.len - 1),
+        std.math.log2_int_ceil(usize, self.function_attributes_set.count() - 1),
+    });
+    errdefer bitcode.deinit();
+
+    // Write LLVM IR magic
+    try bitcode.writeBits(IR.MAGIC, 32);
+
+    var record = std.ArrayListUnmanaged(u64){};
+    defer record.deinit(self.gpa);
+
+    // IDENTIFICATION_BLOCK
+    {
+        const Identification = IR.Identification;
+        var identification_block = try bitcode.enterTopBlock(Identification);
+
+        const producer = try std.fmt.allocPrint(self.gpa, "zig {d}.{d}.{d}", .{
+            build_options.semver.major,
+            build_options.semver.minor,
+            build_options.semver.patch,
+        });
+        defer self.gpa.free(producer);
+
+        try identification_block.writeAbbrev(Identification.Version{ .string = producer });
+        try identification_block.writeAbbrev(Identification.Epoch{ .epoch = 0 });
+
+        try identification_block.end();
+    }
+
+    // MODULE_BLOCK
+    {
+        const Module = IR.Module;
+        var module_block = try bitcode.enterTopBlock(Module);
+
+        try module_block.writeAbbrev(Module.Version{});
+
+        if (self.target_triple.slice(self)) |triple| {
+            try module_block.writeAbbrev(Module.String{
+                .code = 2,
+                .string = triple,
+            });
+        }
+
+        if (self.data_layout.slice(self)) |data_layout| {
+            try module_block.writeAbbrev(Module.String{
+                .code = 3,
+                .string = data_layout,
+            });
+        }
+
+        if (self.source_filename.slice(self)) |source_filename| {
+            try module_block.writeAbbrev(Module.String{
+                .code = 16,
+                .string = source_filename,
+            });
+        }
+
+        if (self.module_asm.items.len != 0) {
+            try module_block.writeAbbrev(Module.String{
+                .code = 4,
+                .string = self.module_asm.items,
+            });
+        }
+
+        // TYPE_BLOCK
+        {
+            var type_block = try module_block.enterSubBlock(IR.Type);
+
+            try type_block.writeAbbrev(IR.Type.NumEntry{ .num = @intCast(self.type_items.items.len) });
+
+            for (self.type_items.items, 0..) |item, i| {
+                const ty: Type = @enumFromInt(i);
+
+                switch (item.tag) {
+                    .simple => try type_block.writeAbbrev(IR.Type.Simple{ .code = @truncate(item.data) }),
+                    .integer => try type_block.writeAbbrev(IR.Type.Integer{ .width = item.data }),
+                    .structure,
+                    .packed_structure,
+                    => |kind| {
+                        const is_packed = switch (kind) {
+                            .structure => false,
+                            .packed_structure => true,
+                            else => unreachable,
+                        };
+                        var extra = self.typeExtraDataTrail(Type.Structure, item.data);
+                        try type_block.writeAbbrev(IR.Type.StructAnon{
+                            .is_packed = is_packed,
+                            .types = extra.trail.next(extra.data.fields_len, Type, self),
+                        });
+                    },
+                    .named_structure => {
+                        const extra = self.typeExtraData(Type.NamedStructure, item.data);
+                        try type_block.writeAbbrev(IR.Type.StructName{
+                            .string = extra.id.slice(self).?,
+                        });
+
+                        const real_struct = self.type_items.items[@intFromEnum(extra.body)];
+                        const is_packed: bool = switch (real_struct.tag) {
+                            .structure => false,
+                            .packed_structure => true,
+                            else => unreachable,
+                        };
+
+                        var real_extra = self.typeExtraDataTrail(Type.Structure, real_struct.data);
+                        try type_block.writeAbbrev(IR.Type.StructNamed{
+                            .is_packed = is_packed,
+                            .types = real_extra.trail.next(real_extra.data.fields_len, Type, self),
+                        });
+                    },
+                    .array,
+                    .small_array,
+                    => try type_block.writeAbbrev(IR.Type.Array{
+                        .len = ty.aggregateLen(self),
+                        .child = ty.childType(self),
+                    }),
+                    .vector,
+                    .scalable_vector,
+                    => try type_block.writeAbbrev(IR.Type.Vector{
+                        .len = ty.aggregateLen(self),
+                        .child = ty.childType(self),
+                    }),
+                    .pointer => try type_block.writeAbbrev(IR.Type.Pointer{
+                        .addr_space = ty.pointerAddrSpace(self),
+                    }),
+                    .target => {
+                        var extra = self.typeExtraDataTrail(Type.Target, item.data);
+                        try type_block.writeAbbrev(IR.Type.StructName{
+                            .string = extra.data.name.slice(self).?,
+                        });
+
+                        const types = extra.trail.next(extra.data.types_len, Type, self);
+                        const ints = extra.trail.next(extra.data.ints_len, u32, self);
+
+                        try type_block.writeAbbrev(IR.Type.Target{
+                            .num_types = extra.data.types_len,
+                            .types = types,
+                            .ints = ints,
+                        });
+                    },
+                    .function, .vararg_function => |kind| {
+                        const is_vararg = switch (kind) {
+                            .function => false,
+                            .vararg_function => true,
+                            else => unreachable,
+                        };
+                        var extra = self.typeExtraDataTrail(Type.Function, item.data);
+                        try type_block.writeAbbrev(IR.Type.Function{
+                            .is_vararg = is_vararg,
+                            .return_type = extra.data.ret,
+                            .param_types = extra.trail.next(extra.data.params_len, Type, self),
+                        });
+                    },
+                }
+            }
+
+            try type_block.end();
+        }
+
+        var attributes_set: std.AutoArrayHashMapUnmanaged(struct {
+            attributes: Attributes,
+            index: u32,
+        }, void) = .{};
+        defer attributes_set.deinit(self.gpa);
+
+        // PARAMATTR_GROUP_BLOCK
+        {
+            const ParamattrGroup = IR.ParamattrGroup;
+
+            var paramattr_group_block = try module_block.enterSubBlock(ParamattrGroup);
+
+            for (self.function_attributes_set.keys()) |func_attributes| {
+                for (func_attributes.slice(self), 0..) |attributes, i| {
+                    const attributes_slice = attributes.slice(self);
+                    if (attributes_slice.len == 0) continue;
+
+                    const attr_gop = try attributes_set.getOrPut(self.gpa, .{
+                        .attributes = attributes,
+                        .index = @intCast(i),
+                    });
+
+                    if (attr_gop.found_existing) continue;
+
+                    record.clearRetainingCapacity();
+                    try record.ensureUnusedCapacity(self.gpa, 2);
+
+                    record.appendAssumeCapacity(attr_gop.index);
+                    record.appendAssumeCapacity(switch (i) {
+                        0 => 0xffffffff,
+                        else => i - 1,
+                    });
+
+                    for (attributes_slice) |attr_index| {
+                        const kind = attr_index.getKind(self);
+                        switch (attr_index.toAttribute(self)) {
+                            .zeroext,
+                            .signext,
+                            .inreg,
+                            .@"noalias",
+                            .nocapture,
+                            .nofree,
+                            .nest,
+                            .returned,
+                            .nonnull,
+                            .swiftself,
+                            .swiftasync,
+                            .swifterror,
+                            .immarg,
+                            .noundef,
+                            .allocalign,
+                            .allocptr,
+                            .readnone,
+                            .readonly,
+                            .writeonly,
+                            .alwaysinline,
+                            .builtin,
+                            .cold,
+                            .convergent,
+                            .disable_sanitizer_information,
+                            .fn_ret_thunk_extern,
+                            .hot,
+                            .inlinehint,
+                            .jumptable,
+                            .minsize,
+                            .naked,
+                            .nobuiltin,
+                            .nocallback,
+                            .noduplicate,
+                            .noimplicitfloat,
+                            .@"noinline",
+                            .nomerge,
+                            .nonlazybind,
+                            .noprofile,
+                            .skipprofile,
+                            .noredzone,
+                            .noreturn,
+                            .norecurse,
+                            .willreturn,
+                            .nosync,
+                            .nounwind,
+                            .nosanitize_bounds,
+                            .nosanitize_coverage,
+                            .null_pointer_is_valid,
+                            .optforfuzzing,
+                            .optnone,
+                            .optsize,
+                            .returns_twice,
+                            .safestack,
+                            .sanitize_address,
+                            .sanitize_memory,
+                            .sanitize_thread,
+                            .sanitize_hwaddress,
+                            .sanitize_memtag,
+                            .speculative_load_hardening,
+                            .speculatable,
+                            .ssp,
+                            .sspstrong,
+                            .sspreq,
+                            .strictfp,
+                            .nocf_check,
+                            .shadowcallstack,
+                            .mustprogress,
+                            .no_sanitize_address,
+                            .no_sanitize_hwaddress,
+                            .sanitize_address_dyninit,
+                            => {
+                                try record.ensureUnusedCapacity(self.gpa, 2);
+                                record.appendAssumeCapacity(0);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                            },
+                            .byval,
+                            .byref,
+                            .preallocated,
+                            .inalloca,
+                            .sret,
+                            .elementtype,
+                            => |ty| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(6);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@intFromEnum(ty));
+                            },
+                            .@"align",
+                            .alignstack,
+                            => |alignment| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(alignment.toByteUnits() orelse 0);
+                            },
+                            .dereferenceable,
+                            .dereferenceable_or_null,
+                            => |size| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(size);
+                            },
+                            .nofpclass => |fpclass| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@as(u32, @bitCast(fpclass)));
+                            },
+                            .allockind => |allockind| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@as(u32, @bitCast(allockind)));
+                            },
+
+                            .allocsize => |allocsize| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@bitCast(allocsize.toLlvm()));
+                            },
+                            .memory => |memory| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@as(u32, @bitCast(memory)));
+                            },
+                            .uwtable => |uwtable| if (uwtable != .none) {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@intFromEnum(uwtable));
+                            },
+                            .vscale_range => |vscale_range| {
+                                try record.ensureUnusedCapacity(self.gpa, 3);
+                                record.appendAssumeCapacity(1);
+                                record.appendAssumeCapacity(@intFromEnum(kind));
+                                record.appendAssumeCapacity(@bitCast(vscale_range.toLlvm()));
+                            },
+                            .string => |string_attr| {
+                                const string_attr_kind_slice = string_attr.kind.slice(self).?;
+                                const string_attr_value_slice = if (string_attr.value != .none)
+                                    string_attr.value.slice(self).?
+                                else
+                                    null;
+
+                                try record.ensureUnusedCapacity(
+                                    self.gpa,
+                                    2 + string_attr_kind_slice.len + if (string_attr_value_slice) |slice| slice.len + 1 else 0,
+                                );
+                                record.appendAssumeCapacity(if (string_attr.value == .none) 3 else 4);
+                                for (string_attr.kind.slice(self).?) |c| {
+                                    record.appendAssumeCapacity(c);
+                                }
+                                record.appendAssumeCapacity(0);
+                                if (string_attr_value_slice) |slice| {
+                                    for (slice) |c| {
+                                        record.appendAssumeCapacity(c);
+                                    }
+                                    record.appendAssumeCapacity(0);
+                                }
+                            },
+                            .none => unreachable,
+                        }
+                    }
+
+                    try paramattr_group_block.writeUnabbrev(3, record.items);
+                }
+            }
+
+            try paramattr_group_block.end();
+        }
+
+        // PARAMATTR_BLOCK
+        {
+            const Paramattr = IR.Paramattr;
+            var paramattr_block = try module_block.enterSubBlock(Paramattr);
+
+            for (self.function_attributes_set.keys()) |func_attributes| {
+                const func_attributes_slice = func_attributes.slice(self);
+                record.clearRetainingCapacity();
+                try record.ensureUnusedCapacity(self.gpa, func_attributes_slice.len);
+                for (func_attributes_slice, 0..) |attributes, i| {
+                    const attributes_slice = attributes.slice(self);
+                    if (attributes_slice.len == 0) continue;
+
+                    const group_index = attributes_set.getIndex(.{
+                        .attributes = attributes,
+                        .index = @intCast(i),
+                    }) orelse unreachable;
+                    record.appendAssumeCapacity(@intCast(group_index));
+                }
+
+                try paramattr_block.writeAbbrev(Paramattr.Entry{ .group_indices = record.items });
+            }
+
+            try paramattr_block.end();
+        }
+
+        var globals = std.AutoArrayHashMapUnmanaged(Global.Index, void){};
+        defer globals.deinit(self.gpa);
+        try globals.ensureUnusedCapacity(
+            self.gpa,
+            self.variables.items.len +
+                self.functions.items.len +
+                self.aliases.items.len,
+        );
+
+        for (self.variables.items) |variable| {
+            if (variable.global.getReplacement(self) != .none) continue;
+
+            globals.putAssumeCapacity(variable.global, {});
+        }
+
+        for (self.functions.items) |function| {
+            if (function.global.getReplacement(self) != .none) continue;
+
+            globals.putAssumeCapacity(function.global, {});
+        }
+
+        for (self.aliases.items) |alias| {
+            if (alias.global.getReplacement(self) != .none) continue;
+
+            globals.putAssumeCapacity(alias.global, {});
+        }
+
+        const ConstantAdapter = struct {
+            const ConstantAdapter = @This();
+            builder: *const Builder,
+            globals: *const std.AutoArrayHashMapUnmanaged(Global.Index, void),
+
+            pub fn get(adapter: @This(), param: anytype, comptime field_name: []const u8) @TypeOf(param) {
+                _ = field_name;
+                return switch (@TypeOf(param)) {
+                    Constant => @enumFromInt(adapter.getConstantIndex(param)),
+                    else => param,
+                };
+            }
+
+            pub fn getConstantIndex(adapter: ConstantAdapter, constant: Constant) u32 {
+                return switch (constant.unwrap()) {
+                    .constant => |c| c + adapter.numGlobals(),
+                    .global => |global| @intCast(adapter.globals.getIndex(global.unwrap(adapter.builder)).?),
+                };
+            }
+
+            pub fn numConstants(adapter: ConstantAdapter) u32 {
+                return @intCast(adapter.globals.count() + adapter.builder.constant_items.len);
+            }
+
+            pub fn numGlobals(adapter: ConstantAdapter) u32 {
+                return @intCast(adapter.globals.count());
+            }
+        };
+
+        const constant_adapter = ConstantAdapter{
+            .builder = self,
+            .globals = &globals,
+        };
+
+        // Globals
+        {
+            var section_map: std.AutoArrayHashMapUnmanaged(String, void) = .{};
+            defer section_map.deinit(self.gpa);
+            try section_map.ensureUnusedCapacity(self.gpa, globals.count());
+
+            for (self.variables.items) |variable| {
+                if (variable.global.getReplacement(self) != .none) continue;
+
+                const section = blk: {
+                    if (variable.section == .none) break :blk 0;
+                    const gop = section_map.getOrPutAssumeCapacity(variable.section);
+                    if (!gop.found_existing) {
+                        try module_block.writeAbbrev(Module.String{
+                            .code = 5,
+                            .string = variable.section.slice(self) orelse unreachable,
+                        });
+                    }
+                    break :blk gop.index + 1;
+                };
+
+                const initid = if (variable.init == .no_init)
+                    0
+                else
+                    (constant_adapter.getConstantIndex(variable.init) + 1);
+
+                const strtab = variable.global.strtab(self);
+
+                const global = variable.global.ptrConst(self);
+                try module_block.writeAbbrev(Module.Variable{
+                    .strtab_offset = strtab.offset,
+                    .strtab_size = strtab.size,
+                    .type_index = global.type,
+                    .is_const = .{
+                        .is_const = switch (variable.mutability) {
+                            .global => false,
+                            .constant => true,
+                        },
+                        .addr_space = global.addr_space,
+                    },
+                    .initid = initid,
+                    .linkage = global.linkage,
+                    .alignment = variable.alignment.toLlvm(),
+                    .section = section,
+                    .visibility = global.visibility,
+                    .thread_local = variable.thread_local,
+                    .unnamed_addr = global.unnamed_addr,
+                    .externally_initialized = global.externally_initialized,
+                    .dllstorageclass = global.dll_storage_class,
+                    .preemption = global.preemption,
+                });
+            }
+
+            for (self.functions.items) |func| {
+                if (func.global.getReplacement(self) != .none) continue;
+
+                const section = blk: {
+                    if (func.section == .none) break :blk 0;
+                    const gop = section_map.getOrPutAssumeCapacity(func.section);
+                    if (!gop.found_existing) {
+                        try module_block.writeAbbrev(Module.String{
+                            .code = 5,
+                            .string = func.section.slice(self) orelse unreachable,
+                        });
+                    }
+                    break :blk gop.index + 1;
+                };
+
+                const paramattr_index = if (self.function_attributes_set.getIndex(func.attributes)) |index|
+                    index + 1
+                else
+                    0;
+
+                const strtab = func.global.strtab(self);
+
+                const global = func.global.ptrConst(self);
+                try module_block.writeAbbrev(Module.Function{
+                    .strtab_offset = strtab.offset,
+                    .strtab_size = strtab.size,
+                    .type_index = global.type,
+                    .call_conv = func.call_conv,
+                    .is_proto = func.instructions.len == 0,
+                    .linkage = global.linkage,
+                    .paramattr = paramattr_index,
+                    .alignment = func.alignment.toLlvm(),
+                    .section = section,
+                    .visibility = global.visibility,
+                    .unnamed_addr = global.unnamed_addr,
+                    .dllstorageclass = global.dll_storage_class,
+                    .preemption = global.preemption,
+                    .addr_space = global.addr_space,
+                });
+            }
+
+            for (self.aliases.items) |alias| {
+                if (alias.global.getReplacement(self) != .none) continue;
+
+                const strtab = alias.global.strtab(self);
+
+                const global = alias.global.ptrConst(self);
+                try module_block.writeAbbrev(Module.Alias{
+                    .strtab_offset = strtab.offset,
+                    .strtab_size = strtab.size,
+                    .type_index = global.type,
+                    .addr_space = global.addr_space,
+                    .aliasee = constant_adapter.getConstantIndex(alias.aliasee),
+                    .linkage = global.linkage,
+                    .visibility = global.visibility,
+                    .thread_local = alias.thread_local,
+                    .unnamed_addr = global.unnamed_addr,
+                    .dllstorageclass = global.dll_storage_class,
+                    .preemption = global.preemption,
+                });
+            }
+        }
+
+        // CONSTANTS_BLOCK
+        {
+            const Constants = IR.Constants;
+            var constants_block = try module_block.enterSubBlock(Constants);
+
+            var current_type: Type = .none;
+            const tags = self.constant_items.items(.tag);
+            const datas = self.constant_items.items(.data);
+            for (0..self.constant_items.len) |index| {
+                record.clearRetainingCapacity();
+                const constant: Constant = @enumFromInt(index);
+                const constant_type = constant.typeOf(self);
+                if (constant_type != current_type) {
+                    try constants_block.writeAbbrev(Constants.SetType{ .type_id = constant_type });
+                    current_type = constant_type;
+                }
+                const data = datas[index];
+                switch (tags[index]) {
+                    .null,
+                    .zeroinitializer,
+                    .none,
+                    => try constants_block.writeAbbrev(Constants.Null{}),
+                    .undef => try constants_block.writeAbbrev(Constants.Undef{}),
+                    .poison => try constants_block.writeAbbrev(Constants.Poison{}),
+                    .positive_integer,
+                    .negative_integer,
+                    => |tag| {
+                        const extra: *align(@alignOf(std.math.big.Limb)) Constant.Integer =
+                            @ptrCast(self.constant_limbs.items[data..][0..Constant.Integer.limbs]);
+                        const limbs = self.constant_limbs
+                            .items[data + Constant.Integer.limbs ..][0..extra.limbs_len];
+                        const bigint = std.math.big.int.Const{
+                            .limbs = limbs,
+                            .positive = tag == .positive_integer,
+                        };
+
+                        const bit_count = extra.type.scalarBits(self);
+                        if (bit_count <= 64) {
+                            const val = bigint.to(i64) catch unreachable;
+                            const emit_val = if (tag == .positive_integer)
+                                @shlWithOverflow(val, 1)[0]
+                            else
+                                (@shlWithOverflow(@addWithOverflow(~val, 1)[0], 1)[0] | 1);
+                            try constants_block.writeAbbrev(Constants.Integer{ .value = @bitCast(emit_val) });
+                        } else {
+                            const word_count = std.mem.alignForward(u24, bit_count, 64) / 64;
+                            try record.ensureUnusedCapacity(self.gpa, word_count);
+                            const buffer: [*]u8 = @ptrCast(record.items.ptr);
+                            bigint.writeTwosComplement(buffer[0..(word_count * 8)], .little);
+
+                            const signed_buffer: [*]i64 = @ptrCast(record.items.ptr);
+                            for (signed_buffer[0..word_count], 0..) |val, i| {
+                                signed_buffer[i] = if (val >= 0)
+                                    @shlWithOverflow(val, 1)[0]
+                                else
+                                    (@shlWithOverflow(@addWithOverflow(~val, 1)[0], 1)[0] | 1);
+                            }
+
+                            try constants_block.writeUnabbrev(5, record.items.ptr[0..word_count]);
+                        }
+                    },
+                    .half,
+                    .bfloat,
+                    => try constants_block.writeAbbrev(Constants.Half{ .value = @truncate(data) }),
+                    .float => try constants_block.writeAbbrev(Constants.Float{ .value = data }),
+                    .double => {
+                        const extra = self.constantExtraData(Constant.Double, data);
+                        try constants_block.writeAbbrev(Constants.Double{
+                            .value = (@as(u64, extra.hi) << 32) | extra.lo,
+                        });
+                    },
+                    .x86_fp80 => {
+                        const extra = self.constantExtraData(Constant.Fp80, data);
+                        try constants_block.writeAbbrev(Constants.Fp80{
+                            .lo = @as(u64, extra.lo_hi) << 32 | @as(u64, extra.lo_lo),
+                            .hi = @intCast(extra.hi),
+                        });
+                    },
+                    .fp128,
+                    .ppc_fp128,
+                    => {
+                        const extra = self.constantExtraData(Constant.Fp128, data);
+                        try constants_block.writeAbbrev(Constants.Fp128{
+                            .lo = @as(u64, extra.lo_hi) << 32 | @as(u64, extra.lo_lo),
+                            .hi = @as(u64, extra.hi_hi) << 32 | @as(u64, extra.hi_lo),
+                        });
+                    },
+                    .array,
+                    .vector,
+                    .structure,
+                    .packed_structure,
+                    => {
+                        var extra = self.constantExtraDataTrail(Constant.Aggregate, data);
+                        const len: u32 = @intCast(extra.data.type.aggregateLen(self));
+                        const values = extra.trail.next(len, Constant, self);
+
+                        try constants_block.writeAbbrevAdapted(
+                            Constants.Aggregate{ .values = values },
+                            constant_adapter,
+                        );
+                    },
+                    .splat => {
+                        const ConstantsWriter = @TypeOf(constants_block);
+                        const extra = self.constantExtraData(Constant.Splat, data);
+                        const vector_len = extra.type.vectorLen(self);
+                        const c = constant_adapter.getConstantIndex(extra.value);
+
+                        try bitcode.writeBits(
+                            ConstantsWriter.abbrevId(Constants.Aggregate),
+                            ConstantsWriter.abbrev_len,
+                        );
+                        try bitcode.writeVBR(vector_len, 6);
+                        for (0..vector_len) |_| {
+                            try bitcode.writeBits(c, Constants.Aggregate.ops[1].array_fixed);
+                        }
+                    },
+                    .string,
+                    .string_null,
+                    => {
+                        const str: String = @enumFromInt(data);
+                        if (str == .none) {
+                            try constants_block.writeAbbrev(Constants.Null{});
+                        } else {
+                            const slice = str.slice(self) orelse unreachable;
+                            switch (tags[index]) {
+                                .string => try constants_block.writeAbbrev(Constants.String{ .string = slice }),
+                                .string_null => try constants_block.writeAbbrev(Constants.CString{ .string = slice }),
+                                else => unreachable,
+                            }
+                        }
+                    },
+                    .bitcast,
+                    .inttoptr,
+                    .ptrtoint,
+                    .fptosi,
+                    .fptoui,
+                    .sitofp,
+                    .uitofp,
+                    .addrspacecast,
+                    .fptrunc,
+                    .trunc,
+                    .fpext,
+                    .sext,
+                    .zext,
+                    => |tag| {
+                        const extra = self.constantExtraData(Constant.Cast, data);
+                        try constants_block.writeAbbrevAdapted(Constants.Cast{
+                            .type_index = extra.type,
+                            .val = extra.val,
+                            .opcode = tag.toCastOpcode(),
+                        }, constant_adapter);
+                    },
+                    .add,
+                    .@"add nsw",
+                    .@"add nuw",
+                    .sub,
+                    .@"sub nsw",
+                    .@"sub nuw",
+                    .mul,
+                    .@"mul nsw",
+                    .@"mul nuw",
+                    .shl,
+                    .lshr,
+                    .ashr,
+                    .@"and",
+                    .@"or",
+                    .xor,
+                    => |tag| {
+                        const extra = self.constantExtraData(Constant.Binary, data);
+                        try constants_block.writeAbbrevAdapted(Constants.Binary{
+                            .opcode = tag.toBinaryOpcode(),
+                            .lhs = extra.lhs,
+                            .rhs = extra.rhs,
+                        }, constant_adapter);
+                    },
+                    .icmp,
+                    .fcmp,
+                    => {
+                        const extra = self.constantExtraData(Constant.Compare, data);
+                        try constants_block.writeAbbrevAdapted(Constants.Cmp{
+                            .ty = extra.lhs.typeOf(self),
+                            .lhs = extra.lhs,
+                            .rhs = extra.rhs,
+                            .pred = extra.cond,
+                        }, constant_adapter);
+                    },
+                    .extractelement => {
+                        const extra = self.constantExtraData(Constant.ExtractElement, data);
+                        try constants_block.writeAbbrevAdapted(Constants.ExtractElement{
+                            .val_type = extra.val.typeOf(self),
+                            .val = extra.val,
+                            .index_type = extra.index.typeOf(self),
+                            .index = extra.index,
+                        }, constant_adapter);
+                    },
+                    .insertelement => {
+                        const extra = self.constantExtraData(Constant.InsertElement, data);
+                        try constants_block.writeAbbrevAdapted(Constants.InsertElement{
+                            .val = extra.val,
+                            .elem = extra.elem,
+                            .index_type = extra.index.typeOf(self),
+                            .index = extra.index,
+                        }, constant_adapter);
+                    },
+                    .shufflevector => {
+                        const extra = self.constantExtraData(Constant.ShuffleVector, data);
+                        const ty = constant.typeOf(self);
+                        const lhs_type = extra.lhs.typeOf(self);
+                        // Check if instruction is widening, truncating or not
+                        if (ty == lhs_type) {
+                            try constants_block.writeAbbrevAdapted(Constants.ShuffleVector{
+                                .lhs = extra.lhs,
+                                .rhs = extra.rhs,
+                                .mask = extra.mask,
+                            }, constant_adapter);
+                        } else {
+                            try constants_block.writeAbbrevAdapted(Constants.ShuffleVectorEx{
+                                .ty = ty,
+                                .lhs = extra.lhs,
+                                .rhs = extra.rhs,
+                                .mask = extra.mask,
+                            }, constant_adapter);
+                        }
+                    },
+                    .getelementptr,
+                    .@"getelementptr inbounds",
+                    => |tag| {
+                        var extra = self.constantExtraDataTrail(Constant.GetElementPtr, data);
+                        const indices = extra.trail.next(extra.data.info.indices_len, Constant, self);
+                        try record.ensureUnusedCapacity(self.gpa, 1 + 2 + 2 * indices.len);
+
+                        record.appendAssumeCapacity(@intFromEnum(extra.data.type));
+
+                        record.appendAssumeCapacity(@intFromEnum(extra.data.base.typeOf(self)));
+                        record.appendAssumeCapacity(constant_adapter.getConstantIndex(extra.data.base));
+
+                        for (indices) |i| {
+                            record.appendAssumeCapacity(@intFromEnum(i.typeOf(self)));
+                            record.appendAssumeCapacity(constant_adapter.getConstantIndex(i));
+                        }
+
+                        try constants_block.writeUnabbrev(switch (tag) {
+                            .getelementptr => 12,
+                            .@"getelementptr inbounds" => 20,
+                            else => unreachable,
+                        }, record.items);
+                    },
+                    .@"asm",
+                    .@"asm sideeffect",
+                    .@"asm alignstack",
+                    .@"asm sideeffect alignstack",
+                    .@"asm inteldialect",
+                    .@"asm sideeffect inteldialect",
+                    .@"asm alignstack inteldialect",
+                    .@"asm sideeffect alignstack inteldialect",
+                    .@"asm unwind",
+                    .@"asm sideeffect unwind",
+                    .@"asm alignstack unwind",
+                    .@"asm sideeffect alignstack unwind",
+                    .@"asm inteldialect unwind",
+                    .@"asm sideeffect inteldialect unwind",
+                    .@"asm alignstack inteldialect unwind",
+                    .@"asm sideeffect alignstack inteldialect unwind",
+                    => |tag| {
+                        const extra = self.constantExtraData(Constant.Assembly, data);
+
+                        const assembly_slice = extra.assembly.slice(self) orelse unreachable;
+                        const constraints_slice = extra.constraints.slice(self) orelse unreachable;
+
+                        try record.ensureUnusedCapacity(self.gpa, 4 + assembly_slice.len + constraints_slice.len);
+
+                        record.appendAssumeCapacity(@intFromEnum(extra.type));
+                        record.appendAssumeCapacity(switch (tag) {
+                            .@"asm" => 0,
+                            .@"asm sideeffect" => 0b0001,
+                            .@"asm sideeffect alignstack" => 0b0011,
+                            .@"asm sideeffect inteldialect" => 0b0101,
+                            .@"asm sideeffect alignstack inteldialect" => 0b0111,
+                            .@"asm sideeffect unwind" => 0b1001,
+                            .@"asm sideeffect alignstack unwind" => 0b1011,
+                            .@"asm sideeffect inteldialect unwind" => 0b1101,
+                            .@"asm sideeffect alignstack inteldialect unwind" => 0b1111,
+                            .@"asm alignstack" => 0b0010,
+                            .@"asm inteldialect" => 0b0100,
+                            .@"asm alignstack inteldialect" => 0b0110,
+                            .@"asm unwind" => 0b1000,
+                            .@"asm alignstack unwind" => 0b1010,
+                            .@"asm inteldialect unwind" => 0b1100,
+                            .@"asm alignstack inteldialect unwind" => 0b1110,
+                            else => unreachable,
+                        });
+
+                        record.appendAssumeCapacity(assembly_slice.len);
+                        for (assembly_slice) |c| record.appendAssumeCapacity(c);
+
+                        record.appendAssumeCapacity(constraints_slice.len);
+                        for (constraints_slice) |c| record.appendAssumeCapacity(c);
+
+                        try constants_block.writeUnabbrev(30, record.items);
+                    },
+                    .blockaddress => {
+                        const extra = self.constantExtraData(Constant.BlockAddress, data);
+                        try constants_block.writeAbbrev(Constants.BlockAddress{
+                            .type_id = extra.function.typeOf(self),
+                            .function = constant_adapter.getConstantIndex(extra.function.toConst(self)),
+                            .block = @intFromEnum(extra.block),
+                        });
+                    },
+                    .dso_local_equivalent,
+                    .no_cfi,
+                    => |tag| {
+                        const function: Function.Index = @enumFromInt(data);
+                        try constants_block.writeAbbrev(Constants.DsoLocalEquivalentOrNoCfi{
+                            .code = switch (tag) {
+                                .dso_local_equivalent => 27,
+                                .no_cfi => 29,
+                                else => unreachable,
+                            },
+                            .type_id = function.typeOf(self),
+                            .function = constant_adapter.getConstantIndex(function.toConst(self)),
+                        });
+                    },
+                }
+            }
+
+            try constants_block.end();
+        }
+
+        // FUNCTION_BLOCKS
+        {
+            const FunctionAdapter = struct {
+                constant_adapter: ConstantAdapter,
+                func: *const Function,
+                instruction_index: u32 = 0,
+
+                pub fn init(
+                    const_adapter: ConstantAdapter,
+                    func: *const Function,
+                ) @This() {
+                    return .{
+                        .constant_adapter = const_adapter,
+                        .func = func,
+                        .instruction_index = 0,
+                    };
+                }
+
+                pub fn get(adapter: @This(), value: anytype, comptime field_name: []const u8) @TypeOf(value) {
+                    _ = field_name;
+                    const Ty = @TypeOf(value);
+                    return switch (Ty) {
+                        Value => @enumFromInt(adapter.getOffsetValueIndex(value)),
+                        Constant => @enumFromInt(adapter.getOffsetConstantIndex(value)),
+                        FunctionAttributes => @enumFromInt(if (value == .none) 0 else (adapter.constant_adapter.builder.function_attributes_set.getIndex(value) orelse unreachable) + 1),
+                        else => value,
+                    };
+                }
+
+                pub fn getValueIndex(adapter: @This(), value: Value) u32 {
+                    return @intCast(switch (value.unwrap()) {
+                        .instruction => |instruction| instruction.valueIndex(adapter.func) + adapter.firstInstr(),
+                        .constant => |constant| adapter.constant_adapter.getConstantIndex(constant),
+                    });
+                }
+
+                pub fn getOffsetValueIndex(adapter: @This(), value: Value) u32 {
+                    return adapter.offset() - adapter.getValueIndex(value);
+                }
+
+                pub fn getOffsetValueSignedIndex(adapter: @This(), value: Value) i32 {
+                    const signed_offset: i32 = @intCast(adapter.offset());
+                    const signed_value: i32 = @intCast(adapter.getValueIndex(value));
+                    return signed_offset - signed_value;
+                }
+
+                pub fn getOffsetConstantIndex(adapter: @This(), constant: Constant) u32 {
+                    return adapter.offset() - adapter.constant_adapter.getConstantIndex(constant);
+                }
+
+                pub fn offset(adapter: @This()) u32 {
+                    return @as(
+                        Function.Instruction.Index,
+                        @enumFromInt(adapter.instruction_index),
+                    ).valueIndex(adapter.func) + adapter.firstInstr();
+                }
+
+                fn firstInstr(adapter: @This()) u32 {
+                    return adapter.constant_adapter.numConstants();
+                }
+
+                pub fn next(adapter: *@This()) void {
+                    adapter.instruction_index += 1;
+                }
+            };
+
+            for (self.functions.items, 0..) |func, func_index| {
+                const FunctionBlock = IR.FunctionBlock;
+                if (func.global.getReplacement(self) != .none) continue;
+
+                if (func.instructions.len == 0) continue;
+
+                var function_block = try module_block.enterSubBlock(FunctionBlock);
+
+                try function_block.writeAbbrev(FunctionBlock.DeclareBlocks{ .num_blocks = func.blocks.len });
+
+                var adapter = FunctionAdapter.init(constant_adapter, &func);
+
+                const tags = func.instructions.items(.tag);
+                const datas = func.instructions.items(.data);
+
+                var block_incoming_len: u32 = undefined;
+                for (0..func.instructions.len) |instr_index| {
+                    const tag = tags[instr_index];
+
+                    record.clearRetainingCapacity();
+
+                    switch (tag) {
+                        .block => block_incoming_len = datas[instr_index],
+                        .arg => {},
+                        .@"unreachable" => try function_block.writeAbbrev(FunctionBlock.Unreachable{}),
+                        .call,
+                        .@"musttail call",
+                        .@"notail call",
+                        .@"tail call",
+                        => |kind| {
+                            var extra = func.extraDataTrail(Function.Instruction.Call, datas[instr_index]);
+
+                            const call_conv = extra.data.info.call_conv;
+                            const args = extra.trail.next(extra.data.args_len, Value, &func);
+                            try function_block.writeAbbrevAdapted(FunctionBlock.Call{
+                                .attributes = extra.data.attributes,
+                                .call_type = switch (kind) {
+                                    .call => .{ .call_conv = call_conv },
+                                    .@"tail call" => .{ .tail = true, .call_conv = call_conv },
+                                    .@"musttail call" => .{ .must_tail = true, .call_conv = call_conv },
+                                    .@"notail call" => .{ .no_tail = true, .call_conv = call_conv },
+                                    else => unreachable,
+                                },
+                                .type_id = extra.data.ty,
+                                .callee = extra.data.callee,
+                                .args = args,
+                            }, adapter);
+                        },
+                        .@"call fast",
+                        .@"musttail call fast",
+                        .@"notail call fast",
+                        .@"tail call fast",
+                        => |kind| {
+                            var extra = func.extraDataTrail(Function.Instruction.Call, datas[instr_index]);
+
+                            const call_conv = extra.data.info.call_conv;
+                            const args = extra.trail.next(extra.data.args_len, Value, &func);
+                            try function_block.writeAbbrevAdapted(FunctionBlock.CallFast{
+                                .attributes = extra.data.attributes,
+                                .call_type = switch (kind) {
+                                    .call => .{ .call_conv = call_conv },
+                                    .@"tail call" => .{ .tail = true, .call_conv = call_conv },
+                                    .@"musttail call" => .{ .must_tail = true, .call_conv = call_conv },
+                                    .@"notail call" => .{ .no_tail = true, .call_conv = call_conv },
+                                    else => unreachable,
+                                },
+                                .fast_math = .{},
+                                .type_id = extra.data.ty,
+                                .callee = extra.data.callee,
+                                .args = args,
+                            }, adapter);
+                        },
+                        .add,
+                        .@"add nsw",
+                        .@"add nuw",
+                        .@"add nuw nsw",
+                        .@"and",
+                        .fadd,
+                        .fdiv,
+                        .fmul,
+                        .mul,
+                        .@"mul nsw",
+                        .@"mul nuw",
+                        .@"mul nuw nsw",
+                        .frem,
+                        .fsub,
+                        .sdiv,
+                        .@"sdiv exact",
+                        .sub,
+                        .@"sub nsw",
+                        .@"sub nuw",
+                        .@"sub nuw nsw",
+                        .udiv,
+                        .@"udiv exact",
+                        .xor,
+                        .shl,
+                        .@"shl nsw",
+                        .@"shl nuw",
+                        .@"shl nuw nsw",
+                        .lshr,
+                        .@"lshr exact",
+                        .@"or",
+                        .urem,
+                        .srem,
+                        .ashr,
+                        .@"ashr exact",
+                        => {
+                            const extra = func.extraData(Function.Instruction.Binary, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Binary{
+                                .opcode = tag.toBinaryOpcode(),
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                            });
+                        },
+                        .@"fadd fast",
+                        .@"fdiv fast",
+                        .@"fmul fast",
+                        .@"frem fast",
+                        .@"fsub fast",
+                        => {
+                            const extra = func.extraData(Function.Instruction.Binary, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.BinaryFast{
+                                .opcode = tag.toBinaryOpcode(),
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .fast_math = .{},
+                            });
+                        },
+                        .alloca,
+                        .@"alloca inalloca",
+                        => |kind| {
+                            const extra = func.extraData(Function.Instruction.Alloca, datas[instr_index]);
+                            const alignment = extra.info.alignment.toLlvm();
+                            try function_block.writeAbbrev(FunctionBlock.Alloca{
+                                .inst_type = extra.type,
+                                .len_type = if (extra.len == .none) .i1 else extra.len.typeOf(@enumFromInt(func_index), self),
+                                .len_value = adapter.getValueIndex(if (extra.len == .none) Constant.true.toValue() else extra.len),
+                                .flags = .{
+                                    .align_lower = @truncate(alignment),
+                                    .inalloca = kind == .@"alloca inalloca",
+                                    .explicit_type = true,
+                                    .swift_error = false,
+                                    .align_upper = @truncate(alignment << 5),
+                                },
+                            });
+                        },
+                        .bitcast,
+                        .inttoptr,
+                        .ptrtoint,
+                        .fptosi,
+                        .fptoui,
+                        .sitofp,
+                        .uitofp,
+                        .addrspacecast,
+                        .fptrunc,
+                        .trunc,
+                        .fpext,
+                        .sext,
+                        .zext,
+                        => {
+                            const extra = func.extraData(Function.Instruction.Cast, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Cast{
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .type_index = extra.type,
+                                .opcode = tag.toCastOpcode(),
+                            });
+                        },
+                        .@"fcmp false",
+                        .@"fcmp oeq",
+                        .@"fcmp oge",
+                        .@"fcmp ogt",
+                        .@"fcmp ole",
+                        .@"fcmp olt",
+                        .@"fcmp one",
+                        .@"fcmp ord",
+                        .@"fcmp true",
+                        .@"fcmp ueq",
+                        .@"fcmp uge",
+                        .@"fcmp ugt",
+                        .@"fcmp ule",
+                        .@"fcmp ult",
+                        .@"fcmp une",
+                        .@"fcmp uno",
+                        .@"icmp eq",
+                        .@"icmp ne",
+                        .@"icmp sge",
+                        .@"icmp sgt",
+                        .@"icmp sle",
+                        .@"icmp slt",
+                        .@"icmp uge",
+                        .@"icmp ugt",
+                        .@"icmp ule",
+                        .@"icmp ult",
+                        => {
+                            const extra = func.extraData(Function.Instruction.Binary, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Cmp{
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .pred = tag.toCmpPredicate(),
+                            });
+                        },
+                        .@"fcmp fast false",
+                        .@"fcmp fast oeq",
+                        .@"fcmp fast oge",
+                        .@"fcmp fast ogt",
+                        .@"fcmp fast ole",
+                        .@"fcmp fast olt",
+                        .@"fcmp fast one",
+                        .@"fcmp fast ord",
+                        .@"fcmp fast true",
+                        .@"fcmp fast ueq",
+                        .@"fcmp fast uge",
+                        .@"fcmp fast ugt",
+                        .@"fcmp fast ule",
+                        .@"fcmp fast ult",
+                        .@"fcmp fast une",
+                        .@"fcmp fast uno",
+                        => {
+                            const extra = func.extraData(Function.Instruction.Binary, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.CmpFast{
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .pred = tag.toCmpPredicate(),
+                                .fast_math = .{},
+                            });
+                        },
+                        .fneg => try function_block.writeAbbrev(FunctionBlock.FNeg{
+                            .val = adapter.getOffsetValueIndex(@enumFromInt(datas[instr_index])),
+                        }),
+                        .@"fneg fast" => try function_block.writeAbbrev(FunctionBlock.FNegFast{
+                            .val = adapter.getOffsetValueIndex(@enumFromInt(datas[instr_index])),
+                            .fast_math = .{},
+                        }),
+                        .extractvalue => {
+                            var extra = func.extraDataTrail(Function.Instruction.ExtractValue, datas[instr_index]);
+                            const indices = extra.trail.next(extra.data.indices_len, u32, &func);
+                            try function_block.writeAbbrev(FunctionBlock.ExtractValue{
+                                .val = adapter.getOffsetValueIndex(extra.data.val),
+                                .indices = indices,
+                            });
+                        },
+                        .insertvalue => {
+                            var extra = func.extraDataTrail(Function.Instruction.InsertValue, datas[instr_index]);
+                            const indices = extra.trail.next(extra.data.indices_len, u32, &func);
+                            try function_block.writeAbbrev(FunctionBlock.InsertValue{
+                                .val = adapter.getOffsetValueIndex(extra.data.val),
+                                .elem = adapter.getOffsetValueIndex(extra.data.elem),
+                                .indices = indices,
+                            });
+                        },
+                        .extractelement => {
+                            const extra = func.extraData(Function.Instruction.ExtractElement, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.ExtractElement{
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .index = adapter.getOffsetValueIndex(extra.index),
+                            });
+                        },
+                        .insertelement => {
+                            const extra = func.extraData(Function.Instruction.InsertElement, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.InsertElement{
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .elem = adapter.getOffsetValueIndex(extra.elem),
+                                .index = adapter.getOffsetValueIndex(extra.index),
+                            });
+                        },
+                        .select => {
+                            const extra = func.extraData(Function.Instruction.Select, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Select{
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .cond = adapter.getOffsetValueIndex(extra.cond),
+                            });
+                        },
+                        .@"select fast" => {
+                            const extra = func.extraData(Function.Instruction.Select, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.SelectFast{
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .cond = adapter.getOffsetValueIndex(extra.cond),
+                                .fast_math = .{},
+                            });
+                        },
+                        .shufflevector => {
+                            const extra = func.extraData(Function.Instruction.ShuffleVector, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.ShuffleVector{
+                                .lhs = adapter.getOffsetValueIndex(extra.lhs),
+                                .rhs = adapter.getOffsetValueIndex(extra.rhs),
+                                .mask = adapter.getOffsetValueIndex(extra.mask),
+                            });
+                        },
+                        .getelementptr,
+                        .@"getelementptr inbounds",
+                        => {
+                            var extra = func.extraDataTrail(Function.Instruction.GetElementPtr, datas[instr_index]);
+                            const indices = extra.trail.next(extra.data.indices_len, Value, &func);
+                            try function_block.writeAbbrevAdapted(
+                                FunctionBlock.GetElementPtr{
+                                    .is_inbounds = tag == .@"getelementptr inbounds",
+                                    .type_index = extra.data.type,
+                                    .base = extra.data.base,
+                                    .indices = indices,
+                                },
+                                adapter,
+                            );
+                        },
+                        .load => {
+                            const extra = func.extraData(Function.Instruction.Load, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Load{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .ty = extra.type,
+                                .alignment = extra.info.alignment.toLlvm(),
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                            });
+                        },
+                        .@"load atomic" => {
+                            const extra = func.extraData(Function.Instruction.Load, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.LoadAtomic{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .ty = extra.type,
+                                .alignment = extra.info.alignment.toLlvm(),
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                                .success_ordering = extra.info.success_ordering,
+                                .sync_scope = extra.info.sync_scope,
+                            });
+                        },
+                        .store => {
+                            const extra = func.extraData(Function.Instruction.Store, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Store{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .alignment = extra.info.alignment.toLlvm(),
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                            });
+                        },
+                        .@"store atomic" => {
+                            const extra = func.extraData(Function.Instruction.Store, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.StoreAtomic{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .alignment = extra.info.alignment.toLlvm(),
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                                .success_ordering = extra.info.success_ordering,
+                                .sync_scope = extra.info.sync_scope,
+                            });
+                        },
+                        .br => {
+                            try function_block.writeAbbrev(FunctionBlock.BrUnconditional{
+                                .block = datas[instr_index],
+                            });
+                        },
+                        .br_cond => {
+                            const extra = func.extraData(Function.Instruction.BrCond, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.BrConditional{
+                                .then_block = @intFromEnum(extra.then),
+                                .else_block = @intFromEnum(extra.@"else"),
+                                .condition = adapter.getOffsetValueIndex(extra.cond),
+                            });
+                        },
+                        .@"switch" => {
+                            var extra = func.extraDataTrail(Function.Instruction.Switch, datas[instr_index]);
+
+                            try record.ensureUnusedCapacity(self.gpa, 3 + extra.data.cases_len * 2);
+
+                            // Conditional type
+                            record.appendAssumeCapacity(@intFromEnum(extra.data.val.typeOf(@enumFromInt(func_index), self)));
+
+                            // Conditional
+                            record.appendAssumeCapacity(adapter.getOffsetValueIndex(extra.data.val));
+
+                            // Default block
+                            record.appendAssumeCapacity(@intFromEnum(extra.data.default));
+
+                            const vals = extra.trail.next(extra.data.cases_len, Constant, &func);
+                            const blocks = extra.trail.next(extra.data.cases_len, Function.Block.Index, &func);
+                            for (vals, blocks) |val, block| {
+                                record.appendAssumeCapacity(adapter.constant_adapter.getConstantIndex(val));
+                                record.appendAssumeCapacity(@intFromEnum(block));
+                            }
+
+                            try function_block.writeUnabbrev(12, record.items);
+                        },
+                        .va_arg => {
+                            const extra = func.extraData(Function.Instruction.VaArg, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.VaArg{
+                                .list_type = extra.list.typeOf(@enumFromInt(func_index), self),
+                                .list = adapter.getOffsetValueIndex(extra.list),
+                                .type = extra.type,
+                            });
+                        },
+                        .phi,
+                        .@"phi fast",
+                        => |kind| {
+                            var extra = func.extraDataTrail(Function.Instruction.Phi, datas[instr_index]);
+                            const vals = extra.trail.next(block_incoming_len, Value, &func);
+                            const blocks = extra.trail.next(block_incoming_len, Function.Block.Index, &func);
+
+                            try record.ensureUnusedCapacity(
+                                self.gpa,
+                                1 + block_incoming_len * 2 + @intFromBool(kind == .@"phi fast"),
+                            );
+
+                            record.appendAssumeCapacity(@intFromEnum(extra.data.type));
+
+                            for (vals, blocks) |val, block| {
+                                const offset_value = adapter.getOffsetValueSignedIndex(val);
+                                const abs_value: u32 = @intCast(@abs(offset_value));
+                                const signed_vbr = if (offset_value > 0) abs_value << 1 else ((abs_value << 1) | 1);
+                                record.appendAssumeCapacity(signed_vbr);
+                                record.appendAssumeCapacity(@intFromEnum(block));
+                            }
+
+                            if (kind == .@"phi fast") record.appendAssumeCapacity(@as(u8, @bitCast(FastMath{})));
+
+                            try function_block.writeUnabbrev(16, record.items);
+                        },
+                        .ret => try function_block.writeAbbrev(FunctionBlock.Ret{
+                            .val = adapter.getOffsetValueIndex(@enumFromInt(datas[instr_index])),
+                        }),
+                        .@"ret void" => try function_block.writeAbbrev(FunctionBlock.RetVoid{}),
+                        .atomicrmw => {
+                            const extra = func.extraData(Function.Instruction.AtomicRmw, datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.AtomicRmw{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .val = adapter.getOffsetValueIndex(extra.val),
+                                .operation = extra.info.atomic_rmw_operation,
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                                .success_ordering = extra.info.success_ordering,
+                                .sync_scope = extra.info.sync_scope,
+                                .alignment = extra.info.alignment.toLlvm(),
+                            });
+                        },
+                        .cmpxchg,
+                        .@"cmpxchg weak",
+                        => |kind| {
+                            const extra = func.extraData(Function.Instruction.CmpXchg, datas[instr_index]);
+
+                            try function_block.writeAbbrev(FunctionBlock.CmpXchg{
+                                .ptr = adapter.getOffsetValueIndex(extra.ptr),
+                                .cmp = adapter.getOffsetValueIndex(extra.cmp),
+                                .new = adapter.getOffsetValueIndex(extra.new),
+                                .is_volatile = extra.info.access_kind == .@"volatile",
+                                .success_ordering = extra.info.success_ordering,
+                                .sync_scope = extra.info.sync_scope,
+                                .failure_ordering = extra.info.failure_ordering,
+                                .is_weak = kind == .@"cmpxchg weak",
+                                .alignment = extra.info.alignment.toLlvm(),
+                            });
+                        },
+                        .fence => {
+                            const info: MemoryAccessInfo = @bitCast(datas[instr_index]);
+                            try function_block.writeAbbrev(FunctionBlock.Fence{
+                                .ordering = info.success_ordering,
+                                .sync_scope = info.sync_scope,
+                            });
+                        },
+                    }
+
+                    adapter.next();
+                }
+
+                // VALUE_SYMTAB
+                if (!self.strip) {
+                    const ValueSymbolTable = IR.FunctionValueSymbolTable;
+
+                    var value_symtab_block = try function_block.enterSubBlock(ValueSymbolTable);
+
+                    for (func.blocks, 0..) |block, block_index| {
+                        const name = block.instruction.name(&func);
+
+                        if (name == .none or name == .empty) continue;
+
+                        try value_symtab_block.writeAbbrev(ValueSymbolTable.BlockEntry{
+                            .value_id = @intCast(block_index),
+                            .string = name.slice(self).?,
+                        });
+                    }
+
+                    // TODO: Emit non block entries if the builder ever starts assigning names to non blocks
+
+                    try value_symtab_block.end();
+                }
+
+                try function_block.end();
+            }
+        }
+
+        try module_block.end();
+    }
+
+    // STRTAB_BLOCK
+    {
+        const Strtab = IR.Strtab;
+        var strtab_block = try bitcode.enterTopBlock(Strtab);
+
+        try strtab_block.writeAbbrev(Strtab.Blob{ .blob = self.string_bytes.items });
+
+        try strtab_block.end();
+    }
+
+    return bitcode.toSlice();
+}
+
 const assert = std.debug.assert;
 const build_options = @import("build_options");
 const builtin = @import("builtin");
@@ -12052,5 +13522,8 @@ else
 const log = std.log.scoped(.llvm);
 const std = @import("std");
 
+const bitcode_writer = @import("bitcode_writer.zig");
+const IR = @import("IR.zig");
+
 const Allocator = std.mem.Allocator;
 const Builder = @This();