rename src-self-hosted/ to src/
This commit is contained in:
607
src/codegen/arm.zig
Normal file
607
src/codegen/arm.zig
Normal file
@@ -0,0 +1,607 @@
|
||||
const std = @import("std");
|
||||
const DW = std.dwarf;
|
||||
const testing = std.testing;
|
||||
|
||||
/// The condition field specifies the flags neccessary for an
|
||||
/// Instruction to be executed
|
||||
pub const Condition = enum(u4) {
|
||||
/// equal
|
||||
eq,
|
||||
/// not equal
|
||||
ne,
|
||||
/// unsigned higher or same
|
||||
cs,
|
||||
/// unsigned lower
|
||||
cc,
|
||||
/// negative
|
||||
mi,
|
||||
/// positive or zero
|
||||
pl,
|
||||
/// overflow
|
||||
vs,
|
||||
/// no overflow
|
||||
vc,
|
||||
/// unsigned higer
|
||||
hi,
|
||||
/// unsigned lower or same
|
||||
ls,
|
||||
/// greater or equal
|
||||
ge,
|
||||
/// less than
|
||||
lt,
|
||||
/// greater than
|
||||
gt,
|
||||
/// less than or equal
|
||||
le,
|
||||
/// always
|
||||
al,
|
||||
};
|
||||
|
||||
/// Represents a register in the ARM instruction set architecture
|
||||
pub const Register = enum(u5) {
|
||||
r0,
|
||||
r1,
|
||||
r2,
|
||||
r3,
|
||||
r4,
|
||||
r5,
|
||||
r6,
|
||||
r7,
|
||||
r8,
|
||||
r9,
|
||||
r10,
|
||||
r11,
|
||||
r12,
|
||||
r13,
|
||||
r14,
|
||||
r15,
|
||||
|
||||
/// Argument / result / scratch register 1
|
||||
a1,
|
||||
/// Argument / result / scratch register 2
|
||||
a2,
|
||||
/// Argument / scratch register 3
|
||||
a3,
|
||||
/// Argument / scratch register 4
|
||||
a4,
|
||||
/// Variable-register 1
|
||||
v1,
|
||||
/// Variable-register 2
|
||||
v2,
|
||||
/// Variable-register 3
|
||||
v3,
|
||||
/// Variable-register 4
|
||||
v4,
|
||||
/// Variable-register 5
|
||||
v5,
|
||||
/// Platform register
|
||||
v6,
|
||||
/// Variable-register 7
|
||||
v7,
|
||||
/// Frame pointer or Variable-register 8
|
||||
fp,
|
||||
/// Intra-Procedure-call scratch register
|
||||
ip,
|
||||
/// Stack pointer
|
||||
sp,
|
||||
/// Link register
|
||||
lr,
|
||||
/// Program counter
|
||||
pc,
|
||||
|
||||
/// Returns the unique 4-bit ID of this register which is used in
|
||||
/// the machine code
|
||||
pub fn id(self: Register) u4 {
|
||||
return @truncate(u4, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Returns the index into `callee_preserved_regs`.
|
||||
pub fn allocIndex(self: Register) ?u4 {
|
||||
inline for (callee_preserved_regs) |cpreg, i| {
|
||||
if (self.id() == cpreg.id()) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn dwarfLocOp(self: Register) u8 {
|
||||
return @as(u8, self.id()) + DW.OP_reg0;
|
||||
}
|
||||
};
|
||||
|
||||
test "Register.id" {
|
||||
testing.expectEqual(@as(u4, 15), Register.r15.id());
|
||||
testing.expectEqual(@as(u4, 15), Register.pc.id());
|
||||
}
|
||||
|
||||
pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 };
|
||||
pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 };
|
||||
pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 };
|
||||
|
||||
/// Represents an instruction in the ARM instruction set architecture
|
||||
pub const Instruction = union(enum) {
|
||||
DataProcessing: packed struct {
|
||||
// Note to self: The order of the fields top-to-bottom is
|
||||
// right-to-left in the actual 32-bit int representation
|
||||
op2: u12,
|
||||
rd: u4,
|
||||
rn: u4,
|
||||
s: u1,
|
||||
opcode: u4,
|
||||
i: u1,
|
||||
fixed: u2 = 0b00,
|
||||
cond: u4,
|
||||
},
|
||||
SingleDataTransfer: packed struct {
|
||||
offset: u12,
|
||||
rd: u4,
|
||||
rn: u4,
|
||||
l: u1,
|
||||
w: u1,
|
||||
b: u1,
|
||||
u: u1,
|
||||
p: u1,
|
||||
i: u1,
|
||||
fixed: u2 = 0b01,
|
||||
cond: u4,
|
||||
},
|
||||
Branch: packed struct {
|
||||
offset: u24,
|
||||
link: u1,
|
||||
fixed: u3 = 0b101,
|
||||
cond: u4,
|
||||
},
|
||||
BranchExchange: packed struct {
|
||||
rn: u4,
|
||||
fixed_1: u1 = 0b1,
|
||||
link: u1,
|
||||
fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
|
||||
cond: u4,
|
||||
},
|
||||
SupervisorCall: packed struct {
|
||||
comment: u24,
|
||||
fixed: u4 = 0b1111,
|
||||
cond: u4,
|
||||
},
|
||||
Breakpoint: packed struct {
|
||||
imm4: u4,
|
||||
fixed_1: u4 = 0b0111,
|
||||
imm12: u12,
|
||||
fixed_2_and_cond: u12 = 0b1110_0001_0010,
|
||||
},
|
||||
|
||||
/// Represents the possible operations which can be performed by a
|
||||
/// DataProcessing instruction
|
||||
const Opcode = enum(u4) {
|
||||
// Rd := Op1 AND Op2
|
||||
@"and",
|
||||
// Rd := Op1 EOR Op2
|
||||
eor,
|
||||
// Rd := Op1 - Op2
|
||||
sub,
|
||||
// Rd := Op2 - Op1
|
||||
rsb,
|
||||
// Rd := Op1 + Op2
|
||||
add,
|
||||
// Rd := Op1 + Op2 + C
|
||||
adc,
|
||||
// Rd := Op1 - Op2 + C - 1
|
||||
sbc,
|
||||
// Rd := Op2 - Op1 + C - 1
|
||||
rsc,
|
||||
// set condition codes on Op1 AND Op2
|
||||
tst,
|
||||
// set condition codes on Op1 EOR Op2
|
||||
teq,
|
||||
// set condition codes on Op1 - Op2
|
||||
cmp,
|
||||
// set condition codes on Op1 + Op2
|
||||
cmn,
|
||||
// Rd := Op1 OR Op2
|
||||
orr,
|
||||
// Rd := Op2
|
||||
mov,
|
||||
// Rd := Op1 AND NOT Op2
|
||||
bic,
|
||||
// Rd := NOT Op2
|
||||
mvn,
|
||||
};
|
||||
|
||||
/// Represents the second operand to a data processing instruction
|
||||
/// which can either be content from a register or an immediate
|
||||
/// value
|
||||
pub const Operand = union(enum) {
|
||||
Register: packed struct {
|
||||
rm: u4,
|
||||
shift: u8,
|
||||
},
|
||||
Immediate: packed struct {
|
||||
imm: u8,
|
||||
rotate: u4,
|
||||
},
|
||||
|
||||
/// Represents multiple ways a register can be shifted. A
|
||||
/// register can be shifted by a specific immediate value or
|
||||
/// by the contents of another register
|
||||
pub const Shift = union(enum) {
|
||||
Immediate: packed struct {
|
||||
fixed: u1 = 0b0,
|
||||
typ: u2,
|
||||
amount: u5,
|
||||
},
|
||||
Register: packed struct {
|
||||
fixed_1: u1 = 0b1,
|
||||
typ: u2,
|
||||
fixed_2: u1 = 0b0,
|
||||
rs: u4,
|
||||
},
|
||||
|
||||
const Type = enum(u2) {
|
||||
LogicalLeft,
|
||||
LogicalRight,
|
||||
ArithmeticRight,
|
||||
RotateRight,
|
||||
};
|
||||
|
||||
const none = Shift{
|
||||
.Immediate = .{
|
||||
.amount = 0,
|
||||
.typ = 0,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn toU8(self: Shift) u8 {
|
||||
return switch (self) {
|
||||
.Register => |v| @bitCast(u8, v),
|
||||
.Immediate => |v| @bitCast(u8, v),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reg(rs: Register, typ: Type) Shift {
|
||||
return Shift{
|
||||
.Register = .{
|
||||
.rs = rs.id(),
|
||||
.typ = @enumToInt(typ),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn imm(amount: u5, typ: Type) Shift {
|
||||
return Shift{
|
||||
.Immediate = .{
|
||||
.amount = amount,
|
||||
.typ = @enumToInt(typ),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn toU12(self: Operand) u12 {
|
||||
return switch (self) {
|
||||
.Register => |v| @bitCast(u12, v),
|
||||
.Immediate => |v| @bitCast(u12, v),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reg(rm: Register, shift: Shift) Operand {
|
||||
return Operand{
|
||||
.Register = .{
|
||||
.rm = rm.id(),
|
||||
.shift = shift.toU8(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn imm(immediate: u8, rotate: u4) Operand {
|
||||
return Operand{
|
||||
.Immediate = .{
|
||||
.imm = immediate,
|
||||
.rotate = rotate,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents the offset operand of a load or store
|
||||
/// instruction. Data can be loaded from memory with either an
|
||||
/// immediate offset or an offset that is stored in some register.
|
||||
pub const Offset = union(enum) {
|
||||
Immediate: u12,
|
||||
Register: packed struct {
|
||||
rm: u4,
|
||||
shift: u8,
|
||||
},
|
||||
|
||||
pub const none = Offset{
|
||||
.Immediate = 0,
|
||||
};
|
||||
|
||||
pub fn toU12(self: Offset) u12 {
|
||||
return switch (self) {
|
||||
.Register => |v| @bitCast(u12, v),
|
||||
.Immediate => |v| v,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reg(rm: Register, shift: u8) Offset {
|
||||
return Offset{
|
||||
.Register = .{
|
||||
.rm = rm.id(),
|
||||
.shift = shift,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn imm(immediate: u8) Offset {
|
||||
return Offset{
|
||||
.Immediate = immediate,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn toU32(self: Instruction) u32 {
|
||||
return switch (self) {
|
||||
.DataProcessing => |v| @bitCast(u32, v),
|
||||
.SingleDataTransfer => |v| @bitCast(u32, v),
|
||||
.Branch => |v| @bitCast(u32, v),
|
||||
.BranchExchange => |v| @bitCast(u32, v),
|
||||
.SupervisorCall => |v| @bitCast(u32, v),
|
||||
.Breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions for the "real" functions below
|
||||
|
||||
fn dataProcessing(
|
||||
cond: Condition,
|
||||
opcode: Opcode,
|
||||
s: u1,
|
||||
rd: Register,
|
||||
rn: Register,
|
||||
op2: Operand,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.DataProcessing = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.i = if (op2 == .Immediate) 1 else 0,
|
||||
.opcode = @enumToInt(opcode),
|
||||
.s = s,
|
||||
.rn = rn.id(),
|
||||
.rd = rd.id(),
|
||||
.op2 = op2.toU12(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn singleDataTransfer(
|
||||
cond: Condition,
|
||||
rd: Register,
|
||||
rn: Register,
|
||||
offset: Offset,
|
||||
pre_post: u1,
|
||||
up_down: u1,
|
||||
byte_word: u1,
|
||||
writeback: u1,
|
||||
load_store: u1,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.SingleDataTransfer = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.rn = rn.id(),
|
||||
.rd = rd.id(),
|
||||
.offset = offset.toU12(),
|
||||
.l = load_store,
|
||||
.w = writeback,
|
||||
.b = byte_word,
|
||||
.u = up_down,
|
||||
.p = pre_post,
|
||||
.i = if (offset == .Immediate) 0 else 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn branch(cond: Condition, offset: i24, link: u1) Instruction {
|
||||
return Instruction{
|
||||
.Branch = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.link = link,
|
||||
.offset = @bitCast(u24, offset),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction {
|
||||
return Instruction{
|
||||
.BranchExchange = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.link = link,
|
||||
.rn = rn.id(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn supervisorCall(cond: Condition, comment: u24) Instruction {
|
||||
return Instruction{
|
||||
.SupervisorCall = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.comment = comment,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn breakpoint(imm: u16) Instruction {
|
||||
return Instruction{
|
||||
.Breakpoint = .{
|
||||
.imm12 = @truncate(u12, imm >> 4),
|
||||
.imm4 = @truncate(u4, imm),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Public functions replicating assembler syntax as closely as
|
||||
// possible
|
||||
|
||||
// Data processing
|
||||
|
||||
pub fn @"and"(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .@"and", s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .eor, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .sub, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .rsb, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .add, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .adc, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn sbc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .sbc, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn rsc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .rsc, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .tst, 1, .r0, rn, op2);
|
||||
}
|
||||
|
||||
pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .teq, 1, .r0, rn, op2);
|
||||
}
|
||||
|
||||
pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .cmp, 1, .r0, rn, op2);
|
||||
}
|
||||
|
||||
pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .cmn, 1, .r0, rn, op2);
|
||||
}
|
||||
|
||||
pub fn orr(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .orr, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .mov, s, rd, .r0, op2);
|
||||
}
|
||||
|
||||
pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .bic, s, rd, rn, op2);
|
||||
}
|
||||
|
||||
pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
|
||||
return dataProcessing(cond, .mvn, s, rd, .r0, op2);
|
||||
}
|
||||
|
||||
// Single data transfer
|
||||
|
||||
pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction {
|
||||
return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1);
|
||||
}
|
||||
|
||||
pub fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction {
|
||||
return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Branch
|
||||
|
||||
pub fn b(cond: Condition, offset: i24) Instruction {
|
||||
return branch(cond, offset, 0);
|
||||
}
|
||||
|
||||
pub fn bl(cond: Condition, offset: i24) Instruction {
|
||||
return branch(cond, offset, 1);
|
||||
}
|
||||
|
||||
// Branch and exchange
|
||||
|
||||
pub fn bx(cond: Condition, rn: Register) Instruction {
|
||||
return branchExchange(cond, rn, 0);
|
||||
}
|
||||
|
||||
pub fn blx(cond: Condition, rn: Register) Instruction {
|
||||
return branchExchange(cond, rn, 1);
|
||||
}
|
||||
|
||||
// Supervisor Call
|
||||
|
||||
pub const swi = svc;
|
||||
|
||||
pub fn svc(cond: Condition, comment: u24) Instruction {
|
||||
return supervisorCall(cond, comment);
|
||||
}
|
||||
|
||||
// Breakpoint
|
||||
|
||||
pub fn bkpt(imm: u16) Instruction {
|
||||
return breakpoint(imm);
|
||||
}
|
||||
};
|
||||
|
||||
test "serialize instructions" {
|
||||
const Testcase = struct {
|
||||
inst: Instruction,
|
||||
expected: u32,
|
||||
};
|
||||
|
||||
const testcases = [_]Testcase{
|
||||
.{ // add r0, r0, r0
|
||||
.inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)),
|
||||
.expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000,
|
||||
},
|
||||
.{ // mov r4, r2
|
||||
.inst = Instruction.mov(.al, 0, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)),
|
||||
.expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010,
|
||||
},
|
||||
.{ // mov r0, #42
|
||||
.inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)),
|
||||
.expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010,
|
||||
},
|
||||
.{ // ldr r0, [r2, #42]
|
||||
.inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)),
|
||||
.expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010,
|
||||
},
|
||||
.{ // str r0, [r3]
|
||||
.inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none),
|
||||
.expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000,
|
||||
},
|
||||
.{ // b #12
|
||||
.inst = Instruction.b(.al, 12),
|
||||
.expected = 0b1110_101_0_0000_0000_0000_0000_0000_1100,
|
||||
},
|
||||
.{ // bl #-4
|
||||
.inst = Instruction.bl(.al, -4),
|
||||
.expected = 0b1110_101_1_1111_1111_1111_1111_1111_1100,
|
||||
},
|
||||
.{ // bx lr
|
||||
.inst = Instruction.bx(.al, .lr),
|
||||
.expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
|
||||
},
|
||||
.{ // svc #0
|
||||
.inst = Instruction.svc(.al, 0),
|
||||
.expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
|
||||
},
|
||||
.{ // bkpt #42
|
||||
.inst = Instruction.bkpt(42),
|
||||
.expected = 0b1110_0001_0010_000000000010_0111_1010,
|
||||
},
|
||||
};
|
||||
|
||||
for (testcases) |case| {
|
||||
const actual = case.inst.toU32();
|
||||
testing.expectEqual(case.expected, actual);
|
||||
}
|
||||
}
|
||||
299
src/codegen/c.zig
Normal file
299
src/codegen/c.zig
Normal file
@@ -0,0 +1,299 @@
|
||||
const std = @import("std");
|
||||
|
||||
const link = @import("../link.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
|
||||
const Inst = @import("../ir.zig").Inst;
|
||||
const Value = @import("../value.zig").Value;
|
||||
const Type = @import("../type.zig").Type;
|
||||
|
||||
const C = link.File.C;
|
||||
const Decl = Module.Decl;
|
||||
const mem = std.mem;
|
||||
|
||||
/// Maps a name from Zig source to C. Currently, this will always give the same
|
||||
/// output for any given input, sometimes resulting in broken identifiers.
|
||||
fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
|
||||
return allocator.dupe(u8, name);
|
||||
}
|
||||
|
||||
fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void {
|
||||
switch (T.zigTypeTag()) {
|
||||
.NoReturn => {
|
||||
try writer.writeAll("zig_noreturn void");
|
||||
},
|
||||
.Void => try writer.writeAll("void"),
|
||||
.Int => {
|
||||
if (T.tag() == .u8) {
|
||||
ctx.file.need_stdint = true;
|
||||
try writer.writeAll("uint8_t");
|
||||
} else if (T.tag() == .usize) {
|
||||
ctx.file.need_stddef = true;
|
||||
try writer.writeAll("size_t");
|
||||
} else {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{});
|
||||
}
|
||||
},
|
||||
else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
|
||||
}
|
||||
}
|
||||
|
||||
fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Value) !void {
|
||||
switch (T.zigTypeTag()) {
|
||||
.Int => {
|
||||
if (T.isSignedInt())
|
||||
return writer.print("{}", .{val.toSignedInt()});
|
||||
return writer.print("{}", .{val.toUnsignedInt()});
|
||||
},
|
||||
else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
|
||||
}
|
||||
}
|
||||
|
||||
fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
|
||||
const tv = decl.typed_value.most_recent.typed_value;
|
||||
try renderType(ctx, writer, tv.ty.fnReturnType());
|
||||
const name = try map(ctx.file.base.allocator, mem.spanZ(decl.name));
|
||||
defer ctx.file.base.allocator.free(name);
|
||||
try writer.print(" {}(", .{name});
|
||||
var param_len = tv.ty.fnParamLen();
|
||||
if (param_len == 0)
|
||||
try writer.writeAll("void")
|
||||
else {
|
||||
var index: usize = 0;
|
||||
while (index < param_len) : (index += 1) {
|
||||
if (index > 0) {
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
try renderType(ctx, writer, tv.ty.fnParamType(index));
|
||||
try writer.print(" arg{}", .{index});
|
||||
}
|
||||
}
|
||||
try writer.writeByte(')');
|
||||
}
|
||||
|
||||
pub fn generate(file: *C, decl: *Decl) !void {
|
||||
switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
|
||||
.Fn => try genFn(file, decl),
|
||||
.Array => try genArray(file, decl),
|
||||
else => |e| return file.fail(decl.src(), "TODO {}", .{e}),
|
||||
}
|
||||
}
|
||||
|
||||
fn genArray(file: *C, decl: *Decl) !void {
|
||||
const tv = decl.typed_value.most_recent.typed_value;
|
||||
// TODO: prevent inline asm constants from being emitted
|
||||
const name = try map(file.base.allocator, mem.span(decl.name));
|
||||
defer file.base.allocator.free(name);
|
||||
if (tv.val.cast(Value.Payload.Bytes)) |payload|
|
||||
if (tv.ty.sentinel()) |sentinel|
|
||||
if (sentinel.toUnsignedInt() == 0)
|
||||
try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
|
||||
else
|
||||
return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{})
|
||||
else
|
||||
return file.fail(decl.src(), "TODO byte arrays without sentinels", .{})
|
||||
else
|
||||
return file.fail(decl.src(), "TODO non-byte arrays", .{});
|
||||
}
|
||||
|
||||
const Context = struct {
|
||||
file: *C,
|
||||
decl: *Decl,
|
||||
inst_map: std.AutoHashMap(*Inst, []u8),
|
||||
argdex: usize = 0,
|
||||
unnamed_index: usize = 0,
|
||||
|
||||
fn name(self: *Context) ![]u8 {
|
||||
const val = try std.fmt.allocPrint(self.file.base.allocator, "__temp_{}", .{self.unnamed_index});
|
||||
self.unnamed_index += 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
fn deinit(self: *Context) void {
|
||||
var it = self.inst_map.iterator();
|
||||
while (it.next()) |kv| {
|
||||
self.file.base.allocator.free(kv.value);
|
||||
}
|
||||
self.inst_map.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
fn genFn(file: *C, decl: *Decl) !void {
|
||||
const writer = file.main.writer();
|
||||
const tv = decl.typed_value.most_recent.typed_value;
|
||||
|
||||
var ctx = Context{
|
||||
.file = file,
|
||||
.decl = decl,
|
||||
.inst_map = std.AutoHashMap(*Inst, []u8).init(file.base.allocator),
|
||||
};
|
||||
defer ctx.deinit();
|
||||
|
||||
try renderFunctionSignature(&ctx, writer, decl);
|
||||
|
||||
try writer.writeAll(" {");
|
||||
|
||||
const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
|
||||
const instructions = func.analysis.success.instructions;
|
||||
if (instructions.len > 0) {
|
||||
try writer.writeAll("\n");
|
||||
for (instructions) |inst| {
|
||||
if (switch (inst.tag) {
|
||||
.assembly => try genAsm(&ctx, inst.castTag(.assembly).?),
|
||||
.call => try genCall(&ctx, inst.castTag(.call).?),
|
||||
.ret => try genRet(&ctx, inst.castTag(.ret).?),
|
||||
.retvoid => try genRetVoid(&ctx),
|
||||
.arg => try genArg(&ctx),
|
||||
.dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
|
||||
.breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),
|
||||
.unreach => try genUnreach(&ctx, inst.castTag(.unreach).?),
|
||||
.intcast => try genIntCast(&ctx, inst.castTag(.intcast).?),
|
||||
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
|
||||
}) |name| {
|
||||
try ctx.inst_map.putNoClobber(inst, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("}\n\n");
|
||||
}
|
||||
|
||||
fn genArg(ctx: *Context) !?[]u8 {
|
||||
const name = try std.fmt.allocPrint(ctx.file.base.allocator, "arg{}", .{ctx.argdex});
|
||||
ctx.argdex += 1;
|
||||
return name;
|
||||
}
|
||||
|
||||
fn genRetVoid(ctx: *Context) !?[]u8 {
|
||||
try ctx.file.main.writer().print(" return;\n", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO return", .{});
|
||||
}
|
||||
|
||||
fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
|
||||
if (inst.base.isUnused())
|
||||
return null;
|
||||
const op = inst.operand;
|
||||
const writer = ctx.file.main.writer();
|
||||
const name = try ctx.name();
|
||||
const from = ctx.inst_map.get(op) orelse
|
||||
return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: intCast argument not found in inst_map", .{});
|
||||
try writer.writeAll(" const ");
|
||||
try renderType(ctx, writer, inst.base.ty);
|
||||
try writer.print(" {} = (", .{name});
|
||||
try renderType(ctx, writer, inst.base.ty);
|
||||
try writer.print("){};\n", .{from});
|
||||
return name;
|
||||
}
|
||||
|
||||
fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
|
||||
const writer = ctx.file.main.writer();
|
||||
const header = ctx.file.header.writer();
|
||||
try writer.writeAll(" ");
|
||||
if (inst.func.castTag(.constant)) |func_inst| {
|
||||
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
|
||||
const target = func_val.func.owner_decl;
|
||||
const target_ty = target.typed_value.most_recent.typed_value.ty;
|
||||
const ret_ty = target_ty.fnReturnType().tag();
|
||||
if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) {
|
||||
try writer.print("(void)", .{});
|
||||
}
|
||||
const tname = mem.spanZ(target.name);
|
||||
if (ctx.file.called.get(tname) == null) {
|
||||
try ctx.file.called.put(tname, void{});
|
||||
try renderFunctionSignature(ctx, header, target);
|
||||
try header.writeAll(";\n");
|
||||
}
|
||||
try writer.print("{}(", .{tname});
|
||||
if (inst.args.len != 0) {
|
||||
for (inst.args) |arg, i| {
|
||||
if (i > 0) {
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
if (arg.cast(Inst.Constant)) |con| {
|
||||
try renderValue(ctx, writer, arg.ty, con.val);
|
||||
} else {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO call pass arg {}", .{arg});
|
||||
}
|
||||
}
|
||||
}
|
||||
try writer.writeAll(");\n");
|
||||
} else {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{});
|
||||
}
|
||||
} else {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
|
||||
// TODO emit #line directive here with line number and filename
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
|
||||
// TODO ??
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
|
||||
try ctx.file.main.writer().writeAll(" zig_unreachable();\n");
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
|
||||
const writer = ctx.file.main.writer();
|
||||
try writer.writeAll(" ");
|
||||
for (as.inputs) |i, index| {
|
||||
if (i[0] == '{' and i[i.len - 1] == '}') {
|
||||
const reg = i[1 .. i.len - 1];
|
||||
const arg = as.args[index];
|
||||
try writer.writeAll("register ");
|
||||
try renderType(ctx, writer, arg.ty);
|
||||
try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
|
||||
// TODO merge constant handling into inst_map as well
|
||||
if (arg.castTag(.constant)) |c| {
|
||||
try renderValue(ctx, writer, arg.ty, c.val);
|
||||
try writer.writeAll(";\n ");
|
||||
} else {
|
||||
const gop = try ctx.inst_map.getOrPut(arg);
|
||||
if (!gop.found_existing) {
|
||||
return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
|
||||
}
|
||||
try writer.print("{};\n ", .{gop.entry.value});
|
||||
}
|
||||
} else {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
|
||||
}
|
||||
}
|
||||
try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
|
||||
if (as.output) |o| {
|
||||
return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{});
|
||||
}
|
||||
if (as.inputs.len > 0) {
|
||||
if (as.output == null) {
|
||||
try writer.writeAll(" :");
|
||||
}
|
||||
try writer.writeAll(": ");
|
||||
for (as.inputs) |i, index| {
|
||||
if (i[0] == '{' and i[i.len - 1] == '}') {
|
||||
const reg = i[1 .. i.len - 1];
|
||||
const arg = as.args[index];
|
||||
if (index > 0) {
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
try writer.print("\"\"({}_constant)", .{reg});
|
||||
} else {
|
||||
// This is blocked by the earlier test
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
try writer.writeAll(");\n");
|
||||
return null;
|
||||
}
|
||||
125
src/codegen/llvm.zig
Normal file
125
src/codegen/llvm.zig
Normal file
@@ -0,0 +1,125 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 {
|
||||
const llvm_arch = switch (target.cpu.arch) {
|
||||
.arm => "arm",
|
||||
.armeb => "armeb",
|
||||
.aarch64 => "aarch64",
|
||||
.aarch64_be => "aarch64_be",
|
||||
.aarch64_32 => "aarch64_32",
|
||||
.arc => "arc",
|
||||
.avr => "avr",
|
||||
.bpfel => "bpfel",
|
||||
.bpfeb => "bpfeb",
|
||||
.hexagon => "hexagon",
|
||||
.mips => "mips",
|
||||
.mipsel => "mipsel",
|
||||
.mips64 => "mips64",
|
||||
.mips64el => "mips64el",
|
||||
.msp430 => "msp430",
|
||||
.powerpc => "powerpc",
|
||||
.powerpc64 => "powerpc64",
|
||||
.powerpc64le => "powerpc64le",
|
||||
.r600 => "r600",
|
||||
.amdgcn => "amdgcn",
|
||||
.riscv32 => "riscv32",
|
||||
.riscv64 => "riscv64",
|
||||
.sparc => "sparc",
|
||||
.sparcv9 => "sparcv9",
|
||||
.sparcel => "sparcel",
|
||||
.s390x => "s390x",
|
||||
.tce => "tce",
|
||||
.tcele => "tcele",
|
||||
.thumb => "thumb",
|
||||
.thumbeb => "thumbeb",
|
||||
.i386 => "i386",
|
||||
.x86_64 => "x86_64",
|
||||
.xcore => "xcore",
|
||||
.nvptx => "nvptx",
|
||||
.nvptx64 => "nvptx64",
|
||||
.le32 => "le32",
|
||||
.le64 => "le64",
|
||||
.amdil => "amdil",
|
||||
.amdil64 => "amdil64",
|
||||
.hsail => "hsail",
|
||||
.hsail64 => "hsail64",
|
||||
.spir => "spir",
|
||||
.spir64 => "spir64",
|
||||
.kalimba => "kalimba",
|
||||
.shave => "shave",
|
||||
.lanai => "lanai",
|
||||
.wasm32 => "wasm32",
|
||||
.wasm64 => "wasm64",
|
||||
.renderscript32 => "renderscript32",
|
||||
.renderscript64 => "renderscript64",
|
||||
.ve => "ve",
|
||||
.spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII,
|
||||
};
|
||||
// TODO Add a sub-arch for some architectures depending on CPU features.
|
||||
|
||||
const llvm_os = switch (target.os.tag) {
|
||||
.freestanding => "unknown",
|
||||
.ananas => "ananas",
|
||||
.cloudabi => "cloudabi",
|
||||
.dragonfly => "dragonfly",
|
||||
.freebsd => "freebsd",
|
||||
.fuchsia => "fuchsia",
|
||||
.ios => "ios",
|
||||
.kfreebsd => "kfreebsd",
|
||||
.linux => "linux",
|
||||
.lv2 => "lv2",
|
||||
.macosx => "macosx",
|
||||
.netbsd => "netbsd",
|
||||
.openbsd => "openbsd",
|
||||
.solaris => "solaris",
|
||||
.windows => "windows",
|
||||
.haiku => "haiku",
|
||||
.minix => "minix",
|
||||
.rtems => "rtems",
|
||||
.nacl => "nacl",
|
||||
.cnk => "cnk",
|
||||
.aix => "aix",
|
||||
.cuda => "cuda",
|
||||
.nvcl => "nvcl",
|
||||
.amdhsa => "amdhsa",
|
||||
.ps4 => "ps4",
|
||||
.elfiamcu => "elfiamcu",
|
||||
.tvos => "tvos",
|
||||
.watchos => "watchos",
|
||||
.mesa3d => "mesa3d",
|
||||
.contiki => "contiki",
|
||||
.amdpal => "amdpal",
|
||||
.hermit => "hermit",
|
||||
.hurd => "hurd",
|
||||
.wasi => "wasi",
|
||||
.emscripten => "emscripten",
|
||||
.uefi => "windows",
|
||||
.other => "unknown",
|
||||
};
|
||||
|
||||
const llvm_abi = switch (target.abi) {
|
||||
.none => "unknown",
|
||||
.gnu => "gnu",
|
||||
.gnuabin32 => "gnuabin32",
|
||||
.gnuabi64 => "gnuabi64",
|
||||
.gnueabi => "gnueabi",
|
||||
.gnueabihf => "gnueabihf",
|
||||
.gnux32 => "gnux32",
|
||||
.code16 => "code16",
|
||||
.eabi => "eabi",
|
||||
.eabihf => "eabihf",
|
||||
.android => "android",
|
||||
.musl => "musl",
|
||||
.musleabi => "musleabi",
|
||||
.musleabihf => "musleabihf",
|
||||
.msvc => "msvc",
|
||||
.itanium => "itanium",
|
||||
.cygnus => "cygnus",
|
||||
.coreclr => "coreclr",
|
||||
.simulator => "simulator",
|
||||
.macabi => "macabi",
|
||||
};
|
||||
|
||||
return std.fmt.allocPrint(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi });
|
||||
}
|
||||
433
src/codegen/riscv64.zig
Normal file
433
src/codegen/riscv64.zig
Normal file
@@ -0,0 +1,433 @@
|
||||
const std = @import("std");
|
||||
const DW = std.dwarf;
|
||||
|
||||
// TODO: this is only tagged to facilitate the monstrosity.
|
||||
// Once packed structs work make it packed.
|
||||
pub const Instruction = union(enum) {
|
||||
R: packed struct {
|
||||
opcode: u7,
|
||||
rd: u5,
|
||||
funct3: u3,
|
||||
rs1: u5,
|
||||
rs2: u5,
|
||||
funct7: u7,
|
||||
},
|
||||
I: packed struct {
|
||||
opcode: u7,
|
||||
rd: u5,
|
||||
funct3: u3,
|
||||
rs1: u5,
|
||||
imm0_11: u12,
|
||||
},
|
||||
S: packed struct {
|
||||
opcode: u7,
|
||||
imm0_4: u5,
|
||||
funct3: u3,
|
||||
rs1: u5,
|
||||
rs2: u5,
|
||||
imm5_11: u7,
|
||||
},
|
||||
B: packed struct {
|
||||
opcode: u7,
|
||||
imm11: u1,
|
||||
imm1_4: u4,
|
||||
funct3: u3,
|
||||
rs1: u5,
|
||||
rs2: u5,
|
||||
imm5_10: u6,
|
||||
imm12: u1,
|
||||
},
|
||||
U: packed struct {
|
||||
opcode: u7,
|
||||
rd: u5,
|
||||
imm12_31: u20,
|
||||
},
|
||||
J: packed struct {
|
||||
opcode: u7,
|
||||
rd: u5,
|
||||
imm12_19: u8,
|
||||
imm11: u1,
|
||||
imm1_10: u10,
|
||||
imm20: u1,
|
||||
},
|
||||
|
||||
// TODO: once packed structs work we can remove this monstrosity.
|
||||
pub fn toU32(self: Instruction) u32 {
|
||||
return switch (self) {
|
||||
.R => |v| @bitCast(u32, v),
|
||||
.I => |v| @bitCast(u32, v),
|
||||
.S => |v| @bitCast(u32, v),
|
||||
.B => |v| @intCast(u32, v.opcode) + (@intCast(u32, v.imm11) << 7) + (@intCast(u32, v.imm1_4) << 8) + (@intCast(u32, v.funct3) << 12) + (@intCast(u32, v.rs1) << 15) + (@intCast(u32, v.rs2) << 20) + (@intCast(u32, v.imm5_10) << 25) + (@intCast(u32, v.imm12) << 31),
|
||||
.U => |v| @bitCast(u32, v),
|
||||
.J => |v| @bitCast(u32, v),
|
||||
};
|
||||
}
|
||||
|
||||
fn rType(op: u7, fn3: u3, fn7: u7, rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return Instruction{
|
||||
.R = .{
|
||||
.opcode = op,
|
||||
.funct3 = fn3,
|
||||
.funct7 = fn7,
|
||||
.rd = @enumToInt(rd),
|
||||
.rs1 = @enumToInt(r1),
|
||||
.rs2 = @enumToInt(r2),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// RISC-V is all signed all the time -- convert immediates to unsigned for processing
|
||||
fn iType(op: u7, fn3: u3, rd: Register, r1: Register, imm: i12) Instruction {
|
||||
const umm = @bitCast(u12, imm);
|
||||
|
||||
return Instruction{
|
||||
.I = .{
|
||||
.opcode = op,
|
||||
.funct3 = fn3,
|
||||
.rd = @enumToInt(rd),
|
||||
.rs1 = @enumToInt(r1),
|
||||
.imm0_11 = umm,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn sType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i12) Instruction {
|
||||
const umm = @bitCast(u12, imm);
|
||||
|
||||
return Instruction{
|
||||
.S = .{
|
||||
.opcode = op,
|
||||
.funct3 = fn3,
|
||||
.rs1 = @enumToInt(r1),
|
||||
.rs2 = @enumToInt(r2),
|
||||
.imm0_4 = @truncate(u5, umm),
|
||||
.imm5_11 = @truncate(u7, umm >> 5),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Use significance value rather than bit value, same for J-type
|
||||
// -- less burden on callsite, bonus semantic checking
|
||||
fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction {
|
||||
const umm = @bitCast(u13, imm);
|
||||
if (umm % 2 != 0) @panic("Internal error: misaligned branch target");
|
||||
|
||||
return Instruction{
|
||||
.B = .{
|
||||
.opcode = op,
|
||||
.funct3 = fn3,
|
||||
.rs1 = @enumToInt(r1),
|
||||
.rs2 = @enumToInt(r2),
|
||||
.imm1_4 = @truncate(u4, umm >> 1),
|
||||
.imm5_10 = @truncate(u6, umm >> 5),
|
||||
.imm11 = @truncate(u1, umm >> 11),
|
||||
.imm12 = @truncate(u1, umm >> 12),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// We have to extract the 20 bits anyway -- let's not make it more painful
|
||||
fn uType(op: u7, rd: Register, imm: i20) Instruction {
|
||||
const umm = @bitCast(u20, imm);
|
||||
|
||||
return Instruction{
|
||||
.U = .{
|
||||
.opcode = op,
|
||||
.rd = @enumToInt(rd),
|
||||
.imm12_31 = umm,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn jType(op: u7, rd: Register, imm: i21) Instruction {
|
||||
const umm = @bitcast(u21, imm);
|
||||
if (umm % 2 != 0) @panic("Internal error: misaligned jump target");
|
||||
|
||||
return Instruction{
|
||||
.J = .{
|
||||
.opcode = op,
|
||||
.rd = @enumToInt(rd),
|
||||
.imm1_10 = @truncate(u10, umm >> 1),
|
||||
.imm11 = @truncate(u1, umm >> 1),
|
||||
.imm12_19 = @truncate(u8, umm >> 12),
|
||||
.imm20 = @truncate(u1, umm >> 20),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// The meat and potatoes. Arguments are in the order in which they would appear in assembly code.
|
||||
|
||||
// Arithmetic/Logical, Register-Register
|
||||
|
||||
pub fn add(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b000, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sub(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b000, 0b0100000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn @"and"(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b111, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn @"or"(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b110, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn xor(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b100, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sll(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b001, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn srl(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b101, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sra(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b101, 0b0100000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn slt(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b010, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sltu(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0110011, 0b011, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
// Arithmetic/Logical, Register-Register (32-bit)
|
||||
|
||||
pub fn addw(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0111011, 0b000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn subw(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0111011, 0b000, 0b0100000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sllw(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0111011, 0b001, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn srlw(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0111011, 0b101, 0b0000000, rd, r1, r2);
|
||||
}
|
||||
|
||||
pub fn sraw(rd: Register, r1: Register, r2: Register) Instruction {
|
||||
return rType(0b0111011, 0b101, 0b0100000, rd, r1, r2);
|
||||
}
|
||||
|
||||
// Arithmetic/Logical, Register-Immediate
|
||||
|
||||
pub fn addi(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0010011, 0b000, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn andi(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0010011, 0b111, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn ori(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0010011, 0b110, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn xori(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0010011, 0b100, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn slli(rd: Register, r1: Register, shamt: u6) Instruction {
|
||||
return iType(0b0010011, 0b001, rd, r1, shamt);
|
||||
}
|
||||
|
||||
pub fn srli(rd: Register, r1: Register, shamt: u6) Instruction {
|
||||
return iType(0b0010011, 0b101, rd, r1, shamt);
|
||||
}
|
||||
|
||||
pub fn srai(rd: Register, r1: Register, shamt: u6) Instruction {
|
||||
return iType(0b0010011, 0b101, rd, r1, (1 << 10) + shamt);
|
||||
}
|
||||
|
||||
pub fn slti(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0010011, 0b010, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn sltiu(rd: Register, r1: Register, imm: u12) Instruction {
|
||||
return iType(0b0010011, 0b011, rd, r1, @bitCast(i12, imm));
|
||||
}
|
||||
|
||||
// Arithmetic/Logical, Register-Immediate (32-bit)
|
||||
|
||||
pub fn addiw(rd: Register, r1: Register, imm: i12) Instruction {
|
||||
return iType(0b0011011, 0b000, rd, r1, imm);
|
||||
}
|
||||
|
||||
pub fn slliw(rd: Register, r1: Register, shamt: u5) Instruction {
|
||||
return iType(0b0011011, 0b001, rd, r1, shamt);
|
||||
}
|
||||
|
||||
pub fn srliw(rd: Register, r1: Register, shamt: u5) Instruction {
|
||||
return iType(0b0011011, 0b101, rd, r1, shamt);
|
||||
}
|
||||
|
||||
pub fn sraiw(rd: Register, r1: Register, shamt: u5) Instruction {
|
||||
return iType(0b0011011, 0b101, rd, r1, (1 << 10) + shamt);
|
||||
}
|
||||
|
||||
// Upper Immediate
|
||||
|
||||
pub fn lui(rd: Register, imm: i20) Instruction {
|
||||
return uType(0b0110111, rd, imm);
|
||||
}
|
||||
|
||||
pub fn auipc(rd: Register, imm: i20) Instruction {
|
||||
return uType(0b0010111, rd, imm);
|
||||
}
|
||||
|
||||
// Load
|
||||
|
||||
pub fn ld(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b011, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lw(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b010, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lwu(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b110, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lh(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b001, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lhu(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b101, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lb(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b000, rd, base, offset);
|
||||
}
|
||||
|
||||
pub fn lbu(rd: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b0000011, 0b100, rd, base, offset);
|
||||
}
|
||||
|
||||
// Store
|
||||
|
||||
pub fn sd(rs: Register, offset: i12, base: Register) Instruction {
|
||||
return sType(0b0100011, 0b011, base, rs, offset);
|
||||
}
|
||||
|
||||
pub fn sw(rs: Register, offset: i12, base: Register) Instruction {
|
||||
return sType(0b0100011, 0b010, base, rs, offset);
|
||||
}
|
||||
|
||||
pub fn sh(rs: Register, offset: i12, base: Register) Instruction {
|
||||
return sType(0b0100011, 0b001, base, rs, offset);
|
||||
}
|
||||
|
||||
pub fn sb(rs: Register, offset: i12, base: Register) Instruction {
|
||||
return sType(0b0100011, 0b000, base, rs, offset);
|
||||
}
|
||||
|
||||
// Fence
|
||||
// TODO: implement fence
|
||||
|
||||
// Branch
|
||||
|
||||
pub fn beq(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b000, r1, r2, offset);
|
||||
}
|
||||
|
||||
pub fn bne(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b001, r1, r2, offset);
|
||||
}
|
||||
|
||||
pub fn blt(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b100, r1, r2, offset);
|
||||
}
|
||||
|
||||
pub fn bge(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b101, r1, r2, offset);
|
||||
}
|
||||
|
||||
pub fn bltu(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b110, r1, r2, offset);
|
||||
}
|
||||
|
||||
pub fn bgeu(r1: Register, r2: Register, offset: u13) Instruction {
|
||||
return bType(0b1100011, 0b111, r1, r2, offset);
|
||||
}
|
||||
|
||||
// Jump
|
||||
|
||||
pub fn jal(link: Register, offset: i21) Instruction {
|
||||
return jType(0b1101111, link, offset);
|
||||
}
|
||||
|
||||
pub fn jalr(link: Register, offset: i12, base: Register) Instruction {
|
||||
return iType(0b1100111, 0b000, link, base, offset);
|
||||
}
|
||||
|
||||
// System
|
||||
|
||||
pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000);
|
||||
pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001);
|
||||
};
|
||||
|
||||
// zig fmt: off
|
||||
pub const RawRegister = enum(u5) {
|
||||
x0, x1, x2, x3, x4, x5, x6, x7,
|
||||
x8, x9, x10, x11, x12, x13, x14, x15,
|
||||
x16, x17, x18, x19, x20, x21, x22, x23,
|
||||
x24, x25, x26, x27, x28, x29, x30, x31,
|
||||
|
||||
pub fn dwarfLocOp(reg: RawRegister) u8 {
|
||||
return @enumToInt(reg) + DW.OP_reg0;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Register = enum(u5) {
|
||||
// 64 bit registers
|
||||
zero, // zero
|
||||
ra, // return address. caller saved
|
||||
sp, // stack pointer. callee saved.
|
||||
gp, // global pointer
|
||||
tp, // thread pointer
|
||||
t0, t1, t2, // temporaries. caller saved.
|
||||
s0, // s0/fp, callee saved.
|
||||
s1, // callee saved.
|
||||
a0, a1, // fn args/return values. caller saved.
|
||||
a2, a3, a4, a5, a6, a7, // fn args. caller saved.
|
||||
s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved.
|
||||
t3, t4, t5, t6, // caller saved
|
||||
|
||||
pub fn parseRegName(name: []const u8) ?Register {
|
||||
if(std.meta.stringToEnum(Register, name)) |reg| return reg;
|
||||
if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg));
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns the index into `callee_preserved_regs`.
|
||||
pub fn allocIndex(self: Register) ?u4 {
|
||||
inline for(callee_preserved_regs) |cpreg, i| {
|
||||
if(self == cpreg) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn dwarfLocOp(reg: Register) u8 {
|
||||
return @as(u8, @enumToInt(reg)) + DW.OP_reg0;
|
||||
}
|
||||
};
|
||||
|
||||
// zig fmt: on
|
||||
|
||||
pub const callee_preserved_regs = [_]Register{
|
||||
.s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
|
||||
};
|
||||
170
src/codegen/spu-mk2.zig
Normal file
170
src/codegen/spu-mk2.zig
Normal file
@@ -0,0 +1,170 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter;
|
||||
|
||||
pub const ExecutionCondition = enum(u3) {
|
||||
always = 0,
|
||||
when_zero = 1,
|
||||
not_zero = 2,
|
||||
greater_zero = 3,
|
||||
less_than_zero = 4,
|
||||
greater_or_equal_zero = 5,
|
||||
less_or_equal_zero = 6,
|
||||
overflow = 7,
|
||||
};
|
||||
|
||||
pub const InputBehaviour = enum(u2) {
|
||||
zero = 0,
|
||||
immediate = 1,
|
||||
peek = 2,
|
||||
pop = 3,
|
||||
};
|
||||
|
||||
pub const OutputBehaviour = enum(u2) {
|
||||
discard = 0,
|
||||
push = 1,
|
||||
jump = 2,
|
||||
jump_relative = 3,
|
||||
};
|
||||
|
||||
pub const Command = enum(u5) {
|
||||
copy = 0,
|
||||
ipget = 1,
|
||||
get = 2,
|
||||
set = 3,
|
||||
store8 = 4,
|
||||
store16 = 5,
|
||||
load8 = 6,
|
||||
load16 = 7,
|
||||
undefined0 = 8,
|
||||
undefined1 = 9,
|
||||
frget = 10,
|
||||
frset = 11,
|
||||
bpget = 12,
|
||||
bpset = 13,
|
||||
spget = 14,
|
||||
spset = 15,
|
||||
add = 16,
|
||||
sub = 17,
|
||||
mul = 18,
|
||||
div = 19,
|
||||
mod = 20,
|
||||
@"and" = 21,
|
||||
@"or" = 22,
|
||||
xor = 23,
|
||||
not = 24,
|
||||
signext = 25,
|
||||
rol = 26,
|
||||
ror = 27,
|
||||
bswap = 28,
|
||||
asr = 29,
|
||||
lsl = 30,
|
||||
lsr = 31,
|
||||
};
|
||||
|
||||
pub const Instruction = packed struct {
|
||||
condition: ExecutionCondition,
|
||||
input0: InputBehaviour,
|
||||
input1: InputBehaviour,
|
||||
modify_flags: bool,
|
||||
output: OutputBehaviour,
|
||||
command: Command,
|
||||
reserved: u1 = 0,
|
||||
|
||||
pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
|
||||
try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
|
||||
try out.writeAll(switch (instr.condition) {
|
||||
.always => " ",
|
||||
.when_zero => "== 0",
|
||||
.not_zero => "!= 0",
|
||||
.greater_zero => " > 0",
|
||||
.less_than_zero => " < 0",
|
||||
.greater_or_equal_zero => ">= 0",
|
||||
.less_or_equal_zero => "<= 0",
|
||||
.overflow => "ovfl",
|
||||
});
|
||||
try out.writeAll(" ");
|
||||
try out.writeAll(switch (instr.input0) {
|
||||
.zero => "zero",
|
||||
.immediate => "imm ",
|
||||
.peek => "peek",
|
||||
.pop => "pop ",
|
||||
});
|
||||
try out.writeAll(" ");
|
||||
try out.writeAll(switch (instr.input1) {
|
||||
.zero => "zero",
|
||||
.immediate => "imm ",
|
||||
.peek => "peek",
|
||||
.pop => "pop ",
|
||||
});
|
||||
try out.writeAll(" ");
|
||||
try out.writeAll(switch (instr.command) {
|
||||
.copy => "copy ",
|
||||
.ipget => "ipget ",
|
||||
.get => "get ",
|
||||
.set => "set ",
|
||||
.store8 => "store8 ",
|
||||
.store16 => "store16 ",
|
||||
.load8 => "load8 ",
|
||||
.load16 => "load16 ",
|
||||
.undefined0 => "undefined",
|
||||
.undefined1 => "undefined",
|
||||
.frget => "frget ",
|
||||
.frset => "frset ",
|
||||
.bpget => "bpget ",
|
||||
.bpset => "bpset ",
|
||||
.spget => "spget ",
|
||||
.spset => "spset ",
|
||||
.add => "add ",
|
||||
.sub => "sub ",
|
||||
.mul => "mul ",
|
||||
.div => "div ",
|
||||
.mod => "mod ",
|
||||
.@"and" => "and ",
|
||||
.@"or" => "or ",
|
||||
.xor => "xor ",
|
||||
.not => "not ",
|
||||
.signext => "signext ",
|
||||
.rol => "rol ",
|
||||
.ror => "ror ",
|
||||
.bswap => "bswap ",
|
||||
.asr => "asr ",
|
||||
.lsl => "lsl ",
|
||||
.lsr => "lsr ",
|
||||
});
|
||||
try out.writeAll(" ");
|
||||
try out.writeAll(switch (instr.output) {
|
||||
.discard => "discard",
|
||||
.push => "push ",
|
||||
.jump => "jmp ",
|
||||
.jump_relative => "rjmp ",
|
||||
});
|
||||
try out.writeAll(" ");
|
||||
try out.writeAll(if (instr.modify_flags)
|
||||
"+ flags"
|
||||
else
|
||||
" ");
|
||||
}
|
||||
};
|
||||
|
||||
pub const FlagRegister = packed struct {
|
||||
zero: bool,
|
||||
negative: bool,
|
||||
carry: bool,
|
||||
carry_enabled: bool,
|
||||
interrupt0_enabled: bool,
|
||||
interrupt1_enabled: bool,
|
||||
interrupt2_enabled: bool,
|
||||
interrupt3_enabled: bool,
|
||||
reserved: u8 = 0,
|
||||
};
|
||||
|
||||
pub const Register = enum {
|
||||
dummy,
|
||||
|
||||
pub fn allocIndex(self: Register) ?u4 {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const callee_preserved_regs = [_]Register{};
|
||||
166
src/codegen/spu-mk2/interpreter.zig
Normal file
166
src/codegen/spu-mk2/interpreter.zig
Normal file
@@ -0,0 +1,166 @@
|
||||
const std = @import("std");
|
||||
const log = std.log.scoped(.SPU_2_Interpreter);
|
||||
const spu = @import("../spu-mk2.zig");
|
||||
const FlagRegister = spu.FlagRegister;
|
||||
const Instruction = spu.Instruction;
|
||||
const ExecutionCondition = spu.ExecutionCondition;
|
||||
|
||||
pub fn Interpreter(comptime Bus: type) type {
|
||||
return struct {
|
||||
ip: u16 = 0,
|
||||
sp: u16 = undefined,
|
||||
bp: u16 = undefined,
|
||||
fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
|
||||
/// This is set to true when we hit an undefined0 instruction, allowing it to
|
||||
/// be used as a trap for testing purposes
|
||||
undefined0: bool = false,
|
||||
/// This is set to true when we hit an undefined1 instruction, allowing it to
|
||||
/// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
|
||||
undefined1: bool = false,
|
||||
bus: Bus,
|
||||
|
||||
pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
|
||||
var count: usize = 0;
|
||||
while (size == null or count < size.?) {
|
||||
count += 1;
|
||||
var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
|
||||
|
||||
log.debug("Executing {}\n", .{instruction});
|
||||
|
||||
self.ip +%= 2;
|
||||
|
||||
const execute = switch (instruction.condition) {
|
||||
.always => true,
|
||||
.not_zero => !self.fr.zero,
|
||||
.when_zero => self.fr.zero,
|
||||
.overflow => self.fr.carry,
|
||||
ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
|
||||
else => return error.Unimplemented,
|
||||
};
|
||||
|
||||
if (execute) {
|
||||
const val0 = switch (instruction.input0) {
|
||||
.zero => @as(u16, 0),
|
||||
.immediate => i: {
|
||||
const val = self.bus.read16(@intCast(u16, self.ip));
|
||||
self.ip +%= 2;
|
||||
break :i val;
|
||||
},
|
||||
else => |e| e: {
|
||||
// peek or pop; show value at current SP, and if pop, increment sp
|
||||
const val = self.bus.read16(self.sp);
|
||||
if (e == .pop) {
|
||||
self.sp +%= 2;
|
||||
}
|
||||
break :e val;
|
||||
},
|
||||
};
|
||||
const val1 = switch (instruction.input1) {
|
||||
.zero => @as(u16, 0),
|
||||
.immediate => i: {
|
||||
const val = self.bus.read16(@intCast(u16, self.ip));
|
||||
self.ip +%= 2;
|
||||
break :i val;
|
||||
},
|
||||
else => |e| e: {
|
||||
// peek or pop; show value at current SP, and if pop, increment sp
|
||||
const val = self.bus.read16(self.sp);
|
||||
if (e == .pop) {
|
||||
self.sp +%= 2;
|
||||
}
|
||||
break :e val;
|
||||
},
|
||||
};
|
||||
|
||||
const output: u16 = switch (instruction.command) {
|
||||
.get => self.bus.read16(self.bp +% (2 *% val0)),
|
||||
.set => a: {
|
||||
self.bus.write16(self.bp +% 2 *% val0, val1);
|
||||
break :a val1;
|
||||
},
|
||||
.load8 => self.bus.read8(val0),
|
||||
.load16 => self.bus.read16(val0),
|
||||
.store8 => a: {
|
||||
const val = @truncate(u8, val1);
|
||||
self.bus.write8(val0, val);
|
||||
break :a val;
|
||||
},
|
||||
.store16 => a: {
|
||||
self.bus.write16(val0, val1);
|
||||
break :a val1;
|
||||
},
|
||||
.copy => val0,
|
||||
.add => a: {
|
||||
var val: u16 = undefined;
|
||||
self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
|
||||
break :a val;
|
||||
},
|
||||
.sub => a: {
|
||||
var val: u16 = undefined;
|
||||
self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
|
||||
break :a val;
|
||||
},
|
||||
.spset => a: {
|
||||
self.sp = val0;
|
||||
break :a val0;
|
||||
},
|
||||
.bpset => a: {
|
||||
self.bp = val0;
|
||||
break :a val0;
|
||||
},
|
||||
.frset => a: {
|
||||
const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
|
||||
self.fr = @bitCast(FlagRegister, val);
|
||||
break :a val;
|
||||
},
|
||||
.bswap => (val0 >> 8) | (val0 << 8),
|
||||
.bpget => self.bp,
|
||||
.spget => self.sp,
|
||||
.ipget => self.ip +% (2 *% val0),
|
||||
.lsl => val0 << 1,
|
||||
.lsr => val0 >> 1,
|
||||
.@"and" => val0 & val1,
|
||||
.@"or" => val0 | val1,
|
||||
.xor => val0 ^ val1,
|
||||
.not => ~val0,
|
||||
.undefined0 => {
|
||||
self.undefined0 = true;
|
||||
// Break out of the loop, and let the caller decide what to do
|
||||
return;
|
||||
},
|
||||
.undefined1 => {
|
||||
self.undefined1 = true;
|
||||
// Break out of the loop, and let the caller decide what to do
|
||||
return;
|
||||
},
|
||||
.signext => if ((val0 & 0x80) != 0)
|
||||
(val0 & 0xFF) | 0xFF00
|
||||
else
|
||||
(val0 & 0xFF),
|
||||
else => return error.Unimplemented,
|
||||
};
|
||||
|
||||
switch (instruction.output) {
|
||||
.discard => {},
|
||||
.push => {
|
||||
self.sp -%= 2;
|
||||
self.bus.write16(self.sp, output);
|
||||
},
|
||||
.jump => {
|
||||
self.ip = output;
|
||||
},
|
||||
else => return error.Unimplemented,
|
||||
}
|
||||
if (instruction.modify_flags) {
|
||||
self.fr.negative = (output & 0x8000) != 0;
|
||||
self.fr.zero = (output == 0x0000);
|
||||
}
|
||||
} else {
|
||||
if (instruction.input0 == .immediate) self.ip +%= 2;
|
||||
if (instruction.input1 == .immediate) self.ip +%= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
142
src/codegen/wasm.zig
Normal file
142
src/codegen/wasm.zig
Normal file
@@ -0,0 +1,142 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
const assert = std.debug.assert;
|
||||
const leb = std.debug.leb;
|
||||
const mem = std.mem;
|
||||
|
||||
const Module = @import("../Module.zig");
|
||||
const Decl = Module.Decl;
|
||||
const Inst = @import("../ir.zig").Inst;
|
||||
const Type = @import("../type.zig").Type;
|
||||
const Value = @import("../value.zig").Value;
|
||||
|
||||
fn genValtype(ty: Type) u8 {
|
||||
return switch (ty.tag()) {
|
||||
.u32, .i32 => 0x7F,
|
||||
.u64, .i64 => 0x7E,
|
||||
.f32 => 0x7D,
|
||||
.f64 => 0x7C,
|
||||
else => @panic("TODO: Implement more types for wasm."),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {
|
||||
const ty = decl.typed_value.most_recent.typed_value.ty;
|
||||
const writer = buf.writer();
|
||||
|
||||
// functype magic
|
||||
try writer.writeByte(0x60);
|
||||
|
||||
// param types
|
||||
try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
|
||||
if (ty.fnParamLen() != 0) {
|
||||
const params = try buf.allocator.alloc(Type, ty.fnParamLen());
|
||||
defer buf.allocator.free(params);
|
||||
ty.fnParamTypes(params);
|
||||
for (params) |param_type| try writer.writeByte(genValtype(param_type));
|
||||
}
|
||||
|
||||
// return type
|
||||
const return_type = ty.fnReturnType();
|
||||
switch (return_type.tag()) {
|
||||
.void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
|
||||
else => {
|
||||
try leb.writeULEB128(writer, @as(u32, 1));
|
||||
try writer.writeByte(genValtype(return_type));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
|
||||
assert(buf.items.len == 0);
|
||||
const writer = buf.writer();
|
||||
|
||||
// Reserve space to write the size after generating the code
|
||||
try buf.resize(5);
|
||||
|
||||
// Write the size of the locals vec
|
||||
// TODO: implement locals
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
|
||||
// Write instructions
|
||||
// TODO: check for and handle death of instructions
|
||||
const tv = decl.typed_value.most_recent.typed_value;
|
||||
const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
|
||||
for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst);
|
||||
|
||||
// Write 'end' opcode
|
||||
try writer.writeByte(0x0B);
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;
|
||||
leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));
|
||||
}
|
||||
|
||||
fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {
|
||||
return switch (inst.tag) {
|
||||
.call => genCall(buf, decl, inst.castTag(.call).?),
|
||||
.constant => genConstant(buf, decl, inst.castTag(.constant).?),
|
||||
.dbg_stmt => {},
|
||||
.ret => genRet(buf, decl, inst.castTag(.ret).?),
|
||||
.retvoid => {},
|
||||
else => error.TODOImplementMoreWasmCodegen,
|
||||
};
|
||||
}
|
||||
|
||||
fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {
|
||||
const writer = buf.writer();
|
||||
switch (inst.base.ty.tag()) {
|
||||
.u32 => {
|
||||
try writer.writeByte(0x41); // i32.const
|
||||
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
|
||||
},
|
||||
.i32 => {
|
||||
try writer.writeByte(0x41); // i32.const
|
||||
try leb.writeILEB128(writer, inst.val.toSignedInt());
|
||||
},
|
||||
.u64 => {
|
||||
try writer.writeByte(0x42); // i64.const
|
||||
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
|
||||
},
|
||||
.i64 => {
|
||||
try writer.writeByte(0x42); // i64.const
|
||||
try leb.writeILEB128(writer, inst.val.toSignedInt());
|
||||
},
|
||||
.f32 => {
|
||||
try writer.writeByte(0x43); // f32.const
|
||||
// TODO: enforce LE byte order
|
||||
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
|
||||
},
|
||||
.f64 => {
|
||||
try writer.writeByte(0x44); // f64.const
|
||||
// TODO: enforce LE byte order
|
||||
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
|
||||
},
|
||||
.void => {},
|
||||
else => return error.TODOImplementMoreWasmCodegen,
|
||||
}
|
||||
}
|
||||
|
||||
fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
|
||||
try genInst(buf, decl, inst.operand);
|
||||
}
|
||||
|
||||
fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
|
||||
const func_inst = inst.func.castTag(.constant).?;
|
||||
const func_val = func_inst.val.cast(Value.Payload.Function).?;
|
||||
const target = func_val.func.owner_decl;
|
||||
const target_ty = target.typed_value.most_recent.typed_value.ty;
|
||||
|
||||
if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
|
||||
|
||||
try buf.append(0x10); // call
|
||||
|
||||
// The function index immediate argument will be filled in using this data
|
||||
// in link.Wasm.flush().
|
||||
try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{
|
||||
.offset = @intCast(u32, buf.items.len),
|
||||
.decl = target,
|
||||
});
|
||||
}
|
||||
123
src/codegen/x86.zig
Normal file
123
src/codegen/x86.zig
Normal file
@@ -0,0 +1,123 @@
|
||||
const std = @import("std");
|
||||
const DW = std.dwarf;
|
||||
|
||||
// zig fmt: off
|
||||
pub const Register = enum(u8) {
|
||||
// 0 through 7, 32-bit registers. id is int value
|
||||
eax, ecx, edx, ebx, esp, ebp, esi, edi,
|
||||
|
||||
// 8-15, 16-bit registers. id is int value - 8.
|
||||
ax, cx, dx, bx, sp, bp, si, di,
|
||||
|
||||
// 16-23, 8-bit registers. id is int value - 16.
|
||||
al, cl, dl, bl, ah, ch, dh, bh,
|
||||
|
||||
/// Returns the bit-width of the register.
|
||||
pub fn size(self: @This()) u7 {
|
||||
return switch (@enumToInt(self)) {
|
||||
0...7 => 32,
|
||||
8...15 => 16,
|
||||
16...23 => 8,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the register's id. This is used in practically every opcode the
|
||||
/// x86 has. It is embedded in some instructions, such as the `B8 +rd` move
|
||||
/// instruction, and is used in the R/M byte.
|
||||
pub fn id(self: @This()) u3 {
|
||||
return @truncate(u3, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Returns the index into `callee_preserved_regs`.
|
||||
pub fn allocIndex(self: Register) ?u4 {
|
||||
return switch (self) {
|
||||
.eax, .ax, .al => 0,
|
||||
.ecx, .cx, .cl => 1,
|
||||
.edx, .dx, .dl => 2,
|
||||
.esi, .si => 3,
|
||||
.edi, .di => 4,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert from any register to its 32 bit alias.
|
||||
pub fn to32(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()));
|
||||
}
|
||||
|
||||
/// Convert from any register to its 16 bit alias.
|
||||
pub fn to16(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 8);
|
||||
}
|
||||
|
||||
/// Convert from any register to its 8 bit alias.
|
||||
pub fn to8(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 16);
|
||||
}
|
||||
|
||||
|
||||
pub fn dwarfLocOp(reg: Register) u8 {
|
||||
return switch (reg.to32()) {
|
||||
.eax => DW.OP_reg0,
|
||||
.ecx => DW.OP_reg1,
|
||||
.edx => DW.OP_reg2,
|
||||
.ebx => DW.OP_reg3,
|
||||
.esp => DW.OP_reg4,
|
||||
.ebp => DW.OP_reg5,
|
||||
.esi => DW.OP_reg6,
|
||||
.edi => DW.OP_reg7,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// zig fmt: on
|
||||
|
||||
pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi };
|
||||
|
||||
// TODO add these to Register enum and corresponding dwarfLocOp
|
||||
// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register.
|
||||
// RA = (8, "RA"),
|
||||
//
|
||||
// ST0 = (11, "st0"),
|
||||
// ST1 = (12, "st1"),
|
||||
// ST2 = (13, "st2"),
|
||||
// ST3 = (14, "st3"),
|
||||
// ST4 = (15, "st4"),
|
||||
// ST5 = (16, "st5"),
|
||||
// ST6 = (17, "st6"),
|
||||
// ST7 = (18, "st7"),
|
||||
//
|
||||
// XMM0 = (21, "xmm0"),
|
||||
// XMM1 = (22, "xmm1"),
|
||||
// XMM2 = (23, "xmm2"),
|
||||
// XMM3 = (24, "xmm3"),
|
||||
// XMM4 = (25, "xmm4"),
|
||||
// XMM5 = (26, "xmm5"),
|
||||
// XMM6 = (27, "xmm6"),
|
||||
// XMM7 = (28, "xmm7"),
|
||||
//
|
||||
// MM0 = (29, "mm0"),
|
||||
// MM1 = (30, "mm1"),
|
||||
// MM2 = (31, "mm2"),
|
||||
// MM3 = (32, "mm3"),
|
||||
// MM4 = (33, "mm4"),
|
||||
// MM5 = (34, "mm5"),
|
||||
// MM6 = (35, "mm6"),
|
||||
// MM7 = (36, "mm7"),
|
||||
//
|
||||
// MXCSR = (39, "mxcsr"),
|
||||
//
|
||||
// ES = (40, "es"),
|
||||
// CS = (41, "cs"),
|
||||
// SS = (42, "ss"),
|
||||
// DS = (43, "ds"),
|
||||
// FS = (44, "fs"),
|
||||
// GS = (45, "gs"),
|
||||
//
|
||||
// TR = (48, "tr"),
|
||||
// LDTR = (49, "ldtr"),
|
||||
//
|
||||
// FS_BASE = (93, "fs.base"),
|
||||
// GS_BASE = (94, "gs.base"),
|
||||
220
src/codegen/x86_64.zig
Normal file
220
src/codegen/x86_64.zig
Normal file
@@ -0,0 +1,220 @@
|
||||
const std = @import("std");
|
||||
const Type = @import("../Type.zig");
|
||||
const DW = std.dwarf;
|
||||
|
||||
// zig fmt: off
|
||||
|
||||
/// Definitions of all of the x64 registers. The order is semantically meaningful.
|
||||
/// The registers are defined such that IDs go in descending order of 64-bit,
|
||||
/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen
|
||||
/// registers. This results in some useful properties:
|
||||
///
|
||||
/// Any 64-bit register can be turned into its 32-bit form by adding 16, and
|
||||
/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it
|
||||
/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit
|
||||
/// form.
|
||||
///
|
||||
/// If (register & 8) is set, the register is extended.
|
||||
///
|
||||
/// The ID can be easily determined by figuring out what range the register is
|
||||
/// in, and then subtracting the base.
|
||||
pub const Register = enum(u8) {
|
||||
// 0 through 15, 64-bit registers. 8-15 are extended.
|
||||
// id is just the int value.
|
||||
rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
|
||||
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
|
||||
// 16 through 31, 32-bit registers. 24-31 are extended.
|
||||
// id is int value - 16.
|
||||
eax, ecx, edx, ebx, esp, ebp, esi, edi,
|
||||
r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
|
||||
|
||||
// 32-47, 16-bit registers. 40-47 are extended.
|
||||
// id is int value - 32.
|
||||
ax, cx, dx, bx, sp, bp, si, di,
|
||||
r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
|
||||
|
||||
// 48-63, 8-bit registers. 56-63 are extended.
|
||||
// id is int value - 48.
|
||||
al, cl, dl, bl, ah, ch, dh, bh,
|
||||
r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
|
||||
|
||||
/// Returns the bit-width of the register.
|
||||
pub fn size(self: Register) u7 {
|
||||
return switch (@enumToInt(self)) {
|
||||
0...15 => 64,
|
||||
16...31 => 32,
|
||||
32...47 => 16,
|
||||
48...64 => 8,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns whether the register is *extended*. Extended registers are the
|
||||
/// new registers added with amd64, r8 through r15. This also includes any
|
||||
/// other variant of access to those registers, such as r8b, r15d, and so
|
||||
/// on. This is needed because access to these registers requires special
|
||||
/// handling via the REX prefix, via the B or R bits, depending on context.
|
||||
pub fn isExtended(self: Register) bool {
|
||||
return @enumToInt(self) & 0x08 != 0;
|
||||
}
|
||||
|
||||
/// This returns the 4-bit register ID, which is used in practically every
|
||||
/// opcode. Note that bit 3 (the highest bit) is *never* used directly in
|
||||
/// an instruction (@see isExtended), and requires special handling. The
|
||||
/// lower three bits are often embedded directly in instructions (such as
|
||||
/// the B8 variant of moves), or used in R/M bytes.
|
||||
pub fn id(self: Register) u4 {
|
||||
return @truncate(u4, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Returns the index into `callee_preserved_regs`.
|
||||
pub fn allocIndex(self: Register) ?u4 {
|
||||
return switch (self) {
|
||||
.rax, .eax, .ax, .al => 0,
|
||||
.rcx, .ecx, .cx, .cl => 1,
|
||||
.rdx, .edx, .dx, .dl => 2,
|
||||
.rsi, .esi, .si => 3,
|
||||
.rdi, .edi, .di => 4,
|
||||
.r8, .r8d, .r8w, .r8b => 5,
|
||||
.r9, .r9d, .r9w, .r9b => 6,
|
||||
.r10, .r10d, .r10w, .r10b => 7,
|
||||
.r11, .r11d, .r11w, .r11b => 8,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert from any register to its 64 bit alias.
|
||||
pub fn to64(self: Register) Register {
|
||||
return @intToEnum(Register, self.id());
|
||||
}
|
||||
|
||||
/// Convert from any register to its 32 bit alias.
|
||||
pub fn to32(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 16);
|
||||
}
|
||||
|
||||
/// Convert from any register to its 16 bit alias.
|
||||
pub fn to16(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 32);
|
||||
}
|
||||
|
||||
/// Convert from any register to its 8 bit alias.
|
||||
pub fn to8(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 48);
|
||||
}
|
||||
|
||||
pub fn dwarfLocOp(self: Register) u8 {
|
||||
return switch (self.to64()) {
|
||||
.rax => DW.OP_reg0,
|
||||
.rdx => DW.OP_reg1,
|
||||
.rcx => DW.OP_reg2,
|
||||
.rbx => DW.OP_reg3,
|
||||
.rsi => DW.OP_reg4,
|
||||
.rdi => DW.OP_reg5,
|
||||
.rbp => DW.OP_reg6,
|
||||
.rsp => DW.OP_reg7,
|
||||
|
||||
.r8 => DW.OP_reg8,
|
||||
.r9 => DW.OP_reg9,
|
||||
.r10 => DW.OP_reg10,
|
||||
.r11 => DW.OP_reg11,
|
||||
.r12 => DW.OP_reg12,
|
||||
.r13 => DW.OP_reg13,
|
||||
.r14 => DW.OP_reg14,
|
||||
.r15 => DW.OP_reg15,
|
||||
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// zig fmt: on
|
||||
|
||||
/// These registers belong to the called function.
|
||||
pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
|
||||
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
|
||||
pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
|
||||
|
||||
// TODO add these registers to the enum and populate dwarfLocOp
|
||||
// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register.
|
||||
// RA = (16, "RA"),
|
||||
//
|
||||
// XMM0 = (17, "xmm0"),
|
||||
// XMM1 = (18, "xmm1"),
|
||||
// XMM2 = (19, "xmm2"),
|
||||
// XMM3 = (20, "xmm3"),
|
||||
// XMM4 = (21, "xmm4"),
|
||||
// XMM5 = (22, "xmm5"),
|
||||
// XMM6 = (23, "xmm6"),
|
||||
// XMM7 = (24, "xmm7"),
|
||||
//
|
||||
// XMM8 = (25, "xmm8"),
|
||||
// XMM9 = (26, "xmm9"),
|
||||
// XMM10 = (27, "xmm10"),
|
||||
// XMM11 = (28, "xmm11"),
|
||||
// XMM12 = (29, "xmm12"),
|
||||
// XMM13 = (30, "xmm13"),
|
||||
// XMM14 = (31, "xmm14"),
|
||||
// XMM15 = (32, "xmm15"),
|
||||
//
|
||||
// ST0 = (33, "st0"),
|
||||
// ST1 = (34, "st1"),
|
||||
// ST2 = (35, "st2"),
|
||||
// ST3 = (36, "st3"),
|
||||
// ST4 = (37, "st4"),
|
||||
// ST5 = (38, "st5"),
|
||||
// ST6 = (39, "st6"),
|
||||
// ST7 = (40, "st7"),
|
||||
//
|
||||
// MM0 = (41, "mm0"),
|
||||
// MM1 = (42, "mm1"),
|
||||
// MM2 = (43, "mm2"),
|
||||
// MM3 = (44, "mm3"),
|
||||
// MM4 = (45, "mm4"),
|
||||
// MM5 = (46, "mm5"),
|
||||
// MM6 = (47, "mm6"),
|
||||
// MM7 = (48, "mm7"),
|
||||
//
|
||||
// RFLAGS = (49, "rFLAGS"),
|
||||
// ES = (50, "es"),
|
||||
// CS = (51, "cs"),
|
||||
// SS = (52, "ss"),
|
||||
// DS = (53, "ds"),
|
||||
// FS = (54, "fs"),
|
||||
// GS = (55, "gs"),
|
||||
//
|
||||
// FS_BASE = (58, "fs.base"),
|
||||
// GS_BASE = (59, "gs.base"),
|
||||
//
|
||||
// TR = (62, "tr"),
|
||||
// LDTR = (63, "ldtr"),
|
||||
// MXCSR = (64, "mxcsr"),
|
||||
// FCW = (65, "fcw"),
|
||||
// FSW = (66, "fsw"),
|
||||
//
|
||||
// XMM16 = (67, "xmm16"),
|
||||
// XMM17 = (68, "xmm17"),
|
||||
// XMM18 = (69, "xmm18"),
|
||||
// XMM19 = (70, "xmm19"),
|
||||
// XMM20 = (71, "xmm20"),
|
||||
// XMM21 = (72, "xmm21"),
|
||||
// XMM22 = (73, "xmm22"),
|
||||
// XMM23 = (74, "xmm23"),
|
||||
// XMM24 = (75, "xmm24"),
|
||||
// XMM25 = (76, "xmm25"),
|
||||
// XMM26 = (77, "xmm26"),
|
||||
// XMM27 = (78, "xmm27"),
|
||||
// XMM28 = (79, "xmm28"),
|
||||
// XMM29 = (80, "xmm29"),
|
||||
// XMM30 = (81, "xmm30"),
|
||||
// XMM31 = (82, "xmm31"),
|
||||
//
|
||||
// K0 = (118, "k0"),
|
||||
// K1 = (119, "k1"),
|
||||
// K2 = (120, "k2"),
|
||||
// K3 = (121, "k3"),
|
||||
// K4 = (122, "k4"),
|
||||
// K5 = (123, "k5"),
|
||||
// K6 = (124, "k6"),
|
||||
// K7 = (125, "k7"),
|
||||
Reference in New Issue
Block a user