aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJacob Young <jacobly0@users.noreply.github.com>2023-04-02 20:36:41 -0400
committerJakub Konka <kubkon@jakubkonka.com>2023-04-03 17:45:16 +0200
commitf5f0d95f0e882ce43146b269f994c6c242f9ed3b (patch)
tree05a6a5a6e7c75d6651765bba56c5b1f2843a1a67 /src
parenta329450aa4107d376ebda753be8a5579dd600c69 (diff)
downloadzig-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.zig199
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,
+ };
}
}