Commit 3fc2e36de2

Jacob Young <jacobly0@users.noreply.github.com>
2023-07-23 12:15:26
llvm: convert global assembly
1 parent 533111e
Changed files (3)
src/codegen/llvm/bindings.zig
@@ -540,7 +540,7 @@ pub const Module = opaque {
     pub const createDIBuilder = ZigLLVMCreateDIBuilder;
     extern fn ZigLLVMCreateDIBuilder(module: *Module, allow_unresolved: bool) *DIBuilder;
 
-    pub const setModuleInlineAsm2 = LLVMSetModuleInlineAsm2;
+    pub const setModuleInlineAsm = LLVMSetModuleInlineAsm2;
     extern fn LLVMSetModuleInlineAsm2(M: *Module, Asm: [*]const u8, Len: usize) void;
 
     pub const printModuleToFile = LLVMPrintModuleToFile;
src/codegen/llvm/Builder.zig
@@ -18,6 +18,7 @@ llvm: if (build_options.have_llvm) struct {
 source_filename: String,
 data_layout: String,
 target_triple: String,
+module_asm: std.ArrayListUnmanaged(u8),
 
 string_map: std.AutoArrayHashMapUnmanaged(void, void),
 string_indices: std.ArrayListUnmanaged(u32),
@@ -95,18 +96,12 @@ pub const String = enum(u32) {
         assert(data.string != .none);
         const sentinel_slice = data.string.slice(data.builder) orelse
             return writer.print("{d}", .{@intFromEnum(data.string)});
-        const full_slice = sentinel_slice[0 .. sentinel_slice.len + comptime @intFromBool(
+        try printEscapedString(sentinel_slice[0 .. sentinel_slice.len + comptime @intFromBool(
             std.mem.indexOfScalar(u8, fmt_str, '@') != null,
-        )];
-        const need_quotes = (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) or
-            !isValidIdentifier(full_slice);
-        if (need_quotes) try writer.writeByte('"');
-        for (full_slice) |character| switch (character) {
-            '\\' => try writer.writeAll("\\\\"),
-            ' '...'"' - 1, '"' + 1...'\\' - 1, '\\' + 1...'~' => try writer.writeByte(character),
-            else => try writer.print("\\{X:0>2}", .{character}),
-        };
-        if (need_quotes) try writer.writeByte('"');
+        )], if (comptime std.mem.indexOfScalar(u8, fmt_str, '"')) |_|
+            .always_quote
+        else
+            .quote_unless_valid_identifier, writer);
     }
     pub fn fmt(self: String, builder: *const Builder) std.fmt.Formatter(format) {
         return .{ .data = .{ .string = self, .builder = builder } };
@@ -3812,7 +3807,7 @@ pub const WipFunction = struct {
                 @intFromEnum(addr_space),
                 instruction.llvmName(self),
             );
-            if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a));
+            if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes));
             self.llvm.instructions.appendAssumeCapacity(llvm_instruction);
         }
         return instruction.toValue();
@@ -3868,7 +3863,7 @@ pub const WipFunction = struct {
                 instruction.llvmName(self),
             );
             if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering)));
-            if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a));
+            if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes));
             self.llvm.instructions.appendAssumeCapacity(llvm_instruction);
         }
         return instruction.toValue();
@@ -3922,7 +3917,7 @@ pub const WipFunction = struct {
                 .@"volatile" => llvm_instruction.setVolatile(.True),
             }
             if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering)));
-            if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a));
+            if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes));
             self.llvm.instructions.appendAssumeCapacity(llvm_instruction);
         }
         return instruction;
@@ -6090,6 +6085,7 @@ pub fn init(options: Options) InitError!Builder {
         .source_filename = .none,
         .data_layout = .none,
         .target_triple = .none,
+        .module_asm = .{},
 
         .string_map = .{},
         .string_indices = .{},
@@ -6207,6 +6203,8 @@ pub fn init(options: Options) InitError!Builder {
 }
 
 pub fn deinit(self: *Builder) void {
+    self.module_asm.deinit(self.gpa);
+
     self.string_map.deinit(self.gpa);
     self.string_indices.deinit(self.gpa);
     self.string_bytes.deinit(self.gpa);
@@ -6440,6 +6438,22 @@ pub fn initializeLLVMTarget(self: *const Builder, arch: std.Target.Cpu.Arch) voi
     }
 }
 
+pub fn setModuleAsm(self: *Builder) std.ArrayListUnmanaged(u8).Writer {
+    self.module_asm.clearRetainingCapacity();
+    return self.appendModuleAsm();
+}
+
+pub fn appendModuleAsm(self: *Builder) std.ArrayListUnmanaged(u8).Writer {
+    return self.module_asm.writer(self.gpa);
+}
+
+pub fn finishModuleAsm(self: *Builder) Allocator.Error!void {
+    if (self.module_asm.getLastOrNull()) |last| if (last != '\n')
+        try self.module_asm.append(self.gpa, '\n');
+    if (self.useLibLlvm())
+        self.llvm.module.?.setModuleInlineAsm(self.module_asm.items.ptr, self.module_asm.items.len);
+}
+
 pub fn string(self: *Builder, bytes: []const u8) Allocator.Error!String {
     try self.string_bytes.ensureUnusedCapacity(self.gpa, bytes.len + 1);
     try self.string_indices.ensureUnusedCapacity(self.gpa, 1);
@@ -7185,548 +7199,605 @@ pub fn printUnbuffered(
     self: *Builder,
     writer: anytype,
 ) (@TypeOf(writer).Error || Allocator.Error)!void {
-    if (self.source_filename != .none) try writer.print(
-        \\; ModuleID = '{s}'
-        \\source_filename = {"}
-        \\
-    , .{ self.source_filename.slice(self).?, self.source_filename.fmt(self) });
-    if (self.data_layout != .none) try writer.print(
-        \\target datalayout = {"}
-        \\
-    , .{self.data_layout.fmt(self)});
-    if (self.target_triple != .none) try writer.print(
-        \\target triple = {"}
-        \\
-    , .{self.target_triple.fmt(self)});
-    try writer.writeByte('\n');
-
-    for (self.types.keys(), self.types.values()) |id, ty| try writer.print(
-        \\%{} = type {}
-        \\
-    , .{ id.fmt(self), ty.fmt(self) });
-    try writer.writeByte('\n');
-
-    for (self.variables.items) |variable| {
-        if (variable.global.getReplacement(self) != .none) continue;
-        const global = variable.global.ptrConst(self);
-        try writer.print(
-            \\{} ={}{}{}{}{}{}{}{} {s} {%}{ }{,}
+    var need_newline = false;
+
+    if (self.source_filename != .none or self.data_layout != .none or self.target_triple != .none) {
+        if (need_newline) try writer.writeByte('\n');
+        if (self.source_filename != .none) try writer.print(
+            \\; ModuleID = '{s}'
+            \\source_filename = {"}
             \\
-        , .{
-            variable.global.fmt(self),
-            global.linkage,
-            global.preemption,
-            global.visibility,
-            global.dll_storage_class,
-            variable.thread_local,
-            global.unnamed_addr,
-            global.addr_space,
-            global.externally_initialized,
-            @tagName(variable.mutability),
-            global.type.fmt(self),
-            variable.init.fmt(self),
-            variable.alignment,
-        });
+        , .{ self.source_filename.slice(self).?, self.source_filename.fmt(self) });
+        if (self.data_layout != .none) try writer.print(
+            \\target datalayout = {"}
+            \\
+        , .{self.data_layout.fmt(self)});
+        if (self.target_triple != .none) try writer.print(
+            \\target triple = {"}
+            \\
+        , .{self.target_triple.fmt(self)});
+        need_newline = true;
     }
-    try writer.writeByte('\n');
 
-    var attribute_groups: std.AutoArrayHashMapUnmanaged(Attributes, void) = .{};
-    defer attribute_groups.deinit(self.gpa);
-    for (0.., self.functions.items) |function_i, function| {
-        const function_index: Function.Index = @enumFromInt(function_i);
-        if (function.global.getReplacement(self) != .none) continue;
-        const global = function.global.ptrConst(self);
-        const params_len = global.type.functionParameters(self).len;
-        const function_attributes = function.attributes.func(self);
-        if (function_attributes != .none) try writer.print(
-            \\; Function Attrs:{}
+    if (self.module_asm.items.len > 0) {
+        if (need_newline) try writer.writeByte('\n');
+        var line_it = std.mem.tokenizeScalar(u8, self.module_asm.items, '\n');
+        while (line_it.next()) |line| {
+            try writer.writeAll("module asm ");
+            try printEscapedString(line, .always_quote, writer);
+            try writer.writeByte('\n');
+        }
+        need_newline = true;
+    }
+
+    if (self.types.count() > 0) {
+        if (need_newline) try writer.writeByte('\n');
+        for (self.types.keys(), self.types.values()) |id, ty| try writer.print(
+            \\%{} = type {}
             \\
-        , .{function_attributes.fmt(self)});
-        try writer.print(
-            \\{s}{}{}{}{}{}{"} {} {}(
-        , .{
-            if (function.instructions.len > 0) "define" else "declare",
-            global.linkage,
-            global.preemption,
-            global.visibility,
-            global.dll_storage_class,
-            function.call_conv,
-            function.attributes.ret(self).fmt(self),
-            global.type.functionReturn(self).fmt(self),
-            function.global.fmt(self),
-        });
-        for (0..params_len) |arg| {
-            if (arg > 0) try writer.writeAll(", ");
+        , .{ id.fmt(self), ty.fmt(self) });
+        need_newline = true;
+    }
+
+    if (self.variables.items.len > 0) {
+        if (need_newline) try writer.writeByte('\n');
+        for (self.variables.items) |variable| {
+            if (variable.global.getReplacement(self) != .none) continue;
+            const global = variable.global.ptrConst(self);
             try writer.print(
-                \\{%}{"}
+                \\{} ={}{}{}{}{}{}{}{} {s} {%}{ }{,}
+                \\
             , .{
-                global.type.functionParameters(self)[arg].fmt(self),
-                function.attributes.param(arg, self).fmt(self),
+                variable.global.fmt(self),
+                global.linkage,
+                global.preemption,
+                global.visibility,
+                global.dll_storage_class,
+                variable.thread_local,
+                global.unnamed_addr,
+                global.addr_space,
+                global.externally_initialized,
+                @tagName(variable.mutability),
+                global.type.fmt(self),
+                variable.init.fmt(self),
+                variable.alignment,
             });
-            if (function.instructions.len > 0)
-                try writer.print(" {}", .{function.arg(@intCast(arg)).fmt(function_index, self)});
-        }
-        switch (global.type.functionKind(self)) {
-            .normal => {},
-            .vararg => {
-                if (params_len > 0) try writer.writeAll(", ");
-                try writer.writeAll("...");
-            },
         }
-        try writer.print("){}{}", .{ global.unnamed_addr, global.addr_space });
-        if (function_attributes != .none) try writer.print(" #{d}", .{
-            (try attribute_groups.getOrPutValue(self.gpa, function_attributes, {})).index,
-        });
-        try writer.print("{}", .{function.alignment});
-        if (function.instructions.len > 0) {
-            var block_incoming_len: u32 = undefined;
-            try writer.writeAll(" {\n");
-            for (params_len..function.instructions.len) |instruction_i| {
-                const instruction_index: Function.Instruction.Index = @enumFromInt(instruction_i);
-                const instruction = function.instructions.get(@intFromEnum(instruction_index));
-                switch (instruction.tag) {
-                    .add,
-                    .@"add nsw",
-                    .@"add nuw",
-                    .@"add nuw nsw",
-                    .@"and",
-                    .ashr,
-                    .@"ashr exact",
-                    .fadd,
-                    .@"fadd fast",
-                    .@"fcmp false",
-                    .@"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",
-                    .@"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",
-                    .fdiv,
-                    .@"fdiv fast",
-                    .fmul,
-                    .@"fmul fast",
-                    .frem,
-                    .@"frem fast",
-                    .fsub,
-                    .@"fsub fast",
-                    .@"icmp eq",
-                    .@"icmp ne",
-                    .@"icmp sge",
-                    .@"icmp sgt",
-                    .@"icmp sle",
-                    .@"icmp slt",
-                    .@"icmp uge",
-                    .@"icmp ugt",
-                    .@"icmp ule",
-                    .@"icmp ult",
-                    .lshr,
-                    .@"lshr exact",
-                    .mul,
-                    .@"mul nsw",
-                    .@"mul nuw",
-                    .@"mul nuw nsw",
-                    .@"or",
-                    .sdiv,
-                    .@"sdiv exact",
-                    .srem,
-                    .shl,
-                    .@"shl nsw",
-                    .@"shl nuw",
-                    .@"shl nuw nsw",
-                    .sub,
-                    .@"sub nsw",
-                    .@"sub nuw",
-                    .@"sub nuw nsw",
-                    .udiv,
-                    .@"udiv exact",
-                    .urem,
-                    .xor,
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Binary, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.lhs.fmt(function_index, self),
-                            extra.rhs.fmt(function_index, self),
-                        });
-                    },
-                    .addrspacecast,
-                    .bitcast,
-                    .fpext,
-                    .fptosi,
-                    .fptoui,
-                    .fptrunc,
-                    .inttoptr,
-                    .ptrtoint,
-                    .sext,
-                    .sitofp,
-                    .trunc,
-                    .uitofp,
-                    .zext,
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Cast, instruction.data);
-                        try writer.print("  %{} = {s} {%} to {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.val.fmt(function_index, self),
-                            extra.type.fmt(self),
-                        });
-                    },
-                    .alloca,
-                    .@"alloca inalloca",
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Alloca, instruction.data);
-                        try writer.print("  %{} = {s} {%}{,%}{,}{,}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.type.fmt(self),
-                            extra.len.fmt(function_index, self),
-                            extra.info.alignment,
-                            extra.info.addr_space,
-                        });
-                    },
-                    .arg => unreachable,
-                    .block => {
-                        block_incoming_len = instruction.data;
-                        const name = instruction_index.name(&function);
-                        if (@intFromEnum(instruction_index) > params_len) try writer.writeByte('\n');
-                        try writer.print("{}:\n", .{name.fmt(self)});
-                    },
-                    .br => |tag| {
-                        const target: Function.Block.Index = @enumFromInt(instruction.data);
-                        try writer.print("  {s} {%}\n", .{
-                            @tagName(tag), target.toInst(&function).fmt(function_index, self),
-                        });
-                    },
-                    .br_cond => {
-                        const extra = function.extraData(Function.Instruction.BrCond, instruction.data);
-                        try writer.print("  br {%}, {%}, {%}\n", .{
-                            extra.cond.fmt(function_index, self),
-                            extra.then.toInst(&function).fmt(function_index, self),
-                            extra.@"else".toInst(&function).fmt(function_index, self),
-                        });
-                    },
-                    .call,
-                    .@"call fast",
-                    .@"musttail call",
-                    .@"musttail call fast",
-                    .@"notail call",
-                    .@"notail call fast",
-                    .@"tail call",
-                    .@"tail call fast",
-                    => |tag| {
-                        var extra =
-                            function.extraDataTrail(Function.Instruction.Call, instruction.data);
-                        const args = extra.trail.next(extra.data.args_len, Value, &function);
-                        try writer.writeAll("  ");
-                        const ret_ty = extra.data.ty.functionReturn(self);
-                        switch (ret_ty) {
-                            .void => {},
-                            else => try writer.print("%{} = ", .{
+        need_newline = true;
+    }
+
+    var attribute_groups: std.AutoArrayHashMapUnmanaged(Attributes, void) = .{};
+    defer attribute_groups.deinit(self.gpa);
+
+    if (self.functions.items.len > 0) {
+        if (need_newline) try writer.writeByte('\n');
+        for (0.., self.functions.items) |function_i, function| {
+            if (function_i > 0) try writer.writeByte('\n');
+            const function_index: Function.Index = @enumFromInt(function_i);
+            if (function.global.getReplacement(self) != .none) continue;
+            const global = function.global.ptrConst(self);
+            const params_len = global.type.functionParameters(self).len;
+            const function_attributes = function.attributes.func(self);
+            if (function_attributes != .none) try writer.print(
+                \\; Function Attrs:{}
+                \\
+            , .{function_attributes.fmt(self)});
+            try writer.print(
+                \\{s}{}{}{}{}{}{"} {} {}(
+            , .{
+                if (function.instructions.len > 0) "define" else "declare",
+                global.linkage,
+                global.preemption,
+                global.visibility,
+                global.dll_storage_class,
+                function.call_conv,
+                function.attributes.ret(self).fmt(self),
+                global.type.functionReturn(self).fmt(self),
+                function.global.fmt(self),
+            });
+            for (0..params_len) |arg| {
+                if (arg > 0) try writer.writeAll(", ");
+                try writer.print(
+                    \\{%}{"}
+                , .{
+                    global.type.functionParameters(self)[arg].fmt(self),
+                    function.attributes.param(arg, self).fmt(self),
+                });
+                if (function.instructions.len > 0)
+                    try writer.print(" {}", .{function.arg(@intCast(arg)).fmt(function_index, self)});
+            }
+            switch (global.type.functionKind(self)) {
+                .normal => {},
+                .vararg => {
+                    if (params_len > 0) try writer.writeAll(", ");
+                    try writer.writeAll("...");
+                },
+            }
+            try writer.print("){}{}", .{ global.unnamed_addr, global.addr_space });
+            if (function_attributes != .none) try writer.print(" #{d}", .{
+                (try attribute_groups.getOrPutValue(self.gpa, function_attributes, {})).index,
+            });
+            try writer.print("{}", .{function.alignment});
+            if (function.instructions.len > 0) {
+                var block_incoming_len: u32 = undefined;
+                try writer.writeAll(" {\n");
+                for (params_len..function.instructions.len) |instruction_i| {
+                    const instruction_index: Function.Instruction.Index = @enumFromInt(instruction_i);
+                    const instruction = function.instructions.get(@intFromEnum(instruction_index));
+                    switch (instruction.tag) {
+                        .add,
+                        .@"add nsw",
+                        .@"add nuw",
+                        .@"add nuw nsw",
+                        .@"and",
+                        .ashr,
+                        .@"ashr exact",
+                        .fadd,
+                        .@"fadd fast",
+                        .@"fcmp false",
+                        .@"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",
+                        .@"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",
+                        .fdiv,
+                        .@"fdiv fast",
+                        .fmul,
+                        .@"fmul fast",
+                        .frem,
+                        .@"frem fast",
+                        .fsub,
+                        .@"fsub fast",
+                        .@"icmp eq",
+                        .@"icmp ne",
+                        .@"icmp sge",
+                        .@"icmp sgt",
+                        .@"icmp sle",
+                        .@"icmp slt",
+                        .@"icmp uge",
+                        .@"icmp ugt",
+                        .@"icmp ule",
+                        .@"icmp ult",
+                        .lshr,
+                        .@"lshr exact",
+                        .mul,
+                        .@"mul nsw",
+                        .@"mul nuw",
+                        .@"mul nuw nsw",
+                        .@"or",
+                        .sdiv,
+                        .@"sdiv exact",
+                        .srem,
+                        .shl,
+                        .@"shl nsw",
+                        .@"shl nuw",
+                        .@"shl nuw nsw",
+                        .sub,
+                        .@"sub nsw",
+                        .@"sub nuw",
+                        .@"sub nuw nsw",
+                        .udiv,
+                        .@"udiv exact",
+                        .urem,
+                        .xor,
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Binary, instruction.data);
+                            try writer.print("  %{} = {s} {%}, {}\n", .{
                                 instruction_index.name(&function).fmt(self),
-                            }),
-                            .none => unreachable,
-                        }
-                        try writer.print("{s}{}{}{} {%} {}(", .{
-                            @tagName(tag),
-                            extra.data.info.call_conv,
-                            extra.data.attributes.ret(self).fmt(self),
-                            extra.data.callee.typeOf(function_index, self).pointerAddrSpace(self),
-                            switch (extra.data.ty.functionKind(self)) {
-                                .normal => ret_ty,
-                                .vararg => extra.data.ty,
-                            }.fmt(self),
-                            extra.data.callee.fmt(function_index, self),
-                        });
-                        for (0.., args) |arg_index, arg| {
-                            if (arg_index > 0) try writer.writeAll(", ");
-                            try writer.print("{%}{} {}", .{
-                                arg.typeOf(function_index, self).fmt(self),
-                                extra.data.attributes.param(arg_index, self).fmt(self),
-                                arg.fmt(function_index, self),
+                                @tagName(tag),
+                                extra.lhs.fmt(function_index, self),
+                                extra.rhs.fmt(function_index, self),
                             });
-                        }
-                        try writer.writeByte(')');
-                        const call_function_attributes = extra.data.attributes.func(self);
-                        if (call_function_attributes != .none) try writer.print(" #{d}", .{
-                            (try attribute_groups.getOrPutValue(
-                                self.gpa,
-                                call_function_attributes,
-                                {},
-                            )).index,
-                        });
-                    },
-                    .extractelement => |tag| {
-                        const extra =
-                            function.extraData(Function.Instruction.ExtractElement, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.val.fmt(function_index, self),
-                            extra.index.fmt(function_index, self),
-                        });
-                    },
-                    .extractvalue => |tag| {
-                        var extra =
-                            function.extraDataTrail(Function.Instruction.ExtractValue, instruction.data);
-                        const indices = extra.trail.next(extra.data.indices_len, u32, &function);
-                        try writer.print("  %{} = {s} {%}", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.data.val.fmt(function_index, self),
-                        });
-                        for (indices) |index| try writer.print(", {d}", .{index});
-                        try writer.writeByte('\n');
-                    },
-                    .fence => |tag| {
-                        const info: MemoryAccessInfo = @bitCast(instruction.data);
-                        try writer.print("  {s}{}{}", .{ @tagName(tag), info.scope, info.ordering });
-                    },
-                    .fneg,
-                    .@"fneg fast",
-                    .ret,
-                    => |tag| {
-                        const val: Value = @enumFromInt(instruction.data);
-                        try writer.print("  {s} {%}\n", .{
-                            @tagName(tag),
-                            val.fmt(function_index, self),
-                        });
-                    },
-                    .getelementptr,
-                    .@"getelementptr inbounds",
-                    => |tag| {
-                        var extra = function.extraDataTrail(
-                            Function.Instruction.GetElementPtr,
-                            instruction.data,
-                        );
-                        const indices = extra.trail.next(extra.data.indices_len, Value, &function);
-                        try writer.print("  %{} = {s} {%}, {%}", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.data.type.fmt(self),
-                            extra.data.base.fmt(function_index, self),
-                        });
-                        for (indices) |index| try writer.print(", {%}", .{
-                            index.fmt(function_index, self),
-                        });
-                        try writer.writeByte('\n');
-                    },
-                    .insertelement => |tag| {
-                        const extra =
-                            function.extraData(Function.Instruction.InsertElement, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.val.fmt(function_index, self),
-                            extra.elem.fmt(function_index, self),
-                            extra.index.fmt(function_index, self),
-                        });
-                    },
-                    .insertvalue => |tag| {
-                        var extra =
-                            function.extraDataTrail(Function.Instruction.InsertValue, instruction.data);
-                        const indices = extra.trail.next(extra.data.indices_len, u32, &function);
-                        try writer.print("  %{} = {s} {%}, {%}", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.data.val.fmt(function_index, self),
-                            extra.data.elem.fmt(function_index, self),
-                        });
-                        for (indices) |index| try writer.print(", {d}", .{index});
-                        try writer.writeByte('\n');
-                    },
-                    .@"llvm.maxnum.",
-                    .@"llvm.minnum.",
-                    .@"llvm.sadd.sat.",
-                    .@"llvm.smax.",
-                    .@"llvm.smin.",
-                    .@"llvm.smul.fix.sat.",
-                    .@"llvm.sshl.sat.",
-                    .@"llvm.ssub.sat.",
-                    .@"llvm.uadd.sat.",
-                    .@"llvm.umax.",
-                    .@"llvm.umin.",
-                    .@"llvm.umul.fix.sat.",
-                    .@"llvm.ushl.sat.",
-                    .@"llvm.usub.sat.",
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Binary, instruction.data);
-                        const ty = instruction_index.typeOf(function_index, self);
-                        try writer.print("  %{} = call {%} @{s}{m}({%}, {%}{s})\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            ty.fmt(self),
-                            @tagName(tag),
-                            ty.fmt(self),
-                            extra.lhs.fmt(function_index, self),
-                            extra.rhs.fmt(function_index, self),
-                            switch (tag) {
-                                .@"llvm.smul.fix.sat.",
-                                .@"llvm.umul.fix.sat.",
-                                => ", i32 0",
-                                else => "",
-                            },
-                        });
-                    },
-                    .load,
-                    .@"load atomic",
-                    .@"load atomic volatile",
-                    .@"load volatile",
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Load, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}{}{}{,}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.type.fmt(self),
-                            extra.ptr.fmt(function_index, self),
-                            extra.info.scope,
-                            extra.info.ordering,
-                            extra.info.alignment,
-                        });
-                    },
-                    .phi,
-                    .@"phi fast",
-                    => |tag| {
-                        var extra = function.extraDataTrail(Function.Instruction.Phi, instruction.data);
-                        const vals = extra.trail.next(block_incoming_len, Value, &function);
-                        const blocks =
-                            extra.trail.next(block_incoming_len, Function.Block.Index, &function);
-                        try writer.print("  %{} = {s} {%} ", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            vals[0].typeOf(function_index, self).fmt(self),
-                        });
-                        for (0.., vals, blocks) |incoming_index, incoming_val, incoming_block| {
-                            if (incoming_index > 0) try writer.writeAll(", ");
-                            try writer.print("[ {}, {} ]", .{
-                                incoming_val.fmt(function_index, self),
-                                incoming_block.toInst(&function).fmt(function_index, self),
+                        },
+                        .addrspacecast,
+                        .bitcast,
+                        .fpext,
+                        .fptosi,
+                        .fptoui,
+                        .fptrunc,
+                        .inttoptr,
+                        .ptrtoint,
+                        .sext,
+                        .sitofp,
+                        .trunc,
+                        .uitofp,
+                        .zext,
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Cast, instruction.data);
+                            try writer.print("  %{} = {s} {%} to {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.val.fmt(function_index, self),
+                                extra.type.fmt(self),
                             });
-                        }
-                        try writer.writeByte('\n');
-                    },
-                    .@"ret void",
-                    .@"unreachable",
-                    => |tag| try writer.print("  {s}\n", .{@tagName(tag)}),
-                    .select,
-                    .@"select fast",
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Select, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.cond.fmt(function_index, self),
-                            extra.lhs.fmt(function_index, self),
-                            extra.rhs.fmt(function_index, self),
-                        });
-                    },
-                    .shufflevector => |tag| {
-                        const extra =
-                            function.extraData(Function.Instruction.ShuffleVector, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.lhs.fmt(function_index, self),
-                            extra.rhs.fmt(function_index, self),
-                            extra.mask.fmt(function_index, self),
-                        });
-                    },
-                    .store,
-                    .@"store atomic",
-                    .@"store atomic volatile",
-                    .@"store volatile",
-                    => |tag| {
-                        const extra = function.extraData(Function.Instruction.Store, instruction.data);
-                        try writer.print("  {s} {%}, {%}{}{}{,}\n", .{
-                            @tagName(tag),
-                            extra.val.fmt(function_index, self),
-                            extra.ptr.fmt(function_index, self),
-                            extra.info.scope,
-                            extra.info.ordering,
-                            extra.info.alignment,
-                        });
-                    },
-                    .@"switch" => |tag| {
-                        var extra =
-                            function.extraDataTrail(Function.Instruction.Switch, instruction.data);
-                        const vals = extra.trail.next(extra.data.cases_len, Constant, &function);
-                        const blocks =
-                            extra.trail.next(extra.data.cases_len, Function.Block.Index, &function);
-                        try writer.print("  {s} {%}, {%} [\n", .{
-                            @tagName(tag),
-                            extra.data.val.fmt(function_index, self),
-                            extra.data.default.toInst(&function).fmt(function_index, self),
-                        });
-                        for (vals, blocks) |case_val, case_block| try writer.print("    {%}, {%}\n", .{
-                            case_val.fmt(self),
-                            case_block.toInst(&function).fmt(function_index, self),
-                        });
-                        try writer.writeAll("  ]\n");
-                    },
-                    .unimplemented => |tag| {
-                        const ty: Type = @enumFromInt(instruction.data);
-                        if (true) {
+                        },
+                        .alloca,
+                        .@"alloca inalloca",
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Alloca, instruction.data);
+                            try writer.print("  %{} = {s} {%}{,%}{,}{,}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.type.fmt(self),
+                                extra.len.fmt(function_index, self),
+                                extra.info.alignment,
+                                extra.info.addr_space,
+                            });
+                        },
+                        .arg => unreachable,
+                        .block => {
+                            block_incoming_len = instruction.data;
+                            const name = instruction_index.name(&function);
+                            if (@intFromEnum(instruction_index) > params_len)
+                                try writer.writeByte('\n');
+                            try writer.print("{}:\n", .{name.fmt(self)});
+                        },
+                        .br => |tag| {
+                            const target: Function.Block.Index = @enumFromInt(instruction.data);
+                            try writer.print("  {s} {%}\n", .{
+                                @tagName(tag), target.toInst(&function).fmt(function_index, self),
+                            });
+                        },
+                        .br_cond => {
+                            const extra =
+                                function.extraData(Function.Instruction.BrCond, instruction.data);
+                            try writer.print("  br {%}, {%}, {%}\n", .{
+                                extra.cond.fmt(function_index, self),
+                                extra.then.toInst(&function).fmt(function_index, self),
+                                extra.@"else".toInst(&function).fmt(function_index, self),
+                            });
+                        },
+                        .call,
+                        .@"call fast",
+                        .@"musttail call",
+                        .@"musttail call fast",
+                        .@"notail call",
+                        .@"notail call fast",
+                        .@"tail call",
+                        .@"tail call fast",
+                        => |tag| {
+                            var extra =
+                                function.extraDataTrail(Function.Instruction.Call, instruction.data);
+                            const args = extra.trail.next(extra.data.args_len, Value, &function);
                             try writer.writeAll("  ");
-                            switch (ty) {
-                                .none, .void => {},
+                            const ret_ty = extra.data.ty.functionReturn(self);
+                            switch (ret_ty) {
+                                .void => {},
                                 else => try writer.print("%{} = ", .{
                                     instruction_index.name(&function).fmt(self),
                                 }),
+                                .none => unreachable,
+                            }
+                            try writer.print("{s}{}{}{} {%} {}(", .{
+                                @tagName(tag),
+                                extra.data.info.call_conv,
+                                extra.data.attributes.ret(self).fmt(self),
+                                extra.data.callee.typeOf(function_index, self).pointerAddrSpace(self),
+                                switch (extra.data.ty.functionKind(self)) {
+                                    .normal => ret_ty,
+                                    .vararg => extra.data.ty,
+                                }.fmt(self),
+                                extra.data.callee.fmt(function_index, self),
+                            });
+                            for (0.., args) |arg_index, arg| {
+                                if (arg_index > 0) try writer.writeAll(", ");
+                                try writer.print("{%}{} {}", .{
+                                    arg.typeOf(function_index, self).fmt(self),
+                                    extra.data.attributes.param(arg_index, self).fmt(self),
+                                    arg.fmt(function_index, self),
+                                });
                             }
-                            try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) });
-                        } else switch (ty) {
-                            .none, .void => {},
-                            else => try writer.print("  %{} = load {%}, ptr undef\n", .{
+                            try writer.writeByte(')');
+                            const call_function_attributes = extra.data.attributes.func(self);
+                            if (call_function_attributes != .none) try writer.print(" #{d}", .{
+                                (try attribute_groups.getOrPutValue(
+                                    self.gpa,
+                                    call_function_attributes,
+                                    {},
+                                )).index,
+                            });
+                            try writer.writeByte('\n');
+                        },
+                        .extractelement => |tag| {
+                            const extra = function.extraData(
+                                Function.Instruction.ExtractElement,
+                                instruction.data,
+                            );
+                            try writer.print("  %{} = {s} {%}, {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.val.fmt(function_index, self),
+                                extra.index.fmt(function_index, self),
+                            });
+                        },
+                        .extractvalue => |tag| {
+                            var extra = function.extraDataTrail(
+                                Function.Instruction.ExtractValue,
+                                instruction.data,
+                            );
+                            const indices = extra.trail.next(extra.data.indices_len, u32, &function);
+                            try writer.print("  %{} = {s} {%}", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.data.val.fmt(function_index, self),
+                            });
+                            for (indices) |index| try writer.print(", {d}", .{index});
+                            try writer.writeByte('\n');
+                        },
+                        .fence => |tag| {
+                            const info: MemoryAccessInfo = @bitCast(instruction.data);
+                            try writer.print("  {s}{}{}", .{ @tagName(tag), info.scope, info.ordering });
+                        },
+                        .fneg,
+                        .@"fneg fast",
+                        .ret,
+                        => |tag| {
+                            const val: Value = @enumFromInt(instruction.data);
+                            try writer.print("  {s} {%}\n", .{
+                                @tagName(tag),
+                                val.fmt(function_index, self),
+                            });
+                        },
+                        .getelementptr,
+                        .@"getelementptr inbounds",
+                        => |tag| {
+                            var extra = function.extraDataTrail(
+                                Function.Instruction.GetElementPtr,
+                                instruction.data,
+                            );
+                            const indices = extra.trail.next(extra.data.indices_len, Value, &function);
+                            try writer.print("  %{} = {s} {%}, {%}", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.data.type.fmt(self),
+                                extra.data.base.fmt(function_index, self),
+                            });
+                            for (indices) |index| try writer.print(", {%}", .{
+                                index.fmt(function_index, self),
+                            });
+                            try writer.writeByte('\n');
+                        },
+                        .insertelement => |tag| {
+                            const extra = function.extraData(
+                                Function.Instruction.InsertElement,
+                                instruction.data,
+                            );
+                            try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.val.fmt(function_index, self),
+                                extra.elem.fmt(function_index, self),
+                                extra.index.fmt(function_index, self),
+                            });
+                        },
+                        .insertvalue => |tag| {
+                            var extra = function.extraDataTrail(
+                                Function.Instruction.InsertValue,
+                                instruction.data,
+                            );
+                            const indices = extra.trail.next(extra.data.indices_len, u32, &function);
+                            try writer.print("  %{} = {s} {%}, {%}", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.data.val.fmt(function_index, self),
+                                extra.data.elem.fmt(function_index, self),
+                            });
+                            for (indices) |index| try writer.print(", {d}", .{index});
+                            try writer.writeByte('\n');
+                        },
+                        .@"llvm.maxnum.",
+                        .@"llvm.minnum.",
+                        .@"llvm.sadd.sat.",
+                        .@"llvm.smax.",
+                        .@"llvm.smin.",
+                        .@"llvm.smul.fix.sat.",
+                        .@"llvm.sshl.sat.",
+                        .@"llvm.ssub.sat.",
+                        .@"llvm.uadd.sat.",
+                        .@"llvm.umax.",
+                        .@"llvm.umin.",
+                        .@"llvm.umul.fix.sat.",
+                        .@"llvm.ushl.sat.",
+                        .@"llvm.usub.sat.",
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Binary, instruction.data);
+                            const ty = instruction_index.typeOf(function_index, self);
+                            try writer.print("  %{} = call {%} @{s}{m}({%}, {%}{s})\n", .{
                                 instruction_index.name(&function).fmt(self),
                                 ty.fmt(self),
-                            }),
-                        }
-                    },
-                    .va_arg => |tag| {
-                        const extra = function.extraData(Function.Instruction.VaArg, instruction.data);
-                        try writer.print("  %{} = {s} {%}, {%}\n", .{
-                            instruction_index.name(&function).fmt(self),
-                            @tagName(tag),
-                            extra.list.fmt(function_index, self),
-                            extra.type.fmt(self),
-                        });
-                    },
+                                @tagName(tag),
+                                ty.fmt(self),
+                                extra.lhs.fmt(function_index, self),
+                                extra.rhs.fmt(function_index, self),
+                                switch (tag) {
+                                    .@"llvm.smul.fix.sat.",
+                                    .@"llvm.umul.fix.sat.",
+                                    => ", i32 0",
+                                    else => "",
+                                },
+                            });
+                        },
+                        .load,
+                        .@"load atomic",
+                        .@"load atomic volatile",
+                        .@"load volatile",
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Load, instruction.data);
+                            try writer.print("  %{} = {s} {%}, {%}{}{}{,}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.type.fmt(self),
+                                extra.ptr.fmt(function_index, self),
+                                extra.info.scope,
+                                extra.info.ordering,
+                                extra.info.alignment,
+                            });
+                        },
+                        .phi,
+                        .@"phi fast",
+                        => |tag| {
+                            var extra =
+                                function.extraDataTrail(Function.Instruction.Phi, instruction.data);
+                            const vals = extra.trail.next(block_incoming_len, Value, &function);
+                            const blocks =
+                                extra.trail.next(block_incoming_len, Function.Block.Index, &function);
+                            try writer.print("  %{} = {s} {%} ", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                vals[0].typeOf(function_index, self).fmt(self),
+                            });
+                            for (0.., vals, blocks) |incoming_index, incoming_val, incoming_block| {
+                                if (incoming_index > 0) try writer.writeAll(", ");
+                                try writer.print("[ {}, {} ]", .{
+                                    incoming_val.fmt(function_index, self),
+                                    incoming_block.toInst(&function).fmt(function_index, self),
+                                });
+                            }
+                            try writer.writeByte('\n');
+                        },
+                        .@"ret void",
+                        .@"unreachable",
+                        => |tag| try writer.print("  {s}\n", .{@tagName(tag)}),
+                        .select,
+                        .@"select fast",
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Select, instruction.data);
+                            try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.cond.fmt(function_index, self),
+                                extra.lhs.fmt(function_index, self),
+                                extra.rhs.fmt(function_index, self),
+                            });
+                        },
+                        .shufflevector => |tag| {
+                            const extra = function.extraData(
+                                Function.Instruction.ShuffleVector,
+                                instruction.data,
+                            );
+                            try writer.print("  %{} = {s} {%}, {%}, {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.lhs.fmt(function_index, self),
+                                extra.rhs.fmt(function_index, self),
+                                extra.mask.fmt(function_index, self),
+                            });
+                        },
+                        .store,
+                        .@"store atomic",
+                        .@"store atomic volatile",
+                        .@"store volatile",
+                        => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.Store, instruction.data);
+                            try writer.print("  {s} {%}, {%}{}{}{,}\n", .{
+                                @tagName(tag),
+                                extra.val.fmt(function_index, self),
+                                extra.ptr.fmt(function_index, self),
+                                extra.info.scope,
+                                extra.info.ordering,
+                                extra.info.alignment,
+                            });
+                        },
+                        .@"switch" => |tag| {
+                            var extra =
+                                function.extraDataTrail(Function.Instruction.Switch, instruction.data);
+                            const vals = extra.trail.next(extra.data.cases_len, Constant, &function);
+                            const blocks =
+                                extra.trail.next(extra.data.cases_len, Function.Block.Index, &function);
+                            try writer.print("  {s} {%}, {%} [\n", .{
+                                @tagName(tag),
+                                extra.data.val.fmt(function_index, self),
+                                extra.data.default.toInst(&function).fmt(function_index, self),
+                            });
+                            for (vals, blocks) |case_val, case_block| try writer.print(
+                                "    {%}, {%}\n",
+                                .{
+                                    case_val.fmt(self),
+                                    case_block.toInst(&function).fmt(function_index, self),
+                                },
+                            );
+                            try writer.writeAll("  ]\n");
+                        },
+                        .unimplemented => |tag| {
+                            const ty: Type = @enumFromInt(instruction.data);
+                            if (true) {
+                                try writer.writeAll("  ");
+                                switch (ty) {
+                                    .none, .void => {},
+                                    else => try writer.print("%{} = ", .{
+                                        instruction_index.name(&function).fmt(self),
+                                    }),
+                                }
+                                try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) });
+                            } else switch (ty) {
+                                .none, .void => {},
+                                else => try writer.print("  %{} = load {%}, ptr undef\n", .{
+                                    instruction_index.name(&function).fmt(self),
+                                    ty.fmt(self),
+                                }),
+                            }
+                        },
+                        .va_arg => |tag| {
+                            const extra =
+                                function.extraData(Function.Instruction.VaArg, instruction.data);
+                            try writer.print("  %{} = {s} {%}, {%}\n", .{
+                                instruction_index.name(&function).fmt(self),
+                                @tagName(tag),
+                                extra.list.fmt(function_index, self),
+                                extra.type.fmt(self),
+                            });
+                        },
+                    }
                 }
+                try writer.writeByte('}');
             }
-            try writer.writeByte('}');
+            try writer.writeByte('\n');
         }
-        try writer.writeAll("\n\n");
+        need_newline = true;
     }
 
-    for (0.., attribute_groups.keys()) |attribute_group_index, attribute_group|
-        try writer.print(
-            \\attributes #{d} = {{{#"} }}
-            \\
-        , .{ attribute_group_index, attribute_group.fmt(self) });
+    if (attribute_groups.count() > 0) {
+        if (need_newline) try writer.writeByte('\n') else need_newline = true;
+        for (0.., attribute_groups.keys()) |attribute_group_index, attribute_group|
+            try writer.print(
+                \\attributes #{d} = {{{#"} }}
+                \\
+            , .{ attribute_group_index, attribute_group.fmt(self) });
+        need_newline = true;
+    }
 }
 
 pub inline fn useLibLlvm(self: *const Builder) bool {
@@ -7736,7 +7807,7 @@ pub inline fn useLibLlvm(self: *const Builder) bool {
 const NoExtra = struct {};
 
 fn isValidIdentifier(id: []const u8) bool {
-    for (id, 0..) |character, index| switch (character) {
+    for (id, 0..) |byte, index| switch (byte) {
         '$', '-', '.', 'A'...'Z', '_', 'a'...'z' => {},
         '0'...'9' => if (index == 0) return false,
         else => return false,
@@ -7744,6 +7815,25 @@ fn isValidIdentifier(id: []const u8) bool {
     return true;
 }
 
+const QuoteBehavior = enum { always_quote, quote_unless_valid_identifier };
+fn printEscapedString(
+    slice: []const u8,
+    quotes: QuoteBehavior,
+    writer: anytype,
+) @TypeOf(writer).Error!void {
+    const need_quotes = switch (quotes) {
+        .always_quote => true,
+        .quote_unless_valid_identifier => !isValidIdentifier(slice),
+    };
+    if (need_quotes) try writer.writeByte('"');
+    for (slice) |byte| switch (byte) {
+        '\\' => try writer.writeAll("\\\\"),
+        ' '...'"' - 1, '"' + 1...'\\' - 1, '\\' + 1...'~' => try writer.writeByte(byte),
+        else => try writer.print("\\{X:0>2}", .{byte}),
+    };
+    if (need_quotes) try writer.writeByte('"');
+}
+
 fn ensureUnusedGlobalCapacity(self: *Builder, name: String) Allocator.Error!void {
     if (self.useLibLlvm()) try self.llvm.globals.ensureUnusedCapacity(self.gpa, 1);
     try self.string_map.ensureUnusedCapacity(self.gpa, 1);
@@ -9318,6 +9408,8 @@ fn asmConstAssumeCapacity(
     assembly: String,
     constraints: String,
 ) Constant {
+    assert(ty.functionKind(self) == .normal);
+
     const Key = struct { tag: Constant.Tag, extra: Constant.Asm };
     const Adapter = struct {
         builder: *const Builder,
src/codegen/llvm.zig
@@ -1034,15 +1034,11 @@ pub const Object = struct {
 
     fn genModuleLevelAssembly(object: *Object) !void {
         const mod = object.module;
-        if (mod.global_assembly.count() == 0) return;
-        var buffer = std.ArrayList(u8).init(mod.gpa);
-        defer buffer.deinit();
-        var it = mod.global_assembly.iterator();
-        while (it.next()) |kv| {
-            try buffer.appendSlice(kv.value_ptr.*);
-            try buffer.append('\n');
-        }
-        object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1);
+
+        const writer = object.builder.setModuleAsm();
+        var it = mod.global_assembly.valueIterator();
+        while (it.next()) |assembly| try writer.print("{s}\n", .{assembly.*});
+        try object.builder.finishModuleAsm();
     }
 
     fn resolveExportExternCollisions(object: *Object) !void {