From 32edb9b55d0e41a58a11e0437149c4a9705c4699 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 17 Feb 2022 22:20:49 -0700 Subject: [PATCH 1/3] stage2: eliminate ZIR arg instruction references to ZIR Prior to this commit, the AIR arg instruction kept a reference to a ZIR string index for the corresponding parameter name. This is used by DWARF emitting code. However, this is a design flaw because we want AIR objects to be independent from ZIR. This commit saves the parameter names into memory managed by `Module.Fn`. This is sub-optimal because we should be able to get the parameter names from the ZIR for a function without having them redundantly stored along with `Fn` memory. However the current way that ZIR param instructions are encoded does not support this case. They appear in the same ZIR body as the function instruction, just before it. Instead, they should be embedded within the function instruction, which will allow this TODO to be solved. That improvement is too big for this commit, however. After this there is one last dependency to untangle, which is for inline assembly. The issue for that is #10784. --- src/Air.zig | 11 ++--------- src/Module.zig | 26 +++++++++++++++++++++----- src/Sema.zig | 28 ++++++++++++++-------------- src/arch/arm/Emit.zig | 6 ++---- src/arch/riscv64/CodeGen.zig | 10 ++++------ src/arch/x86_64/Emit.zig | 10 ++++------ src/print_air.zig | 9 +-------- 7 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 623da2625543..586fe65c677c 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -32,8 +32,7 @@ pub const Inst = struct { /// The first N instructions in the main block must be one arg instruction per /// function parameter. This makes function parameters participate in /// liveness analysis without any special handling. - /// Uses the `ty_str` field. - /// The string is the parameter name. + /// Uses the `ty` field. arg, /// Float or integer addition. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type @@ -615,11 +614,6 @@ pub const Inst = struct { // Index into a different array. payload: u32, }, - ty_str: struct { - ty: Ref, - // ZIR string table index. - str: u32, - }, br: struct { block_inst: Index, operand: Ref, @@ -759,8 +753,6 @@ pub fn typeOf(air: Air, inst: Air.Inst.Ref) Type { pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { const datas = air.instructions.items(.data); switch (air.instructions.items(.tag)[inst]) { - .arg => return air.getRefType(datas[inst].ty_str.ty), - .add, .addwrap, .add_sat, @@ -827,6 +819,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .alloc, .ret_ptr, + .arg, => return datas[inst].ty, .assembly, diff --git a/src/Module.zig b/src/Module.zig index 524e8402cdc5..15e90ab068e2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1370,6 +1370,14 @@ pub const Fn = struct { /// ZIR instruction. zir_body_inst: Zir.Inst.Index, + /// Prefer to use `getParamName` to access this because of the future improvement + /// we want to do mentioned in the TODO below. + /// Stored in gpa. + /// TODO: change param ZIR instructions to be embedded inside the function + /// ZIR instruction instead of before it, so that `zir_body_inst` can be used to + /// determine param names rather than redundantly storing them here. + param_names: []const [:0]const u8, + /// Relative to owner Decl. lbrace_line: u32, /// Relative to owner Decl. @@ -1466,6 +1474,18 @@ pub const Fn = struct { gpa.destroy(node); it = next; } + + for (func.param_names) |param_name| { + gpa.free(param_name); + } + gpa.free(func.param_names); + } + + pub fn getParamName(func: Fn, index: u32) [:0]const u8 { + // TODO rework ZIR of parameters so that this function looks up + // param names in ZIR instead of redundantly saving them into Fn. + // const zir = func.owner_decl.getFileScope().zir; + return func.param_names[index]; } }; @@ -4606,15 +4626,11 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: Allocator) Sem runtime_param_index += 1; continue; } - const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); sema.air_instructions.appendAssumeCapacity(.{ .tag = .arg, - .data = .{ .ty_str = .{ - .ty = ty_ref, - .str = param.name, - } }, + .data = .{ .ty = param_type }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); total_param_index += 1; diff --git a/src/Sema.zig b/src/Sema.zig index a153be51194a..7bf1b88260b5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -134,6 +134,7 @@ pub const Block = struct { /// `noreturn` means `anytype`. ty: Type, is_comptime: bool, + name: []const u8, }; /// This `Block` maps a block ZIR instruction to the corresponding @@ -284,13 +285,10 @@ pub const Block = struct { }); } - fn addArg(block: *Block, ty: Type, name: u32) error{OutOfMemory}!Air.Inst.Ref { + fn addArg(block: *Block, ty: Type) error{OutOfMemory}!Air.Inst.Ref { return block.addInst(.{ .tag = .arg, - .data = .{ .ty_str = .{ - .ty = try block.sema.addType(ty), - .str = name, - } }, + .data = .{ .ty = ty }, }); } @@ -4645,7 +4643,7 @@ fn analyzeCall( } else { // We insert into the map an instruction which is runtime-known // but has the type of the argument. - const child_arg = try child_block.addArg(arg_ty, 0); + const child_arg = try child_block.addArg(arg_ty); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } } @@ -5712,6 +5710,11 @@ fn funcCommon( break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr; } else null; + const param_names = try sema.gpa.alloc([:0]const u8, block.params.items.len); + for (param_names) |*param_name, i| { + param_name.* = try sema.gpa.dupeZ(u8, block.params.items[i].name); + } + const fn_payload = try sema.arena.create(Value.Payload.Function); new_func.* = .{ .state = anal_state, @@ -5722,6 +5725,7 @@ fn funcCommon( .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), .rbrace_column = @truncate(u16, src_locs.columns >> 16), + .param_names = param_names, }; if (maybe_inferred_error_set_node) |node| { new_func.inferred_error_sets.prepend(node); @@ -5746,10 +5750,6 @@ fn zirParam( const param_name = sema.code.nullTerminatedString(extra.data.name); const body = sema.code.extra[extra.end..][0..extra.data.body_len]; - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - _ = param_name; - // We could be in a generic function instantiation, or we could be evaluating a generic // function without any comptime args provided. const param_ty = param_ty: { @@ -5776,6 +5776,7 @@ fn zirParam( try block.params.append(sema.gpa, .{ .ty = Type.initTag(.generic_poison), .is_comptime = comptime_syntax, + .name = param_name, }); try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison); return; @@ -5801,6 +5802,7 @@ fn zirParam( try block.params.append(sema.gpa, .{ .ty = param_ty, .is_comptime = is_comptime, + .name = param_name, }); const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); try sema.inst_map.putNoClobber(sema.gpa, inst, result); @@ -5816,10 +5818,6 @@ fn zirParamAnytype( const src = inst_data.src(); const param_name = inst_data.get(sema.code); - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - _ = param_name; - if (sema.inst_map.get(inst)) |air_ref| { const param_ty = sema.typeOf(air_ref); if (comptime_syntax or try sema.typeRequiresComptime(block, src, param_ty)) { @@ -5831,6 +5829,7 @@ fn zirParamAnytype( try block.params.append(sema.gpa, .{ .ty = param_ty, .is_comptime = false, + .name = param_name, }); return; } @@ -5840,6 +5839,7 @@ fn zirParamAnytype( try block.params.append(sema.gpa, .{ .ty = Type.initTag(.generic_poison), .is_comptime = comptime_syntax, + .name = param_name, }); try sema.inst_map.put(sema.gpa, inst, .generic_poison); } diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index 72eb4307696d..3ee70583d06f 100644 --- a/src/arch/arm/Emit.zig +++ b/src/arch/arm/Emit.zig @@ -393,11 +393,9 @@ fn addDbgInfoTypeReloc(self: *Emit, ty: Type) !void { fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void { const mcv = self.function.args[arg_index]; - const ty_str = self.function.air.instructions.items(.data)[inst].ty_str; - const zir = &self.function.mod_fn.owner_decl.getFileScope().zir; - const name = zir.nullTerminatedString(ty_str.str); + const ty = self.function.air.instructions.items(.data)[inst].ty; + const name = self.function.mod_fn.getParamName(arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; - const ty = self.function.air.getRefType(ty_str.ty); switch (mcv) { .register => |reg| { diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 09ca92f22910..a6e15dd76c2c 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1340,12 +1340,10 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); } -fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue) !void { - const ty_str = self.air.instructions.items(.data)[inst].ty_str; - const zir = &self.mod_fn.owner_decl.getFileScope().zir; - const name = zir.nullTerminatedString(ty_str.str); +fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { + const ty = self.air.instructions.items(.data)[inst].ty; + const name = self.mod_fn.getParamName(arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; - const ty = self.air.getRefType(ty_str.ty); switch (mcv) { .register => |reg| { @@ -1388,7 +1386,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { // TODO support stack-only arguments // TODO Copy registers to the stack const mcv = result; - try self.genArgDbgInfo(inst, mcv); + try self.genArgDbgInfo(inst, mcv, @intCast(u32, arg_index)); if (self.liveness.isUnused(inst)) return self.finishAirBookkeeping(); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5ad3508aa2fb..5d15b28ee8d5 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -946,15 +946,13 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data; const mcv = emit.mir.function.args[arg_dbg_info.arg_index]; - try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv, arg_dbg_info.max_stack); + try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv, arg_dbg_info.max_stack, arg_dbg_info.arg_index); } -fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32) !void { - const ty_str = emit.mir.function.air.instructions.items(.data)[inst].ty_str; - const zir = &emit.mir.function.mod_fn.owner_decl.getFileScope().zir; - const name = zir.nullTerminatedString(ty_str.str); +fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32, arg_index: u32) !void { + const ty = emit.mir.function.air.instructions.items(.data)[inst].ty; + const name = emit.mir.function.mod_fn.getParamName(arg_index); const name_with_null = name.ptr[0 .. name.len + 1]; - const ty = emit.mir.function.air.getRefType(ty_str.ty); switch (mcv) { .register => |reg| { diff --git a/src/print_air.zig b/src/print_air.zig index bcadf31e7466..7270f626021a 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -100,8 +100,6 @@ const Writer = struct { const tag = tags[inst]; try s.print("= {s}(", .{@tagName(tags[inst])}); switch (tag) { - .arg => try w.writeTyStr(s, inst), - .add, .addwrap, .add_sat, @@ -181,6 +179,7 @@ const Writer = struct { .const_ty, .alloc, .ret_ptr, + .arg, => try w.writeTy(s, inst), .not, @@ -257,12 +256,6 @@ const Writer = struct { } } - fn writeTyStr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { - const ty_str = w.air.instructions.items(.data)[inst].ty_str; - const name = w.zir.nullTerminatedString(ty_str.str); - try s.print("\"{}\", {}", .{ std.zig.fmtEscapes(name), w.air.getRefType(ty_str.ty) }); - } - fn writeBinOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const bin_op = w.air.instructions.items(.data)[inst].bin_op; try w.writeOperand(s, inst, 0, bin_op.lhs); From 123076ea88a809888692b308ab5bb214dc3a2caf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Feb 2022 19:35:39 -0700 Subject: [PATCH 2/3] ArrayList: add unusedCapacitySlice to the unmanaged API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function is super nice thank you whoever added it 👍 --- lib/std/array_list.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 76e73a49b566..eb726f4dfa37 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -780,6 +780,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ pub fn allocatedSlice(self: Self) Slice { return self.items.ptr[0..self.capacity]; } + + /// Returns a slice of only the extra capacity after items. + /// This can be useful for writing directly into an ArrayList. + /// Note that such an operation must be followed up with a direct + /// modification of `self.items.len`. + pub fn unusedCapacitySlice(self: Self) Slice { + return self.allocatedSlice()[self.items.len..]; + } }; } From 4e1e5ab6221b72ef2be9f1fb40c2e6d1235718fe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Feb 2022 19:41:32 -0700 Subject: [PATCH 3/3] stage2: make AIR not reference ZIR for inline assembly Instead it stores all the information it needs to into AIR. closes #10784 --- src/Air.zig | 22 ++++-- src/Compilation.zig | 4 +- src/Liveness.zig | 38 ++++++---- src/Sema.zig | 52 ++++++++++++-- src/arch/aarch64/CodeGen.zig | 133 +++++++++++++++++------------------ src/arch/arm/CodeGen.zig | 103 ++++++++++++++++----------- src/arch/riscv64/CodeGen.zig | 102 ++++++++++++++++----------- src/arch/x86_64/CodeGen.zig | 112 ++++++++++++++++++----------- src/codegen/c.zig | 97 ++++++++++++++----------- src/codegen/llvm.zig | 101 +++++++++++++------------- src/print_air.zig | 93 +++++++++++++----------- 11 files changed, 506 insertions(+), 351 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 586fe65c677c..5bb6d9f3ff13 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -697,11 +697,25 @@ pub const Bin = struct { /// Trailing: /// 0. `Inst.Ref` for every outputs_len /// 1. `Inst.Ref` for every inputs_len +/// 2. for every outputs_len +/// - constraint: memory at this position is reinterpreted as a null +/// terminated string. pad to the next u32 after the null byte. +/// 3. for every inputs_len +/// - constraint: memory at this position is reinterpreted as a null +/// terminated string. pad to the next u32 after the null byte. +/// 4. for every clobbers_len +/// - clobber_name: memory at this position is reinterpreted as a null +/// terminated string. pad to the next u32 after the null byte. +/// 5. A number of u32 elements follow according to the equation `(source_len + 3) / 4`. +/// Memory starting at this position is reinterpreted as the source bytes. pub const Asm = struct { - /// Index to the corresponding ZIR instruction. - /// `asm_source`, `outputs_len`, `inputs_len`, `clobbers_len`, `is_volatile`, and - /// clobbers are found via here. - zir_index: u32, + /// Length of the assembly source in bytes. + source_len: u32, + outputs_len: u32, + inputs_len: u32, + /// The MSB is `is_volatile`. + /// The rest of the bits are `clobbers_len`. + flags: u32, }; pub const Cmpxchg = struct { diff --git a/src/Compilation.zig b/src/Compilation.zig index e776b43508a9..118fa92a0066 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2778,7 +2778,7 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress errdefer if (!liveness_frame_ended) liveness_frame.end(); log.debug("analyze liveness of {s}", .{decl.name}); - var liveness = try Liveness.analyze(gpa, air, decl.getFileScope().zir); + var liveness = try Liveness.analyze(gpa, air); defer liveness.deinit(gpa); liveness_frame.end(); @@ -2786,7 +2786,7 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress if (builtin.mode == .Debug and comp.verbose_air) { std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name}); - @import("print_air.zig").dump(gpa, air, decl.getFileScope().zir, liveness); + @import("print_air.zig").dump(gpa, air, liveness); std.debug.print("# End Function AIR: {s}\n\n", .{decl.name}); } diff --git a/src/Liveness.zig b/src/Liveness.zig index 12ba63fc005b..e2c729b87368 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -12,7 +12,6 @@ const log = std.log.scoped(.liveness); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Air = @import("Air.zig"); -const Zir = @import("Zir.zig"); const Log2Int = std.math.Log2Int; /// This array is split into sets of 4 bits per AIR instruction. @@ -52,7 +51,7 @@ pub const SwitchBr = struct { else_death_count: u32, }; -pub fn analyze(gpa: Allocator, air: Air, zir: Zir) Allocator.Error!Liveness { +pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness { const tracy = trace(@src()); defer tracy.end(); @@ -66,7 +65,6 @@ pub fn analyze(gpa: Allocator, air: Air, zir: Zir) Allocator.Error!Liveness { ), .extra = .{}, .special = .{}, - .zir = &zir, }; errdefer gpa.free(a.tomb_bits); errdefer a.special.deinit(gpa); @@ -157,7 +155,6 @@ const Analysis = struct { tomb_bits: []usize, special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32), extra: std.ArrayListUnmanaged(u32), - zir: *const Zir, fn storeTombBits(a: *Analysis, inst: Air.Inst.Index, tomb_bits: Bpi) void { const usize_index = (inst * bpi) / @bitSizeOf(usize); @@ -444,15 +441,24 @@ fn analyzeInst( }, .assembly => { const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload); - const extended = a.zir.instructions.items(.data)[extra.data.zir_index].extended; - const outputs_len = @truncate(u5, extended.small); - const inputs_len = @truncate(u5, extended.small >> 5); - const outputs = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end + outputs.len ..][0..inputs_len]); - if (outputs.len + args.len <= bpi - 1) { + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; + + simple: { var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1); - std.mem.copy(Air.Inst.Ref, &buf, outputs); - std.mem.copy(Air.Inst.Ref, buf[outputs.len..], args); + var buf_index: usize = 0; + for (outputs) |output| { + if (output != .none) { + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); return trackOperands(a, new_set, inst, main_tomb, buf); } var extra_tombs: ExtraTombs = .{ @@ -462,10 +468,12 @@ fn analyzeInst( .main_tomb = main_tomb, }; for (outputs) |output| { - try extra_tombs.feed(output); + if (output != .none) { + try extra_tombs.feed(output); + } } - for (args) |arg| { - try extra_tombs.feed(arg); + for (inputs) |input| { + try extra_tombs.feed(input); } return extra_tombs.finish(); }, diff --git a/src/Sema.zig b/src/Sema.zig index 7bf1b88260b5..d3b6ae25210e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1124,7 +1124,7 @@ fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .frame_address => return sema.zirFrameAddress( block, extended), .alloc => return sema.zirAllocExtended( block, extended), .builtin_extern => return sema.zirBuiltinExtern( block, extended), - .@"asm" => return sema.zirAsm( block, extended, inst), + .@"asm" => return sema.zirAsm( block, extended), .typeof_peer => return sema.zirTypeofPeer( block, extended), .compile_log => return sema.zirCompileLog( block, extended), .add_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), @@ -9083,7 +9083,6 @@ fn zirAsm( sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, - inst: Zir.Inst.Index, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -9094,6 +9093,7 @@ fn zirAsm( const outputs_len = @truncate(u5, extended.small); const inputs_len = @truncate(u5, extended.small >> 5); const clobbers_len = @truncate(u5, extended.small >> 10); + const is_volatile = @truncate(u1, extended.small >> 15) != 0; if (extra.data.asm_source == 0) { // This can move to become an AstGen error after inline assembly improvements land @@ -9107,6 +9107,7 @@ fn zirAsm( var extra_i = extra.end; var output_type_bits = extra.data.output_type_bits; + var needed_capacity: usize = @typeInfo(Air.Asm).Struct.fields.len + outputs_len + inputs_len; const Output = struct { constraint: []const u8, ty: Type }; const output: ?Output = if (outputs_len == 0) null else blk: { @@ -9121,6 +9122,8 @@ fn zirAsm( } const constraint = sema.code.nullTerminatedString(output.data.constraint); + needed_capacity += constraint.len / 4 + 1; + break :blk Output{ .constraint = constraint, .ty = try sema.resolveType(block, ret_ty_src, output.data.operand), @@ -9138,28 +9141,65 @@ fn zirAsm( _ = name; // TODO: use the name arg.* = sema.resolveInst(input.data.operand); - inputs[arg_i] = sema.code.nullTerminatedString(input.data.constraint); + const constraint = sema.code.nullTerminatedString(input.data.constraint); + needed_capacity += constraint.len / 4 + 1; + inputs[arg_i] = constraint; } const clobbers = try sema.arena.alloc([]const u8, clobbers_len); for (clobbers) |*name| { name.* = sema.code.nullTerminatedString(sema.code.extra[extra_i]); extra_i += 1; + + needed_capacity += name.*.len / 4 + 1; } - try sema.requireRuntimeBlock(block, src); + const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); + needed_capacity += (asm_source.len + 3) / 4; + const gpa = sema.gpa; - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Asm).Struct.fields.len + args.len); + try sema.requireRuntimeBlock(block, src); + try sema.air_extra.ensureUnusedCapacity(gpa, needed_capacity); const asm_air = try block.addInst(.{ .tag = .assembly, .data = .{ .ty_pl = .{ .ty = if (output) |o| try sema.addType(o.ty) else Air.Inst.Ref.void_type, .payload = sema.addExtraAssumeCapacity(Air.Asm{ - .zir_index = inst, + .source_len = @intCast(u32, asm_source.len), + .outputs_len = outputs_len, + .inputs_len = @intCast(u32, args.len), + .flags = (@as(u32, @boolToInt(is_volatile)) << 31) | @intCast(u32, clobbers.len), }), } }, }); + if (output != null) { + // Indicate the output is the asm instruction return value. + sema.air_extra.appendAssumeCapacity(@enumToInt(Air.Inst.Ref.none)); + } sema.appendRefsAssumeCapacity(args); + if (output) |o| { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, o.constraint); + buffer[o.constraint.len] = 0; + sema.air_extra.items.len += o.constraint.len / 4 + 1; + } + for (inputs) |constraint| { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, constraint); + buffer[constraint.len] = 0; + sema.air_extra.items.len += constraint.len / 4 + 1; + } + for (clobbers) |clobber| { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, clobber); + buffer[clobber.len] = 0; + sema.air_extra.items.len += clobber.len / 4 + 1; + } + { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, asm_source); + sema.air_extra.items.len += (asm_source.len + 3) / 4; + } return asm_air; } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 6e83ad72d1e3..d911c49904b4 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -4,7 +4,6 @@ const mem = std.mem; const math = std.math; const assert = std.debug.assert; const Air = @import("../../Air.zig"); -const Zir = @import("../../Zir.zig"); const Mir = @import("Mir.zig"); const Emit = @import("Emit.zig"); const Liveness = @import("../../Liveness.zig"); @@ -2007,36 +2006,6 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); } -fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue) !void { - const ty_str = self.air.instructions.items(.data)[inst].ty_str; - const zir = &self.mod_fn.owner_decl.getFileScope().zir; - const name = zir.nullTerminatedString(ty_str.str); - const name_with_null = name.ptr[0 .. name.len + 1]; - const ty = self.air.getRefType(ty_str.ty); - - switch (mcv) { - .register => |reg| { - switch (self.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_info.ensureUnusedCapacity(3); - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); - dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1, // ULEB128 dwarf expression length - reg.dwarfLocOp(), - }); - try dbg_out.dbg_info.ensureUnusedCapacity(5 + name_with_null.len); - try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 - dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string - }, - .plan9 => {}, - .none => {}, - } - }, - .stack_offset => {}, - else => {}, - } -} - fn airArg(self: *Self, inst: Air.Inst.Index) !void { const arg_index = self.arg_index; self.arg_index += 1; @@ -2852,40 +2821,39 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { - const air_datas = self.air.instructions.items(.data); - const air_extra = self.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = self.mod_fn.owner_decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - _ = clobbers_len; // TODO honor these - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end + outputs.len ..][0..args_len]); - - if (outputs_len > 1) { - return self.fail("TODO implement codegen for asm with more than 1 output", .{}); - } - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; const dead = !is_volatile and self.liveness.isUnused(inst); const result: MCValue = if (dead) .dead else result: { - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + if (outputs.len > 1) { + return self.fail("TODO implement codegen for asm with more than 1 output", .{}); + } + + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return self.fail("TODO implement codegen for non-expr asm", .{}); + } + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + break constraint; + } else null; + + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); @@ -2894,11 +2862,25 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - const arg_mcv = try self.resolveInst(arg); + const arg_mcv = try self.resolveInst(input); try self.register_manager.getReg(reg, null); - try self.genSetReg(self.air.typeOf(arg), reg, arg_mcv); + try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); + } + + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } } + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; + if (mem.eql(u8, asm_source, "svc #0")) { _ = try self.addInst(.{ .tag = .svc, @@ -2925,18 +2907,29 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { break :result MCValue{ .none = {} }; } }; - if (outputs.len + args.len <= Liveness.bpi - 1) { + + simple: { var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - std.mem.copy(Air.Inst.Ref, &buf, outputs); - std.mem.copy(Air.Inst.Ref, buf[outputs.len..], args); + var buf_index: usize = 0; + for (outputs) |output| { + if (output == .none) continue; + + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); return self.finishAir(inst, result, buf); } - var bt = try self.iterateBigTomb(inst, outputs.len + args.len); + var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); for (outputs) |output| { + if (output == .none) continue; + bt.feed(output); } - for (args) |arg| { - bt.feed(arg); + for (inputs) |input| { + bt.feed(input); } return bt.finishAir(result); } diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index e211f9d7bc84..7f0cecc14d76 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -4,7 +4,6 @@ const mem = std.mem; const math = std.math; const assert = std.debug.assert; const Air = @import("../../Air.zig"); -const Zir = @import("../../Zir.zig"); const Mir = @import("Mir.zig"); const Emit = @import("Emit.zig"); const Liveness = @import("../../Liveness.zig"); @@ -3059,40 +3058,39 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { - const air_datas = self.air.instructions.items(.data); - const air_extra = self.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = self.mod_fn.owner_decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - _ = clobbers_len; // TODO honor these - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end + outputs.len ..][0..args_len]); - - if (outputs_len > 1) { - return self.fail("TODO implement codegen for asm with more than 1 output", .{}); - } - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; const dead = !is_volatile and self.liveness.isUnused(inst); const result: MCValue = if (dead) .dead else result: { - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + if (outputs.len > 1) { + return self.fail("TODO implement codegen for asm with more than 1 output", .{}); + } + + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return self.fail("TODO implement codegen for non-expr asm", .{}); + } + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + break constraint; + } else null; + + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); @@ -3101,11 +3099,25 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - const arg_mcv = try self.resolveInst(arg); + const arg_mcv = try self.resolveInst(input); try self.register_manager.getReg(reg, null); - try self.genSetReg(self.air.typeOf(arg), reg, arg_mcv); + try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); + } + + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } } + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; + if (mem.eql(u8, asm_source, "svc #0")) { _ = try self.addInst(.{ .tag = .svc, @@ -3128,18 +3140,29 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { break :result MCValue{ .none = {} }; } }; - if (outputs.len + args.len <= Liveness.bpi - 1) { + + simple: { var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - std.mem.copy(Air.Inst.Ref, &buf, outputs); - std.mem.copy(Air.Inst.Ref, buf[outputs.len..], args); + var buf_index: usize = 0; + for (outputs) |output| { + if (output == .none) continue; + + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); return self.finishAir(inst, result, buf); } - var bt = try self.iterateBigTomb(inst, outputs.len + args.len); + var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); for (outputs) |output| { + if (output == .none) continue; + bt.feed(output); } - for (args) |arg| { - bt.feed(arg); + for (inputs) |input| { + bt.feed(input); } return bt.finishAir(result); } diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index a6e15dd76c2c..bcb96188041f 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -4,7 +4,6 @@ const mem = std.mem; const math = std.math; const assert = std.debug.assert; const Air = @import("../../Air.zig"); -const Zir = @import("../../Zir.zig"); const Mir = @import("Mir.zig"); const Emit = @import("Emit.zig"); const Liveness = @import("../../Liveness.zig"); @@ -1822,40 +1821,39 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { - const air_datas = self.air.instructions.items(.data); - const air_extra = self.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = self.mod_fn.owner_decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - _ = clobbers_len; // TODO honor these - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end + outputs.len ..][0..args_len]); - - if (outputs_len > 1) { - return self.fail("TODO implement codegen for asm with more than 1 output", .{}); - } - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; const dead = !is_volatile and self.liveness.isUnused(inst); const result: MCValue = if (dead) .dead else result: { - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + if (outputs.len > 1) { + return self.fail("TODO implement codegen for asm with more than 1 output", .{}); + } + + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return self.fail("TODO implement codegen for non-expr asm", .{}); + } + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + break constraint; + } else null; + + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); @@ -1864,11 +1862,25 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - const arg_mcv = try self.resolveInst(arg); + const arg_mcv = try self.resolveInst(input); try self.register_manager.getReg(reg, null); - try self.genSetReg(self.air.typeOf(arg), reg, arg_mcv); + try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); } + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } + } + + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; + if (mem.eql(u8, asm_source, "ecall")) { _ = try self.addInst(.{ .tag = .ecall, @@ -1890,18 +1902,28 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { break :result MCValue{ .none = {} }; } }; - if (outputs.len + args.len <= Liveness.bpi - 1) { + simple: { var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - std.mem.copy(Air.Inst.Ref, &buf, outputs); - std.mem.copy(Air.Inst.Ref, buf[outputs.len..], args); + var buf_index: usize = 0; + for (outputs) |output| { + if (output == .none) continue; + + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); return self.finishAir(inst, result, buf); } - var bt = try self.iterateBigTomb(inst, outputs.len + args.len); + var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); for (outputs) |output| { + if (output == .none) continue; + bt.feed(output); } - for (args) |arg| { - bt.feed(arg); + for (inputs) |input| { + bt.feed(input); } return bt.finishAir(result); } diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index bf49aedd2ddd..6c04023c383c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -26,7 +26,6 @@ const Target = std.Target; const Type = @import("../../type.zig").Type; const TypedValue = @import("../../TypedValue.zig"); const Value = @import("../../value.zig").Value; -const Zir = @import("../../Zir.zig"); const InnerError = error{ OutOfMemory, @@ -3415,41 +3414,39 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { - const air_datas = self.air.instructions.items(.data); - const air_extra = self.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = self.mod_fn.owner_decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - _ = clobbers_len; // TODO honor these - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_extra.end..][0..args_len]); - - if (outputs_len > 1) { - return self.fail("TODO implement codegen for asm with more than 1 output", .{}); - } - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; const dead = !is_volatile and self.liveness.isUnused(inst); - const result: MCValue = if (dead) - .dead - else result: { - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + const result: MCValue = if (dead) .dead else result: { + if (outputs.len > 1) { + return self.fail("TODO implement codegen for asm with more than 1 output", .{}); + } + + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return self.fail("TODO implement codegen for non-expr asm", .{}); + } + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + break constraint; + } else null; + + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); @@ -3458,11 +3455,25 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - const arg_mcv = try self.resolveInst(arg); + const arg_mcv = try self.resolveInst(input); try self.register_manager.getReg(reg, null); - try self.genSetReg(self.air.typeOf(arg), reg, arg_mcv); + try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); + } + + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } } + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; + { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { @@ -3529,14 +3540,29 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { break :result MCValue{ .none = {} }; } }; - if (args.len <= Liveness.bpi - 1) { + + simple: { var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - std.mem.copy(Air.Inst.Ref, &buf, args); + var buf_index: usize = 0; + for (outputs) |output| { + if (output == .none) continue; + + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); return self.finishAir(inst, result, buf); } - var bt = try self.iterateBigTomb(inst, args.len); - for (args) |arg| { - bt.feed(arg); + var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); + for (outputs) |output| { + if (output == .none) continue; + + bt.feed(output); + } + for (inputs) |input| { + bt.feed(input); } return bt.finishAir(result); } @@ -3615,7 +3641,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, - else => return self.fail("TODO implement args on stack for {} with abi size > 8", .{mcv}), + else => return self.fail("TODO implement inputs on stack for {} with abi size > 8", .{mcv}), } }, .embedded_in_code => { @@ -3623,7 +3649,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); } - return self.fail("TODO implement args on stack for {} with abi size > 8", .{mcv}); + return self.fail("TODO implement inputs on stack for {} with abi size > 8", .{mcv}); }, .memory, .direct_load, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 0391fa4c8445..460347740411 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -15,7 +15,6 @@ const Decl = Module.Decl; const trace = @import("../tracy.zig").trace; const LazySrcLoc = Module.LazySrcLoc; const Air = @import("../Air.zig"); -const Zir = @import("../Zir.zig"); const Liveness = @import("../Liveness.zig"); const Mutability = enum { Const, Mut }; @@ -2805,49 +2804,48 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue { } fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { - const air_datas = f.air.instructions.items(.data); - const air_extra = f.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = f.object.dg.decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - _ = clobbers_len; // TODO honor these - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const outputs = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end + outputs.len ..][0..args_len]); - - if (outputs_len > 1) { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + const outputs = @bitCast([]const Air.Inst.Ref, f.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, f.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; + + if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; + + if (outputs.len > 1) { return f.fail("TODO implement codegen for asm with more than 1 output", .{}); } - if (f.liveness.isUnused(inst) and !is_volatile) - return CValue.none; - - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return f.fail("TODO implement codegen for non-expr asm", .{}); } - break :out null; - }; - const args_extra_begin = extra_i; + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + break constraint; + } else null; const writer = f.object.writer(); - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + const inputs_extra_begin = extra_i; + + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; - const arg_c_value = try f.resolveInst(arg); + const arg_c_value = try f.resolveInst(input); try writer.writeAll("register "); - try f.renderType(writer, f.air.typeOf(arg)); + try f.renderType(writer, f.air.typeOf(input)); try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); try f.writeCValue(writer, arg_c_value); @@ -2856,21 +2854,38 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { return f.fail("TODO non-explicit inline asm regs", .{}); } } + + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } + } + + const asm_source = std.mem.sliceAsBytes(f.air.extra[extra_i..])[0..extra.data.source_len]; + const volatile_string: []const u8 = if (is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, asm_source }); if (output_constraint) |_| { return f.fail("TODO: CBE inline asm output", .{}); } - if (args.len > 0) { + if (inputs.len > 0) { if (output_constraint == null) { try writer.writeAll(" :"); } try writer.writeAll(": "); - extra_i = args_extra_begin; - for (args) |_, index| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + extra_i = inputs_extra_begin; + for (inputs) |_, index| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; if (index > 0) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 3d65829c1ed3..03db7306e660 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2,24 +2,21 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Compilation = @import("../Compilation.zig"); -const llvm = @import("llvm/bindings.zig"); -const link = @import("../link.zig"); const log = std.log.scoped(.codegen); const math = std.math; const native_endian = builtin.cpu.arch.endian(); +const llvm = @import("llvm/bindings.zig"); +const link = @import("../link.zig"); +const Compilation = @import("../Compilation.zig"); const build_options = @import("build_options"); const Module = @import("../Module.zig"); const TypedValue = @import("../TypedValue.zig"); -const Zir = @import("../Zir.zig"); const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); const target_util = @import("../target.zig"); - const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; - const LazySrcLoc = Module.LazySrcLoc; const Error = error{ OutOfMemory, CodegenFail }; @@ -2893,33 +2890,21 @@ pub const FuncGen = struct { // as stage1. const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const air_asm = self.air.extraData(Air.Asm, ty_pl.payload); - const zir = self.dg.decl.getFileScope().zir; - const extended = zir.instructions.items(.data)[air_asm.data.zir_index].extended; - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - if (!is_volatile and self.liveness.isUnused(inst)) { - return null; - } - const outputs_len = @truncate(u5, extended.small); - if (outputs_len > 1) { + const extra = self.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + + if (!is_volatile and self.liveness.isUnused(inst)) return null; + + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; + + if (outputs.len > 1) { return self.todo("implement llvm codegen for asm with more than 1 output", .{}); } - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_asm.end..][0..args_len]); - - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; var llvm_constraints: std.ArrayListUnmanaged(u8) = .{}; defer llvm_constraints.deinit(self.gpa); @@ -2928,14 +2913,21 @@ pub const FuncGen = struct { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const llvm_params_len = args.len; + const llvm_params_len = inputs.len; const llvm_param_types = try arena.alloc(*const llvm.Type, llvm_params_len); const llvm_param_values = try arena.alloc(*const llvm.Value, llvm_params_len); - var llvm_param_i: usize = 0; var total_i: usize = 0; - if (output_constraint) |constraint| { + for (outputs) |output| { + if (output != .none) { + return self.todo("implement inline asm with non-returned output", .{}); + } + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1); if (total_i != 0) { llvm_constraints.appendAssumeCapacity(','); @@ -2946,11 +2938,13 @@ pub const FuncGen = struct { total_i += 1; } - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); - const arg_llvm_value = try self.resolveInst(arg); + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + const arg_llvm_value = try self.resolveInst(input); llvm_param_values[llvm_param_i] = arg_llvm_value; llvm_param_types[llvm_param_i] = arg_llvm_value.typeOf(); @@ -2965,19 +2959,26 @@ pub const FuncGen = struct { total_i += 1; } - const clobbers = zir.extra[extra_i..][0..clobbers_len]; - for (clobbers) |clobber_index| { - const clobber = zir.nullTerminatedString(clobber_index); - try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4); - if (total_i != 0) { - llvm_constraints.appendAssumeCapacity(','); - } - llvm_constraints.appendSliceAssumeCapacity("~{"); - llvm_constraints.appendSliceAssumeCapacity(clobber); - llvm_constraints.appendSliceAssumeCapacity("}"); + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4); + if (total_i != 0) { + llvm_constraints.appendAssumeCapacity(','); + } + llvm_constraints.appendSliceAssumeCapacity("~{"); + llvm_constraints.appendSliceAssumeCapacity(clobber); + llvm_constraints.appendSliceAssumeCapacity("}"); - total_i += 1; + total_i += 1; + } } + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; const ret_ty = self.air.typeOfIndex(inst); const ret_llvm_ty = try self.dg.llvmType(ret_ty); diff --git a/src/print_air.zig b/src/print_air.zig index 7270f626021a..dd83d3fdb8d2 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -4,11 +4,10 @@ const fmtIntSizeBin = std.fmt.fmtIntSizeBin; const Module = @import("Module.zig"); const Value = @import("value.zig").Value; -const Zir = @import("Zir.zig"); const Air = @import("Air.zig"); const Liveness = @import("Liveness.zig"); -pub fn dump(gpa: Allocator, air: Air, zir: Zir, liveness: Liveness) void { +pub fn dump(gpa: Allocator, air: Air, liveness: Liveness) void { const instruction_bytes = air.instructions.len * // Here we don't use @sizeOf(Air.Inst.Data) because it would include // the debug safety tag but we want to measure release size. @@ -49,7 +48,6 @@ pub fn dump(gpa: Allocator, air: Air, zir: Zir, liveness: Liveness) void { .gpa = gpa, .arena = arena.allocator(), .air = air, - .zir = zir, .liveness = liveness, .indent = 2, }; @@ -63,7 +61,6 @@ const Writer = struct { gpa: Allocator, arena: Allocator, air: Air, - zir: Zir, liveness: Liveness, indent: usize, @@ -431,51 +428,67 @@ const Writer = struct { fn writeAssembly(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; - const air_asm = w.air.extraData(Air.Asm, ty_pl.payload); - const zir = w.zir; - const extended = zir.instructions.items(.data)[air_asm.data.zir_index].extended; - const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); - const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); - const outputs_len = @truncate(u5, extended.small); - const args_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - const args = @bitCast([]const Air.Inst.Ref, w.air.extra[air_asm.end..][0..args_len]); - - var extra_i: usize = zir_extra.end; - const output_constraint: ?[]const u8 = out: { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); - extra_i = output.end; - break :out zir.nullTerminatedString(output.data.constraint); - } - break :out null; - }; + const extra = w.air.extraData(Air.Asm, ty_pl.payload); + const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; + const clobbers_len = @truncate(u31, extra.data.flags); + var extra_i: usize = extra.end; + var op_index: usize = 0; + + const ret_ty = w.air.typeOfIndex(inst); + try s.print("{}", .{ret_ty}); + + if (is_volatile) { + try s.writeAll(", volatile"); + } - try s.print("\"{s}\"", .{asm_source}); + const outputs = @bitCast([]const Air.Inst.Ref, w.air.extra[extra_i..][0..extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @bitCast([]const Air.Inst.Ref, w.air.extra[extra_i..][0..extra.data.inputs_len]); + extra_i += inputs.len; - if (output_constraint) |constraint| { - const ret_ty = w.air.typeOfIndex(inst); - try s.print(", {s} -> {}", .{ constraint, ret_ty }); + for (outputs) |output| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(w.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; + + if (output == .none) { + try s.print(", -> {s}", .{constraint}); + } else { + try s.print(", out {s} = (", .{constraint}); + try w.writeOperand(s, inst, op_index, output); + op_index += 1; + try s.writeByte(')'); + } } - for (args) |arg| { - const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); - extra_i = input.end; - const constraint = zir.nullTerminatedString(input.data.constraint); + for (inputs) |input| { + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(w.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += constraint.len / 4 + 1; - try s.print(", {s} = (", .{constraint}); - try w.writeOperand(s, inst, 0, arg); + try s.print(", in {s} = (", .{constraint}); + try w.writeOperand(s, inst, op_index, input); + op_index += 1; try s.writeByte(')'); } - const clobbers = zir.extra[extra_i..][0..clobbers_len]; - for (clobbers) |clobber_index| { - const clobber = zir.nullTerminatedString(clobber_index); - try s.writeAll(", ~{"); - try s.writeAll(clobber); - try s.writeAll("}"); + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(w.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + try s.writeAll(", ~{"); + try s.writeAll(clobber); + try s.writeAll("}"); + } } + const asm_source = std.mem.sliceAsBytes(w.air.extra[extra_i..])[0..extra.data.source_len]; + try s.print(", \"{s}\"", .{asm_source}); } fn writeDbgStmt(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {