rename src-self-hosted/ to src/

This commit is contained in:
Andrew Kelley
2020-09-21 18:38:55 -07:00
parent b9f61d4015
commit 528832bd3a
109 changed files with 29 additions and 30 deletions

607
src/codegen/arm.zig Normal file
View 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
View 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
View 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
View 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
View 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{};

View 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
View 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
View 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
View 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"),