diff options
| author | Jacob Young <jacobly0@users.noreply.github.com> | 2023-04-02 20:36:41 -0400 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2023-04-03 17:45:16 +0200 |
| commit | f5f0d95f0e882ce43146b269f994c6c242f9ed3b (patch) | |
| tree | 05a6a5a6e7c75d6651765bba56c5b1f2843a1a67 /src | |
| parent | a329450aa4107d376ebda753be8a5579dd600c69 (diff) | |
| download | zig-f5f0d95f0e882ce43146b269f994c6c242f9ed3b.tar.gz zig-f5f0d95f0e882ce43146b269f994c6c242f9ed3b.zip | |
x86_64: implement a more generic assembler for inline assembly
Diffstat (limited to 'src')
| -rw-r--r-- | src/arch/x86_64/CodeGen.zig | 199 |
1 files changed, 135 insertions, 64 deletions
diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c7dfa2c6a9..8309fe7335 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -28,10 +28,11 @@ const Type = @import("../../type.zig").Type; const TypedValue = @import("../../TypedValue.zig"); const Value = @import("../../value.zig").Value; -const bits = @import("bits.zig"); const abi = @import("abi.zig"); -const errUnionPayloadOffset = codegen.errUnionPayloadOffset; +const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); const errUnionErrorOffset = codegen.errUnionErrorOffset; +const errUnionPayloadOffset = codegen.errUnionPayloadOffset; const Condition = bits.Condition; const Immediate = bits.Immediate; @@ -6570,7 +6571,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { return self.fail("unrecognized constraint: '{s}'", .{constraint}); args.putAssumeCapacity(name, mcv); switch (mcv) { - .register => |reg| _ = self.register_manager.lockRegAssumeUnused(reg), + .register => |reg| _ = if (RegisterManager.indexOfRegIntoTracked(reg)) |_| + self.register_manager.lockRegAssumeUnused(reg), else => {}, } if (output == .none) result = mcv; @@ -6609,70 +6611,139 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { } const asm_source = mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; - var line_it = mem.tokenize(u8, asm_source, "\n\r"); + var line_it = mem.tokenize(u8, asm_source, "\n\r;"); while (line_it.next()) |line| { var mnem_it = mem.tokenize(u8, line, " \t"); - const mnem = mnem_it.next() orelse continue; - if (mem.startsWith(u8, mnem, "#")) continue; - var arg_it = mem.tokenize(u8, mnem_it.rest(), ", "); - if (std.ascii.eqlIgnoreCase(mnem, "syscall")) { - if (arg_it.next()) |trailing| if (!mem.startsWith(u8, trailing, "#")) - return self.fail("Too many operands: '{s}'", .{line}); - try self.asmOpOnly(.syscall); - } else if (std.ascii.eqlIgnoreCase(mnem, "push")) { - const src = arg_it.next() orelse - return self.fail("Not enough operands: '{s}'", .{line}); - if (arg_it.next()) |trailing| if (!mem.startsWith(u8, trailing, "#")) - return self.fail("Too many operands: '{s}'", .{line}); - if (mem.startsWith(u8, src, "$")) { - const imm = std.fmt.parseInt(u32, src["$".len..], 0) catch - return self.fail("Invalid immediate: '{s}'", .{src}); - try self.asmImmediate(.push, Immediate.u(imm)); - } else if (mem.startsWith(u8, src, "%%")) { - const reg = parseRegName(src["%%".len..]) orelse - return self.fail("Invalid register: '{s}'", .{src}); - try self.asmRegister(.push, reg); - } else return self.fail("Unsupported operand: '{s}'", .{src}); - } else if (std.ascii.eqlIgnoreCase(mnem, "pop")) { - const dst = arg_it.next() orelse - return self.fail("Not enough operands: '{s}'", .{line}); - if (arg_it.next()) |trailing| if (!mem.startsWith(u8, trailing, "#")) - return self.fail("Too many operands: '{s}'", .{line}); - if (mem.startsWith(u8, dst, "%%")) { - const reg = parseRegName(dst["%%".len..]) orelse - return self.fail("Invalid register: '{s}'", .{dst}); - try self.asmRegister(.pop, reg); - } else return self.fail("Unsupported operand: '{s}'", .{dst}); - } else if (std.ascii.eqlIgnoreCase(mnem, "movq")) { - const src = arg_it.next() orelse - return self.fail("Not enough operands: '{s}'", .{line}); - const dst = arg_it.next() orelse - return self.fail("Not enough operands: '{s}'", .{line}); - if (arg_it.next()) |trailing| if (!mem.startsWith(u8, trailing, "#")) - return self.fail("Too many operands: '{s}'", .{line}); - if (mem.startsWith(u8, src, "%%")) { - const colon = mem.indexOfScalarPos(u8, src, "%%".len + 2, ':'); - const src_reg = parseRegName(src["%%".len .. colon orelse src.len]) orelse - return self.fail("Invalid register: '{s}'", .{src}); + const mnem_str = mnem_it.next() orelse continue; + if (mem.startsWith(u8, mnem_str, "#")) continue; + + const mnem_size: ?Memory.PtrSize = if (mem.endsWith(u8, mnem_str, "b")) + .byte + else if (mem.endsWith(u8, mnem_str, "w")) + .word + else if (mem.endsWith(u8, mnem_str, "l")) + .dword + else if (mem.endsWith(u8, mnem_str, "q")) + .qword + else + null; + const mnem = std.meta.stringToEnum(Mir.Inst.Tag, mnem_str) orelse + (if (mnem_size) |_| + std.meta.stringToEnum(Mir.Inst.Tag, mnem_str[0 .. mnem_str.len - 1]) + else + null) orelse return self.fail("Invalid mnemonic: '{s}'", .{mnem_str}); + + var op_it = mem.tokenize(u8, mnem_it.rest(), ","); + var ops = [1]encoder.Instruction.Operand{.none} ** 4; + for (&ops) |*op| { + const op_str = mem.trim(u8, op_it.next() orelse break, " \t"); + if (mem.startsWith(u8, op_str, "#")) break; + if (mem.startsWith(u8, op_str, "%%")) { + const colon = mem.indexOfScalarPos(u8, op_str, "%%".len + 2, ':'); + const reg = parseRegName(op_str["%%".len .. colon orelse op_str.len]) orelse + return self.fail("Invalid register: '{s}'", .{op_str}); if (colon) |colon_pos| { - const src_disp = std.fmt.parseInt(i32, src[colon_pos + 1 ..], 0) catch - return self.fail("Invalid immediate: '{s}'", .{src}); - if (mem.startsWith(u8, dst, "%[") and mem.endsWith(u8, dst, "]")) { - switch (args.get(dst["%[".len .. dst.len - "]".len]) orelse - return self.fail("no matching constraint for: '{s}'", .{dst})) { - .register => |dst_reg| try self.asmRegisterMemory( - .mov, - dst_reg, - Memory.sib(.qword, .{ .base = src_reg, .disp = src_disp }), - ), - else => return self.fail("Invalid constraint: '{s}'", .{dst}), - } - } else return self.fail("Unsupported operand: '{s}'", .{dst}); - } else return self.fail("Unsupported operand: '{s}'", .{src}); - } - } else { - return self.fail("Unsupported instruction: '{s}'", .{mnem}); - } + const disp = std.fmt.parseInt(i32, op_str[colon_pos + 1 ..], 0) catch + return self.fail("Invalid displacement: '{s}'", .{op_str}); + op.* = .{ .mem = Memory.sib( + mnem_size orelse return self.fail("Unknown size: '{s}'", .{op_str}), + .{ .base = reg, .disp = disp }, + ) }; + } else { + if (mnem_size) |size| if (reg.bitSize() != size.bitSize()) + return self.fail("Invalid register size: '{s}'", .{op_str}); + op.* = .{ .reg = reg }; + } + } else if (mem.startsWith(u8, op_str, "%[") and mem.endsWith(u8, op_str, "]")) { + switch (args.get(op_str["%[".len .. op_str.len - "]".len]) orelse + return self.fail("No matching constraint: '{s}'", .{op_str})) { + .register => |reg| op.* = .{ .reg = reg }, + else => return self.fail("Invalid constraint: '{s}'", .{op_str}), + } + } else if (mem.startsWith(u8, op_str, "$")) { + if (std.fmt.parseInt(i32, op_str["$".len..], 0)) |s| { + if (mnem_size) |size| { + const max = @as(u64, std.math.maxInt(u64)) >> + @intCast(u6, 64 - (size.bitSize() - 1)); + if ((if (s < 0) ~s else s) > max) + return self.fail("Invalid immediate size: '{s}'", .{op_str}); + } + op.* = .{ .imm = Immediate.s(s) }; + } else |_| if (std.fmt.parseInt(u64, op_str["$".len..], 0)) |u| { + if (mnem_size) |size| { + const max = @as(u64, std.math.maxInt(u64)) >> + @intCast(u6, 64 - size.bitSize()); + if (u > max) + return self.fail("Invalid immediate size: '{s}'", .{op_str}); + } + op.* = .{ .imm = Immediate.u(u) }; + } else |_| return self.fail("Invalid immediate: '{s}'", .{op_str}); + } else return self.fail("Invalid operand: '{s}'", .{op_str}); + } else if (op_it.next()) |op_str| return self.fail("Extra operand: '{s}'", .{op_str}); + + (switch (ops[0]) { + .none => self.asmOpOnly(mnem), + .reg => |reg0| switch (ops[1]) { + .none => self.asmRegister(mnem, reg0), + .reg => |reg1| switch (ops[2]) { + .none => self.asmRegisterRegister(mnem, reg1, reg0), + .reg => |reg2| switch (ops[3]) { + .none => self.asmRegisterRegisterRegister(mnem, reg2, reg1, reg0), + else => error.InvalidInstruction, + }, + .mem => |mem2| switch (ops[3]) { + .none => self.asmMemoryRegisterRegister(mnem, mem2, reg1, reg0), + else => error.InvalidInstruction, + }, + else => error.InvalidInstruction, + }, + .mem => |mem1| switch (ops[2]) { + .none => self.asmMemoryRegister(mnem, mem1, reg0), + else => error.InvalidInstruction, + }, + else => error.InvalidInstruction, + }, + .mem => |mem0| switch (ops[1]) { + .none => self.asmMemory(mnem, mem0), + .reg => |reg1| switch (ops[2]) { + .none => self.asmRegisterMemory(mnem, reg1, mem0), + else => error.InvalidInstruction, + }, + else => error.InvalidInstruction, + }, + .imm => |imm0| switch (ops[1]) { + .none => self.asmImmediate(mnem, imm0), + .reg => |reg1| switch (ops[2]) { + .none => self.asmRegisterImmediate(mnem, reg1, imm0), + .reg => |reg2| switch (ops[3]) { + .none => self.asmRegisterRegisterImmediate(mnem, reg2, reg1, imm0), + else => error.InvalidInstruction, + }, + .mem => |mem2| switch (ops[3]) { + .none => self.asmMemoryRegisterImmediate(mnem, mem2, reg1, imm0), + else => error.InvalidInstruction, + }, + else => error.InvalidInstruction, + }, + .mem => |mem1| switch (ops[2]) { + .none => self.asmMemoryImmediate(mnem, mem1, imm0), + else => error.InvalidInstruction, + }, + else => error.InvalidInstruction, + }, + }) catch |err| switch (err) { + error.InvalidInstruction => return self.fail( + "Invalid instruction: '{s} {s} {s} {s} {s}'", + .{ + @tagName(mnem), + @tagName(ops[0]), + @tagName(ops[1]), + @tagName(ops[2]), + @tagName(ops[3]), + }, + ), + else => |e| return e, + }; } } |
