From 0fb26b03690eb327164bbc7a21ba2d69d3b1a5b8 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 5 Nov 2021 16:21:01 +0100 Subject: [PATCH] stage2 RISCV64: introduce MIR --- src/arch/riscv64/CodeGen.zig | 245 +++++++++++++++++++++-------------- src/arch/riscv64/Emit.zig | 192 +++++++++++++++++++++++++++ src/arch/riscv64/Mir.zig | 125 ++++++++++++++++++ 3 files changed, 463 insertions(+), 99 deletions(-) create mode 100644 src/arch/riscv64/Emit.zig create mode 100644 src/arch/riscv64/Mir.zig diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 6b8043df27..4dc8fedff7 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -5,6 +5,8 @@ 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"); const Type = @import("../../type.zig").Type; const Value = @import("../../value.zig").Value; @@ -47,13 +49,14 @@ arg_index: usize, src_loc: Module.SrcLoc, stack_align: u32, -prev_di_line: u32, -prev_di_column: u32, +/// MIR Instructions +mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, +/// MIR extra data +mir_extra: std.ArrayListUnmanaged(u32) = .{}, + /// Byte offset within the source file of the ending curly. end_di_line: u32, end_di_column: u32, -/// Relative to the beginning of `code`. -prev_di_pc: usize, /// The value is an offset into the `Function` `code` from the beginning. /// To perform the reloc, write 32-bit signed little-endian integer @@ -276,9 +279,6 @@ pub fn generate( .branch_stack = &branch_stack, .src_loc = src_loc, .stack_align = undefined, - .prev_di_pc = 0, - .prev_di_line = module_fn.lbrace_line, - .prev_di_column = module_fn.lbrace_column, .end_di_line = module_fn.rbrace_line, .end_di_column = module_fn.rbrace_column, }; @@ -302,6 +302,30 @@ pub fn generate( else => |e| return e, }; + var mir = Mir{ + .instructions = function.mir_instructions.toOwnedSlice(), + .extra = function.mir_extra.toOwnedSlice(bin_file.allocator), + }; + defer mir.deinit(bin_file.allocator); + + var emit = Emit{ + .mir = mir, + .bin_file = bin_file, + .debug_output = debug_output, + .target = &bin_file.options.target, + .src_loc = src_loc, + .code = code, + .prev_di_pc = 0, + .prev_di_line = module_fn.lbrace_line, + .prev_di_column = module_fn.lbrace_column, + }; + defer emit.deinit(); + + emit.emitMir() catch |err| switch (err) { + error.EmitFail => return FnResult{ .fail = emit.err_msg.? }, + else => |e| return e, + }; + if (function.err_msg) |em| { return FnResult{ .fail = em }; } else { @@ -309,13 +333,56 @@ pub fn generate( } } +fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { + const gpa = self.gpa; + + try self.mir_instructions.ensureUnusedCapacity(gpa, 1); + + const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); + self.mir_instructions.appendAssumeCapacity(inst); + return result_index; +} + +pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { + const fields = std.meta.fields(@TypeOf(extra)); + try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len); + return self.addExtraAssumeCapacity(extra); +} + +pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { + const fields = std.meta.fields(@TypeOf(extra)); + const result = @intCast(u32, self.mir_extra.items.len); + inline for (fields) |field| { + self.mir_extra.appendAssumeCapacity(switch (field.field_type) { + u32 => @field(extra, field.name), + i32 => @bitCast(u32, @field(extra, field.name)), + else => @compileError("bad field type"), + }); + } + return result; +} + fn gen(self: *Self) !void { - try self.dbgSetPrologueEnd(); + _ = try self.addInst(.{ + .tag = .dbg_prologue_end, + .data = .{ .nop = {} }, + }); + try self.genBody(self.air.getMainBody()); - try self.dbgSetEpilogueBegin(); + + _ = try self.addInst(.{ + .tag = .dbg_epilogue_begin, + .data = .{ .nop = {} }, + }); // Drop them off at the rbrace. - try self.dbgAdvancePCAndLine(self.end_di_line, self.end_di_column); + _ = try self.addInst(.{ + .tag = .dbg_line, + .data = .{ .dbg_line_column = .{ + .line = self.end_di_line, + .column = self.end_di_column, + } }, + }); } fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { @@ -456,79 +523,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { } } -fn dbgSetPrologueEnd(self: *Self) InnerError!void { - switch (self.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); - try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); - }, - .plan9 => {}, - .none => {}, - } -} - -fn dbgSetEpilogueBegin(self: *Self) InnerError!void { - switch (self.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); - try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); - }, - .plan9 => {}, - .none => {}, - } -} - -fn dbgAdvancePCAndLine(self: *Self, line: u32, column: u32) InnerError!void { - const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line); - const delta_pc: usize = self.code.items.len - self.prev_di_pc; - switch (self.debug_output) { - .dwarf => |dbg_out| { - // TODO Look into using the DWARF special opcodes to compress this data. - // It lets you emit single-byte opcodes that add different numbers to - // both the PC and the line number at the same time. - try dbg_out.dbg_line.ensureUnusedCapacity(11); - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc); - leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable; - if (delta_line != 0) { - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line); - leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable; - } - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); - self.prev_di_pc = self.code.items.len; - self.prev_di_line = line; - self.prev_di_column = column; - self.prev_di_pc = self.code.items.len; - }, - .plan9 => |dbg_out| { - if (delta_pc <= 0) return; // only do this when the pc changes - // we have already checked the target in the linker to make sure it is compatable - const quant = @import("../../link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable; - - // increasing the line number - try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line); - // increasing the pc - const d_pc_p9 = @intCast(i64, delta_pc) - quant; - if (d_pc_p9 > 0) { - // minus one because if its the last one, we want to leave space to change the line which is one quanta - try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); - if (dbg_out.pcop_change_index.*) |pci| - dbg_out.dbg_line.items[pci] += 1; - dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1); - } else if (d_pc_p9 == 0) { - // we don't need to do anything, because adding the quant does it for us - } else unreachable; - if (dbg_out.start_line.* == null) - dbg_out.start_line.* = self.prev_di_line; - dbg_out.end_line.* = line; - // only do this if the pc changed - self.prev_di_line = line; - self.prev_di_column = column; - self.prev_di_pc = self.code.items.len; - }, - .none => {}, - } -} - /// Asserts there is already capacity to insert into top branch inst_table. fn processDeath(self: *Self, inst: Air.Inst.Index) void { const air_tags = self.air.instructions.items(.tag); @@ -1291,7 +1285,10 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { } fn airBreakpoint(self: *Self) !void { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); + _ = try self.addInst(.{ + .tag = .ebreak, + .data = .{ .nop = {} }, + }); return self.finishAirBookkeeping(); } @@ -1330,7 +1327,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { unreachable; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.ra, 0, .ra).toU32()); + _ = try self.addInst(.{ + .tag = .jalr, + .data = .{ .i_type = .{ + .rd = .ra, + .rs1 = .ra, + .imm12 = 0, + } }, + }); } else if (func_value.castTag(.extern_fn)) |_| { return self.fail("TODO implement calling extern functions", .{}); } else { @@ -1375,7 +1379,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { fn ret(self: *Self, mcv: MCValue) !void { const ret_ty = self.fn_type.fnReturnType(); try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); + _ = try self.addInst(.{ + .tag = .jalr, + .data = .{ .i_type = .{ + .rd = .zero, + .rs1 = .ra, + .imm12 = 0, + } }, + }); } fn airRet(self: *Self, inst: Air.Inst.Index) !void { @@ -1414,7 +1425,15 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; - try self.dbgAdvancePCAndLine(dbg_stmt.line, dbg_stmt.column); + + _ = try self.addInst(.{ + .tag = .dbg_line, + .data = .{ .dbg_line_column = .{ + .line = dbg_stmt.line, + .column = dbg_stmt.column, + } }, + }); + return self.finishAirBookkeeping(); } @@ -1706,7 +1725,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { } if (mem.eql(u8, asm_source, "ecall")) { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ecall.toU32()); + _ = try self.addInst(.{ + .tag = .ecall, + .data = .{ .nop = {} }, + }); } else { return self.fail("TODO implement support for more riscv64 assembly instructions", .{}); } @@ -1785,29 +1807,54 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .immediate => |unsigned_x| { const x = @bitCast(i64, unsigned_x); if (math.minInt(i12) <= x and x <= math.maxInt(i12)) { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.addi(reg, .zero, @truncate(i12, x)).toU32()); - return; - } - if (math.minInt(i32) <= x and x <= math.maxInt(i32)) { + _ = try self.addInst(.{ + .tag = .addi, + .data = .{ .i_type = .{ + .rd = reg, + .rs1 = .zero, + .imm12 = @intCast(i12, x), + } }, + }); + } else if (math.minInt(i32) <= x and x <= math.maxInt(i32)) { const lo12 = @truncate(i12, x); const carry: i32 = if (lo12 < 0) 1 else 0; const hi20 = @truncate(i20, (x >> 12) +% carry); // TODO: add test case for 32-bit immediate - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.lui(reg, hi20).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.addi(reg, reg, lo12).toU32()); - return; + _ = try self.addInst(.{ + .tag = .lui, + .data = .{ .u_type = .{ + .rd = reg, + .imm20 = hi20, + } }, + }); + _ = try self.addInst(.{ + .tag = .addi, + .data = .{ .i_type = .{ + .rd = reg, + .rs1 = reg, + .imm12 = lo12, + } }, + }); + } else { + // li rd, immediate + // "Myriad sequences" + return self.fail("TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf } - // li rd, immediate - // "Myriad sequences" - return self.fail("TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf }, .memory => |addr| { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. try self.genSetReg(ty, reg, .{ .immediate = addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ld(reg, 0, reg).toU32()); + _ = try self.addInst(.{ + .tag = .ld, + .data = .{ .i_type = .{ + .rd = reg, + .rs1 = reg, + .imm12 = 0, + } }, + }); // LOAD imm=[i12 offset = 0], rs1 = // return self.fail("TODO implement genSetReg memory for riscv64"); diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig new file mode 100644 index 0000000000..3316fbbaf3 --- /dev/null +++ b/src/arch/riscv64/Emit.zig @@ -0,0 +1,192 @@ +//! This file contains the functionality for lowering AArch64 MIR into +//! machine code + +const Emit = @This(); +const std = @import("std"); +const math = std.math; +const Mir = @import("Mir.zig"); +const bits = @import("bits.zig"); +const link = @import("../../link.zig"); +const Module = @import("../../Module.zig"); +const ErrorMsg = Module.ErrorMsg; +const assert = std.debug.assert; +const DW = std.dwarf; +const leb128 = std.leb; +const Instruction = bits.Instruction; +const Register = bits.Register; +const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; + +mir: Mir, +bin_file: *link.File, +debug_output: DebugInfoOutput, +target: *const std.Target, +err_msg: ?*ErrorMsg = null, +src_loc: Module.SrcLoc, +code: *std.ArrayList(u8), + +prev_di_line: u32, +prev_di_column: u32, +/// Relative to the beginning of `code`. +prev_di_pc: usize, + +const InnerError = error{ + OutOfMemory, + EmitFail, +}; + +pub fn emitMir( + emit: *Emit, +) InnerError!void { + const mir_tags = emit.mir.instructions.items(.tag); + + // Emit machine code + for (mir_tags) |tag, index| { + const inst = @intCast(u32, index); + switch (tag) { + .addi => try emit.mirIType(inst), + .jalr => try emit.mirIType(inst), + .ld => try emit.mirIType(inst), + + .ebreak => try emit.mirSystem(inst), + .ecall => try emit.mirSystem(inst), + + .dbg_line => try emit.mirDbgLine(inst), + + .dbg_prologue_end => try emit.mirDebugPrologueEnd(), + .dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(), + + .lui => try emit.mirUType(inst), + } + } +} + +pub fn deinit(emit: *Emit) void { + emit.* = undefined; +} + +fn writeInstruction(emit: *Emit, instruction: Instruction) !void { + const endian = emit.target.cpu.arch.endian(); + std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian); +} + +fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + assert(emit.err_msg == null); + emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); + return error.EmitFail; +} + +fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void { + const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line); + const delta_pc: usize = self.code.items.len - self.prev_di_pc; + switch (self.debug_output) { + .dwarf => |dbg_out| { + // TODO Look into using the DWARF special opcodes to compress this data. + // It lets you emit single-byte opcodes that add different numbers to + // both the PC and the line number at the same time. + try dbg_out.dbg_line.ensureUnusedCapacity(11); + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc); + leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable; + if (delta_line != 0) { + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line); + leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable; + } + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); + self.prev_di_pc = self.code.items.len; + self.prev_di_line = line; + self.prev_di_column = column; + self.prev_di_pc = self.code.items.len; + }, + .plan9 => |dbg_out| { + if (delta_pc <= 0) return; // only do this when the pc changes + // we have already checked the target in the linker to make sure it is compatable + const quant = @import("../../link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable; + + // increasing the line number + try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line); + // increasing the pc + const d_pc_p9 = @intCast(i64, delta_pc) - quant; + if (d_pc_p9 > 0) { + // minus one because if its the last one, we want to leave space to change the line which is one quanta + try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); + if (dbg_out.pcop_change_index.*) |pci| + dbg_out.dbg_line.items[pci] += 1; + dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1); + } else if (d_pc_p9 == 0) { + // we don't need to do anything, because adding the quant does it for us + } else unreachable; + if (dbg_out.start_line.* == null) + dbg_out.start_line.* = self.prev_di_line; + dbg_out.end_line.* = line; + // only do this if the pc changed + self.prev_di_line = line; + self.prev_di_column = column; + self.prev_di_pc = self.code.items.len; + }, + .none => {}, + } +} + +fn mirIType(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const i_type = emit.mir.instructions.items(.data)[inst].i_type; + + switch (tag) { + .addi => try emit.writeInstruction(Instruction.addi(i_type.rd, i_type.rs1, i_type.imm12)), + .jalr => try emit.writeInstruction(Instruction.jalr(i_type.rd, i_type.imm12, i_type.rs1)), + .ld => try emit.writeInstruction(Instruction.ld(i_type.rd, i_type.imm12, i_type.rs1)), + else => unreachable, + } +} + +fn mirSystem(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + + switch (tag) { + .ebreak => try emit.writeInstruction(Instruction.ebreak), + .ecall => try emit.writeInstruction(Instruction.ecall), + else => unreachable, + } +} + +fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const dbg_line_column = emit.mir.instructions.items(.data)[inst].dbg_line_column; + + switch (tag) { + .dbg_line => try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column), + else => unreachable, + } +} + +fn mirDebugPrologueEnd(self: *Emit) !void { + switch (self.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); + try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); + }, + .plan9 => {}, + .none => {}, + } +} + +fn mirDebugEpilogueBegin(self: *Emit) !void { + switch (self.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); + try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); + }, + .plan9 => {}, + .none => {}, + } +} + +fn mirUType(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const u_type = emit.mir.instructions.items(.data)[inst].u_type; + + switch (tag) { + .lui => try emit.writeInstruction(Instruction.lui(u_type.rd, u_type.imm20)), + else => unreachable, + } +} diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig new file mode 100644 index 0000000000..3530b3db24 --- /dev/null +++ b/src/arch/riscv64/Mir.zig @@ -0,0 +1,125 @@ +//! Machine Intermediate Representation. +//! This data is produced by RISCV64 Codegen or RISCV64 assembly parsing +//! These instructions have a 1:1 correspondence with machine code instructions +//! for the target. MIR can be lowered to source-annotated textual assembly code +//! instructions, or it can be lowered to machine code. +//! The main purpose of MIR is to postpone the assignment of offsets until Isel, +//! so that, for example, the smaller encodings of jump instructions can be used. + +const Mir = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const bits = @import("bits.zig"); +const Register = bits.Register; + +instructions: std.MultiArrayList(Inst).Slice, +/// The meaning of this data is determined by `Inst.Tag` value. +extra: []const u32, + +pub const Inst = struct { + tag: Tag, + /// The meaning of this depends on `tag`. + data: Data, + + pub const Tag = enum(u16) { + addi, + /// Pseudo-instruction: End of prologue + dbg_prologue_end, + /// Pseudo-instruction: Beginning of epilogue + dbg_epilogue_begin, + /// Pseudo-instruction: Update debug line + dbg_line, + ebreak, + ecall, + jalr, + ld, + lui, + }; + + /// The position of an MIR instruction within the `Mir` instructions array. + pub const Index = u32; + + /// All instructions have a 4-byte payload, which is contained within + /// this union. `Tag` determines which union field is active, as well as + /// how to interpret the data within. + pub const Data = union { + /// No additional data + /// + /// Used by e.g. ebreak + nop: void, + /// Another instruction. + /// + /// Used by e.g. b + inst: Index, + /// A 16-bit immediate value. + /// + /// Used by e.g. svc + imm16: u16, + /// Index into `extra`. Meaning of what can be found there is context-dependent. + /// + /// Used by e.g. load_memory + payload: u32, + /// A register + /// + /// Used by e.g. blr + reg: Register, + /// I-Type + /// + /// Used by e.g. jalr + i_type: struct { + rd: Register, + rs1: Register, + imm12: i12, + }, + /// U-Type + /// + /// Used by e.g. lui + u_type: struct { + rd: Register, + imm20: i20, + }, + /// Debug info: line and column + /// + /// Used by e.g. dbg_line + dbg_line_column: struct { + line: u32, + column: u32, + }, + }; + + // Make sure we don't accidentally make instructions bigger than expected. + // Note that in Debug builds, Zig is allowed to insert a secret field for safety checks. + // comptime { + // if (builtin.mode != .Debug) { + // assert(@sizeOf(Inst) == 8); + // } + // } +}; + +pub fn deinit(mir: *Mir, gpa: *std.mem.Allocator) void { + mir.instructions.deinit(gpa); + gpa.free(mir.extra); + mir.* = undefined; +} + +/// Returns the requested data, as well as the new index which is at the start of the +/// trailers for the object. +pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { + const fields = std.meta.fields(T); + var i: usize = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.field_type) { + u32 => mir.extra[i], + i32 => @bitCast(i32, mir.extra[i]), + else => @compileError("bad field type"), + }; + i += 1; + } + return .{ + .data = result, + .end = i, + }; +}