Merge pull request #11434 from koachan/sparc64-codegen

This commit is contained in:
Jakub Konka 2022-04-14 21:38:35 +02:00 committed by GitHub
commit 2635e4ca6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 2450 additions and 76 deletions

View File

@ -716,6 +716,9 @@ pub const CompilerBackend = enum(u64) {
/// The reference implementation self-hosted compiler of Zig, using the
/// riscv64 backend.
stage2_riscv64 = 9,
/// The reference implementation self-hosted compiler of Zig, using the
/// sparcv9 backend.
stage2_sparcv9 = 10,
_,
};
@ -761,7 +764,8 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn
builtin.zig_backend == .stage2_aarch64 or
builtin.zig_backend == .stage2_x86_64 or
builtin.zig_backend == .stage2_x86 or
builtin.zig_backend == .stage2_riscv64)
builtin.zig_backend == .stage2_riscv64 or
builtin.zig_backend == .stage2_sparcv9)
{
while (true) {
@breakpoint();

View File

@ -29,6 +29,7 @@ comptime {
builtin.zig_backend == .stage2_aarch64 or
builtin.zig_backend == .stage2_arm or
builtin.zig_backend == .stage2_riscv64 or
builtin.zig_backend == .stage2_sparcv9 or
(builtin.zig_backend == .stage2_llvm and native_os != .linux) or
(builtin.zig_backend == .stage2_llvm and native_arch != .x86_64))
{
@ -165,6 +166,14 @@ fn exit2(code: usize) noreturn {
: "rcx", "r11", "memory"
);
},
.sparcv9 => {
asm volatile ("ta 0x6d"
:
: [number] "{g1}" (1),
[arg1] "{o0}" (code),
: "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory"
);
},
else => @compileError("TODO"),
},
// exits(0)

View File

@ -4531,6 +4531,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
.i386 => .stage2_x86,
.aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64,
.riscv64 => .stage2_riscv64,
.sparcv9 => .stage2_sparcv9,
else => .other,
};
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,298 @@
//! This file contains the functionality for lowering SPARCv9 MIR into
//! machine code
const std = @import("std");
const Endian = std.builtin.Endian;
const assert = std.debug.assert;
const link = @import("../../link.zig");
const Module = @import("../../Module.zig");
const ErrorMsg = Module.ErrorMsg;
const Liveness = @import("../../Liveness.zig");
const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
const DW = std.dwarf;
const leb128 = std.leb;
const Emit = @This();
const Mir = @import("Mir.zig");
const bits = @import("bits.zig");
const Instruction = bits.Instruction;
const Register = bits.Register;
mir: Mir,
bin_file: *link.File,
debug_output: DebugInfoOutput,
target: *const std.Target,
err_msg: ?*ErrorMsg = null,
src_loc: Module.SrcLoc,
code: *std.ArrayList(u8),
prev_di_line: u32,
prev_di_column: u32,
/// Relative to the beginning of `code`.
prev_di_pc: usize,
const InnerError = error{
OutOfMemory,
EmitFail,
};
pub fn emitMir(
emit: *Emit,
) InnerError!void {
const mir_tags = emit.mir.instructions.items(.tag);
// Emit machine code
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(),
.add => try emit.mirArithmetic3Op(inst),
.bpcc => @panic("TODO implement sparcv9 bpcc"),
.call => @panic("TODO implement sparcv9 call"),
.jmpl => @panic("TODO implement sparcv9 jmpl"),
.jmpl_i => @panic("TODO implement sparcv9 jmpl to reg"),
.ldub => try emit.mirArithmetic3Op(inst),
.lduh => try emit.mirArithmetic3Op(inst),
.lduw => try emit.mirArithmetic3Op(inst),
.ldx => try emit.mirArithmetic3Op(inst),
.@"or" => try emit.mirArithmetic3Op(inst),
.nop => try emit.mirNop(),
.@"return" => try emit.mirArithmetic2Op(inst),
.save => try emit.mirArithmetic3Op(inst),
.restore => try emit.mirArithmetic3Op(inst),
.sethi => try emit.mirSethi(inst),
.sllx => @panic("TODO implement sparcv9 sllx"),
.sub => try emit.mirArithmetic3Op(inst),
.tcc => try emit.mirTrap(inst),
}
}
}
pub fn deinit(emit: *Emit) void {
emit.* = undefined;
}
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;
switch (tag) {
.dbg_line => try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column),
else => unreachable,
}
}
fn mirDebugPrologueEnd(self: *Emit) !void {
switch (self.debug_output) {
.dwarf => |dbg_out| {
try dbg_out.dbg_line.append(DW.LNS.set_prologue_end);
try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
fn mirDebugEpilogueBegin(self: *Emit) !void {
switch (self.debug_output) {
.dwarf => |dbg_out| {
try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin);
try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
fn mirArithmetic2Op(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const data = emit.mir.instructions.items(.data)[inst].arithmetic_2op;
const rs1 = data.rs1;
if (data.is_imm) {
const imm = data.rs2_or_imm.imm;
switch (tag) {
.@"return" => try emit.writeInstruction(Instruction.@"return"(i13, rs1, imm)),
else => unreachable,
}
} else {
const rs2 = data.rs2_or_imm.rs2;
switch (tag) {
.@"return" => try emit.writeInstruction(Instruction.@"return"(Register, rs1, rs2)),
else => unreachable,
}
}
}
fn mirArithmetic3Op(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const data = emit.mir.instructions.items(.data)[inst].arithmetic_3op;
const rd = data.rd;
const rs1 = data.rs1;
if (data.is_imm) {
const imm = data.rs2_or_imm.imm;
switch (tag) {
.add => try emit.writeInstruction(Instruction.add(i13, rs1, imm, rd)),
.ldub => try emit.writeInstruction(Instruction.ldub(i13, rs1, imm, rd)),
.lduh => try emit.writeInstruction(Instruction.lduh(i13, rs1, imm, rd)),
.lduw => try emit.writeInstruction(Instruction.lduw(i13, rs1, imm, rd)),
.ldx => try emit.writeInstruction(Instruction.ldx(i13, rs1, imm, rd)),
.@"or" => try emit.writeInstruction(Instruction.@"or"(i13, rs1, imm, rd)),
.save => try emit.writeInstruction(Instruction.save(i13, rs1, imm, rd)),
.restore => try emit.writeInstruction(Instruction.restore(i13, rs1, imm, rd)),
.sub => try emit.writeInstruction(Instruction.sub(i13, rs1, imm, rd)),
else => unreachable,
}
} else {
const rs2 = data.rs2_or_imm.rs2;
switch (tag) {
.add => try emit.writeInstruction(Instruction.add(Register, rs1, rs2, rd)),
.ldub => try emit.writeInstruction(Instruction.ldub(Register, rs1, rs2, rd)),
.lduh => try emit.writeInstruction(Instruction.lduh(Register, rs1, rs2, rd)),
.lduw => try emit.writeInstruction(Instruction.lduw(Register, rs1, rs2, rd)),
.ldx => try emit.writeInstruction(Instruction.ldx(Register, rs1, rs2, rd)),
.@"or" => try emit.writeInstruction(Instruction.@"or"(Register, rs1, rs2, rd)),
.save => try emit.writeInstruction(Instruction.save(Register, rs1, rs2, rd)),
.restore => try emit.writeInstruction(Instruction.restore(Register, rs1, rs2, rd)),
.sub => try emit.writeInstruction(Instruction.sub(Register, rs1, rs2, rd)),
else => unreachable,
}
}
}
fn mirNop(emit: *Emit) !void {
try emit.writeInstruction(Instruction.nop());
}
fn mirSethi(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const data = emit.mir.instructions.items(.data)[inst].sethi;
const imm = data.imm;
const rd = data.rd;
assert(tag == .sethi);
try emit.writeInstruction(Instruction.sethi(imm, rd));
}
fn mirTrap(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const data = emit.mir.instructions.items(.data)[inst].trap;
const cond = data.cond;
const ccr = data.ccr;
const rs1 = data.rs1;
if (data.is_imm) {
const imm = data.rs2_or_imm.imm;
switch (tag) {
.tcc => try emit.writeInstruction(Instruction.trap(u7, cond, ccr, rs1, imm)),
else => unreachable,
}
} else {
const rs2 = data.rs2_or_imm.rs2;
switch (tag) {
.tcc => try emit.writeInstruction(Instruction.trap(Register, cond, ccr, rs1, rs2)),
else => unreachable,
}
}
}
// Common helper functions
fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line);
const delta_pc: usize = self.code.items.len - self.prev_di_pc;
switch (self.debug_output) {
.dwarf => |dbg_out| {
// TODO Look into using the DWARF special opcodes to compress this data.
// It lets you emit single-byte opcodes that add different numbers to
// both the PC and the line number at the same time.
try dbg_out.dbg_line.ensureUnusedCapacity(11);
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable;
if (delta_line != 0) {
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line);
leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable;
}
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy);
self.prev_di_pc = self.code.items.len;
self.prev_di_line = line;
self.prev_di_column = column;
self.prev_di_pc = self.code.items.len;
},
.plan9 => |dbg_out| {
if (delta_pc <= 0) return; // only do this when the pc changes
// we have already checked the target in the linker to make sure it is compatable
const quant = @import("../../link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable;
// increasing the line number
try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line);
// increasing the pc
const d_pc_p9 = @intCast(i64, delta_pc) - quant;
if (d_pc_p9 > 0) {
// minus one because if its the last one, we want to leave space to change the line which is one quanta
try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant);
if (dbg_out.pcop_change_index.*) |pci|
dbg_out.dbg_line.items[pci] += 1;
dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1);
} else if (d_pc_p9 == 0) {
// we don't need to do anything, because adding the quant does it for us
} else unreachable;
if (dbg_out.start_line.* == null)
dbg_out.start_line.* = self.prev_di_line;
dbg_out.end_line.* = line;
// only do this if the pc changed
self.prev_di_line = line;
self.prev_di_column = column;
self.prev_di_pc = self.code.items.len;
},
.none => {},
}
}
fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
assert(emit.err_msg == null);
emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args);
return error.EmitFail;
}
fn writeInstruction(emit: *Emit, instruction: Instruction) !void {
// SPARCv9 instructions are always arranged in BE regardless of the
// endianness mode the CPU is running in (Section 3.1 of the ISA specification).
// This is to ease porting in case someone wants to do a LE SPARCv9 backend.
const endian = Endian.Big;
std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian);
}

View File

@ -6,6 +6,264 @@
//! The main purpose of MIR is to postpone the assignment of offsets until Isel,
//! 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,
/// The meaning of this data is determined by `Inst.Tag` value.
extra: []const u32,
pub const Inst = struct {
tag: Tag,
/// The meaning of this depends on `tag`.
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
dbg_epilogue_begin,
/// Pseudo-instruction: Update debug line
dbg_line,
// All the real instructions are ordered by their section number
// in The SPARC Architecture Manual, Version 9.
/// A.2 Add
/// Those uses the arithmetic_3op field.
// TODO add other operations.
add,
/// 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
/// jmpl (far direct jump) uses the branch_link field,
/// while jmpl_i (indirect jump) uses the branch_link_indirect field.
/// Those two MIR instructions will be lowered into SPARCv9 jmpl instruction.
jmpl,
jmpl_i,
/// A.27 Load Integer
/// Those uses the arithmetic_3op field.
/// Note that the ldd variant of this instruction is deprecated, so do not emit
/// it unless specifically requested (e.g. by inline assembly).
// TODO add other operations.
ldub,
lduh,
lduw,
ldx,
/// A.31 Logical Operations
/// Those uses the arithmetic_3op field.
// TODO add other operations.
@"or",
/// A.40 No Operation
/// It uses the nop field.
nop,
/// A.45 RETURN
/// It uses the arithmetic_2op field.
@"return",
/// A.46 SAVE and RESTORE
/// Those uses the arithmetic_3op field.
save,
restore,
/// A.48 SETHI
/// It uses the sethi field.
sethi,
/// A.49 Shift
/// Those uses the shift field.
// TODO add other operations.
sllx,
/// A.56 Subtract
/// Those uses the arithmetic_3op field.
// TODO add other operations.
sub,
/// A.61 Trap on Integer Condition Codes (Tcc)
/// It uses the trap field.
tcc,
};
/// The position of an MIR instruction within the `Mir` instructions array.
pub const Index = u32;
/// 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.
// TODO this is a quick-n-dirty solution that needs to be cleaned up.
pub const Data = union {
/// Debug info: argument
///
/// Used by e.g. dbg_arg
dbg_arg_info: struct {
air_inst: Air.Inst.Index,
arg_index: usize,
},
/// 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. return
arithmetic_2op: struct {
is_imm: bool,
rs1: Register,
rs2_or_imm: union {
rs2: Register,
imm: i13,
},
},
/// 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. add, sub
arithmetic_3op: struct {
is_imm: bool,
rd: Register,
rs1: Register,
rs2_or_imm: union {
rs2: Register,
imm: i13,
},
},
/// Branch and link (always unconditional).
/// Used by e.g. call
branch_link: struct {
inst: Index,
link: Register = .o7,
},
/// Indirect branch and link (always unconditional).
/// Used by e.g. jmpl_i
branch_link_indirect: struct {
reg: Register,
link: Register = .o7,
},
/// Branch with prediction.
/// Used by e.g. bpcc
branch_predict: struct {
annul: bool = false,
pt: bool = true,
ccr: Instruction.CCR,
cond: Instruction.Condition,
inst: Index,
},
/// No additional data
///
/// Used by e.g. flushw
nop: void,
/// SETHI operands.
///
/// Used by sethi
sethi: struct {
rd: Register,
imm: u22,
},
/// Shift operands.
/// 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
shift: struct {
is_imm: bool,
width: Instruction.ShiftWidth,
rd: Register,
rs1: Register,
rs2_or_imm: union {
rs2: Register,
imm: u6,
},
},
/// Trap.
/// if is_imm true then it uses the imm field of rs2_or_imm,
/// otherwise it uses rs2 field.
///
/// Used by e.g. tcc
trap: struct {
is_imm: bool = true,
cond: Instruction.Condition,
ccr: Instruction.CCR = .icc,
rs1: Register = .g0,
rs2_or_imm: union {
rs2: Register,
imm: u7,
},
},
};
// Make sure we don't accidentally make instructions bigger than expected.
// Note that in Debug builds, Zig is allowed to insert a secret field for safety checks.
comptime {
if (builtin.mode != .Debug) {
// TODO clean up the definition of Data before enabling this.
// I'll do that after the PoC backend can produce usable binaries.
// assert(@sizeOf(Data) == 8);
}
}
};
pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
mir.instructions.deinit(gpa);
gpa.free(mir.extra);
mir.* = undefined;
}
/// Returns the requested data, as well as the new index which is at the start of the
/// trailers for the object.
pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
const fields = std.meta.fields(T);
var i: usize = index;
var result: T = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.field_type) {
u32 => mir.extra[i],
i32 => @bitCast(i32, mir.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
}
return .{
.data = result,
.end = i,
};
}

View File

@ -1,12 +1,25 @@
const bits = @import("bits.zig");
const Register = bits.Register;
// Register windowing mechanism will take care of preserving registers
// so no need to do it manually
pub const callee_preserved_regs = [_]Register{};
// There are no callee-preserved registers since the windowing
// mechanism already takes care of them.
// We still need to preserve %o0-%o5, %g1, %g4, and %g5 before calling
// something, though, as those are shared with the callee and might be
// thrashed by it.
pub const caller_preserved_regs = [_]Register{ .o0, .o1, .o2, .o3, .o4, .o5, .g1, .g4, .g5 };
// Try to allocate i, l, o, then g sets of registers, in order of priority.
pub const allocatable_regs = [_]Register{
// zig fmt: off
.@"i0", .@"i1", .@"i2", .@"i3", .@"i4", .@"i5",
.l0, .l1, .l2, .l3, .l4, .l5, .l6, .l7,
.o0, .o1, .o2, .o3, .o4, .o5,
.g1, .g4, .g5,
// zig fmt: on
};
pub const c_abi_int_param_regs_caller_view = [_]Register{ .o0, .o1, .o2, .o3, .o4, .o5 };
pub const c_abi_int_param_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3", .@"i4", .@"i5" };
pub const c_abi_int_return_regs_caller_view = [_]Register{ .o0, .o1, .o2, .o3, .o4, .o5 };
pub const c_abi_int_return_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3", .@"i4", .@"i5" };
pub const c_abi_int_return_regs_caller_view = [_]Register{ .o0, .o1, .o2, .o3 };
pub const c_abi_int_return_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3" };

View File

@ -164,27 +164,33 @@ pub const Instruction = union(enum) {
// name them with letters since there's no official naming scheme.
// TODO: need to rename the minor formats to a more descriptive name.
// I am using regular structs instead of packed ones to avoid
// endianness-dependent behavior when constructing the actual
// assembly instructions.
// See also: https://github.com/ziglang/zig/issues/10113
// TODO: change it back to packed structs once the issue is resolved.
// Format 1 (op = 1): CALL
format_1: packed struct {
format_1: struct {
op: u2 = 0b01,
disp30: u30,
},
// Format 2 (op = 0): SETHI & Branches (Bicc, BPcc, BPr, FBfcc, FBPfcc)
format_2a: packed struct {
format_2a: struct {
op: u2 = 0b00,
rd: u5,
op2: u3,
imm22: u22,
},
format_2b: packed struct {
format_2b: struct {
op: u2 = 0b00,
a: u1,
cond: u4,
op2: u3,
disp22: u22,
},
format_2c: packed struct {
format_2c: struct {
op: u2 = 0b00,
a: u1,
cond: u4,
@ -194,7 +200,7 @@ pub const Instruction = union(enum) {
p: u1,
disp19: u19,
},
format_2d: packed struct {
format_2d: struct {
op: u2 = 0b00,
a: u1,
fixed: u1 = 0b0,
@ -207,7 +213,7 @@ pub const Instruction = union(enum) {
},
// Format 3 (op = 2 or 3): Arithmetic, Logical, MOVr, MEMBAR, Load, and Store
format_3a: packed struct {
format_3a: struct {
op: u2,
rd: u5,
op3: u6,
@ -224,7 +230,7 @@ pub const Instruction = union(enum) {
i: u1 = 0b1,
simm13: u13,
},
format_3c: packed struct {
format_3c: struct {
op: u2,
reserved1: u5 = 0b00000,
op3: u6,
@ -241,7 +247,7 @@ pub const Instruction = union(enum) {
i: u1 = 0b1,
simm13: u13,
},
format_3e: packed struct {
format_3e: struct {
op: u2,
rd: u5,
op3: u6,
@ -260,7 +266,7 @@ pub const Instruction = union(enum) {
rcond: u3,
simm10: u10,
},
format_3g: packed struct {
format_3g: struct {
op: u2,
rd: u5,
op3: u6,
@ -269,7 +275,7 @@ pub const Instruction = union(enum) {
reserved: u8 = 0b00000000,
rs2: u5,
},
format_3h: packed struct {
format_3h: struct {
op: u2 = 0b10,
fixed1: u5 = 0b00000,
op3: u6 = 0b101000,
@ -279,7 +285,7 @@ pub const Instruction = union(enum) {
cmask: u3,
mmask: u4,
},
format_3i: packed struct {
format_3i: struct {
op: u2,
rd: u5,
op3: u6,
@ -288,13 +294,13 @@ pub const Instruction = union(enum) {
imm_asi: u8,
rs2: u5,
},
format_3j: packed struct {
format_3j: struct {
op: u2,
impl_dep1: u5,
op3: u6,
impl_dep2: u19,
},
format_3k: packed struct {
format_3k: struct {
op: u2,
rd: u5,
op3: u6,
@ -304,7 +310,7 @@ pub const Instruction = union(enum) {
reserved: u7 = 0b0000000,
rs2: u5,
},
format_3l: packed struct {
format_3l: struct {
op: u2,
rd: u5,
op3: u6,
@ -314,7 +320,7 @@ pub const Instruction = union(enum) {
reserved: u7 = 0b0000000,
shcnt32: u5,
},
format_3m: packed struct {
format_3m: struct {
op: u2,
rd: u5,
op3: u6,
@ -324,7 +330,7 @@ pub const Instruction = union(enum) {
reserved: u6 = 0b000000,
shcnt64: u6,
},
format_3n: packed struct {
format_3n: struct {
op: u2,
rd: u5,
op3: u6,
@ -332,7 +338,7 @@ pub const Instruction = union(enum) {
opf: u9,
rs2: u5,
},
format_3o: packed struct {
format_3o: struct {
op: u2,
fixed: u3 = 0b000,
cc1: u1,
@ -342,7 +348,7 @@ pub const Instruction = union(enum) {
opf: u9,
rs2: u5,
},
format_3p: packed struct {
format_3p: struct {
op: u2,
rd: u5,
op3: u6,
@ -350,20 +356,20 @@ pub const Instruction = union(enum) {
opf: u9,
rs2: u5,
},
format_3q: packed struct {
format_3q: struct {
op: u2,
rd: u5,
op3: u6,
rs1: u5,
reserved: u14 = 0b00000000000000,
},
format_3r: packed struct {
format_3r: struct {
op: u2,
fcn: u5,
op3: u6,
reserved: u19 = 0b0000000000000000000,
},
format_3s: packed struct {
format_3s: struct {
op: u2,
rd: u5,
op3: u6,
@ -371,7 +377,7 @@ pub const Instruction = union(enum) {
},
//Format 4 (op = 2): MOVcc, FMOVr, FMOVcc, and Tcc
format_4a: packed struct {
format_4a: struct {
op: u2 = 0b10,
rd: u5,
op3: u6,
@ -392,7 +398,7 @@ pub const Instruction = union(enum) {
cc0: u1,
simm11: u11,
},
format_4c: packed struct {
format_4c: struct {
op: u2 = 0b10,
rd: u5,
op3: u6,
@ -415,7 +421,7 @@ pub const Instruction = union(enum) {
cc0: u1,
simm11: u11,
},
format_4e: packed struct {
format_4e: struct {
op: u2 = 0b10,
rd: u5,
op3: u6,
@ -426,7 +432,7 @@ pub const Instruction = union(enum) {
reserved: u4 = 0b0000,
sw_trap: u7,
},
format_4f: packed struct {
format_4f: struct {
op: u2 = 0b10,
rd: u5,
op3: u6,
@ -436,7 +442,7 @@ pub const Instruction = union(enum) {
opf_low: u5,
rs2: u5,
},
format_4g: packed struct {
format_4g: struct {
op: u2 = 0b10,
rd: u5,
op3: u6,
@ -512,40 +518,43 @@ pub const Instruction = union(enum) {
pub fn toU32(self: Instruction) u32 {
// TODO: Remove this once packed structs work.
return switch (self) {
.format_1 => |v| @bitCast(u32, v),
.format_2a => |v| @bitCast(u32, v),
.format_2b => |v| @bitCast(u32, v),
.format_2c => |v| @bitCast(u32, v),
.format_2d => |v| @bitCast(u32, v),
.format_3a => |v| @bitCast(u32, v),
.format_1 => |v| (@as(u32, v.op) << 30) | @as(u32, v.disp30),
.format_2a => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op2) << 22) | @as(u32, v.imm22),
.format_2b => |v| (@as(u32, v.op) << 30) | (@as(u32, v.a) << 29) | (@as(u32, v.cond) << 25) | (@as(u32, v.op2) << 22) | @as(u32, v.disp22),
.format_2c => |v| (@as(u32, v.op) << 30) | (@as(u32, v.a) << 29) | (@as(u32, v.cond) << 25) | (@as(u32, v.op2) << 22) | (@as(u32, v.cc1) << 21) | (@as(u32, v.cc0) << 20) | (@as(u32, v.p) << 19) | @as(u32, v.disp19),
.format_2d => |v| (@as(u32, v.op) << 30) | (@as(u32, v.a) << 29) | (@as(u32, v.fixed) << 28) | (@as(u32, v.rcond) << 25) | (@as(u32, v.op2) << 22) | (@as(u32, v.d16hi) << 20) | (@as(u32, v.p) << 19) | (@as(u32, v.rs1) << 14) | @as(u32, v.d16lo),
.format_3a => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_3b => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | @as(u32, v.simm13),
.format_3c => |v| @bitCast(u32, v),
.format_3c => |v| (@as(u32, v.op) << 30) | (@as(u32, v.reserved1) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.reserved2) << 5) | @as(u32, v.rs2),
.format_3d => |v| (@as(u32, v.op) << 30) | (@as(u32, v.reserved) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | @as(u32, v.simm13),
.format_3e => |v| @bitCast(u32, v),
.format_3e => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.rcond) << 10) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_3f => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.rcond) << 10) | @as(u32, v.simm10),
.format_3g => |v| @bitCast(u32, v),
.format_3h => |v| @bitCast(u32, v),
.format_3i => |v| @bitCast(u32, v),
.format_3j => |v| @bitCast(u32, v),
.format_3k => |v| @bitCast(u32, v),
.format_3l => |v| @bitCast(u32, v),
.format_3m => |v| @bitCast(u32, v),
.format_3n => |v| @bitCast(u32, v),
.format_3o => |v| @bitCast(u32, v),
.format_3p => |v| @bitCast(u32, v),
.format_3q => |v| @bitCast(u32, v),
.format_3r => |v| @bitCast(u32, v),
.format_3s => |v| @bitCast(u32, v),
.format_4a => |v| @bitCast(u32, v),
.format_3g => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_3h => |v| (@as(u32, v.op) << 30) | (@as(u32, v.fixed1) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.fixed2) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.reserved) << 7) | (@as(u32, v.cmask) << 4) | @as(u32, v.mmask),
.format_3i => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.imm_asi) << 5) | @as(u32, v.rs2),
.format_3j => |v| (@as(u32, v.op) << 30) | (@as(u32, v.impl_dep1) << 25) | (@as(u32, v.op3) << 19) | @as(u32, v.impl_dep2),
.format_3k => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.x) << 12) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_3l => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.x) << 12) | (@as(u32, v.reserved) << 5) | @as(u32, v.shcnt32),
.format_3m => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.x) << 12) | (@as(u32, v.reserved) << 6) | @as(u32, v.shcnt64),
.format_3n => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.reserved) << 14) | (@as(u32, v.opf) << 5) | @as(u32, v.rs2),
.format_3o => |v| (@as(u32, v.op) << 30) | (@as(u32, v.fixed) << 27) | (@as(u32, v.cc1) << 26) | (@as(u32, v.cc0) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.opf) << 5) | @as(u32, v.rs2),
.format_3p => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.opf) << 5) | @as(u32, v.rs2),
.format_3q => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | @as(u32, v.reserved),
.format_3r => |v| (@as(u32, v.op) << 30) | (@as(u32, v.fcn) << 25) | (@as(u32, v.op3) << 19) | @as(u32, v.reserved),
.format_3s => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | @as(u32, v.reserved),
.format_4a => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.cc1) << 12) | (@as(u32, v.cc0) << 11) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_4b => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.cc1) << 12) | (@as(u32, v.cc0) << 11) | @as(u32, v.simm11),
.format_4c => |v| @bitCast(u32, v),
.format_4c => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.cc2) << 18) | (@as(u32, v.cond) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.cc1) << 12) | (@as(u32, v.cc0) << 11) | (@as(u32, v.reserved) << 5) | @as(u32, v.rs2),
.format_4d => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.cc2) << 18) | (@as(u32, v.cond) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.cc1) << 12) | (@as(u32, v.cc0) << 11) | @as(u32, v.simm11),
.format_4e => |v| @bitCast(u32, v),
.format_4f => |v| @bitCast(u32, v),
.format_4g => |v| @bitCast(u32, v),
.format_4e => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.i) << 13) | (@as(u32, v.cc1) << 12) | (@as(u32, v.cc0) << 11) | (@as(u32, v.reserved) << 7) | @as(u32, v.sw_trap),
.format_4f => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.rs1) << 14) | (@as(u32, v.fixed) << 13) | (@as(u32, v.rcond) << 10) | (@as(u32, v.opf_low) << 5) | @as(u32, v.rs2),
.format_4g => |v| (@as(u32, v.op) << 30) | (@as(u32, v.rd) << 25) | (@as(u32, v.op3) << 19) | (@as(u32, v.fixed) << 18) | (@as(u32, v.cond) << 14) | (@as(u32, v.opf_cc) << 11) | (@as(u32, v.opf_low) << 5) | @as(u32, v.rs2),
};
}
// SPARCv9 Instruction formats.
// See section 6.2 of the SPARCv9 ISA manual.
fn format1(disp: i32) Instruction {
const udisp = @bitCast(u32, disp);
@ -561,7 +570,7 @@ pub const Instruction = union(enum) {
};
}
fn format2a(op2: u3, rd: Register, imm: u22) Instruction {
fn format2a(op2: u3, imm: u22, rd: Register) Instruction {
return Instruction{
.format_2a = .{
.rd = rd.enc(),
@ -956,6 +965,106 @@ pub const Instruction = union(enum) {
},
};
}
// SPARCv9 Instruction definition.
// See appendix A of the SPARCv9 ISA manual.
pub fn add(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b10, 0b00_0000, rs1, rs2, rd),
i13 => format3b(0b10, 0b00_0000, rs1, rs2, rd),
else => unreachable,
};
}
pub fn @"or"(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b10, 0b00_0010, rs1, rs2, rd),
i13 => format3b(0b10, 0b00_0010, rs1, rs2, rd),
else => unreachable,
};
}
pub fn ldub(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b11, 0b00_0001, rs1, rs2, rd),
i13 => format3b(0b11, 0b00_0001, rs1, rs2, rd),
else => unreachable,
};
}
pub fn lduh(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b11, 0b00_0010, rs1, rs2, rd),
i13 => format3b(0b11, 0b00_0010, rs1, rs2, rd),
else => unreachable,
};
}
pub fn lduw(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b11, 0b00_0000, rs1, rs2, rd),
i13 => format3b(0b11, 0b00_0000, rs1, rs2, rd),
else => unreachable,
};
}
pub fn ldx(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b11, 0b00_1011, rs1, rs2, rd),
i13 => format3b(0b11, 0b00_1011, rs1, rs2, rd),
else => unreachable,
};
}
pub fn nop() Instruction {
return sethi(0, .g0);
}
pub fn @"return"(comptime s2: type, rs1: Register, rs2: s2) Instruction {
return switch (s2) {
Register => format3c(0b10, 0b11_1001, rs1, rs2),
i13 => format3d(0b10, 0b11_1001, rs1, rs2),
else => unreachable,
};
}
pub fn save(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b10, 0b11_1100, rs1, rs2, rd),
i13 => format3b(0b10, 0b11_1100, rs1, rs2, rd),
else => unreachable,
};
}
pub fn restore(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b10, 0b11_1101, rs1, rs2, rd),
i13 => format3b(0b10, 0b11_1101, rs1, rs2, rd),
else => unreachable,
};
}
pub fn sethi(imm: u22, rd: Register) Instruction {
return format2a(0b100, imm, rd);
}
pub fn sub(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction {
return switch (s2) {
Register => format3a(0b10, 0b00_0100, rs1, rs2, rd),
i13 => format3b(0b10, 0b00_0100, rs1, rs2, rd),
else => unreachable,
};
}
pub fn trap(comptime s2: type, cond: Condition, ccr: CCR, rs1: Register, rs2: s2) Instruction {
// Tcc instructions abuse the rd field to store the conditionals.
return switch (s2) {
Register => format4a(0b11_1010, ccr, rs1, rs2, @intToEnum(Register, cond)),
u7 => format4e(0b11_1010, ccr, rs1, @intToEnum(Register, cond), rs2),
else => unreachable,
};
}
};
test "Serialize formats" {
@ -973,7 +1082,7 @@ test "Serialize formats" {
.expected = 0b01_000000000000000000000000000001,
},
.{
.inst = Instruction.format2a(4, .g0, 0),
.inst = Instruction.format2a(4, 0, .g0),
.expected = 0b00_00000_100_0000000000000000000000,
},
.{
@ -1096,6 +1205,10 @@ test "Serialize formats" {
for (testcases) |case| {
const actual = case.inst.toU32();
try testing.expectEqual(case.expected, actual);
testing.expectEqual(case.expected, actual) catch |err| {
std.debug.print("error: {x}\n", .{err});
std.debug.print("case: {x}\n", .{case});
return err;
};
}
}

View File

@ -65,7 +65,7 @@ phdr_load_rw_index: ?u16 = null,
phdr_shdr_table: std.AutoHashMapUnmanaged(u16, u16) = .{},
entry_addr: ?u64 = null,
page_size: u16,
page_size: u32,
shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
shstrtab_index: ?u16 = null,
@ -304,7 +304,12 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf {
};
const self = try gpa.create(Elf);
errdefer gpa.destroy(self);
const page_size: u16 = 0x1000; // TODO ppc64le requires 64KB
const page_size: u32 = switch (options.target.cpu.arch) {
.powerpc64le => 0x10000,
.sparcv9 => 0x2000,
else => 0x1000,
};
var dwarf: ?Dwarf = if (!options.strip and options.module != null)
Dwarf.init(gpa, .elf, options.target)
@ -472,7 +477,7 @@ pub fn allocatedSize(self: *Elf, start: u64) u64 {
return min_pos - start;
}
pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 {
pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u32) u64 {
var start: u64 = 0;
while (self.detectAllocCollision(start, object_size)) |item_end| {
start = mem.alignForwardGeneric(u64, item_end, min_alignment);

View File

@ -669,6 +669,7 @@ pub fn defaultFunctionAlignment(target: std.Target) u32 {
return switch (target.cpu.arch) {
.arm, .armeb => 4,
.aarch64, .aarch64_32, .aarch64_be => 4,
.sparc, .sparcel, .sparcv9 => 4,
.riscv64 => 2,
else => 1,
};

View File

@ -16,6 +16,7 @@ pub fn addCases(ctx: *TestContext) !void {
try @import("stage2/riscv64.zig").addCases(ctx);
try @import("stage2/plan9.zig").addCases(ctx);
try @import("stage2/x86_64.zig").addCases(ctx);
try @import("stage2/sparcv9.zig").addCases(ctx);
// https://github.com/ziglang/zig/issues/10968
//try @import("stage2/nvptx.zig").addCases(ctx);
}

View File

@ -159,7 +159,7 @@ pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("hello world with updates", macos_aarch64);
case.addError("", &[_][]const u8{
":108:9: error: struct 'tmp.tmp' has no member named 'main'",
":109:9: error: struct 'tmp.tmp' has no member named 'main'",
});
// Incorrect return type

39
test/stage2/sparcv9.zig Normal file
View File

@ -0,0 +1,39 @@
const std = @import("std");
const TestContext = @import("../../src/test.zig").TestContext;
const linux_sparcv9 = std.zig.CrossTarget{
.cpu_arch = .sparcv9,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("sparcv9 hello world", linux_sparcv9);
// Regular old hello world
case.addCompareOutput(
\\const msg = "Hello, World!\n";
\\
\\pub export fn _start() noreturn {
\\ asm volatile ("ta 0x6d"
\\ :
\\ : [number] "{g1}" (4),
\\ [arg1] "{o0}" (1),
\\ [arg2] "{o1}" (@ptrToInt(msg)),
\\ [arg3] "{o2}" (msg.len)
\\ : "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory"
\\ );
\\
\\ asm volatile ("ta 0x6d"
\\ :
\\ : [number] "{g1}" (1),
\\ [arg1] "{o0}" (0)
\\ : "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory"
\\ );
\\
\\ unreachable;
\\}
,
"Hello, World!\n",
);
}
}

View File

@ -1925,7 +1925,7 @@ fn addLinuxTestCases(ctx: *TestContext) !void {
var case = ctx.exe("hello world with updates", linux_x64);
case.addError("", &[_][]const u8{
":108:9: error: struct 'tmp.tmp' has no member named 'main'",
":109:9: error: struct 'tmp.tmp' has no member named 'main'",
});
// Incorrect return type
@ -2176,7 +2176,7 @@ fn addMacOsTestCases(ctx: *TestContext) !void {
{
var case = ctx.exe("darwin hello world with updates", macos_x64);
case.addError("", &[_][]const u8{
":108:9: error: struct 'tmp.tmp' has no member named 'main'",
":109:9: error: struct 'tmp.tmp' has no member named 'main'",
});
// Incorrect return type