Commit 1972a2b080
Changed files (3)
src
arch
sparcv9
src/arch/sparcv9/CodeGen.zig
@@ -2,11 +2,14 @@
//! This lowers AIR into MIR.
const std = @import("std");
const assert = std.debug.assert;
+const log = std.log.scoped(.codegen);
+const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const builtin = @import("builtin");
const link = @import("../../link.zig");
const Module = @import("../../Module.zig");
+const TypedValue = @import("../../TypedValue.zig");
const ErrorMsg = Module.ErrorMsg;
const Air = @import("../../Air.zig");
const Mir = @import("Mir.zig");
@@ -33,6 +36,11 @@ const InnerError = error{
OutOfRegisters,
};
+const RegisterView = enum(u1) {
+ caller,
+ callee,
+};
+
gpa: Allocator,
air: Air,
liveness: Liveness,
@@ -165,7 +173,7 @@ const StackAllocation = struct {
};
const BlockData = struct {
- relocs: std.ArrayListUnmanaged(Reloc),
+ relocs: std.ArrayListUnmanaged(Mir.Inst.Index),
/// The first break instruction encounters `null` here and chooses a
/// machine code value for the block result, populating this field.
/// Following break instructions encounter that value and use it for
@@ -173,18 +181,6 @@ const BlockData = struct {
mcv: MCValue,
};
-const Reloc = union(enum) {
- /// The value is an offset into the `Function` `code` from the beginning.
- /// To perform the reloc, write 32-bit signed little-endian integer
- /// which is a relative jump, based on the address following the reloc.
- rel32: usize,
- /// A branch in the ARM instruction set
- arm_branch: struct {
- pos: usize,
- cond: @import("../arm/bits.zig").Condition,
- },
-};
-
const CallMCValues = struct {
args: []MCValue,
return_value: MCValue,
@@ -245,7 +241,7 @@ pub fn generate(
defer function.blocks.deinit(bin_file.allocator);
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
- var call_info = function.resolveCallingConventionValues(fn_type, false) catch |err| switch (err) {
+ var call_info = function.resolveCallingConventionValues(fn_type, .callee) catch |err| switch (err) {
error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
error.OutOfRegisters => return FnResult{
.fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
@@ -298,92 +294,6 @@ pub fn generate(
}
}
-/// Caller must call `CallMCValues.deinit`.
-fn resolveCallingConventionValues(self: *Self, fn_ty: Type, is_caller: bool) !CallMCValues {
- const cc = fn_ty.fnCallingConvention();
- const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
- defer self.gpa.free(param_types);
- fn_ty.fnParamTypes(param_types);
- var result: CallMCValues = .{
- .args = try self.gpa.alloc(MCValue, param_types.len),
- // These undefined values must be populated before returning from this function.
- .return_value = undefined,
- .stack_byte_count = undefined,
- .stack_align = undefined,
- };
- errdefer self.gpa.free(result.args);
-
- const ret_ty = fn_ty.fnReturnType();
-
- switch (cc) {
- .Naked => {
- assert(result.args.len == 0);
- result.return_value = .{ .unreach = {} };
- result.stack_byte_count = 0;
- result.stack_align = 1;
- return result;
- },
- .Unspecified, .C => {
- // SPARC Compliance Definition 2.4.1, Chapter 3
- // Low-Level System Information (64-bit psABI) - Function Calling Sequence
-
- var next_register: usize = 0;
- var next_stack_offset: u32 = 0;
-
- // The caller puts the argument in %o0-%o5, which becomes %i0-%i5 inside the callee.
- const argument_registers = if (is_caller) abi.c_abi_int_param_regs_caller_view else abi.c_abi_int_param_regs_callee_view;
-
- for (param_types) |ty, i| {
- const param_size = @intCast(u32, ty.abiSize(self.target.*));
- if (param_size <= 8) {
- if (next_register < argument_registers.len) {
- result.args[i] = .{ .register = argument_registers[next_register] };
- next_register += 1;
- } else {
- result.args[i] = .{ .stack_offset = next_stack_offset };
- next_register += next_stack_offset;
- }
- } else if (param_size <= 16) {
- if (next_register < argument_registers.len - 1) {
- return self.fail("TODO MCValues with 2 registers", .{});
- } else if (next_register < argument_registers.len) {
- return self.fail("TODO MCValues split register + stack", .{});
- } else {
- result.args[i] = .{ .stack_offset = next_stack_offset };
- next_register += next_stack_offset;
- }
- } else {
- result.args[i] = .{ .stack_offset = next_stack_offset };
- next_register += next_stack_offset;
- }
- }
-
- result.stack_byte_count = next_stack_offset;
- result.stack_align = 16;
- },
- else => return self.fail("TODO implement function parameters for {} on sparcv9", .{cc}),
- }
-
- if (ret_ty.zigTypeTag() == .NoReturn) {
- result.return_value = .{ .unreach = {} };
- } else if (!ret_ty.hasRuntimeBits()) {
- result.return_value = .{ .none = {} };
- } else switch (cc) {
- .Naked => unreachable,
- .Unspecified, .C => {
- const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
- // The callee puts the return values in %i0-%i3, which becomes %o0-%o3 inside the caller.
- if (ret_ty_size <= 8) {
- result.return_value = if (is_caller) .{ .register = abi.c_abi_int_return_regs_caller_view[0] } else .{ .register = abi.c_abi_int_return_regs_callee_view[0] };
- } else {
- return self.fail("TODO support more return values for sparcv9", .{});
- }
- },
- else => return self.fail("TODO implement function return values for {} on sparcv9", .{cc}),
- }
- return result;
-}
-
fn gen(self: *Self) !void {
const cc = self.fn_type.fnCallingConvention();
if (cc != .Naked) {
@@ -519,7 +429,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul_with_overflow => @panic("TODO try self.airMulWithOverflow(inst)"),
.shl_with_overflow => @panic("TODO try self.airShlWithOverflow(inst)"),
- .div_float, .div_trunc, .div_floor, .div_exact => @panic("TODO try self.airDiv(inst)"),
+ .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
.cmp_lt => @panic("TODO try self.airCmp(inst, .lt)"),
.cmp_lte => @panic("TODO try self.airCmp(inst, .lte)"),
@@ -537,18 +447,18 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.shr, .shr_exact => @panic("TODO try self.airShr(inst)"),
.alloc => @panic("TODO try self.airAlloc(inst)"),
- .ret_ptr => @panic("TODO try self.airRetPtr(inst)"),
- .arg => @panic("TODO try self.airArg(inst)"),
- .assembly => @panic("TODO try self.airAsm(inst)"),
+ .ret_ptr => try self.airRetPtr(inst),
+ .arg => try self.airArg(inst),
+ .assembly => try self.airAsm(inst),
.bitcast => @panic("TODO try self.airBitCast(inst)"),
- .block => @panic("TODO try self.airBlock(inst)"),
+ .block => try self.airBlock(inst),
.br => @panic("TODO try self.airBr(inst)"),
.breakpoint => @panic("TODO try self.airBreakpoint()"),
.ret_addr => @panic("TODO try self.airRetAddr(inst)"),
.frame_addr => @panic("TODO try self.airFrameAddress(inst)"),
.fence => @panic("TODO try self.airFence()"),
.cond_br => @panic("TODO try self.airCondBr(inst)"),
- .dbg_stmt => @panic("TODO try self.airDbgStmt(inst)"),
+ .dbg_stmt => try self.airDbgStmt(inst),
.fptrunc => @panic("TODO try self.airFptrunc(inst)"),
.fpext => @panic("TODO try self.airFpext(inst)"),
.intcast => @panic("TODO try self.airIntCast(inst)"),
@@ -567,8 +477,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.not => @panic("TODO try self.airNot(inst)"),
.ptrtoint => @panic("TODO try self.airPtrToInt(inst)"),
.ret => @panic("TODO try self.airRet(inst)"),
- .ret_load => @panic("TODO try self.airRetLoad(inst)"),
- .store => @panic("TODO try self.airStore(inst)"),
+ .ret_load => try self.airRetLoad(inst),
+ .store => try self.airStore(inst),
.struct_field_ptr=> @panic("TODO try self.airStructFieldPtr(inst)"),
.struct_field_val=> @panic("TODO try self.airStructFieldVal(inst)"),
.array_to_slice => @panic("TODO try self.airArrayToSlice(inst)"),
@@ -600,20 +510,20 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.dbg_var_ptr,
.dbg_var_val,
- => @panic("TODO try self.airDbgVar(inst)"),
+ => try self.airDbgVar(inst),
.dbg_inline_begin,
.dbg_inline_end,
- => @panic("TODO try self.airDbgInline(inst)"),
+ => try self.airDbgInline(inst),
.dbg_block_begin,
.dbg_block_end,
- => @panic("TODO try self.airDbgBlock(inst)"),
+ => try self.airDbgBlock(inst),
- .call => @panic("TODO try self.airCall(inst, .auto)"),
+ .call => try self.airCall(inst, .auto),
.call_always_tail => @panic("TODO try self.airCall(inst, .always_tail)"),
.call_never_tail => @panic("TODO try self.airCall(inst, .never_tail)"),
- .call_never_inline => @panic("TODO try self.airCall(inst, .never_inline)"),
+ .call_never_inline => try self.airCall(inst, .never_inline),
.atomic_store_unordered => @panic("TODO try self.airAtomicStore(inst, .Unordered)"),
.atomic_store_monotonic => @panic("TODO try self.airAtomicStore(inst, .Monotonic)"),
@@ -627,7 +537,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),
- .switch_br => @panic("TODO try self.airSwitch(inst)"),
+ .switch_br => try self.airSwitch(inst),
.slice_ptr => @panic("TODO try self.airSlicePtr(inst)"),
.slice_len => @panic("TODO try self.airSliceLen(inst)"),
@@ -642,7 +552,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.constant => unreachable, // excluded from function bodies
.const_ty => unreachable, // excluded from function bodies
- .unreach => @panic("TODO self.finishAirBookkeeping()"),
+ .unreach => self.finishAirBookkeeping(),
.optional_payload => @panic("TODO try self.airOptionalPayload(inst)"),
.optional_payload_ptr => @panic("TODO try self.airOptionalPayloadPtr(inst)"),
@@ -670,6 +580,212 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
}
}
+fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const extra = self.air.extraData(Air.Asm, ty_pl.payload);
+ const is_volatile = (extra.data.flags & 0x80000000) != 0;
+ const clobbers_len = @truncate(u31, extra.data.flags);
+ var extra_i: usize = extra.end;
+ const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]);
+ extra_i += outputs.len;
+ const inputs = @bitCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]);
+ extra_i += inputs.len;
+
+ const dead = !is_volatile and self.liveness.isUnused(inst);
+ _ = dead;
+ _ = clobbers_len;
+
+ return self.fail("TODO implement asm for {}", .{self.target.cpu.arch});
+}
+
+fn airArg(self: *Self, inst: Air.Inst.Index) !void {
+ const arg_index = self.arg_index;
+ self.arg_index += 1;
+
+ const ty = self.air.typeOfIndex(inst);
+ _ = ty;
+
+ const result = self.args[arg_index];
+ // TODO support stack-only arguments
+ // TODO Copy registers to the stack
+ const mcv = result;
+
+ _ = try self.addInst(.{
+ .tag = .dbg_arg,
+ .data = .{
+ .dbg_arg_info = .{
+ .air_inst = inst,
+ .arg_index = arg_index,
+ },
+ },
+ });
+
+ if (self.liveness.isUnused(inst))
+ return self.finishAirBookkeeping();
+
+ switch (mcv) {
+ .register => |reg| {
+ self.register_manager.getRegAssumeFree(reg, inst);
+ },
+ else => {},
+ }
+
+ return self.finishAir(inst, mcv, .{ .none, .none, .none });
+}
+
+fn airBlock(self: *Self, inst: Air.Inst.Index) !void {
+ try self.blocks.putNoClobber(self.gpa, inst, .{
+ // A block is a setup to be able to jump to the end.
+ .relocs = .{},
+ // It also acts as a receptacle for break operands.
+ // Here we use `MCValue.none` to represent a null value so that the first
+ // break instruction will choose a MCValue for the block result and overwrite
+ // this field. Following break instructions will use that MCValue to put their
+ // block results.
+ .mcv = MCValue{ .none = {} },
+ });
+ defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa);
+
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const extra = self.air.extraData(Air.Block, ty_pl.payload);
+ const body = self.air.extra[extra.end..][0..extra.data.body_len];
+ try self.genBody(body);
+
+ // relocations for `bpcc` instructions
+ const relocs = &self.blocks.getPtr(inst).?.relocs;
+ if (relocs.items.len > 0 and relocs.items[relocs.items.len - 1] == self.mir_instructions.len - 1) {
+ // If the last Mir instruction is the last relocation (which
+ // would just jump one instruction further), it can be safely
+ // removed
+ self.mir_instructions.orderedRemove(relocs.pop());
+ }
+ for (relocs.items) |reloc| {
+ try self.performReloc(reloc);
+ }
+
+ const result = self.blocks.getPtr(inst).?.mcv;
+ return self.finishAir(inst, result, .{ .none, .none, .none });
+}
+
+fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !void {
+ if (modifier == .always_tail) return self.fail("TODO implement tail calls for {}", .{self.target.cpu.arch});
+
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const callee = pl_op.operand;
+ const extra = self.air.extraData(Air.Call, pl_op.payload);
+ const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end .. extra.end + extra.data.args_len]);
+ const ty = self.air.typeOf(callee);
+ const fn_ty = switch (ty.zigTypeTag()) {
+ .Fn => ty,
+ .Pointer => ty.childType(),
+ else => unreachable,
+ };
+
+ var info = try self.resolveCallingConventionValues(fn_ty, .caller);
+ defer info.deinit(self);
+ for (info.args) |mc_arg, arg_i| {
+ const arg = args[arg_i];
+ const arg_ty = self.air.typeOf(arg);
+ const arg_mcv = try self.resolveInst(arg);
+
+ switch (mc_arg) {
+ .none => continue,
+ .undef => unreachable,
+ .immediate => unreachable,
+ .unreach => unreachable,
+ .dead => unreachable,
+ .memory => unreachable,
+ .compare_flags_signed => unreachable,
+ .compare_flags_unsigned => unreachable,
+ .got_load => unreachable,
+ .direct_load => unreachable,
+ .register => |reg| {
+ try self.register_manager.getReg(reg, null);
+ try self.genSetReg(arg_ty, reg, arg_mcv);
+ },
+ .stack_offset => {
+ return self.fail("TODO implement calling with parameters in memory", .{});
+ },
+ .ptr_stack_offset => {
+ return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+ },
+ }
+ }
+
+ return self.fail("TODO implement call for {}", .{self.target.cpu.arch});
+}
+
+fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void {
+ // TODO emit debug info lexical block
+ return self.finishAir(inst, .dead, .{ .none, .none, .none });
+}
+
+fn airDbgInline(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const function = self.air.values[ty_pl.payload].castTag(.function).?.data;
+ // TODO emit debug info for function change
+ _ = function;
+ return self.finishAir(inst, .dead, .{ .none, .none, .none });
+}
+
+fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
+ const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
+
+ _ = try self.addInst(.{
+ .tag = .dbg_line,
+ .data = .{
+ .dbg_line_column = .{
+ .line = dbg_stmt.line,
+ .column = dbg_stmt.column,
+ },
+ },
+ });
+
+ return self.finishAirBookkeeping();
+}
+
+fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const name = self.air.nullTerminatedString(pl_op.payload);
+ const operand = pl_op.operand;
+ // TODO emit debug info for this variable
+ _ = name;
+ return self.finishAir(inst, .dead, .{ operand, .none, .none });
+}
+
+fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+}
+
+fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
+ _ = inst;
+ return self.fail("TODO implement airRetLoad for {}", .{self.target.cpu.arch});
+ //return self.finishAir(inst, .dead, .{ un_op, .none, .none });
+}
+
+fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
+ const stack_offset = try self.allocMemPtr(inst);
+ return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
+}
+
+fn airStore(self: *Self, inst: Air.Inst.Index) !void {
+ _ = self;
+ _ = inst;
+
+ return self.fail("TODO implement store for {}", .{self.target.cpu.arch});
+}
+
+fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
+ _ = self;
+ _ = inst;
+
+ return self.fail("TODO implement switch for {}", .{self.target.cpu.arch});
+}
+
+// Common helper functions
+
fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
const gpa = self.gpa;
@@ -680,6 +796,42 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
return result_index;
}
+fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u32 {
+ if (abi_align > self.stack_align)
+ self.stack_align = abi_align;
+ // TODO find a free slot instead of always appending
+ const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align);
+ self.next_stack_offset = offset + abi_size;
+ if (self.next_stack_offset > self.max_end_stack)
+ self.max_end_stack = self.next_stack_offset;
+ try self.stack.putNoClobber(self.gpa, offset, .{
+ .inst = inst,
+ .size = abi_size,
+ });
+ return offset;
+}
+
+/// Use a pointer instruction as the basis for allocating stack memory.
+fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
+ const elem_ty = self.air.typeOfIndex(inst).elemType();
+
+ if (!elem_ty.hasRuntimeBits()) {
+ // As this stack item will never be dereferenced at runtime,
+ // return the stack offset 0. Stack offset 0 will be where all
+ // zero-sized stack allocations live as non-zero-sized
+ // allocations will always have an offset > 0.
+ return @as(u32, 0);
+ }
+
+ const target = self.target.*;
+ const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
+ return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(target)});
+ };
+ // TODO swap this for inst.ty.ptrAlign
+ const abi_align = elem_ty.abiAlignment(self.target.*);
+ return self.allocMem(inst, abi_size, abi_align);
+}
+
fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
try table.ensureUnusedCapacity(self.gpa, additional_count);
@@ -691,3 +843,361 @@ fn fail(self: *Self, comptime format: []const u8, args: anytype) InnerError {
self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args);
return error.CodegenFail;
}
+
+/// Called when there are no operands, and the instruction is always unreferenced.
+fn finishAirBookkeeping(self: *Self) void {
+ if (std.debug.runtime_safety) {
+ self.air_bookkeeping += 1;
+ }
+}
+
+fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Liveness.bpi - 1]Air.Inst.Ref) void {
+ var tomb_bits = self.liveness.getTombBits(inst);
+ for (operands) |op| {
+ const dies = @truncate(u1, tomb_bits) != 0;
+ tomb_bits >>= 1;
+ if (!dies) continue;
+ const op_int = @enumToInt(op);
+ if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
+ const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len);
+ self.processDeath(op_index);
+ }
+ const is_used = @truncate(u1, tomb_bits) == 0;
+ if (is_used) {
+ log.debug("%{d} => {}", .{ inst, result });
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ branch.inst_table.putAssumeCapacityNoClobber(inst, result);
+
+ switch (result) {
+ .register => |reg| {
+ // In some cases (such as bitcast), an operand
+ // may be the same MCValue as the result. If
+ // that operand died and was a register, it
+ // was freed by processDeath. We have to
+ // "re-allocate" the register.
+ if (self.register_manager.isRegFree(reg)) {
+ self.register_manager.getRegAssumeFree(reg, inst);
+ }
+ },
+ else => {},
+ }
+ }
+ self.finishAirBookkeeping();
+}
+
+fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
+ if (typed_value.val.isUndef())
+ return MCValue{ .undef = {} };
+
+ if (typed_value.val.castTag(.decl_ref)) |payload| {
+ return self.lowerDeclRef(typed_value, payload.data);
+ }
+ if (typed_value.val.castTag(.decl_ref_mut)) |payload| {
+ return self.lowerDeclRef(typed_value, payload.data.decl);
+ }
+ const target = self.target.*;
+
+ switch (typed_value.ty.zigTypeTag()) {
+ .Pointer => switch (typed_value.ty.ptrSize()) {
+ .Slice => {
+ return self.lowerUnnamedConst(typed_value);
+ },
+ else => {
+ switch (typed_value.val.tag()) {
+ .int_u64 => {
+ return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) };
+ },
+ .slice => {
+ return self.lowerUnnamedConst(typed_value);
+ },
+ else => {
+ return self.fail("TODO codegen more kinds of const pointers: {}", .{typed_value.val.tag()});
+ },
+ }
+ },
+ },
+ .Int => {
+ const info = typed_value.ty.intInfo(self.target.*);
+ if (info.bits <= 64) {
+ const unsigned = switch (info.signedness) {
+ .signed => blk: {
+ const signed = typed_value.val.toSignedInt();
+ break :blk @bitCast(u64, signed);
+ },
+ .unsigned => typed_value.val.toUnsignedInt(target),
+ };
+
+ return MCValue{ .immediate = unsigned };
+ } else {
+ return self.lowerUnnamedConst(typed_value);
+ }
+ },
+ .Bool => {
+ return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) };
+ },
+ .ComptimeInt => unreachable, // semantic analysis prevents this
+ .ComptimeFloat => unreachable, // semantic analysis prevents this
+ .Optional => {
+ if (typed_value.ty.isPtrLikeOptional()) {
+ if (typed_value.val.isNull())
+ return MCValue{ .immediate = 0 };
+
+ var buf: Type.Payload.ElemType = undefined;
+ return self.genTypedValue(.{
+ .ty = typed_value.ty.optionalChild(&buf),
+ .val = typed_value.val,
+ });
+ } else if (typed_value.ty.abiSize(self.target.*) == 1) {
+ return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) };
+ }
+ return self.fail("TODO non pointer optionals", .{});
+ },
+ .Enum => {
+ if (typed_value.val.castTag(.enum_field_index)) |field_index| {
+ switch (typed_value.ty.tag()) {
+ .enum_simple => {
+ return MCValue{ .immediate = field_index.data };
+ },
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data;
+ if (enum_full.values.count() != 0) {
+ const tag_val = enum_full.values.keys()[field_index.data];
+ return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val });
+ } else {
+ return MCValue{ .immediate = field_index.data };
+ }
+ },
+ else => unreachable,
+ }
+ } else {
+ var int_tag_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer);
+ return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val });
+ }
+ },
+ .ErrorSet => {
+ const err_name = typed_value.val.castTag(.@"error").?.data.name;
+ const module = self.bin_file.options.module.?;
+ const global_error_set = module.global_error_set;
+ const error_index = global_error_set.get(err_name).?;
+ return MCValue{ .immediate = error_index };
+ },
+ .ErrorUnion => {
+ const error_type = typed_value.ty.errorUnionSet();
+ const payload_type = typed_value.ty.errorUnionPayload();
+
+ if (typed_value.val.castTag(.eu_payload)) |pl| {
+ if (!payload_type.hasRuntimeBits()) {
+ // We use the error type directly as the type.
+ return MCValue{ .immediate = 0 };
+ }
+
+ _ = pl;
+ return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty.fmtDebug()});
+ } else {
+ if (!payload_type.hasRuntimeBits()) {
+ // We use the error type directly as the type.
+ return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val });
+ }
+
+ return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty.fmtDebug()});
+ }
+ },
+ .Struct => {
+ return self.lowerUnnamedConst(typed_value);
+ },
+ else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty.fmtDebug()}),
+ }
+}
+
+fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
+ // Treat each stack item as a "layer" on top of the previous one.
+ var i: usize = self.branch_stack.items.len;
+ while (true) {
+ i -= 1;
+ if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| {
+ assert(mcv != .dead);
+ return mcv;
+ }
+ }
+}
+
+fn performReloc(self: *Self, inst: Mir.Inst.Index) !void {
+ const tag = self.mir_instructions.items(.tag)[inst];
+ switch (tag) {
+ .bpcc => self.mir_instructions.items(.data)[inst].branch_predict.inst = @intCast(Mir.Inst.Index, self.mir_instructions.len),
+ else => unreachable,
+ }
+}
+
+/// Asserts there is already capacity to insert into top branch inst_table.
+fn processDeath(self: *Self, inst: Air.Inst.Index) void {
+ const air_tags = self.air.instructions.items(.tag);
+ if (air_tags[inst] == .constant) return; // Constants are immortal.
+ // When editing this function, note that the logic must synchronize with `reuseOperand`.
+ const prev_value = self.getResolvedInstValue(inst);
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ branch.inst_table.putAssumeCapacity(inst, .dead);
+ switch (prev_value) {
+ .register => |reg| {
+ self.register_manager.freeReg(reg);
+ },
+ else => {}, // TODO process stack allocation death
+ }
+}
+
+/// Caller must call `CallMCValues.deinit`.
+fn resolveCallingConventionValues(self: *Self, fn_ty: Type, role: RegisterView) !CallMCValues {
+ const cc = fn_ty.fnCallingConvention();
+ const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
+ defer self.gpa.free(param_types);
+ fn_ty.fnParamTypes(param_types);
+ var result: CallMCValues = .{
+ .args = try self.gpa.alloc(MCValue, param_types.len),
+ // These undefined values must be populated before returning from this function.
+ .return_value = undefined,
+ .stack_byte_count = undefined,
+ .stack_align = undefined,
+ };
+ errdefer self.gpa.free(result.args);
+
+ const ret_ty = fn_ty.fnReturnType();
+
+ switch (cc) {
+ .Naked => {
+ assert(result.args.len == 0);
+ result.return_value = .{ .unreach = {} };
+ result.stack_byte_count = 0;
+ result.stack_align = 1;
+ return result;
+ },
+ .Unspecified, .C => {
+ // SPARC Compliance Definition 2.4.1, Chapter 3
+ // Low-Level System Information (64-bit psABI) - Function Calling Sequence
+
+ var next_register: usize = 0;
+ var next_stack_offset: u32 = 0;
+
+ // The caller puts the argument in %o0-%o5, which becomes %i0-%i5 inside the callee.
+ const argument_registers = switch (role) {
+ .caller => abi.c_abi_int_param_regs_caller_view,
+ .callee => abi.c_abi_int_param_regs_callee_view,
+ };
+
+ for (param_types) |ty, i| {
+ const param_size = @intCast(u32, ty.abiSize(self.target.*));
+ if (param_size <= 8) {
+ if (next_register < argument_registers.len) {
+ result.args[i] = .{ .register = argument_registers[next_register] };
+ next_register += 1;
+ } else {
+ result.args[i] = .{ .stack_offset = next_stack_offset };
+ next_register += next_stack_offset;
+ }
+ } else if (param_size <= 16) {
+ if (next_register < argument_registers.len - 1) {
+ return self.fail("TODO MCValues with 2 registers", .{});
+ } else if (next_register < argument_registers.len) {
+ return self.fail("TODO MCValues split register + stack", .{});
+ } else {
+ result.args[i] = .{ .stack_offset = next_stack_offset };
+ next_register += next_stack_offset;
+ }
+ } else {
+ result.args[i] = .{ .stack_offset = next_stack_offset };
+ next_register += next_stack_offset;
+ }
+ }
+
+ result.stack_byte_count = next_stack_offset;
+ result.stack_align = 16;
+
+ if (ret_ty.zigTypeTag() == .NoReturn) {
+ result.return_value = .{ .unreach = {} };
+ } else if (!ret_ty.hasRuntimeBits()) {
+ result.return_value = .{ .none = {} };
+ } else {
+ const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
+ // The callee puts the return values in %i0-%i3, which becomes %o0-%o3 inside the caller.
+ if (ret_ty_size <= 8) {
+ result.return_value = switch (role) {
+ .caller => .{ .register = abi.c_abi_int_return_regs_caller_view[0] },
+ .callee => .{ .register = abi.c_abi_int_return_regs_callee_view[0] },
+ };
+ } else {
+ return self.fail("TODO support more return values for sparcv9", .{});
+ }
+ }
+ },
+ else => return self.fail("TODO implement function parameters for {} on sparcv9", .{cc}),
+ }
+
+ return result;
+}
+
+fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
+ // First section of indexes correspond to a set number of constant values.
+ const ref_int = @enumToInt(inst);
+ if (ref_int < Air.Inst.Ref.typed_value_map.len) {
+ const tv = Air.Inst.Ref.typed_value_map[ref_int];
+ if (!tv.ty.hasRuntimeBits()) {
+ return MCValue{ .none = {} };
+ }
+ return self.genTypedValue(tv);
+ }
+
+ // If the type has no codegen bits, no need to store it.
+ const inst_ty = self.air.typeOf(inst);
+ if (!inst_ty.hasRuntimeBits())
+ return MCValue{ .none = {} };
+
+ const inst_index = @intCast(Air.Inst.Index, ref_int - Air.Inst.Ref.typed_value_map.len);
+ switch (self.air.instructions.items(.tag)[inst_index]) {
+ .constant => {
+ // Constants have static lifetimes, so they are always memoized in the outer most table.
+ const branch = &self.branch_stack.items[0];
+ const gop = try branch.inst_table.getOrPut(self.gpa, inst_index);
+ if (!gop.found_existing) {
+ const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
+ gop.value_ptr.* = try self.genTypedValue(.{
+ .ty = inst_ty,
+ .val = self.air.values[ty_pl.payload],
+ });
+ }
+ return gop.value_ptr.*;
+ },
+ .const_ty => unreachable,
+ else => return self.getResolvedInstValue(inst_index),
+ }
+}
+
+fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
+ if (!self.liveness.operandDies(inst, op_index))
+ return false;
+
+ switch (mcv) {
+ .register => |reg| {
+ // If it's in the registers table, need to associate the register with the
+ // new instruction.
+ if (RegisterManager.indexOfRegIntoTracked(reg)) |index| {
+ if (!self.register_manager.isRegFree(reg)) {
+ self.register_manager.registers[index] = inst;
+ }
+ }
+ log.debug("%{d} => {} (reused)", .{ inst, reg });
+ },
+ .stack_offset => |off| {
+ log.debug("%{d} => stack offset {d} (reused)", .{ inst, off });
+ },
+ else => return false,
+ }
+
+ // Prevent the operand deaths processing code from deallocating it.
+ self.liveness.clearOperandDeath(inst, op_index);
+
+ // That makes us responsible for doing the rest of the stuff that processDeath would have done.
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ branch.inst_table.putAssumeCapacity(Air.refToIndex(operand).?, .dead);
+
+ return true;
+}
src/arch/sparcv9/Emit.zig
@@ -42,16 +42,23 @@ pub fn emitMir(
for (mir_tags) |tag, index| {
const inst = @intCast(u32, index);
switch (tag) {
+ .dbg_arg => try emit.mirDbgArg(inst),
.dbg_line => try emit.mirDbgLine(inst),
.dbg_prologue_end => try emit.mirDebugPrologueEnd(),
.dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(),
- .nop => @panic("TODO implement nop"),
+ .bpcc => @panic("TODO implement sparcv9 bpcc"),
- .save => @panic("TODO implement save"),
- .restore => @panic("TODO implement restore"),
+ .call => @panic("TODO implement sparcv9 call"),
- .@"return" => @panic("TODO implement return"),
+ .jmpl => @panic("TODO implement sparcv9 jmpl"),
+
+ .nop => @panic("TODO implement sparcv9 nop"),
+
+ .@"return" => @panic("TODO implement sparcv9 return"),
+
+ .save => @panic("TODO implement sparcv9 save"),
+ .restore => @panic("TODO implement sparcv9 restore"),
}
}
}
@@ -111,6 +118,17 @@ fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
}
}
+fn mirDbgArg(emit: *Emit, inst: Mir.Inst.Index) !void {
+ const tag = emit.mir.instructions.items(.tag)[inst];
+ const dbg_arg_info = emit.mir.instructions.items(.data)[inst].dbg_arg_info;
+ _ = dbg_arg_info;
+
+ switch (tag) {
+ .dbg_arg => {}, // TODO try emit.genArgDbgInfo(dbg_arg_info.air_inst, dbg_arg_info.arg_index),
+ else => unreachable,
+ }
+}
+
fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const dbg_line_column = emit.mir.instructions.items(.data)[inst].dbg_line_column;
src/arch/sparcv9/Mir.zig
@@ -7,9 +7,14 @@
//! so that, for example, the smaller encodings of jump instructions can be used.
const std = @import("std");
+const builtin = @import("builtin");
+const assert = std.debug.assert;
const Mir = @This();
const bits = @import("bits.zig");
+const Air = @import("../../Air.zig");
+
+const Instruction = bits.Instruction;
const Register = bits.Register;
instructions: std.MultiArrayList(Inst).Slice,
@@ -23,6 +28,8 @@ pub const Inst = struct {
data: Data,
pub const Tag = enum(u16) {
+ /// Pseudo-instruction: Argument
+ dbg_arg,
/// Pseudo-instruction: End of prologue
dbg_prologue_end,
/// Pseudo-instruction: Beginning of epilogue
@@ -33,6 +40,18 @@ pub const Inst = struct {
// All the real instructions are ordered by their section number
// in The SPARC Architecture Manual, Version 9.
+ /// A.7 Branch on Integer Condition Codes with Prediction (BPcc)
+ /// It uses the branch_predict field.
+ bpcc,
+
+ /// A.8 Call and Link
+ /// It uses the branch_link field.
+ call,
+
+ /// A.24 Jump and Link
+ /// It uses the branch_link field.
+ jmpl,
+
/// A.40 No Operation
/// It uses the nop field.
nop,
@@ -50,23 +69,33 @@ pub const Inst = struct {
/// The position of an MIR instruction within the `Mir` instructions array.
pub const Index = u32;
- /// All instructions have a 4-byte payload, which is contained within
+ /// All instructions have a 8-byte payload, which is contained within
/// this union. `Tag` determines which union field is active, as well as
/// how to interpret the data within.
pub const Data = union {
- /// No additional data
+ /// Debug info: argument
///
- /// Used by e.g. flushw
- nop: void,
+ /// Used by e.g. dbg_arg
+ dbg_arg_info: struct {
+ air_inst: Air.Inst.Index,
+ arg_index: usize,
+ },
- /// Three operand arithmetic.
+ /// Debug info: line and column
+ ///
+ /// Used by e.g. dbg_line
+ dbg_line_column: struct {
+ line: u32,
+ column: u32,
+ },
+
+ /// Two operand arithmetic.
/// if is_imm true then it uses the imm field of rs2_or_imm,
/// otherwise it uses rs2 field.
///
- /// Used by e.g. add, sub
- arithmetic_3op: struct {
+ /// Used by e.g. return
+ arithmetic_2op: struct {
is_imm: bool,
- rd: Register,
rs1: Register,
rs2_or_imm: union {
rs2: Register,
@@ -74,13 +103,14 @@ pub const Inst = struct {
},
},
- /// Two operand arithmetic.
+ /// Three operand arithmetic.
/// if is_imm true then it uses the imm field of rs2_or_imm,
/// otherwise it uses rs2 field.
///
- /// Used by e.g. return
- arithmetic_2op: struct {
+ /// Used by e.g. add, sub
+ arithmetic_3op: struct {
is_imm: bool,
+ rd: Register,
rs1: Register,
rs2_or_imm: union {
rs2: Register,
@@ -88,13 +118,27 @@ pub const Inst = struct {
},
},
- /// Debug info: line and column
- ///
- /// Used by e.g. dbg_line
- dbg_line_column: struct {
- line: u32,
- column: u32,
+ /// Branch and link (always unconditional).
+ /// Used by e.g. call
+ branch_link: struct {
+ inst: Index,
+ link: Register,
+ },
+
+ /// Branch with prediction.
+ /// Used by e.g. bpcc
+ branch_predict: struct {
+ annul: bool,
+ pt: bool,
+ ccr: Instruction.CCR,
+ cond: Instruction.Condition,
+ inst: Index,
},
+
+ /// No additional data
+ ///
+ /// Used by e.g. flushw
+ nop: void,
};
};