diff options
Diffstat (limited to 'src/codegen')
| -rw-r--r-- | src/codegen/arm.zig | 607 | ||||
| -rw-r--r-- | src/codegen/c.zig | 299 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 125 | ||||
| -rw-r--r-- | src/codegen/riscv64.zig | 433 | ||||
| -rw-r--r-- | src/codegen/spu-mk2.zig | 170 | ||||
| -rw-r--r-- | src/codegen/spu-mk2/interpreter.zig | 166 | ||||
| -rw-r--r-- | src/codegen/wasm.zig | 142 | ||||
| -rw-r--r-- | src/codegen/x86.zig | 123 | ||||
| -rw-r--r-- | src/codegen/x86_64.zig | 220 |
9 files changed, 2285 insertions, 0 deletions
diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig new file mode 100644 index 0000000000..05178ea7d3 --- /dev/null +++ b/src/codegen/arm.zig @@ -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); + } +} diff --git a/src/codegen/c.zig b/src/codegen/c.zig new file mode 100644 index 0000000000..34ddcfbb3b --- /dev/null +++ b/src/codegen/c.zig @@ -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; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig new file mode 100644 index 0000000000..01fa0baf02 --- /dev/null +++ b/src/codegen/llvm.zig @@ -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 }); +} diff --git a/src/codegen/riscv64.zig b/src/codegen/riscv64.zig new file mode 100644 index 0000000000..96b9c58f9c --- /dev/null +++ b/src/codegen/riscv64.zig @@ -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, +}; diff --git a/src/codegen/spu-mk2.zig b/src/codegen/spu-mk2.zig new file mode 100644 index 0000000000..542862caca --- /dev/null +++ b/src/codegen/spu-mk2.zig @@ -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{}; diff --git a/src/codegen/spu-mk2/interpreter.zig b/src/codegen/spu-mk2/interpreter.zig new file mode 100644 index 0000000000..1ec99546c6 --- /dev/null +++ b/src/codegen/spu-mk2/interpreter.zig @@ -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; + } + } + } + }; +} diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig new file mode 100644 index 0000000000..4ea8838409 --- /dev/null +++ b/src/codegen/wasm.zig @@ -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, + }); +} diff --git a/src/codegen/x86.zig b/src/codegen/x86.zig new file mode 100644 index 0000000000..fdad4e56db --- /dev/null +++ b/src/codegen/x86.zig @@ -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"), diff --git a/src/codegen/x86_64.zig b/src/codegen/x86_64.zig new file mode 100644 index 0000000000..dea39f82cd --- /dev/null +++ b/src/codegen/x86_64.zig @@ -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"), |
