diff --git a/CMakeLists.txt b/CMakeLists.txt index dd5791807373f9760b501537f7942cd819582848..cc962fcff18ec2e6dc353a0f75ee38cb4e0bd14b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -612,11 +612,11 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/arch/riscv64/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig" "${CMAKE_SOURCE_DIR}/src/arch/riscv64/abi.zig" - "${CMAKE_SOURCE_DIR}/src/arch/sparcv9/CodeGen.zig" - "${CMAKE_SOURCE_DIR}/src/arch/sparcv9/Emit.zig" - "${CMAKE_SOURCE_DIR}/src/arch/sparcv9/Mir.zig" - "${CMAKE_SOURCE_DIR}/src/arch/sparcv9/bits.zig" - "${CMAKE_SOURCE_DIR}/src/arch/sparcv9/abi.zig" + "${CMAKE_SOURCE_DIR}/src/arch/sparc64/CodeGen.zig" + "${CMAKE_SOURCE_DIR}/src/arch/sparc64/Emit.zig" + "${CMAKE_SOURCE_DIR}/src/arch/sparc64/Mir.zig" + "${CMAKE_SOURCE_DIR}/src/arch/sparc64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/sparc64/abi.zig" "${CMAKE_SOURCE_DIR}/src/arch/wasm/CodeGen.zig" "${CMAKE_SOURCE_DIR}/src/arch/wasm/Emit.zig" "${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig" diff --git a/doc/langref.html.in b/doc/langref.html.in index f5d3c0b7c158be4b91fc83f1bc63252a098b908f..32320d5113c82c9b3351f1f633b37582ec5cc49a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -11392,7 +11392,7 @@ Architectures: riscv32 riscv64 sparc - sparcv9 + sparc64 sparcel s390x thumb @@ -11543,7 +11543,7 @@ Available libcs: s390x-linux-gnu s390x-linux-musl sparc-linux-gnu - sparcv9-linux-gnu + sparc64-linux-gnu wasm32-freestanding-musl wasm32-wasi-musl x86_64-linux-gnu diff --git a/lib/c.zig b/lib/c.zig index 525bdd267de74e3a8b5c06f4b853c4e41566ba84..30c6e0cd76e6b30c553d37a90cb8eb26451b8722 100644 --- a/lib/c.zig +++ b/lib/c.zig @@ -625,7 +625,7 @@ fn clone() callconv(.Naked) void { \\ sc ); }, - .sparcv9 => { + .sparc64 => { // __clone(func, stack, flags, arg, ptid, tls, ctid) // i0, i1, i2, i3, i4, i5, sp // syscall(SYS_clone, flags, stack, ptid, tls, ctid) diff --git a/lib/compiler_rt/clear_cache.zig b/lib/compiler_rt/clear_cache.zig index d6ce02249e15743648a3c1792dd1e6a0240236ca..0765a23811c036838d1f02e7826ed1e1ef3e8d12 100644 --- a/lib/compiler_rt/clear_cache.zig +++ b/lib/compiler_rt/clear_cache.zig @@ -36,7 +36,7 @@ pub fn clear_cache(start: usize, end: usize) callconv(.C) void { else => false, }; const sparc = switch (arch) { - .sparc, .sparcv9, .sparcel => true, + .sparc, .sparc64, .sparcel => true, else => false, }; const apple = switch (os) { diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 45d2ac0040e099c81553bc0f4e3349d804206d48..9e29c82672c7b09019dfd5d7d18d804a9b9d154c 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -859,7 +859,7 @@ const LinuxThreadImpl = struct { [len] "r" (self.mapped.len), : "memory" ), - .sparcv9 => asm volatile ( + .sparc64 => asm volatile ( \\ # SPARCs really don't like it when active stack frames \\ # is unmapped (it will result in a segfault), so we \\ # force-deactivate it by running `restore` until diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index bf9a2d568209a3ce62145d2e5609564f91d31ad8..53786c86616ea02e69bf81803bae15dd68cf5983 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -727,8 +727,8 @@ pub const CompilerBackend = enum(u64) { /// riscv64 backend. stage2_riscv64 = 9, /// The reference implementation self-hosted compiler of Zig, using the - /// sparcv9 backend. - stage2_sparcv9 = 10, + /// sparc64 backend. + stage2_sparc64 = 10, _, }; @@ -775,7 +775,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_x86 or builtin.zig_backend == .stage2_riscv64 or - builtin.zig_backend == .stage2_sparcv9) + builtin.zig_backend == .stage2_sparc64) { while (true) { @breakpoint(); diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 5f96fe3fe0929ad5fe9388c0d9fee81bf2d682c9..62a7d3e964f9381b4d4dc4b86159763ec0ee18a8 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -115,7 +115,7 @@ pub const _errno = switch (native_abi) { }; pub const Stat = switch (native_arch) { - .sparcv9 => extern struct { + .sparc64 => extern struct { dev: u64, __pad1: u16, ino: ino_t, @@ -345,7 +345,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = switch (native_abi) { .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => switch (native_arch) { .aarch64 => 48, .x86_64 => if (native_abi == .gnux32) 40 else 32, - .mips64, .powerpc64, .powerpc64le, .sparcv9 => 40, + .mips64, .powerpc64, .powerpc64le, .sparc64 => 40, else => if (@sizeOf(usize) == 8) 40 else 24, }, .android => if (@sizeOf(usize) == 8) 40 else 4, diff --git a/lib/std/c/minix.zig b/lib/std/c/minix.zig index 9c644a79867c892e05f67335872813d41c6e347d..62cefc14fb578105239cf45e4b824b7f407bf83f 100644 --- a/lib/std/c/minix.zig +++ b/lib/std/c/minix.zig @@ -11,7 +11,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = switch (builtin.abi) { .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => switch (builtin.cpu.arch) { .aarch64 => 48, .x86_64 => if (builtin.abi == .gnux32) 40 else 32, - .mips64, .powerpc64, .powerpc64le, .sparcv9 => 40, + .mips64, .powerpc64, .powerpc64le, .sparc64 => 40, else => if (@sizeOf(usize) == 8) 40 else 24, }, else => unreachable, diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index b1f8e8846ecb2606ba90df07ababdf0ccaf56d7e..e481de099676a9e68d6c988848630295bfd19f8c 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -99,14 +99,14 @@ const pthread_spin_t = switch (builtin.cpu.arch) { .powerpc, .powerpc64, .powerpc64le => i32, .i386, .x86_64 => u8, .arm, .armeb, .thumb, .thumbeb => i32, - .sparc, .sparcel, .sparcv9 => u8, + .sparc, .sparcel, .sparc64 => u8, .riscv32, .riscv64 => u32, else => @compileError("undefined pthread_spin_t for this arch"), }; const padded_pthread_spin_t = switch (builtin.cpu.arch) { .i386, .x86_64 => u32, - .sparc, .sparcel, .sparcv9 => u32, + .sparc, .sparcel, .sparc64 => u32, else => pthread_spin_t, }; @@ -1070,7 +1070,7 @@ pub const ucontext_t = extern struct { .i386 => 4, .mips, .mipsel, .mips64, .mips64el => 14, .arm, .armeb, .thumb, .thumbeb => 1, - .sparc, .sparcel, .sparcv9 => if (@sizeOf(usize) == 4) 43 else 8, + .sparc, .sparcel, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, else => 0, } ]u32, diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 0aa90c741a2271d567eae90949c4875e4bfa65d9..71c9d21ce35254660a969ea9b85be34f64f14fcf 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -1263,7 +1263,7 @@ pub const E = enum(u16) { const _MAX_PAGE_SHIFT = switch (builtin.cpu.arch) { .i386 => 12, - .sparcv9 => 13, + .sparc64 => 13, }; pub const MINSIGSTKSZ = 1 << _MAX_PAGE_SHIFT; pub const SIGSTKSZ = MINSIGSTKSZ + (1 << _MAX_PAGE_SHIFT) * 4; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 1a34d67f9dbb3fac0e1d527e978a54fd65c9a0dd..83667c758b772ced8827b7d68c35bfacbea8dbf5 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -401,7 +401,7 @@ pub const StackIterator = struct { fp: usize, pub fn init(first_address: ?usize, fp: ?usize) StackIterator { - if (native_arch == .sparcv9) { + if (native_arch == .sparc64) { // Flush all the register windows on stack. asm volatile ( \\ flushw diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 2ea928f8912eca59a13a9ea3137a730e4e82f565..d80ae2f6a0f7c3d53d5d77403f4fc4965a99ce90 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1511,7 +1511,7 @@ pub const EM = enum(u16) { .RISCV => .riscv64, .X86_64 => .x86_64, .BPF => .bpfel, - .SPARCV9 => .sparcv9, + .SPARCV9 => .sparc64, .S390 => .s390x, .SPU_2 => .spu_2, // there's many cases we don't (yet) handle, or will never have a diff --git a/lib/std/mem.zig b/lib/std/mem.zig index eec9d4d61f493709dd7cb68cb02c94a16249bb48..73cc24c45c2b651e351def4557e436c1f3539d23 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -18,7 +18,7 @@ pub const page_size = switch (builtin.cpu.arch) { .macos, .ios, .watchos, .tvos => 16 * 1024, else => 4 * 1024, }, - .sparcv9 => 8 * 1024, + .sparc64 => 8 * 1024, else => 4 * 1024, }; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 94e2dd47b930e2d91fbfc0ab1a42c658f27eee14..fec9f55ad1a2c738af4d8540f2f695f278f2d1cb 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -38,7 +38,7 @@ const arch_bits = switch (native_arch) { .aarch64 => @import("linux/arm64.zig"), .arm, .thumb => @import("linux/arm-eabi.zig"), .riscv64 => @import("linux/riscv64.zig"), - .sparcv9 => @import("linux/sparc64.zig"), + .sparc64 => @import("linux/sparc64.zig"), .mips, .mipsel => @import("linux/mips.zig"), .powerpc => @import("linux/powerpc.zig"), .powerpc64, .powerpc64le => @import("linux/powerpc64.zig"), @@ -1098,7 +1098,7 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact const result = switch (native_arch) { // The sparc version of rt_sigaction needs the restorer function to be passed as an argument too. - .sparc, .sparcv9 => syscall5(.rt_sigaction, sig, ksa_arg, oldksa_arg, @ptrToInt(ksa.restorer), mask_size), + .sparc, .sparc64 => syscall5(.rt_sigaction, sig, ksa_arg, oldksa_arg, @ptrToInt(ksa.restorer), mask_size), else => syscall4(.rt_sigaction, sig, ksa_arg, oldksa_arg, mask_size), }; if (getErrno(result) != .SUCCESS) return result; @@ -1698,7 +1698,7 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize { pub const E = switch (native_arch) { .mips, .mipsel => @import("linux/errno/mips.zig").E, - .sparc, .sparcel, .sparcv9 => @import("linux/errno/sparc.zig").E, + .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E, else => @import("linux/errno/generic.zig").E, }; @@ -4090,7 +4090,7 @@ pub const V = switch (native_arch) { pub const LNEXT = 15; pub const DISCARD = 16; }, - .sparc, .sparcv9 => struct { + .sparc, .sparc64 => struct { pub const INTR = 0; pub const QUIT = 1; pub const ERASE = 2; @@ -5427,7 +5427,7 @@ pub const AUDIT = struct { .aarch64 => .AARCH64, .arm, .thumb => .ARM, .riscv64 => .RISCV64, - .sparcv9 => .SPARC64, + .sparc64 => .SPARC64, .mips => .MIPS, .mipsel => .MIPSEL, .powerpc => .PPC, @@ -5454,7 +5454,7 @@ pub const AUDIT = struct { RISCV64 = toAudit(.riscv64), S390X = toAudit(.s390x), SPARC = toAudit(.sparc), - SPARC64 = toAudit(.sparcv9), + SPARC64 = toAudit(.sparc64), X86_64 = toAudit(.x86_64), fn toAudit(arch: std.Target.Cpu.Arch) u32 { diff --git a/lib/std/os/linux/ioctl.zig b/lib/std/os/linux/ioctl.zig index 35ff1bfc32664fab899732ee00dd667497bdd748..96ec96c3061c594a4ec2d106914028f7f76d5c1a 100644 --- a/lib/std/os/linux/ioctl.zig +++ b/lib/std/os/linux/ioctl.zig @@ -10,7 +10,7 @@ const bits = switch (@import("builtin").cpu.arch) { .powerpc64, .powerpc64le, .sparc, - .sparcv9, + .sparc64, .sparcel, => .{ .size = 13, .dir = 3, .none = 1, .read = 2, .write = 4 }, else => .{ .size = 14, .dir = 2, .none = 0, .read = 2, .write = 1 }, diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 770ab9b92cd7caecc4f956fe6d01719b89ae7941..d91f7d7a9e83d5bb517ea5ad409fb68d2fe69799 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -49,7 +49,7 @@ const TLSVariant = enum { const tls_variant = switch (native_arch) { .arm, .armeb, .thumb, .aarch64, .aarch64_be, .riscv32, .riscv64, .mips, .mipsel, .powerpc, .powerpc64, .powerpc64le => TLSVariant.VariantI, - .x86_64, .i386, .sparcv9 => TLSVariant.VariantII, + .x86_64, .i386, .sparc64 => TLSVariant.VariantII, else => @compileError("undefined tls_variant for this architecture"), }; @@ -174,7 +174,7 @@ pub fn setThreadPointer(addr: usize) void { : [addr] "r" (addr), ); }, - .sparcv9 => { + .sparc64 => { asm volatile ( \\ mov %[addr], %%g7 : diff --git a/lib/std/start.zig b/lib/std/start.zig index 81c7942bc18a371875426317da0d1c53d72ccb6d..516d6363d70a75532a3024f3d42efb4982f9584e 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -29,7 +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) + builtin.zig_backend == .stage2_sparc64) { if (builtin.output_mode == .Exe) { if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) { @@ -164,7 +164,7 @@ fn exit2(code: usize) noreturn { : "rcx", "r11", "memory" ); }, - .sparcv9 => { + .sparc64 => { asm volatile ("ta 0x6d" : : [number] "{g1}" (1), @@ -323,7 +323,7 @@ fn _start() callconv(.Naked) noreturn { : "r0" ); }, - .sparcv9 => { + .sparc64 => { // argc is stored after a register window (16 registers) plus stack bias argc_argv_ptr = asm ( \\ mov %%g0, %%i6 diff --git a/lib/std/target.zig b/lib/std/target.zig index 2db1415cd34fcb58bbe6b11140c5ce8fe3e6f279..b11408cfce6c4c4cf6d3dc03c5030dbea1d6a449 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -785,7 +785,7 @@ pub const Target = struct { riscv32, riscv64, sparc, - sparcv9, + sparc64, sparcel, s390x, tce, @@ -884,7 +884,7 @@ pub const Target = struct { pub fn isSPARC(arch: Arch) bool { return switch (arch) { - .sparc, .sparcel, .sparcv9 => true, + .sparc, .sparcel, .sparc64 => true, else => false, }; } @@ -964,7 +964,7 @@ pub const Target = struct { .bpfel => .BPF, .bpfeb => .BPF, .csky => .CSKY, - .sparcv9 => .SPARCV9, + .sparc64 => .SPARCV9, .s390x => .S390, .ve => .NONE, .spu_2 => .SPU_2, @@ -1025,7 +1025,7 @@ pub const Target = struct { .bpfel => .Unknown, .bpfeb => .Unknown, .csky => .Unknown, - .sparcv9 => .Unknown, + .sparc64 => .Unknown, .s390x => .Unknown, .ve => .Unknown, .spu_2 => .Unknown, @@ -1092,7 +1092,7 @@ pub const Target = struct { .powerpc64, .thumbeb, .sparc, - .sparcv9, + .sparc64, .tce, .lanai, .s390x, @@ -1159,7 +1159,7 @@ pub const Target = struct { .amdgcn, .bpfel, .bpfeb, - .sparcv9, + .sparc64, .s390x, .ve, .spirv64, @@ -1177,7 +1177,7 @@ pub const Target = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", .amdgcn => "amdgpu", .riscv32, .riscv64 => "riscv", - .sparc, .sparcv9, .sparcel => "sparc", + .sparc, .sparc64, .sparcel => "sparc", .s390x => "systemz", .i386, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", @@ -1200,7 +1200,7 @@ pub const Target = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => &powerpc.all_features, .amdgcn => &amdgpu.all_features, .riscv32, .riscv64 => &riscv.all_features, - .sparc, .sparcv9, .sparcel => &sparc.all_features, + .sparc, .sparc64, .sparcel => &sparc.all_features, .spirv32, .spirv64 => &spirv.all_features, .s390x => &systemz.all_features, .i386, .x86_64 => &x86.all_features, @@ -1225,7 +1225,7 @@ pub const Target = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => comptime allCpusFromDecls(powerpc.cpu), .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), - .sparc, .sparcv9, .sparcel => comptime allCpusFromDecls(sparc.cpu), + .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), .s390x => comptime allCpusFromDecls(systemz.cpu), .i386, .x86_64 => comptime allCpusFromDecls(x86.cpu), .nvptx, .nvptx64 => comptime allCpusFromDecls(nvptx.cpu), @@ -1286,7 +1286,7 @@ pub const Target = struct { .riscv32 => &riscv.cpu.generic_rv32, .riscv64 => &riscv.cpu.generic_rv64, .sparc, .sparcel => &sparc.cpu.generic, - .sparcv9 => &sparc.cpu.v9, + .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline .s390x => &systemz.cpu.generic, .i386 => &x86.cpu._i386, .x86_64 => &x86.cpu.x86_64, @@ -1587,7 +1587,7 @@ pub const Target = struct { .powerpc, .powerpcle => return copy(&result, "/lib/ld.so.1"), .powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"), .s390x => return copy(&result, "/lib64/ld64.so.1"), - .sparcv9 => return copy(&result, "/lib64/ld-linux.so.2"), + .sparc64 => return copy(&result, "/lib64/ld-linux.so.2"), .x86_64 => return copy(&result, switch (self.abi) { .gnux32 => "/libx32/ld-linux-x32.so.2", else => "/lib64/ld-linux-x86-64.so.2", @@ -1735,7 +1735,7 @@ pub const Target = struct { .mips64, .mips64el, .sparc, - .sparcv9, + .sparc64, .sparcel, .powerpc, .powerpcle, @@ -1760,7 +1760,7 @@ pub const Target = struct { .mips64, .mips64el, .sparc, - .sparcv9, + .sparc64, .sparcel, .powerpc, .powerpcle, @@ -1810,14 +1810,14 @@ pub const Target = struct { // 1. Better machine code when loading into SIMD register. // 2. The C ABI wants 16 for extern structs. // 3. 16-byte cmpxchg needs 16-byte alignment. - // Same logic for riscv64, powerpc64, mips64, sparcv9. + // Same logic for riscv64, powerpc64, mips64, sparc64. .x86_64, .riscv64, .powerpc64, .powerpc64le, .mips64, .mips64el, - .sparcv9, + .sparc64, // Even LLVMABIAlignmentOfType(i128) agrees on these targets. .aarch64, diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig index 9055d1c2156b9c4421b119a89a8fc89009c9918a..0956e469af90eb5f89a08737bb3d4b0a09f2b3e6 100644 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ b/lib/std/zig/system/NativeTargetInfo.zig @@ -931,7 +931,7 @@ pub fn getExternalExecutor( .riscv64 => Executor{ .qemu = "qemu-riscv64" }, .s390x => Executor{ .qemu = "qemu-s390x" }, .sparc => Executor{ .qemu = "qemu-sparc" }, - .sparcv9 => Executor{ .qemu = "qemu-sparc64" }, + .sparc64 => Executor{ .qemu = "qemu-sparc64" }, .x86_64 => Executor{ .qemu = "qemu-x86_64" }, else => return bad_result, }; diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 0594ff1e2f829d93c2537f04d0375ec2c0f19cc1..ee1e987464a44d4782c687faea07104d96ef2cb9 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -66,7 +66,7 @@ const SparcCpuinfoImpl = struct { const SparcCpuinfoParser = CpuinfoParser(SparcCpuinfoImpl); test "cpuinfo: SPARC" { - try testParser(SparcCpuinfoParser, .sparcv9, &Target.sparc.cpu.niagara2, + try testParser(SparcCpuinfoParser, .sparc64, &Target.sparc.cpu.niagara2, \\cpu : UltraSparc T2 (Niagara2) \\fpu : UltraSparc T2 integrated FPU \\pmu : niagara2 @@ -456,7 +456,7 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu { .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be, .aarch64_32 => { return ArmCpuinfoParser.parse(current_arch, f.reader()) catch null; }, - .sparcv9 => { + .sparc64 => { return SparcCpuinfoParser.parse(current_arch, f.reader()) catch null; }, .powerpc, .powerpcle, .powerpc64, .powerpc64le => { diff --git a/src/Compilation.zig b/src/Compilation.zig index 5e4ab0ec12f1b9e512548d94bae3fb2bb9969218..2091502da2e57338e26097c01b1bba2259cd62fd 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4566,7 +4566,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, + .sparc64 => .stage2_sparc64, else => .other, }; }; diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig new file mode 100644 index 0000000000000000000000000000000000000000..0745cd46c92c087b2435d1a4cc0c5539aa8567e2 --- /dev/null +++ b/src/arch/sparc64/CodeGen.zig @@ -0,0 +1,1791 @@ +//! SPARCv9 codegen. +//! This lowers AIR into MIR. +//! For now this only implements medium/low code model with absolute addressing. +//! TODO add support for other code models. +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"); +const Emit = @import("Emit.zig"); +const Liveness = @import("../../Liveness.zig"); +const Type = @import("../../type.zig").Type; +const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError; +const FnResult = @import("../../codegen.zig").FnResult; +const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; +const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager; +const RegisterManager = RegisterManagerFn(Self, Register, &abi.allocatable_regs); + +const build_options = @import("build_options"); + +const bits = @import("bits.zig"); +const abi = @import("abi.zig"); +const Register = bits.Register; + +const Self = @This(); + +const InnerError = error{ + OutOfMemory, + CodegenFail, + OutOfRegisters, +}; + +const RegisterView = enum(u1) { + caller, + callee, +}; + +gpa: Allocator, +air: Air, +liveness: Liveness, +bin_file: *link.File, +target: *const std.Target, +mod_fn: *const Module.Fn, +code: *std.ArrayList(u8), +debug_output: DebugInfoOutput, +err_msg: ?*ErrorMsg, +args: []MCValue, +ret_mcv: MCValue, +fn_type: Type, +arg_index: usize, +src_loc: Module.SrcLoc, +stack_align: u32, + +/// MIR Instructions +mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, +/// MIR extra data +mir_extra: std.ArrayListUnmanaged(u32) = .{}, + +/// Byte offset within the source file of the ending curly. +end_di_line: u32, +end_di_column: u32, + +/// 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. +exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, + +/// Whenever there is a runtime branch, we push a Branch onto this stack, +/// and pop it off when the runtime branch joins. This provides an "overlay" +/// of the table of mappings from instructions to `MCValue` from within the branch. +/// This way we can modify the `MCValue` for an instruction in different ways +/// within different branches. Special consideration is needed when a branch +/// joins with its parent, to make sure all instructions have the same MCValue +/// across each runtime branch upon joining. +branch_stack: *std.ArrayList(Branch), + +// Key is the block instruction +blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, + +register_manager: RegisterManager = .{}, + +/// Maps offset to what is stored there. +stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, + +/// Offset from the stack base, representing the end of the stack frame. +max_end_stack: u32 = 0, +/// Represents the current end stack offset. If there is no existing slot +/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. +next_stack_offset: u32 = 0, + +/// Debug field, used to find bugs in the compiler. +air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init, + +const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {}; + +const MCValue = union(enum) { + /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. + /// TODO Look into deleting this tag and using `dead` instead, since every use + /// of MCValue.none should be instead looking at the type and noticing it is 0 bits. + none, + /// Control flow will not allow this value to be observed. + unreach, + /// No more references to this value remain. + dead, + /// The value is undefined. + undef, + /// A pointer-sized integer that fits in a register. + /// If the type is a pointer, this is the pointer address in virtual address space. + immediate: u64, + /// The value is in a target-specific register. + register: Register, + /// The value is in memory at a hard-coded address. + /// If the type is a pointer, it means the pointer address is at this memory location. + memory: u64, + /// The value is one of the stack variables. + /// If the type is a pointer, it means the pointer address is in the stack at this offset. + stack_offset: u32, + /// The value is a pointer to one of the stack variables (payload is stack offset). + ptr_stack_offset: u32, + + fn isMemory(mcv: MCValue) bool { + return switch (mcv) { + .memory, .stack_offset => true, + else => false, + }; + } + + fn isImmediate(mcv: MCValue) bool { + return switch (mcv) { + .immediate => true, + else => false, + }; + } + + fn isMutable(mcv: MCValue) bool { + return switch (mcv) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + + .immediate, + .memory, + .ptr_stack_offset, + .undef, + => false, + + .register, + .stack_offset, + => true, + }; + } +}; + +const Branch = struct { + inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{}, + + fn deinit(self: *Branch, gpa: Allocator) void { + self.inst_table.deinit(gpa); + self.* = undefined; + } +}; + +const StackAllocation = struct { + inst: Air.Inst.Index, + /// TODO do we need size? should be determined by inst.ty.abiSize() + size: u32, +}; + +const BlockData = struct { + 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 + /// the location to store their block results. + mcv: MCValue, +}; + +const CallMCValues = struct { + args: []MCValue, + return_value: MCValue, + stack_byte_count: u32, + stack_align: u32, + + fn deinit(self: *CallMCValues, func: *Self) void { + func.gpa.free(self.args); + self.* = undefined; + } +}; + +const BigTomb = struct { + function: *Self, + inst: Air.Inst.Index, + tomb_bits: Liveness.Bpi, + big_tomb_bits: u32, + bit_index: usize, + + fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void { + const this_bit_index = bt.bit_index; + bt.bit_index += 1; + + const op_int = @enumToInt(op_ref); + if (op_int < Air.Inst.Ref.typed_value_map.len) return; + const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); + + if (this_bit_index < Liveness.bpi - 1) { + const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0; + if (!dies) return; + } else { + const big_bit_index = @intCast(u5, this_bit_index - (Liveness.bpi - 1)); + const dies = @truncate(u1, bt.big_tomb_bits >> big_bit_index) != 0; + if (!dies) return; + } + bt.function.processDeath(op_index); + } + + fn finishAir(bt: *BigTomb, result: MCValue) void { + const is_used = !bt.function.liveness.isUnused(bt.inst); + if (is_used) { + log.debug("%{d} => {}", .{ bt.inst, result }); + const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1]; + branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result); + } + bt.function.finishAirBookkeeping(); + } +}; + +pub fn generate( + bin_file: *link.File, + src_loc: Module.SrcLoc, + module_fn: *Module.Fn, + air: Air, + liveness: Liveness, + code: *std.ArrayList(u8), + debug_output: DebugInfoOutput, +) GenerateSymbolError!FnResult { + if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { + @panic("Attempted to compile for architecture that was disabled by build configuration"); + } + + const mod = bin_file.options.module.?; + const fn_owner_decl = mod.declPtr(module_fn.owner_decl); + assert(fn_owner_decl.has_tv); + const fn_type = fn_owner_decl.ty; + + var branch_stack = std.ArrayList(Branch).init(bin_file.allocator); + defer { + assert(branch_stack.items.len == 1); + branch_stack.items[0].deinit(bin_file.allocator); + branch_stack.deinit(); + } + try branch_stack.append(.{}); + + var function = Self{ + .gpa = bin_file.allocator, + .air = air, + .liveness = liveness, + .target = &bin_file.options.target, + .bin_file = bin_file, + .mod_fn = module_fn, + .code = code, + .debug_output = debug_output, + .err_msg = null, + .args = undefined, // populated after `resolveCallingConventionValues` + .ret_mcv = undefined, // populated after `resolveCallingConventionValues` + .fn_type = fn_type, + .arg_index = 0, + .branch_stack = &branch_stack, + .src_loc = src_loc, + .stack_align = undefined, + .end_di_line = module_fn.rbrace_line, + .end_di_column = module_fn.rbrace_column, + }; + defer function.stack.deinit(bin_file.allocator); + defer function.blocks.deinit(bin_file.allocator); + defer function.exitlude_jump_relocs.deinit(bin_file.allocator); + + 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.", .{}), + }, + else => |e| return e, + }; + defer call_info.deinit(&function); + + function.args = call_info.args; + function.ret_mcv = call_info.return_value; + function.stack_align = call_info.stack_align; + function.max_end_stack = call_info.stack_byte_count; + + function.gen() 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.", .{}), + }, + else => |e| return e, + }; + + var mir = Mir{ + .instructions = function.mir_instructions.toOwnedSlice(), + .extra = function.mir_extra.toOwnedSlice(bin_file.allocator), + }; + defer mir.deinit(bin_file.allocator); + + var emit = Emit{ + .mir = mir, + .bin_file = bin_file, + .debug_output = debug_output, + .target = &bin_file.options.target, + .src_loc = src_loc, + .code = code, + .prev_di_pc = 0, + .prev_di_line = module_fn.lbrace_line, + .prev_di_column = module_fn.lbrace_column, + }; + defer emit.deinit(); + + emit.emitMir() catch |err| switch (err) { + error.EmitFail => return FnResult{ .fail = emit.err_msg.? }, + else => |e| return e, + }; + + if (function.err_msg) |em| { + return FnResult{ .fail = em }; + } else { + return FnResult{ .appended = {} }; + } +} + +fn gen(self: *Self) !void { + const cc = self.fn_type.fnCallingConvention(); + if (cc != .Naked) { + // TODO Finish function prologue and epilogue for sparc64. + + // save %sp, stack_save_area, %sp + const save_inst = try self.addInst(.{ + .tag = .save, + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = .sp, + .rs1 = .sp, + .rs2_or_imm = .{ .imm = -abi.stack_save_area }, + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .dbg_prologue_end, + .data = .{ .nop = {} }, + }); + + try self.genBody(self.air.getMainBody()); + + _ = try self.addInst(.{ + .tag = .dbg_epilogue_begin, + .data = .{ .nop = {} }, + }); + + // exitlude jumps + if (self.exitlude_jump_relocs.items.len > 0 and + self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2) + { + // If the last Mir instruction (apart from the + // dbg_epilogue_begin) is the last exitlude jump + // relocation (which would just jump one instruction + // further), it can be safely removed + self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop()); + } + + for (self.exitlude_jump_relocs.items) |jmp_reloc| { + _ = jmp_reloc; + return self.fail("TODO add branches in sparc64", .{}); + } + + // Backpatch stack offset + const total_stack_size = self.max_end_stack + abi.stack_save_area; // TODO + self.saved_regs_stack_space; + const stack_size = mem.alignForwardGeneric(u32, total_stack_size, self.stack_align); + if (math.cast(i13, stack_size)) |size| { + self.mir_instructions.set(save_inst, .{ + .tag = .save, + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = .sp, + .rs1 = .sp, + .rs2_or_imm = .{ .imm = -size }, + }, + }, + }); + } else |_| { + // TODO for large stacks, replace the prologue with: + // setx stack_size, %g1 + // save %sp, %g1, %sp + return self.fail("TODO SPARCv9: allow larger stacks", .{}); + } + + // return %i7 + 8 + _ = try self.addInst(.{ + .tag = .@"return", + .data = .{ + .arithmetic_2op = .{ + .is_imm = true, + .rs1 = .@"i7", + .rs2_or_imm = .{ .imm = 8 }, + }, + }, + }); + + // Branches in SPARC have a delay slot, that is, the instruction + // following it will unconditionally be executed. + // See: Section 3.2.3 Control Transfer in SPARCv9 manual. + // See also: https://arcb.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/delaybra.html + // TODO Find a way to fill this delay slot + // nop + _ = try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); + } else { + _ = try self.addInst(.{ + .tag = .dbg_prologue_end, + .data = .{ .nop = {} }, + }); + + try self.genBody(self.air.getMainBody()); + + _ = try self.addInst(.{ + .tag = .dbg_epilogue_begin, + .data = .{ .nop = {} }, + }); + } + + // Drop them off at the rbrace. + _ = try self.addInst(.{ + .tag = .dbg_line, + .data = .{ .dbg_line_column = .{ + .line = self.end_di_line, + .column = self.end_di_column, + } }, + }); +} + +fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { + const air_tags = self.air.instructions.items(.tag); + + for (body) |inst| { + const old_air_bookkeeping = self.air_bookkeeping; + try self.ensureProcessDeathCapacity(Liveness.bpi); + + switch (air_tags[inst]) { + // zig fmt: off + .add, .ptr_add => @panic("TODO try self.airBinOp(inst)"), + .addwrap => @panic("TODO try self.airAddWrap(inst)"), + .add_sat => @panic("TODO try self.airAddSat(inst)"), + .sub, .ptr_sub => @panic("TODO try self.airBinOp(inst)"), + .subwrap => @panic("TODO try self.airSubWrap(inst)"), + .sub_sat => @panic("TODO try self.airSubSat(inst)"), + .mul => @panic("TODO try self.airMul(inst)"), + .mulwrap => @panic("TODO try self.airMulWrap(inst)"), + .mul_sat => @panic("TODO try self.airMulSat(inst)"), + .rem => @panic("TODO try self.airRem(inst)"), + .mod => @panic("TODO try self.airMod(inst)"), + .shl, .shl_exact => @panic("TODO try self.airShl(inst)"), + .shl_sat => @panic("TODO try self.airShlSat(inst)"), + .min => @panic("TODO try self.airMin(inst)"), + .max => @panic("TODO try self.airMax(inst)"), + .slice => @panic("TODO try self.airSlice(inst)"), + + .sqrt, + .sin, + .cos, + .tan, + .exp, + .exp2, + .log, + .log2, + .log10, + .fabs, + .floor, + .ceil, + .round, + .trunc_float, + => @panic("TODO try self.airUnaryMath(inst)"), + + .add_with_overflow => @panic("TODO try self.airAddWithOverflow(inst)"), + .sub_with_overflow => @panic("TODO try self.airSubWithOverflow(inst)"), + .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 => try self.airDiv(inst), + + .cmp_lt => @panic("TODO try self.airCmp(inst, .lt)"), + .cmp_lte => @panic("TODO try self.airCmp(inst, .lte)"), + .cmp_eq => @panic("TODO try self.airCmp(inst, .eq)"), + .cmp_gte => @panic("TODO try self.airCmp(inst, .gte)"), + .cmp_gt => @panic("TODO try self.airCmp(inst, .gt)"), + .cmp_neq => @panic("TODO try self.airCmp(inst, .neq)"), + .cmp_vector => @panic("TODO try self.airCmpVector(inst)"), + .cmp_lt_errors_len => @panic("TODO try self.airCmpLtErrorsLen(inst)"), + + .bool_and => @panic("TODO try self.airBoolOp(inst)"), + .bool_or => @panic("TODO try self.airBoolOp(inst)"), + .bit_and => @panic("TODO try self.airBitAnd(inst)"), + .bit_or => @panic("TODO try self.airBitOr(inst)"), + .xor => @panic("TODO try self.airXor(inst)"), + .shr, .shr_exact => @panic("TODO try self.airShr(inst)"), + + .alloc => @panic("TODO try self.airAlloc(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 => try self.airBlock(inst), + .br => @panic("TODO try self.airBr(inst)"), + .breakpoint => 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 => 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)"), + .trunc => @panic("TODO try self.airTrunc(inst)"), + .bool_to_int => @panic("TODO try self.airBoolToInt(inst)"), + .is_non_null => @panic("TODO try self.airIsNonNull(inst)"), + .is_non_null_ptr => @panic("TODO try self.airIsNonNullPtr(inst)"), + .is_null => @panic("TODO try self.airIsNull(inst)"), + .is_null_ptr => @panic("TODO try self.airIsNullPtr(inst)"), + .is_non_err => @panic("TODO try self.airIsNonErr(inst)"), + .is_non_err_ptr => @panic("TODO try self.airIsNonErrPtr(inst)"), + .is_err => @panic("TODO try self.airIsErr(inst)"), + .is_err_ptr => @panic("TODO try self.airIsErrPtr(inst)"), + .load => @panic("TODO try self.airLoad(inst)"), + .loop => @panic("TODO try self.airLoop(inst)"), + .not => @panic("TODO try self.airNot(inst)"), + .ptrtoint => @panic("TODO try self.airPtrToInt(inst)"), + .ret => try self.airRet(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)"), + .int_to_float => @panic("TODO try self.airIntToFloat(inst)"), + .float_to_int => @panic("TODO try self.airFloatToInt(inst)"), + .cmpxchg_strong => @panic("TODO try self.airCmpxchg(inst)"), + .cmpxchg_weak => @panic("TODO try self.airCmpxchg(inst)"), + .atomic_rmw => @panic("TODO try self.airAtomicRmw(inst)"), + .atomic_load => @panic("TODO try self.airAtomicLoad(inst)"), + .memcpy => @panic("TODO try self.airMemcpy(inst)"), + .memset => @panic("TODO try self.airMemset(inst)"), + .set_union_tag => @panic("TODO try self.airSetUnionTag(inst)"), + .get_union_tag => @panic("TODO try self.airGetUnionTag(inst)"), + .clz => @panic("TODO try self.airClz(inst)"), + .ctz => @panic("TODO try self.airCtz(inst)"), + .popcount => @panic("TODO try self.airPopcount(inst)"), + .byte_swap => @panic("TODO try self.airByteSwap(inst)"), + .bit_reverse => @panic("TODO try self.airBitReverse(inst)"), + .tag_name => @panic("TODO try self.airTagName(inst)"), + .error_name => @panic("TODO try self.airErrorName(inst)"), + .splat => @panic("TODO try self.airSplat(inst)"), + .select => @panic("TODO try self.airSelect(inst)"), + .shuffle => @panic("TODO try self.airShuffle(inst)"), + .reduce => @panic("TODO try self.airReduce(inst)"), + .aggregate_init => @panic("TODO try self.airAggregateInit(inst)"), + .union_init => @panic("TODO try self.airUnionInit(inst)"), + .prefetch => @panic("TODO try self.airPrefetch(inst)"), + .mul_add => @panic("TODO try self.airMulAdd(inst)"), + + .dbg_var_ptr, + .dbg_var_val, + => try self.airDbgVar(inst), + + .dbg_inline_begin, + .dbg_inline_end, + => try self.airDbgInline(inst), + + .dbg_block_begin, + .dbg_block_end, + => try self.airDbgBlock(inst), + + .call => try self.airCall(inst, .auto), + .call_always_tail => try self.airCall(inst, .always_tail), + .call_never_tail => try self.airCall(inst, .never_tail), + .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)"), + .atomic_store_release => @panic("TODO try self.airAtomicStore(inst, .Release)"), + .atomic_store_seq_cst => @panic("TODO try self.airAtomicStore(inst, .SeqCst)"), + + .struct_field_ptr_index_0 => @panic("TODO try self.airStructFieldPtrIndex(inst, 0)"), + .struct_field_ptr_index_1 => @panic("TODO try self.airStructFieldPtrIndex(inst, 1)"), + .struct_field_ptr_index_2 => @panic("TODO try self.airStructFieldPtrIndex(inst, 2)"), + .struct_field_ptr_index_3 => @panic("TODO try self.airStructFieldPtrIndex(inst, 3)"), + + .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"), + + .switch_br => try self.airSwitch(inst), + .slice_ptr => @panic("TODO try self.airSlicePtr(inst)"), + .slice_len => @panic("TODO try self.airSliceLen(inst)"), + + .ptr_slice_len_ptr => @panic("TODO try self.airPtrSliceLenPtr(inst)"), + .ptr_slice_ptr_ptr => @panic("TODO try self.airPtrSlicePtrPtr(inst)"), + + .array_elem_val => @panic("TODO try self.airArrayElemVal(inst)"), + .slice_elem_val => @panic("TODO try self.airSliceElemVal(inst)"), + .slice_elem_ptr => @panic("TODO try self.airSliceElemPtr(inst)"), + .ptr_elem_val => @panic("TODO try self.airPtrElemVal(inst)"), + .ptr_elem_ptr => @panic("TODO try self.airPtrElemPtr(inst)"), + + .constant => unreachable, // excluded from function bodies + .const_ty => unreachable, // excluded from function bodies + .unreach => self.finishAirBookkeeping(), + + .optional_payload => @panic("TODO try self.airOptionalPayload(inst)"), + .optional_payload_ptr => @panic("TODO try self.airOptionalPayloadPtr(inst)"), + .optional_payload_ptr_set => @panic("TODO try self.airOptionalPayloadPtrSet(inst)"), + .unwrap_errunion_err => @panic("TODO try self.airUnwrapErrErr(inst)"), + .unwrap_errunion_payload => @panic("TODO try self.airUnwrapErrPayload(inst)"), + .unwrap_errunion_err_ptr => @panic("TODO try self.airUnwrapErrErrPtr(inst)"), + .unwrap_errunion_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(inst)"), + .errunion_payload_ptr_set => @panic("TODO try self.airErrUnionPayloadPtrSet(inst)"), + + .wrap_optional => @panic("TODO try self.airWrapOptional(inst)"), + .wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"), + .wrap_errunion_err => @panic("TODO try self.airWrapErrUnionErr(inst)"), + + .wasm_memory_size => unreachable, + .wasm_memory_grow => unreachable, + // zig fmt: on + } + + if (std.debug.runtime_safety) { + if (self.air_bookkeeping < old_air_bookkeeping + 1) { + std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[inst] }); + } + } + } +} + +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 = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i .. extra_i + extra.data.outputs_len]); + extra_i += outputs.len; + const inputs = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i .. extra_i + extra.data.inputs_len]); + extra_i += inputs.len; + + const dead = !is_volatile and self.liveness.isUnused(inst); + const result: MCValue = if (dead) .dead else result: { + if (outputs.len > 1) { + return self.fail("TODO implement codegen for asm with more than 1 output", .{}); + } + + const output_constraint: ?[]const u8 = for (outputs) |output| { + if (output != .none) { + return self.fail("TODO implement codegen for non-expr asm", .{}); + } + const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += (constraint.len + name.len + (2 + 3)) / 4; + + break constraint; + } else null; + + for (inputs) |input| { + const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); + const constraint = std.mem.sliceTo(input_bytes, 0); + const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += (constraint.len + name.len + (2 + 3)) / 4; + + if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { + return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); + } + const reg_name = constraint[1 .. constraint.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail("unrecognized register: '{s}'", .{reg_name}); + + const arg_mcv = try self.resolveInst(input); + try self.register_manager.getReg(reg, null); + try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); + } + + { + var clobber_i: u32 = 0; + while (clobber_i < clobbers_len) : (clobber_i += 1) { + const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + extra_i += clobber.len / 4 + 1; + + // TODO honor these + } + } + + const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; + + if (mem.eql(u8, asm_source, "ta 0x6d")) { + _ = try self.addInst(.{ + .tag = .tcc, + .data = .{ + .trap = .{ + .is_imm = true, + .cond = 0b1000, // TODO need to look into changing this into an enum + .rs2_or_imm = .{ .imm = 0x6d }, + }, + }, + }); + } else { + return self.fail("TODO implement a full SPARCv9 assembly parsing", .{}); + } + + if (output_constraint) |output| { + if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { + return self.fail("unrecognized asm output constraint: '{s}'", .{output}); + } + const reg_name = output[2 .. output.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail("unrecognized register: '{s}'", .{reg_name}); + break :result MCValue{ .register = reg }; + } else { + break :result MCValue{ .none = {} }; + } + }; + + simple: { + var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); + var buf_index: usize = 0; + for (outputs) |output| { + if (output == .none) continue; + + if (buf_index >= buf.len) break :simple; + buf[buf_index] = output; + buf_index += 1; + } + if (buf_index + inputs.len > buf.len) break :simple; + std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); + return self.finishAir(inst, result, buf); + } + + var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); + for (outputs) |output| { + if (output == .none) continue; + + bt.feed(output); + } + for (inputs) |input| { + bt.feed(input); + } + return bt.finishAir(result); +} + +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.genArgDbgInfo(inst, mcv, @intCast(u32, 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 airBreakpoint(self: *Self) !void { + // ta 0x01 + _ = try self.addInst(.{ + .tag = .tcc, + .data = .{ + .trap = .{ + .is_imm = true, + .cond = 0b1000, // TODO need to look into changing this into an enum + .rs2_or_imm = .{ .imm = 0x01 }, + }, + }, + }); + return self.finishAirBookkeeping(); +} + +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 = @ptrCast([]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, + .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", .{}); + }, + } + } + + // Due to incremental compilation, how function calls are generated depends + // on linking. + if (self.air.value(callee)) |func_value| { + if (self.bin_file.tag == link.File.Elf.base_tag) { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + const mod = self.bin_file.options.module.?; + break :blk @intCast(u32, got.p_vaddr + mod.declPtr(func.owner_decl).link.elf.offset_table_index * ptr_bytes); + } else unreachable; + + try self.genSetReg(Type.initTag(.usize), .o7, .{ .memory = got_addr }); + + _ = try self.addInst(.{ + .tag = .jmpl, + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = .o7, + .rs1 = .o7, + .rs2_or_imm = .{ .rs2 = .g0 }, + }, + }, + }); + + // TODO Find a way to fill this delay slot + _ = try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); + } else if (func_value.castTag(.extern_fn)) |_| { + return self.fail("TODO implement calling extern functions", .{}); + } else { + return self.fail("TODO implement calling bitcasted functions", .{}); + } + } else @panic("TODO SPARCv9 currently does not support non-ELF binaries"); + } else { + assert(ty.zigTypeTag() == .Pointer); + const mcv = try self.resolveInst(callee); + try self.genSetReg(ty, .o7, mcv); + + _ = try self.addInst(.{ + .tag = .jmpl, + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = .o7, + .rs1 = .o7, + .rs2_or_imm = .{ .rs2 = .g0 }, + }, + }, + }); + + // TODO Find a way to fill this delay slot + _ = try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); + } + + const result = info.return_value; + + if (args.len + 1 <= Liveness.bpi - 1) { + var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); + buf[0] = callee; + std.mem.copy(Air.Inst.Ref, buf[1..], args); + return self.finishAir(inst, result, buf); + } + + @panic("TODO handle return value with BigTomb"); +} + +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 airRet(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + try self.ret(operand); + return self.finishAir(inst, .dead, .{ un_op, .none, .none }); +} + +fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const ptr = try self.resolveInst(un_op); + _ = ptr; + 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 + +/// Adds a Type to the .debug_info at the current position. The bytes will be populated later, +/// after codegen for this symbol is done. +fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void { + switch (self.debug_output) { + .dwarf => |dw| { + assert(ty.hasRuntimeBits()); + const dbg_info = &dw.dbg_info; + const index = dbg_info.items.len; + try dbg_info.resize(index + 4); // DW.AT.type, DW.FORM.ref4 + const mod = self.bin_file.options.module.?; + const atom = switch (self.bin_file.tag) { + .elf => &mod.declPtr(self.mod_fn.owner_decl).link.elf.dbg_info_atom, + else => unreachable, + }; + try dw.addTypeReloc(atom, ty, @intCast(u32, index), null); + }, + else => {}, + } +} + +fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { + const gpa = self.gpa; + try self.mir_instructions.ensureUnusedCapacity(gpa, 1); + const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); + self.mir_instructions.appendAssumeCapacity(inst); + 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 abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { + const mod = self.bin_file.options.module.?; + return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)}); + }; + // TODO swap this for inst.ty.ptrAlign + const abi_align = elem_ty.abiAlignment(self.target.*); + return self.allocMem(inst, abi_size, abi_align); +} + +fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue { + const elem_ty = self.air.typeOfIndex(inst); + const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { + const mod = self.bin_file.options.module.?; + return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)}); + }; + const abi_align = elem_ty.abiAlignment(self.target.*); + if (abi_align > self.stack_align) + self.stack_align = abi_align; + + if (reg_ok) { + // Make sure the type can fit in a register before we try to allocate one. + if (abi_size <= 8) { + if (self.register_manager.tryAllocReg(inst)) |reg| { + return MCValue{ .register = reg }; + } + } + } + const stack_offset = try self.allocMem(inst, abi_size, abi_align); + return MCValue{ .stack_offset = stack_offset }; +} + +/// Copies a value to a register without tracking the register. The register is not considered +/// allocated. A second call to `copyToTmpRegister` may return the same register. +/// This can have a side effect of spilling instructions to the stack to free up a register. +fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register { + const reg = try self.register_manager.allocReg(null); + try self.genSetReg(ty, reg, mcv); + return reg; +} + +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); +} + +fn fail(self: *Self, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + assert(self.err_msg == null); + 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 genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { + const ty = self.air.instructions.items(.data)[inst].ty; + const name = self.mod_fn.getParamName(arg_index); + const name_with_null = name.ptr[0 .. name.len + 1]; + + switch (mcv) { + .register => |reg| { + switch (self.debug_output) { + .dwarf => |dw| { + const dbg_info = &dw.dbg_info; + try dbg_info.ensureUnusedCapacity(3); + dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter)); + dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc + 1, // ULEB128 dwarf expression length + reg.dwarfLocOp(), + }); + try dbg_info.ensureUnusedCapacity(5 + name_with_null.len); + try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 + dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string + }, + else => {}, + } + }, + .stack_offset => |offset| { + _ = offset; + switch (self.debug_output) { + .dwarf => {}, + else => {}, + } + }, + else => {}, + } +} + +fn genLoad(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void { + assert(off_type == Register or off_type == i13); + + const is_imm = (off_type == i13); + const rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off }; + + switch (abi_size) { + 1, 2, 4, 8 => { + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => .ldub, + 2 => .lduh, + 4 => .lduw, + 8 => .ldx, + else => unreachable, // unexpected abi size + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ + .arithmetic_3op = .{ + .is_imm = is_imm, + .rd = value_reg, + .rs1 = addr_reg, + .rs2_or_imm = rs2_or_imm, + }, + }, + }); + }, + 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}), + else => unreachable, + } +} + +fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { + switch (mcv) { + .dead => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // Write the debug undefined value. + return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }); + }, + .ptr_stack_offset => |off| { + const simm13 = math.cast(u12, off) catch + return self.fail("TODO larger stack offsets", .{}); + + _ = try self.addInst(.{ + .tag = .add, + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = .sp, + .rs2_or_imm = .{ .imm = simm13 }, + }, + }, + }); + }, + .immediate => |x| { + if (x <= math.maxInt(u12)) { + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = .g0, + .rs2_or_imm = .{ .imm = @truncate(u12, x) }, + }, + }, + }); + } else if (x <= math.maxInt(u32)) { + _ = try self.addInst(.{ + .tag = .sethi, + .data = .{ + .sethi = .{ + .rd = reg, + .imm = @truncate(u22, x >> 10), + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = @truncate(u10, x) }, + }, + }, + }); + } else if (x <= math.maxInt(u44)) { + try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 12) }); + + _ = try self.addInst(.{ + .tag = .sllx, + .data = .{ + .shift = .{ + .is_imm = true, + .width = .shift64, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = 12 }, + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = @truncate(u12, x) }, + }, + }, + }); + } else { + // Need to allocate a temporary register to load 64-bit immediates. + const tmp_reg = try self.register_manager.allocReg(null); + + try self.genSetReg(ty, tmp_reg, .{ .immediate = @truncate(u32, x) }); + try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 32) }); + + _ = try self.addInst(.{ + .tag = .sllx, + .data = .{ + .shift = .{ + .is_imm = true, + .width = .shift64, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = 32 }, + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .rs2 = tmp_reg }, + }, + }, + }); + } + }, + .register => |src_reg| { + // If the registers are the same, nothing to do. + if (src_reg.id() == reg.id()) + return; + + // or %g0, src, dst (aka mov src, dst) + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = reg, + .rs1 = .g0, + .rs2_or_imm = .{ .rs2 = src_reg }, + }, + }, + }); + }, + .memory => |addr| { + // The value is in memory at a hard-coded address. + // If the type is a pointer, it means the pointer address is at this memory location. + try self.genSetReg(ty, reg, .{ .immediate = addr }); + try self.genLoad(reg, reg, i13, 0, ty.abiSize(self.target.*)); + }, + .stack_offset => |off| { + const real_offset = off + abi.stack_bias + abi.stack_save_area; + const simm13 = math.cast(i13, real_offset) catch + return self.fail("TODO larger stack offsets", .{}); + try self.genLoad(reg, .sp, i13, simm13, ty.abiSize(self.target.*)); + }, + } +} + +fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { + const abi_size = ty.abiSize(self.target.*); + switch (mcv) { + .dead => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // TODO Upgrade this to a memset call when we have that available. + switch (ty.abiSize(self.target.*)) { + 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }), + 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }), + 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), + 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), + else => return self.fail("TODO implement memset", .{}), + } + }, + .immediate, + .ptr_stack_offset, + => { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); + }, + .register => |reg| { + const real_offset = stack_offset + abi.stack_bias + abi.stack_save_area; + const simm13 = math.cast(i13, real_offset) catch + return self.fail("TODO larger stack offsets", .{}); + return self.genStore(reg, .sp, i13, simm13, abi_size); + }, + .memory, .stack_offset => return self.fail("TODO implement memcpy", .{}), + } +} + +fn genStore(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void { + assert(off_type == Register or off_type == i13); + + const is_imm = (off_type == i13); + const rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off }; + + switch (abi_size) { + 1, 2, 4, 8 => { + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => .stb, + 2 => .sth, + 4 => .stw, + 8 => .stx, + else => unreachable, // unexpected abi size + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ + .arithmetic_3op = .{ + .is_imm = is_imm, + .rd = value_reg, + .rs1 = addr_reg, + .rs2_or_imm = rs2_or_imm, + }, + }, + }); + }, + 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}), + else => unreachable, + } +} + +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_index); + } + const target = self.target.*; + + switch (typed_value.ty.zigTypeTag()) { + .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.fail("TODO implement int genTypedValue of > 64 bits", .{}); + } + }, + .ComptimeInt => unreachable, // semantic analysis prevents this + .ComptimeFloat => unreachable, // semantic analysis prevents this + 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 iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb { + try self.ensureProcessDeathCapacity(operand_count + 1); + return BigTomb{ + .function = self, + .inst = inst, + .tomb_bits = self.liveness.getTombBits(inst), + .big_tomb_bits = self.liveness.special.get(inst) orelse 0, + .bit_index = 0, + }; +} + +fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + + // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? + if (tv.ty.zigTypeTag() == .Pointer) blk: { + if (tv.ty.castPtrToFn()) |_| break :blk; + if (!tv.ty.elemType2().hasRuntimeBits()) { + return MCValue.none; + } + } + + const mod = self.bin_file.options.module.?; + const decl = mod.declPtr(decl_index); + + mod.markDeclAlive(decl); + if (self.bin_file.cast(link.File.Elf)) |elf_file| { + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; + return MCValue{ .memory = got_addr }; + } else { + return self.fail("TODO codegen non-ELF const Decl pointer", .{}); + } +} + +fn parseRegName(name: []const u8) ?Register { + if (@hasDecl(Register, "parseRegName")) { + return Register.parseRegName(name); + } + return std.meta.stringToEnum(Register, name); +} + +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 sparc64", .{}); + } + } + }, + else => return self.fail("TODO implement function parameters for {} on sparc64", .{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 ret(self: *Self, mcv: MCValue) !void { + const ret_ty = self.fn_type.fnReturnType(); + try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); + + // Just add space for an instruction, patch this later + const index = try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); + try self.exitlude_jump_relocs.append(self.gpa, 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; +} + +/// Sets the value without any modifications to register allocation metadata or stack allocation metadata. +fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { + switch (loc) { + .none => return, + .register => |reg| return self.genSetReg(ty, reg, val), + .stack_offset => |off| return self.genSetStack(ty, off, val), + .memory => { + return self.fail("TODO implement setRegOrMem for memory", .{}); + }, + else => unreachable, + } +} + +pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void { + const stack_mcv = try self.allocRegOrMem(inst, false); + log.debug("spilling {d} to stack mcv {any}", .{ inst, stack_mcv }); + const reg_mcv = self.getResolvedInstValue(inst); + assert(reg == reg_mcv.register); + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + try branch.inst_table.put(self.gpa, inst, stack_mcv); + try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv); +} + +/// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`. +fn wantSafety(self: *Self) bool { + return switch (self.bin_file.options.optimize_mode) { + .Debug => true, + .ReleaseSafe => true, + .ReleaseFast => false, + .ReleaseSmall => false, + }; +} diff --git a/src/arch/sparc64/Emit.zig b/src/arch/sparc64/Emit.zig new file mode 100644 index 0000000000000000000000000000000000000000..81ae062c616af96a0ff6dbd9bcc7b2dcd8963f12 --- /dev/null +++ b/src/arch/sparc64/Emit.zig @@ -0,0 +1,274 @@ +//! 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_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 sparc64 bpcc"), + + .call => @panic("TODO implement sparc64 call"), + + .jmpl => try emit.mirArithmetic3Op(inst), + + .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 sparc64 sllx"), + + .stb => try emit.mirArithmetic3Op(inst), + .sth => try emit.mirArithmetic3Op(inst), + .stw => try emit.mirArithmetic3Op(inst), + .stx => try emit.mirArithmetic3Op(inst), + + .sub => try emit.mirArithmetic3Op(inst), + + .tcc => try emit.mirTrap(inst), + } + } +} + +pub fn deinit(emit: *Emit) void { + emit.* = undefined; +} + +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(emit: *Emit) !void { + switch (emit.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); + try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); + }, + .plan9 => {}, + .none => {}, + } +} + +fn mirDebugEpilogueBegin(emit: *Emit) !void { + switch (emit.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); + try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.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)), + .jmpl => try emit.writeInstruction(Instruction.jmpl(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)), + .stb => try emit.writeInstruction(Instruction.stb(i13, rs1, imm, rd)), + .sth => try emit.writeInstruction(Instruction.sth(i13, rs1, imm, rd)), + .stw => try emit.writeInstruction(Instruction.stw(i13, rs1, imm, rd)), + .stx => try emit.writeInstruction(Instruction.stx(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)), + .jmpl => try emit.writeInstruction(Instruction.jmpl(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)), + .stb => try emit.writeInstruction(Instruction.stb(Register, rs1, rs2, rd)), + .sth => try emit.writeInstruction(Instruction.sth(Register, rs1, rs2, rd)), + .stw => try emit.writeInstruction(Instruction.stw(Register, rs1, rs2, rd)), + .stx => try emit.writeInstruction(Instruction.stx(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(emit: *Emit, line: u32, column: u32) !void { + const delta_line = @intCast(i32, line) - @intCast(i32, emit.prev_di_line); + const delta_pc: usize = emit.code.items.len - emit.prev_di_pc; + switch (emit.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); + emit.prev_di_pc = emit.code.items.len; + emit.prev_di_line = line; + emit.prev_di_column = column; + emit.prev_di_pc = emit.code.items.len; + }, + else => {}, + } +} + +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); +} diff --git a/src/arch/sparc64/Mir.zig b/src/arch/sparc64/Mir.zig new file mode 100644 index 0000000000000000000000000000000000000000..ef0be93f4cef33dedf6f5ad3d65ae294bf0a3871 --- /dev/null +++ b/src/arch/sparc64/Mir.zig @@ -0,0 +1,256 @@ +//! Machine Intermediate Representation. +//! This data is produced by SPARCv9 Codegen or SPARCv9 assembly parsing +//! These instructions have a 1:1 correspondence with machine code instructions +//! for the target. MIR can be lowered to source-annotated textual assembly code +//! instructions, or it can be lowered to machine code. +//! 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: 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 + /// This uses the arithmetic_3op field. + // TODO add other operations. + add, + + /// A.7 Branch on Integer Condition Codes with Prediction (BPcc) + /// This uses the branch_predict field. + bpcc, + + /// A.8 Call and Link + /// This uses the branch_link field. + call, + + /// A.24 Jump and Link + /// This uses the arithmetic_3op field. + jmpl, + + /// A.27 Load Integer + /// This 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 + /// This uses the arithmetic_3op field. + // TODO add other operations. + @"or", + + /// A.40 No Operation + /// This uses the nop field. + nop, + + /// A.45 RETURN + /// This uses the arithmetic_2op field. + @"return", + + /// A.46 SAVE and RESTORE + /// This uses the arithmetic_3op field. + save, + restore, + + /// A.48 SETHI + /// This uses the sethi field. + sethi, + + /// A.49 Shift + /// This uses the shift field. + // TODO add other operations. + sllx, + + /// A.54 Store Integer + /// This uses the arithmetic_3op field. + /// Note that the std variant of this instruction is deprecated, so do not emit + /// it unless specifically requested (e.g. by inline assembly). + // TODO add other operations. + stb, + sth, + stw, + stx, + + /// A.56 Subtract + /// This uses the arithmetic_3op field. + // TODO add other operations. + sub, + + /// A.61 Trap on Integer Condition Codes (Tcc) + /// This 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: 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, + }, + + /// 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) { + 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, + }; +} diff --git a/src/arch/sparc64/abi.zig b/src/arch/sparc64/abi.zig new file mode 100644 index 0000000000000000000000000000000000000000..a9001c7dc746c06a085f926439a02aa6806ec3b9 --- /dev/null +++ b/src/arch/sparc64/abi.zig @@ -0,0 +1,37 @@ +const bits = @import("bits.zig"); +const Register = bits.Register; + +// SPARCv9 stack constants. +// See: Registers and the Stack Frame, page 3P-8, SCD 2.4.1. + +// On SPARCv9, %sp points to top of stack + stack bias, +// and %fp points to top of previous frame + stack bias. +pub const stack_bias = 2047; + +// The first 176 bytes of the stack is reserved for register saving purposes. +// SPARCv9 requires to reserve space in the stack for the first six arguments, +// even though they are usually passed in registers. +pub const stack_save_area = 176; + +// 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 }; +pub const c_abi_int_return_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3" }; diff --git a/src/arch/sparc64/bits.zig b/src/arch/sparc64/bits.zig new file mode 100644 index 0000000000000000000000000000000000000000..e66b24f6171608c09f7797426089eb82a28d4deb --- /dev/null +++ b/src/arch/sparc64/bits.zig @@ -0,0 +1,1256 @@ +const std = @import("std"); +const DW = std.dwarf; +const assert = std.debug.assert; +const testing = std.testing; + +/// General purpose registers in the SPARCv9 instruction set +pub const Register = enum(u6) { + // zig fmt: off + g0, g1, g2, g3, g4, g5, g6, g7, + o0, o1, o2, o3, o4, o5, o6, o7, + l0, l1, l2, l3, l4, l5, l6, l7, + @"i0", @"i1", @"i2", @"i3", @"i4", @"i5", @"i6", @"i7", + + sp = 46, // stack pointer (o6) + fp = 62, // frame pointer (i6) + // zig fmt: on + + pub fn id(self: Register) u5 { + return @truncate(u5, @enumToInt(self)); + } + + pub fn enc(self: Register) u5 { + // For integer registers, enc() == id(). + return self.id(); + } + + pub fn dwarfLocOp(reg: Register) u8 { + return @as(u8, reg.id()) + DW.OP.reg0; + } +}; + +test "Register.id" { + // SP + try testing.expectEqual(@as(u5, 14), Register.o6.id()); + try testing.expectEqual(Register.o6.id(), Register.sp.id()); + + // FP + try testing.expectEqual(@as(u5, 30), Register.@"i6".id()); + try testing.expectEqual(Register.@"i6".id(), Register.fp.id()); + + // x0 + try testing.expectEqual(@as(u5, 0), Register.g0.id()); + try testing.expectEqual(@as(u5, 8), Register.o0.id()); + try testing.expectEqual(@as(u5, 16), Register.l0.id()); + try testing.expectEqual(@as(u5, 24), Register.@"i0".id()); +} + +test "Register.enc" { + // x0 + try testing.expectEqual(@as(u5, 0), Register.g0.enc()); + try testing.expectEqual(@as(u5, 8), Register.o0.enc()); + try testing.expectEqual(@as(u5, 16), Register.l0.enc()); + try testing.expectEqual(@as(u5, 24), Register.@"i0".enc()); + + // For integer registers, enc() == id(). + try testing.expectEqual(Register.g0.enc(), Register.g0.id()); + try testing.expectEqual(Register.o0.enc(), Register.o0.id()); + try testing.expectEqual(Register.l0.enc(), Register.l0.id()); + try testing.expectEqual(Register.@"i0".enc(), Register.@"i0".id()); +} + +/// Scalar floating point registers in the SPARCv9 instruction set +pub const FloatingPointRegister = enum(u7) { + // SPARCv9 has 64 f32 registers, 32 f64 registers, and 16 f128 registers, + // which are aliased in this way: + // + // | %d0 | %d2 | + // %q0 | %f0 | %f1 | %f2 | %f3 | + // | %d4 | %d6 | + // %q4 | %f4 | %f5 | %f6 | %f7 | + // ... + // | %d60 | %d62 | + // %q60 | %f60 | %f61 | %f62 | %f63 | + // + // Though, since the instructions uses five-bit addressing, only %f0-%f31 + // is usable with f32 instructions. + + // zig fmt: off + + // 32-bit registers + @"f0", @"f1", @"f2", @"f3", @"f4", @"f5", @"f6", @"f7", + @"f8", @"f9", @"f10", @"f11", @"f12", @"f13", @"f14", @"f15", + @"f16", @"f17", @"f18", @"f19", @"f20", @"f21", @"f22", @"f23", + @"f24", @"f25", @"f26", @"f27", @"f28", @"f29", @"f30", @"f31", + + // 64-bit registers + d0, d2, d4, d6, d8, d10, d12, d14, + d16, d18, d20, d22, d24, d26, d28, d30, + d32, d34, d36, d38, d40, d42, d44, d46, + d48, d50, d52, d54, d56, d58, d60, d62, + + // 128-bit registers + q0, q4, q8, q12, q16, q20, q24, q28, + q32, q36, q40, q44, q48, q52, q56, q60, + // zig fmt: on + + pub fn id(self: FloatingPointRegister) u6 { + return switch (self.size()) { + 32 => @truncate(u6, @enumToInt(self)), + 64 => @truncate(u6, (@enumToInt(self) - 32) * 2), + 128 => @truncate(u6, (@enumToInt(self) - 64) * 4), + else => unreachable, + }; + } + + pub fn enc(self: FloatingPointRegister) u5 { + // Floating point registers use an encoding scheme to map from the 6-bit + // ID to 5-bit encoded value. + // (See section 5.1.4.1 of SPARCv9 ISA specification) + + const reg_id = self.id(); + return @truncate(u5, reg_id | (reg_id >> 5)); + } + + /// Returns the bit-width of the register. + pub fn size(self: FloatingPointRegister) u8 { + return switch (@enumToInt(self)) { + 0...31 => 32, + 32...63 => 64, + 64...79 => 128, + else => unreachable, + }; + } +}; + +test "FloatingPointRegister.id" { + // Low region + try testing.expectEqual(@as(u6, 0), FloatingPointRegister.q0.id()); + try testing.expectEqual(FloatingPointRegister.q0.id(), FloatingPointRegister.d0.id()); + try testing.expectEqual(FloatingPointRegister.d0.id(), FloatingPointRegister.@"f0".id()); + + try testing.expectEqual(@as(u6, 28), FloatingPointRegister.q28.id()); + try testing.expectEqual(FloatingPointRegister.q28.id(), FloatingPointRegister.d28.id()); + try testing.expectEqual(FloatingPointRegister.d28.id(), FloatingPointRegister.@"f28".id()); + + // High region + try testing.expectEqual(@as(u6, 32), FloatingPointRegister.q32.id()); + try testing.expectEqual(FloatingPointRegister.q32.id(), FloatingPointRegister.d32.id()); + + try testing.expectEqual(@as(u6, 60), FloatingPointRegister.q60.id()); + try testing.expectEqual(FloatingPointRegister.q60.id(), FloatingPointRegister.d60.id()); +} + +test "FloatingPointRegister.enc" { + // f registers + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.@"f0".enc()); + try testing.expectEqual(@as(u5, 1), FloatingPointRegister.@"f1".enc()); + try testing.expectEqual(@as(u5, 31), FloatingPointRegister.@"f31".enc()); + + // d registers + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.enc()); + try testing.expectEqual(@as(u5, 1), FloatingPointRegister.d32.enc()); + try testing.expectEqual(@as(u5, 31), FloatingPointRegister.d62.enc()); + + // q registers + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.enc()); + try testing.expectEqual(@as(u5, 1), FloatingPointRegister.q32.enc()); + try testing.expectEqual(@as(u5, 29), FloatingPointRegister.q60.enc()); +} + +/// Represents an instruction in the SPARCv9 instruction set +pub const Instruction = union(enum) { + // Some of the instruction formats have several minor formats, here I + // 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: struct { + op: u2 = 0b01, + disp30: u30, + }, + + // Format 2 (op = 0): SETHI & Branches (Bicc, BPcc, BPr, FBfcc, FBPfcc) + format_2a: struct { + op: u2 = 0b00, + rd: u5, + op2: u3, + imm22: u22, + }, + format_2b: struct { + op: u2 = 0b00, + a: u1, + cond: u4, + op2: u3, + disp22: u22, + }, + format_2c: struct { + op: u2 = 0b00, + a: u1, + cond: u4, + op2: u3, + cc1: u1, + cc0: u1, + p: u1, + disp19: u19, + }, + format_2d: struct { + op: u2 = 0b00, + a: u1, + fixed: u1 = 0b0, + rcond: u3, + op2: u3, + d16hi: u2, + p: u1, + rs1: u5, + d16lo: u14, + }, + + // Format 3 (op = 2 or 3): Arithmetic, Logical, MOVr, MEMBAR, Load, and Store + format_3a: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b0, + reserved: u8 = 0b00000000, + rs2: u5, + }, + format_3b: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + simm13: u13, + }, + format_3c: struct { + op: u2, + reserved1: u5 = 0b00000, + op3: u6, + rs1: u5, + i: u1 = 0b0, + reserved2: u8 = 0b00000000, + rs2: u5, + }, + format_3d: struct { + op: u2, + reserved: u5 = 0b00000, + op3: u6, + rs1: u5, + i: u1 = 0b1, + simm13: u13, + }, + format_3e: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b0, + rcond: u3, + reserved: u5 = 0b00000, + rs2: u5, + }, + format_3f: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + rcond: u3, + simm10: u10, + }, + format_3g: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + // See Errata 58 of SPARCv9 specification + // https://sparc.org/errata-for-v9/#58 + i: u1 = 0b1, + reserved: u8 = 0b00000000, + rs2: u5, + }, + format_3h: struct { + op: u2 = 0b10, + fixed1: u5 = 0b00000, + op3: u6 = 0b101000, + fixed2: u5 = 0b01111, + i: u1 = 0b1, + reserved: u6 = 0b000000, + cmask: u3, + mmask: u4, + }, + format_3i: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b0, + imm_asi: u8, + rs2: u5, + }, + format_3j: struct { + op: u2, + impl_dep1: u5, + op3: u6, + impl_dep2: u19, + }, + format_3k: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b0, + x: u1, + reserved: u7 = 0b0000000, + rs2: u5, + }, + format_3l: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + x: u1 = 0b0, + reserved: u7 = 0b0000000, + shcnt32: u5, + }, + format_3m: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + x: u1 = 0b1, + reserved: u6 = 0b000000, + shcnt64: u6, + }, + format_3n: struct { + op: u2, + rd: u5, + op3: u6, + reserved: u5 = 0b00000, + opf: u9, + rs2: u5, + }, + format_3o: struct { + op: u2, + fixed: u3 = 0b000, + cc1: u1, + cc0: u1, + op3: u6, + rs1: u5, + opf: u9, + rs2: u5, + }, + format_3p: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + opf: u9, + rs2: u5, + }, + format_3q: struct { + op: u2, + rd: u5, + op3: u6, + rs1: u5, + reserved: u14 = 0b00000000000000, + }, + format_3r: struct { + op: u2, + fcn: u5, + op3: u6, + reserved: u19 = 0b0000000000000000000, + }, + format_3s: struct { + op: u2, + rd: u5, + op3: u6, + reserved: u19 = 0b0000000000000000000, + }, + + //Format 4 (op = 2): MOVcc, FMOVr, FMOVcc, and Tcc + format_4a: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b0, + cc1: u1, + cc0: u1, + reserved: u6 = 0b000000, + rs2: u5, + }, + format_4b: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + cc1: u1, + cc0: u1, + simm11: u11, + }, + format_4c: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + cc2: u1, + cond: u4, + i: u1 = 0b0, + cc1: u1, + cc0: u1, + reserved: u6 = 0b000000, + rs2: u5, + }, + format_4d: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + cc2: u1, + cond: u4, + i: u1 = 0b1, + cc1: u1, + cc0: u1, + simm11: u11, + }, + format_4e: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + rs1: u5, + i: u1 = 0b1, + cc1: u1, + cc0: u1, + reserved: u4 = 0b0000, + sw_trap: u7, + }, + format_4f: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + rs1: u5, + fixed: u1 = 0b0, + rcond: u3, + opf_low: u5, + rs2: u5, + }, + format_4g: struct { + op: u2 = 0b10, + rd: u5, + op3: u6, + fixed: u1 = 0b0, + cond: u4, + opf_cc: u3, + opf_low: u6, + rs2: u5, + }, + + pub const CCR = enum(u3) { + fcc0, + fcc1, + fcc2, + fcc3, + icc, + reserved1, + xcc, + reserved2, + }; + + pub const RCondition = enum(u3) { + reserved1, + eq_zero, + le_zero, + lt_zero, + reserved2, + ne_zero, + gt_zero, + ge_zero, + }; + + pub const ASI = enum(u8) { + asi_nucleus = 0x04, + asi_nucleus_little = 0x0c, + asi_as_if_user_primary = 0x10, + asi_as_if_user_secondary = 0x11, + asi_as_if_user_primary_little = 0x18, + asi_as_if_user_secondary_little = 0x19, + asi_primary = 0x80, + asi_secondary = 0x81, + asi_primary_nofault = 0x82, + asi_secondary_nofault = 0x83, + asi_primary_little = 0x88, + asi_secondary_little = 0x89, + asi_primary_nofault_little = 0x8a, + asi_secondary_nofault_little = 0x8b, + }; + + pub const ShiftWidth = enum(u1) { + shift32, + shift64, + }; + + pub const MemOrderingConstraint = packed struct { + store_store: bool = false, + load_store: bool = false, + store_load: bool = false, + load_load: bool = false, + }; + + pub const MemCompletionConstraint = packed struct { + sync: bool = false, + mem_issue: bool = false, + lookaside: bool = false, + }; + + // TODO: Need to define an enum for `cond` values + // This is kinda challenging since the cond values have different meanings + // depending on whether it's operating on integer or FP CCR. + pub const Condition = u4; + + pub fn toU32(self: Instruction) u32 { + // TODO: Remove this once packed structs work. + return switch (self) { + .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| (@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| (@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| (@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| (@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| (@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); + + // In SPARC, branch target needs to be aligned to 4 bytes. + assert(udisp % 4 == 0); + + // Discard the last two bits since those are implicitly zero. + const udisp_truncated = @truncate(u30, udisp >> 2); + return Instruction{ + .format_1 = .{ + .disp30 = udisp_truncated, + }, + }; + } + + fn format2a(op2: u3, imm: u22, rd: Register) Instruction { + return Instruction{ + .format_2a = .{ + .rd = rd.enc(), + .op2 = op2, + .imm22 = imm, + }, + }; + } + + fn format2b(op2: u3, cond: Condition, annul: bool, disp: i24) Instruction { + const udisp = @bitCast(u24, disp); + + // In SPARC, branch target needs to be aligned to 4 bytes. + assert(udisp % 4 == 0); + + // Discard the last two bits since those are implicitly zero. + const udisp_truncated = @truncate(u22, udisp >> 2); + return Instruction{ + .format_2b = .{ + .a = @boolToInt(annul), + .cond = cond, + .op2 = op2, + .disp22 = udisp_truncated, + }, + }; + } + + fn format2c(op2: u3, cond: Condition, annul: bool, pt: bool, ccr: CCR, disp: i21) Instruction { + const udisp = @bitCast(u21, disp); + + // In SPARC, branch target needs to be aligned to 4 bytes. + assert(udisp % 4 == 0); + + // Discard the last two bits since those are implicitly zero. + const udisp_truncated = @truncate(u19, udisp >> 2); + + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_2c = .{ + .a = @boolToInt(annul), + .cond = cond, + .op2 = op2, + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .p = @boolToInt(pt), + .disp19 = udisp_truncated, + }, + }; + } + + fn format2d(op2: u3, rcond: RCondition, annul: bool, pt: bool, rs1: Register, disp: i18) Instruction { + const udisp = @bitCast(u18, disp); + + // In SPARC, branch target needs to be aligned to 4 bytes. + assert(udisp % 4 == 0); + + // Discard the last two bits since those are implicitly zero, + // and split it into low and high parts. + const udisp_truncated = @truncate(u16, udisp >> 2); + const udisp_hi = @truncate(u2, (udisp_truncated & 0b1100_0000_0000_0000) >> 14); + const udisp_lo = @truncate(u14, udisp_truncated & 0b0011_1111_1111_1111); + return Instruction{ + .format_2d = .{ + .a = @boolToInt(annul), + .rcond = @enumToInt(rcond), + .op2 = op2, + .p = @boolToInt(pt), + .rs1 = rs1.enc(), + .d16hi = udisp_hi, + .d16lo = udisp_lo, + }, + }; + } + + fn format3a(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3a = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .rs2 = rs2.enc(), + }, + }; + } + fn format3b(op: u2, op3: u6, rs1: Register, imm: i13, rd: Register) Instruction { + return Instruction{ + .format_3b = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .simm13 = @bitCast(u13, imm), + }, + }; + } + fn format3c(op: u2, op3: u6, rs1: Register, rs2: Register) Instruction { + return Instruction{ + .format_3c = .{ + .op = op, + .op3 = op3, + .rs1 = rs1.enc(), + .rs2 = rs2.enc(), + }, + }; + } + fn format3d(op: u2, op3: u6, rs1: Register, imm: i13) Instruction { + return Instruction{ + .format_3d = .{ + .op = op, + .op3 = op3, + .rs1 = rs1.enc(), + .simm13 = @bitCast(u13, imm), + }, + }; + } + fn format3e(op: u2, op3: u6, rcond: RCondition, rs1: Register, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3e = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .rcond = @enumToInt(rcond), + .rs2 = rs2.enc(), + }, + }; + } + fn format3f(op: u2, op3: u6, rcond: RCondition, rs1: Register, imm: i10, rd: Register) Instruction { + return Instruction{ + .format_3f = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .rcond = @enumToInt(rcond), + .simm10 = @bitCast(u10, imm), + }, + }; + } + fn format3g(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3g = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .rs2 = rs2.enc(), + }, + }; + } + fn format3h(cmask: MemCompletionConstraint, mmask: MemOrderingConstraint) Instruction { + return Instruction{ + .format_3h = .{ + .cmask = @bitCast(u3, cmask), + .mmask = @bitCast(u4, mmask), + }, + }; + } + fn format3i(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register, asi: ASI) Instruction { + return Instruction{ + .format_3i = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .imm_asi = @enumToInt(asi), + .rs2 = rs2.enc(), + }, + }; + } + fn format3j(op: u2, op3: u6, impl_dep1: u5, impl_dep2: u19) Instruction { + return Instruction{ + .format_3j = .{ + .op = op, + .impl_dep1 = impl_dep1, + .op3 = op3, + .impl_dep2 = impl_dep2, + }, + }; + } + fn format3k(op: u2, op3: u6, sw: ShiftWidth, rs1: Register, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3k = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .x = @enumToInt(sw), + .rs2 = rs2.enc(), + }, + }; + } + fn format3l(op: u2, op3: u6, rs1: Register, shift_count: u5, rd: Register) Instruction { + return Instruction{ + .format_3l = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .shcnt32 = shift_count, + }, + }; + } + fn format3m(op: u2, op3: u6, rs1: Register, shift_count: u6, rd: Register) Instruction { + return Instruction{ + .format_3m = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .shcnt64 = shift_count, + }, + }; + } + fn format3n(op: u2, op3: u6, opf: u9, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3n = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .opf = opf, + .rs2 = rs2.enc(), + }, + }; + } + fn format3o(op: u2, op3: u6, opf: u9, ccr: CCR, rs1: Register, rs2: Register) Instruction { + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_3o = .{ + .op = op, + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .op3 = op3, + .rs1 = rs1.enc(), + .opf = opf, + .rs2 = rs2.enc(), + }, + }; + } + fn format3p(op: u2, op3: u6, opf: u9, rs1: Register, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_3p = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .opf = opf, + .rs2 = rs2.enc(), + }, + }; + } + fn format3q(op: u2, op3: u6, rs1: Register, rd: Register) Instruction { + return Instruction{ + .format_3q = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + }, + }; + } + fn format3r(op: u2, op3: u6, fcn: u5) Instruction { + return Instruction{ + .format_3r = .{ + .op = op, + .fcn = fcn, + .op3 = op3, + }, + }; + } + fn format3s(op: u2, op3: u6, rd: Register) Instruction { + return Instruction{ + .format_3s = .{ + .op = op, + .rd = rd.enc(), + .op3 = op3, + }, + }; + } + + fn format4a(op3: u6, ccr: CCR, rs1: Register, rs2: Register, rd: Register) Instruction { + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_4a = .{ + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .rs2 = rs2.enc(), + }, + }; + } + + fn format4b(op3: u6, ccr: CCR, rs1: Register, imm: i11, rd: Register) Instruction { + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_4b = .{ + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .simm11 = @bitCast(u11, imm), + }, + }; + } + + fn format4c(op3: u6, cond: Condition, ccr: CCR, rs2: Register, rd: Register) Instruction { + const ccr_cc2 = @truncate(u1, @enumToInt(ccr) >> 2); + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_4c = .{ + .rd = rd.enc(), + .op3 = op3, + .cc2 = ccr_cc2, + .cond = cond, + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .rs2 = rs2.enc(), + }, + }; + } + + fn format4d(op3: u6, cond: Condition, ccr: CCR, imm: i11, rd: Register) Instruction { + const ccr_cc2 = @truncate(u1, @enumToInt(ccr) >> 2); + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_4d = .{ + .rd = rd.enc(), + .op3 = op3, + .cc2 = ccr_cc2, + .cond = cond, + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .simm11 = @bitCast(u11, imm), + }, + }; + } + + fn format4e(op3: u6, ccr: CCR, rs1: Register, rd: Register, sw_trap: u7) Instruction { + const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); + const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); + return Instruction{ + .format_4e = .{ + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .cc1 = ccr_cc1, + .cc0 = ccr_cc0, + .sw_trap = sw_trap, + }, + }; + } + + fn format4f( + op3: u6, + opf_low: u5, + rcond: RCondition, + rs1: Register, + rs2: Register, + rd: Register, + ) Instruction { + return Instruction{ + .format_4f = .{ + .rd = rd.enc(), + .op3 = op3, + .rs1 = rs1.enc(), + .rcond = @enumToInt(rcond), + .opf_low = opf_low, + .rs2 = rs2.enc(), + }, + }; + } + + fn format4g(op3: u6, opf_low: u6, opf_cc: u3, cond: Condition, rs2: Register, rd: Register) Instruction { + return Instruction{ + .format_4g = .{ + .rd = rd.enc(), + .op3 = op3, + .cond = cond, + .opf_cc = opf_cc, + .opf_low = opf_low, + .rs2 = rs2.enc(), + }, + }; + } + + // 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 jmpl(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { + return switch (s2) { + Register => format3a(0b10, 0b11_1000, rs1, rs2, rd), + i13 => format3b(0b10, 0b11_1000, 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 @"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 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 stb(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { + return switch (s2) { + Register => format3a(0b11, 0b00_0101, rs1, rs2, rd), + i13 => format3b(0b11, 0b00_0101, rs1, rs2, rd), + else => unreachable, + }; + } + + pub fn sth(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { + return switch (s2) { + Register => format3a(0b11, 0b00_0110, rs1, rs2, rd), + i13 => format3b(0b11, 0b00_0110, rs1, rs2, rd), + else => unreachable, + }; + } + + pub fn stw(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { + return switch (s2) { + Register => format3a(0b11, 0b00_0100, rs1, rs2, rd), + i13 => format3b(0b11, 0b00_0100, rs1, rs2, rd), + else => unreachable, + }; + } + + pub fn stx(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { + return switch (s2) { + Register => format3a(0b11, 0b00_1110, rs1, rs2, rd), + i13 => format3b(0b11, 0b00_1110, rs1, rs2, rd), + else => unreachable, + }; + } + + 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" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + // Note that the testcases might or might not be a valid instruction + // This is mostly just to check the behavior of the format packed structs + // since currently stage1 doesn't properly implement it in all cases + const testcases = [_]Testcase{ + .{ + .inst = Instruction.format1(4), + .expected = 0b01_000000000000000000000000000001, + }, + .{ + .inst = Instruction.format2a(4, 0, .g0), + .expected = 0b00_00000_100_0000000000000000000000, + }, + .{ + .inst = Instruction.format2b(6, 3, true, -4), + .expected = 0b00_1_0011_110_1111111111111111111111, + }, + .{ + .inst = Instruction.format2c(3, 0, false, true, .xcc, 8), + .expected = 0b00_0_0000_011_1_0_1_0000000000000000010, + }, + .{ + .inst = Instruction.format2d(7, .eq_zero, false, true, .o0, 20), + .expected = 0b00_0_0_001_111_00_1_01000_00000000000101, + }, + .{ + .inst = Instruction.format3a(3, 5, .g0, .o1, .l2), + .expected = 0b11_10010_000101_00000_0_00000000_01001, + }, + .{ + .inst = Instruction.format3b(3, 5, .g0, -1, .l2), + .expected = 0b11_10010_000101_00000_1_1111111111111, + }, + .{ + .inst = Instruction.format3c(3, 5, .g0, .o1), + .expected = 0b11_00000_000101_00000_0_00000000_01001, + }, + .{ + .inst = Instruction.format3d(3, 5, .g0, 0), + .expected = 0b11_00000_000101_00000_1_0000000000000, + }, + .{ + .inst = Instruction.format3e(3, 5, .ne_zero, .g0, .o1, .l2), + .expected = 0b11_10010_000101_00000_0_101_00000_01001, + }, + .{ + .inst = Instruction.format3f(3, 5, .ne_zero, .g0, -1, .l2), + .expected = 0b11_10010_000101_00000_1_101_1111111111, + }, + .{ + .inst = Instruction.format3g(3, 5, .g0, .o1, .l2), + .expected = 0b11_10010_000101_00000_1_00000000_01001, + }, + .{ + .inst = Instruction.format3h(.{}, .{}), + .expected = 0b10_00000_101000_01111_1_000000_000_0000, + }, + .{ + .inst = Instruction.format3i(3, 5, .g0, .o1, .l2, .asi_primary_little), + .expected = 0b11_10010_000101_00000_0_10001000_01001, + }, + .{ + .inst = Instruction.format3j(3, 5, 31, 0), + .expected = 0b11_11111_000101_0000000000000000000, + }, + .{ + .inst = Instruction.format3k(3, 5, .shift32, .g0, .o1, .l2), + .expected = 0b11_10010_000101_00000_0_0_0000000_01001, + }, + .{ + .inst = Instruction.format3l(3, 5, .g0, 31, .l2), + .expected = 0b11_10010_000101_00000_1_0_0000000_11111, + }, + .{ + .inst = Instruction.format3m(3, 5, .g0, 63, .l2), + .expected = 0b11_10010_000101_00000_1_1_000000_111111, + }, + .{ + .inst = Instruction.format3n(3, 5, 0, .o1, .l2), + .expected = 0b11_10010_000101_00000_000000000_01001, + }, + .{ + .inst = Instruction.format3o(3, 5, 0, .xcc, .o1, .l2), + .expected = 0b11_000_1_0_000101_01001_000000000_10010, + }, + .{ + .inst = Instruction.format3p(3, 5, 0, .g0, .o1, .l2), + .expected = 0b11_10010_000101_00000_000000000_01001, + }, + .{ + .inst = Instruction.format3q(3, 5, .g0, .o1), + .expected = 0b11_01001_000101_00000_00000000000000, + }, + .{ + .inst = Instruction.format3r(3, 5, 4), + .expected = 0b11_00100_000101_0000000000000000000, + }, + .{ + .inst = Instruction.format3s(3, 5, .g0), + .expected = 0b11_00000_000101_0000000000000000000, + }, + .{ + .inst = Instruction.format4a(8, .xcc, .g0, .o1, .l2), + .expected = 0b10_10010_001000_00000_0_1_0_000000_01001, + }, + .{ + .inst = Instruction.format4b(8, .xcc, .g0, -1, .l2), + .expected = 0b10_10010_001000_00000_1_1_0_11111111111, + }, + .{ + .inst = Instruction.format4c(8, 0, .xcc, .g0, .o1), + .expected = 0b10_01001_001000_1_0000_0_1_0_000000_00000, + }, + .{ + .inst = Instruction.format4d(8, 0, .xcc, 0, .l2), + .expected = 0b10_10010_001000_1_0000_1_1_0_00000000000, + }, + .{ + .inst = Instruction.format4e(8, .xcc, .g0, .o1, 0), + .expected = 0b10_01001_001000_00000_1_1_0_0000_0000000, + }, + .{ + .inst = Instruction.format4f(8, 4, .eq_zero, .g0, .o1, .l2), + .expected = 0b10_10010_001000_00000_0_001_00100_01001, + }, + .{ + .inst = Instruction.format4g(8, 4, 2, 0, .o1, .l2), + .expected = 0b10_10010_001000_0_0000_010_000100_01001, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + testing.expectEqual(case.expected, actual) catch |err| { + std.debug.print("error: {x}\n", .{err}); + std.debug.print("case: {x}\n", .{case}); + return err; + }; + } +} diff --git a/src/arch/sparcv9/CodeGen.zig b/src/arch/sparcv9/CodeGen.zig deleted file mode 100644 index 35d9a78dcdc9da1734c5f34a96e2cd3479ae4a3b..0000000000000000000000000000000000000000 --- a/src/arch/sparcv9/CodeGen.zig +++ /dev/null @@ -1,1791 +0,0 @@ -//! SPARCv9 codegen. -//! This lowers AIR into MIR. -//! For now this only implements medium/low code model with absolute addressing. -//! TODO add support for other code models. -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"); -const Emit = @import("Emit.zig"); -const Liveness = @import("../../Liveness.zig"); -const Type = @import("../../type.zig").Type; -const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError; -const FnResult = @import("../../codegen.zig").FnResult; -const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; -const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager; -const RegisterManager = RegisterManagerFn(Self, Register, &abi.allocatable_regs); - -const build_options = @import("build_options"); - -const bits = @import("bits.zig"); -const abi = @import("abi.zig"); -const Register = bits.Register; - -const Self = @This(); - -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; - -const RegisterView = enum(u1) { - caller, - callee, -}; - -gpa: Allocator, -air: Air, -liveness: Liveness, -bin_file: *link.File, -target: *const std.Target, -mod_fn: *const Module.Fn, -code: *std.ArrayList(u8), -debug_output: DebugInfoOutput, -err_msg: ?*ErrorMsg, -args: []MCValue, -ret_mcv: MCValue, -fn_type: Type, -arg_index: usize, -src_loc: Module.SrcLoc, -stack_align: u32, - -/// MIR Instructions -mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, -/// MIR extra data -mir_extra: std.ArrayListUnmanaged(u32) = .{}, - -/// Byte offset within the source file of the ending curly. -end_di_line: u32, -end_di_column: u32, - -/// 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. -exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, - -/// Whenever there is a runtime branch, we push a Branch onto this stack, -/// and pop it off when the runtime branch joins. This provides an "overlay" -/// of the table of mappings from instructions to `MCValue` from within the branch. -/// This way we can modify the `MCValue` for an instruction in different ways -/// within different branches. Special consideration is needed when a branch -/// joins with its parent, to make sure all instructions have the same MCValue -/// across each runtime branch upon joining. -branch_stack: *std.ArrayList(Branch), - -// Key is the block instruction -blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, - -register_manager: RegisterManager = .{}, - -/// Maps offset to what is stored there. -stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, - -/// Offset from the stack base, representing the end of the stack frame. -max_end_stack: u32 = 0, -/// Represents the current end stack offset. If there is no existing slot -/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. -next_stack_offset: u32 = 0, - -/// Debug field, used to find bugs in the compiler. -air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init, - -const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {}; - -const MCValue = union(enum) { - /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. - /// TODO Look into deleting this tag and using `dead` instead, since every use - /// of MCValue.none should be instead looking at the type and noticing it is 0 bits. - none, - /// Control flow will not allow this value to be observed. - unreach, - /// No more references to this value remain. - dead, - /// The value is undefined. - undef, - /// A pointer-sized integer that fits in a register. - /// If the type is a pointer, this is the pointer address in virtual address space. - immediate: u64, - /// The value is in a target-specific register. - register: Register, - /// The value is in memory at a hard-coded address. - /// If the type is a pointer, it means the pointer address is at this memory location. - memory: u64, - /// The value is one of the stack variables. - /// If the type is a pointer, it means the pointer address is in the stack at this offset. - stack_offset: u32, - /// The value is a pointer to one of the stack variables (payload is stack offset). - ptr_stack_offset: u32, - - fn isMemory(mcv: MCValue) bool { - return switch (mcv) { - .memory, .stack_offset => true, - else => false, - }; - } - - fn isImmediate(mcv: MCValue) bool { - return switch (mcv) { - .immediate => true, - else => false, - }; - } - - fn isMutable(mcv: MCValue) bool { - return switch (mcv) { - .none => unreachable, - .unreach => unreachable, - .dead => unreachable, - - .immediate, - .memory, - .ptr_stack_offset, - .undef, - => false, - - .register, - .stack_offset, - => true, - }; - } -}; - -const Branch = struct { - inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{}, - - fn deinit(self: *Branch, gpa: Allocator) void { - self.inst_table.deinit(gpa); - self.* = undefined; - } -}; - -const StackAllocation = struct { - inst: Air.Inst.Index, - /// TODO do we need size? should be determined by inst.ty.abiSize() - size: u32, -}; - -const BlockData = struct { - 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 - /// the location to store their block results. - mcv: MCValue, -}; - -const CallMCValues = struct { - args: []MCValue, - return_value: MCValue, - stack_byte_count: u32, - stack_align: u32, - - fn deinit(self: *CallMCValues, func: *Self) void { - func.gpa.free(self.args); - self.* = undefined; - } -}; - -const BigTomb = struct { - function: *Self, - inst: Air.Inst.Index, - tomb_bits: Liveness.Bpi, - big_tomb_bits: u32, - bit_index: usize, - - fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void { - const this_bit_index = bt.bit_index; - bt.bit_index += 1; - - const op_int = @enumToInt(op_ref); - if (op_int < Air.Inst.Ref.typed_value_map.len) return; - const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); - - if (this_bit_index < Liveness.bpi - 1) { - const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0; - if (!dies) return; - } else { - const big_bit_index = @intCast(u5, this_bit_index - (Liveness.bpi - 1)); - const dies = @truncate(u1, bt.big_tomb_bits >> big_bit_index) != 0; - if (!dies) return; - } - bt.function.processDeath(op_index); - } - - fn finishAir(bt: *BigTomb, result: MCValue) void { - const is_used = !bt.function.liveness.isUnused(bt.inst); - if (is_used) { - log.debug("%{d} => {}", .{ bt.inst, result }); - const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1]; - branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result); - } - bt.function.finishAirBookkeeping(); - } -}; - -pub fn generate( - bin_file: *link.File, - src_loc: Module.SrcLoc, - module_fn: *Module.Fn, - air: Air, - liveness: Liveness, - code: *std.ArrayList(u8), - debug_output: DebugInfoOutput, -) GenerateSymbolError!FnResult { - if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { - @panic("Attempted to compile for architecture that was disabled by build configuration"); - } - - const mod = bin_file.options.module.?; - const fn_owner_decl = mod.declPtr(module_fn.owner_decl); - assert(fn_owner_decl.has_tv); - const fn_type = fn_owner_decl.ty; - - var branch_stack = std.ArrayList(Branch).init(bin_file.allocator); - defer { - assert(branch_stack.items.len == 1); - branch_stack.items[0].deinit(bin_file.allocator); - branch_stack.deinit(); - } - try branch_stack.append(.{}); - - var function = Self{ - .gpa = bin_file.allocator, - .air = air, - .liveness = liveness, - .target = &bin_file.options.target, - .bin_file = bin_file, - .mod_fn = module_fn, - .code = code, - .debug_output = debug_output, - .err_msg = null, - .args = undefined, // populated after `resolveCallingConventionValues` - .ret_mcv = undefined, // populated after `resolveCallingConventionValues` - .fn_type = fn_type, - .arg_index = 0, - .branch_stack = &branch_stack, - .src_loc = src_loc, - .stack_align = undefined, - .end_di_line = module_fn.rbrace_line, - .end_di_column = module_fn.rbrace_column, - }; - defer function.stack.deinit(bin_file.allocator); - defer function.blocks.deinit(bin_file.allocator); - defer function.exitlude_jump_relocs.deinit(bin_file.allocator); - - 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.", .{}), - }, - else => |e| return e, - }; - defer call_info.deinit(&function); - - function.args = call_info.args; - function.ret_mcv = call_info.return_value; - function.stack_align = call_info.stack_align; - function.max_end_stack = call_info.stack_byte_count; - - function.gen() 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.", .{}), - }, - else => |e| return e, - }; - - var mir = Mir{ - .instructions = function.mir_instructions.toOwnedSlice(), - .extra = function.mir_extra.toOwnedSlice(bin_file.allocator), - }; - defer mir.deinit(bin_file.allocator); - - var emit = Emit{ - .mir = mir, - .bin_file = bin_file, - .debug_output = debug_output, - .target = &bin_file.options.target, - .src_loc = src_loc, - .code = code, - .prev_di_pc = 0, - .prev_di_line = module_fn.lbrace_line, - .prev_di_column = module_fn.lbrace_column, - }; - defer emit.deinit(); - - emit.emitMir() catch |err| switch (err) { - error.EmitFail => return FnResult{ .fail = emit.err_msg.? }, - else => |e| return e, - }; - - if (function.err_msg) |em| { - return FnResult{ .fail = em }; - } else { - return FnResult{ .appended = {} }; - } -} - -fn gen(self: *Self) !void { - const cc = self.fn_type.fnCallingConvention(); - if (cc != .Naked) { - // TODO Finish function prologue and epilogue for sparcv9. - - // save %sp, stack_save_area, %sp - const save_inst = try self.addInst(.{ - .tag = .save, - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = .sp, - .rs1 = .sp, - .rs2_or_imm = .{ .imm = -abi.stack_save_area }, - }, - }, - }); - - _ = try self.addInst(.{ - .tag = .dbg_prologue_end, - .data = .{ .nop = {} }, - }); - - try self.genBody(self.air.getMainBody()); - - _ = try self.addInst(.{ - .tag = .dbg_epilogue_begin, - .data = .{ .nop = {} }, - }); - - // exitlude jumps - if (self.exitlude_jump_relocs.items.len > 0 and - self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2) - { - // If the last Mir instruction (apart from the - // dbg_epilogue_begin) is the last exitlude jump - // relocation (which would just jump one instruction - // further), it can be safely removed - self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop()); - } - - for (self.exitlude_jump_relocs.items) |jmp_reloc| { - _ = jmp_reloc; - return self.fail("TODO add branches in sparcv9", .{}); - } - - // Backpatch stack offset - const total_stack_size = self.max_end_stack + abi.stack_save_area; // TODO + self.saved_regs_stack_space; - const stack_size = mem.alignForwardGeneric(u32, total_stack_size, self.stack_align); - if (math.cast(i13, stack_size)) |size| { - self.mir_instructions.set(save_inst, .{ - .tag = .save, - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = .sp, - .rs1 = .sp, - .rs2_or_imm = .{ .imm = -size }, - }, - }, - }); - } else |_| { - // TODO for large stacks, replace the prologue with: - // setx stack_size, %g1 - // save %sp, %g1, %sp - return self.fail("TODO SPARCv9: allow larger stacks", .{}); - } - - // return %i7 + 8 - _ = try self.addInst(.{ - .tag = .@"return", - .data = .{ - .arithmetic_2op = .{ - .is_imm = true, - .rs1 = .@"i7", - .rs2_or_imm = .{ .imm = 8 }, - }, - }, - }); - - // Branches in SPARC have a delay slot, that is, the instruction - // following it will unconditionally be executed. - // See: Section 3.2.3 Control Transfer in SPARCv9 manual. - // See also: https://arcb.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/delaybra.html - // TODO Find a way to fill this delay slot - // nop - _ = try self.addInst(.{ - .tag = .nop, - .data = .{ .nop = {} }, - }); - } else { - _ = try self.addInst(.{ - .tag = .dbg_prologue_end, - .data = .{ .nop = {} }, - }); - - try self.genBody(self.air.getMainBody()); - - _ = try self.addInst(.{ - .tag = .dbg_epilogue_begin, - .data = .{ .nop = {} }, - }); - } - - // Drop them off at the rbrace. - _ = try self.addInst(.{ - .tag = .dbg_line, - .data = .{ .dbg_line_column = .{ - .line = self.end_di_line, - .column = self.end_di_column, - } }, - }); -} - -fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { - const air_tags = self.air.instructions.items(.tag); - - for (body) |inst| { - const old_air_bookkeeping = self.air_bookkeeping; - try self.ensureProcessDeathCapacity(Liveness.bpi); - - switch (air_tags[inst]) { - // zig fmt: off - .add, .ptr_add => @panic("TODO try self.airBinOp(inst)"), - .addwrap => @panic("TODO try self.airAddWrap(inst)"), - .add_sat => @panic("TODO try self.airAddSat(inst)"), - .sub, .ptr_sub => @panic("TODO try self.airBinOp(inst)"), - .subwrap => @panic("TODO try self.airSubWrap(inst)"), - .sub_sat => @panic("TODO try self.airSubSat(inst)"), - .mul => @panic("TODO try self.airMul(inst)"), - .mulwrap => @panic("TODO try self.airMulWrap(inst)"), - .mul_sat => @panic("TODO try self.airMulSat(inst)"), - .rem => @panic("TODO try self.airRem(inst)"), - .mod => @panic("TODO try self.airMod(inst)"), - .shl, .shl_exact => @panic("TODO try self.airShl(inst)"), - .shl_sat => @panic("TODO try self.airShlSat(inst)"), - .min => @panic("TODO try self.airMin(inst)"), - .max => @panic("TODO try self.airMax(inst)"), - .slice => @panic("TODO try self.airSlice(inst)"), - - .sqrt, - .sin, - .cos, - .tan, - .exp, - .exp2, - .log, - .log2, - .log10, - .fabs, - .floor, - .ceil, - .round, - .trunc_float, - => @panic("TODO try self.airUnaryMath(inst)"), - - .add_with_overflow => @panic("TODO try self.airAddWithOverflow(inst)"), - .sub_with_overflow => @panic("TODO try self.airSubWithOverflow(inst)"), - .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 => try self.airDiv(inst), - - .cmp_lt => @panic("TODO try self.airCmp(inst, .lt)"), - .cmp_lte => @panic("TODO try self.airCmp(inst, .lte)"), - .cmp_eq => @panic("TODO try self.airCmp(inst, .eq)"), - .cmp_gte => @panic("TODO try self.airCmp(inst, .gte)"), - .cmp_gt => @panic("TODO try self.airCmp(inst, .gt)"), - .cmp_neq => @panic("TODO try self.airCmp(inst, .neq)"), - .cmp_vector => @panic("TODO try self.airCmpVector(inst)"), - .cmp_lt_errors_len => @panic("TODO try self.airCmpLtErrorsLen(inst)"), - - .bool_and => @panic("TODO try self.airBoolOp(inst)"), - .bool_or => @panic("TODO try self.airBoolOp(inst)"), - .bit_and => @panic("TODO try self.airBitAnd(inst)"), - .bit_or => @panic("TODO try self.airBitOr(inst)"), - .xor => @panic("TODO try self.airXor(inst)"), - .shr, .shr_exact => @panic("TODO try self.airShr(inst)"), - - .alloc => @panic("TODO try self.airAlloc(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 => try self.airBlock(inst), - .br => @panic("TODO try self.airBr(inst)"), - .breakpoint => 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 => 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)"), - .trunc => @panic("TODO try self.airTrunc(inst)"), - .bool_to_int => @panic("TODO try self.airBoolToInt(inst)"), - .is_non_null => @panic("TODO try self.airIsNonNull(inst)"), - .is_non_null_ptr => @panic("TODO try self.airIsNonNullPtr(inst)"), - .is_null => @panic("TODO try self.airIsNull(inst)"), - .is_null_ptr => @panic("TODO try self.airIsNullPtr(inst)"), - .is_non_err => @panic("TODO try self.airIsNonErr(inst)"), - .is_non_err_ptr => @panic("TODO try self.airIsNonErrPtr(inst)"), - .is_err => @panic("TODO try self.airIsErr(inst)"), - .is_err_ptr => @panic("TODO try self.airIsErrPtr(inst)"), - .load => @panic("TODO try self.airLoad(inst)"), - .loop => @panic("TODO try self.airLoop(inst)"), - .not => @panic("TODO try self.airNot(inst)"), - .ptrtoint => @panic("TODO try self.airPtrToInt(inst)"), - .ret => try self.airRet(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)"), - .int_to_float => @panic("TODO try self.airIntToFloat(inst)"), - .float_to_int => @panic("TODO try self.airFloatToInt(inst)"), - .cmpxchg_strong => @panic("TODO try self.airCmpxchg(inst)"), - .cmpxchg_weak => @panic("TODO try self.airCmpxchg(inst)"), - .atomic_rmw => @panic("TODO try self.airAtomicRmw(inst)"), - .atomic_load => @panic("TODO try self.airAtomicLoad(inst)"), - .memcpy => @panic("TODO try self.airMemcpy(inst)"), - .memset => @panic("TODO try self.airMemset(inst)"), - .set_union_tag => @panic("TODO try self.airSetUnionTag(inst)"), - .get_union_tag => @panic("TODO try self.airGetUnionTag(inst)"), - .clz => @panic("TODO try self.airClz(inst)"), - .ctz => @panic("TODO try self.airCtz(inst)"), - .popcount => @panic("TODO try self.airPopcount(inst)"), - .byte_swap => @panic("TODO try self.airByteSwap(inst)"), - .bit_reverse => @panic("TODO try self.airBitReverse(inst)"), - .tag_name => @panic("TODO try self.airTagName(inst)"), - .error_name => @panic("TODO try self.airErrorName(inst)"), - .splat => @panic("TODO try self.airSplat(inst)"), - .select => @panic("TODO try self.airSelect(inst)"), - .shuffle => @panic("TODO try self.airShuffle(inst)"), - .reduce => @panic("TODO try self.airReduce(inst)"), - .aggregate_init => @panic("TODO try self.airAggregateInit(inst)"), - .union_init => @panic("TODO try self.airUnionInit(inst)"), - .prefetch => @panic("TODO try self.airPrefetch(inst)"), - .mul_add => @panic("TODO try self.airMulAdd(inst)"), - - .dbg_var_ptr, - .dbg_var_val, - => try self.airDbgVar(inst), - - .dbg_inline_begin, - .dbg_inline_end, - => try self.airDbgInline(inst), - - .dbg_block_begin, - .dbg_block_end, - => try self.airDbgBlock(inst), - - .call => try self.airCall(inst, .auto), - .call_always_tail => try self.airCall(inst, .always_tail), - .call_never_tail => try self.airCall(inst, .never_tail), - .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)"), - .atomic_store_release => @panic("TODO try self.airAtomicStore(inst, .Release)"), - .atomic_store_seq_cst => @panic("TODO try self.airAtomicStore(inst, .SeqCst)"), - - .struct_field_ptr_index_0 => @panic("TODO try self.airStructFieldPtrIndex(inst, 0)"), - .struct_field_ptr_index_1 => @panic("TODO try self.airStructFieldPtrIndex(inst, 1)"), - .struct_field_ptr_index_2 => @panic("TODO try self.airStructFieldPtrIndex(inst, 2)"), - .struct_field_ptr_index_3 => @panic("TODO try self.airStructFieldPtrIndex(inst, 3)"), - - .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"), - - .switch_br => try self.airSwitch(inst), - .slice_ptr => @panic("TODO try self.airSlicePtr(inst)"), - .slice_len => @panic("TODO try self.airSliceLen(inst)"), - - .ptr_slice_len_ptr => @panic("TODO try self.airPtrSliceLenPtr(inst)"), - .ptr_slice_ptr_ptr => @panic("TODO try self.airPtrSlicePtrPtr(inst)"), - - .array_elem_val => @panic("TODO try self.airArrayElemVal(inst)"), - .slice_elem_val => @panic("TODO try self.airSliceElemVal(inst)"), - .slice_elem_ptr => @panic("TODO try self.airSliceElemPtr(inst)"), - .ptr_elem_val => @panic("TODO try self.airPtrElemVal(inst)"), - .ptr_elem_ptr => @panic("TODO try self.airPtrElemPtr(inst)"), - - .constant => unreachable, // excluded from function bodies - .const_ty => unreachable, // excluded from function bodies - .unreach => self.finishAirBookkeeping(), - - .optional_payload => @panic("TODO try self.airOptionalPayload(inst)"), - .optional_payload_ptr => @panic("TODO try self.airOptionalPayloadPtr(inst)"), - .optional_payload_ptr_set => @panic("TODO try self.airOptionalPayloadPtrSet(inst)"), - .unwrap_errunion_err => @panic("TODO try self.airUnwrapErrErr(inst)"), - .unwrap_errunion_payload => @panic("TODO try self.airUnwrapErrPayload(inst)"), - .unwrap_errunion_err_ptr => @panic("TODO try self.airUnwrapErrErrPtr(inst)"), - .unwrap_errunion_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(inst)"), - .errunion_payload_ptr_set => @panic("TODO try self.airErrUnionPayloadPtrSet(inst)"), - - .wrap_optional => @panic("TODO try self.airWrapOptional(inst)"), - .wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"), - .wrap_errunion_err => @panic("TODO try self.airWrapErrUnionErr(inst)"), - - .wasm_memory_size => unreachable, - .wasm_memory_grow => unreachable, - // zig fmt: on - } - - if (std.debug.runtime_safety) { - if (self.air_bookkeeping < old_air_bookkeeping + 1) { - std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[inst] }); - } - } - } -} - -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 = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i .. extra_i + extra.data.outputs_len]); - extra_i += outputs.len; - const inputs = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i .. extra_i + extra.data.inputs_len]); - extra_i += inputs.len; - - const dead = !is_volatile and self.liveness.isUnused(inst); - const result: MCValue = if (dead) .dead else result: { - if (outputs.len > 1) { - return self.fail("TODO implement codegen for asm with more than 1 output", .{}); - } - - const output_constraint: ?[]const u8 = for (outputs) |output| { - if (output != .none) { - return self.fail("TODO implement codegen for non-expr asm", .{}); - } - const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - break constraint; - } else null; - - for (inputs) |input| { - const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); - const constraint = std.mem.sliceTo(input_bytes, 0); - const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { - return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); - } - const reg_name = constraint[1 .. constraint.len - 1]; - const reg = parseRegName(reg_name) orelse - return self.fail("unrecognized register: '{s}'", .{reg_name}); - - const arg_mcv = try self.resolveInst(input); - try self.register_manager.getReg(reg, null); - try self.genSetReg(self.air.typeOf(input), reg, arg_mcv); - } - - { - var clobber_i: u32 = 0; - while (clobber_i < clobbers_len) : (clobber_i += 1) { - const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += clobber.len / 4 + 1; - - // TODO honor these - } - } - - const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; - - if (mem.eql(u8, asm_source, "ta 0x6d")) { - _ = try self.addInst(.{ - .tag = .tcc, - .data = .{ - .trap = .{ - .is_imm = true, - .cond = 0b1000, // TODO need to look into changing this into an enum - .rs2_or_imm = .{ .imm = 0x6d }, - }, - }, - }); - } else { - return self.fail("TODO implement a full SPARCv9 assembly parsing", .{}); - } - - if (output_constraint) |output| { - if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { - return self.fail("unrecognized asm output constraint: '{s}'", .{output}); - } - const reg_name = output[2 .. output.len - 1]; - const reg = parseRegName(reg_name) orelse - return self.fail("unrecognized register: '{s}'", .{reg_name}); - break :result MCValue{ .register = reg }; - } else { - break :result MCValue{ .none = {} }; - } - }; - - simple: { - var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - var buf_index: usize = 0; - for (outputs) |output| { - if (output == .none) continue; - - if (buf_index >= buf.len) break :simple; - buf[buf_index] = output; - buf_index += 1; - } - if (buf_index + inputs.len > buf.len) break :simple; - std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs); - return self.finishAir(inst, result, buf); - } - - var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len); - for (outputs) |output| { - if (output == .none) continue; - - bt.feed(output); - } - for (inputs) |input| { - bt.feed(input); - } - return bt.finishAir(result); -} - -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.genArgDbgInfo(inst, mcv, @intCast(u32, 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 airBreakpoint(self: *Self) !void { - // ta 0x01 - _ = try self.addInst(.{ - .tag = .tcc, - .data = .{ - .trap = .{ - .is_imm = true, - .cond = 0b1000, // TODO need to look into changing this into an enum - .rs2_or_imm = .{ .imm = 0x01 }, - }, - }, - }); - return self.finishAirBookkeeping(); -} - -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 = @ptrCast([]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, - .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", .{}); - }, - } - } - - // Due to incremental compilation, how function calls are generated depends - // on linking. - if (self.air.value(callee)) |func_value| { - if (self.bin_file.tag == link.File.Elf.base_tag) { - if (func_value.castTag(.function)) |func_payload| { - const func = func_payload.data; - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { - const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; - const mod = self.bin_file.options.module.?; - break :blk @intCast(u32, got.p_vaddr + mod.declPtr(func.owner_decl).link.elf.offset_table_index * ptr_bytes); - } else unreachable; - - try self.genSetReg(Type.initTag(.usize), .o7, .{ .memory = got_addr }); - - _ = try self.addInst(.{ - .tag = .jmpl, - .data = .{ - .arithmetic_3op = .{ - .is_imm = false, - .rd = .o7, - .rs1 = .o7, - .rs2_or_imm = .{ .rs2 = .g0 }, - }, - }, - }); - - // TODO Find a way to fill this delay slot - _ = try self.addInst(.{ - .tag = .nop, - .data = .{ .nop = {} }, - }); - } else if (func_value.castTag(.extern_fn)) |_| { - return self.fail("TODO implement calling extern functions", .{}); - } else { - return self.fail("TODO implement calling bitcasted functions", .{}); - } - } else @panic("TODO SPARCv9 currently does not support non-ELF binaries"); - } else { - assert(ty.zigTypeTag() == .Pointer); - const mcv = try self.resolveInst(callee); - try self.genSetReg(ty, .o7, mcv); - - _ = try self.addInst(.{ - .tag = .jmpl, - .data = .{ - .arithmetic_3op = .{ - .is_imm = false, - .rd = .o7, - .rs1 = .o7, - .rs2_or_imm = .{ .rs2 = .g0 }, - }, - }, - }); - - // TODO Find a way to fill this delay slot - _ = try self.addInst(.{ - .tag = .nop, - .data = .{ .nop = {} }, - }); - } - - const result = info.return_value; - - if (args.len + 1 <= Liveness.bpi - 1) { - var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); - buf[0] = callee; - std.mem.copy(Air.Inst.Ref, buf[1..], args); - return self.finishAir(inst, result, buf); - } - - @panic("TODO handle return value with BigTomb"); -} - -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 airRet(self: *Self, inst: Air.Inst.Index) !void { - const un_op = self.air.instructions.items(.data)[inst].un_op; - const operand = try self.resolveInst(un_op); - try self.ret(operand); - return self.finishAir(inst, .dead, .{ un_op, .none, .none }); -} - -fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { - const un_op = self.air.instructions.items(.data)[inst].un_op; - const ptr = try self.resolveInst(un_op); - _ = ptr; - 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 - -/// Adds a Type to the .debug_info at the current position. The bytes will be populated later, -/// after codegen for this symbol is done. -fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void { - switch (self.debug_output) { - .dwarf => |dw| { - assert(ty.hasRuntimeBits()); - const dbg_info = &dw.dbg_info; - const index = dbg_info.items.len; - try dbg_info.resize(index + 4); // DW.AT.type, DW.FORM.ref4 - const mod = self.bin_file.options.module.?; - const atom = switch (self.bin_file.tag) { - .elf => &mod.declPtr(self.mod_fn.owner_decl).link.elf.dbg_info_atom, - else => unreachable, - }; - try dw.addTypeReloc(atom, ty, @intCast(u32, index), null); - }, - else => {}, - } -} - -fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { - const gpa = self.gpa; - try self.mir_instructions.ensureUnusedCapacity(gpa, 1); - const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); - self.mir_instructions.appendAssumeCapacity(inst); - 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 abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { - const mod = self.bin_file.options.module.?; - return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)}); - }; - // TODO swap this for inst.ty.ptrAlign - const abi_align = elem_ty.abiAlignment(self.target.*); - return self.allocMem(inst, abi_size, abi_align); -} - -fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue { - const elem_ty = self.air.typeOfIndex(inst); - const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { - const mod = self.bin_file.options.module.?; - return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)}); - }; - const abi_align = elem_ty.abiAlignment(self.target.*); - if (abi_align > self.stack_align) - self.stack_align = abi_align; - - if (reg_ok) { - // Make sure the type can fit in a register before we try to allocate one. - if (abi_size <= 8) { - if (self.register_manager.tryAllocReg(inst)) |reg| { - return MCValue{ .register = reg }; - } - } - } - const stack_offset = try self.allocMem(inst, abi_size, abi_align); - return MCValue{ .stack_offset = stack_offset }; -} - -/// Copies a value to a register without tracking the register. The register is not considered -/// allocated. A second call to `copyToTmpRegister` may return the same register. -/// This can have a side effect of spilling instructions to the stack to free up a register. -fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register { - const reg = try self.register_manager.allocReg(null); - try self.genSetReg(ty, reg, mcv); - return reg; -} - -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); -} - -fn fail(self: *Self, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - assert(self.err_msg == null); - 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 genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { - const ty = self.air.instructions.items(.data)[inst].ty; - const name = self.mod_fn.getParamName(arg_index); - const name_with_null = name.ptr[0 .. name.len + 1]; - - switch (mcv) { - .register => |reg| { - switch (self.debug_output) { - .dwarf => |dw| { - const dbg_info = &dw.dbg_info; - try dbg_info.ensureUnusedCapacity(3); - dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter)); - dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1, // ULEB128 dwarf expression length - reg.dwarfLocOp(), - }); - try dbg_info.ensureUnusedCapacity(5 + name_with_null.len); - try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 - dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string - }, - else => {}, - } - }, - .stack_offset => |offset| { - _ = offset; - switch (self.debug_output) { - .dwarf => {}, - else => {}, - } - }, - else => {}, - } -} - -fn genLoad(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void { - assert(off_type == Register or off_type == i13); - - const is_imm = (off_type == i13); - const rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off }; - - switch (abi_size) { - 1, 2, 4, 8 => { - const tag: Mir.Inst.Tag = switch (abi_size) { - 1 => .ldub, - 2 => .lduh, - 4 => .lduw, - 8 => .ldx, - else => unreachable, // unexpected abi size - }; - - _ = try self.addInst(.{ - .tag = tag, - .data = .{ - .arithmetic_3op = .{ - .is_imm = is_imm, - .rd = value_reg, - .rs1 = addr_reg, - .rs2_or_imm = rs2_or_imm, - }, - }, - }); - }, - 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}), - else => unreachable, - } -} - -fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { - switch (mcv) { - .dead => unreachable, - .unreach, .none => return, // Nothing to do. - .undef => { - if (!self.wantSafety()) - return; // The already existing value will do just fine. - // Write the debug undefined value. - return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }); - }, - .ptr_stack_offset => |off| { - const simm13 = math.cast(u12, off) catch - return self.fail("TODO larger stack offsets", .{}); - - _ = try self.addInst(.{ - .tag = .add, - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = reg, - .rs1 = .sp, - .rs2_or_imm = .{ .imm = simm13 }, - }, - }, - }); - }, - .immediate => |x| { - if (x <= math.maxInt(u12)) { - _ = try self.addInst(.{ - .tag = .@"or", - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = reg, - .rs1 = .g0, - .rs2_or_imm = .{ .imm = @truncate(u12, x) }, - }, - }, - }); - } else if (x <= math.maxInt(u32)) { - _ = try self.addInst(.{ - .tag = .sethi, - .data = .{ - .sethi = .{ - .rd = reg, - .imm = @truncate(u22, x >> 10), - }, - }, - }); - - _ = try self.addInst(.{ - .tag = .@"or", - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = reg, - .rs1 = reg, - .rs2_or_imm = .{ .imm = @truncate(u10, x) }, - }, - }, - }); - } else if (x <= math.maxInt(u44)) { - try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 12) }); - - _ = try self.addInst(.{ - .tag = .sllx, - .data = .{ - .shift = .{ - .is_imm = true, - .width = .shift64, - .rd = reg, - .rs1 = reg, - .rs2_or_imm = .{ .imm = 12 }, - }, - }, - }); - - _ = try self.addInst(.{ - .tag = .@"or", - .data = .{ - .arithmetic_3op = .{ - .is_imm = true, - .rd = reg, - .rs1 = reg, - .rs2_or_imm = .{ .imm = @truncate(u12, x) }, - }, - }, - }); - } else { - // Need to allocate a temporary register to load 64-bit immediates. - const tmp_reg = try self.register_manager.allocReg(null); - - try self.genSetReg(ty, tmp_reg, .{ .immediate = @truncate(u32, x) }); - try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 32) }); - - _ = try self.addInst(.{ - .tag = .sllx, - .data = .{ - .shift = .{ - .is_imm = true, - .width = .shift64, - .rd = reg, - .rs1 = reg, - .rs2_or_imm = .{ .imm = 32 }, - }, - }, - }); - - _ = try self.addInst(.{ - .tag = .@"or", - .data = .{ - .arithmetic_3op = .{ - .is_imm = false, - .rd = reg, - .rs1 = reg, - .rs2_or_imm = .{ .rs2 = tmp_reg }, - }, - }, - }); - } - }, - .register => |src_reg| { - // If the registers are the same, nothing to do. - if (src_reg.id() == reg.id()) - return; - - // or %g0, src, dst (aka mov src, dst) - _ = try self.addInst(.{ - .tag = .@"or", - .data = .{ - .arithmetic_3op = .{ - .is_imm = false, - .rd = reg, - .rs1 = .g0, - .rs2_or_imm = .{ .rs2 = src_reg }, - }, - }, - }); - }, - .memory => |addr| { - // The value is in memory at a hard-coded address. - // If the type is a pointer, it means the pointer address is at this memory location. - try self.genSetReg(ty, reg, .{ .immediate = addr }); - try self.genLoad(reg, reg, i13, 0, ty.abiSize(self.target.*)); - }, - .stack_offset => |off| { - const real_offset = off + abi.stack_bias + abi.stack_save_area; - const simm13 = math.cast(i13, real_offset) catch - return self.fail("TODO larger stack offsets", .{}); - try self.genLoad(reg, .sp, i13, simm13, ty.abiSize(self.target.*)); - }, - } -} - -fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { - const abi_size = ty.abiSize(self.target.*); - switch (mcv) { - .dead => unreachable, - .unreach, .none => return, // Nothing to do. - .undef => { - if (!self.wantSafety()) - return; // The already existing value will do just fine. - // TODO Upgrade this to a memset call when we have that available. - switch (ty.abiSize(self.target.*)) { - 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }), - 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }), - 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), - 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), - else => return self.fail("TODO implement memset", .{}), - } - }, - .immediate, - .ptr_stack_offset, - => { - const reg = try self.copyToTmpRegister(ty, mcv); - return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); - }, - .register => |reg| { - const real_offset = stack_offset + abi.stack_bias + abi.stack_save_area; - const simm13 = math.cast(i13, real_offset) catch - return self.fail("TODO larger stack offsets", .{}); - return self.genStore(reg, .sp, i13, simm13, abi_size); - }, - .memory, .stack_offset => return self.fail("TODO implement memcpy", .{}), - } -} - -fn genStore(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void { - assert(off_type == Register or off_type == i13); - - const is_imm = (off_type == i13); - const rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off }; - - switch (abi_size) { - 1, 2, 4, 8 => { - const tag: Mir.Inst.Tag = switch (abi_size) { - 1 => .stb, - 2 => .sth, - 4 => .stw, - 8 => .stx, - else => unreachable, // unexpected abi size - }; - - _ = try self.addInst(.{ - .tag = tag, - .data = .{ - .arithmetic_3op = .{ - .is_imm = is_imm, - .rd = value_reg, - .rs1 = addr_reg, - .rs2_or_imm = rs2_or_imm, - }, - }, - }); - }, - 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}), - else => unreachable, - } -} - -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_index); - } - const target = self.target.*; - - switch (typed_value.ty.zigTypeTag()) { - .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.fail("TODO implement int genTypedValue of > 64 bits", .{}); - } - }, - .ComptimeInt => unreachable, // semantic analysis prevents this - .ComptimeFloat => unreachable, // semantic analysis prevents this - 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 iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb { - try self.ensureProcessDeathCapacity(operand_count + 1); - return BigTomb{ - .function = self, - .inst = inst, - .tomb_bits = self.liveness.getTombBits(inst), - .big_tomb_bits = self.liveness.special.get(inst) orelse 0, - .bit_index = 0, - }; -} - -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - - // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? - if (tv.ty.zigTypeTag() == .Pointer) blk: { - if (tv.ty.castPtrToFn()) |_| break :blk; - if (!tv.ty.elemType2().hasRuntimeBits()) { - return MCValue.none; - } - } - - const mod = self.bin_file.options.module.?; - const decl = mod.declPtr(decl_index); - - mod.markDeclAlive(decl); - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; - const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } -} - -fn parseRegName(name: []const u8) ?Register { - if (@hasDecl(Register, "parseRegName")) { - return Register.parseRegName(name); - } - return std.meta.stringToEnum(Register, name); -} - -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 ret(self: *Self, mcv: MCValue) !void { - const ret_ty = self.fn_type.fnReturnType(); - try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); - - // Just add space for an instruction, patch this later - const index = try self.addInst(.{ - .tag = .nop, - .data = .{ .nop = {} }, - }); - try self.exitlude_jump_relocs.append(self.gpa, 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; -} - -/// Sets the value without any modifications to register allocation metadata or stack allocation metadata. -fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { - switch (loc) { - .none => return, - .register => |reg| return self.genSetReg(ty, reg, val), - .stack_offset => |off| return self.genSetStack(ty, off, val), - .memory => { - return self.fail("TODO implement setRegOrMem for memory", .{}); - }, - else => unreachable, - } -} - -pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void { - const stack_mcv = try self.allocRegOrMem(inst, false); - log.debug("spilling {d} to stack mcv {any}", .{ inst, stack_mcv }); - const reg_mcv = self.getResolvedInstValue(inst); - assert(reg == reg_mcv.register); - const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; - try branch.inst_table.put(self.gpa, inst, stack_mcv); - try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv); -} - -/// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`. -fn wantSafety(self: *Self) bool { - return switch (self.bin_file.options.optimize_mode) { - .Debug => true, - .ReleaseSafe => true, - .ReleaseFast => false, - .ReleaseSmall => false, - }; -} diff --git a/src/arch/sparcv9/Emit.zig b/src/arch/sparcv9/Emit.zig deleted file mode 100644 index 4da2215c4629d0ad9ec194ea02dbd885643299c4..0000000000000000000000000000000000000000 --- a/src/arch/sparcv9/Emit.zig +++ /dev/null @@ -1,274 +0,0 @@ -//! 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_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 => try emit.mirArithmetic3Op(inst), - - .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"), - - .stb => try emit.mirArithmetic3Op(inst), - .sth => try emit.mirArithmetic3Op(inst), - .stw => try emit.mirArithmetic3Op(inst), - .stx => try emit.mirArithmetic3Op(inst), - - .sub => try emit.mirArithmetic3Op(inst), - - .tcc => try emit.mirTrap(inst), - } - } -} - -pub fn deinit(emit: *Emit) void { - emit.* = undefined; -} - -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(emit: *Emit) !void { - switch (emit.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); - try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); - }, - .plan9 => {}, - .none => {}, - } -} - -fn mirDebugEpilogueBegin(emit: *Emit) !void { - switch (emit.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); - try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.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)), - .jmpl => try emit.writeInstruction(Instruction.jmpl(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)), - .stb => try emit.writeInstruction(Instruction.stb(i13, rs1, imm, rd)), - .sth => try emit.writeInstruction(Instruction.sth(i13, rs1, imm, rd)), - .stw => try emit.writeInstruction(Instruction.stw(i13, rs1, imm, rd)), - .stx => try emit.writeInstruction(Instruction.stx(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)), - .jmpl => try emit.writeInstruction(Instruction.jmpl(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)), - .stb => try emit.writeInstruction(Instruction.stb(Register, rs1, rs2, rd)), - .sth => try emit.writeInstruction(Instruction.sth(Register, rs1, rs2, rd)), - .stw => try emit.writeInstruction(Instruction.stw(Register, rs1, rs2, rd)), - .stx => try emit.writeInstruction(Instruction.stx(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(emit: *Emit, line: u32, column: u32) !void { - const delta_line = @intCast(i32, line) - @intCast(i32, emit.prev_di_line); - const delta_pc: usize = emit.code.items.len - emit.prev_di_pc; - switch (emit.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); - emit.prev_di_pc = emit.code.items.len; - emit.prev_di_line = line; - emit.prev_di_column = column; - emit.prev_di_pc = emit.code.items.len; - }, - else => {}, - } -} - -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); -} diff --git a/src/arch/sparcv9/Mir.zig b/src/arch/sparcv9/Mir.zig deleted file mode 100644 index ef0be93f4cef33dedf6f5ad3d65ae294bf0a3871..0000000000000000000000000000000000000000 --- a/src/arch/sparcv9/Mir.zig +++ /dev/null @@ -1,256 +0,0 @@ -//! Machine Intermediate Representation. -//! This data is produced by SPARCv9 Codegen or SPARCv9 assembly parsing -//! These instructions have a 1:1 correspondence with machine code instructions -//! for the target. MIR can be lowered to source-annotated textual assembly code -//! instructions, or it can be lowered to machine code. -//! 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: 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 - /// This uses the arithmetic_3op field. - // TODO add other operations. - add, - - /// A.7 Branch on Integer Condition Codes with Prediction (BPcc) - /// This uses the branch_predict field. - bpcc, - - /// A.8 Call and Link - /// This uses the branch_link field. - call, - - /// A.24 Jump and Link - /// This uses the arithmetic_3op field. - jmpl, - - /// A.27 Load Integer - /// This 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 - /// This uses the arithmetic_3op field. - // TODO add other operations. - @"or", - - /// A.40 No Operation - /// This uses the nop field. - nop, - - /// A.45 RETURN - /// This uses the arithmetic_2op field. - @"return", - - /// A.46 SAVE and RESTORE - /// This uses the arithmetic_3op field. - save, - restore, - - /// A.48 SETHI - /// This uses the sethi field. - sethi, - - /// A.49 Shift - /// This uses the shift field. - // TODO add other operations. - sllx, - - /// A.54 Store Integer - /// This uses the arithmetic_3op field. - /// Note that the std variant of this instruction is deprecated, so do not emit - /// it unless specifically requested (e.g. by inline assembly). - // TODO add other operations. - stb, - sth, - stw, - stx, - - /// A.56 Subtract - /// This uses the arithmetic_3op field. - // TODO add other operations. - sub, - - /// A.61 Trap on Integer Condition Codes (Tcc) - /// This 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: 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, - }, - - /// 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) { - 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, - }; -} diff --git a/src/arch/sparcv9/abi.zig b/src/arch/sparcv9/abi.zig deleted file mode 100644 index a9001c7dc746c06a085f926439a02aa6806ec3b9..0000000000000000000000000000000000000000 --- a/src/arch/sparcv9/abi.zig +++ /dev/null @@ -1,37 +0,0 @@ -const bits = @import("bits.zig"); -const Register = bits.Register; - -// SPARCv9 stack constants. -// See: Registers and the Stack Frame, page 3P-8, SCD 2.4.1. - -// On SPARCv9, %sp points to top of stack + stack bias, -// and %fp points to top of previous frame + stack bias. -pub const stack_bias = 2047; - -// The first 176 bytes of the stack is reserved for register saving purposes. -// SPARCv9 requires to reserve space in the stack for the first six arguments, -// even though they are usually passed in registers. -pub const stack_save_area = 176; - -// 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 }; -pub const c_abi_int_return_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3" }; diff --git a/src/arch/sparcv9/bits.zig b/src/arch/sparcv9/bits.zig deleted file mode 100644 index e66b24f6171608c09f7797426089eb82a28d4deb..0000000000000000000000000000000000000000 --- a/src/arch/sparcv9/bits.zig +++ /dev/null @@ -1,1256 +0,0 @@ -const std = @import("std"); -const DW = std.dwarf; -const assert = std.debug.assert; -const testing = std.testing; - -/// General purpose registers in the SPARCv9 instruction set -pub const Register = enum(u6) { - // zig fmt: off - g0, g1, g2, g3, g4, g5, g6, g7, - o0, o1, o2, o3, o4, o5, o6, o7, - l0, l1, l2, l3, l4, l5, l6, l7, - @"i0", @"i1", @"i2", @"i3", @"i4", @"i5", @"i6", @"i7", - - sp = 46, // stack pointer (o6) - fp = 62, // frame pointer (i6) - // zig fmt: on - - pub fn id(self: Register) u5 { - return @truncate(u5, @enumToInt(self)); - } - - pub fn enc(self: Register) u5 { - // For integer registers, enc() == id(). - return self.id(); - } - - pub fn dwarfLocOp(reg: Register) u8 { - return @as(u8, reg.id()) + DW.OP.reg0; - } -}; - -test "Register.id" { - // SP - try testing.expectEqual(@as(u5, 14), Register.o6.id()); - try testing.expectEqual(Register.o6.id(), Register.sp.id()); - - // FP - try testing.expectEqual(@as(u5, 30), Register.@"i6".id()); - try testing.expectEqual(Register.@"i6".id(), Register.fp.id()); - - // x0 - try testing.expectEqual(@as(u5, 0), Register.g0.id()); - try testing.expectEqual(@as(u5, 8), Register.o0.id()); - try testing.expectEqual(@as(u5, 16), Register.l0.id()); - try testing.expectEqual(@as(u5, 24), Register.@"i0".id()); -} - -test "Register.enc" { - // x0 - try testing.expectEqual(@as(u5, 0), Register.g0.enc()); - try testing.expectEqual(@as(u5, 8), Register.o0.enc()); - try testing.expectEqual(@as(u5, 16), Register.l0.enc()); - try testing.expectEqual(@as(u5, 24), Register.@"i0".enc()); - - // For integer registers, enc() == id(). - try testing.expectEqual(Register.g0.enc(), Register.g0.id()); - try testing.expectEqual(Register.o0.enc(), Register.o0.id()); - try testing.expectEqual(Register.l0.enc(), Register.l0.id()); - try testing.expectEqual(Register.@"i0".enc(), Register.@"i0".id()); -} - -/// Scalar floating point registers in the SPARCv9 instruction set -pub const FloatingPointRegister = enum(u7) { - // SPARCv9 has 64 f32 registers, 32 f64 registers, and 16 f128 registers, - // which are aliased in this way: - // - // | %d0 | %d2 | - // %q0 | %f0 | %f1 | %f2 | %f3 | - // | %d4 | %d6 | - // %q4 | %f4 | %f5 | %f6 | %f7 | - // ... - // | %d60 | %d62 | - // %q60 | %f60 | %f61 | %f62 | %f63 | - // - // Though, since the instructions uses five-bit addressing, only %f0-%f31 - // is usable with f32 instructions. - - // zig fmt: off - - // 32-bit registers - @"f0", @"f1", @"f2", @"f3", @"f4", @"f5", @"f6", @"f7", - @"f8", @"f9", @"f10", @"f11", @"f12", @"f13", @"f14", @"f15", - @"f16", @"f17", @"f18", @"f19", @"f20", @"f21", @"f22", @"f23", - @"f24", @"f25", @"f26", @"f27", @"f28", @"f29", @"f30", @"f31", - - // 64-bit registers - d0, d2, d4, d6, d8, d10, d12, d14, - d16, d18, d20, d22, d24, d26, d28, d30, - d32, d34, d36, d38, d40, d42, d44, d46, - d48, d50, d52, d54, d56, d58, d60, d62, - - // 128-bit registers - q0, q4, q8, q12, q16, q20, q24, q28, - q32, q36, q40, q44, q48, q52, q56, q60, - // zig fmt: on - - pub fn id(self: FloatingPointRegister) u6 { - return switch (self.size()) { - 32 => @truncate(u6, @enumToInt(self)), - 64 => @truncate(u6, (@enumToInt(self) - 32) * 2), - 128 => @truncate(u6, (@enumToInt(self) - 64) * 4), - else => unreachable, - }; - } - - pub fn enc(self: FloatingPointRegister) u5 { - // Floating point registers use an encoding scheme to map from the 6-bit - // ID to 5-bit encoded value. - // (See section 5.1.4.1 of SPARCv9 ISA specification) - - const reg_id = self.id(); - return @truncate(u5, reg_id | (reg_id >> 5)); - } - - /// Returns the bit-width of the register. - pub fn size(self: FloatingPointRegister) u8 { - return switch (@enumToInt(self)) { - 0...31 => 32, - 32...63 => 64, - 64...79 => 128, - else => unreachable, - }; - } -}; - -test "FloatingPointRegister.id" { - // Low region - try testing.expectEqual(@as(u6, 0), FloatingPointRegister.q0.id()); - try testing.expectEqual(FloatingPointRegister.q0.id(), FloatingPointRegister.d0.id()); - try testing.expectEqual(FloatingPointRegister.d0.id(), FloatingPointRegister.@"f0".id()); - - try testing.expectEqual(@as(u6, 28), FloatingPointRegister.q28.id()); - try testing.expectEqual(FloatingPointRegister.q28.id(), FloatingPointRegister.d28.id()); - try testing.expectEqual(FloatingPointRegister.d28.id(), FloatingPointRegister.@"f28".id()); - - // High region - try testing.expectEqual(@as(u6, 32), FloatingPointRegister.q32.id()); - try testing.expectEqual(FloatingPointRegister.q32.id(), FloatingPointRegister.d32.id()); - - try testing.expectEqual(@as(u6, 60), FloatingPointRegister.q60.id()); - try testing.expectEqual(FloatingPointRegister.q60.id(), FloatingPointRegister.d60.id()); -} - -test "FloatingPointRegister.enc" { - // f registers - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.@"f0".enc()); - try testing.expectEqual(@as(u5, 1), FloatingPointRegister.@"f1".enc()); - try testing.expectEqual(@as(u5, 31), FloatingPointRegister.@"f31".enc()); - - // d registers - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.enc()); - try testing.expectEqual(@as(u5, 1), FloatingPointRegister.d32.enc()); - try testing.expectEqual(@as(u5, 31), FloatingPointRegister.d62.enc()); - - // q registers - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.enc()); - try testing.expectEqual(@as(u5, 1), FloatingPointRegister.q32.enc()); - try testing.expectEqual(@as(u5, 29), FloatingPointRegister.q60.enc()); -} - -/// Represents an instruction in the SPARCv9 instruction set -pub const Instruction = union(enum) { - // Some of the instruction formats have several minor formats, here I - // 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: struct { - op: u2 = 0b01, - disp30: u30, - }, - - // Format 2 (op = 0): SETHI & Branches (Bicc, BPcc, BPr, FBfcc, FBPfcc) - format_2a: struct { - op: u2 = 0b00, - rd: u5, - op2: u3, - imm22: u22, - }, - format_2b: struct { - op: u2 = 0b00, - a: u1, - cond: u4, - op2: u3, - disp22: u22, - }, - format_2c: struct { - op: u2 = 0b00, - a: u1, - cond: u4, - op2: u3, - cc1: u1, - cc0: u1, - p: u1, - disp19: u19, - }, - format_2d: struct { - op: u2 = 0b00, - a: u1, - fixed: u1 = 0b0, - rcond: u3, - op2: u3, - d16hi: u2, - p: u1, - rs1: u5, - d16lo: u14, - }, - - // Format 3 (op = 2 or 3): Arithmetic, Logical, MOVr, MEMBAR, Load, and Store - format_3a: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b0, - reserved: u8 = 0b00000000, - rs2: u5, - }, - format_3b: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - simm13: u13, - }, - format_3c: struct { - op: u2, - reserved1: u5 = 0b00000, - op3: u6, - rs1: u5, - i: u1 = 0b0, - reserved2: u8 = 0b00000000, - rs2: u5, - }, - format_3d: struct { - op: u2, - reserved: u5 = 0b00000, - op3: u6, - rs1: u5, - i: u1 = 0b1, - simm13: u13, - }, - format_3e: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b0, - rcond: u3, - reserved: u5 = 0b00000, - rs2: u5, - }, - format_3f: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - rcond: u3, - simm10: u10, - }, - format_3g: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - // See Errata 58 of SPARCv9 specification - // https://sparc.org/errata-for-v9/#58 - i: u1 = 0b1, - reserved: u8 = 0b00000000, - rs2: u5, - }, - format_3h: struct { - op: u2 = 0b10, - fixed1: u5 = 0b00000, - op3: u6 = 0b101000, - fixed2: u5 = 0b01111, - i: u1 = 0b1, - reserved: u6 = 0b000000, - cmask: u3, - mmask: u4, - }, - format_3i: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b0, - imm_asi: u8, - rs2: u5, - }, - format_3j: struct { - op: u2, - impl_dep1: u5, - op3: u6, - impl_dep2: u19, - }, - format_3k: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b0, - x: u1, - reserved: u7 = 0b0000000, - rs2: u5, - }, - format_3l: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - x: u1 = 0b0, - reserved: u7 = 0b0000000, - shcnt32: u5, - }, - format_3m: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - x: u1 = 0b1, - reserved: u6 = 0b000000, - shcnt64: u6, - }, - format_3n: struct { - op: u2, - rd: u5, - op3: u6, - reserved: u5 = 0b00000, - opf: u9, - rs2: u5, - }, - format_3o: struct { - op: u2, - fixed: u3 = 0b000, - cc1: u1, - cc0: u1, - op3: u6, - rs1: u5, - opf: u9, - rs2: u5, - }, - format_3p: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - opf: u9, - rs2: u5, - }, - format_3q: struct { - op: u2, - rd: u5, - op3: u6, - rs1: u5, - reserved: u14 = 0b00000000000000, - }, - format_3r: struct { - op: u2, - fcn: u5, - op3: u6, - reserved: u19 = 0b0000000000000000000, - }, - format_3s: struct { - op: u2, - rd: u5, - op3: u6, - reserved: u19 = 0b0000000000000000000, - }, - - //Format 4 (op = 2): MOVcc, FMOVr, FMOVcc, and Tcc - format_4a: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b0, - cc1: u1, - cc0: u1, - reserved: u6 = 0b000000, - rs2: u5, - }, - format_4b: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - cc1: u1, - cc0: u1, - simm11: u11, - }, - format_4c: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - cc2: u1, - cond: u4, - i: u1 = 0b0, - cc1: u1, - cc0: u1, - reserved: u6 = 0b000000, - rs2: u5, - }, - format_4d: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - cc2: u1, - cond: u4, - i: u1 = 0b1, - cc1: u1, - cc0: u1, - simm11: u11, - }, - format_4e: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - rs1: u5, - i: u1 = 0b1, - cc1: u1, - cc0: u1, - reserved: u4 = 0b0000, - sw_trap: u7, - }, - format_4f: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - rs1: u5, - fixed: u1 = 0b0, - rcond: u3, - opf_low: u5, - rs2: u5, - }, - format_4g: struct { - op: u2 = 0b10, - rd: u5, - op3: u6, - fixed: u1 = 0b0, - cond: u4, - opf_cc: u3, - opf_low: u6, - rs2: u5, - }, - - pub const CCR = enum(u3) { - fcc0, - fcc1, - fcc2, - fcc3, - icc, - reserved1, - xcc, - reserved2, - }; - - pub const RCondition = enum(u3) { - reserved1, - eq_zero, - le_zero, - lt_zero, - reserved2, - ne_zero, - gt_zero, - ge_zero, - }; - - pub const ASI = enum(u8) { - asi_nucleus = 0x04, - asi_nucleus_little = 0x0c, - asi_as_if_user_primary = 0x10, - asi_as_if_user_secondary = 0x11, - asi_as_if_user_primary_little = 0x18, - asi_as_if_user_secondary_little = 0x19, - asi_primary = 0x80, - asi_secondary = 0x81, - asi_primary_nofault = 0x82, - asi_secondary_nofault = 0x83, - asi_primary_little = 0x88, - asi_secondary_little = 0x89, - asi_primary_nofault_little = 0x8a, - asi_secondary_nofault_little = 0x8b, - }; - - pub const ShiftWidth = enum(u1) { - shift32, - shift64, - }; - - pub const MemOrderingConstraint = packed struct { - store_store: bool = false, - load_store: bool = false, - store_load: bool = false, - load_load: bool = false, - }; - - pub const MemCompletionConstraint = packed struct { - sync: bool = false, - mem_issue: bool = false, - lookaside: bool = false, - }; - - // TODO: Need to define an enum for `cond` values - // This is kinda challenging since the cond values have different meanings - // depending on whether it's operating on integer or FP CCR. - pub const Condition = u4; - - pub fn toU32(self: Instruction) u32 { - // TODO: Remove this once packed structs work. - return switch (self) { - .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| (@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| (@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| (@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| (@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| (@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); - - // In SPARC, branch target needs to be aligned to 4 bytes. - assert(udisp % 4 == 0); - - // Discard the last two bits since those are implicitly zero. - const udisp_truncated = @truncate(u30, udisp >> 2); - return Instruction{ - .format_1 = .{ - .disp30 = udisp_truncated, - }, - }; - } - - fn format2a(op2: u3, imm: u22, rd: Register) Instruction { - return Instruction{ - .format_2a = .{ - .rd = rd.enc(), - .op2 = op2, - .imm22 = imm, - }, - }; - } - - fn format2b(op2: u3, cond: Condition, annul: bool, disp: i24) Instruction { - const udisp = @bitCast(u24, disp); - - // In SPARC, branch target needs to be aligned to 4 bytes. - assert(udisp % 4 == 0); - - // Discard the last two bits since those are implicitly zero. - const udisp_truncated = @truncate(u22, udisp >> 2); - return Instruction{ - .format_2b = .{ - .a = @boolToInt(annul), - .cond = cond, - .op2 = op2, - .disp22 = udisp_truncated, - }, - }; - } - - fn format2c(op2: u3, cond: Condition, annul: bool, pt: bool, ccr: CCR, disp: i21) Instruction { - const udisp = @bitCast(u21, disp); - - // In SPARC, branch target needs to be aligned to 4 bytes. - assert(udisp % 4 == 0); - - // Discard the last two bits since those are implicitly zero. - const udisp_truncated = @truncate(u19, udisp >> 2); - - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_2c = .{ - .a = @boolToInt(annul), - .cond = cond, - .op2 = op2, - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .p = @boolToInt(pt), - .disp19 = udisp_truncated, - }, - }; - } - - fn format2d(op2: u3, rcond: RCondition, annul: bool, pt: bool, rs1: Register, disp: i18) Instruction { - const udisp = @bitCast(u18, disp); - - // In SPARC, branch target needs to be aligned to 4 bytes. - assert(udisp % 4 == 0); - - // Discard the last two bits since those are implicitly zero, - // and split it into low and high parts. - const udisp_truncated = @truncate(u16, udisp >> 2); - const udisp_hi = @truncate(u2, (udisp_truncated & 0b1100_0000_0000_0000) >> 14); - const udisp_lo = @truncate(u14, udisp_truncated & 0b0011_1111_1111_1111); - return Instruction{ - .format_2d = .{ - .a = @boolToInt(annul), - .rcond = @enumToInt(rcond), - .op2 = op2, - .p = @boolToInt(pt), - .rs1 = rs1.enc(), - .d16hi = udisp_hi, - .d16lo = udisp_lo, - }, - }; - } - - fn format3a(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3a = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .rs2 = rs2.enc(), - }, - }; - } - fn format3b(op: u2, op3: u6, rs1: Register, imm: i13, rd: Register) Instruction { - return Instruction{ - .format_3b = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .simm13 = @bitCast(u13, imm), - }, - }; - } - fn format3c(op: u2, op3: u6, rs1: Register, rs2: Register) Instruction { - return Instruction{ - .format_3c = .{ - .op = op, - .op3 = op3, - .rs1 = rs1.enc(), - .rs2 = rs2.enc(), - }, - }; - } - fn format3d(op: u2, op3: u6, rs1: Register, imm: i13) Instruction { - return Instruction{ - .format_3d = .{ - .op = op, - .op3 = op3, - .rs1 = rs1.enc(), - .simm13 = @bitCast(u13, imm), - }, - }; - } - fn format3e(op: u2, op3: u6, rcond: RCondition, rs1: Register, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3e = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .rcond = @enumToInt(rcond), - .rs2 = rs2.enc(), - }, - }; - } - fn format3f(op: u2, op3: u6, rcond: RCondition, rs1: Register, imm: i10, rd: Register) Instruction { - return Instruction{ - .format_3f = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .rcond = @enumToInt(rcond), - .simm10 = @bitCast(u10, imm), - }, - }; - } - fn format3g(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3g = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .rs2 = rs2.enc(), - }, - }; - } - fn format3h(cmask: MemCompletionConstraint, mmask: MemOrderingConstraint) Instruction { - return Instruction{ - .format_3h = .{ - .cmask = @bitCast(u3, cmask), - .mmask = @bitCast(u4, mmask), - }, - }; - } - fn format3i(op: u2, op3: u6, rs1: Register, rs2: Register, rd: Register, asi: ASI) Instruction { - return Instruction{ - .format_3i = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .imm_asi = @enumToInt(asi), - .rs2 = rs2.enc(), - }, - }; - } - fn format3j(op: u2, op3: u6, impl_dep1: u5, impl_dep2: u19) Instruction { - return Instruction{ - .format_3j = .{ - .op = op, - .impl_dep1 = impl_dep1, - .op3 = op3, - .impl_dep2 = impl_dep2, - }, - }; - } - fn format3k(op: u2, op3: u6, sw: ShiftWidth, rs1: Register, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3k = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .x = @enumToInt(sw), - .rs2 = rs2.enc(), - }, - }; - } - fn format3l(op: u2, op3: u6, rs1: Register, shift_count: u5, rd: Register) Instruction { - return Instruction{ - .format_3l = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .shcnt32 = shift_count, - }, - }; - } - fn format3m(op: u2, op3: u6, rs1: Register, shift_count: u6, rd: Register) Instruction { - return Instruction{ - .format_3m = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .shcnt64 = shift_count, - }, - }; - } - fn format3n(op: u2, op3: u6, opf: u9, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3n = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .opf = opf, - .rs2 = rs2.enc(), - }, - }; - } - fn format3o(op: u2, op3: u6, opf: u9, ccr: CCR, rs1: Register, rs2: Register) Instruction { - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_3o = .{ - .op = op, - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .op3 = op3, - .rs1 = rs1.enc(), - .opf = opf, - .rs2 = rs2.enc(), - }, - }; - } - fn format3p(op: u2, op3: u6, opf: u9, rs1: Register, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_3p = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .opf = opf, - .rs2 = rs2.enc(), - }, - }; - } - fn format3q(op: u2, op3: u6, rs1: Register, rd: Register) Instruction { - return Instruction{ - .format_3q = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - }, - }; - } - fn format3r(op: u2, op3: u6, fcn: u5) Instruction { - return Instruction{ - .format_3r = .{ - .op = op, - .fcn = fcn, - .op3 = op3, - }, - }; - } - fn format3s(op: u2, op3: u6, rd: Register) Instruction { - return Instruction{ - .format_3s = .{ - .op = op, - .rd = rd.enc(), - .op3 = op3, - }, - }; - } - - fn format4a(op3: u6, ccr: CCR, rs1: Register, rs2: Register, rd: Register) Instruction { - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_4a = .{ - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .rs2 = rs2.enc(), - }, - }; - } - - fn format4b(op3: u6, ccr: CCR, rs1: Register, imm: i11, rd: Register) Instruction { - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_4b = .{ - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .simm11 = @bitCast(u11, imm), - }, - }; - } - - fn format4c(op3: u6, cond: Condition, ccr: CCR, rs2: Register, rd: Register) Instruction { - const ccr_cc2 = @truncate(u1, @enumToInt(ccr) >> 2); - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_4c = .{ - .rd = rd.enc(), - .op3 = op3, - .cc2 = ccr_cc2, - .cond = cond, - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .rs2 = rs2.enc(), - }, - }; - } - - fn format4d(op3: u6, cond: Condition, ccr: CCR, imm: i11, rd: Register) Instruction { - const ccr_cc2 = @truncate(u1, @enumToInt(ccr) >> 2); - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_4d = .{ - .rd = rd.enc(), - .op3 = op3, - .cc2 = ccr_cc2, - .cond = cond, - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .simm11 = @bitCast(u11, imm), - }, - }; - } - - fn format4e(op3: u6, ccr: CCR, rs1: Register, rd: Register, sw_trap: u7) Instruction { - const ccr_cc1 = @truncate(u1, @enumToInt(ccr) >> 1); - const ccr_cc0 = @truncate(u1, @enumToInt(ccr)); - return Instruction{ - .format_4e = .{ - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .cc1 = ccr_cc1, - .cc0 = ccr_cc0, - .sw_trap = sw_trap, - }, - }; - } - - fn format4f( - op3: u6, - opf_low: u5, - rcond: RCondition, - rs1: Register, - rs2: Register, - rd: Register, - ) Instruction { - return Instruction{ - .format_4f = .{ - .rd = rd.enc(), - .op3 = op3, - .rs1 = rs1.enc(), - .rcond = @enumToInt(rcond), - .opf_low = opf_low, - .rs2 = rs2.enc(), - }, - }; - } - - fn format4g(op3: u6, opf_low: u6, opf_cc: u3, cond: Condition, rs2: Register, rd: Register) Instruction { - return Instruction{ - .format_4g = .{ - .rd = rd.enc(), - .op3 = op3, - .cond = cond, - .opf_cc = opf_cc, - .opf_low = opf_low, - .rs2 = rs2.enc(), - }, - }; - } - - // 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 jmpl(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { - return switch (s2) { - Register => format3a(0b10, 0b11_1000, rs1, rs2, rd), - i13 => format3b(0b10, 0b11_1000, 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 @"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 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 stb(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { - return switch (s2) { - Register => format3a(0b11, 0b00_0101, rs1, rs2, rd), - i13 => format3b(0b11, 0b00_0101, rs1, rs2, rd), - else => unreachable, - }; - } - - pub fn sth(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { - return switch (s2) { - Register => format3a(0b11, 0b00_0110, rs1, rs2, rd), - i13 => format3b(0b11, 0b00_0110, rs1, rs2, rd), - else => unreachable, - }; - } - - pub fn stw(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { - return switch (s2) { - Register => format3a(0b11, 0b00_0100, rs1, rs2, rd), - i13 => format3b(0b11, 0b00_0100, rs1, rs2, rd), - else => unreachable, - }; - } - - pub fn stx(comptime s2: type, rs1: Register, rs2: s2, rd: Register) Instruction { - return switch (s2) { - Register => format3a(0b11, 0b00_1110, rs1, rs2, rd), - i13 => format3b(0b11, 0b00_1110, rs1, rs2, rd), - else => unreachable, - }; - } - - 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" { - const Testcase = struct { - inst: Instruction, - expected: u32, - }; - - // Note that the testcases might or might not be a valid instruction - // This is mostly just to check the behavior of the format packed structs - // since currently stage1 doesn't properly implement it in all cases - const testcases = [_]Testcase{ - .{ - .inst = Instruction.format1(4), - .expected = 0b01_000000000000000000000000000001, - }, - .{ - .inst = Instruction.format2a(4, 0, .g0), - .expected = 0b00_00000_100_0000000000000000000000, - }, - .{ - .inst = Instruction.format2b(6, 3, true, -4), - .expected = 0b00_1_0011_110_1111111111111111111111, - }, - .{ - .inst = Instruction.format2c(3, 0, false, true, .xcc, 8), - .expected = 0b00_0_0000_011_1_0_1_0000000000000000010, - }, - .{ - .inst = Instruction.format2d(7, .eq_zero, false, true, .o0, 20), - .expected = 0b00_0_0_001_111_00_1_01000_00000000000101, - }, - .{ - .inst = Instruction.format3a(3, 5, .g0, .o1, .l2), - .expected = 0b11_10010_000101_00000_0_00000000_01001, - }, - .{ - .inst = Instruction.format3b(3, 5, .g0, -1, .l2), - .expected = 0b11_10010_000101_00000_1_1111111111111, - }, - .{ - .inst = Instruction.format3c(3, 5, .g0, .o1), - .expected = 0b11_00000_000101_00000_0_00000000_01001, - }, - .{ - .inst = Instruction.format3d(3, 5, .g0, 0), - .expected = 0b11_00000_000101_00000_1_0000000000000, - }, - .{ - .inst = Instruction.format3e(3, 5, .ne_zero, .g0, .o1, .l2), - .expected = 0b11_10010_000101_00000_0_101_00000_01001, - }, - .{ - .inst = Instruction.format3f(3, 5, .ne_zero, .g0, -1, .l2), - .expected = 0b11_10010_000101_00000_1_101_1111111111, - }, - .{ - .inst = Instruction.format3g(3, 5, .g0, .o1, .l2), - .expected = 0b11_10010_000101_00000_1_00000000_01001, - }, - .{ - .inst = Instruction.format3h(.{}, .{}), - .expected = 0b10_00000_101000_01111_1_000000_000_0000, - }, - .{ - .inst = Instruction.format3i(3, 5, .g0, .o1, .l2, .asi_primary_little), - .expected = 0b11_10010_000101_00000_0_10001000_01001, - }, - .{ - .inst = Instruction.format3j(3, 5, 31, 0), - .expected = 0b11_11111_000101_0000000000000000000, - }, - .{ - .inst = Instruction.format3k(3, 5, .shift32, .g0, .o1, .l2), - .expected = 0b11_10010_000101_00000_0_0_0000000_01001, - }, - .{ - .inst = Instruction.format3l(3, 5, .g0, 31, .l2), - .expected = 0b11_10010_000101_00000_1_0_0000000_11111, - }, - .{ - .inst = Instruction.format3m(3, 5, .g0, 63, .l2), - .expected = 0b11_10010_000101_00000_1_1_000000_111111, - }, - .{ - .inst = Instruction.format3n(3, 5, 0, .o1, .l2), - .expected = 0b11_10010_000101_00000_000000000_01001, - }, - .{ - .inst = Instruction.format3o(3, 5, 0, .xcc, .o1, .l2), - .expected = 0b11_000_1_0_000101_01001_000000000_10010, - }, - .{ - .inst = Instruction.format3p(3, 5, 0, .g0, .o1, .l2), - .expected = 0b11_10010_000101_00000_000000000_01001, - }, - .{ - .inst = Instruction.format3q(3, 5, .g0, .o1), - .expected = 0b11_01001_000101_00000_00000000000000, - }, - .{ - .inst = Instruction.format3r(3, 5, 4), - .expected = 0b11_00100_000101_0000000000000000000, - }, - .{ - .inst = Instruction.format3s(3, 5, .g0), - .expected = 0b11_00000_000101_0000000000000000000, - }, - .{ - .inst = Instruction.format4a(8, .xcc, .g0, .o1, .l2), - .expected = 0b10_10010_001000_00000_0_1_0_000000_01001, - }, - .{ - .inst = Instruction.format4b(8, .xcc, .g0, -1, .l2), - .expected = 0b10_10010_001000_00000_1_1_0_11111111111, - }, - .{ - .inst = Instruction.format4c(8, 0, .xcc, .g0, .o1), - .expected = 0b10_01001_001000_1_0000_0_1_0_000000_00000, - }, - .{ - .inst = Instruction.format4d(8, 0, .xcc, 0, .l2), - .expected = 0b10_10010_001000_1_0000_1_1_0_00000000000, - }, - .{ - .inst = Instruction.format4e(8, .xcc, .g0, .o1, 0), - .expected = 0b10_01001_001000_00000_1_1_0_0000_0000000, - }, - .{ - .inst = Instruction.format4f(8, 4, .eq_zero, .g0, .o1, .l2), - .expected = 0b10_10010_001000_00000_0_001_00100_01001, - }, - .{ - .inst = Instruction.format4g(8, 4, 2, 0, .o1, .l2), - .expected = 0b10_10010_001000_0_0000_010_000100_01001, - }, - }; - - for (testcases) |case| { - const actual = case.inst.toU32(); - testing.expectEqual(case.expected, actual) catch |err| { - std.debug.print("error: {x}\n", .{err}); - std.debug.print("case: {x}\n", .{case}); - return err; - }; - } -} diff --git a/src/codegen.zig b/src/codegen.zig index debd7b5e9d72b9ed5057c6fe6b7714f5bb7289d9..def69d952fdd353b3b61615b21577219f0d4265d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -103,7 +103,7 @@ pub fn generateFunction( //.riscv32 => return Function(.riscv32).generate(bin_file, src_loc, func, air, liveness, code, debug_output), .riscv64 => return @import("arch/riscv64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output), //.sparc => return Function(.sparc).generate(bin_file, src_loc, func, air, liveness, code, debug_output), - .sparcv9 => return @import("arch/sparcv9/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output), + .sparc64 => return @import("arch/sparc64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output), //.sparcel => return Function(.sparcel).generate(bin_file, src_loc, func, air, liveness, code, debug_output), //.s390x => return Function(.s390x).generate(bin_file, src_loc, func, air, liveness, code, debug_output), //.tce => return Function(.tce).generate(bin_file, src_loc, func, air, liveness, code, debug_output), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5e70710046f23691411e9a2274c6d3bbaaa7bdc1..5a8516b363f9435c3b1b12259640cc8010724564 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -53,7 +53,7 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { .riscv32 => "riscv32", .riscv64 => "riscv64", .sparc => "sparc", - .sparcv9 => "sparcv9", + .sparc64 => "sparc64", .sparcel => "sparcel", .s390x => "s390x", .tce => "tce", @@ -7797,7 +7797,7 @@ fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { llvm.LLVMInitializeRISCVAsmPrinter(); llvm.LLVMInitializeRISCVAsmParser(); }, - .sparc, .sparcv9, .sparcel => { + .sparc, .sparc64, .sparcel => { llvm.LLVMInitializeSparcTarget(); llvm.LLVMInitializeSparcTargetInfo(); llvm.LLVMInitializeSparcTargetMC(); diff --git a/src/glibc.zig b/src/glibc.zig index dee4e50e37a0e56ae6714b16c015f32622549de8..4deac5275fba5db8502d5adf04ea403864d37a42 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -43,6 +43,29 @@ pub const libs = [_]Lib{ .{ .name = "util", .sover = 1 }, }; +// glibc's naming of Zig architectures +const Arch = enum(c_int) { + arm, + armeb, + aarch64, + aarch64_be, + mips, + mipsel, + mips64, + mips64el, + powerpc, + powerpc64, + powerpc64le, + riscv32, + riscv64, + sparc, + sparcv9, + sparcel, + s390x, + i386, + x86_64, +}; + pub const LoadMetaDataError = error{ /// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data. ZigInstallationCorrupt, @@ -134,7 +157,7 @@ pub fn loadMetaData(gpa: Allocator, zig_lib_dir: fs.Dir) LoadMetaDataError!*ABI log.err("abilists: expected ABI name", .{}); return error.ZigInstallationCorrupt; }; - const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { + const arch_tag = std.meta.stringToEnum(Arch, arch_name) orelse { log.err("abilists: unrecognized arch: '{s}'", .{arch_name}); return error.ZigInstallationCorrupt; }; @@ -148,7 +171,7 @@ pub fn loadMetaData(gpa: Allocator, zig_lib_dir: fs.Dir) LoadMetaDataError!*ABI }; targets[i] = .{ - .arch = arch_tag, + .arch = glibcToZigArch(arch_tag), .os = .linux, .abi = abi_tag, }; @@ -381,7 +404,7 @@ fn start_asm_path(comp: *Compilation, arena: Allocator, basename: []const u8) ![ const arch = comp.getTarget().cpu.arch; const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; - const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9; + const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64; const is_64 = arch.ptrBitWidth() == 64; const s = path.sep_str; @@ -519,7 +542,7 @@ fn add_include_dirs_arch( const is_x86 = arch == .i386 or arch == .x86_64; const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; - const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9; + const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64; const is_64 = arch.ptrBitWidth() == 64; const s = path.sep_str; @@ -1115,6 +1138,30 @@ fn buildSharedLib( try sub_compilation.updateSubCompilation(); } +fn glibcToZigArch(arch_tag: Arch) std.Target.Cpu.Arch { + return switch (arch_tag) { + .arm => .arm, + .armeb => .armeb, + .aarch64 => .aarch64, + .aarch64_be => .aarch64_be, + .mips => .mips, + .mipsel => .mipsel, + .mips64 => .mips64, + .mips64el => .mips64el, + .powerpc => .powerpc, + .powerpc64 => .powerpc64, + .powerpc64le => .powerpc64le, + .riscv32 => .riscv32, + .riscv64 => .riscv64, + .sparc => .sparc, + .sparcv9 => .sparc64, // In glibc, sparc64 is called sparcv9. + .sparcel => .sparcel, + .s390x => .s390x, + .i386 => .i386, + .x86_64 => .x86_64, + }; +} + // Return true if glibc has crti/crtn sources for that architecture. pub fn needsCrtiCrtn(target: std.Target) bool { return switch (target.cpu.arch) { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 11a701fcf9cd33176337391b4bd258dd7d36d8dc..be366076b467b2e929ee84bc211080ccb663d9a1 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -307,7 +307,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { const page_size: u32 = switch (options.target.cpu.arch) { .powerpc64le => 0x10000, - .sparcv9 => 0x2000, + .sparc64 => 0x2000, else => 0x1000, }; @@ -2940,7 +2940,7 @@ fn getLDMOption(target: std.Target) ?[]const u8 { .powerpc64 => return "elf64ppc", .powerpc64le => return "elf64lppc", .sparc, .sparcel => return "elf32_sparc", - .sparcv9 => return "elf64_sparc", + .sparc64 => return "elf64_sparc", .mips => return "elf32btsmip", .mipsel => return "elf32ltsmip", .mips64 => { diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig index 39994516fac687738dd9651e8e0fe19956059343..09f39d89907af199580bca527845bdea06a56a83 100644 --- a/src/link/Plan9/aout.zig +++ b/src/link/Plan9/aout.zig @@ -110,7 +110,7 @@ pub const R_MAGIC = _MAGIC(HDR_MAGIC, 28); // arm64 pub fn magicFromArch(arch: std.Target.Cpu.Arch) !u32 { return switch (arch) { .i386 => I_MAGIC, - .sparc => K_MAGIC, // TODO should sparcv9 and sparcel go here? + .sparc => K_MAGIC, // TODO should sparc64 and sparcel go here? .mips => V_MAGIC, .arm => E_MAGIC, .aarch64 => R_MAGIC, diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index d101030c33df8635831a80c387c64bc52b188868..d6fb6d475376bb360da80a9fe21395ac9b07b09d 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -21,6 +21,7 @@ #include "zigendian.h" #include +#include #include #include @@ -3596,7 +3597,7 @@ static LLVMValueRef gen_soft_float_bin_op(CodeGen *g, LLVMValueRef op1_value, LL result = LLVMBuildLoad(g->builder, result, ""); } - // Some operations are implemented as compound ops and require us to perform some + // Some operations are implemented as compound ops and require us to perform some // more operations before we obtain the final result switch (op_id) { case IrBinOpDivTrunc: @@ -9983,6 +9984,12 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { cur_arch = arch_name; } } + + // Workaround to LLVM/Zig naming mismatch. + // LLVM calls it sparcv9, while Zig calls it sparc64. + if (!strcmp(cur_arch, "sparcv9")) { + cur_arch = "sparc64"; + } } assert(cur_arch != nullptr); diff --git a/src/target.zig b/src/target.zig index 9249ed1b606fb767b036e8f70768cdad2607e7d4..c794ea52b02152c041654299aae9140af249ca22 100644 --- a/src/target.zig +++ b/src/target.zig @@ -63,7 +63,7 @@ pub const available_libcs = [_]ArchOsAbi{ .{ .arch = .s390x, .os = .linux, .abi = .gnu }, .{ .arch = .s390x, .os = .linux, .abi = .musl }, .{ .arch = .sparc, .os = .linux, .abi = .gnu }, - .{ .arch = .sparcv9, .os = .linux, .abi = .gnu }, + .{ .arch = .sparc64, .os = .linux, .abi = .gnu }, .{ .arch = .wasm32, .os = .freestanding, .abi = .musl }, .{ .arch = .wasm32, .os = .wasi, .abi = .musl }, .{ .arch = .x86_64, .os = .linux, .abi = .gnu }, @@ -118,7 +118,7 @@ pub fn osArchName(target: std.Target) [:0]const u8 { .mips, .mipsel, .mips64, .mips64el => "mips", .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", .riscv32, .riscv64 => "riscv", - .sparc, .sparcel, .sparcv9 => "sparc", + .sparc, .sparcel, .sparc64 => "sparc", .i386, .x86_64 => "x86", else => @tagName(target.cpu.arch), }, @@ -232,7 +232,7 @@ pub fn hasLlvmSupport(target: std.Target) bool { .riscv32, .riscv64, .sparc, - .sparcv9, + .sparc64, .sparcel, .s390x, .tce, @@ -351,7 +351,7 @@ pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { .riscv32 => .riscv32, .riscv64 => .riscv64, .sparc => .sparc, - .sparcv9 => .sparcv9, + .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. .sparcel => .sparcel, .s390x => .systemz, .tce => .tce, @@ -617,7 +617,7 @@ pub fn atomicPtrAlignment( .powerpc64, .powerpc64le, .riscv64, - .sparcv9, + .sparc64, .s390x, .amdil64, .hsail64, @@ -723,7 +723,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, + .sparc, .sparcel, .sparc64 => 4, .riscv64 => 2, else => 1, }; diff --git a/src/type.zig b/src/type.zig index 8742d9b61c86435ff10346dd8025e056a6bfb638..2c3ce0d900087874441d9285813400822a13b1da 100644 --- a/src/type.zig +++ b/src/type.zig @@ -6229,7 +6229,7 @@ pub const CType = enum { .mips64, .mips64el, .sparc, - .sparcv9, + .sparc64, .sparcel, .powerpc, .powerpcle, @@ -6267,7 +6267,7 @@ pub const CType = enum { .mips64, .mips64el, .sparc, - .sparcv9, + .sparc64, .sparcel, .powerpc, .powerpcle, diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 6c1122323a69fe22e011cea254fcf299a0fda0af..b89cd5ee692c5dac0e75e6aa807aa5df2c48ec02 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -135,7 +135,7 @@ test "alignment and size of structs with 128-bit fields" { .powerpc64, .powerpc64le, .riscv64, - .sparcv9, + .sparc64, .x86_64, .aarch64, .aarch64_be, diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index aaf61745b03c6c05b722e5a07f0d95c475945817..9847054692c847fb01694892ebe938ed2d0eacd4 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -604,7 +604,7 @@ test "vector shift operators" { .mips64, .mips64el, .riscv64, - .sparcv9, + .sparc64, => { // LLVM miscompiles on this architecture // https://github.com/ziglang/zig/issues/4951 diff --git a/test/cases/sparc64-linux/hello_world.zig b/test/cases/sparc64-linux/hello_world.zig new file mode 100644 index 0000000000000000000000000000000000000000..b5a2562c44c20d5b8ffea3eae1b36dc50fbaf1cb --- /dev/null +++ b/test/cases/sparc64-linux/hello_world.zig @@ -0,0 +1,22 @@ +const msg = "Hello, World!\n"; + +fn length() usize { + return msg.len; +} + +pub fn main() void { + asm volatile ("ta 0x6d" + : + : [number] "{g1}" (4), + [arg1] "{o0}" (1), + [arg2] "{o1}" (@ptrToInt(msg)), + [arg3] "{o2}" (length()), + : "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory" + ); +} + +// run +// target=sparc64-linux +// +// Hello, World! +// diff --git a/test/cases/sparcv9-linux/hello_world.zig b/test/cases/sparcv9-linux/hello_world.zig deleted file mode 100644 index 1aea9da7a341b5e334a737b76830402f03406f7d..0000000000000000000000000000000000000000 --- a/test/cases/sparcv9-linux/hello_world.zig +++ /dev/null @@ -1,22 +0,0 @@ -const msg = "Hello, World!\n"; - -fn length() usize { - return msg.len; -} - -pub fn main() void { - asm volatile ("ta 0x6d" - : - : [number] "{g1}" (4), - [arg1] "{o0}" (1), - [arg2] "{o1}" (@ptrToInt(msg)), - [arg3] "{o2}" (length()), - : "o0", "o1", "o2", "o3", "o4", "o5", "o6", "o7", "memory" - ); -} - -// run -// target=sparcv9-linux -// -// Hello, World! -// diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 579667a5a631dd44b76abd084213f3a1591417f5..39fbe08a4a9c6e5d2e8f8daaa672c9708aa5818f 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -170,13 +170,13 @@ const glibc_targets = [_]LibCTarget{ .abi = MultiAbi{ .specific = Abi.gnu }, }, LibCTarget{ - .name = "sparc64-linux-gnu", + .name = "sparc-linux-gnu", .arch = MultiArch{ .specific = Arch.sparc }, .abi = MultiAbi{ .specific = Abi.gnu }, }, LibCTarget{ .name = "sparcv9-linux-gnu", - .arch = MultiArch{ .specific = Arch.sparcv9 }, + .arch = MultiArch{ .specific = Arch.sparc64 }, .abi = MultiAbi{ .specific = Abi.gnu }, }, LibCTarget{