mirror of
https://github.com/ziglang/zig.git
synced 2025-12-21 05:33:15 +00:00
222 lines
8.7 KiB
Zig
Vendored
222 lines
8.7 KiB
Zig
Vendored
const std = @import("std");
|
|
const Codegen = @import("../Codegen_legacy.zig");
|
|
const Tree = @import("../Tree.zig");
|
|
const NodeIndex = Tree.NodeIndex;
|
|
const x86_64 = @import("zig").codegen.x86_64;
|
|
const Register = x86_64.Register;
|
|
const RegisterManager = @import("zig").RegisterManager;
|
|
|
|
const Fn = @This();
|
|
|
|
const Value = union(enum) {
|
|
symbol: []const u8,
|
|
immediate: i64,
|
|
register: Register,
|
|
none,
|
|
};
|
|
|
|
register_manager: RegisterManager(Fn, Register, &x86_64.callee_preserved_regs) = .{},
|
|
data: *std.ArrayList(u8),
|
|
c: *Codegen,
|
|
|
|
pub fn deinit(func: *Fn) void {
|
|
func.* = undefined;
|
|
}
|
|
|
|
pub fn genFn(c: *Codegen, decl: NodeIndex, data: *std.ArrayList(u8)) Codegen.Error!void {
|
|
var func = Fn{ .data = data, .c = c };
|
|
defer func.deinit();
|
|
|
|
// function prologue
|
|
try func.data.appendSlice(&.{
|
|
0x55, // push rbp
|
|
0x48, 0x89, 0xe5, // mov rbp,rsp
|
|
});
|
|
_ = try func.genNode(c.node_data[@intFromEnum(decl)].decl.node);
|
|
// all functions are guaranteed to end in a return statement so no extra work required here
|
|
}
|
|
|
|
pub fn spillInst(f: *Fn, reg: Register, inst: u32) !void {
|
|
_ = inst;
|
|
_ = reg;
|
|
_ = f;
|
|
}
|
|
|
|
fn setReg(func: *Fn, val: Value, reg: Register) !void {
|
|
switch (val) {
|
|
.none => unreachable,
|
|
.symbol => |sym| {
|
|
// lea address with 0 and add relocation
|
|
const encoder = try x86_64.Encoder.init(func.data, 8);
|
|
encoder.rex(.{ .w = true });
|
|
encoder.opcode_1byte(0x8D);
|
|
encoder.modRm_RIPDisp32(reg.low_id());
|
|
|
|
const offset = func.data.items.len;
|
|
encoder.imm32(0);
|
|
|
|
try func.c.obj.addRelocation(sym, .func, offset, -4);
|
|
},
|
|
.immediate => |x| if (x == 0) {
|
|
// 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
|
|
// register is the fastest way to zero a register.
|
|
// The encoding for `xor r32, r32` is `0x31 /r`.
|
|
const encoder = try x86_64.Encoder.init(func.data, 3);
|
|
|
|
// If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since
|
|
// this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB.
|
|
// Both R and B are set, as we're extending, in effect, the register bits *and* the operand.
|
|
encoder.rex(.{ .r = reg.isExtended(), .b = reg.isExtended() });
|
|
encoder.opcode_1byte(0x31);
|
|
// Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the
|
|
// ModR/M byte of the instruction contains a register operand and an r/m operand."
|
|
encoder.modRm_direct(reg.low_id(), reg.low_id());
|
|
} else if (x <= std.math.maxInt(i32)) {
|
|
// Next best case: if we set the lower four bytes, the upper four will be zeroed.
|
|
//
|
|
// The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM.
|
|
|
|
const encoder = try x86_64.Encoder.init(func.data, 6);
|
|
// Just as with XORing, we need a REX prefix. This time though, we only
|
|
// need the B bit set, as we're extending the opcode's register field,
|
|
// and there is no Mod R/M byte.
|
|
encoder.rex(.{ .b = reg.isExtended() });
|
|
encoder.opcode_withReg(0xB8, reg.low_id());
|
|
|
|
// no ModR/M byte
|
|
|
|
// IMM
|
|
encoder.imm32(@intCast(x));
|
|
} else {
|
|
// Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls
|
|
// this `movabs`, though this is officially just a different variant of the plain `mov`
|
|
// instruction.
|
|
//
|
|
// This encoding is, in fact, the *same* as the one used for 32-bit loads. The only
|
|
// difference is that we set REX.W before the instruction, which extends the load to
|
|
// 64-bit and uses the full bit-width of the register.
|
|
{
|
|
const encoder = try x86_64.Encoder.init(func.data, 10);
|
|
encoder.rex(.{ .w = true, .b = reg.isExtended() });
|
|
encoder.opcode_withReg(0xB8, reg.low_id());
|
|
encoder.imm64(@bitCast(x));
|
|
}
|
|
},
|
|
.register => |src_reg| {
|
|
// If the registers are the same, nothing to do.
|
|
if (src_reg.id() == reg.id())
|
|
return;
|
|
|
|
// This is a variant of 8B /r.
|
|
const encoder = try x86_64.Encoder.init(func.data, 3);
|
|
encoder.rex(.{
|
|
.w = true,
|
|
.r = reg.isExtended(),
|
|
.b = src_reg.isExtended(),
|
|
});
|
|
encoder.opcode_1byte(0x8B);
|
|
encoder.modRm_direct(reg.low_id(), src_reg.low_id());
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genNode(func: *Fn, node: NodeIndex) Codegen.Error!Value {
|
|
if (func.c.tree.value_map.get(node)) |some| {
|
|
if (some.tag == .int)
|
|
return Value{ .immediate = @bitCast(some.data.int) };
|
|
}
|
|
|
|
const data = func.c.node_data[@intFromEnum(node)];
|
|
switch (func.c.node_tag[@intFromEnum(node)]) {
|
|
.static_assert => return Value{ .none = {} },
|
|
.compound_stmt_two => {
|
|
if (data.bin.lhs != .none) _ = try func.genNode(data.bin.lhs);
|
|
if (data.bin.rhs != .none) _ = try func.genNode(data.bin.rhs);
|
|
return Value{ .none = {} };
|
|
},
|
|
.compound_stmt => {
|
|
for (func.c.tree.data[data.range.start..data.range.end]) |stmt| {
|
|
_ = try func.genNode(stmt);
|
|
}
|
|
return Value{ .none = {} };
|
|
},
|
|
.call_expr_one => if (data.bin.rhs != .none)
|
|
return func.genCall(data.bin.lhs, &.{data.bin.rhs})
|
|
else
|
|
return func.genCall(data.bin.lhs, &.{}),
|
|
.call_expr => return func.genCall(func.c.tree.data[data.range.start], func.c.tree.data[data.range.start + 1 .. data.range.end]),
|
|
.explicit_cast, .implicit_cast => {
|
|
switch (data.cast.kind) {
|
|
.function_to_pointer,
|
|
.array_to_pointer,
|
|
=> return func.genNode(data.cast.operand), // no-op
|
|
else => return func.c.comp.diag.fatalNoSrc("TODO x86_64 genNode for cast {s}\n", .{@tagName(data.cast.kind)}),
|
|
}
|
|
},
|
|
.decl_ref_expr => {
|
|
// TODO locals and arguments
|
|
return Value{ .symbol = func.c.tree.tokSlice(data.decl_ref) };
|
|
},
|
|
.return_stmt => {
|
|
const value = try func.genNode(data.un);
|
|
try func.setReg(value, x86_64.c_abi_int_return_regs[0]);
|
|
try func.data.appendSlice(&.{
|
|
0x5d, // pop rbp
|
|
0xc3, // ret
|
|
});
|
|
return Value{ .none = {} };
|
|
},
|
|
.implicit_return => {
|
|
try func.setReg(.{ .immediate = 0 }, x86_64.c_abi_int_return_regs[0]);
|
|
try func.data.appendSlice(&.{
|
|
0x5d, // pop rbp
|
|
0xc3, // ret
|
|
});
|
|
return Value{ .none = {} };
|
|
},
|
|
.int_literal => return Value{ .immediate = @bitCast(data.int) },
|
|
.string_literal_expr => {
|
|
const range = func.c.tree.value_map.get(node).?.data.bytes;
|
|
const str_bytes = range.slice(func.c.tree.strings);
|
|
const section = try func.c.obj.getSection(.strings);
|
|
const start = section.items.len;
|
|
try section.appendSlice(str_bytes);
|
|
const symbol_name = try func.c.obj.declareSymbol(.strings, null, .Internal, .variable, start, str_bytes.len);
|
|
return Value{ .symbol = symbol_name };
|
|
},
|
|
else => return func.c.comp.diag.fatalNoSrc("TODO x86_64 genNode {}\n", .{func.c.node_tag[@intFromEnum(node)]}),
|
|
}
|
|
}
|
|
|
|
fn genCall(func: *Fn, lhs: NodeIndex, args: []const NodeIndex) Codegen.Error!Value {
|
|
if (args.len > x86_64.c_abi_int_param_regs.len)
|
|
return func.c.comp.diag.fatalNoSrc("TODO more than args {d}\n", .{x86_64.c_abi_int_param_regs.len});
|
|
|
|
const func_value = try func.genNode(lhs);
|
|
for (args, 0..) |arg, i| {
|
|
const value = try func.genNode(arg);
|
|
try func.setReg(value, x86_64.c_abi_int_param_regs[i]);
|
|
}
|
|
|
|
switch (func_value) {
|
|
.none => unreachable,
|
|
.symbol => |sym| {
|
|
const encoder = try x86_64.Encoder.init(func.data, 5);
|
|
encoder.opcode_1byte(0xe8);
|
|
|
|
const offset = func.data.items.len;
|
|
encoder.imm32(0);
|
|
|
|
try func.c.obj.addRelocation(sym, .func, offset, -4);
|
|
},
|
|
.immediate => return func.c.comp.diag.fatalNoSrc("TODO call immediate\n", .{}),
|
|
.register => return func.c.comp.diag.fatalNoSrc("TODO call reg\n", .{}),
|
|
}
|
|
return Value{ .register = x86_64.c_abi_int_return_regs[0] };
|
|
}
|
|
|
|
pub fn genVar(c: *Codegen, decl: NodeIndex) Codegen.Error!void {
|
|
_ = c;
|
|
_ = decl;
|
|
}
|