diff --git a/CMakeLists.txt b/CMakeLists.txt index d9824e5c1264a99f7f6c59d7351e3620f053ec43..b78b0012f148c7170eac5be9fafe31d5448b13d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -550,6 +550,14 @@ set(ZIG_STAGE2_SOURCES src/clang_options.zig src/clang_options_data.zig src/codegen.zig + src/codegen/aarch64.zig + src/codegen/aarch64/abi.zig + src/codegen/aarch64/Assemble.zig + src/codegen/aarch64/Disassemble.zig + src/codegen/aarch64/encoding.zig + src/codegen/aarch64/instructions.zon + src/codegen/aarch64/Mir.zig + src/codegen/aarch64/Select.zig src/codegen/c.zig src/codegen/c/Type.zig src/codegen/llvm.zig diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 8b60a75399a8c06c95143a4417a5a55774afb5b1..e618f72d2ff689bcda24776081718e26f9660917 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -16,6 +16,7 @@ var stdin_buffer: [4096]u8 = undefined; var stdout_buffer: [4096]u8 = undefined; const crippled = switch (builtin.zig_backend) { + .stage2_aarch64, .stage2_powerpc, .stage2_riscv64, => true, @@ -287,13 +288,14 @@ pub fn log( /// work-in-progress backends can handle it. pub fn mainSimple() anyerror!void { @disableInstrumentation(); - // is the backend capable of printing to stderr? + // is the backend capable of calling `std.fs.File.writeAll`? + const enable_write = switch (builtin.zig_backend) { + .stage2_aarch64, .stage2_riscv64 => true, + else => false, + }; + // is the backend capable of calling `std.Io.Writer.print`? const enable_print = switch (builtin.zig_backend) { - else => false, - }; - // is the backend capable of using std.fmt.format to print a summary at the end? - const print_summary = switch (builtin.zig_backend) { - .stage2_riscv64 => true, + .stage2_aarch64, .stage2_riscv64 => true, else => false, }; @@ -302,34 +304,31 @@ pub fn mainSimple() anyerror!void { var failed: u64 = 0; // we don't want to bring in File and Writer if the backend doesn't support it - const stderr = if (comptime enable_print) std.fs.File.stderr() else {}; + const stdout = if (enable_write) std.fs.File.stdout() else {}; for (builtin.test_functions) |test_fn| { + if (enable_write) { + stdout.writeAll(test_fn.name) catch {}; + stdout.writeAll("... ") catch {}; + } if (test_fn.func()) |_| { - if (enable_print) { - stderr.writeAll(test_fn.name) catch {}; - stderr.writeAll("... ") catch {}; - stderr.writeAll("PASS\n") catch {}; - } + if (enable_write) stdout.writeAll("PASS\n") catch {}; } else |err| { - if (enable_print) { - stderr.writeAll(test_fn.name) catch {}; - stderr.writeAll("... ") catch {}; - } if (err != error.SkipZigTest) { - if (enable_print) stderr.writeAll("FAIL\n") catch {}; + if (enable_write) stdout.writeAll("FAIL\n") catch {}; failed += 1; - if (!enable_print) return err; + if (!enable_write) return err; continue; } - if (enable_print) stderr.writeAll("SKIP\n") catch {}; + if (enable_write) stdout.writeAll("SKIP\n") catch {}; skipped += 1; continue; } passed += 1; } - if (enable_print and print_summary) { - stderr.deprecatedWriter().print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {}; + if (enable_print) { + var stdout_writer = stdout.writer(&.{}); + stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {}; } if (failed != 0) std.process.exit(1); } diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index 46db464fd9fc2d9bdc8cf7e372903ceb361480d1..17e9e04da7f73b180001bfab583de0550a68f734 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -240,7 +240,7 @@ comptime { _ = @import("compiler_rt/udivmodti4.zig"); // extra - _ = @import("compiler_rt/os_version_check.zig"); + if (builtin.zig_backend != .stage2_aarch64) _ = @import("compiler_rt/os_version_check.zig"); _ = @import("compiler_rt/emutls.zig"); _ = @import("compiler_rt/arm.zig"); _ = @import("compiler_rt/aulldiv.zig"); @@ -249,12 +249,12 @@ comptime { _ = @import("compiler_rt/hexagon.zig"); if (@import("builtin").object_format != .c) { - _ = @import("compiler_rt/atomics.zig"); + if (builtin.zig_backend != .stage2_aarch64) _ = @import("compiler_rt/atomics.zig"); _ = @import("compiler_rt/stack_probe.zig"); // macOS has these functions inside libSystem. if (builtin.cpu.arch.isAARCH64() and !builtin.os.tag.isDarwin()) { - _ = @import("compiler_rt/aarch64_outline_atomics.zig"); + if (builtin.zig_backend != .stage2_aarch64) _ = @import("compiler_rt/aarch64_outline_atomics.zig"); } _ = @import("compiler_rt/memcpy.zig"); diff --git a/lib/compiler_rt/addo.zig b/lib/compiler_rt/addo.zig index beb6249223e6036f0cd61424b4a70615dfcb83b3..610d6206904b8fe5368d56330448df3a59e8a3e6 100644 --- a/lib/compiler_rt/addo.zig +++ b/lib/compiler_rt/addo.zig @@ -1,6 +1,4 @@ const std = @import("std"); -const builtin = @import("builtin"); -const is_test = builtin.is_test; const common = @import("./common.zig"); pub const panic = @import("common.zig").panic; @@ -16,7 +14,7 @@ comptime { // - addoXi4_generic as default inline fn addoXi4_generic(comptime ST: type, a: ST, b: ST, overflow: *c_int) ST { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); overflow.* = 0; const sum: ST = a +% b; // Hackers Delight: section Overflow Detection, subsection Signed Add/Subtract diff --git a/lib/compiler_rt/addoti4_test.zig b/lib/compiler_rt/addoti4_test.zig index dc85830df939a134f3f3a9f467f0d3440179f646..d031d1d428a24a36fc22e98e4a022e1c9c02c7d3 100644 --- a/lib/compiler_rt/addoti4_test.zig +++ b/lib/compiler_rt/addoti4_test.zig @@ -1,4 +1,5 @@ const addv = @import("addo.zig"); +const builtin = @import("builtin"); const std = @import("std"); const testing = std.testing; const math = std.math; @@ -23,6 +24,8 @@ fn simple_addoti4(a: i128, b: i128, overflow: *c_int) i128 { } test "addoti4" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + const min: i128 = math.minInt(i128); const max: i128 = math.maxInt(i128); var i: i128 = 1; diff --git a/lib/compiler_rt/clear_cache.zig b/lib/compiler_rt/clear_cache.zig index e4a0a9d00db623d4bb859c2388d40f138498894c..c43d35602cbebace6045c1dca3c5c40fe77254b9 100644 --- a/lib/compiler_rt/clear_cache.zig +++ b/lib/compiler_rt/clear_cache.zig @@ -97,8 +97,7 @@ fn clear_cache(start: usize, end: usize) callconv(.c) void { .nbytes = end - start, .whichcache = 3, // ICACHE | DCACHE }; - asm volatile ( - \\ syscall + asm volatile ("syscall" : : [_] "{$2}" (165), // nr = SYS_sysarch [_] "{$4}" (0), // op = MIPS_CACHEFLUSH @@ -116,11 +115,8 @@ fn clear_cache(start: usize, end: usize) callconv(.c) void { } else if (arm64 and !apple) { // Get Cache Type Info. // TODO memoize this? - var ctr_el0: u64 = 0; - asm volatile ( - \\mrs %[x], ctr_el0 - \\ - : [x] "=r" (ctr_el0), + const ctr_el0 = asm volatile ("mrs %[ctr_el0], ctr_el0" + : [ctr_el0] "=r" (-> u64), ); // The DC and IC instructions must use 64-bit registers so we don't use // uintptr_t in case this runs in an IPL32 environment. @@ -187,9 +183,7 @@ fn clear_cache(start: usize, end: usize) callconv(.c) void { exportIt(); } else if (os == .linux and loongarch) { // See: https://github.com/llvm/llvm-project/blob/cf54cae26b65fc3201eff7200ffb9b0c9e8f9a13/compiler-rt/lib/builtins/clear_cache.c#L94-L95 - asm volatile ( - \\ ibar 0 - ); + asm volatile ("ibar 0"); exportIt(); } diff --git a/lib/compiler_rt/cmp.zig b/lib/compiler_rt/cmp.zig index e1273aa6222536a965d70dcd4c3ac45cd2ef4bdb..67cb5b0938fa27c41902bf86111a27ca3a8f436c 100644 --- a/lib/compiler_rt/cmp.zig +++ b/lib/compiler_rt/cmp.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const is_test = builtin.is_test; const common = @import("common.zig"); pub const panic = common.panic; diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index f5423019f14d0192d78042f60db906f00cb36905..1160b1c718e5898c8cb1ba21975364f4fa2d887b 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -102,9 +102,14 @@ pub const gnu_f16_abi = switch (builtin.cpu.arch) { pub const want_sparc_abi = builtin.cpu.arch.isSPARC(); +pub const test_safety = switch (builtin.zig_backend) { + .stage2_aarch64 => false, + else => builtin.is_test, +}; + // Avoid dragging in the runtime safety mechanisms into this .o file, unless // we're trying to test compiler-rt. -pub const panic = if (builtin.is_test) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.no_panic; +pub const panic = if (test_safety) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.no_panic; /// This seems to mostly correspond to `clang::TargetInfo::HasFloat16`. pub fn F16T(comptime OtherType: type) type { diff --git a/lib/compiler_rt/comparedf2_test.zig b/lib/compiler_rt/comparedf2_test.zig index 9444c6adf7bbcfd614d7d67022eeab6353d658a6..dbae6bbeeca09ef401a17334d57f3f3be824c781 100644 --- a/lib/compiler_rt/comparedf2_test.zig +++ b/lib/compiler_rt/comparedf2_test.zig @@ -4,7 +4,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const is_test = builtin.is_test; const __eqdf2 = @import("./cmpdf2.zig").__eqdf2; const __ledf2 = @import("./cmpdf2.zig").__ledf2; diff --git a/lib/compiler_rt/comparesf2_test.zig b/lib/compiler_rt/comparesf2_test.zig index 40b1324cfa708fe00ec59ce153237a6c8cba29bb..65e78da99e1f4c48bcd2522772c4af45c4b14366 100644 --- a/lib/compiler_rt/comparesf2_test.zig +++ b/lib/compiler_rt/comparesf2_test.zig @@ -4,7 +4,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const is_test = builtin.is_test; const __eqsf2 = @import("./cmpsf2.zig").__eqsf2; const __lesf2 = @import("./cmpsf2.zig").__lesf2; diff --git a/lib/compiler_rt/count0bits.zig b/lib/compiler_rt/count0bits.zig index c9bdfb7c2366603ccc4fdb1439d77e79c97ce56b..874604eb2c60c240e1d4aee1721c7ecbdb62bf0b 100644 --- a/lib/compiler_rt/count0bits.zig +++ b/lib/compiler_rt/count0bits.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const is_test = builtin.is_test; const common = @import("common.zig"); pub const panic = common.panic; diff --git a/lib/compiler_rt/divdf3.zig b/lib/compiler_rt/divdf3.zig index 0340404a6946e328520d792a902056475f87acb2..7b47cd3a703c0e55e3b630bad6b132383cb812ab 100644 --- a/lib/compiler_rt/divdf3.zig +++ b/lib/compiler_rt/divdf3.zig @@ -5,7 +5,6 @@ const std = @import("std"); const builtin = @import("builtin"); const arch = builtin.cpu.arch; -const is_test = builtin.is_test; const common = @import("common.zig"); const normalize = common.normalize; diff --git a/lib/compiler_rt/divmodei4.zig b/lib/compiler_rt/divmodei4.zig index 3f12e8697d278170fbeaa1d61bad5f32686d27bd..ab11452206f5d52d3a709b5c90fb23344a59cf72 100644 --- a/lib/compiler_rt/divmodei4.zig +++ b/lib/compiler_rt/divmodei4.zig @@ -34,7 +34,7 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []u32, v: []u32) !void { } pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size])); const u: []u32 = @ptrCast(@alignCast(u_p[0..byte_size])); @@ -43,7 +43,7 @@ pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) vo } pub fn __modei4(r_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); const r: []u32 = @ptrCast(@alignCast(r_p[0..byte_size])); const u: []u32 = @ptrCast(@alignCast(u_p[0..byte_size])); diff --git a/lib/compiler_rt/fixint_test.zig b/lib/compiler_rt/fixint_test.zig index 57b4093809c1c2e61a512d71de8887395ca1903b..198167ab867c22ed6160e83ed90920a6c5e97f28 100644 --- a/lib/compiler_rt/fixint_test.zig +++ b/lib/compiler_rt/fixint_test.zig @@ -1,4 +1,3 @@ -const is_test = @import("builtin").is_test; const std = @import("std"); const math = std.math; const testing = std.testing; diff --git a/lib/compiler_rt/int.zig b/lib/compiler_rt/int.zig index 4a89d0799d4f9762033d4c6090d969f25b02ea01..16c504ee66d1eb545d2629a8f54174da37c6a82d 100644 --- a/lib/compiler_rt/int.zig +++ b/lib/compiler_rt/int.zig @@ -6,7 +6,6 @@ const testing = std.testing; const maxInt = std.math.maxInt; const minInt = std.math.minInt; const arch = builtin.cpu.arch; -const is_test = builtin.is_test; const common = @import("common.zig"); const udivmod = @import("udivmod.zig").udivmod; const __divti3 = @import("divti3.zig").__divti3; diff --git a/lib/compiler_rt/memcpy.zig b/lib/compiler_rt/memcpy.zig index 30971677ab0f9302e01006cd5e3fe159b89b77f0..424e92954d78e38c1317d9a987efe57238274200 100644 --- a/lib/compiler_rt/memcpy.zig +++ b/lib/compiler_rt/memcpy.zig @@ -11,7 +11,7 @@ comptime { .visibility = common.visibility, }; - if (builtin.mode == .ReleaseSmall) + if (builtin.mode == .ReleaseSmall or builtin.zig_backend == .stage2_aarch64) @export(&memcpySmall, export_options) else @export(&memcpyFast, export_options); @@ -195,6 +195,8 @@ inline fn copyRange4( } test "memcpy" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + const S = struct { fn testFunc(comptime copy_func: anytype) !void { const max_len = 1024; diff --git a/lib/compiler_rt/memmove.zig b/lib/compiler_rt/memmove.zig index 71289a50ae4eb1ea78c598b49fa8712653272748..46c5a631cb2e110862c75624ddf80e3a81b7c1a8 100644 --- a/lib/compiler_rt/memmove.zig +++ b/lib/compiler_rt/memmove.zig @@ -14,7 +14,7 @@ comptime { .visibility = common.visibility, }; - if (builtin.mode == .ReleaseSmall) + if (builtin.mode == .ReleaseSmall or builtin.zig_backend == .stage2_aarch64) @export(&memmoveSmall, export_options) else @export(&memmoveFast, export_options); @@ -39,7 +39,7 @@ fn memmoveSmall(opt_dest: ?[*]u8, opt_src: ?[*]const u8, len: usize) callconv(.c } fn memmoveFast(dest: ?[*]u8, src: ?[*]u8, len: usize) callconv(.c) ?[*]u8 { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const small_limit = @max(2 * @sizeOf(Element), @sizeOf(Element)); if (copySmallLength(small_limit, dest.?, src.?, len)) return dest; @@ -79,7 +79,7 @@ inline fn copyLessThan16( src: [*]const u8, len: usize, ) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); if (len < 4) { if (len == 0) return; const b = len / 2; @@ -100,7 +100,7 @@ inline fn copy16ToSmallLimit( src: [*]const u8, len: usize, ) bool { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); inline for (2..(std.math.log2(small_limit) + 1) / 2 + 1) |p| { const limit = 1 << (2 * p); if (len < limit) { @@ -119,7 +119,7 @@ inline fn copyRange4( src: [*]const u8, len: usize, ) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); comptime assert(std.math.isPowerOfTwo(copy_len)); assert(len >= copy_len); assert(len < 4 * copy_len); @@ -147,7 +147,7 @@ inline fn copyForwards( src: [*]const u8, len: usize, ) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); assert(len >= 2 * @sizeOf(Element)); const head = src[0..@sizeOf(Element)].*; @@ -181,7 +181,7 @@ inline fn copyBlocks( src: anytype, max_bytes: usize, ) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const T = @typeInfo(@TypeOf(dest)).pointer.child; comptime assert(T == @typeInfo(@TypeOf(src)).pointer.child); @@ -217,6 +217,8 @@ inline fn copyBackwards( } test memmoveFast { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + const max_len = 1024; var buffer: [max_len + @alignOf(Element) - 1]u8 = undefined; for (&buffer, 0..) |*b, i| { diff --git a/lib/compiler_rt/mulf3.zig b/lib/compiler_rt/mulf3.zig index ad60ec41a5bfa4b93df30726be9cedd2509a84e2..34d39fb9b705dbf570a1841be8a6695906448d09 100644 --- a/lib/compiler_rt/mulf3.zig +++ b/lib/compiler_rt/mulf3.zig @@ -6,7 +6,7 @@ const common = @import("./common.zig"); /// Ported from: /// https://github.com/llvm/llvm-project/blob/2ffb1b0413efa9a24eb3c49e710e36f92e2cb50b/compiler-rt/lib/builtins/fp_mul_impl.inc pub inline fn mulf3(comptime T: type, a: T, b: T) T { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const typeWidth = @typeInfo(T).float.bits; const significandBits = math.floatMantissaBits(T); const fractionalBits = math.floatFractionalBits(T); @@ -163,7 +163,7 @@ pub inline fn mulf3(comptime T: type, a: T, b: T) T { /// /// This is analogous to an shr version of `@shlWithOverflow` fn wideShrWithTruncation(comptime Z: type, hi: *Z, lo: *Z, count: u32) bool { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const typeWidth = @typeInfo(Z).int.bits; var inexact = false; if (count < typeWidth) { diff --git a/lib/compiler_rt/rem_pio2_large.zig b/lib/compiler_rt/rem_pio2_large.zig index b107a0fabb527dfd440f14ff412d4a0f9d971ba4..f15e0d71f62e882f922e93b97e7463117da5a40d 100644 --- a/lib/compiler_rt/rem_pio2_large.zig +++ b/lib/compiler_rt/rem_pio2_large.zig @@ -251,7 +251,7 @@ const PIo2 = [_]f64{ /// compiler will convert from decimal to binary accurately enough /// to produce the hexadecimal values shown. /// -pub fn rem_pio2_large(x: []f64, y: []f64, e0: i32, nx: i32, prec: usize) i32 { +pub fn rem_pio2_large(x: []const f64, y: []f64, e0: i32, nx: i32, prec: usize) i32 { var jz: i32 = undefined; var jx: i32 = undefined; var jv: i32 = undefined; diff --git a/lib/compiler_rt/stack_probe.zig b/lib/compiler_rt/stack_probe.zig index 94212b7a23b0870bcbcc7bb632f8e849010b2a3c..21259ec4354a09e6ef8e0b82fdd9271b48ef6c85 100644 --- a/lib/compiler_rt/stack_probe.zig +++ b/lib/compiler_rt/stack_probe.zig @@ -4,7 +4,6 @@ const common = @import("common.zig"); const os_tag = builtin.os.tag; const arch = builtin.cpu.arch; const abi = builtin.abi; -const is_test = builtin.is_test; pub const panic = common.panic; diff --git a/lib/compiler_rt/suboti4_test.zig b/lib/compiler_rt/suboti4_test.zig index 68ad0ff72fe9e6048becccc92cc8e0b63d8b7211..65018bc966e5bcce2769a89a531470ad24bc0230 100644 --- a/lib/compiler_rt/suboti4_test.zig +++ b/lib/compiler_rt/suboti4_test.zig @@ -1,4 +1,5 @@ const subo = @import("subo.zig"); +const builtin = @import("builtin"); const std = @import("std"); const testing = std.testing; const math = std.math; @@ -27,6 +28,8 @@ pub fn simple_suboti4(a: i128, b: i128, overflow: *c_int) i128 { } test "suboti3" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + const min: i128 = math.minInt(i128); const max: i128 = math.maxInt(i128); var i: i128 = 1; diff --git a/lib/compiler_rt/udivmod.zig b/lib/compiler_rt/udivmod.zig index a9705f317d322c056afbe7e35062f4d00e367391..bf6aaadeae9451edaa7e1ffa0dfc8a74f4aa0877 100644 --- a/lib/compiler_rt/udivmod.zig +++ b/lib/compiler_rt/udivmod.zig @@ -1,8 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const is_test = builtin.is_test; const Log2Int = std.math.Log2Int; -const HalveInt = @import("common.zig").HalveInt; +const common = @import("common.zig"); +const HalveInt = common.HalveInt; const lo = switch (builtin.cpu.arch.endian()) { .big => 1, @@ -14,7 +14,7 @@ const hi = 1 - lo; // Returns U / v_ and sets r = U % v_. fn divwide_generic(comptime T: type, _u1: T, _u0: T, v_: T, r: *T) T { const HalfT = HalveInt(T, false).HalfT; - @setRuntimeSafety(is_test); + @setRuntimeSafety(common.test_safety); var v = v_; const b = @as(T, 1) << (@bitSizeOf(T) / 2); @@ -70,7 +70,7 @@ fn divwide_generic(comptime T: type, _u1: T, _u0: T, v_: T, r: *T) T { } fn divwide(comptime T: type, _u1: T, _u0: T, v: T, r: *T) T { - @setRuntimeSafety(is_test); + @setRuntimeSafety(common.test_safety); if (T == u64 and builtin.target.cpu.arch == .x86_64 and builtin.target.os.tag != .windows) { var rem: T = undefined; const quo = asm ( @@ -90,7 +90,7 @@ fn divwide(comptime T: type, _u1: T, _u0: T, v: T, r: *T) T { // Returns a_ / b_ and sets maybe_rem = a_ % b. pub fn udivmod(comptime T: type, a_: T, b_: T, maybe_rem: ?*T) T { - @setRuntimeSafety(is_test); + @setRuntimeSafety(common.test_safety); const HalfT = HalveInt(T, false).HalfT; const SignedT = std.meta.Int(.signed, @bitSizeOf(T)); diff --git a/lib/compiler_rt/udivmodei4.zig b/lib/compiler_rt/udivmodei4.zig index 6d6f6c1b65e163642eed65a620eacdb527ecf57e..0923f3f222fcdc070334afc3fdad4a610ecb4f6f 100644 --- a/lib/compiler_rt/udivmodei4.zig +++ b/lib/compiler_rt/udivmodei4.zig @@ -113,7 +113,7 @@ pub fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void { } pub fn __udivei4(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) callconv(.c) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size])); const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); @@ -122,7 +122,7 @@ pub fn __udivei4(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) ca } pub fn __umodei4(r_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) callconv(.c) void { - @setRuntimeSafety(builtin.is_test); + @setRuntimeSafety(common.test_safety); const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); const r: []u32 = @ptrCast(@alignCast(r_p[0..byte_size])); const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); @@ -131,6 +131,7 @@ pub fn __umodei4(r_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) ca } test "__udivei4/__umodei4" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 07230735925b9ac34a1c4986db8946f48a9a8986..55672c557c96e846705bfcee954a4a98925085a6 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2239,6 +2239,10 @@ pub const Discarding = struct { pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { if (File.Handle == void) return error.Unimplemented; + switch (builtin.zig_backend) { + else => {}, + .stage2_aarch64 => return error.Unimplemented, + } const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); d.count += w.end; w.end = 0; diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 2634553d254209c6be6fbc52c38452bc0de71060..5ee5828970cf38698097a474821a41e14971329a 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -408,6 +408,9 @@ pub const have_ipc = switch (builtin.os.tag) { const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { .wasi, .freestanding => true, else => false, +} or switch (builtin.zig_backend) { + .stage2_aarch64 => true, + else => false, }; /// Initializes a global Progress instance. @@ -754,7 +757,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } fn clearWrittenWithEscapeCodes() anyerror!void { - if (!global_progress.need_clear) return; + if (noop_impl or !global_progress.need_clear) return; global_progress.need_clear = false; try write(clear); diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 8f4aefc713579fe6ffcd698c6eb254fa4e685202..54376426e262df1e35f05ad44cb2fef03fd4e0b8 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -772,7 +772,7 @@ pub const Endian = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. -pub const Signedness = enum { +pub const Signedness = enum(u1) { signed, unsigned, }; @@ -894,7 +894,10 @@ pub const VaList = switch (builtin.cpu.arch) { .aarch64, .aarch64_be => switch (builtin.os.tag) { .windows => *u8, .ios, .macos, .tvos, .watchos, .visionos => *u8, - else => @compileError("disabled due to miscompilations"), // VaListAarch64, + else => switch (builtin.zig_backend) { + .stage2_aarch64 => VaListAarch64, + else => @compileError("disabled due to miscompilations"), + }, }, .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) { .ios, .macos, .tvos, .watchos, .visionos => *u8, diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 47b3add84f6dc4cc822d3dcd8d654c29862f8750..2583e83d19c1e68d6ffa6153cc3e5625eb7b4d9c 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -2001,7 +2001,7 @@ pub const R_AARCH64 = enum(u32) { TLSLE_LDST64_TPREL_LO12 = 558, /// Likewise; no check. TLSLE_LDST64_TPREL_LO12_NC = 559, - /// PC-rel. load immediate 20:2. + /// PC-rel. load immediate 20:2. TLSDESC_LD_PREL19 = 560, /// PC-rel. ADR immediate 20:0. TLSDESC_ADR_PREL21 = 561, diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 138807972ea17d0aafb6b16c3da2587940b3c832..39111f634d2db4fa1097cb2b7db884f5b5f72bad 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1554,7 +1554,10 @@ pub const Writer = struct { return .{ .vtable = &.{ .drain = drain, - .sendFile = sendFile, + .sendFile = switch (builtin.zig_backend) { + else => sendFile, + .stage2_aarch64 => std.io.Writer.unimplementedSendFile, + }, }, .buffer = buffer, }; diff --git a/lib/std/math.zig b/lib/std/math.zig index 1cd9a83a14034d6657f8043d2da125e3290e0a1a..9f2d12a65e1a6e190a9154c9ae97daa0d6127e54 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254 /// 180.0/pi pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861; +pub const Sign = enum(u1) { positive, negative }; pub const FloatRepr = float.FloatRepr; pub const floatExponentBits = float.floatExponentBits; pub const floatMantissaBits = float.floatMantissaBits; @@ -594,27 +595,30 @@ pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T { /// Shifts left. Overflowed bits are truncated. /// A negative shift amount results in a right shift. pub fn shl(comptime T: type, a: T, shift_amt: anytype) T { + const is_shl = shift_amt >= 0; const abs_shift_amt = @abs(shift_amt); - - const casted_shift_amt = blk: { - if (@typeInfo(T) == .vector) { - const C = @typeInfo(T).vector.child; - const len = @typeInfo(T).vector.len; - if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0); - break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt)))); - } else { - if (abs_shift_amt >= @typeInfo(T).int.bits) return 0; - break :blk @as(Log2Int(T), @intCast(abs_shift_amt)); - } + const casted_shift_amt = casted_shift_amt: switch (@typeInfo(T)) { + .int => |info| { + if (abs_shift_amt < info.bits) break :casted_shift_amt @as( + Log2Int(T), + @intCast(abs_shift_amt), + ); + if (info.signedness == .unsigned or is_shl) return 0; + return a >> (info.bits - 1); + }, + .vector => |info| { + const Child = info.child; + const child_info = @typeInfo(Child).int; + if (abs_shift_amt < child_info.bits) break :casted_shift_amt @as( + @Vector(info.len, Log2Int(Child)), + @splat(@as(Log2Int(Child), @intCast(abs_shift_amt))), + ); + if (child_info.signedness == .unsigned or is_shl) return @splat(0); + return a >> @splat(child_info.bits - 1); + }, + else => comptime unreachable, }; - - if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) { - if (shift_amt < 0) { - return a >> casted_shift_amt; - } - } - - return a << casted_shift_amt; + return if (is_shl) a << casted_shift_amt else a >> casted_shift_amt; } test shl { @@ -629,32 +633,40 @@ test shl { try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) << 1); try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) >> 1); try testing.expect(shl(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0); + + try testing.expect(shl(i8, -1, -100) == -1); + try testing.expect(shl(i8, -1, 100) == 0); + try testing.expect(@reduce(.And, shl(@Vector(2, i8), .{ -1, 1 }, -100) == @Vector(2, i8){ -1, 0 })); + try testing.expect(@reduce(.And, shl(@Vector(2, i8), .{ -1, 1 }, 100) == @Vector(2, i8){ 0, 0 })); } /// Shifts right. Overflowed bits are truncated. /// A negative shift amount results in a left shift. pub fn shr(comptime T: type, a: T, shift_amt: anytype) T { + const is_shl = shift_amt < 0; const abs_shift_amt = @abs(shift_amt); - - const casted_shift_amt = blk: { - if (@typeInfo(T) == .vector) { - const C = @typeInfo(T).vector.child; - const len = @typeInfo(T).vector.len; - if (abs_shift_amt >= @typeInfo(C).int.bits) return @splat(0); - break :blk @as(@Vector(len, Log2Int(C)), @splat(@as(Log2Int(C), @intCast(abs_shift_amt)))); - } else { - if (abs_shift_amt >= @typeInfo(T).int.bits) return 0; - break :blk @as(Log2Int(T), @intCast(abs_shift_amt)); - } + const casted_shift_amt = casted_shift_amt: switch (@typeInfo(T)) { + .int => |info| { + if (abs_shift_amt < info.bits) break :casted_shift_amt @as( + Log2Int(T), + @intCast(abs_shift_amt), + ); + if (info.signedness == .unsigned or is_shl) return 0; + return a >> (info.bits - 1); + }, + .vector => |info| { + const Child = info.child; + const child_info = @typeInfo(Child).int; + if (abs_shift_amt < child_info.bits) break :casted_shift_amt @as( + @Vector(info.len, Log2Int(Child)), + @splat(@as(Log2Int(Child), @intCast(abs_shift_amt))), + ); + if (child_info.signedness == .unsigned or is_shl) return @splat(0); + return a >> @splat(child_info.bits - 1); + }, + else => comptime unreachable, }; - - if (@TypeOf(shift_amt) == comptime_int or @typeInfo(@TypeOf(shift_amt)).int.signedness == .signed) { - if (shift_amt < 0) { - return a << casted_shift_amt; - } - } - - return a >> casted_shift_amt; + return if (is_shl) a << casted_shift_amt else a >> casted_shift_amt; } test shr { @@ -669,6 +681,11 @@ test shr { try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(usize, 1))[0] == @as(u32, 42) >> 1); try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, @as(isize, -1))[0] == @as(u32, 42) << 1); try testing.expect(shr(@Vector(1, u32), @Vector(1, u32){42}, 33)[0] == 0); + + try testing.expect(shr(i8, -1, -100) == 0); + try testing.expect(shr(i8, -1, 100) == -1); + try testing.expect(@reduce(.And, shr(@Vector(2, i8), .{ -1, 1 }, -100) == @Vector(2, i8){ 0, 0 })); + try testing.expect(@reduce(.And, shr(@Vector(2, i8), .{ -1, 1 }, 100) == @Vector(2, i8){ -1, 0 })); } /// Rotates right. Only unsigned values can be rotated. Negative shift diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index f44b254cf196d49f02d267263a74d4212eb106e0..bb6deeb7789f019363280e903bec510087c460f0 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -2774,7 +2774,6 @@ test "bitNotWrap more than two limbs" { // This test requires int sizes greater than 128 bits. if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO // LLVM: unexpected runtime library name: __umodei4 if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.cpu.arch.isWasm()) return error.SkipZigTest; // TODO diff --git a/lib/std/math/float.zig b/lib/std/math/float.zig index df7d7fe1abcf32176ab09d0197e44d122f19c1d5..6ffbd85bd2404ca2787ef5aba4f68cc576df250e 100644 --- a/lib/std/math/float.zig +++ b/lib/std/math/float.zig @@ -4,8 +4,6 @@ const assert = std.debug.assert; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; -pub const Sign = enum(u1) { positive, negative }; - pub fn FloatRepr(comptime Float: type) type { const fractional_bits = floatFractionalBits(Float); const exponent_bits = floatExponentBits(Float); @@ -14,7 +12,7 @@ pub fn FloatRepr(comptime Float: type) type { mantissa: StoredMantissa, exponent: BiasedExponent, - sign: Sign, + sign: std.math.Sign, pub const StoredMantissa = @Type(.{ .int = .{ .signedness = .unsigned, @@ -69,7 +67,7 @@ pub fn FloatRepr(comptime Float: type) type { /// This currently truncates denormal values, which needs to be fixed before this can be used to /// produce a rounded value. - pub fn reconstruct(normalized: Normalized, sign: Sign) Float { + pub fn reconstruct(normalized: Normalized, sign: std.math.Sign) Float { if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{ .mantissa = 0, .exponent = .infinite, diff --git a/lib/std/math/log10.zig b/lib/std/math/log10.zig index 655a42215e249f9a6c16575a2a02c0c73b6a3a76..9ac5c6da24dedc6f24cb041affc1928b3c87ed38 100644 --- a/lib/std/math/log10.zig +++ b/lib/std/math/log10.zig @@ -132,7 +132,6 @@ inline fn less_than_5(x: u32) u32 { test log10_int { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.cpu.arch.isWasm()) return error.SkipZigTest; // TODO diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 1a61076f32caec5d98e008b691ae54b40d6a2cc0..3b72a2b57989d2030cf5cb890208053950c14431 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -676,6 +676,7 @@ test lessThan { const eqlBytes_allowed = switch (builtin.zig_backend) { // These backends don't support vectors yet. + .stage2_aarch64, .stage2_powerpc, .stage2_riscv64, => false, @@ -4482,7 +4483,7 @@ pub fn doNotOptimizeAway(val: anytype) void { ); asm volatile ("" : - : [val2] "r" (val2), + : [_] "r" (val2), ); } else doNotOptimizeAway(&val); }, @@ -4490,7 +4491,7 @@ pub fn doNotOptimizeAway(val: anytype) void { if ((t.float.bits == 32 or t.float.bits == 64) and builtin.zig_backend != .stage2_c) { asm volatile ("" : - : [val] "rm" (val), + : [_] "rm" (val), ); } else doNotOptimizeAway(&val); }, @@ -4500,7 +4501,7 @@ pub fn doNotOptimizeAway(val: anytype) void { } else { asm volatile ("" : - : [val] "m" (val), + : [_] "m" (val), : .{ .memory = true }); } }, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 75494145b9bc95ae764ab8e4c8360fcf57034d7a..a02451c0fd138dbecf3810e1e2edae4710595e1b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -503,7 +503,6 @@ pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; /// Whether an external or internal getauxval implementation is used. const extern_getauxval = switch (builtin.zig_backend) { // Calling extern functions is not yet supported with these backends - .stage2_aarch64, .stage2_arm, .stage2_powerpc, .stage2_riscv64, diff --git a/lib/std/start.zig b/lib/std/start.zig index 22ccda1e40b79ae835cfb8f5f5cc04cc31adfae2..43355d34f4f400b8c577d7682386f153cef36d8f 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -101,17 +101,11 @@ comptime { // Simplified start code for stage2 until it supports more language features /// fn main2() callconv(.c) c_int { - root.main(); - return 0; + return callMain(); } fn _start2() callconv(.withStackAlign(.c, 1)) noreturn { - callMain2(); -} - -fn callMain2() noreturn { - root.main(); - exit2(0); + std.posix.exit(callMain()); } fn spirvMain2() callconv(.kernel) void { @@ -119,51 +113,7 @@ fn spirvMain2() callconv(.kernel) void { } fn wWinMainCRTStartup2() callconv(.c) noreturn { - root.main(); - exit2(0); -} - -fn exit2(code: usize) noreturn { - switch (native_os) { - .linux => switch (builtin.cpu.arch) { - .x86_64 => { - asm volatile ("syscall" - : - : [number] "{rax}" (231), - [arg1] "{rdi}" (code), - : .{ .rcx = true, .r11 = true, .memory = true }); - }, - .arm => { - asm volatile ("svc #0" - : - : [number] "{r7}" (1), - [arg1] "{r0}" (code), - : .{ .memory = true }); - }, - .aarch64 => { - asm volatile ("svc #0" - : - : [number] "{x8}" (93), - [arg1] "{x0}" (code), - : .{ .memory = true }); - }, - .sparc64 => { - asm volatile ("ta 0x6d" - : - : [number] "{g1}" (1), - [arg1] "{o0}" (code), - : .{ .o0 = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o6 = true, .o7 = true, .memory = true }); - }, - else => @compileError("TODO"), - }, - // exits(0) - .plan9 => std.os.plan9.exits(null), - .windows => { - std.os.windows.ntdll.RtlExitUserProcess(@truncate(code)); - }, - else => @compileError("TODO"), - } - unreachable; + std.posix.exit(callMain()); } //////////////////////////////////////////////////////////////////////////////// @@ -676,10 +626,11 @@ pub inline fn callMain() u8 { const result = root.main() catch |err| { switch (builtin.zig_backend) { + .stage2_aarch64, .stage2_powerpc, .stage2_riscv64, => { - std.debug.print("error: failed with error\n", .{}); + _ = std.posix.write(std.posix.STDERR_FILENO, "error: failed with error\n") catch {}; return 1; }, else => {}, diff --git a/lib/std/testing.zig b/lib/std/testing.zig index f9027a4f47a982d29e16e9b1692320a0405ff748..e80e961b13e4a27eb377f44c122f24542ed3623b 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -33,6 +33,7 @@ pub var log_level = std.log.Level.warn; // Disable printing in tests for simple backends. pub const backend_can_print = switch (builtin.zig_backend) { + .stage2_aarch64, .stage2_powerpc, .stage2_riscv64, .stage2_spirv, diff --git a/src/Compilation.zig b/src/Compilation.zig index 649288dab25bfd47a8bb4f285e4caf604335bf36..412d6a023cd5b35a27e9732ef4fff08eb1abbac6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1850,7 +1850,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // approach, since the ubsan runtime uses quite a lot of the standard library // and this reduces unnecessary bloat. const ubsan_rt_strat: RtStrat = s: { - const can_build_ubsan_rt = target_util.canBuildLibUbsanRt(target); + const can_build_ubsan_rt = target_util.canBuildLibUbsanRt(target, use_llvm, build_options.have_llvm); const want_ubsan_rt = options.want_ubsan_rt orelse (can_build_ubsan_rt and any_sanitize_c == .full and is_exe_or_dyn_lib); if (!want_ubsan_rt) break :s .none; if (options.skip_linker_dependencies) break :s .none; diff --git a/src/InternPool.zig b/src/InternPool.zig index c9036da45b4b16969dc33e4b7df60cea3088cd2d..15d895aed07330e9061ac662ffabf8b1b9536725 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -7556,12 +7556,18 @@ fn extraFuncCoerced(ip: *const InternPool, extra: Local.Extra, extra_index: u32) fn indexToKeyBigInt(ip: *const InternPool, tid: Zcu.PerThread.Id, limb_index: u32, positive: bool) Key { const limbs_items = ip.getLocalShared(tid).getLimbs().view().items(.@"0"); const int: Int = @bitCast(limbs_items[limb_index..][0..Int.limbs_items_len].*); + const big_int: BigIntConst = .{ + .limbs = limbs_items[limb_index + Int.limbs_items_len ..][0..int.limbs_len], + .positive = positive, + }; return .{ .int = .{ .ty = int.ty, - .storage = .{ .big_int = .{ - .limbs = limbs_items[limb_index + Int.limbs_items_len ..][0..int.limbs_len], - .positive = positive, - } }, + .storage = if (big_int.toInt(u64)) |x| + .{ .u64 = x } + else |_| if (big_int.toInt(i64)) |x| + .{ .i64 = x } + else |_| + .{ .big_int = big_int }, } }; } diff --git a/src/Sema.zig b/src/Sema.zig index d2e3e322149eaa5a850b67c83be28cdd9ab8e021..788107f786ed5021e51c425af8358099d9bfc0dc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16522,7 +16522,7 @@ fn zirAsm( break :empty try sema.structInitEmpty(block, clobbers_ty, src, src); } else try sema.resolveInst(extra.data.clobbers); // Already coerced by AstGen. const clobbers_val = try sema.resolveConstDefinedValue(block, src, clobbers, .{ .simple = .clobber }); - needed_capacity += (asm_source.len + 3) / 4; + needed_capacity += asm_source.len / 4 + 1; const gpa = sema.gpa; try sema.air_extra.ensureUnusedCapacity(gpa, needed_capacity); @@ -16562,7 +16562,8 @@ fn zirAsm( { const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); @memcpy(buffer[0..asm_source.len], asm_source); - sema.air_extra.items.len += (asm_source.len + 3) / 4; + buffer[asm_source.len] = 0; + sema.air_extra.items.len += asm_source.len / 4 + 1; } return asm_air; } @@ -24846,7 +24847,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins }, .@"packed" => { const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) + - (if (zcu.typeToStruct(parent_ty)) |struct_obj| pt.structPackedFieldBitOffset(struct_obj, field_index) else 0) - + (if (zcu.typeToStruct(parent_ty)) |struct_obj| zcu.structPackedFieldBitOffset(struct_obj, field_index) else 0) - actual_field_ptr_info.packed_offset.bit_offset), 8) catch return sema.fail(block, inst_src, "pointer bit-offset mismatch", .{}); actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(if (byte_offset > 0) @@ -24873,7 +24874,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins // Logic lifted from type computation above - I'm just assuming it's correct. // `catch unreachable` since error case handled above. const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) + - pt.structPackedFieldBitOffset(zcu.typeToStruct(parent_ty).?, field_index) - + zcu.structPackedFieldBitOffset(zcu.typeToStruct(parent_ty).?, field_index) - actual_field_ptr_info.packed_offset.bit_offset), 8) catch unreachable; const parent_ptr_val = try sema.ptrSubtract(block, field_ptr_src, field_ptr_val, byte_offset, actual_parent_ptr_ty); break :result Air.internedToRef(parent_ptr_val.toIntern()); diff --git a/src/Type.zig b/src/Type.zig index a199811c8e529de209deb4afa8723fa9d5c01105..9316bec11e12092aab5e2cd2b0760fba830deb2e 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -4166,7 +4166,7 @@ pub const generic_poison: Type = .{ .ip_index = .generic_poison_type }; pub fn smallestUnsignedBits(max: u64) u16 { return switch (max) { 0 => 0, - else => 1 + std.math.log2_int(u64, max), + else => @as(u16, 1) + std.math.log2_int(u64, max), }; } diff --git a/src/Zcu.zig b/src/Zcu.zig index d337f0b943f7c36666b7dcc63ea81404c390693d..df35777231e5c08c0043e6d9f4962edc3f2d8aad 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3891,6 +3891,29 @@ pub fn typeToPackedStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructTyp return s; } +/// https://github.com/ziglang/zig/issues/17178 explored storing these bit offsets +/// into the packed struct InternPool data rather than computing this on the +/// fly, however it was found to perform worse when measured on real world +/// projects. +pub fn structPackedFieldBitOffset( + zcu: *Zcu, + struct_type: InternPool.LoadedStructType, + field_index: u32, +) u16 { + const ip = &zcu.intern_pool; + assert(struct_type.layout == .@"packed"); + assert(struct_type.haveLayout(ip)); + var bit_sum: u64 = 0; + for (0..struct_type.field_types.len) |i| { + if (i == field_index) { + return @intCast(bit_sum); + } + const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]); + bit_sum += field_ty.bitSize(zcu); + } + unreachable; // index out of bounds +} + pub fn typeToUnion(zcu: *const Zcu, ty: Type) ?InternPool.LoadedUnionType { if (ty.ip_index == .none) return null; const ip = &zcu.intern_pool; @@ -4436,11 +4459,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu else => false, }, .stage2_aarch64 => switch (cc) { - .aarch64_aapcs, - .aarch64_aapcs_darwin, - .aarch64_aapcs_win, - => |opts| opts.incoming_stack_alignment == null, - .naked => true, + .aarch64_aapcs, .aarch64_aapcs_darwin, .naked => true, else => false, }, .stage2_x86 => switch (cc) { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 26f008e1c8880a497bfe5e0b5984c15f2eff690b..119b742a89ea295cf72c763d05fc37877357cf2b 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3737,30 +3737,6 @@ pub fn intBitsForValue(pt: Zcu.PerThread, val: Value, sign: bool) u16 { } } -/// https://github.com/ziglang/zig/issues/17178 explored storing these bit offsets -/// into the packed struct InternPool data rather than computing this on the -/// fly, however it was found to perform worse when measured on real world -/// projects. -pub fn structPackedFieldBitOffset( - pt: Zcu.PerThread, - struct_type: InternPool.LoadedStructType, - field_index: u32, -) u16 { - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - assert(struct_type.layout == .@"packed"); - assert(struct_type.haveLayout(ip)); - var bit_sum: u64 = 0; - for (0..struct_type.field_types.len) |i| { - if (i == field_index) { - return @intCast(bit_sum); - } - const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]); - bit_sum += field_ty.bitSize(zcu); - } - unreachable; // index out of bounds -} - pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Error!Type { const zcu = pt.zcu; const ip = &zcu.intern_pool; @@ -4381,8 +4357,11 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e try air.legalize(pt, features); } - var liveness: Air.Liveness = try .analyze(zcu, air.*, ip); - defer liveness.deinit(gpa); + var liveness: ?Air.Liveness = if (codegen.wantsLiveness(pt, nav)) + try .analyze(zcu, air.*, ip) + else + null; + defer if (liveness) |*l| l.deinit(gpa); if (build_options.enable_debug_extensions and comp.verbose_air) { const stderr = std.debug.lockStderrWriter(&.{}); @@ -4392,12 +4371,12 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {}; } - if (std.debug.runtime_safety) { + if (std.debug.runtime_safety) verify_liveness: { var verify: Air.Liveness.Verify = .{ .gpa = gpa, .zcu = zcu, .air = air.*, - .liveness = liveness, + .liveness = liveness orelse break :verify_liveness, .intern_pool = ip, }; defer verify.deinit(); diff --git a/src/arch/aarch64/bits.zig b/src/arch/aarch64/bits.zig deleted file mode 100644 index 9c4227a712cdb34aac73b8b3be49ebf96c73b761..0000000000000000000000000000000000000000 --- a/src/arch/aarch64/bits.zig +++ /dev/null @@ -1,2063 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const testing = std.testing; - -/// Disjoint sets of registers. Every register must belong to -/// exactly one register class. -pub const RegisterClass = enum { - general_purpose, - stack_pointer, - floating_point, -}; - -/// Registers in the AArch64 instruction set -pub const Register = enum(u8) { - // zig fmt: off - // 64-bit general-purpose registers - x0, x1, x2, x3, x4, x5, x6, x7, - x8, x9, x10, x11, x12, x13, x14, x15, - x16, x17, x18, x19, x20, x21, x22, x23, - x24, x25, x26, x27, x28, x29, x30, xzr, - - // 32-bit general-purpose registers - w0, w1, w2, w3, w4, w5, w6, w7, - w8, w9, w10, w11, w12, w13, w14, w15, - w16, w17, w18, w19, w20, w21, w22, w23, - w24, w25, w26, w27, w28, w29, w30, wzr, - - // Stack pointer - sp, wsp, - - // 128-bit floating-point registers - q0, q1, q2, q3, q4, q5, q6, q7, - q8, q9, q10, q11, q12, q13, q14, q15, - q16, q17, q18, q19, q20, q21, q22, q23, - q24, q25, q26, q27, q28, q29, q30, q31, - - // 64-bit floating-point registers - d0, d1, d2, d3, d4, d5, d6, d7, - d8, d9, d10, d11, d12, d13, d14, d15, - d16, d17, d18, d19, d20, d21, d22, d23, - d24, d25, d26, d27, d28, d29, d30, d31, - - // 32-bit floating-point registers - s0, s1, s2, s3, s4, s5, s6, s7, - s8, s9, s10, s11, s12, s13, s14, s15, - s16, s17, s18, s19, s20, s21, s22, s23, - s24, s25, s26, s27, s28, s29, s30, s31, - - // 16-bit floating-point registers - h0, h1, h2, h3, h4, h5, h6, h7, - h8, h9, h10, h11, h12, h13, h14, h15, - h16, h17, h18, h19, h20, h21, h22, h23, - h24, h25, h26, h27, h28, h29, h30, h31, - - // 8-bit floating-point registers - b0, b1, b2, b3, b4, b5, b6, b7, - b8, b9, b10, b11, b12, b13, b14, b15, - b16, b17, b18, b19, b20, b21, b22, b23, - b24, b25, b26, b27, b28, b29, b30, b31, - // zig fmt: on - - pub fn class(self: Register) RegisterClass { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => .general_purpose, - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => .general_purpose, - - @intFromEnum(Register.sp) => .stack_pointer, - @intFromEnum(Register.wsp) => .stack_pointer, - - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => .floating_point, - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => .floating_point, - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => .floating_point, - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => .floating_point, - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => .floating_point, - else => unreachable, - }; - } - - pub fn id(self: Register) u6 { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.x0))), - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.w0))), - - @intFromEnum(Register.sp) => 32, - @intFromEnum(Register.wsp) => 32, - - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.q0) + 33)), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.d0) + 33)), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.s0) + 33)), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.h0) + 33)), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as(u6, @intCast(@intFromEnum(self) - @intFromEnum(Register.b0) + 33)), - else => unreachable, - }; - } - - pub fn enc(self: Register) u5 { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.x0))), - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.w0))), - - @intFromEnum(Register.sp) => 31, - @intFromEnum(Register.wsp) => 31, - - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.q0))), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.d0))), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.s0))), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.h0))), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as(u5, @intCast(@intFromEnum(self) - @intFromEnum(Register.b0))), - else => unreachable, - }; - } - - /// Returns the bit-width of the register. - pub fn size(self: Register) u8 { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => 64, - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => 32, - - @intFromEnum(Register.sp) => 64, - @intFromEnum(Register.wsp) => 32, - - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => 128, - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => 64, - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => 32, - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => 16, - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => 8, - else => unreachable, - }; - } - - /// Convert from a general-purpose register to its 64 bit alias. - pub fn toX(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.x0) + @intFromEnum(Register.x0)), - ), - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.w0) + @intFromEnum(Register.x0)), - ), - else => unreachable, - }; - } - - /// Convert from a general-purpose register to its 32 bit alias. - pub fn toW(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.x0)...@intFromEnum(Register.xzr) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.x0) + @intFromEnum(Register.w0)), - ), - @intFromEnum(Register.w0)...@intFromEnum(Register.wzr) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.w0) + @intFromEnum(Register.w0)), - ), - else => unreachable, - }; - } - - /// Convert from a floating-point register to its 128 bit alias. - pub fn toQ(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.q0) + @intFromEnum(Register.q0)), - ), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.d0) + @intFromEnum(Register.q0)), - ), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.s0) + @intFromEnum(Register.q0)), - ), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.h0) + @intFromEnum(Register.q0)), - ), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.b0) + @intFromEnum(Register.q0)), - ), - else => unreachable, - }; - } - - /// Convert from a floating-point register to its 64 bit alias. - pub fn toD(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.q0) + @intFromEnum(Register.d0)), - ), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.d0) + @intFromEnum(Register.d0)), - ), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.s0) + @intFromEnum(Register.d0)), - ), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.h0) + @intFromEnum(Register.d0)), - ), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.b0) + @intFromEnum(Register.d0)), - ), - else => unreachable, - }; - } - - /// Convert from a floating-point register to its 32 bit alias. - pub fn toS(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.q0) + @intFromEnum(Register.s0)), - ), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.d0) + @intFromEnum(Register.s0)), - ), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.s0) + @intFromEnum(Register.s0)), - ), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.h0) + @intFromEnum(Register.s0)), - ), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.b0) + @intFromEnum(Register.s0)), - ), - else => unreachable, - }; - } - - /// Convert from a floating-point register to its 16 bit alias. - pub fn toH(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.q0) + @intFromEnum(Register.h0)), - ), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.d0) + @intFromEnum(Register.h0)), - ), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.s0) + @intFromEnum(Register.h0)), - ), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.h0) + @intFromEnum(Register.h0)), - ), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.b0) + @intFromEnum(Register.h0)), - ), - else => unreachable, - }; - } - - /// Convert from a floating-point register to its 8 bit alias. - pub fn toB(self: Register) Register { - return switch (@intFromEnum(self)) { - @intFromEnum(Register.q0)...@intFromEnum(Register.q31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.q0) + @intFromEnum(Register.b0)), - ), - @intFromEnum(Register.d0)...@intFromEnum(Register.d31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.d0) + @intFromEnum(Register.b0)), - ), - @intFromEnum(Register.s0)...@intFromEnum(Register.s31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.s0) + @intFromEnum(Register.b0)), - ), - @intFromEnum(Register.h0)...@intFromEnum(Register.h31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.h0) + @intFromEnum(Register.b0)), - ), - @intFromEnum(Register.b0)...@intFromEnum(Register.b31) => @as( - Register, - @enumFromInt(@intFromEnum(self) - @intFromEnum(Register.b0) + @intFromEnum(Register.b0)), - ), - else => unreachable, - }; - } - - pub fn dwarfNum(self: Register) u5 { - return self.enc(); - } -}; - -test "Register.enc" { - try testing.expectEqual(@as(u5, 0), Register.x0.enc()); - try testing.expectEqual(@as(u5, 0), Register.w0.enc()); - - try testing.expectEqual(@as(u5, 31), Register.xzr.enc()); - try testing.expectEqual(@as(u5, 31), Register.wzr.enc()); - - try testing.expectEqual(@as(u5, 31), Register.sp.enc()); - try testing.expectEqual(@as(u5, 31), Register.sp.enc()); -} - -test "Register.size" { - try testing.expectEqual(@as(u8, 64), Register.x19.size()); - try testing.expectEqual(@as(u8, 32), Register.w3.size()); -} - -test "Register.toX/toW" { - try testing.expectEqual(Register.x0, Register.w0.toX()); - try testing.expectEqual(Register.x0, Register.x0.toX()); - - try testing.expectEqual(Register.w3, Register.w3.toW()); - try testing.expectEqual(Register.w3, Register.x3.toW()); -} - -/// Represents an instruction in the AArch64 instruction set -pub const Instruction = union(enum) { - move_wide_immediate: packed struct { - rd: u5, - imm16: u16, - hw: u2, - fixed: u6 = 0b100101, - opc: u2, - sf: u1, - }, - pc_relative_address: packed struct { - rd: u5, - immhi: u19, - fixed: u5 = 0b10000, - immlo: u2, - op: u1, - }, - load_store_register: packed struct { - rt: u5, - rn: u5, - offset: u12, - opc: u2, - op1: u2, - v: u1, - fixed: u3 = 0b111, - size: u2, - }, - load_store_register_pair: packed struct { - rt1: u5, - rn: u5, - rt2: u5, - imm7: u7, - load: u1, - encoding: u2, - fixed: u5 = 0b101_0_0, - opc: u2, - }, - load_literal: packed struct { - rt: u5, - imm19: u19, - fixed: u6 = 0b011_0_00, - opc: u2, - }, - exception_generation: packed struct { - ll: u2, - op2: u3, - imm16: u16, - opc: u3, - fixed: u8 = 0b1101_0100, - }, - unconditional_branch_register: packed struct { - op4: u5, - rn: u5, - op3: u6, - op2: u5, - opc: u4, - fixed: u7 = 0b1101_011, - }, - unconditional_branch_immediate: packed struct { - imm26: u26, - fixed: u5 = 0b00101, - op: u1, - }, - no_operation: packed struct { - fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, - }, - logical_shifted_register: packed struct { - rd: u5, - rn: u5, - imm6: u6, - rm: u5, - n: u1, - shift: u2, - fixed: u5 = 0b01010, - opc: u2, - sf: u1, - }, - add_subtract_immediate: packed struct { - rd: u5, - rn: u5, - imm12: u12, - sh: u1, - fixed: u6 = 0b100010, - s: u1, - op: u1, - sf: u1, - }, - logical_immediate: packed struct { - rd: u5, - rn: u5, - imms: u6, - immr: u6, - n: u1, - fixed: u6 = 0b100100, - opc: u2, - sf: u1, - }, - bitfield: packed struct { - rd: u5, - rn: u5, - imms: u6, - immr: u6, - n: u1, - fixed: u6 = 0b100110, - opc: u2, - sf: u1, - }, - add_subtract_shifted_register: packed struct { - rd: u5, - rn: u5, - imm6: u6, - rm: u5, - fixed_1: u1 = 0b0, - shift: u2, - fixed_2: u5 = 0b01011, - s: u1, - op: u1, - sf: u1, - }, - add_subtract_extended_register: packed struct { - rd: u5, - rn: u5, - imm3: u3, - option: u3, - rm: u5, - fixed: u8 = 0b01011_00_1, - s: u1, - op: u1, - sf: u1, - }, - conditional_branch: struct { - cond: u4, - o0: u1, - imm19: u19, - o1: u1, - fixed: u7 = 0b0101010, - }, - compare_and_branch: struct { - rt: u5, - imm19: u19, - op: u1, - fixed: u6 = 0b011010, - sf: u1, - }, - conditional_select: struct { - rd: u5, - rn: u5, - op2: u2, - cond: u4, - rm: u5, - fixed: u8 = 0b11010100, - s: u1, - op: u1, - sf: u1, - }, - data_processing_3_source: packed struct { - rd: u5, - rn: u5, - ra: u5, - o0: u1, - rm: u5, - op31: u3, - fixed: u5 = 0b11011, - op54: u2, - sf: u1, - }, - data_processing_2_source: packed struct { - rd: u5, - rn: u5, - opcode: u6, - rm: u5, - fixed_1: u8 = 0b11010110, - s: u1, - fixed_2: u1 = 0b0, - sf: u1, - }, - - pub const Condition = enum(u4) { - /// Integer: Equal - /// Floating point: Equal - eq, - /// Integer: Not equal - /// Floating point: Not equal or unordered - ne, - /// Integer: Carry set - /// Floating point: Greater than, equal, or unordered - cs, - /// Integer: Carry clear - /// Floating point: Less than - cc, - /// Integer: Minus, negative - /// Floating point: Less than - mi, - /// Integer: Plus, positive or zero - /// Floating point: Greater than, equal, or unordered - pl, - /// Integer: Overflow - /// Floating point: Unordered - vs, - /// Integer: No overflow - /// Floating point: Ordered - vc, - /// Integer: Unsigned higher - /// Floating point: Greater than, or unordered - hi, - /// Integer: Unsigned lower or same - /// Floating point: Less than or equal - ls, - /// Integer: Signed greater than or equal - /// Floating point: Greater than or equal - ge, - /// Integer: Signed less than - /// Floating point: Less than, or unordered - lt, - /// Integer: Signed greater than - /// Floating point: Greater than - gt, - /// Integer: Signed less than or equal - /// Floating point: Less than, equal, or unordered - le, - /// Integer: Always - /// Floating point: Always - al, - /// Integer: Always - /// Floating point: Always - nv, - - /// Converts a std.math.CompareOperator into a condition flag, - /// i.e. returns the condition that is true iff the result of the - /// comparison is true. Assumes signed comparison - pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition { - return switch (op) { - .gte => .ge, - .gt => .gt, - .neq => .ne, - .lt => .lt, - .lte => .le, - .eq => .eq, - }; - } - - /// Converts a std.math.CompareOperator into a condition flag, - /// i.e. returns the condition that is true iff the result of the - /// comparison is true. Assumes unsigned comparison - pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition { - return switch (op) { - .gte => .cs, - .gt => .hi, - .neq => .ne, - .lt => .cc, - .lte => .ls, - .eq => .eq, - }; - } - - /// Returns the condition which is true iff the given condition is - /// false (if such a condition exists) - pub fn negate(cond: Condition) Condition { - return switch (cond) { - .eq => .ne, - .ne => .eq, - .cs => .cc, - .cc => .cs, - .mi => .pl, - .pl => .mi, - .vs => .vc, - .vc => .vs, - .hi => .ls, - .ls => .hi, - .ge => .lt, - .lt => .ge, - .gt => .le, - .le => .gt, - .al => unreachable, - .nv => unreachable, - }; - } - }; - - pub fn toU32(self: Instruction) u32 { - return switch (self) { - .move_wide_immediate => |v| @as(u32, @bitCast(v)), - .pc_relative_address => |v| @as(u32, @bitCast(v)), - .load_store_register => |v| @as(u32, @bitCast(v)), - .load_store_register_pair => |v| @as(u32, @bitCast(v)), - .load_literal => |v| @as(u32, @bitCast(v)), - .exception_generation => |v| @as(u32, @bitCast(v)), - .unconditional_branch_register => |v| @as(u32, @bitCast(v)), - .unconditional_branch_immediate => |v| @as(u32, @bitCast(v)), - .no_operation => |v| @as(u32, @bitCast(v)), - .logical_shifted_register => |v| @as(u32, @bitCast(v)), - .add_subtract_immediate => |v| @as(u32, @bitCast(v)), - .logical_immediate => |v| @as(u32, @bitCast(v)), - .bitfield => |v| @as(u32, @bitCast(v)), - .add_subtract_shifted_register => |v| @as(u32, @bitCast(v)), - .add_subtract_extended_register => |v| @as(u32, @bitCast(v)), - // TODO once packed structs work, this can be refactored - .conditional_branch => |v| @as(u32, v.cond) | (@as(u32, v.o0) << 4) | (@as(u32, v.imm19) << 5) | (@as(u32, v.o1) << 24) | (@as(u32, v.fixed) << 25), - .compare_and_branch => |v| @as(u32, v.rt) | (@as(u32, v.imm19) << 5) | (@as(u32, v.op) << 24) | (@as(u32, v.fixed) << 25) | (@as(u32, v.sf) << 31), - .conditional_select => |v| @as(u32, v.rd) | @as(u32, v.rn) << 5 | @as(u32, v.op2) << 10 | @as(u32, v.cond) << 12 | @as(u32, v.rm) << 16 | @as(u32, v.fixed) << 21 | @as(u32, v.s) << 29 | @as(u32, v.op) << 30 | @as(u32, v.sf) << 31, - .data_processing_3_source => |v| @as(u32, @bitCast(v)), - .data_processing_2_source => |v| @as(u32, @bitCast(v)), - }; - } - - fn moveWideImmediate( - opc: u2, - rd: Register, - imm16: u16, - shift: u6, - ) Instruction { - assert(shift % 16 == 0); - assert(!(rd.size() == 32 and shift > 16)); - assert(!(rd.size() == 64 and shift > 48)); - - return Instruction{ - .move_wide_immediate = .{ - .rd = rd.enc(), - .imm16 = imm16, - .hw = @as(u2, @intCast(shift / 16)), - .opc = opc, - .sf = switch (rd.size()) { - 32 => 0, - 64 => 1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn pcRelativeAddress(rd: Register, imm21: i21, op: u1) Instruction { - assert(rd.size() == 64); - const imm21_u = @as(u21, @bitCast(imm21)); - return Instruction{ - .pc_relative_address = .{ - .rd = rd.enc(), - .immlo = @as(u2, @truncate(imm21_u)), - .immhi = @as(u19, @truncate(imm21_u >> 2)), - .op = op, - }, - }; - } - - pub const LoadStoreOffsetImmediate = union(enum) { - post_index: i9, - pre_index: i9, - unsigned: u12, - }; - - pub const LoadStoreOffsetRegister = struct { - rm: u5, - shift: union(enum) { - uxtw: u2, - lsl: u2, - sxtw: u2, - sxtx: u2, - }, - }; - - /// Represents the offset operand of a load or store instruction. - /// Data can be loaded from memory with either an immediate offset - /// or an offset that is stored in some register. - pub const LoadStoreOffset = union(enum) { - immediate: LoadStoreOffsetImmediate, - register: LoadStoreOffsetRegister, - - pub const none = LoadStoreOffset{ - .immediate = .{ .unsigned = 0 }, - }; - - pub fn toU12(self: LoadStoreOffset) u12 { - return switch (self) { - .immediate => |imm_type| switch (imm_type) { - .post_index => |v| (@as(u12, @intCast(@as(u9, @bitCast(v)))) << 2) + 1, - .pre_index => |v| (@as(u12, @intCast(@as(u9, @bitCast(v)))) << 2) + 3, - .unsigned => |v| v, - }, - .register => |r| switch (r.shift) { - .uxtw => |v| (@as(u12, @intCast(r.rm)) << 6) + (@as(u12, @intCast(v)) << 2) + 16 + 2050, - .lsl => |v| (@as(u12, @intCast(r.rm)) << 6) + (@as(u12, @intCast(v)) << 2) + 24 + 2050, - .sxtw => |v| (@as(u12, @intCast(r.rm)) << 6) + (@as(u12, @intCast(v)) << 2) + 48 + 2050, - .sxtx => |v| (@as(u12, @intCast(r.rm)) << 6) + (@as(u12, @intCast(v)) << 2) + 56 + 2050, - }, - }; - } - - pub fn imm(offset: u12) LoadStoreOffset { - return .{ - .immediate = .{ .unsigned = offset }, - }; - } - - pub fn imm_post_index(offset: i9) LoadStoreOffset { - return .{ - .immediate = .{ .post_index = offset }, - }; - } - - pub fn imm_pre_index(offset: i9) LoadStoreOffset { - return .{ - .immediate = .{ .pre_index = offset }, - }; - } - - pub fn reg(rm: Register) LoadStoreOffset { - return .{ - .register = .{ - .rm = rm.enc(), - .shift = .{ - .lsl = 0, - }, - }, - }; - } - - pub fn reg_uxtw(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 32 and (shift == 0 or shift == 2)); - return .{ - .register = .{ - .rm = rm.enc(), - .shift = .{ - .uxtw = shift, - }, - }, - }; - } - - pub fn reg_lsl(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 64 and (shift == 0 or shift == 3)); - return .{ - .register = .{ - .rm = rm.enc(), - .shift = .{ - .lsl = shift, - }, - }, - }; - } - - pub fn reg_sxtw(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 32 and (shift == 0 or shift == 2)); - return .{ - .register = .{ - .rm = rm.enc(), - .shift = .{ - .sxtw = shift, - }, - }, - }; - } - - pub fn reg_sxtx(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 64 and (shift == 0 or shift == 3)); - return .{ - .register = .{ - .rm = rm.enc(), - .shift = .{ - .sxtx = shift, - }, - }, - }; - } - }; - - /// Which kind of load/store to perform - const LoadStoreVariant = enum { - /// 32 bits or 64 bits - str, - /// 8 bits, zero-extended - strb, - /// 16 bits, zero-extended - strh, - /// 32 bits or 64 bits - ldr, - /// 8 bits, zero-extended - ldrb, - /// 16 bits, zero-extended - ldrh, - /// 8 bits, sign extended - ldrsb, - /// 16 bits, sign extended - ldrsh, - /// 32 bits, sign extended - ldrsw, - }; - - fn loadStoreRegister( - rt: Register, - rn: Register, - offset: LoadStoreOffset, - variant: LoadStoreVariant, - ) Instruction { - assert(rn.size() == 64); - assert(rn.id() != Register.xzr.id()); - - const off = offset.toU12(); - - const op1: u2 = blk: { - switch (offset) { - .immediate => |imm| switch (imm) { - .unsigned => break :blk 0b01, - else => {}, - }, - else => {}, - } - break :blk 0b00; - }; - - const opc: u2 = blk: { - switch (variant) { - .ldr, .ldrh, .ldrb => break :blk 0b01, - .str, .strh, .strb => break :blk 0b00, - .ldrsb, - .ldrsh, - => switch (rt.size()) { - 32 => break :blk 0b11, - 64 => break :blk 0b10, - else => unreachable, // unexpected register size - }, - .ldrsw => break :blk 0b10, - } - }; - - const size: u2 = blk: { - switch (variant) { - .ldr, .str => switch (rt.size()) { - 32 => break :blk 0b10, - 64 => break :blk 0b11, - else => unreachable, // unexpected register size - }, - .ldrsw => break :blk 0b10, - .ldrh, .ldrsh, .strh => break :blk 0b01, - .ldrb, .ldrsb, .strb => break :blk 0b00, - } - }; - - return Instruction{ - .load_store_register = .{ - .rt = rt.enc(), - .rn = rn.enc(), - .offset = off, - .opc = opc, - .op1 = op1, - .v = 0, - .size = size, - }, - }; - } - - fn loadStoreRegisterPair( - rt1: Register, - rt2: Register, - rn: Register, - offset: i9, - encoding: u2, - load: bool, - ) Instruction { - assert(rn.size() == 64); - assert(rn.id() != Register.xzr.id()); - - switch (rt1.size()) { - 32 => { - assert(-256 <= offset and offset <= 252); - const imm7 = @as(u7, @truncate(@as(u9, @bitCast(offset >> 2)))); - return Instruction{ - .load_store_register_pair = .{ - .rt1 = rt1.enc(), - .rn = rn.enc(), - .rt2 = rt2.enc(), - .imm7 = imm7, - .load = @intFromBool(load), - .encoding = encoding, - .opc = 0b00, - }, - }; - }, - 64 => { - assert(-512 <= offset and offset <= 504); - const imm7 = @as(u7, @truncate(@as(u9, @bitCast(offset >> 3)))); - return Instruction{ - .load_store_register_pair = .{ - .rt1 = rt1.enc(), - .rn = rn.enc(), - .rt2 = rt2.enc(), - .imm7 = imm7, - .load = @intFromBool(load), - .encoding = encoding, - .opc = 0b10, - }, - }; - }, - else => unreachable, // unexpected register size - } - } - - fn loadLiteral(rt: Register, imm19: u19) Instruction { - return Instruction{ - .load_literal = .{ - .rt = rt.enc(), - .imm19 = imm19, - .opc = switch (rt.size()) { - 32 => 0b00, - 64 => 0b01, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn exceptionGeneration( - opc: u3, - op2: u3, - ll: u2, - imm16: u16, - ) Instruction { - return Instruction{ - .exception_generation = .{ - .ll = ll, - .op2 = op2, - .imm16 = imm16, - .opc = opc, - }, - }; - } - - fn unconditionalBranchRegister( - opc: u4, - op2: u5, - op3: u6, - rn: Register, - op4: u5, - ) Instruction { - assert(rn.size() == 64); - - return Instruction{ - .unconditional_branch_register = .{ - .op4 = op4, - .rn = rn.enc(), - .op3 = op3, - .op2 = op2, - .opc = opc, - }, - }; - } - - fn unconditionalBranchImmediate( - op: u1, - offset: i28, - ) Instruction { - return Instruction{ - .unconditional_branch_immediate = .{ - .imm26 = @as(u26, @bitCast(@as(i26, @intCast(offset >> 2)))), - .op = op, - }, - }; - } - - pub const LogicalShiftedRegisterShift = enum(u2) { lsl, lsr, asr, ror }; - - fn logicalShiftedRegister( - opc: u2, - n: u1, - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - assert(rd.size() == rn.size()); - assert(rd.size() == rm.size()); - if (rd.size() == 32) assert(amount < 32); - - return Instruction{ - .logical_shifted_register = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imm6 = amount, - .rm = rm.enc(), - .n = n, - .shift = @intFromEnum(shift), - .opc = opc, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, - }, - }, - }; - } - - fn addSubtractImmediate( - op: u1, - s: u1, - rd: Register, - rn: Register, - imm12: u12, - shift: bool, - ) Instruction { - assert(rd.size() == rn.size()); - assert(rn.id() != Register.xzr.id()); - - return Instruction{ - .add_subtract_immediate = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imm12 = imm12, - .sh = @intFromBool(shift), - .s = s, - .op = op, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn logicalImmediate( - opc: u2, - rd: Register, - rn: Register, - imms: u6, - immr: u6, - n: u1, - ) Instruction { - assert(rd.size() == rn.size()); - assert(!(rd.size() == 32 and n != 0)); - - return Instruction{ - .logical_immediate = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imms = imms, - .immr = immr, - .n = n, - .opc = opc, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn initBitfield( - opc: u2, - n: u1, - rd: Register, - rn: Register, - immr: u6, - imms: u6, - ) Instruction { - assert(rd.size() == rn.size()); - assert(!(rd.size() == 64 and n != 1)); - assert(!(rd.size() == 32 and (n != 0 or immr >> 5 != 0 or immr >> 5 != 0))); - - return Instruction{ - .bitfield = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imms = imms, - .immr = immr, - .n = n, - .opc = opc, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - pub const AddSubtractShiftedRegisterShift = enum(u2) { lsl, lsr, asr, _ }; - - fn addSubtractShiftedRegister( - op: u1, - s: u1, - shift: AddSubtractShiftedRegisterShift, - rd: Register, - rn: Register, - rm: Register, - imm6: u6, - ) Instruction { - assert(rd.size() == rn.size()); - assert(rd.size() == rm.size()); - - return Instruction{ - .add_subtract_shifted_register = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imm6 = imm6, - .rm = rm.enc(), - .shift = @intFromEnum(shift), - .s = s, - .op = op, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - pub const AddSubtractExtendedRegisterOption = enum(u3) { - uxtb, - uxth, - uxtw, - uxtx, // serves also as lsl - sxtb, - sxth, - sxtw, - sxtx, - }; - - fn addSubtractExtendedRegister( - op: u1, - s: u1, - rd: Register, - rn: Register, - rm: Register, - extend: AddSubtractExtendedRegisterOption, - imm3: u3, - ) Instruction { - return Instruction{ - .add_subtract_extended_register = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .imm3 = imm3, - .option = @intFromEnum(extend), - .rm = rm.enc(), - .s = s, - .op = op, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn conditionalBranch( - o0: u1, - o1: u1, - cond: Condition, - offset: i21, - ) Instruction { - assert(offset & 0b11 == 0b00); - - return Instruction{ - .conditional_branch = .{ - .cond = @intFromEnum(cond), - .o0 = o0, - .imm19 = @as(u19, @bitCast(@as(i19, @intCast(offset >> 2)))), - .o1 = o1, - }, - }; - } - - fn compareAndBranch( - op: u1, - rt: Register, - offset: i21, - ) Instruction { - assert(offset & 0b11 == 0b00); - - return Instruction{ - .compare_and_branch = .{ - .rt = rt.enc(), - .imm19 = @as(u19, @bitCast(@as(i19, @intCast(offset >> 2)))), - .op = op, - .sf = switch (rt.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn conditionalSelect( - op2: u2, - op: u1, - s: u1, - rd: Register, - rn: Register, - rm: Register, - cond: Condition, - ) Instruction { - assert(rd.size() == rn.size()); - assert(rd.size() == rm.size()); - - return Instruction{ - .conditional_select = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .op2 = op2, - .cond = @intFromEnum(cond), - .rm = rm.enc(), - .s = s, - .op = op, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn dataProcessing3Source( - op54: u2, - op31: u3, - o0: u1, - rd: Register, - rn: Register, - rm: Register, - ra: Register, - ) Instruction { - return Instruction{ - .data_processing_3_source = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .ra = ra.enc(), - .o0 = o0, - .rm = rm.enc(), - .op31 = op31, - .op54 = op54, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn dataProcessing2Source( - s: u1, - opcode: u6, - rd: Register, - rn: Register, - rm: Register, - ) Instruction { - assert(rd.size() == rn.size()); - assert(rd.size() == rm.size()); - - return Instruction{ - .data_processing_2_source = .{ - .rd = rd.enc(), - .rn = rn.enc(), - .opcode = opcode, - .rm = rm.enc(), - .s = s, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - // Helper functions for assembly syntax functions - - // Move wide (immediate) - - pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction { - return moveWideImmediate(0b00, rd, imm16, shift); - } - - pub fn movz(rd: Register, imm16: u16, shift: u6) Instruction { - return moveWideImmediate(0b10, rd, imm16, shift); - } - - pub fn movk(rd: Register, imm16: u16, shift: u6) Instruction { - return moveWideImmediate(0b11, rd, imm16, shift); - } - - // PC relative address - - pub fn adr(rd: Register, imm21: i21) Instruction { - return pcRelativeAddress(rd, imm21, 0b0); - } - - pub fn adrp(rd: Register, imm21: i21) Instruction { - return pcRelativeAddress(rd, imm21, 0b1); - } - - // Load or store register - - pub fn ldrLiteral(rt: Register, literal: u19) Instruction { - return loadLiteral(rt, literal); - } - - pub fn ldr(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldr); - } - - pub fn ldrh(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldrh); - } - - pub fn ldrb(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldrb); - } - - pub fn ldrsb(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldrsb); - } - - pub fn ldrsh(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldrsh); - } - - pub fn ldrsw(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .ldrsw); - } - - pub fn str(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .str); - } - - pub fn strh(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .strh); - } - - pub fn strb(rt: Register, rn: Register, offset: LoadStoreOffset) Instruction { - return loadStoreRegister(rt, rn, offset, .strb); - } - - // Load or store pair of registers - - pub const LoadStorePairOffset = struct { - encoding: enum(u2) { - post_index = 0b01, - signed = 0b10, - pre_index = 0b11, - }, - offset: i9, - - pub fn none() LoadStorePairOffset { - return .{ .encoding = .signed, .offset = 0 }; - } - - pub fn post_index(imm: i9) LoadStorePairOffset { - return .{ .encoding = .post_index, .offset = imm }; - } - - pub fn pre_index(imm: i9) LoadStorePairOffset { - return .{ .encoding = .pre_index, .offset = imm }; - } - - pub fn signed(imm: i9) LoadStorePairOffset { - return .{ .encoding = .signed, .offset = imm }; - } - }; - - pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @intFromEnum(offset.encoding), true); - } - - pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, true); - } - - pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @intFromEnum(offset.encoding), false); - } - - pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, false); - } - - // Exception generation - - pub fn svc(imm16: u16) Instruction { - return exceptionGeneration(0b000, 0b000, 0b01, imm16); - } - - pub fn hvc(imm16: u16) Instruction { - return exceptionGeneration(0b000, 0b000, 0b10, imm16); - } - - pub fn smc(imm16: u16) Instruction { - return exceptionGeneration(0b000, 0b000, 0b11, imm16); - } - - pub fn brk(imm16: u16) Instruction { - return exceptionGeneration(0b001, 0b000, 0b00, imm16); - } - - pub fn hlt(imm16: u16) Instruction { - return exceptionGeneration(0b010, 0b000, 0b00, imm16); - } - - // Unconditional branch (register) - - pub fn br(rn: Register) Instruction { - return unconditionalBranchRegister(0b0000, 0b11111, 0b000000, rn, 0b00000); - } - - pub fn blr(rn: Register) Instruction { - return unconditionalBranchRegister(0b0001, 0b11111, 0b000000, rn, 0b00000); - } - - pub fn ret(rn: ?Register) Instruction { - return unconditionalBranchRegister(0b0010, 0b11111, 0b000000, rn orelse .x30, 0b00000); - } - - // Unconditional branch (immediate) - - pub fn b(offset: i28) Instruction { - return unconditionalBranchImmediate(0, offset); - } - - pub fn bl(offset: i28) Instruction { - return unconditionalBranchImmediate(1, offset); - } - - // Nop - - pub fn nop() Instruction { - return Instruction{ .no_operation = .{} }; - } - - // Logical (shifted register) - - pub fn andShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b00, 0b0, rd, rn, rm, shift, amount); - } - - pub fn bicShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b00, 0b1, rd, rn, rm, shift, amount); - } - - pub fn orrShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b01, 0b0, rd, rn, rm, shift, amount); - } - - pub fn ornShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b01, 0b1, rd, rn, rm, shift, amount); - } - - pub fn eorShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b10, 0b0, rd, rn, rm, shift, amount); - } - - pub fn eonShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b10, 0b1, rd, rn, rm, shift, amount); - } - - pub fn andsShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b11, 0b0, rd, rn, rm, shift, amount); - } - - pub fn bicsShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: LogicalShiftedRegisterShift, - amount: u6, - ) Instruction { - return logicalShiftedRegister(0b11, 0b1, rd, rn, rm, shift, amount); - } - - // Add/subtract (immediate) - - pub fn add(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b0, 0b0, rd, rn, imm, shift); - } - - pub fn adds(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b0, 0b1, rd, rn, imm, shift); - } - - pub fn sub(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b1, 0b0, rd, rn, imm, shift); - } - - pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift); - } - - // Logical (immediate) - - pub fn andImmediate(rd: Register, rn: Register, imms: u6, immr: u6, n: u1) Instruction { - return logicalImmediate(0b00, rd, rn, imms, immr, n); - } - - pub fn orrImmediate(rd: Register, rn: Register, imms: u6, immr: u6, n: u1) Instruction { - return logicalImmediate(0b01, rd, rn, imms, immr, n); - } - - pub fn eorImmediate(rd: Register, rn: Register, imms: u6, immr: u6, n: u1) Instruction { - return logicalImmediate(0b10, rd, rn, imms, immr, n); - } - - pub fn andsImmediate(rd: Register, rn: Register, imms: u6, immr: u6, n: u1) Instruction { - return logicalImmediate(0b11, rd, rn, imms, immr, n); - } - - // Bitfield - - pub fn sbfm(rd: Register, rn: Register, immr: u6, imms: u6) Instruction { - const n: u1 = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }; - return initBitfield(0b00, n, rd, rn, immr, imms); - } - - pub fn bfm(rd: Register, rn: Register, immr: u6, imms: u6) Instruction { - const n: u1 = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }; - return initBitfield(0b01, n, rd, rn, immr, imms); - } - - pub fn ubfm(rd: Register, rn: Register, immr: u6, imms: u6) Instruction { - const n: u1 = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }; - return initBitfield(0b10, n, rd, rn, immr, imms); - } - - pub fn asrImmediate(rd: Register, rn: Register, shift: u6) Instruction { - const imms = @as(u6, @intCast(rd.size() - 1)); - return sbfm(rd, rn, shift, imms); - } - - pub fn sbfx(rd: Register, rn: Register, lsb: u6, width: u7) Instruction { - return sbfm(rd, rn, lsb, @as(u6, @intCast(lsb + width - 1))); - } - - pub fn sxtb(rd: Register, rn: Register) Instruction { - return sbfm(rd, rn, 0, 7); - } - - pub fn sxth(rd: Register, rn: Register) Instruction { - return sbfm(rd, rn, 0, 15); - } - - pub fn sxtw(rd: Register, rn: Register) Instruction { - assert(rd.size() == 64); - return sbfm(rd, rn, 0, 31); - } - - pub fn lslImmediate(rd: Register, rn: Register, shift: u6) Instruction { - const size = @as(u6, @intCast(rd.size() - 1)); - return ubfm(rd, rn, size - shift + 1, size - shift); - } - - pub fn lsrImmediate(rd: Register, rn: Register, shift: u6) Instruction { - const imms = @as(u6, @intCast(rd.size() - 1)); - return ubfm(rd, rn, shift, imms); - } - - pub fn ubfx(rd: Register, rn: Register, lsb: u6, width: u7) Instruction { - return ubfm(rd, rn, lsb, @as(u6, @intCast(lsb + width - 1))); - } - - pub fn uxtb(rd: Register, rn: Register) Instruction { - return ubfm(rd, rn, 0, 7); - } - - pub fn uxth(rd: Register, rn: Register) Instruction { - return ubfm(rd, rn, 0, 15); - } - - // Add/subtract (shifted register) - - pub fn addShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: AddSubtractShiftedRegisterShift, - imm6: u6, - ) Instruction { - return addSubtractShiftedRegister(0b0, 0b0, shift, rd, rn, rm, imm6); - } - - pub fn addsShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: AddSubtractShiftedRegisterShift, - imm6: u6, - ) Instruction { - return addSubtractShiftedRegister(0b0, 0b1, shift, rd, rn, rm, imm6); - } - - pub fn subShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: AddSubtractShiftedRegisterShift, - imm6: u6, - ) Instruction { - return addSubtractShiftedRegister(0b1, 0b0, shift, rd, rn, rm, imm6); - } - - pub fn subsShiftedRegister( - rd: Register, - rn: Register, - rm: Register, - shift: AddSubtractShiftedRegisterShift, - imm6: u6, - ) Instruction { - return addSubtractShiftedRegister(0b1, 0b1, shift, rd, rn, rm, imm6); - } - - // Add/subtract (extended register) - - pub fn addExtendedRegister( - rd: Register, - rn: Register, - rm: Register, - extend: AddSubtractExtendedRegisterOption, - imm3: u3, - ) Instruction { - return addSubtractExtendedRegister(0b0, 0b0, rd, rn, rm, extend, imm3); - } - - pub fn addsExtendedRegister( - rd: Register, - rn: Register, - rm: Register, - extend: AddSubtractExtendedRegisterOption, - imm3: u3, - ) Instruction { - return addSubtractExtendedRegister(0b0, 0b1, rd, rn, rm, extend, imm3); - } - - pub fn subExtendedRegister( - rd: Register, - rn: Register, - rm: Register, - extend: AddSubtractExtendedRegisterOption, - imm3: u3, - ) Instruction { - return addSubtractExtendedRegister(0b1, 0b0, rd, rn, rm, extend, imm3); - } - - pub fn subsExtendedRegister( - rd: Register, - rn: Register, - rm: Register, - extend: AddSubtractExtendedRegisterOption, - imm3: u3, - ) Instruction { - return addSubtractExtendedRegister(0b1, 0b1, rd, rn, rm, extend, imm3); - } - - // Conditional branch - - pub fn bCond(cond: Condition, offset: i21) Instruction { - return conditionalBranch(0b0, 0b0, cond, offset); - } - - // Compare and branch - - pub fn cbz(rt: Register, offset: i21) Instruction { - return compareAndBranch(0b0, rt, offset); - } - - pub fn cbnz(rt: Register, offset: i21) Instruction { - return compareAndBranch(0b1, rt, offset); - } - - // Conditional select - - pub fn csel(rd: Register, rn: Register, rm: Register, cond: Condition) Instruction { - return conditionalSelect(0b00, 0b0, 0b0, rd, rn, rm, cond); - } - - pub fn csinc(rd: Register, rn: Register, rm: Register, cond: Condition) Instruction { - return conditionalSelect(0b01, 0b0, 0b0, rd, rn, rm, cond); - } - - pub fn csinv(rd: Register, rn: Register, rm: Register, cond: Condition) Instruction { - return conditionalSelect(0b00, 0b1, 0b0, rd, rn, rm, cond); - } - - pub fn csneg(rd: Register, rn: Register, rm: Register, cond: Condition) Instruction { - return conditionalSelect(0b01, 0b1, 0b0, rd, rn, rm, cond); - } - - // Data processing (3 source) - - pub fn madd(rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - return dataProcessing3Source(0b00, 0b000, 0b0, rd, rn, rm, ra); - } - - pub fn smaddl(rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - assert(rd.size() == 64 and rn.size() == 32 and rm.size() == 32 and ra.size() == 64); - return dataProcessing3Source(0b00, 0b001, 0b0, rd, rn, rm, ra); - } - - pub fn umaddl(rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - assert(rd.size() == 64 and rn.size() == 32 and rm.size() == 32 and ra.size() == 64); - return dataProcessing3Source(0b00, 0b101, 0b0, rd, rn, rm, ra); - } - - pub fn msub(rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - return dataProcessing3Source(0b00, 0b000, 0b1, rd, rn, rm, ra); - } - - pub fn mul(rd: Register, rn: Register, rm: Register) Instruction { - return madd(rd, rn, rm, .xzr); - } - - pub fn smull(rd: Register, rn: Register, rm: Register) Instruction { - return smaddl(rd, rn, rm, .xzr); - } - - pub fn smulh(rd: Register, rn: Register, rm: Register) Instruction { - assert(rd.size() == 64); - return dataProcessing3Source(0b00, 0b010, 0b0, rd, rn, rm, .xzr); - } - - pub fn umull(rd: Register, rn: Register, rm: Register) Instruction { - return umaddl(rd, rn, rm, .xzr); - } - - pub fn umulh(rd: Register, rn: Register, rm: Register) Instruction { - assert(rd.size() == 64); - return dataProcessing3Source(0b00, 0b110, 0b0, rd, rn, rm, .xzr); - } - - pub fn mneg(rd: Register, rn: Register, rm: Register) Instruction { - return msub(rd, rn, rm, .xzr); - } - - // Data processing (2 source) - - pub fn udiv(rd: Register, rn: Register, rm: Register) Instruction { - return dataProcessing2Source(0b0, 0b000010, rd, rn, rm); - } - - pub fn sdiv(rd: Register, rn: Register, rm: Register) Instruction { - return dataProcessing2Source(0b0, 0b000011, rd, rn, rm); - } - - pub fn lslv(rd: Register, rn: Register, rm: Register) Instruction { - return dataProcessing2Source(0b0, 0b001000, rd, rn, rm); - } - - pub fn lsrv(rd: Register, rn: Register, rm: Register) Instruction { - return dataProcessing2Source(0b0, 0b001001, rd, rn, rm); - } - - pub fn asrv(rd: Register, rn: Register, rm: Register) Instruction { - return dataProcessing2Source(0b0, 0b001010, rd, rn, rm); - } - - pub const asrRegister = asrv; - pub const lslRegister = lslv; - pub const lsrRegister = lsrv; -}; - -test { - testing.refAllDecls(@This()); -} - -test "serialize instructions" { - const Testcase = struct { - inst: Instruction, - expected: u32, - }; - - const testcases = [_]Testcase{ - .{ // orr x0, xzr, x1 - .inst = Instruction.orrShiftedRegister(.x0, .xzr, .x1, .lsl, 0), - .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, - }, - .{ // orn x0, xzr, x1 - .inst = Instruction.ornShiftedRegister(.x0, .xzr, .x1, .lsl, 0), - .expected = 0b1_01_01010_00_1_00001_000000_11111_00000, - }, - .{ // movz x1, #4 - .inst = Instruction.movz(.x1, 4, 0), - .expected = 0b1_10_100101_00_0000000000000100_00001, - }, - .{ // movz x1, #4, lsl 16 - .inst = Instruction.movz(.x1, 4, 16), - .expected = 0b1_10_100101_01_0000000000000100_00001, - }, - .{ // movz x1, #4, lsl 32 - .inst = Instruction.movz(.x1, 4, 32), - .expected = 0b1_10_100101_10_0000000000000100_00001, - }, - .{ // movz x1, #4, lsl 48 - .inst = Instruction.movz(.x1, 4, 48), - .expected = 0b1_10_100101_11_0000000000000100_00001, - }, - .{ // movz w1, #4 - .inst = Instruction.movz(.w1, 4, 0), - .expected = 0b0_10_100101_00_0000000000000100_00001, - }, - .{ // movz w1, #4, lsl 16 - .inst = Instruction.movz(.w1, 4, 16), - .expected = 0b0_10_100101_01_0000000000000100_00001, - }, - .{ // svc #0 - .inst = Instruction.svc(0), - .expected = 0b1101_0100_000_0000000000000000_00001, - }, - .{ // svc #0x80 ; typical on Darwin - .inst = Instruction.svc(0x80), - .expected = 0b1101_0100_000_0000000010000000_00001, - }, - .{ // ret - .inst = Instruction.ret(null), - .expected = 0b1101_011_00_10_11111_0000_00_11110_00000, - }, - .{ // bl #0x10 - .inst = Instruction.bl(0x10), - .expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100, - }, - .{ // ldr x2, [x1] - .inst = Instruction.ldr(.x2, .x1, Instruction.LoadStoreOffset.none), - .expected = 0b11_111_0_01_01_000000000000_00001_00010, - }, - .{ // ldr x2, [x1, #1]! - .inst = Instruction.ldr(.x2, .x1, Instruction.LoadStoreOffset.imm_pre_index(1)), - .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, - }, - .{ // ldr x2, [x1], #-1 - .inst = Instruction.ldr(.x2, .x1, Instruction.LoadStoreOffset.imm_post_index(-1)), - .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, - }, - .{ // ldr x2, [x1], (x3) - .inst = Instruction.ldr(.x2, .x1, Instruction.LoadStoreOffset.reg(.x3)), - .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010, - }, - .{ // ldr x2, label - .inst = Instruction.ldrLiteral(.x2, 0x1), - .expected = 0b01_011_0_00_0000000000000000001_00010, - }, - .{ // ldrh x7, [x4], #0xaa - .inst = Instruction.ldrh(.x7, .x4, Instruction.LoadStoreOffset.imm_post_index(0xaa)), - .expected = 0b01_111_0_00_01_0_010101010_01_00100_00111, - }, - .{ // ldrb x9, [x15, #0xff]! - .inst = Instruction.ldrb(.x9, .x15, Instruction.LoadStoreOffset.imm_pre_index(0xff)), - .expected = 0b00_111_0_00_01_0_011111111_11_01111_01001, - }, - .{ // str x2, [x1] - .inst = Instruction.str(.x2, .x1, Instruction.LoadStoreOffset.none), - .expected = 0b11_111_0_01_00_000000000000_00001_00010, - }, - .{ // str x2, [x1], (x3) - .inst = Instruction.str(.x2, .x1, Instruction.LoadStoreOffset.reg(.x3)), - .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010, - }, - .{ // strh w0, [x1] - .inst = Instruction.strh(.w0, .x1, Instruction.LoadStoreOffset.none), - .expected = 0b01_111_0_01_00_000000000000_00001_00000, - }, - .{ // strb w8, [x9] - .inst = Instruction.strb(.w8, .x9, Instruction.LoadStoreOffset.none), - .expected = 0b00_111_0_01_00_000000000000_01001_01000, - }, - .{ // adr x2, #0x8 - .inst = Instruction.adr(.x2, 0x8), - .expected = 0b0_00_10000_0000000000000000010_00010, - }, - .{ // adr x2, -#0x8 - .inst = Instruction.adr(.x2, -0x8), - .expected = 0b0_00_10000_1111111111111111110_00010, - }, - .{ // adrp x2, #0x8 - .inst = Instruction.adrp(.x2, 0x8), - .expected = 0b1_00_10000_0000000000000000010_00010, - }, - .{ // adrp x2, -#0x8 - .inst = Instruction.adrp(.x2, -0x8), - .expected = 0b1_00_10000_1111111111111111110_00010, - }, - .{ // stp x1, x2, [sp, #8] - .inst = Instruction.stp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.signed(8)), - .expected = 0b10_101_0_010_0_0000001_00010_11111_00001, - }, - .{ // ldp x1, x2, [sp, #8] - .inst = Instruction.ldp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.signed(8)), - .expected = 0b10_101_0_010_1_0000001_00010_11111_00001, - }, - .{ // stp x1, x2, [sp, #-16]! - .inst = Instruction.stp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.pre_index(-16)), - .expected = 0b10_101_0_011_0_1111110_00010_11111_00001, - }, - .{ // ldp x1, x2, [sp], #16 - .inst = Instruction.ldp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.post_index(16)), - .expected = 0b10_101_0_001_1_0000010_00010_11111_00001, - }, - .{ // and x0, x4, x2 - .inst = Instruction.andShiftedRegister(.x0, .x4, .x2, .lsl, 0), - .expected = 0b1_00_01010_00_0_00010_000000_00100_00000, - }, - .{ // and x0, x4, x2, lsl #0x8 - .inst = Instruction.andShiftedRegister(.x0, .x4, .x2, .lsl, 0x8), - .expected = 0b1_00_01010_00_0_00010_001000_00100_00000, - }, - .{ // add x0, x10, #10 - .inst = Instruction.add(.x0, .x10, 10, false), - .expected = 0b1_0_0_100010_0_0000_0000_1010_01010_00000, - }, - .{ // subs x0, x5, #11, lsl #12 - .inst = Instruction.subs(.x0, .x5, 11, true), - .expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000, - }, - .{ // b.hi #-4 - .inst = Instruction.bCond(.hi, -4), - .expected = 0b0101010_0_1111111111111111111_0_1000, - }, - .{ // cbz x10, #40 - .inst = Instruction.cbz(.x10, 40), - .expected = 0b1_011010_0_0000000000000001010_01010, - }, - .{ // add x0, x1, x2, lsl #5 - .inst = Instruction.addShiftedRegister(.x0, .x1, .x2, .lsl, 5), - .expected = 0b1_0_0_01011_00_0_00010_000101_00001_00000, - }, - .{ // csinc x1, x2, x4, eq - .inst = Instruction.csinc(.x1, .x2, .x4, .eq), - .expected = 0b1_0_0_11010100_00100_0000_0_1_00010_00001, - }, - .{ // mul x1, x4, x9 - .inst = Instruction.mul(.x1, .x4, .x9), - .expected = 0b1_00_11011_000_01001_0_11111_00100_00001, - }, - .{ // eor x3, x5, #1 - .inst = Instruction.eorImmediate(.x3, .x5, 0b000000, 0b000000, 0b1), - .expected = 0b1_10_100100_1_000000_000000_00101_00011, - }, - .{ // lslv x6, x9, x10 - .inst = Instruction.lslv(.x6, .x9, .x10), - .expected = 0b1_0_0_11010110_01010_0010_00_01001_00110, - }, - .{ // lsl x4, x2, #42 - .inst = Instruction.lslImmediate(.x4, .x2, 42), - .expected = 0b1_10_100110_1_010110_010101_00010_00100, - }, - .{ // lsl x4, x2, #63 - .inst = Instruction.lslImmediate(.x4, .x2, 63), - .expected = 0b1_10_100110_1_000001_000000_00010_00100, - }, - .{ // lsr x4, x2, #42 - .inst = Instruction.lsrImmediate(.x4, .x2, 42), - .expected = 0b1_10_100110_1_101010_111111_00010_00100, - }, - .{ // lsr x4, x2, #63 - .inst = Instruction.lsrImmediate(.x4, .x2, 63), - .expected = 0b1_10_100110_1_111111_111111_00010_00100, - }, - .{ // umull x0, w0, w1 - .inst = Instruction.umull(.x0, .w0, .w1), - .expected = 0b1_00_11011_1_01_00001_0_11111_00000_00000, - }, - .{ // smull x0, w0, w1 - .inst = Instruction.smull(.x0, .w0, .w1), - .expected = 0b1_00_11011_0_01_00001_0_11111_00000_00000, - }, - .{ // tst x0, #0xffffffff00000000 - .inst = Instruction.andsImmediate(.xzr, .x0, 0b011111, 0b100000, 0b1), - .expected = 0b1_11_100100_1_100000_011111_00000_11111, - }, - .{ // umulh x0, x1, x2 - .inst = Instruction.umulh(.x0, .x1, .x2), - .expected = 0b1_00_11011_1_10_00010_0_11111_00001_00000, - }, - .{ // smulh x0, x1, x2 - .inst = Instruction.smulh(.x0, .x1, .x2), - .expected = 0b1_00_11011_0_10_00010_0_11111_00001_00000, - }, - .{ // adds x0, x1, x2, sxtx - .inst = Instruction.addsExtendedRegister(.x0, .x1, .x2, .sxtx, 0), - .expected = 0b1_0_1_01011_00_1_00010_111_000_00001_00000, - }, - }; - - for (testcases) |case| { - const actual = case.inst.toU32(); - try testing.expectEqual(case.expected, actual); - } -} diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 0753cc5d16e2560ea2f238e31962d0bde9167334..ffff65d4d1fc4535cc8ef833227ea040dca3f968 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -744,7 +744,7 @@ pub fn generate( src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, air: *const Air, - liveness: *const Air.Liveness, + liveness: *const ?Air.Liveness, ) CodeGenError!Mir { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -767,7 +767,7 @@ pub fn generate( .pt = pt, .mod = mod, .bin_file = bin_file, - .liveness = liveness.*, + .liveness = liveness.*.?, .target = &mod.resolved_target.result, .owner = .{ .nav_index = func.owner_nav }, .args = undefined, // populated after `resolveCallingConventionValues` @@ -4584,7 +4584,7 @@ fn structFieldPtr(func: *Func, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde const field_offset: i32 = switch (container_ty.containerLayout(zcu)) { .auto, .@"extern" => @intCast(container_ty.structFieldOffset(index, zcu)), .@"packed" => @divExact(@as(i32, ptr_container_ty.ptrInfo(zcu).packed_offset.bit_offset) + - (if (zcu.typeToStruct(container_ty)) |struct_obj| pt.structPackedFieldBitOffset(struct_obj, index) else 0) - + (if (zcu.typeToStruct(container_ty)) |struct_obj| zcu.structPackedFieldBitOffset(struct_obj, index) else 0) - ptr_field_ty.ptrInfo(zcu).packed_offset.bit_offset, 8), }; @@ -4615,7 +4615,7 @@ fn airStructFieldVal(func: *Func, inst: Air.Inst.Index) !void { const field_off: u32 = switch (struct_ty.containerLayout(zcu)) { .auto, .@"extern" => @intCast(struct_ty.structFieldOffset(index, zcu) * 8), .@"packed" => if (zcu.typeToStruct(struct_ty)) |struct_type| - pt.structPackedFieldBitOffset(struct_type, index) + zcu.structPackedFieldBitOffset(struct_type, index) else 0, }; @@ -8059,7 +8059,7 @@ fn airAggregateInit(func: *Func, inst: Air.Inst.Index) !void { const elem_abi_size: u32 = @intCast(elem_ty.abiSize(zcu)); const elem_abi_bits = elem_abi_size * 8; - const elem_off = pt.structPackedFieldBitOffset(struct_obj, elem_i); + const elem_off = zcu.structPackedFieldBitOffset(struct_obj, elem_i); const elem_byte_off: i32 = @intCast(elem_off / elem_abi_bits * elem_abi_size); const elem_bit_off = elem_off % elem_abi_bits; const elem_mcv = try func.resolveInst(elem); diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 31a7f39d6900215db7d4cf0601d1730feabc9089..6ab5dea4ec31b3189ea7a9924b29e9d1ef4c731d 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -267,7 +267,7 @@ pub fn generate( src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, air: *const Air, - liveness: *const Air.Liveness, + liveness: *const ?Air.Liveness, ) CodeGenError!Mir { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -288,7 +288,7 @@ pub fn generate( .gpa = gpa, .pt = pt, .air = air.*, - .liveness = liveness.*, + .liveness = liveness.*.?, .target = target, .bin_file = lf, .func_index = func_index, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 50d104a7bc85fcd27ba12757a67b7249d2805960..fca32627b9a7452529761a433c184118874e097c 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1173,7 +1173,7 @@ pub fn generate( src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, air: *const Air, - liveness: *const Air.Liveness, + liveness: *const ?Air.Liveness, ) Error!Mir { _ = src_loc; _ = bin_file; @@ -1194,7 +1194,7 @@ pub fn generate( .gpa = gpa, .pt = pt, .air = air.*, - .liveness = liveness.*, + .liveness = liveness.*.?, .owner_nav = cg.owner_nav, .target = target, .ptr_size = switch (target.cpu.arch) { @@ -3776,7 +3776,7 @@ fn structFieldPtr( break :offset @as(u32, 0); } const struct_type = zcu.typeToStruct(struct_ty).?; - break :offset @divExact(pt.structPackedFieldBitOffset(struct_type, index) + struct_ptr_ty_info.packed_offset.bit_offset, 8); + break :offset @divExact(zcu.structPackedFieldBitOffset(struct_type, index) + struct_ptr_ty_info.packed_offset.bit_offset, 8); }, .@"union" => 0, else => unreachable, @@ -3812,7 +3812,7 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .@"packed" => switch (struct_ty.zigTypeTag(zcu)) { .@"struct" => result: { const packed_struct = zcu.typeToPackedStruct(struct_ty).?; - const offset = pt.structPackedFieldBitOffset(packed_struct, field_index); + const offset = zcu.structPackedFieldBitOffset(packed_struct, field_index); const backing_ty = Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)); const host_bits = backing_ty.intInfo(zcu).bits; @@ -5696,7 +5696,7 @@ fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .auto, .@"extern" => parent_ty.structFieldOffset(field_index, zcu), .@"packed" => offset: { const parent_ptr_offset = parent_ptr_ty.ptrInfo(zcu).packed_offset.bit_offset; - const field_offset = if (zcu.typeToStruct(parent_ty)) |loaded_struct| pt.structPackedFieldBitOffset(loaded_struct, field_index) else 0; + const field_offset = if (zcu.typeToStruct(parent_ty)) |loaded_struct| zcu.structPackedFieldBitOffset(loaded_struct, field_index) else 0; const field_ptr_offset = field_ptr_ty.ptrInfo(zcu).packed_offset.bit_offset; break :offset @divExact(parent_ptr_offset + field_offset - field_ptr_offset, 8); }, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 6341f7e3d288b83353849260170d1d7789e9f4f4..89a23d351442202188bc95d0fbceab66ce00f765 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -878,7 +878,7 @@ pub fn generate( src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, air: *const Air, - liveness: *const Air.Liveness, + liveness: *const ?Air.Liveness, ) codegen.CodeGenError!Mir { _ = bin_file; const zcu = pt.zcu; @@ -894,7 +894,7 @@ pub fn generate( .gpa = gpa, .pt = pt, .air = air.*, - .liveness = liveness.*, + .liveness = liveness.*.?, .target = &mod.resolved_target.result, .mod = mod, .owner = .{ .nav_index = func.owner_nav }, @@ -100674,11 +100674,12 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; const struct_field = cg.air.extraData(Air.StructField, ty_pl.payload).data; var ops = try cg.tempsFromOperands(inst, .{struct_field.struct_operand}); - try ops[0].toOffset(cg.fieldOffset( + try ops[0].toOffset(@intCast(codegen.fieldOffset( cg.typeOf(struct_field.struct_operand), ty_pl.ty.toType(), struct_field.field_index, - ), cg); + zcu, + )), cg); try ops[0].finish(inst, &.{struct_field.struct_operand}, &ops, cg); }, .struct_field_ptr_index_0, @@ -100688,7 +100689,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { => |air_tag| { const ty_op = air_datas[@intFromEnum(inst)].ty_op; var ops = try cg.tempsFromOperands(inst, .{ty_op.operand}); - try ops[0].toOffset(cg.fieldOffset( + try ops[0].toOffset(@intCast(codegen.fieldOffset( cg.typeOf(ty_op.operand), ty_op.ty.toType(), switch (air_tag) { @@ -100698,7 +100699,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .struct_field_ptr_index_2 => 2, .struct_field_ptr_index_3 => 3, }, - ), cg); + zcu, + )), cg); try ops[0].finish(inst, &.{ty_op.operand}, &ops, cg); }, .struct_field_val => { @@ -168108,11 +168110,12 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; const field_parent_ptr = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; var ops = try cg.tempsFromOperands(inst, .{field_parent_ptr.field_ptr}); - try ops[0].toOffset(-cg.fieldOffset( + try ops[0].toOffset(-@as(i32, @intCast(codegen.fieldOffset( ty_pl.ty.toType(), cg.typeOf(field_parent_ptr.field_ptr), field_parent_ptr.field_index, - ), cg); + zcu, + ))), cg); try ops[0].finish(inst, &.{field_parent_ptr.field_ptr}, &ops, cg); }, .wasm_memory_size, .wasm_memory_grow => unreachable, @@ -174809,18 +174812,6 @@ fn airStore(self: *CodeGen, inst: Air.Inst.Index, safety: bool) !void { return self.finishAir(inst, .none, .{ bin_op.lhs, bin_op.rhs, .none }); } -fn fieldOffset(self: *CodeGen, ptr_agg_ty: Type, ptr_field_ty: Type, field_index: u32) i32 { - const pt = self.pt; - const zcu = pt.zcu; - const agg_ty = ptr_agg_ty.childType(zcu); - return switch (agg_ty.containerLayout(zcu)) { - .auto, .@"extern" => @intCast(agg_ty.structFieldOffset(field_index, zcu)), - .@"packed" => @divExact(@as(i32, ptr_agg_ty.ptrInfo(zcu).packed_offset.bit_offset) + - (if (zcu.typeToStruct(agg_ty)) |loaded_struct| pt.structPackedFieldBitOffset(loaded_struct, field_index) else 0) - - ptr_field_ty.ptrInfo(zcu).packed_offset.bit_offset, 8), - }; -} - fn genUnOp(self: *CodeGen, maybe_inst: ?Air.Inst.Index, tag: Air.Inst.Tag, src_air: Air.Inst.Ref) !MCValue { const pt = self.pt; const zcu = pt.zcu; @@ -184575,7 +184566,7 @@ fn airAggregateInit(self: *CodeGen, inst: Air.Inst.Index) !void { } const elem_abi_size: u32 = @intCast(elem_ty.abiSize(zcu)); const elem_abi_bits = elem_abi_size * 8; - const elem_off = pt.structPackedFieldBitOffset(loaded_struct, elem_i); + const elem_off = zcu.structPackedFieldBitOffset(loaded_struct, elem_i); const elem_byte_off: i32 = @intCast(elem_off / elem_abi_bits * elem_abi_size); const elem_bit_off = elem_off % elem_abi_bits; const elem_mcv = try self.resolveInst(elem); @@ -185625,21 +185616,19 @@ fn resolveCallingConventionValues( fn fail(cg: *CodeGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @branchHint(.cold); const zcu = cg.pt.zcu; - switch (cg.owner) { - .nav_index => |i| return zcu.codegenFail(i, format, args), - .lazy_sym => |s| return zcu.codegenFailType(s.ty, format, args), - } - return error.CodegenFail; + return switch (cg.owner) { + .nav_index => |i| zcu.codegenFail(i, format, args), + .lazy_sym => |s| zcu.codegenFailType(s.ty, format, args), + }; } fn failMsg(cg: *CodeGen, msg: *Zcu.ErrorMsg) error{ OutOfMemory, CodegenFail } { @branchHint(.cold); const zcu = cg.pt.zcu; - switch (cg.owner) { - .nav_index => |i| return zcu.codegenFailMsg(i, msg), - .lazy_sym => |s| return zcu.codegenFailTypeMsg(s.ty, msg), - } - return error.CodegenFail; + return switch (cg.owner) { + .nav_index => |i| zcu.codegenFailMsg(i, msg), + .lazy_sym => |s| zcu.codegenFailTypeMsg(s.ty, msg), + }; } fn parseRegName(name: []const u8) ?Register { diff --git a/src/codegen.zig b/src/codegen.zig index 9bddc51963c817f595df014c69faa392cca470e3..d5092341482aecad21b24e17269810be008f3eb4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -22,6 +22,8 @@ const Zir = std.zig.Zir; const Alignment = InternPool.Alignment; const dev = @import("dev.zig"); +pub const aarch64 = @import("codegen/aarch64.zig"); + pub const CodeGenError = GenerateSymbolError || error{ /// Indicates the error is already stored in Zcu `failed_codegen`. CodegenFail, @@ -48,7 +50,7 @@ fn devFeatureForBackend(backend: std.builtin.CompilerBackend) dev.Feature { fn importBackend(comptime backend: std.builtin.CompilerBackend) type { return switch (backend) { .other, .stage1 => unreachable, - .stage2_aarch64 => unreachable, + .stage2_aarch64 => aarch64, .stage2_arm => unreachable, .stage2_c => @import("codegen/c.zig"), .stage2_llvm => @import("codegen/llvm.zig"), @@ -71,6 +73,7 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co .stage2_c, .stage2_wasm, .stage2_x86_64, + .stage2_aarch64, .stage2_x86, .stage2_riscv64, .stage2_sparc64, @@ -82,10 +85,20 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co } } +pub fn wantsLiveness(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) bool { + const zcu = pt.zcu; + const target = &zcu.navFileScope(nav_index).mod.?.resolved_target.result; + return switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) { + else => true, + .stage2_aarch64 => false, + }; +} + /// Every code generation backend has a different MIR representation. However, we want to pass /// MIR from codegen to the linker *regardless* of which backend is in use. So, we use this: a /// union of all MIR types. The active tag is known from the backend in use; see `AnyMir.tag`. pub const AnyMir = union { + aarch64: @import("codegen/aarch64/Mir.zig"), riscv64: @import("arch/riscv64/Mir.zig"), sparc64: @import("arch/sparc64/Mir.zig"), x86_64: @import("arch/x86_64/Mir.zig"), @@ -95,7 +108,6 @@ pub const AnyMir = union { pub inline fn tag(comptime backend: std.builtin.CompilerBackend) []const u8 { return switch (backend) { .stage2_aarch64 => "aarch64", - .stage2_arm => "arm", .stage2_riscv64 => "riscv64", .stage2_sparc64 => "sparc64", .stage2_x86_64 => "x86_64", @@ -110,7 +122,8 @@ pub const AnyMir = union { const backend = target_util.zigBackend(&zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm); switch (backend) { else => unreachable, - inline .stage2_riscv64, + inline .stage2_aarch64, + .stage2_riscv64, .stage2_sparc64, .stage2_x86_64, .stage2_wasm, @@ -131,14 +144,15 @@ pub fn generateFunction( src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, air: *const Air, - liveness: *const Air.Liveness, + liveness: *const ?Air.Liveness, ) CodeGenError!AnyMir { const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result; switch (target_util.zigBackend(target, false)) { else => unreachable, - inline .stage2_riscv64, + inline .stage2_aarch64, + .stage2_riscv64, .stage2_sparc64, .stage2_x86_64, .stage2_wasm, @@ -173,7 +187,8 @@ pub fn emitFunction( const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result; switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) { else => unreachable, - inline .stage2_riscv64, + inline .stage2_aarch64, + .stage2_riscv64, .stage2_sparc64, .stage2_x86_64, => |backend| { @@ -420,7 +435,7 @@ pub fn generateSymbol( const int_tag_ty = ty.intTagType(zcu); try generateSymbol(bin_file, pt, src_loc, try pt.getCoerced(Value.fromInterned(enum_tag.int), int_tag_ty), code, reloc_parent); }, - .float => |float| switch (float.storage) { + .float => |float| storage: switch (float.storage) { .f16 => |f16_val| writeFloat(f16, f16_val, target, endian, try code.addManyAsArray(gpa, 2)), .f32 => |f32_val| writeFloat(f32, f32_val, target, endian, try code.addManyAsArray(gpa, 4)), .f64 => |f64_val| writeFloat(f64, f64_val, target, endian, try code.addManyAsArray(gpa, 8)), @@ -429,7 +444,13 @@ pub fn generateSymbol( const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse return error.Overflow; try code.appendNTimes(gpa, 0, abi_size - 10); }, - .f128 => |f128_val| writeFloat(f128, f128_val, target, endian, try code.addManyAsArray(gpa, 16)), + .f128 => |f128_val| switch (Type.fromInterned(float.ty).floatBits(target)) { + else => unreachable, + 16 => continue :storage .{ .f16 = @floatCast(f128_val) }, + 32 => continue :storage .{ .f32 = @floatCast(f128_val) }, + 64 => continue :storage .{ .f64 = @floatCast(f128_val) }, + 128 => writeFloat(f128, f128_val, target, endian, try code.addManyAsArray(gpa, 16)), + }, }, .ptr => try lowerPtr(bin_file, pt, src_loc, val.toIntern(), code, reloc_parent, 0), .slice => |slice| { @@ -1218,3 +1239,17 @@ pub fn errUnionErrorOffset(payload_ty: Type, zcu: *Zcu) u64 { return 0; } } + +pub fn fieldOffset(ptr_agg_ty: Type, ptr_field_ty: Type, field_index: u32, zcu: *Zcu) u64 { + const agg_ty = ptr_agg_ty.childType(zcu); + return switch (agg_ty.containerLayout(zcu)) { + .auto, .@"extern" => agg_ty.structFieldOffset(field_index, zcu), + .@"packed" => @divExact(@as(u64, ptr_agg_ty.ptrInfo(zcu).packed_offset.bit_offset) + + (if (zcu.typeToPackedStruct(agg_ty)) |loaded_struct| zcu.structPackedFieldBitOffset(loaded_struct, field_index) else 0) - + ptr_field_ty.ptrInfo(zcu).packed_offset.bit_offset, 8), + }; +} + +test { + _ = aarch64; +} diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig new file mode 100644 index 0000000000000000000000000000000000000000..6f95fff58eb611ec6192ea85f5c4abc76f712925 --- /dev/null +++ b/src/codegen/aarch64.zig @@ -0,0 +1,194 @@ +pub const abi = @import("aarch64/abi.zig"); +pub const Assemble = @import("aarch64/Assemble.zig"); +pub const Disassemble = @import("aarch64/Disassemble.zig"); +pub const encoding = @import("aarch64/encoding.zig"); +pub const Mir = @import("aarch64/Mir.zig"); +pub const Select = @import("aarch64/Select.zig"); + +pub fn legalizeFeatures(_: *const std.Target) ?*Air.Legalize.Features { + return null; +} + +pub fn generate( + _: *link.File, + pt: Zcu.PerThread, + _: Zcu.LazySrcLoc, + func_index: InternPool.Index, + air: *const Air, + liveness: *const ?Air.Liveness, +) !Mir { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const func = zcu.funcInfo(func_index); + const func_type = zcu.intern_pool.indexToKey(func.ty).func_type; + assert(liveness.* == null); + + const mod = zcu.navFileScope(func.owner_nav).mod.?; + var isel: Select = .{ + .pt = pt, + .target = &mod.resolved_target.result, + .air = air.*, + .nav_index = zcu.funcInfo(func_index).owner_nav, + + .def_order = .empty, + .blocks = .empty, + .loops = .empty, + .active_loops = .empty, + .loop_live = .{ + .set = .empty, + .list = .empty, + }, + .dom_start = 0, + .dom_len = 0, + .dom = .empty, + + .saved_registers = comptime .initEmpty(), + .instructions = .empty, + .literals = .empty, + .nav_relocs = .empty, + .uav_relocs = .empty, + .global_relocs = .empty, + .literal_relocs = .empty, + + .returns = false, + .va_list = undefined, + .stack_size = 0, + .stack_align = .@"16", + + .live_registers = comptime .initFill(.free), + .live_values = .empty, + .values = .empty, + }; + defer isel.deinit(); + + const air_main_body = air.getMainBody(); + var param_it: Select.CallAbiIterator = .init; + const air_args = for (air_main_body, 0..) |air_inst_index, body_index| { + if (air.instructions.items(.tag)[@intFromEnum(air_inst_index)] != .arg) break air_main_body[0..body_index]; + const param_ty = air.instructions.items(.data)[@intFromEnum(air_inst_index)].arg.ty.toType(); + const param_vi = try param_it.param(&isel, param_ty); + tracking_log.debug("${d} <- %{d}", .{ @intFromEnum(param_vi.?), @intFromEnum(air_inst_index) }); + try isel.live_values.putNoClobber(gpa, air_inst_index, param_vi.?); + } else unreachable; + + const saved_gra_start = if (mod.strip) param_it.ngrn else Select.CallAbiIterator.ngrn_start; + const saved_gra_end = if (func_type.is_var_args) Select.CallAbiIterator.ngrn_end else param_it.ngrn; + const saved_gra_len = @intFromEnum(saved_gra_end) - @intFromEnum(saved_gra_start); + + const saved_vra_start = if (mod.strip) param_it.nsrn else Select.CallAbiIterator.nsrn_start; + const saved_vra_end = if (func_type.is_var_args) Select.CallAbiIterator.nsrn_end else param_it.nsrn; + const saved_vra_len = @intFromEnum(saved_vra_end) - @intFromEnum(saved_vra_start); + + const frame_record = 2; + const named_stack_args: Select.Value.Indirect = .{ + .base = .fp, + .offset = 8 * std.mem.alignForward(u7, frame_record + saved_gra_len, 2), + }; + isel.va_list = .{ + .__stack = named_stack_args.withOffset(param_it.nsaa), + .__gr_top = named_stack_args, + .__vr_top = .{ .base = .fp, .offset = 0 }, + }; + + // translate arg locations from caller-based to callee-based + for (air_args) |air_inst_index| { + assert(air.instructions.items(.tag)[@intFromEnum(air_inst_index)] == .arg); + const arg_vi = isel.live_values.get(air_inst_index).?; + const passed_vi = switch (arg_vi.parent(&isel)) { + .unallocated, .stack_slot => arg_vi, + .value, .constant => unreachable, + .address => |address_vi| address_vi, + }; + switch (passed_vi.parent(&isel)) { + .unallocated => if (!mod.strip) { + var part_it = arg_vi.parts(&isel); + const first_passed_part_vi = part_it.next() orelse passed_vi; + const hint_ra = first_passed_part_vi.hint(&isel).?; + passed_vi.setParent(&isel, .{ .stack_slot = if (hint_ra.isVector()) + isel.va_list.__vr_top.withOffset(@as(i8, -16) * + (@intFromEnum(saved_vra_end) - @intFromEnum(hint_ra))) + else + isel.va_list.__gr_top.withOffset(@as(i8, -8) * + (@intFromEnum(saved_gra_end) - @intFromEnum(hint_ra))) }); + }, + .stack_slot => |stack_slot| { + assert(stack_slot.base == .sp); + passed_vi.setParent(&isel, .{ + .stack_slot = named_stack_args.withOffset(stack_slot.offset), + }); + }, + .address, .value, .constant => unreachable, + } + } + + ret: { + var ret_it: Select.CallAbiIterator = .init; + const ret_vi = try ret_it.ret(&isel, .fromInterned(func_type.return_type)) orelse break :ret; + tracking_log.debug("${d} <- %main", .{@intFromEnum(ret_vi)}); + try isel.live_values.putNoClobber(gpa, Select.Block.main, ret_vi); + } + + assert(!(try isel.blocks.getOrPut(gpa, Select.Block.main)).found_existing); + try isel.analyze(air_main_body); + try isel.finishAnalysis(); + isel.verify(false); + + isel.blocks.values()[0] = .{ + .live_registers = isel.live_registers, + .target_label = @intCast(isel.instructions.items.len), + }; + try isel.body(air_main_body); + if (isel.live_values.fetchRemove(Select.Block.main)) |ret_vi| { + switch (ret_vi.value.parent(&isel)) { + .unallocated, .stack_slot => {}, + .value, .constant => unreachable, + .address => |address_vi| try address_vi.liveIn( + &isel, + address_vi.hint(&isel).?, + comptime &.initFill(.free), + ), + } + ret_vi.value.deref(&isel); + } + isel.verify(true); + + const prologue = isel.instructions.items.len; + const epilogue = try isel.layout( + param_it, + func_type.is_var_args, + saved_gra_len, + saved_vra_len, + mod, + ); + + const instructions = try isel.instructions.toOwnedSlice(gpa); + var mir: Mir = .{ + .prologue = instructions[prologue..epilogue], + .body = instructions[0..prologue], + .epilogue = instructions[epilogue..], + .literals = &.{}, + .nav_relocs = &.{}, + .uav_relocs = &.{}, + .global_relocs = &.{}, + .literal_relocs = &.{}, + }; + errdefer mir.deinit(gpa); + mir.literals = try isel.literals.toOwnedSlice(gpa); + mir.nav_relocs = try isel.nav_relocs.toOwnedSlice(gpa); + mir.uav_relocs = try isel.uav_relocs.toOwnedSlice(gpa); + mir.global_relocs = try isel.global_relocs.toOwnedSlice(gpa); + mir.literal_relocs = try isel.literal_relocs.toOwnedSlice(gpa); + return mir; +} + +test { + _ = Assemble; +} + +const Air = @import("../Air.zig"); +const assert = std.debug.assert; +const InternPool = @import("../InternPool.zig"); +const link = @import("../link.zig"); +const std = @import("std"); +const tracking_log = std.log.scoped(.tracking); +const Zcu = @import("../Zcu.zig"); diff --git a/src/codegen/aarch64/Assemble.zig b/src/codegen/aarch64/Assemble.zig new file mode 100644 index 0000000000000000000000000000000000000000..235c445411991255501f06885eb459e158a5ddbb --- /dev/null +++ b/src/codegen/aarch64/Assemble.zig @@ -0,0 +1,1653 @@ +source: [*:0]const u8, +operands: std.StringHashMapUnmanaged(Operand), + +pub const Operand = union(enum) { + register: aarch64.encoding.Register, +}; + +pub fn nextInstruction(as: *Assemble) !?Instruction { + @setEvalBranchQuota(37_000); + comptime var ct_token_buf: [token_buf_len]u8 = undefined; + var token_buf: [token_buf_len]u8 = undefined; + const original_source = while (true) { + const original_source = as.source; + const source_token = try as.nextToken(&token_buf, .{}); + if (source_token.len == 0) return null; + if (source_token[0] != '\n') break original_source; + }; + log.debug( + \\. + \\========================= + \\= Assembling "{f}" + \\========================= + \\ + , .{std.zig.fmtString(std.mem.span(original_source))}); + inline for (instructions) |instruction| { + next_pattern: { + as.source = original_source; + var symbols: Symbols: { + const symbols = @typeInfo(@TypeOf(instruction.symbols)).@"struct".fields; + var symbol_fields: [symbols.len]std.builtin.Type.StructField = undefined; + for (&symbol_fields, symbols) |*symbol_field, symbol| symbol_field.* = .{ + .name = symbol.name, + .type = zonCast(SymbolSpec, @field(instruction.symbols, symbol.name), .{}).Storage(), + .default_value_ptr = null, + .is_comptime = false, + .alignment = 0, + }; + break :Symbols @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = &symbol_fields, + .decls = &.{}, + .is_tuple = false, + } }); + } = undefined; + comptime var pattern_as: Assemble = .{ .source = instruction.pattern, .operands = undefined }; + inline while (true) { + const pattern_token = comptime pattern_as.nextToken(&ct_token_buf, .{ .placeholders = true }) catch |err| + @compileError(@errorName(err) ++ " while parsing '" ++ instruction.pattern ++ "'"); + const source_token = try as.nextToken(&token_buf, .{ .operands = true }); + log.debug("\"{f}\" -> \"{f}\"", .{ + std.zig.fmtString(pattern_token), + std.zig.fmtString(source_token), + }); + if (pattern_token.len == 0) { + if (source_token.len > 0 and source_token[0] != '\n') break :next_pattern; + const encode = @field(Instruction, @tagName(instruction.encode[0])); + const Encode = @TypeOf(encode); + var args: std.meta.ArgsTuple(Encode) = undefined; + inline for (&args, @typeInfo(Encode).@"fn".params, 1..instruction.encode.len) |*arg, param, encode_index| + arg.* = zonCast(param.type.?, instruction.encode[encode_index], symbols); + return @call(.auto, encode, args); + } else if (pattern_token[0] == '<') { + const symbol_name = comptime pattern_token[1 .. std.mem.indexOfScalarPos(u8, pattern_token, 1, '|') orelse + pattern_token.len - 1]; + const symbol = &@field(symbols, symbol_name); + symbol.* = zonCast(SymbolSpec, @field(instruction.symbols, symbol_name), .{}).parse(source_token) orelse break :next_pattern; + log.debug("{s} = {any}", .{ symbol_name, symbol.* }); + } else if (!std.ascii.eqlIgnoreCase(pattern_token, source_token)) break :next_pattern; + } + } + log.debug("'{s}' not matched...", .{instruction.pattern}); + } + as.source = original_source; + log.debug("Nothing matched!\n", .{}); + return error.InvalidSyntax; +} + +fn zonCast(comptime Result: type, zon_value: anytype, symbols: anytype) Result { + const ZonValue = @TypeOf(zon_value); + const Symbols = @TypeOf(symbols); + switch (@typeInfo(ZonValue)) { + .void, .bool, .int, .float, .pointer, .comptime_float, .comptime_int, .@"enum" => return zon_value, + .@"struct" => |zon_struct| switch (@typeInfo(Result)) { + .@"struct" => |result_struct| { + comptime var used_zon_fields = 0; + var result: Result = undefined; + inline for (result_struct.fields) |result_field| @field(result, result_field.name) = if (@hasField(ZonValue, result_field.name)) result: { + used_zon_fields += 1; + break :result zonCast(@FieldType(Result, result_field.name), @field(zon_value, result_field.name), symbols); + } else result_field.defaultValue() orelse @compileError(std.fmt.comptimePrint("missing zon field '{s}': {} <- {any}", .{ result_field.name, Result, zon_value })); + if (used_zon_fields != zon_struct.fields.len) @compileError(std.fmt.comptimePrint("unused zon field: {} <- {any}", .{ Result, zon_value })); + return result; + }, + .@"union" => { + if (zon_struct.fields.len != 1) @compileError(std.fmt.comptimePrint("{} <- {any}", .{ Result, zon_value })); + const field_name = zon_struct.fields[0].name; + return @unionInit( + Result, + field_name, + zonCast(@FieldType(Result, field_name), @field(zon_value, field_name), symbols), + ); + }, + else => @compileError(std.fmt.comptimePrint("unsupported zon type: {} <- {any}", .{ Result, zon_value })), + }, + .enum_literal => if (@hasField(Symbols, @tagName(zon_value))) { + const symbol = @field(symbols, @tagName(zon_value)); + const Symbol = @TypeOf(symbol); + switch (@typeInfo(Result)) { + .@"enum" => switch (@typeInfo(Symbol)) { + .int => |symbol_int| { + var buf: [ + std.fmt.count("{d}", .{switch (symbol_int.signedness) { + .signed => std.math.minInt(Symbol), + .unsigned => std.math.maxInt(Symbol), + }}) + ]u8 = undefined; + return std.meta.stringToEnum(Result, std.fmt.bufPrint(&buf, "{d}", .{symbol}) catch unreachable).?; + }, + else => return symbol, + }, + else => return symbol, + } + } else return if (@hasDecl(Result, @tagName(zon_value))) @field(Result, @tagName(zon_value)) else zon_value, + else => @compileError(std.fmt.comptimePrint("unsupported zon type: {} <- {any}", .{ Result, zon_value })), + } +} + +const token_buf_len = "v31.b[15]".len; +fn nextToken(as: *Assemble, buf: *[token_buf_len]u8, comptime opts: struct { + operands: bool = false, + placeholders: bool = false, +}) ![]const u8 { + const invalid_syntax: u8 = 1; + while (true) c: switch (as.source[0]) { + 0 => return as.source[0..0], + '\t', '\n' + 1...'\r', ' ' => as.source = as.source[1..], + '\n', '!', '#', ',', '[', ']' => { + defer as.source = as.source[1..]; + return as.source[0..1]; + }, + '%' => if (opts.operands) { + if (as.source[1] != '[') continue :c invalid_syntax; + const name_start: usize = 2; + var index = name_start; + while (switch (as.source[index]) { + else => true, + ':', ']' => false, + }) index += 1; + const operand = as.operands.get(as.source[name_start..index]) orelse continue :c invalid_syntax; + const modifier = modifier: switch (as.source[index]) { + else => unreachable, + ':' => { + index += 1; + const modifier_start = index; + while (switch (as.source[index]) { + else => true, + ']' => false, + }) index += 1; + break :modifier as.source[modifier_start..index]; + }, + ']' => "", + }; + assert(as.source[index] == ']'); + const modified_operand: Operand = if (std.mem.eql(u8, modifier, "")) + operand + else if (std.mem.eql(u8, modifier, "w")) switch (operand) { + .register => |reg| .{ .register = reg.alias.w() }, + } else if (std.mem.eql(u8, modifier, "x")) switch (operand) { + .register => |reg| .{ .register = reg.alias.x() }, + } else if (std.mem.eql(u8, modifier, "b")) switch (operand) { + .register => |reg| .{ .register = reg.alias.b() }, + } else if (std.mem.eql(u8, modifier, "h")) switch (operand) { + .register => |reg| .{ .register = reg.alias.h() }, + } else if (std.mem.eql(u8, modifier, "s")) switch (operand) { + .register => |reg| .{ .register = reg.alias.s() }, + } else if (std.mem.eql(u8, modifier, "d")) switch (operand) { + .register => |reg| .{ .register = reg.alias.d() }, + } else if (std.mem.eql(u8, modifier, "q")) switch (operand) { + .register => |reg| .{ .register = reg.alias.q() }, + } else if (std.mem.eql(u8, modifier, "Z")) switch (operand) { + .register => |reg| .{ .register = reg.alias.z() }, + } else continue :c invalid_syntax; + switch (modified_operand) { + .register => |reg| { + as.source = as.source[index + 1 ..]; + return std.fmt.bufPrint(buf, "{f}", .{reg.fmt()}) catch unreachable; + }, + } + } else continue :c invalid_syntax, + '-', '0'...'9', 'A'...'Z', '_', 'a'...'z' => { + var index: usize = 1; + while (switch (as.source[index]) { + '0'...'9', 'A'...'Z', '_', 'a'...'z' => true, + else => false, + }) index += 1; + defer as.source = as.source[index..]; + return as.source[0..index]; + }, + '<' => if (opts.placeholders) { + var index: usize = 1; + while (switch (as.source[index]) { + 0 => return error.UnterminatedPlaceholder, + '>' => false, + else => true, + }) index += 1; + defer as.source = as.source[index + 1 ..]; + return as.source[0 .. index + 1]; + } else continue :c invalid_syntax, + else => { + if (!@inComptime()) log.debug("invalid token \"{f}\"", .{std.zig.fmtString(std.mem.span(as.source))}); + return error.InvalidSyntax; + }, + }; +} + +const SymbolSpec = union(enum) { + reg: struct { format: aarch64.encoding.Register.Format, allow_sp: bool = false }, + imm: struct { + type: std.builtin.Type.Int, + multiple_of: comptime_int = 1, + max_valid: ?comptime_int = null, + }, + extend: struct { size: aarch64.encoding.Register.IntegerSize }, + shift: struct { allow_ror: bool = true }, + barrier: struct { only_sy: bool = false }, + + fn Storage(comptime spec: SymbolSpec) type { + return switch (spec) { + .reg => aarch64.encoding.Register, + .imm => |imm| @Type(.{ .int = imm.type }), + .extend => Instruction.DataProcessingRegister.AddSubtractExtendedRegister.Option, + .shift => Instruction.DataProcessingRegister.Shift.Op, + .barrier => Instruction.BranchExceptionGeneratingSystem.Barriers.Option, + }; + } + + fn parse(comptime spec: SymbolSpec, token: []const u8) ?Storage(spec) { + const Result = Storage(spec); + switch (spec) { + .reg => |reg_spec| { + var buf: [token_buf_len]u8 = undefined; + const reg = Result.parse(std.ascii.lowerString(&buf, token[0..@min(token.len, buf.len)])) orelse { + log.debug("invalid register: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + if (reg.format.integer != reg_spec.format.integer) { + log.debug("invalid register size: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + if (reg.alias == if (reg_spec.allow_sp) .zr else .sp) { + log.debug("invalid register usage: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + return reg; + }, + .imm => |imm_spec| { + const imm = std.fmt.parseInt(Result, token, 0) catch { + log.debug("invalid immediate: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + if (@rem(imm, imm_spec.multiple_of) != 0) { + log.debug("invalid immediate usage: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + if (imm_spec.max_valid) |max_valid| if (imm > max_valid) { + log.debug("out of range immediate: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + return imm; + }, + .extend => |extend_spec| { + const Option = Instruction.DataProcessingRegister.AddSubtractExtendedRegister.Option; + var buf: [ + max_len: { + var max_len = 0; + for (@typeInfo(Option).@"enum".fields) |field| max_len = @max(max_len, field.name.len); + break :max_len max_len; + } + 1 + ]u8 = undefined; + const extend = std.meta.stringToEnum(Option, std.ascii.lowerString( + &buf, + token[0..@min(token.len, buf.len)], + )) orelse { + log.debug("invalid extend: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + if (extend.sf() != extend_spec.size) { + log.debug("invalid extend: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + return extend; + }, + .shift => |shift_spec| { + const ShiftOp = Instruction.DataProcessingRegister.Shift.Op; + var buf: [ + max_len: { + var max_len = 0; + for (@typeInfo(ShiftOp).@"enum".fields) |field| max_len = @max(max_len, field.name.len); + break :max_len max_len; + } + 1 + ]u8 = undefined; + const shift = std.meta.stringToEnum(ShiftOp, std.ascii.lowerString( + &buf, + token[0..@min(token.len, buf.len)], + )) orelse { + log.debug("invalid shift: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + if (!shift_spec.allow_ror and shift == .ror) { + log.debug("invalid shift usage: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + return shift; + }, + .barrier => |barrier_spec| { + const Option = Instruction.BranchExceptionGeneratingSystem.Barriers.Option; + var buf: [ + max_len: { + var max_len = 0; + for (@typeInfo(Option).@"enum".fields) |field| max_len = @max(max_len, field.name.len); + break :max_len max_len; + } + 1 + ]u8 = undefined; + const barrier = std.meta.stringToEnum(Option, std.ascii.lowerString( + &buf, + token[0..@min(token.len, buf.len)], + )) orelse { + log.debug("invalid barrier: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + }; + if (barrier_spec.only_sy and barrier != .sy) { + log.debug("invalid barrier: \"{f}\"", .{std.zig.fmtString(token)}); + return null; + } + return barrier; + }, + } + } +}; + +test "add sub" { + var as: Assemble = .{ + .source = + \\ add w0, w0, w1 + \\ add w2, w3, w4 + \\ add wsp, w5, w6 + \\ add w7, wsp, w8 + \\ add wsp, wsp, w9 + \\ add w10, w10, wzr + \\ add w11, w12, wzr + \\ add wsp, w13, wzr + \\ add w14, wsp, wzr + \\ add wsp, wsp, wzr + \\ + \\ add x0, x0, x1 + \\ add x2, x3, x4 + \\ add sp, x5, x6 + \\ add x7, sp, x8 + \\ add sp, sp, x9 + \\ add x10, x10, xzr + \\ add x11, x12, xzr + \\ add sp, x13, xzr + \\ add x14, sp, xzr + \\ add sp, sp, xzr + \\ + \\ add w0, w0, w1 + \\ add w2, w3, w4, uxtb #0 + \\ add wsp, w5, w6, uxth #1 + \\ add w7, wsp, w8, uxtw #0 + \\ add wsp, wsp, w9, uxtw #2 + \\ add w10, w10, wzr, uxtw #3 + \\ add w11, w12, wzr, sxtb #4 + \\ add wsp, w13, wzr, sxth #0 + \\ add w14, wsp, wzr, sxtw #1 + \\ add wsp, wsp, wzr, sxtw #2 + \\ + \\ add x0, x0, x1 + \\ add x2, x3, w4, uxtb #0 + \\ add sp, x5, w6, uxth #1 + \\ add x7, sp, w8, uxtw #2 + \\ add sp, sp, x9, uxtx #0 + \\ add x10, x10, xzr, uxtx #3 + \\ add x11, x12, wzr, sxtb #4 + \\ add sp, x13, wzr, sxth #0 + \\ add x14, sp, wzr, sxtw #1 + \\ add sp, sp, xzr, sxtx #2 + \\ + \\ add w0, w0, #0 + \\ add w0, w1, #1, lsl #0 + \\ add wsp, w2, #2, lsl #12 + \\ add w3, wsp, #3, lsl #0 + \\ add wsp, wsp, #4095, lsl #12 + \\ add w0, w1, #0 + \\ add w2, w3, #0, lsl #0 + \\ add w4, wsp, #0 + \\ add w5, wsp, #0, lsl #0 + \\ add wsp, w6, #0 + \\ add wsp, w7, #0, lsl #0 + \\ add wsp, wsp, #0 + \\ add wsp, wsp, #0, lsl #0 + \\ + \\ add x0, x0, #0 + \\ add x0, x1, #1, lsl #0 + \\ add sp, x2, #2, lsl #12 + \\ add x3, sp, #3, lsl #0 + \\ add sp, sp, #4095, lsl #12 + \\ add x0, x1, #0 + \\ add x2, x3, #0, lsl #0 + \\ add x4, sp, #0 + \\ add x5, sp, #0, lsl #0 + \\ add sp, x6, #0 + \\ add sp, x7, #0, lsl #0 + \\ add sp, sp, #0 + \\ add sp, sp, #0, lsl #0 + \\ + \\ add w0, w0, w0 + \\ add w1, w1, w2, lsl #0 + \\ add w3, w4, w5, lsl #1 + \\ add w6, w6, wzr, lsl #31 + \\ add w7, wzr, w8, lsr #0 + \\ add w9, wzr, wzr, lsr #30 + \\ add wzr, w10, w11, lsr #31 + \\ add wzr, w12, wzr, asr #0x0 + \\ add wzr, wzr, w13, asr #0x10 + \\ add wzr, wzr, wzr, asr #0x1f + \\ + \\ add x0, x0, x0 + \\ add x1, x1, x2, lsl #0 + \\ add x3, x4, x5, lsl #1 + \\ add x6, x6, xzr, lsl #63 + \\ add x7, xzr, x8, lsr #0 + \\ add x9, xzr, xzr, lsr #62 + \\ add xzr, x10, x11, lsr #63 + \\ add xzr, x12, xzr, asr #0x0 + \\ add xzr, xzr, x13, asr #0x1F + \\ add xzr, xzr, xzr, asr #0x3f + \\ + \\ sub w0, w0, w1 + \\ sub w2, w3, w4 + \\ sub wsp, w5, w6 + \\ sub w7, wsp, w8 + \\ sub wsp, wsp, w9 + \\ sub w10, w10, wzr + \\ sub w11, w12, wzr + \\ sub wsp, w13, wzr + \\ sub w14, wsp, wzr + \\ sub wsp, wsp, wzr + \\ + \\ sub x0, x0, x1 + \\ sub x2, x3, x4 + \\ sub sp, x5, x6 + \\ sub x7, sp, x8 + \\ sub sp, sp, x9 + \\ sub x10, x10, xzr + \\ sub x11, x12, xzr + \\ sub sp, x13, xzr + \\ sub x14, sp, xzr + \\ sub sp, sp, xzr + \\ + \\ sub w0, w0, w1 + \\ sub w2, w3, w4, uxtb #0 + \\ sub wsp, w5, w6, uxth #1 + \\ sub w7, wsp, w8, uxtw #0 + \\ sub wsp, wsp, w9, uxtw #2 + \\ sub w10, w10, wzr, uxtw #3 + \\ sub w11, w12, wzr, sxtb #4 + \\ sub wsp, w13, wzr, sxth #0 + \\ sub w14, wsp, wzr, sxtw #1 + \\ sub wsp, wsp, wzr, sxtw #2 + \\ + \\ sub x0, x0, x1 + \\ sub x2, x3, w4, uxtb #0 + \\ sub sp, x5, w6, uxth #1 + \\ sub x7, sp, w8, uxtw #2 + \\ sub sp, sp, x9, uxtx #0 + \\ sub x10, x10, xzr, uxtx #3 + \\ sub x11, x12, wzr, sxtb #4 + \\ sub sp, x13, wzr, sxth #0 + \\ sub x14, sp, wzr, sxtw #1 + \\ sub sp, sp, xzr, sxtx #2 + \\ + \\ sub w0, w0, #0 + \\ sub w0, w1, #1, lsl #0 + \\ sub wsp, w2, #2, lsl #12 + \\ sub w3, wsp, #3, lsl #0 + \\ sub wsp, wsp, #4095, lsl #12 + \\ sub w0, w1, #0 + \\ sub w2, w3, #0, lsl #0 + \\ sub w4, wsp, #0 + \\ sub w5, wsp, #0, lsl #0 + \\ sub wsp, w6, #0 + \\ sub wsp, w7, #0, lsl #0 + \\ sub wsp, wsp, #0 + \\ sub wsp, wsp, #0, lsl #0 + \\ + \\ sub x0, x0, #0 + \\ sub x0, x1, #1, lsl #0 + \\ sub sp, x2, #2, lsl #12 + \\ sub x3, sp, #3, lsl #0 + \\ sub sp, sp, #4095, lsl #12 + \\ sub x0, x1, #0 + \\ sub x2, x3, #0, lsl #0 + \\ sub x4, sp, #0 + \\ sub x5, sp, #0, lsl #0 + \\ sub sp, x6, #0 + \\ sub sp, x7, #0, lsl #0 + \\ sub sp, sp, #0 + \\ sub sp, sp, #0, lsl #0 + \\ + \\ sub w0, w0, w0 + \\ sub w1, w1, w2, lsl #0 + \\ sub w3, w4, w5, lsl #1 + \\ sub w6, w6, wzr, lsl #31 + \\ sub w7, wzr, w8, lsr #0 + \\ sub w9, wzr, wzr, lsr #30 + \\ sub wzr, w10, w11, lsr #31 + \\ sub wzr, w12, wzr, asr #0x0 + \\ sub wzr, wzr, w13, asr #0x10 + \\ sub wzr, wzr, wzr, asr #0x1f + \\ + \\ sub x0, x0, x0 + \\ sub x1, x1, x2, lsl #0 + \\ sub x3, x4, x5, lsl #1 + \\ sub x6, x6, xzr, lsl #63 + \\ sub x7, xzr, x8, lsr #0 + \\ sub x9, xzr, xzr, lsr #62 + \\ sub xzr, x10, x11, lsr #63 + \\ sub xzr, x12, xzr, asr #0x0 + \\ sub xzr, xzr, x13, asr #0x1F + \\ sub xzr, xzr, xzr, asr #0x3f + \\ + \\ neg w0, w0 + \\ neg w1, w2, lsl #0 + \\ neg w3, wzr, lsl #7 + \\ neg wzr, w4, lsr #14 + \\ neg wzr, wzr, asr #21 + \\ + \\ neg x0, x0 + \\ neg x1, x2, lsl #0 + \\ neg x3, xzr, lsl #11 + \\ neg xzr, x4, lsr #22 + \\ neg xzr, xzr, asr #33 + , + .operands = .empty, + }; + + try std.testing.expectFmt("add w0, w0, w1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w2, w3, w4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, w5, w6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w7, wsp, w8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, wsp, w9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w10, w10, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w11, w12, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, w13, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w14, wsp, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, wsp, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add x0, x0, x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x2, x3, x4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, x5, x6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x7, sp, x8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, sp, x9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x10, x10, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x11, x12, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, x13, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x14, sp, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, sp, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add w0, w0, w1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w2, w3, w4, uxtb #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, w5, w6, uxth #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w7, wsp, w8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, wsp, w9, uxtw #2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w10, w10, wzr, uxtw #3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w11, w12, wzr, sxtb #4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, w13, wzr, sxth #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w14, wsp, wzr, sxtw #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, wsp, wzr, sxtw #2", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add x0, x0, x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x2, x3, w4, uxtb #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, x5, w6, uxth #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x7, sp, w8, uxtw #2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, sp, x9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x10, x10, xzr, uxtx #3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x11, x12, wzr, sxtb #4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, x13, wzr, sxth #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x14, sp, wzr, sxtw #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, sp, xzr, sxtx #2", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add w0, w0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w0, w1, #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, w2, #0x2, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w3, wsp, #0x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wsp, wsp, #0xfff, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w0, w1, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w2, w3, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w4, wsp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w5, wsp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, w6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, w7", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, wsp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, wsp", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add x0, x0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x0, x1, #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, x2, #0x2, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x3, sp, #0x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add sp, sp, #0xfff, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x0, x1, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x2, x3, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x4, sp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x5, sp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, x6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, x7", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, sp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, sp", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w7, wzr, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add w9, wzr, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wzr, w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wzr, w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wzr, wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add wzr, wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("add x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x7, xzr, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add x9, xzr, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add xzr, x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add xzr, x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add xzr, xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("add xzr, xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub w0, w0, w1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w2, w3, w4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w5, w6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w7, wsp, w8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, w9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w10, w10, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w11, w12, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w13, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w14, wsp, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub x0, x0, x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x2, x3, x4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x5, x6", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x7, sp, x8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, x9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x10, x10, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x11, x12, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x13, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x14, sp, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub w0, w0, w1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w2, w3, w4, uxtb #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w5, w6, uxth #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w7, wsp, w8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, w9, uxtw #2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w10, w10, wzr, uxtw #3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w11, w12, wzr, sxtb #4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w13, wzr, sxth #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w14, wsp, wzr, sxtw #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, wzr, sxtw #2", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub x0, x0, x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x2, x3, w4, uxtb #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x5, w6, uxth #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x7, sp, w8, uxtw #2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, x9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x10, x10, xzr, uxtx #3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x11, x12, wzr, sxtb #4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x13, wzr, sxth #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x14, sp, wzr, sxtw #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, xzr, sxtx #2", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub w0, w0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w0, w1, #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w2, #0x2, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w3, wsp, #0x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, #0xfff, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w0, w1, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w2, w3, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w4, wsp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w5, wsp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w6, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, w7, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wsp, wsp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub x0, x0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x0, x1, #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x2, #0x2, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x3, sp, #0x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, #0xfff, lsl #12", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x0, x1, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x2, x3, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x4, sp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x5, sp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x6, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, x7, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub sp, sp, #0x0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg w7, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg w9, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wzr, w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub wzr, w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sub x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg x7, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg x9, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub xzr, x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sub xzr, x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("neg w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg w3, wzr, lsl #7", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg wzr, w4, lsr #14", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg wzr, wzr, asr #21", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("neg x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg x3, xzr, lsl #11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg xzr, x4, lsr #22", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("neg xzr, xzr, asr #33", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "bitfield" { + var as: Assemble = .{ + .source = + \\sbfm w0, w0, #0, #31 + \\sbfm w0, w0, #31, #0 + \\ + \\sbfm x0, x0, #0, #63 + \\sbfm x0, x0, #63, #0 + \\ + \\bfm w0, w0, #0, #31 + \\bfm w0, w0, #31, #0 + \\ + \\bfm x0, x0, #0, #63 + \\bfm x0, x0, #63, #0 + \\ + \\ubfm w0, w0, #0, #31 + \\ubfm w0, w0, #31, #0 + \\ + \\ubfm x0, x0, #0, #63 + \\ubfm x0, x0, #63, #0 + , + .operands = .empty, + }; + + try std.testing.expectFmt("sbfm w0, w0, #0, #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sbfm w0, w0, #31, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("sbfm x0, x0, #0, #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sbfm x0, x0, #63, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("bfm w0, w0, #0, #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("bfm w0, w0, #31, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("bfm x0, x0, #0, #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("bfm x0, x0, #63, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ubfm w0, w0, #0, #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ubfm w0, w0, #31, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ubfm x0, x0, #0, #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ubfm x0, x0, #63, #0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "branch register" { + var as: Assemble = .{ + .source = + \\ret + \\br x30 + \\blr x30 + \\ret x30 + \\br x29 + \\blr x29 + \\ret x29 + \\br x2 + \\blr x1 + \\ret x0 + , + .operands = .empty, + }; + + try std.testing.expectFmt("ret", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("br x30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("blr x30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ret", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("br x29", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("blr x29", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ret x29", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("br x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("blr x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ret x0", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "exception generating" { + var as: Assemble = .{ + .source = + \\SVC #0 + \\HVC #0x1 + \\SMC #0o15 + \\BRK #42 + \\HLT #0x42 + \\TCANCEL #123 + \\DCPS1 #1234 + \\DCPS2 #12345 + \\DCPS3 #65535 + \\DCPS3 #0x0 + \\DCPS2 #0 + \\DCPS1 + , + .operands = .empty, + }; + + try std.testing.expectFmt("svc #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("hvc #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("smc #0xd", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("brk #0x2a", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("hlt #0x42", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tcancel #0x7b", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps1 #0x4d2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps2 #0x3039", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps3 #0xffff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("dcps1", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "extract" { + var as: Assemble = .{ + .source = + \\extr W0, W1, W2, #0 + \\extr W3, W3, W4, #1 + \\extr W5, W5, W5, #31 + \\ + \\extr X0, X1, X2, #0 + \\extr X3, X3, X4, #1 + \\extr X5, X5, X5, #63 + , + .operands = .empty, + }; + + try std.testing.expectFmt("extr w0, w1, w2, #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("extr w3, w3, w4, #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("extr w5, w5, w5, #31", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("extr x0, x1, x2, #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("extr x3, x3, x4, #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("extr x5, x5, x5, #63", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "hints" { + var as: Assemble = .{ + .source = + \\NOP + \\hint #0 + \\YiElD + \\Hint #0x1 + \\WfE + \\hInt #02 + \\wFi + \\hiNt #0b11 + \\sEv + \\hinT #4 + \\sevl + \\HINT #0b101 + \\hint #0x7F + , + .operands = .empty, + }; + + try std.testing.expectFmt("nop", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("nop", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("yield", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("yield", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("wfe", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("wfe", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("wfi", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("wfi", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sev", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sev", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sevl", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("sevl", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("hint #0x7f", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "load store" { + var as: Assemble = .{ + .source = + \\ LDP w0, w1, [x2], #-256 + \\ LDP w3, w4, [x5], #0 + \\ LDP w6, w7, [sp], #252 + \\ LDP w0, w1, [x2, #-0x100]! + \\ LDP w3, w4, [x5, #0]! + \\ LDP w6, w7, [sp, #0xfc]! + \\ LDP w0, w1, [x2, #-256] + \\ LDP w3, w4, [x5] + \\ LDP w6, w7, [x8, #0] + \\ LDP w9, w10, [sp, #252] + \\ + \\ LDP x0, x1, [x2], #-512 + \\ LDP x3, x4, [x5], #0 + \\ LDP x6, x7, [sp], #504 + \\ LDP x0, x1, [x2, #-0x200]! + \\ LDP x3, x4, [x5, #0]! + \\ LDP x6, x7, [sp, #0x1f8]! + \\ LDP x0, x1, [x2, #-512] + \\ LDP x3, x4, [x5] + \\ LDP x6, x7, [x8, #0] + \\ LDP x9, x10, [sp, #504] + \\ + \\ LDR w0, [x1], #-256 + \\ LDR w2, [x3], #0 + \\ LDR w4, [sp], #255 + \\ LDR w0, [x1, #-0x100]! + \\ LDR w2, [x3, #0]! + \\ LDR w4, [sp, #0xff]! + \\ LDR w0, [x1, #0] + \\ LDR w2, [x3] + \\ LDR w4, [sp, #16380] + \\ + \\ LDR x0, [x1], #-256 + \\ LDR x2, [x3], #0 + \\ LDR x4, [sp], #255 + \\ LDR x0, [x1, #-0x100]! + \\ LDR x2, [x3, #0]! + \\ LDR x4, [sp, #0xff]! + \\ LDR x0, [x1, #0] + \\ LDR x2, [x3] + \\ LDR x4, [sp, #32760] + \\ + \\ STP w0, w1, [x2], #-256 + \\ STP w3, w4, [x5], #0 + \\ STP w6, w7, [sp], #252 + \\ STP w0, w1, [x2, #-0x100]! + \\ STP w3, w4, [x5, #0]! + \\ STP w6, w7, [sp, #0xfc]! + \\ STP w0, w1, [x2, #-256] + \\ STP w3, w4, [x5] + \\ STP w6, w7, [x8, #0] + \\ STP w9, w10, [sp, #252] + \\ + \\ STP x0, x1, [x2], #-512 + \\ STP x3, x4, [x5], #0 + \\ STP x6, x7, [sp], #504 + \\ STP x0, x1, [x2, #-0x200]! + \\ STP x3, x4, [x5, #0]! + \\ STP x6, x7, [sp, #0x1f8]! + \\ STP x0, x1, [x2, #-512] + \\ STP x3, x4, [x5] + \\ STP x6, x7, [x8, #0] + \\ STP x9, x10, [sp, #504] + \\ + \\ STR w0, [x1], #-256 + \\ STR w2, [x3], #0 + \\ STR w4, [sp], #255 + \\ STR w0, [x1, #-0x100]! + \\ STR w2, [x3, #0]! + \\ STR w4, [sp, #0xff]! + \\ STR w0, [x1, #0] + \\ STR w2, [x3] + \\ STR w4, [sp, #16380] + \\ + \\ STR x0, [x1], #-256 + \\ STR x2, [x3], #0 + \\ STR x4, [sp], #255 + \\ STR x0, [x1, #-0x100]! + \\ STR x2, [x3, #0]! + \\ STR x4, [sp, #0xff]! + \\ STR x0, [x1, #0] + \\ STR x2, [x3] + \\ STR x4, [sp, #32760] + , + .operands = .empty, + }; + + try std.testing.expectFmt("ldp w0, w1, [x2], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w3, w4, [x5], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w6, w7, [sp], #0xfc", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w0, w1, [x2, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w3, w4, [x5, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w6, w7, [sp, #0xfc]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w0, w1, [x2, #-0x100]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w3, w4, [x5]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w6, w7, [x8]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp w9, w10, [sp, #0xfc]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ldp x0, x1, [x2], #-0x200", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x3, x4, [x5], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x6, x7, [sp], #0x1f8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x0, x1, [x2, #-0x200]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x3, x4, [x5, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x6, x7, [sp, #0x1f8]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x0, x1, [x2, #-0x200]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x3, x4, [x5]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x6, x7, [x8]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldp x9, x10, [sp, #0x1f8]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ldr w0, [x1], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w2, [x3], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w4, [sp], #0xff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w0, [x1, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w2, [x3, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w4, [sp, #0xff]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w0, [x1]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w2, [x3]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr w4, [sp, #0x3ffc]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ldr x0, [x1], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x2, [x3], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x4, [sp], #0xff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x0, [x1, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x2, [x3, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x4, [sp, #0xff]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x0, [x1]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x2, [x3]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ldr x4, [sp, #0x7ff8]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("stp w0, w1, [x2], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w3, w4, [x5], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w6, w7, [sp], #0xfc", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w0, w1, [x2, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w3, w4, [x5, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w6, w7, [sp, #0xfc]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w0, w1, [x2, #-0x100]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w3, w4, [x5]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w6, w7, [x8]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp w9, w10, [sp, #0xfc]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("stp x0, x1, [x2], #-0x200", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x3, x4, [x5], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x6, x7, [sp], #0x1f8", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x0, x1, [x2, #-0x200]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x3, x4, [x5, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x6, x7, [sp, #0x1f8]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x0, x1, [x2, #-0x200]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x3, x4, [x5]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x6, x7, [x8]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("stp x9, x10, [sp, #0x1f8]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("str w0, [x1], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w2, [x3], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w4, [sp], #0xff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w0, [x1, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w2, [x3, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w4, [sp, #0xff]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w0, [x1]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w2, [x3]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str w4, [sp, #0x3ffc]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("str x0, [x1], #-0x100", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x2, [x3], #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x4, [sp], #0xff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x0, [x1, #-0x100]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x2, [x3, #0x0]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x4, [sp, #0xff]!", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x0, [x1]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x2, [x3]", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("str x4, [sp, #0x7ff8]", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "logical" { + var as: Assemble = .{ + .source = + \\ and w0, w0, w0 + \\ and w1, w1, w2, lsl #0 + \\ and w3, w4, w5, lsl #1 + \\ and w6, w6, wzr, lsl #31 + \\ and w7, wzr, w8, lsr #0 + \\ and w9, wzr, wzr, lsr #30 + \\ and wzr, w10, w11, lsr #31 + \\ and wzr, w12, wzr, asr #0x0 + \\ and wzr, wzr, w13, asr #0x10 + \\ and wzr, wzr, wzr, asr #0x1f + \\ and w0, w0, wzr + \\ and w1, w2, wzr, lsl #0 + \\ and w3, wzr, w3 + \\ and w4, wzr, w5, lsl #0 + \\ and w6, wzr, wzr + \\ and w7, wzr, wzr, lsl #0 + \\ and wzr, w8, wzr + \\ and wzr, w9, wzr, lsl #0 + \\ and wzr, wzr, w10 + \\ and wzr, wzr, w11, lsl #0 + \\ and wzr, wzr, wzr + \\ and wzr, wzr, wzr, lsl #0 + \\ + \\ and x0, x0, x0 + \\ and x1, x1, x2, lsl #0 + \\ and x3, x4, x5, lsl #1 + \\ and x6, x6, xzr, lsl #63 + \\ and x7, xzr, x8, lsr #0 + \\ and x9, xzr, xzr, lsr #62 + \\ and xzr, x10, x11, lsr #63 + \\ and xzr, x12, xzr, asr #0x0 + \\ and xzr, xzr, x13, asr #0x1F + \\ and xzr, xzr, xzr, asr #0x3f + \\ and x0, x0, xzr + \\ and x1, x2, xzr, lsl #0 + \\ and x3, xzr, x3 + \\ and x4, xzr, x5, lsl #0 + \\ and x6, xzr, xzr + \\ and x7, xzr, xzr, lsl #0 + \\ and xzr, x8, xzr + \\ and xzr, x9, xzr, lsl #0 + \\ and xzr, xzr, x10 + \\ and xzr, xzr, x11, lsl #0 + \\ and xzr, xzr, xzr + \\ and xzr, xzr, xzr, lsl #0 + \\ + \\ orr w0, w0, w0 + \\ orr w1, w1, w2, lsl #0 + \\ orr w3, w4, w5, lsl #1 + \\ orr w6, w6, wzr, lsl #31 + \\ orr w7, wzr, w8, lsr #0 + \\ orr w9, wzr, wzr, lsr #30 + \\ orr wzr, w10, w11, lsr #31 + \\ orr wzr, w12, wzr, asr #0x0 + \\ orr wzr, wzr, w13, asr #0x10 + \\ orr wzr, wzr, wzr, asr #0x1f + \\ orr w0, w0, wzr + \\ orr w1, w2, wzr, lsl #0 + \\ orr w3, wzr, w3 + \\ orr w4, wzr, w5, lsl #0 + \\ orr w6, wzr, wzr + \\ orr w7, wzr, wzr, lsl #0 + \\ orr wzr, w8, wzr + \\ orr wzr, w9, wzr, lsl #0 + \\ orr wzr, wzr, w10 + \\ orr wzr, wzr, w11, lsl #0 + \\ orr wzr, wzr, wzr + \\ orr wzr, wzr, wzr, lsl #0 + \\ + \\ orr x0, x0, x0 + \\ orr x1, x1, x2, lsl #0 + \\ orr x3, x4, x5, lsl #1 + \\ orr x6, x6, xzr, lsl #63 + \\ orr x7, xzr, x8, lsr #0 + \\ orr x9, xzr, xzr, lsr #62 + \\ orr xzr, x10, x11, lsr #63 + \\ orr xzr, x12, xzr, asr #0x0 + \\ orr xzr, xzr, x13, asr #0x1F + \\ orr xzr, xzr, xzr, asr #0x3f + \\ orr x0, x0, xzr + \\ orr x1, x2, xzr, lsl #0 + \\ orr x3, xzr, x3 + \\ orr x4, xzr, x5, lsl #0 + \\ orr x6, xzr, xzr + \\ orr x7, xzr, xzr, lsl #0 + \\ orr xzr, x8, xzr + \\ orr xzr, x9, xzr, lsl #0 + \\ orr xzr, xzr, x10 + \\ orr xzr, xzr, x11, lsl #0 + \\ orr xzr, xzr, xzr + \\ orr xzr, xzr, xzr, lsl #0 + \\ + \\ eor w0, w0, w0 + \\ eor w1, w1, w2, lsl #0 + \\ eor w3, w4, w5, lsl #1 + \\ eor w6, w6, wzr, lsl #31 + \\ eor w7, wzr, w8, lsr #0 + \\ eor w9, wzr, wzr, lsr #30 + \\ eor wzr, w10, w11, lsr #31 + \\ eor wzr, w12, wzr, asr #0x0 + \\ eor wzr, wzr, w13, asr #0x10 + \\ eor wzr, wzr, wzr, asr #0x1f + \\ eor w0, w0, wzr + \\ eor w1, w2, wzr, lsl #0 + \\ eor w3, wzr, w3 + \\ eor w4, wzr, w5, lsl #0 + \\ eor w6, wzr, wzr + \\ eor w7, wzr, wzr, lsl #0 + \\ eor wzr, w8, wzr + \\ eor wzr, w9, wzr, lsl #0 + \\ eor wzr, wzr, w10 + \\ eor wzr, wzr, w11, lsl #0 + \\ eor wzr, wzr, wzr + \\ eor wzr, wzr, wzr, lsl #0 + \\ + \\ eor x0, x0, x0 + \\ eor x1, x1, x2, lsl #0 + \\ eor x3, x4, x5, lsl #1 + \\ eor x6, x6, xzr, lsl #63 + \\ eor x7, xzr, x8, lsr #0 + \\ eor x9, xzr, xzr, lsr #62 + \\ eor xzr, x10, x11, lsr #63 + \\ eor xzr, x12, xzr, asr #0x0 + \\ eor xzr, xzr, x13, asr #0x1F + \\ eor xzr, xzr, xzr, asr #0x3f + \\ eor x0, x0, xzr + \\ eor x1, x2, xzr, lsl #0 + \\ eor x3, xzr, x3 + \\ eor x4, xzr, x5, lsl #0 + \\ eor x6, xzr, xzr + \\ eor x7, xzr, xzr, lsl #0 + \\ eor xzr, x8, xzr + \\ eor xzr, x9, xzr, lsl #0 + \\ eor xzr, xzr, x10 + \\ eor xzr, xzr, x11, lsl #0 + \\ eor xzr, xzr, xzr + \\ eor xzr, xzr, xzr, lsl #0 + \\ + \\ ands w0, w0, w0 + \\ ands w1, w1, w2, lsl #0 + \\ ands w3, w4, w5, lsl #1 + \\ ands w6, w6, wzr, lsl #31 + \\ ands w7, wzr, w8, lsr #0 + \\ ands w9, wzr, wzr, lsr #30 + \\ ands wzr, w10, w11, lsr #31 + \\ ands wzr, w12, wzr, asr #0x0 + \\ ands wzr, wzr, w13, asr #0x10 + \\ ands wzr, wzr, wzr, asr #0x1f + \\ ands w0, w0, wzr + \\ ands w1, w2, wzr, lsl #0 + \\ ands w3, wzr, w3 + \\ ands w4, wzr, w5, lsl #0 + \\ ands w6, wzr, wzr + \\ ands w7, wzr, wzr, lsl #0 + \\ ands wzr, w8, wzr + \\ ands wzr, w9, wzr, lsl #0 + \\ ands wzr, wzr, w10 + \\ ands wzr, wzr, w11, lsl #0 + \\ ands wzr, wzr, wzr + \\ ands wzr, wzr, wzr, lsl #0 + \\ + \\ ands x0, x0, x0 + \\ ands x1, x1, x2, lsl #0 + \\ ands x3, x4, x5, lsl #1 + \\ ands x6, x6, xzr, lsl #63 + \\ ands x7, xzr, x8, lsr #0 + \\ ands x9, xzr, xzr, lsr #62 + \\ ands xzr, x10, x11, lsr #63 + \\ ands xzr, x12, xzr, asr #0x0 + \\ ands xzr, xzr, x13, asr #0x1F + \\ ands xzr, xzr, xzr, asr #0x3f + \\ ands x0, x0, xzr + \\ ands x1, x2, xzr, lsl #0 + \\ ands x3, xzr, x3 + \\ ands x4, xzr, x5, lsl #0 + \\ ands x6, xzr, xzr + \\ ands x7, xzr, xzr, lsl #0 + \\ ands xzr, x8, xzr + \\ ands xzr, x9, xzr, lsl #0 + \\ ands xzr, xzr, x10 + \\ ands xzr, xzr, x11, lsl #0 + \\ ands xzr, xzr, xzr + \\ ands xzr, xzr, xzr, lsl #0 + , + .operands = .empty, + }; + + try std.testing.expectFmt("and w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w7, wzr, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w9, wzr, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w0, w0, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w1, w2, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w3, wzr, w3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w4, wzr, w5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w6, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and w7, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, w8, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, w9, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, w10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, w11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and wzr, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("and x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x7, xzr, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x9, xzr, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x0, x0, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x1, x2, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x3, xzr, x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x4, xzr, x5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x6, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and x7, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, x8, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, x9, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, x10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, x11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("and xzr, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("orr w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w7, wzr, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w9, wzr, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w0, w0, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr w1, w2, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w3, w3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w4, w5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w6, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w7, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, w8, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr wzr, w9, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, w10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, w11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("orr x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x7, xzr, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x9, xzr, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x0, x0, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr x1, x2, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x3, x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x4, x5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x6, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x7, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, x8, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("orr xzr, x9, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, x10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, x11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("eor w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w7, wzr, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w9, wzr, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w0, w0, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w1, w2, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w3, wzr, w3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w4, wzr, w5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w6, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor w7, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, w8, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, w9, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, w10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, w11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor wzr, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("eor x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x7, xzr, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x9, xzr, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x0, x0, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x1, x2, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x3, xzr, x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x4, xzr, x5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x6, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor x7, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, x8, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, x9, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, x10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, x11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("eor xzr, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ands w0, w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w1, w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w3, w4, w5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w6, w6, wzr, lsl #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w7, wzr, w8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w9, wzr, wzr, lsr #30", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst w10, w11, lsr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst w12, wzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, w13, asr #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, wzr, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w0, w0, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w1, w2, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w3, wzr, w3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w4, wzr, w5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w6, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands w7, wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst w8, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst w9, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, w10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, w11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("ands x0, x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x1, x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x3, x4, x5, lsl #1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x6, x6, xzr, lsl #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x7, xzr, x8, lsr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x9, xzr, xzr, lsr #62", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst x10, x11, lsr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst x12, xzr, asr #0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, x13, asr #31", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, xzr, asr #63", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x0, x0, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x1, x2, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x3, xzr, x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x4, xzr, x5", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x6, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("ands x7, xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst x8, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst x9, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, x10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, x11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("tst xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "mov" { + var as: Assemble = .{ + .source = + \\MOV W0, #0 + \\MOV WZR, #0xffff + \\ + \\MOV X0, #0 + \\MOV XZR, #0xffff + \\ + \\MOV W0, WSP + \\MOV WSP, W1 + \\MOV WSP, WSP + \\MOV X0, SP + \\MOV SP, X1 + \\MOV SP, SP + \\ + \\MOV W0, W0 + \\MOV W1, W2 + \\MOV W3, WZR + \\MOV WZR, W4 + \\MOV WZR, WZR + \\MOV X0, X0 + \\MOV X1, X2 + \\MOV X3, XZR + \\MOV XZR, X4 + \\MOV XZR, XZR + \\ + \\MOVK W0, #0 + \\MOVK W1, #1, lsl #0 + \\MOVK W2, #2, lsl #16 + \\MOVK X3, #3 + \\MOVK X4, #4, lsl #0x00 + \\MOVK X5, #5, lsl #0x10 + \\MOVK X6, #6, lsl #0x20 + \\MOVK X7, #7, lsl #0x30 + \\ + \\MOVN W0, #8 + \\MOVN W1, #9, lsl #0 + \\MOVN W2, #10, lsl #16 + \\MOVN X3, #11 + \\MOVN X4, #12, lsl #0x00 + \\MOVN X5, #13, lsl #0x10 + \\MOVN X6, #14, lsl #0x20 + \\MOVN X7, #15, lsl #0x30 + \\ + \\MOVN WZR, #0, lsl #0 + \\MOVN WZR, #0, lsl #16 + \\MOVN XZR, #0, lsl #0 + \\MOVN XZR, #0, lsl #16 + \\MOVN XZR, #0, lsl #32 + \\MOVN XZR, #0, lsl #48 + \\ + \\MOVN WZR, #0xffff, lsl #0 + \\MOVN WZR, #0xffff, lsl #16 + \\MOVN XZR, #0xffff, lsl #0 + \\MOVN XZR, #0xffff, lsl #16 + \\MOVN XZR, #0xffff, lsl #32 + \\MOVN XZR, #0xffff, lsl #48 + \\ + \\MOVZ W0, #16 + \\MOVZ W1, #17, lsl #0 + \\MOVZ W2, #18, lsl #16 + \\MOVZ X3, #19 + \\MOVZ X4, #20, lsl #0x00 + \\MOVZ X5, #21, lsl #0x10 + \\MOVZ X6, #22, lsl #0x20 + \\MOVZ X7, #23, lsl #0x30 + \\ + \\MOVZ WZR, #0, lsl #0 + \\MOVZ WZR, #0, lsl #16 + \\MOVZ XZR, #0, lsl #0 + \\MOVZ XZR, #0, lsl #16 + \\MOVZ XZR, #0, lsl #32 + \\MOVZ XZR, #0, lsl #48 + \\ + \\MOVZ WZR, #0xffff, lsl #0 + \\MOVZ WZR, #0xffff, lsl #16 + \\MOVZ XZR, #0xffff, lsl #0 + \\MOVZ XZR, #0xffff, lsl #16 + \\MOVZ XZR, #0xffff, lsl #32 + \\MOVZ XZR, #0xffff, lsl #48 + , + .operands = .empty, + }; + + try std.testing.expectFmt("mov w0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, #0xffff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0xffff", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov w0, wsp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, w1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wsp, wsp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x0, sp", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov sp, sp", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov w0, w0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w1, w2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w3, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, w4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, wzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x0, x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x1, x2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x3, xzr", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, x4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, xzr", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("movk w0, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk w1, #0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk w2, #0x2, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk x3, #0x3", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk x4, #0x4", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk x5, #0x5, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk x6, #0x6, lsl #32", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movk x7, #0x7, lsl #48", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov w0, #-0x9", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w1, #-0xa", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w2, #-0xa0001", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x3, #-0xc", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x4, #-0xd", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x5, #-0xd0001", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x6, #-0xe00000001", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x7, #-0xf000000000001", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov wzr, #-0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movn wzr, #0x0, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #-0x1", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movn xzr, #0x0, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movn xzr, #0x0, lsl #32", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movn xzr, #0x0, lsl #48", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("movn wzr, #0xffff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movn wzr, #0xffff, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #-0x10000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #-0xffff0001", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #-0xffff00000001", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0xffffffffffff", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov w0, #0x10", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w1, #0x11", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov w2, #0x120000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x3, #0x13", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x4, #0x14", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x5, #0x150000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x6, #0x1600000000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov x7, #0x17000000000000", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov wzr, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movz wzr, #0x0, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movz xzr, #0x0, lsl #16", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movz xzr, #0x0, lsl #32", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("movz xzr, #0x0, lsl #48", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expectFmt("mov wzr, #0xffff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov wzr, #-0x10000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0xffff", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0xffff0000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #0xffff00000000", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("mov xzr, #-0x1000000000000", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} +test "reserved" { + var as: Assemble = .{ + .source = "\n\nudf #0x0\n\t\n\tudf\t#01234\n \nudf#65535", + .operands = .empty, + }; + + try std.testing.expectFmt("udf #0x0", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("udf #0x4d2", "{f}", .{(try as.nextInstruction()).?}); + try std.testing.expectFmt("udf #0xffff", "{f}", .{(try as.nextInstruction()).?}); + + try std.testing.expect(null == try as.nextInstruction()); +} + +const aarch64 = @import("../aarch64.zig"); +const Assemble = @This(); +const assert = std.debug.assert; +const Instruction = aarch64.encoding.Instruction; +const instructions = @import("instructions.zon"); +const std = @import("std"); +const log = std.log.scoped(.@"asm"); diff --git a/src/codegen/aarch64/Disassemble.zig b/src/codegen/aarch64/Disassemble.zig new file mode 100644 index 0000000000000000000000000000000000000000..e3b4df93d43cbda01586740cac50409541696915 --- /dev/null +++ b/src/codegen/aarch64/Disassemble.zig @@ -0,0 +1,905 @@ +case: Case = .lower, +mnemonic_operands_separator: []const u8 = " ", +operands_separator: []const u8 = ", ", +enable_aliases: bool = true, + +pub const Case = enum { lower, upper }; + +pub fn printInstruction(dis: Disassemble, inst: Instruction, writer: *std.Io.Writer) std.Io.Writer.Error!void { + unallocated: switch (inst.decode()) { + .unallocated => break :unallocated, + .reserved => |reserved| switch (reserved.decode()) { + .unallocated => break :unallocated, + .udf => |udf| return writer.print("{f}{s}#0x{x}", .{ + fmtCase(.udf, dis.case), + dis.mnemonic_operands_separator, + udf.imm16, + }), + }, + .sme => {}, + .sve => {}, + .data_processing_immediate => |data_processing_immediate| switch (data_processing_immediate.decode()) { + .unallocated => break :unallocated, + .pc_relative_addressing => |pc_relative_addressing| { + const group = pc_relative_addressing.group; + const imm = (@as(i33, group.immhi) << 2 | @as(i33, group.immlo) << 0) + @as(i33, switch (group.op) { + .adr => Instruction.size, + .adrp => 0, + }); + return writer.print("{f}{s}{f}{s}.{c}0x{x}", .{ + fmtCase(group.op, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(.doubleword, .{}).fmtCase(dis.case), + dis.operands_separator, + @as(u8, if (imm < 0) '-' else '+'), + switch (group.op) { + .adr => @abs(imm), + .adrp => @abs(imm) << 12, + }, + }); + }, + .add_subtract_immediate => |add_subtract_immediate| { + const group = add_subtract_immediate.group; + const op = group.op; + const S = group.S; + const sf = group.sf; + const sh = group.sh; + const imm12 = group.imm12; + const Rn = group.Rn.decodeInteger(sf, .{ .sp = true }); + const Rd = group.Rd.decodeInteger(sf, .{ .sp = !S }); + const elide_shift = sh == .@"0"; + if (dis.enable_aliases and op == .add and S == false and elide_shift and imm12 == 0 and + (Rn.alias == .sp or Rd.alias == .sp)) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(.mov, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + }) else try writer.print("{f}{s}{s}{f}{s}{f}{s}#0x{x}", .{ + fmtCase(op, dis.case), + if (S) "s" else "", + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + imm12, + }); + return if (!elide_shift) writer.print("{s}{f} #{s}", .{ + dis.operands_separator, + fmtCase(.lsl, dis.case), + @tagName(sh), + }); + }, + .add_subtract_immediate_with_tags => {}, + .logical_immediate => |logical_immediate| { + const decoded = logical_immediate.decode(); + if (decoded == .unallocated) break :unallocated; + const group = logical_immediate.group; + const sf = group.sf; + const decoded_imm = group.imm.decodeImmediate(sf); + const imm = switch (sf) { + .word => @as(i32, @bitCast(@as(u32, @intCast(decoded_imm)))), + .doubleword => @as(i64, @bitCast(decoded_imm)), + }; + const Rn = group.Rn.decodeInteger(sf, .{}); + const Rd = group.Rd.decodeInteger(sf, .{ .sp = decoded != .ands }); + return if (dis.enable_aliases and decoded == .orr and Rn.alias == .zr and !group.imm.moveWidePreferred(sf)) writer.print("{f}{s}{f}{s}#{s}0x{x}", .{ + fmtCase(.mov, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + if (imm < 0) "-" else "", + @abs(imm), + }) else if (dis.enable_aliases and decoded == .ands and Rd.alias == .zr) writer.print("{f}{s}{f}{s}#{s}0x{x}", .{ + fmtCase(.tst, dis.case), + dis.mnemonic_operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + if (imm < 0) "-" else "", + @abs(imm), + }) else writer.print("{f}{s}{f}{s}{f}{s}#0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + decoded_imm, + }); + }, + .move_wide_immediate => |move_wide_immediate| { + const decoded = move_wide_immediate.decode(); + if (decoded == .unallocated) break :unallocated; + const group = move_wide_immediate.group; + const sf = group.sf; + const hw = group.hw; + const imm16 = group.imm16; + const Rd = group.Rd.decodeInteger(sf, .{}); + const elide_shift = hw == .@"0"; + if (dis.enable_aliases and switch (decoded) { + .unallocated => unreachable, + .movz => elide_shift or group.imm16 != 0, + .movn => (elide_shift or group.imm16 != 0) and switch (sf) { + .word => group.imm16 != std.math.maxInt(u16), + .doubleword => true, + }, + .movk => false, + }) { + const decoded_imm = switch (sf) { + .word => @as(i32, @bitCast(@as(u32, group.imm16) << @intCast(hw.int()))), + .doubleword => @as(i64, @bitCast(@as(u64, group.imm16) << hw.int())), + }; + const imm = switch (decoded) { + .unallocated => unreachable, + .movz => decoded_imm, + .movn => ~decoded_imm, + .movk => unreachable, + }; + return writer.print("{f}{s}{f}{s}#{s}0x{x}", .{ + fmtCase(.mov, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + if (imm < 0) "-" else "", + @abs(imm), + }); + } + try writer.print("{f}{s}{f}{s}#0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + imm16, + }); + return if (!elide_shift) writer.print("{s}{f} #{s}", .{ + dis.operands_separator, + fmtCase(.lsl, dis.case), + @tagName(hw), + }); + }, + .bitfield => |bitfield| { + const decoded = bitfield.decode(); + if (decoded == .unallocated) break :unallocated; + const group = bitfield.group; + const sf = group.sf; + return writer.print("{f}{s}{f}{s}{f}{s}#{d}{s}#{d}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.imm.immr, + dis.operands_separator, + group.imm.imms, + }); + }, + .extract => |extract| { + const decoded = extract.decode(); + if (decoded == .unallocated) break :unallocated; + const group = extract.group; + const sf = group.sf; + return writer.print("{f}{s}{f}{s}{f}{s}{f}{s}#{d}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rm.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.imms, + }); + }, + }, + .branch_exception_generating_system => |branch_exception_generating_system| switch (branch_exception_generating_system.decode()) { + .unallocated => break :unallocated, + .conditional_branch_immediate => |conditional_branch_immediate| { + const decoded = conditional_branch_immediate.decode(); + if (decoded == .unallocated) break :unallocated; + const group = conditional_branch_immediate.group; + const imm = @as(i21, group.imm19); + return writer.print("{f}.{f}{s}.{c}0x{x}", .{ + fmtCase(decoded, dis.case), + fmtCase(group.cond, dis.case), + dis.mnemonic_operands_separator, + @as(u8, if (imm < 0) '-' else '+'), + @abs(imm) << 2, + }); + }, + .exception_generating => |exception_generating| { + const decoded = exception_generating.decode(); + switch (decoded) { + .unallocated => break :unallocated, + .svc, .hvc, .smc, .brk, .hlt, .tcancel => {}, + .dcps1, .dcps2, .dcps3 => switch (exception_generating.group.imm16) { + 0 => return writer.print("{f}", .{fmtCase(decoded, dis.case)}), + else => {}, + }, + } + return switch (exception_generating.group.imm16) { + 0 => writer.print("{f}{s}#0", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + }), + else => writer.print("{f}{s}#0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + exception_generating.group.imm16, + }), + }; + }, + .system_register_argument => {}, + .hints => |hints| switch (hints.decode()) { + .hint => |hint| return writer.print("{f}{s}#0x{x}", .{ + fmtCase(.hint, dis.case), + dis.mnemonic_operands_separator, + @as(u7, hint.CRm) << 3 | @as(u7, hint.op2) << 0, + }), + else => |decoded| return writer.print("{f}", .{fmtCase(decoded, dis.case)}), + }, + .barriers => {}, + .pstate => {}, + .system_result => {}, + .system => {}, + .system_register_move => {}, + .unconditional_branch_register => |unconditional_branch_register| { + const decoded = unconditional_branch_register.decode(); + if (decoded == .unallocated) break :unallocated; + const group = unconditional_branch_register.group; + const Rn = group.Rn.decodeInteger(.doubleword, .{}); + try writer.print("{f}", .{fmtCase(decoded, dis.case)}); + return if (decoded != .ret or Rn.alias != .r30) try writer.print("{s}{f}", .{ + dis.mnemonic_operands_separator, + Rn.fmtCase(dis.case), + }); + }, + .unconditional_branch_immediate => |unconditional_branch_immediate| { + const group = unconditional_branch_immediate.group; + const imm = @as(i28, group.imm26); + return writer.print("{f}{s}.{c}0x{x}", .{ + fmtCase(group.op, dis.case), + dis.mnemonic_operands_separator, + @as(u8, if (imm < 0) '-' else '+'), + @abs(imm) << 2, + }); + }, + .compare_branch_immediate => |compare_branch_immediate| { + const group = compare_branch_immediate.group; + const imm = @as(i21, group.imm19); + return writer.print("{f}{s}{f}{s}.{c}0x{x}", .{ + fmtCase(group.op, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(group.sf, .{}).fmtCase(dis.case), + dis.operands_separator, + @as(u8, if (imm < 0) '-' else '+'), + @abs(imm) << 2, + }); + }, + .test_branch_immediate => |test_branch_immediate| { + const group = test_branch_immediate.group; + const imm = @as(i16, group.imm14); + return writer.print("{f}{s}{f}{s}#0x{d}{s}.{c}0x{x}", .{ + fmtCase(group.op, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(@enumFromInt(group.b5), .{}).fmtCase(dis.case), + dis.operands_separator, + @as(u6, group.b5) << 5 | + @as(u6, group.b40) << 0, + dis.operands_separator, + @as(u8, if (imm < 0) '-' else '+'), + @abs(imm) << 2, + }); + }, + }, + .load_store => |load_store| switch (load_store.decode()) { + .unallocated => break :unallocated, + .register_literal => {}, + .memory => {}, + .no_allocate_pair_offset => {}, + .register_pair_post_indexed => |register_pair_post_indexed| switch (register_pair_post_indexed.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + if (decoded == .unallocated) break :unallocated; + const group = integer.group; + const sf: aarch64.encoding.Register.IntegerSize = @enumFromInt(group.opc >> 1); + return writer.print("{f}{s}{f}{s}{f}{s}[{f}]{s}#{s}0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u10, @abs(group.imm7)) << (@as(u2, 2) + @intFromEnum(sf)), + }); + }, + .vector => |vector| { + const decoded = vector.decode(); + if (decoded == .unallocated) break :unallocated; + const group = vector.group; + const vs = group.opc.decode(); + return writer.print("{f}{s}{f}{s}{f}{s}[{f}]{s}#{s}0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u11, @abs(group.imm7)) << (@as(u3, 2) + @intFromEnum(vs)), + }); + }, + }, + .register_pair_offset => |register_pair_offset| switch (register_pair_offset.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + if (decoded == .unallocated) break :unallocated; + const group = integer.group; + const sf: aarch64.encoding.Register.IntegerSize = @enumFromInt(group.opc >> 1); + try writer.print("{f}{s}{f}{s}{f}{s}[{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + }); + if (group.imm7 != 0) try writer.print("{s}#{s}0x{x}", .{ + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u10, @abs(group.imm7)) << (@as(u2, 2) + @intFromEnum(sf)), + }); + return writer.writeByte(']'); + }, + .vector => |vector| { + const decoded = vector.decode(); + if (decoded == .unallocated) break :unallocated; + const group = vector.group; + const vs = group.opc.decode(); + try writer.print("{f}{s}{f}{s}{f}{s}[{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + }); + if (group.imm7 != 0) try writer.print("{s}#{s}0x{x}", .{ + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u11, @abs(group.imm7)) << (@as(u3, 2) + @intFromEnum(vs)), + }); + return writer.writeByte(']'); + }, + }, + .register_pair_pre_indexed => |register_pair_pre_indexed| switch (register_pair_pre_indexed.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + if (decoded == .unallocated) break :unallocated; + const group = integer.group; + const sf: aarch64.encoding.Register.IntegerSize = @enumFromInt(group.opc >> 1); + return writer.print("{f}{s}{f}{s}{f}{s}[{f}{s}#{s}0x{x}]!", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u10, @abs(group.imm7)) << (@as(u2, 2) + @intFromEnum(sf)), + }); + }, + .vector => |vector| { + const decoded = vector.decode(); + if (decoded == .unallocated) break :unallocated; + const group = vector.group; + const vs = group.opc.decode(); + return writer.print("{f}{s}{f}{s}{f}{s}[{f}{s}#{s}0x{x}]!", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rt2.decodeVector(vs).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm7 < 0) "-" else "", + @as(u11, @abs(group.imm7)) << (@as(u3, 2) + @intFromEnum(vs)), + }); + }, + }, + .register_unscaled_immediate => {}, + .register_immediate_post_indexed => |register_immediate_post_indexed| switch (register_immediate_post_indexed.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + const sf: aarch64.encoding.Register.IntegerSize = switch (decoded) { + .unallocated => break :unallocated, + .strb, .ldrb, .strh, .ldrh => .word, + inline .ldrsb, .ldrsh => |encoded| switch (encoded.opc0) { + 0b0 => .doubleword, + 0b1 => .word, + }, + .ldrsw => .doubleword, + inline .str, .ldr => |encoded| encoded.sf, + }; + const group = integer.group; + return writer.print("{f}{s}{f}{s}[{f}]{s}#{s}0x{x}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm9 < 0) "-" else "", + @abs(group.imm9), + }); + }, + .vector => {}, + }, + .register_unprivileged => {}, + .register_immediate_pre_indexed => |register_immediate_pre_indexed| switch (register_immediate_pre_indexed.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + const sf: aarch64.encoding.Register.IntegerSize = switch (decoded) { + .unallocated => break :unallocated, + inline .ldrsb, .ldrsh => |encoded| switch (encoded.opc0) { + 0b0 => .doubleword, + 0b1 => .word, + }, + .strb, .ldrb, .strh, .ldrh => .word, + .ldrsw => .doubleword, + inline .str, .ldr => |encoded| encoded.sf, + }; + const group = integer.group; + return writer.print("{f}{s}{f}{s}[{f}{s}#{s}0x{x}]!", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm9 < 0) "-" else "", + @abs(group.imm9), + }); + }, + .vector => |vector| { + const decoded = vector.decode(); + if (decoded == .unallocated) break :unallocated; + const group = vector.group; + return writer.print("{f}{s}{f}{s}[{f}{s}#{s}0x{x}]!", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeVector(group.opc1.decode(group.size)).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + if (group.imm9 < 0) "-" else "", + @abs(group.imm9), + }); + }, + }, + .register_register_offset => |register_register_offset| switch (register_register_offset.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + const sf: aarch64.encoding.Register.IntegerSize = switch (decoded) { + .unallocated, .prfm => break :unallocated, + .strb, .ldrb, .strh, .ldrh => .word, + inline .ldrsb, .ldrsh => |encoded| switch (encoded.opc0) { + 0b0 => .doubleword, + 0b1 => .word, + }, + .ldrsw => .doubleword, + inline .str, .ldr => |encoded| encoded.sf, + }; + const group = integer.group; + try writer.print("{f}{s}{f}{s}[{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + dis.operands_separator, + group.Rm.decodeInteger(group.option.sf(), .{}).fmtCase(dis.case), + }); + if (group.option != .lsl or group.S) { + try writer.print("{s}{f}", .{ + dis.operands_separator, + fmtCase(group.option, dis.case), + }); + if (group.S) try writer.print(" #{d}", .{ + @intFromEnum(group.size), + }); + } + return writer.writeByte(']'); + }, + .vector => {}, + }, + .register_unsigned_immediate => |register_unsigned_immediate| switch (register_unsigned_immediate.decode()) { + .integer => |integer| { + const decoded = integer.decode(); + const sf: aarch64.encoding.Register.IntegerSize = switch (decoded) { + .unallocated, .prfm => break :unallocated, + .strb, .ldrb, .strh, .ldrh => .word, + inline .ldrsb, .ldrsh => |encoded| switch (encoded.opc0) { + 0b0 => .doubleword, + 0b1 => .word, + }, + .ldrsw => .doubleword, + inline .str, .ldr => |encoded| encoded.sf, + }; + const group = integer.group; + try writer.print("{f}{s}{f}{s}[{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rt.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(.doubleword, .{ .sp = true }).fmtCase(dis.case), + }); + if (group.imm12 > 0) try writer.print("{s}#0x{x}", .{ + dis.operands_separator, + @as(u15, group.imm12) << @intFromEnum(group.size), + }); + return writer.writeByte(']'); + }, + .vector => {}, + }, + }, + .data_processing_register => |data_processing_register| switch (data_processing_register.decode()) { + .unallocated => break :unallocated, + .data_processing_two_source => |data_processing_two_source| { + const decoded = data_processing_two_source.decode(); + if (decoded == .unallocated) break :unallocated; + const group = data_processing_two_source.group; + const sf = group.sf; + return writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rm.decodeInteger(sf, .{}).fmtCase(dis.case), + }); + }, + .data_processing_one_source => |data_processing_one_source| { + const decoded = data_processing_one_source.decode(); + if (decoded == .unallocated) break :unallocated; + const group = data_processing_one_source.group; + const sf = group.sf; + return writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(sf, .{}).fmtCase(dis.case), + }); + }, + .logical_shifted_register => |logical_shifted_register| { + const decoded = logical_shifted_register.decode(); + if (decoded == .unallocated) break :unallocated; + const group = logical_shifted_register.group; + const sf = group.sf; + const shift = group.shift; + const Rm = group.Rm.decodeInteger(sf, .{}); + const amount = group.imm6; + const Rn = group.Rn.decodeInteger(sf, .{}); + const Rd = group.Rd.decodeInteger(sf, .{}); + const elide_shift = shift == .lsl and amount == 0; + if (dis.enable_aliases and switch (decoded) { + else => false, + .orr => elide_shift, + .orn => true, + } and Rn.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { mov, mvn }, switch (decoded) { + else => unreachable, + .orr => .mov, + .orn => .mvn, + }), dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else if (dis.enable_aliases and decoded == .ands and Rd.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(.tst, dis.case), + dis.mnemonic_operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else try writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }); + return if (!elide_shift) writer.print("{s}{f} #{d}", .{ + dis.operands_separator, + fmtCase(shift, dis.case), + amount, + }); + }, + .add_subtract_shifted_register => |add_subtract_shifted_register| { + const decoded = add_subtract_shifted_register.decode(); + if (decoded == .unallocated) break :unallocated; + const group = add_subtract_shifted_register.group; + const sf = group.sf; + const shift = group.shift; + const Rm = group.Rm.decodeInteger(sf, .{}); + const imm6 = group.imm6; + const Rn = group.Rn.decodeInteger(sf, .{}); + const Rd = group.Rd.decodeInteger(sf, .{}); + if (dis.enable_aliases and group.S and Rd.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { cmn, cmp }, switch (group.op) { + .add => .cmn, + .sub => .cmp, + }), dis.case), + dis.mnemonic_operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else if (dis.enable_aliases and group.op == .sub and Rn.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { neg, negs }, switch (group.S) { + false => .neg, + true => .negs, + }), dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else try writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }); + return if (shift != .lsl or imm6 != 0) return writer.print("{s}{f} #{d}", .{ + dis.operands_separator, + fmtCase(shift, dis.case), + imm6, + }); + }, + .add_subtract_extended_register => |add_subtract_extended_register| { + const decoded = add_subtract_extended_register.decode(); + if (decoded == .unallocated) break :unallocated; + const group = add_subtract_extended_register.group; + const sf = group.sf; + const Rm = group.Rm.decodeInteger(group.option.sf(), .{}); + const Rn = group.Rn.decodeInteger(sf, .{ .sp = true }); + const Rd = group.Rd.decodeInteger(sf, .{ .sp = true }); + if (dis.enable_aliases and group.S and Rd.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { cmn, cmp }, switch (group.op) { + .add => .cmn, + .sub => .cmp, + }), dis.case), + dis.mnemonic_operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else try writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }); + return if (group.option != @as(Instruction.DataProcessingRegister.AddSubtractExtendedRegister.Option, switch (sf) { + .word => .uxtw, + .doubleword => .uxtx, + }) or group.imm3 != 0) writer.print("{s}{f} #{d}", .{ + dis.operands_separator, + fmtCase(group.option, dis.case), + group.imm3, + }); + }, + .add_subtract_with_carry => |add_subtract_with_carry| { + const decoded = add_subtract_with_carry.decode(); + const group = add_subtract_with_carry.group; + const sf = group.sf; + const Rm = group.Rm.decodeInteger(sf, .{}); + const Rn = group.Rn.decodeInteger(sf, .{}); + const Rd = group.Rd.decodeInteger(sf, .{}); + return if (dis.enable_aliases and group.op == .sbc and Rn.alias == .zr) try writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { ngc, ngcs }, switch (group.S) { + false => .ngc, + true => .ngcs, + }), dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }) else try writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + }); + }, + .rotate_right_into_flags => {}, + .evaluate_into_flags => {}, + .conditional_compare_register => {}, + .conditional_compare_immediate => {}, + .conditional_select => |conditional_select| { + const decoded = conditional_select.decode(); + if (decoded == .unallocated) break :unallocated; + const group = conditional_select.group; + const sf = group.sf; + const Rm = group.Rm.decodeInteger(sf, .{}); + const cond = group.cond; + const Rn = group.Rn.decodeInteger(sf, .{}); + const Rd = group.Rd.decodeInteger(sf, .{}); + return if (dis.enable_aliases and group.op != group.op2 and Rm.alias == .zr and cond != .al and cond != .nv and Rn.alias == Rm.alias) writer.print("{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { cset, csetm }, switch (decoded) { + else => unreachable, + .csinc => .cset, + .csinv => .csetm, + }), dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + fmtCase(cond.invert(), dis.case), + }) else if (dis.enable_aliases and decoded != .csel and cond != .al and cond != .nv and Rn.alias == Rm.alias) writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(@as(enum { cinc, cinv, cneg }, switch (decoded) { + else => unreachable, + .csinc => .cinc, + .csinv => .cinv, + .csneg => .cneg, + }), dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + fmtCase(cond.invert(), dis.case), + }) else writer.print("{f}{s}{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + Rd.fmtCase(dis.case), + dis.operands_separator, + Rn.fmtCase(dis.case), + dis.operands_separator, + Rm.fmtCase(dis.case), + dis.operands_separator, + fmtCase(cond, dis.case), + }); + }, + .data_processing_three_source => |data_processing_three_source| { + const decoded = data_processing_three_source.decode(); + if (decoded == .unallocated) break :unallocated; + const group = data_processing_three_source.group; + const sf = group.sf; + try writer.print("{f}{s}{f}{s}{f}{s}{f}", .{ + fmtCase(decoded, dis.case), + dis.mnemonic_operands_separator, + group.Rd.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rn.decodeInteger(sf, .{}).fmtCase(dis.case), + dis.operands_separator, + group.Rm.decodeInteger(sf, .{}).fmtCase(dis.case), + }); + return switch (decoded) { + .unallocated => unreachable, + .madd, .msub, .smaddl, .smsubl, .umaddl, .umsubl => writer.print("{s}{f}", .{ + dis.operands_separator, + group.Ra.decodeInteger(sf, .{}).fmtCase(dis.case), + }), + .smulh, .umulh => {}, + }; + }, + }, + .data_processing_vector => {}, + } + return writer.print(".{f}{s}0x{x:0>8}", .{ + fmtCase(.word, dis.case), + dis.mnemonic_operands_separator, + @as(Instruction.Backing, @bitCast(inst)), + }); +} + +fn fmtCase(tag: anytype, case: Case) struct { + tag: []const u8, + case: Case, + pub fn format(data: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + for (data.tag) |c| try writer.writeByte(switch (data.case) { + .lower => std.ascii.toLower(c), + .upper => std.ascii.toUpper(c), + }); + } +} { + return .{ .tag = @tagName(tag), .case = case }; +} + +pub const RegisterFormatter = struct { + reg: aarch64.encoding.Register, + case: Case, + pub fn format(data: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + switch (data.reg.format) { + .alias => try writer.print("{f}", .{fmtCase(data.reg.alias, data.case)}), + .integer => |size| switch (data.reg.alias) { + .r0, + .r1, + .r2, + .r3, + .r4, + .r5, + .r6, + .r7, + .r8, + .r9, + .r10, + .r11, + .r12, + .r13, + .r14, + .r15, + .r16, + .r17, + .r18, + .r19, + .r20, + .r21, + .r22, + .r23, + .r24, + .r25, + .r26, + .r27, + .r28, + .r29, + .r30, + => |alias| try writer.print("{c}{d}", .{ + size.prefix(), + @intFromEnum(alias.encode(.{})), + }), + .zr => try writer.print("{c}{f}", .{ + size.prefix(), + fmtCase(data.reg.alias, data.case), + }), + else => try writer.print("{s}{f}", .{ + switch (size) { + .word => "w", + .doubleword => "", + }, + fmtCase(data.reg.alias, data.case), + }), + }, + .scalar => |size| try writer.print("{c}{d}", .{ + size.prefix(), + @intFromEnum(data.reg.alias.encode(.{ .V = true })), + }), + .vector => |arrangement| try writer.print("{f}.{f}", .{ + fmtCase(data.reg.alias, data.case), + fmtCase(arrangement, data.case), + }), + .element => |element| try writer.print("{f}.{c}[{d}]", .{ + fmtCase(data.reg.alias, data.case), + element.size.prefix(), + element.index, + }), + } + } +}; + +const aarch64 = @import("../aarch64.zig"); +const Disassemble = @This(); +const Instruction = aarch64.encoding.Instruction; +const std = @import("std"); diff --git a/src/codegen/aarch64/Mir.zig b/src/codegen/aarch64/Mir.zig new file mode 100644 index 0000000000000000000000000000000000000000..1446238888b0383ee9028aa3d323bc9b6fc0d278 --- /dev/null +++ b/src/codegen/aarch64/Mir.zig @@ -0,0 +1,275 @@ +prologue: []const Instruction, +body: []const Instruction, +epilogue: []const Instruction, +literals: []const u32, +nav_relocs: []const Reloc.Nav, +uav_relocs: []const Reloc.Uav, +global_relocs: []const Reloc.Global, +literal_relocs: []const Reloc.Literal, + +pub const Reloc = struct { + label: u32, + addend: u64 align(@alignOf(u32)) = 0, + + pub const Nav = struct { + nav: InternPool.Nav.Index, + reloc: Reloc, + }; + + pub const Uav = struct { + uav: InternPool.Key.Ptr.BaseAddr.Uav, + reloc: Reloc, + }; + + pub const Global = struct { + global: [*:0]const u8, + reloc: Reloc, + }; + + pub const Literal = struct { + label: u32, + }; +}; + +pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { + assert(mir.body.ptr + mir.body.len == mir.prologue.ptr); + assert(mir.prologue.ptr + mir.prologue.len == mir.epilogue.ptr); + gpa.free(mir.body.ptr[0 .. mir.body.len + mir.prologue.len + mir.epilogue.len]); + gpa.free(mir.literals); + gpa.free(mir.nav_relocs); + gpa.free(mir.uav_relocs); + gpa.free(mir.global_relocs); + gpa.free(mir.literal_relocs); + mir.* = undefined; +} + +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, +) !void { + _ = debug_output; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const gpa = zcu.gpa; + const func = zcu.funcInfo(func_index); + const nav = ip.getNav(func.owner_nav); + const mod = zcu.navFileScope(func.owner_nav).mod.?; + const target = &mod.resolved_target.result; + mir_log.debug("{f}:", .{nav.fqn.fmt(ip)}); + + const func_align = switch (nav.status.fully_resolved.alignment) { + .none => switch (mod.optimize_mode) { + .Debug, .ReleaseSafe, .ReleaseFast => target_util.defaultFunctionAlignment(target), + .ReleaseSmall => target_util.minFunctionAlignment(target), + }, + else => |a| a.maxStrict(target_util.minFunctionAlignment(target)), + }; + const code_len = mir.prologue.len + mir.body.len + mir.epilogue.len; + const literals_align_gap = -%code_len & (@divExact( + @as(u5, @intCast(func_align.minStrict(.@"16").toByteUnits().?)), + Instruction.size, + ) - 1); + try code.ensureUnusedCapacity(gpa, Instruction.size * + (code_len + literals_align_gap + mir.literals.len)); + emitInstructionsForward(code, mir.prologue); + emitInstructionsBackward(code, mir.body); + const body_end: u32 = @intCast(code.items.len); + emitInstructionsBackward(code, mir.epilogue); + code.appendNTimesAssumeCapacity(0, Instruction.size * literals_align_gap); + code.appendSliceAssumeCapacity(@ptrCast(mir.literals)); + mir_log.debug("", .{}); + + for (mir.nav_relocs) |nav_reloc| try emitReloc( + lf, + zcu, + func.owner_nav, + switch (try @import("../../codegen.zig").genNavRef( + lf, + pt, + src_loc, + nav_reloc.nav, + &mod.resolved_target.result, + )) { + .sym_index => |sym_index| sym_index, + .fail => |em| return zcu.codegenFailMsg(func.owner_nav, em), + }, + mir.body[nav_reloc.reloc.label], + body_end - Instruction.size * (1 + nav_reloc.reloc.label), + nav_reloc.reloc.addend, + ); + for (mir.uav_relocs) |uav_reloc| try emitReloc( + lf, + zcu, + func.owner_nav, + switch (try lf.lowerUav( + pt, + uav_reloc.uav.val, + ZigType.fromInterned(uav_reloc.uav.orig_ty).ptrAlignment(zcu), + src_loc, + )) { + .sym_index => |sym_index| sym_index, + .fail => |em| return zcu.codegenFailMsg(func.owner_nav, em), + }, + mir.body[uav_reloc.reloc.label], + body_end - Instruction.size * (1 + uav_reloc.reloc.label), + uav_reloc.reloc.addend, + ); + for (mir.global_relocs) |global_reloc| try emitReloc( + lf, + zcu, + func.owner_nav, + if (lf.cast(.elf)) |ef| + try ef.getGlobalSymbol(std.mem.span(global_reloc.global), null) + else if (lf.cast(.macho)) |mf| + try mf.getGlobalSymbol(std.mem.span(global_reloc.global), null) + else if (lf.cast(.coff)) |cf| + try cf.getGlobalSymbol(std.mem.span(global_reloc.global), "compiler_rt") + else + return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}), + mir.body[global_reloc.reloc.label], + body_end - Instruction.size * (1 + global_reloc.reloc.label), + global_reloc.reloc.addend, + ); + const literal_reloc_offset: i19 = @intCast(mir.epilogue.len + literals_align_gap); + for (mir.literal_relocs) |literal_reloc| { + var instruction = mir.body[literal_reloc.label]; + instruction.load_store.register_literal.group.imm19 += literal_reloc_offset; + instruction.write( + code.items[body_end - Instruction.size * (1 + literal_reloc.label) ..][0..Instruction.size], + ); + } +} + +fn emitInstructionsForward(code: *std.ArrayListUnmanaged(u8), instructions: []const Instruction) void { + for (instructions) |instruction| emitInstruction(code, instruction); +} +fn emitInstructionsBackward(code: *std.ArrayListUnmanaged(u8), instructions: []const Instruction) void { + var instruction_index = instructions.len; + while (instruction_index > 0) { + instruction_index -= 1; + emitInstruction(code, instructions[instruction_index]); + } +} +fn emitInstruction(code: *std.ArrayListUnmanaged(u8), instruction: Instruction) void { + mir_log.debug(" {f}", .{instruction}); + instruction.write(code.addManyAsArrayAssumeCapacity(Instruction.size)); +} + +fn emitReloc( + lf: *link.File, + zcu: *Zcu, + owner_nav: InternPool.Nav.Index, + sym_index: u32, + instruction: Instruction, + offset: u32, + addend: u64, +) !void { + const gpa = zcu.gpa; + switch (instruction.decode()) { + else => unreachable, + .branch_exception_generating_system => |decoded| if (lf.cast(.elf)) |ef| { + const zo = ef.zigObjectPtr().?; + const atom = zo.symbol(try zo.getOrCreateMetadataForNav(zcu, owner_nav)).atom(ef).?; + const r_type: std.elf.R_AARCH64 = switch (decoded.decode().unconditional_branch_immediate.group.op) { + .b => .JUMP26, + .bl => .CALL26, + }; + try atom.addReloc(gpa, .{ + .r_offset = offset, + .r_info = @as(u64, sym_index) << 32 | @intFromEnum(r_type), + .r_addend = @bitCast(addend), + }, zo); + } else if (lf.cast(.macho)) |mf| { + const zo = mf.getZigObject().?; + const atom = zo.symbols.items[try zo.getOrCreateMetadataForNav(mf, owner_nav)].getAtom(mf).?; + try atom.addReloc(mf, .{ + .tag = .@"extern", + .offset = offset, + .target = sym_index, + .addend = @bitCast(addend), + .type = .branch, + .meta = .{ + .pcrel = true, + .has_subtractor = false, + .length = 2, + .symbolnum = @intCast(sym_index), + }, + }); + }, + .data_processing_immediate => |decoded| if (lf.cast(.elf)) |ef| { + const zo = ef.zigObjectPtr().?; + const atom = zo.symbol(try zo.getOrCreateMetadataForNav(zcu, owner_nav)).atom(ef).?; + const r_type: std.elf.R_AARCH64 = switch (decoded.decode()) { + else => unreachable, + .pc_relative_addressing => |pc_relative_addressing| switch (pc_relative_addressing.group.op) { + .adr => .ADR_PREL_LO21, + .adrp => .ADR_PREL_PG_HI21, + }, + .add_subtract_immediate => |add_subtract_immediate| switch (add_subtract_immediate.group.op) { + .add => .ADD_ABS_LO12_NC, + .sub => unreachable, + }, + }; + try atom.addReloc(gpa, .{ + .r_offset = offset, + .r_info = @as(u64, sym_index) << 32 | @intFromEnum(r_type), + .r_addend = @bitCast(addend), + }, zo); + } else if (lf.cast(.macho)) |mf| { + const zo = mf.getZigObject().?; + const atom = zo.symbols.items[try zo.getOrCreateMetadataForNav(mf, owner_nav)].getAtom(mf).?; + switch (decoded.decode()) { + else => unreachable, + .pc_relative_addressing => |pc_relative_addressing| switch (pc_relative_addressing.group.op) { + .adr => unreachable, + .adrp => try atom.addReloc(mf, .{ + .tag = .@"extern", + .offset = offset, + .target = sym_index, + .addend = @bitCast(addend), + .type = .page, + .meta = .{ + .pcrel = true, + .has_subtractor = false, + .length = 2, + .symbolnum = @intCast(sym_index), + }, + }), + }, + .add_subtract_immediate => |add_subtract_immediate| switch (add_subtract_immediate.group.op) { + .add => try atom.addReloc(mf, .{ + .tag = .@"extern", + .offset = offset, + .target = sym_index, + .addend = @bitCast(addend), + .type = .pageoff, + .meta = .{ + .pcrel = false, + .has_subtractor = false, + .length = 2, + .symbolnum = @intCast(sym_index), + }, + }), + .sub => unreachable, + }, + } + }, + } +} + +const Air = @import("../../Air.zig"); +const assert = std.debug.assert; +const mir_log = std.log.scoped(.mir); +const Instruction = @import("encoding.zig").Instruction; +const InternPool = @import("../../InternPool.zig"); +const link = @import("../../link.zig"); +const Mir = @This(); +const std = @import("std"); +const target_util = @import("../../target.zig"); +const Zcu = @import("../../Zcu.zig"); +const ZigType = @import("../../Type.zig"); diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig new file mode 100644 index 0000000000000000000000000000000000000000..10cf4f40c24b24feb1937f944b5bad249fc6d691 --- /dev/null +++ b/src/codegen/aarch64/Select.zig @@ -0,0 +1,10981 @@ +pt: Zcu.PerThread, +target: *const std.Target, +air: Air, +nav_index: InternPool.Nav.Index, + +// Blocks +def_order: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, void), +blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, Block), +loops: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, Loop), +active_loops: std.ArrayListUnmanaged(Loop.Index), +loop_live: struct { + set: std.AutoArrayHashMapUnmanaged(struct { Loop.Index, Air.Inst.Index }, void), + list: std.ArrayListUnmanaged(Air.Inst.Index), +}, +dom_start: u32, +dom_len: u32, +dom: std.ArrayListUnmanaged(DomInt), + +// Wip Mir +saved_registers: std.enums.EnumSet(Register.Alias), +instructions: std.ArrayListUnmanaged(codegen.aarch64.encoding.Instruction), +literals: std.ArrayListUnmanaged(u32), +nav_relocs: std.ArrayListUnmanaged(codegen.aarch64.Mir.Reloc.Nav), +uav_relocs: std.ArrayListUnmanaged(codegen.aarch64.Mir.Reloc.Uav), +global_relocs: std.ArrayListUnmanaged(codegen.aarch64.Mir.Reloc.Global), +literal_relocs: std.ArrayListUnmanaged(codegen.aarch64.Mir.Reloc.Literal), + +// Stack Frame +returns: bool, +va_list: struct { + __stack: Value.Indirect, + __gr_top: Value.Indirect, + __vr_top: Value.Indirect, +}, +stack_size: u24, +stack_align: InternPool.Alignment, + +// Value Tracking +live_registers: LiveRegisters, +live_values: std.AutoHashMapUnmanaged(Air.Inst.Index, Value.Index), +values: std.ArrayListUnmanaged(Value), + +pub const LiveRegisters = std.enums.EnumArray(Register.Alias, Value.Index); + +pub const Block = struct { + live_registers: LiveRegisters, + target_label: u32, + + pub const main: Air.Inst.Index = @enumFromInt( + std.math.maxInt(@typeInfo(Air.Inst.Index).@"enum".tag_type), + ); + + fn branch(block: *const Block, isel: *Select) !void { + if (isel.instructions.items.len > block.target_label) { + try isel.emit(.b(@intCast((isel.instructions.items.len + 1 - block.target_label) << 2))); + } + try isel.merge(&block.live_registers, .{}); + } +}; + +pub const Loop = struct { + def_order: u32, + dom: u32, + depth: u32, + live: u32, + live_registers: LiveRegisters, + repeat_list: u32, + + pub const invalid: Air.Inst.Index = @enumFromInt( + std.math.maxInt(@typeInfo(Air.Inst.Index).@"enum".tag_type), + ); + + pub const Index = enum(u32) { + _, + + fn inst(li: Loop.Index, isel: *Select) Air.Inst.Index { + return isel.loops.keys()[@intFromEnum(li)]; + } + + fn get(li: Loop.Index, isel: *Select) *Loop { + return &isel.loops.values()[@intFromEnum(li)]; + } + }; + + pub const empty_list: u32 = std.math.maxInt(u32); + + fn branch(loop: *Loop, isel: *Select) !void { + try isel.instructions.ensureUnusedCapacity(isel.pt.zcu.gpa, 1); + const repeat_list_tail = loop.repeat_list; + loop.repeat_list = @intCast(isel.instructions.items.len); + isel.instructions.appendAssumeCapacity(@bitCast(repeat_list_tail)); + try isel.merge(&loop.live_registers, .{}); + } +}; + +pub fn deinit(isel: *Select) void { + const gpa = isel.pt.zcu.gpa; + + isel.def_order.deinit(gpa); + isel.blocks.deinit(gpa); + isel.loops.deinit(gpa); + isel.active_loops.deinit(gpa); + isel.loop_live.set.deinit(gpa); + isel.loop_live.list.deinit(gpa); + isel.dom.deinit(gpa); + + isel.instructions.deinit(gpa); + isel.literals.deinit(gpa); + isel.nav_relocs.deinit(gpa); + isel.uav_relocs.deinit(gpa); + isel.global_relocs.deinit(gpa); + isel.literal_relocs.deinit(gpa); + + isel.live_values.deinit(gpa); + isel.values.deinit(gpa); + + isel.* = undefined; +} + +pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { + const zcu = isel.pt.zcu; + const ip = &zcu.intern_pool; + const gpa = zcu.gpa; + const air_tags = isel.air.instructions.items(.tag); + const air_data = isel.air.instructions.items(.data); + var air_body_index: usize = 0; + var air_inst_index = air_body[air_body_index]; + const initial_def_order_len = isel.def_order.count(); + air_tag: switch (air_tags[@intFromEnum(air_inst_index)]) { + .arg, + .ret_addr, + .frame_addr, + .err_return_trace, + .save_err_return_trace_index, + .runtime_nav_ptr, + .c_va_start, + => { + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .add, + .add_safe, + .add_optimized, + .add_wrap, + .add_sat, + .sub, + .sub_safe, + .sub_optimized, + .sub_wrap, + .sub_sat, + .mul, + .mul_safe, + .mul_optimized, + .mul_wrap, + .mul_sat, + .div_float, + .div_float_optimized, + .div_trunc, + .div_trunc_optimized, + .div_floor, + .div_floor_optimized, + .div_exact, + .div_exact_optimized, + .rem, + .rem_optimized, + .mod, + .mod_optimized, + .max, + .min, + .bit_and, + .bit_or, + .shr, + .shr_exact, + .shl, + .shl_exact, + .shl_sat, + .xor, + .cmp_lt, + .cmp_lt_optimized, + .cmp_lte, + .cmp_lte_optimized, + .cmp_eq, + .cmp_eq_optimized, + .cmp_gte, + .cmp_gte_optimized, + .cmp_gt, + .cmp_gt_optimized, + .cmp_neq, + .cmp_neq_optimized, + .bool_and, + .bool_or, + .array_elem_val, + .slice_elem_val, + .ptr_elem_val, + => { + const bin_op = air_data[@intFromEnum(air_inst_index)].bin_op; + + try isel.analyzeUse(bin_op.lhs); + try isel.analyzeUse(bin_op.rhs); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .ptr_add, + .ptr_sub, + .add_with_overflow, + .sub_with_overflow, + .mul_with_overflow, + .shl_with_overflow, + .slice, + .slice_elem_ptr, + .ptr_elem_ptr, + => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + + try isel.analyzeUse(bin_op.lhs); + try isel.analyzeUse(bin_op.rhs); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .alloc => { + const ty = air_data[@intFromEnum(air_inst_index)].ty; + + isel.stack_align = isel.stack_align.maxStrict(ty.ptrAlignment(zcu)); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .inferred_alloc, + .inferred_alloc_comptime, + .wasm_memory_size, + .wasm_memory_grow, + .work_item_id, + .work_group_size, + .work_group_id, + => unreachable, + .ret_ptr => { + const ty = air_data[@intFromEnum(air_inst_index)].ty; + + if (isel.live_values.get(Block.main)) |ret_vi| switch (ret_vi.parent(isel)) { + .unallocated, .stack_slot => isel.stack_align = isel.stack_align.maxStrict(ty.ptrAlignment(zcu)), + .value, .constant => unreachable, + .address => |address_vi| try isel.live_values.putNoClobber(gpa, air_inst_index, address_vi.ref(isel)), + }; + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .assembly => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.Asm, ty_pl.payload); + const operands: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0 .. extra.data.flags.outputs_len + extra.data.inputs_len]); + + for (operands) |operand| if (operand != .none) try isel.analyzeUse(operand); + if (ty_pl.ty != .void_type) try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .not, + .clz, + .ctz, + .popcount, + .byte_swap, + .bit_reverse, + .abs, + .load, + .fptrunc, + .fpext, + .intcast, + .intcast_safe, + .trunc, + .optional_payload, + .optional_payload_ptr, + .optional_payload_ptr_set, + .wrap_optional, + .unwrap_errunion_payload, + .unwrap_errunion_err, + .unwrap_errunion_payload_ptr, + .unwrap_errunion_err_ptr, + .errunion_payload_ptr_set, + .wrap_errunion_payload, + .wrap_errunion_err, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, + .get_union_tag, + .ptr_slice_len_ptr, + .ptr_slice_ptr_ptr, + .array_to_slice, + .int_from_float, + .int_from_float_optimized, + .int_from_float_safe, + .int_from_float_optimized_safe, + .float_from_int, + .splat, + .error_set_has_value, + .addrspace_cast, + .c_va_arg, + .c_va_copy, + => { + const ty_op = air_data[@intFromEnum(air_inst_index)].ty_op; + + try isel.analyzeUse(ty_op.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .bitcast => { + const ty_op = air_data[@intFromEnum(air_inst_index)].ty_op; + maybe_noop: { + if (ty_op.ty.toInterned().? != isel.air.typeOf(ty_op.operand, ip).toIntern()) break :maybe_noop; + if (true) break :maybe_noop; + if (ty_op.operand.toIndex()) |src_air_inst_index| { + if (isel.hints.get(src_air_inst_index)) |hint_vpsi| { + try isel.hints.putNoClobber(gpa, air_inst_index, hint_vpsi); + } + } + } + try isel.analyzeUse(ty_op.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + inline .block, .dbg_inline_block => |air_tag| { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(switch (air_tag) { + else => comptime unreachable, + .block => Air.Block, + .dbg_inline_block => Air.DbgInlineBlock, + }, ty_pl.payload); + const result_ty = ty_pl.ty.toInterned().?; + + if (result_ty == .noreturn_type) { + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + + air_body_index += 1; + break :air_tag; + } + + assert(!(try isel.blocks.getOrPut(gpa, air_inst_index)).found_existing); + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + const block_entry = isel.blocks.pop().?; + assert(block_entry.key == air_inst_index); + + if (result_ty != .void_type) try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .loop => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.Block, ty_pl.payload); + + const initial_dom_start = isel.dom_start; + const initial_dom_len = isel.dom_len; + isel.dom_start = @intCast(isel.dom.items.len); + isel.dom_len = @intCast(isel.blocks.count()); + try isel.active_loops.append(gpa, @enumFromInt(isel.loops.count())); + try isel.loops.putNoClobber(gpa, air_inst_index, .{ + .def_order = @intCast(isel.def_order.count()), + .dom = isel.dom_start, + .depth = isel.dom_len, + .live = 0, + .live_registers = undefined, + .repeat_list = undefined, + }); + try isel.dom.appendNTimes(gpa, 0, std.math.divCeil(usize, isel.dom_len, @bitSizeOf(DomInt)) catch unreachable); + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + for ( + isel.dom.items[initial_dom_start..].ptr, + isel.dom.items[isel.dom_start..][0 .. std.math.divCeil(usize, initial_dom_len, @bitSizeOf(DomInt)) catch unreachable], + ) |*initial_dom, loop_dom| initial_dom.* |= loop_dom; + isel.dom_start = initial_dom_start; + isel.dom_len = initial_dom_len; + assert(isel.active_loops.pop().?.inst(isel) == air_inst_index); + + air_body_index += 1; + }, + .repeat, .trap, .unreach => air_body_index += 1, + .br => { + const br = air_data[@intFromEnum(air_inst_index)].br; + const block_index = isel.blocks.getIndex(br.block_inst).?; + if (block_index < isel.dom_len) isel.dom.items[isel.dom_start + block_index / @bitSizeOf(DomInt)] |= @as(DomInt, 1) << @truncate(block_index); + try isel.analyzeUse(br.operand); + + air_body_index += 1; + }, + .breakpoint, + .dbg_stmt, + .dbg_empty_stmt, + .dbg_var_ptr, + .dbg_var_val, + .dbg_arg_inline, + => { + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .call, + .call_always_tail, + .call_never_tail, + .call_never_inline, + => { + const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; + const extra = isel.air.extraData(Air.Call, pl_op.payload); + const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]); + isel.saved_registers.insert(.lr); + + try isel.analyzeUse(pl_op.operand); + var param_it: CallAbiIterator = .init; + for (args) |arg| { + const restore_values_len = isel.values.items.len; + defer isel.values.shrinkRetainingCapacity(restore_values_len); + const param_vi = try param_it.param(isel, isel.air.typeOf(arg, ip)) orelse continue; + const param_parent = param_vi.parent(isel); + switch (switch (param_parent) { + .unallocated, .stack_slot => param_parent, + .value, .constant => unreachable, + .address => |address_vi| address_vi.parent(isel), + }) { + .unallocated => {}, + .stack_slot => |stack_slot| { + assert(stack_slot.base == .sp); + isel.stack_size = @max(isel.stack_size, stack_slot.offset); + }, + .value, .constant, .address => unreachable, + } + + try isel.analyzeUse(arg); + } + + var ret_it: CallAbiIterator = .init; + if (try ret_it.ret(isel, isel.air.typeOfIndex(air_inst_index, ip))) |ret_vi| { + tracking_log.debug("${d} <- %{d}", .{ @intFromEnum(ret_vi), @intFromEnum(air_inst_index) }); + switch (ret_vi.parent(isel)) { + .unallocated, .stack_slot => {}, + .value, .constant => unreachable, + .address => |address_vi| { + defer address_vi.deref(isel); + const ret_value = ret_vi.get(isel); + ret_value.flags.parent_tag = .unallocated; + ret_value.parent_payload = .{ .unallocated = {} }; + }, + } + try isel.live_values.putNoClobber(gpa, air_inst_index, ret_vi); + + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + } + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .sqrt, + .sin, + .cos, + .tan, + .exp, + .exp2, + .log, + .log2, + .log10, + .floor, + .ceil, + .round, + .trunc_float, + .neg, + .neg_optimized, + .is_null, + .is_non_null, + .is_null_ptr, + .is_non_null_ptr, + .is_err, + .is_non_err, + .is_err_ptr, + .is_non_err_ptr, + .is_named_enum_value, + .tag_name, + .error_name, + .cmp_lt_errors_len, + => { + const un_op = air_data[@intFromEnum(air_inst_index)].un_op; + + try isel.analyzeUse(un_op); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .cmp_vector, .cmp_vector_optimized => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.VectorCmp, ty_pl.payload).data; + + try isel.analyzeUse(extra.lhs); + try isel.analyzeUse(extra.rhs); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .cond_br => { + const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; + const extra = isel.air.extraData(Air.CondBr, pl_op.payload); + + try isel.analyzeUse(pl_op.operand); + + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len])); + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len])); + + air_body_index += 1; + }, + .switch_br => { + const switch_br = isel.air.unwrapSwitch(air_inst_index); + + try isel.analyzeUse(switch_br.operand); + + var cases_it = switch_br.iterateCases(); + while (cases_it.next()) |case| try isel.analyze(case.body); + if (switch_br.else_body_len > 0) try isel.analyze(cases_it.elseBody()); + + air_body_index += 1; + }, + .loop_switch_br => { + const switch_br = isel.air.unwrapSwitch(air_inst_index); + + const initial_dom_start = isel.dom_start; + const initial_dom_len = isel.dom_len; + isel.dom_start = @intCast(isel.dom.items.len); + isel.dom_len = @intCast(isel.blocks.count()); + try isel.active_loops.append(gpa, @enumFromInt(isel.loops.count())); + try isel.loops.putNoClobber(gpa, air_inst_index, .{ + .def_order = @intCast(isel.def_order.count()), + .dom = isel.dom_start, + .depth = isel.dom_len, + .live = 0, + .live_registers = undefined, + .repeat_list = undefined, + }); + try isel.dom.appendNTimes(gpa, 0, std.math.divCeil(usize, isel.dom_len, @bitSizeOf(DomInt)) catch unreachable); + + var cases_it = switch_br.iterateCases(); + while (cases_it.next()) |case| try isel.analyze(case.body); + if (switch_br.else_body_len > 0) try isel.analyze(cases_it.elseBody()); + + for ( + isel.dom.items[initial_dom_start..].ptr, + isel.dom.items[isel.dom_start..][0 .. std.math.divCeil(usize, initial_dom_len, @bitSizeOf(DomInt)) catch unreachable], + ) |*initial_dom, loop_dom| initial_dom.* |= loop_dom; + isel.dom_start = initial_dom_start; + isel.dom_len = initial_dom_len; + assert(isel.active_loops.pop().?.inst(isel) == air_inst_index); + + air_body_index += 1; + }, + .switch_dispatch => { + const br = air_data[@intFromEnum(air_inst_index)].br; + + try isel.analyzeUse(br.operand); + + air_body_index += 1; + }, + .@"try", .try_cold, .try_ptr, .try_ptr_cold => { + const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; + const extra = isel.air.extraData(Air.Try, pl_op.payload); + + try isel.analyzeUse(pl_op.operand); + try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .ret, .ret_safe, .ret_load => { + const un_op = air_data[@intFromEnum(air_inst_index)].un_op; + isel.returns = true; + + const block_index = 0; + assert(isel.blocks.keys()[block_index] == Block.main); + if (isel.dom_len > 0) isel.dom.items[isel.dom_start] |= 1 << block_index; + + try isel.analyzeUse(un_op); + + air_body_index += 1; + }, + .store, + .store_safe, + .set_union_tag, + .memset, + .memset_safe, + .memcpy, + .memmove, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, + => { + const bin_op = air_data[@intFromEnum(air_inst_index)].bin_op; + + try isel.analyzeUse(bin_op.lhs); + try isel.analyzeUse(bin_op.rhs); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .struct_field_ptr, .struct_field_val => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data; + + try isel.analyzeUse(extra.struct_operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .slice_len => { + const ty_op = air_data[@intFromEnum(air_inst_index)].ty_op; + + try isel.analyzeUse(ty_op.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + const slice_vi = try isel.use(ty_op.operand); + var len_part_it = slice_vi.field(isel.air.typeOf(ty_op.operand, ip), 8, 8); + if (try len_part_it.only(isel)) |len_part_vi| + try isel.live_values.putNoClobber(gpa, air_inst_index, len_part_vi.ref(isel)); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .slice_ptr => { + const ty_op = air_data[@intFromEnum(air_inst_index)].ty_op; + + try isel.analyzeUse(ty_op.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + const slice_vi = try isel.use(ty_op.operand); + var ptr_part_it = slice_vi.field(isel.air.typeOf(ty_op.operand, ip), 0, 8); + if (try ptr_part_it.only(isel)) |ptr_part_vi| + try isel.live_values.putNoClobber(gpa, air_inst_index, ptr_part_vi.ref(isel)); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .reduce, .reduce_optimized => { + const reduce = air_data[@intFromEnum(air_inst_index)].reduce; + + try isel.analyzeUse(reduce.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .shuffle_one => { + const extra = isel.air.unwrapShuffleOne(zcu, air_inst_index); + + try isel.analyzeUse(extra.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .shuffle_two => { + const extra = isel.air.unwrapShuffleTwo(zcu, air_inst_index); + + try isel.analyzeUse(extra.operand_a); + try isel.analyzeUse(extra.operand_b); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .select, .mul_add => { + const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; + const bin_op = isel.air.extraData(Air.Bin, pl_op.payload).data; + + try isel.analyzeUse(pl_op.operand); + try isel.analyzeUse(bin_op.lhs); + try isel.analyzeUse(bin_op.rhs); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .cmpxchg_weak, .cmpxchg_strong => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.Cmpxchg, ty_pl.payload).data; + + try isel.analyzeUse(extra.ptr); + try isel.analyzeUse(extra.expected_value); + try isel.analyzeUse(extra.new_value); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .atomic_load => { + const atomic_load = air_data[@intFromEnum(air_inst_index)].atomic_load; + + try isel.analyzeUse(atomic_load.ptr); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .atomic_rmw => { + const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; + const extra = isel.air.extraData(Air.AtomicRmw, pl_op.payload).data; + + try isel.analyzeUse(extra.operand); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .aggregate_init => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const elements: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[ty_pl.payload..][0..@intCast(ty_pl.ty.toType().arrayLen(zcu))]); + + for (elements) |element| try isel.analyzeUse(element); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .union_init => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.UnionInit, ty_pl.payload).data; + + try isel.analyzeUse(extra.init); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .prefetch => { + const prefetch = air_data[@intFromEnum(air_inst_index)].prefetch; + + try isel.analyzeUse(prefetch.ptr); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .field_parent_ptr => { + const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; + const extra = isel.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; + + try isel.analyzeUse(extra.field_ptr); + try isel.def_order.putNoClobber(gpa, air_inst_index, {}); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .set_err_return_trace, .c_va_end => { + const un_op = air_data[@intFromEnum(air_inst_index)].un_op; + + try isel.analyzeUse(un_op); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + .vector_store_elem => { + const vector_store_elem = air_data[@intFromEnum(air_inst_index)].vector_store_elem; + const bin_op = isel.air.extraData(Air.Bin, vector_store_elem.payload).data; + + try isel.analyzeUse(vector_store_elem.vector_ptr); + try isel.analyzeUse(bin_op.lhs); + try isel.analyzeUse(bin_op.rhs); + + air_body_index += 1; + air_inst_index = air_body[air_body_index]; + continue :air_tag air_tags[@intFromEnum(air_inst_index)]; + }, + } + assert(air_body_index == air_body.len); + isel.def_order.shrinkRetainingCapacity(initial_def_order_len); +} + +fn analyzeUse(isel: *Select, air_ref: Air.Inst.Ref) !void { + const air_inst_index = air_ref.toIndex() orelse return; + const def_order_index = isel.def_order.getIndex(air_inst_index).?; + + // Loop liveness + var active_loop_index = isel.active_loops.items.len; + while (active_loop_index > 0) { + const prev_active_loop_index = active_loop_index - 1; + const active_loop = isel.active_loops.items[prev_active_loop_index]; + if (def_order_index >= active_loop.get(isel).def_order) break; + active_loop_index = prev_active_loop_index; + } + if (active_loop_index < isel.active_loops.items.len) { + const active_loop = isel.active_loops.items[active_loop_index]; + const loop_live_gop = + try isel.loop_live.set.getOrPut(isel.pt.zcu.gpa, .{ active_loop, air_inst_index }); + if (!loop_live_gop.found_existing) active_loop.get(isel).live += 1; + } +} + +pub fn finishAnalysis(isel: *Select) !void { + const gpa = isel.pt.zcu.gpa; + + // Loop Liveness + if (isel.loops.count() > 0) { + try isel.loops.ensureUnusedCapacity(gpa, 1); + + const loop_live_len: u32 = @intCast(isel.loop_live.set.count()); + if (loop_live_len > 0) { + try isel.loop_live.list.resize(gpa, loop_live_len); + + const loops = isel.loops.values(); + for (loops[1..], loops[0 .. loops.len - 1]) |*loop, prev_loop| loop.live += prev_loop.live; + assert(loops[loops.len - 1].live == loop_live_len); + + for (isel.loop_live.set.keys()) |entry| { + const loop, const inst = entry; + const loop_live = &loop.get(isel).live; + loop_live.* -= 1; + isel.loop_live.list.items[loop_live.*] = inst; + } + assert(loops[0].live == 0); + } + + const invalid_gop = isel.loops.getOrPutAssumeCapacity(Loop.invalid); + assert(!invalid_gop.found_existing); + invalid_gop.value_ptr.live = loop_live_len; + } +} + +pub fn body(isel: *Select, air_body: []const Air.Inst.Index) !void { + const zcu = isel.pt.zcu; + const ip = &zcu.intern_pool; + const gpa = zcu.gpa; + + { + var live_reg_it = isel.live_registers.iterator(); + while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) { + _ => { + const ra = &live_reg_entry.value.get(isel).location_payload.small.register; + assert(ra.* == live_reg_entry.key); + ra.* = .zr; + live_reg_entry.value.* = .free; + }, + .allocating => live_reg_entry.value.* = .free, + .free => {}, + }; + } + + var air: struct { + isel: *Select, + tag_items: []const Air.Inst.Tag, + data_items: []const Air.Inst.Data, + body: []const Air.Inst.Index, + body_index: u32, + inst_index: Air.Inst.Index, + + fn tag(it: *@This(), inst_index: Air.Inst.Index) Air.Inst.Tag { + return it.tag_items[@intFromEnum(inst_index)]; + } + + fn data(it: *@This(), inst_index: Air.Inst.Index) Air.Inst.Data { + return it.data_items[@intFromEnum(inst_index)]; + } + + fn next(it: *@This()) ?Air.Inst.Tag { + if (it.body_index == 0) { + @branchHint(.unlikely); + return null; + } + it.body_index -= 1; + it.inst_index = it.body[it.body_index]; + wip_mir_log.debug("{f}", .{it.fmtAir(it.inst_index)}); + return it.tag(it.inst_index); + } + + fn fmtAir(it: @This(), inst: Air.Inst.Index) struct { + isel: *Select, + inst: Air.Inst.Index, + pub fn format(fmt_air: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + fmt_air.isel.air.writeInst(writer, fmt_air.inst, fmt_air.isel.pt, null); + } + } { + return .{ .isel = it.isel, .inst = inst }; + } + } = .{ + .isel = isel, + .tag_items = isel.air.instructions.items(.tag), + .data_items = isel.air.instructions.items(.data), + .body = air_body, + .body_index = @intCast(air_body.len), + .inst_index = undefined, + }; + air_tag: switch (air.next().?) { + else => |air_tag| return isel.fail("unimplemented {s}", .{@tagName(air_tag)}), + .arg => { + const arg_vi = isel.live_values.fetchRemove(air.inst_index).?.value; + defer arg_vi.deref(isel); + switch (arg_vi.parent(isel)) { + .unallocated, .stack_slot => if (arg_vi.hint(isel)) |arg_ra| { + try arg_vi.defLiveIn(isel, arg_ra, comptime &.initFill(.free)); + } else { + var arg_part_it = arg_vi.parts(isel); + while (arg_part_it.next()) |arg_part| { + try arg_part.defLiveIn(isel, arg_part.hint(isel).?, comptime &.initFill(.free)); + } + }, + .value, .constant => unreachable, + .address => |address_vi| try address_vi.defLiveIn(isel, address_vi.hint(isel).?, comptime &.initFill(.free)), + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .add, .add_optimized, .add_wrap, .sub, .sub_optimized, .sub_wrap => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) try res_vi.value.addOrSubtract(isel, ty, try isel.use(bin_op.lhs), switch (air_tag) { + else => unreachable, + .add, .add_wrap => .add, + .sub, .sub_wrap => .sub, + }, try isel.use(bin_op.rhs), .{ .wrap = switch (air_tag) { + else => unreachable, + .add, .sub => false, + .add_wrap, .sub_wrap => true, + } }) else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) continue :bits 32 else switch (air_tag) { + else => unreachable, + .add, .add_optimized => .fadd(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + .sub, .sub_optimized => .fsub(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + }, + 32 => switch (air_tag) { + else => unreachable, + .add, .add_optimized => .fadd(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + .sub, .sub_optimized => .fsub(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + }, + 64 => switch (air_tag) { + else => unreachable, + .add, .add_optimized => .fadd(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + .sub, .sub_optimized => .fsub(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + }, + }); + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (air_tag) { + else => unreachable, + .add, .add_optimized => switch (bits) { + else => unreachable, + 16 => "__addhf3", + 32 => "__addsf3", + 64 => "__adddf3", + 80 => "__addxf3", + 128 => "__addtf3", + }, + .sub, .sub_optimized => switch (bits) { + else => unreachable, + 16 => "__subhf3", + 32 => "__subsf3", + 64 => "__subdf3", + 80 => "__subxf3", + 128 => "__subtf3", + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .add_sat, .sub_sat => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 32, 64 => |bits| switch (int_info.signedness) { + .signed => return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + .unsigned => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const unsat_res_ra = try isel.allocIntReg(); + defer isel.freeReg(unsat_res_ra); + switch (air_tag) { + else => unreachable, + .add_sat => switch (bits) { + else => unreachable, + 32 => { + try isel.emit(.csinv(res_ra.w(), unsat_res_ra.w(), .wzr, .invert(.cs))); + try isel.emit(.adds(unsat_res_ra.w(), lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + }, + 64 => { + try isel.emit(.csinv(res_ra.x(), unsat_res_ra.x(), .xzr, .invert(.cs))); + try isel.emit(.adds(unsat_res_ra.x(), lhs_mat.ra.x(), .{ .register = rhs_mat.ra.x() })); + }, + }, + .sub_sat => switch (bits) { + else => unreachable, + 32 => { + try isel.emit(.csel(res_ra.w(), unsat_res_ra.w(), .wzr, .invert(.cc))); + try isel.emit(.subs(unsat_res_ra.w(), lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + }, + 64 => { + try isel.emit(.csel(res_ra.x(), unsat_res_ra.x(), .xzr, .invert(.cc))); + try isel.emit(.subs(unsat_res_ra.x(), lhs_mat.ra.x(), .{ .register = rhs_mat.ra.x() })); + }, + }, + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .mul, .mul_optimized, .mul_wrap => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) { + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + switch (int_info.signedness) { + .signed => switch (air_tag) { + else => unreachable, + .mul => break :unused try isel.emit(.orr(res_ra.w(), .wzr, .{ .register = .wzr })), + .mul_wrap => {}, + }, + .unsigned => {}, + } + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + try isel.emit(.@"and"(res_ra.w(), lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 2...32 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + switch (air_tag) { + else => unreachable, + .mul => {}, + .mul_wrap => switch (bits) { + else => unreachable, + 1...31 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }), + 32 => {}, + }, + } + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + try isel.emit(.madd(res_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w(), .wzr)); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 33...64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + switch (air_tag) { + else => unreachable, + .mul => {}, + .mul_wrap => switch (bits) { + else => unreachable, + 33...63 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }), + 64 => {}, + }, + } + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + try isel.emit(.madd(res_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x(), .xzr)); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 65...128 => |bits| { + var res_hi64_it = res_vi.value.field(ty, 8, 8); + const res_hi64_vi = try res_hi64_it.only(isel); + const res_hi64_ra = try res_hi64_vi.?.defReg(isel); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + const res_lo64_ra = try res_lo64_vi.?.defReg(isel); + if (res_hi64_ra == null and res_lo64_ra == null) break :unused; + if (res_hi64_ra) |res_ra| switch (air_tag) { + else => unreachable, + .mul => {}, + .mul_wrap => switch (bits) { + else => unreachable, + 65...127 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }), + 128 => {}, + }, + }; + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_lo64_mat, const rhs_lo64_mat = lo64_mat: { + const res_hi64_lock: RegLock = if (res_hi64_ra != null and res_lo64_ra != null) + isel.lockReg(res_hi64_ra.?) + else + .empty; + defer res_hi64_lock.unlock(isel); + + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + const rhs_lo64_mat = try rhs_lo64_vi.?.matReg(isel); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + const lhs_lo64_mat = try lhs_lo64_vi.?.matReg(isel); + break :lo64_mat .{ lhs_lo64_mat, rhs_lo64_mat }; + }; + if (res_lo64_ra) |res_ra| try isel.emit(.madd(res_ra.x(), lhs_lo64_mat.ra.x(), rhs_lo64_mat.ra.x(), .xzr)); + if (res_hi64_ra) |res_ra| { + var rhs_hi64_it = rhs_vi.field(ty, 8, 8); + const rhs_hi64_vi = try rhs_hi64_it.only(isel); + const rhs_hi64_mat = try rhs_hi64_vi.?.matReg(isel); + var lhs_hi64_it = lhs_vi.field(ty, 8, 8); + const lhs_hi64_vi = try lhs_hi64_it.only(isel); + const lhs_hi64_mat = try lhs_hi64_vi.?.matReg(isel); + const acc_ra = try isel.allocIntReg(); + defer isel.freeReg(acc_ra); + try isel.emit(.madd(res_ra.x(), lhs_hi64_mat.ra.x(), rhs_lo64_mat.ra.x(), acc_ra.x())); + try isel.emit(.madd(acc_ra.x(), lhs_lo64_mat.ra.x(), rhs_hi64_mat.ra.x(), acc_ra.x())); + try isel.emit(.umulh(acc_ra.x(), lhs_lo64_mat.ra.x(), rhs_lo64_mat.ra.x())); + try rhs_hi64_mat.finish(isel); + try lhs_hi64_mat.finish(isel); + } + try rhs_lo64_mat.finish(isel); + try lhs_lo64_mat.finish(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) + continue :bits 32 + else + .fmul(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + 32 => .fmul(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + 64 => .fmul(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + }); + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__mulhf3", + 32 => "__mulsf3", + 64 => "__muldf3", + 80 => "__mulxf3", + 128 => "__multf3", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .mul_sat => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + switch (int_info.signedness) { + .signed => try isel.emit(.orr(res_ra.w(), .wzr, .{ .register = .wzr })), + .unsigned => { + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + try isel.emit(.@"and"(res_ra.w(), lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + } + }, + 2...32 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const saturated_ra = switch (int_info.signedness) { + .signed => try isel.allocIntReg(), + .unsigned => switch (bits) { + else => unreachable, + 2...31 => try isel.allocIntReg(), + 32 => .zr, + }, + }; + defer if (saturated_ra != .zr) isel.freeReg(saturated_ra); + const unwrapped_ra = try isel.allocIntReg(); + defer isel.freeReg(unwrapped_ra); + try isel.emit(switch (saturated_ra) { + else => .csel(res_ra.w(), unwrapped_ra.w(), saturated_ra.w(), .eq), + .zr => .csinv(res_ra.w(), unwrapped_ra.w(), saturated_ra.w(), .eq), + }); + switch (bits) { + else => unreachable, + 2...7, 9...15, 17...31 => switch (int_info.signedness) { + .signed => { + const wrapped_ra = try isel.allocIntReg(); + defer isel.freeReg(wrapped_ra); + switch (bits) { + else => unreachable, + 1...7, 9...15 => { + try isel.emit(.subs(.wzr, unwrapped_ra.w(), .{ .register = wrapped_ra.w() })); + try isel.emit(.sbfm(wrapped_ra.w(), unwrapped_ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + })); + }, + 17...31 => { + try isel.emit(.subs(.xzr, unwrapped_ra.x(), .{ .register = wrapped_ra.x() })); + try isel.emit(.sbfm(wrapped_ra.x(), unwrapped_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + })); + }, + } + }, + .unsigned => switch (bits) { + else => unreachable, + 1...7, 9...15 => try isel.emit(.ands(.wzr, unwrapped_ra.w(), .{ .immediate = .{ + .N = .word, + .immr = @intCast(32 - bits), + .imms = @intCast(32 - bits - 1), + } })), + 17...31 => try isel.emit(.ands(.xzr, unwrapped_ra.x(), .{ .immediate = .{ + .N = .doubleword, + .immr = @intCast(64 - bits), + .imms = @intCast(64 - bits - 1), + } })), + }, + }, + 8 => try isel.emit(.subs(.wzr, unwrapped_ra.w(), .{ .extended_register = .{ + .register = unwrapped_ra.w(), + .extend = switch (int_info.signedness) { + .signed => .{ .sxtb = 0 }, + .unsigned => .{ .uxtb = 0 }, + }, + } })), + 16 => try isel.emit(.subs(.wzr, unwrapped_ra.w(), .{ .extended_register = .{ + .register = unwrapped_ra.w(), + .extend = switch (int_info.signedness) { + .signed => .{ .sxth = 0 }, + .unsigned => .{ .uxth = 0 }, + }, + } })), + 32 => try isel.emit(.subs(.xzr, unwrapped_ra.x(), .{ .extended_register = .{ + .register = unwrapped_ra.w(), + .extend = switch (int_info.signedness) { + .signed => .{ .sxtw = 0 }, + .unsigned => .{ .uxtw = 0 }, + }, + } })), + } + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + switch (int_info.signedness) { + .signed => { + try isel.emit(.eor(saturated_ra.w(), saturated_ra.w(), .{ .immediate = .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1 - 1), + } })); + try isel.emit(.sbfm(saturated_ra.w(), saturated_ra.w(), .{ + .N = .word, + .immr = @intCast(bits - 1), + .imms = @intCast(bits - 1 + 1 - 1), + })); + try isel.emit(.eor(saturated_ra.w(), lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + }, + .unsigned => switch (bits) { + else => unreachable, + 2...31 => try isel.movImmediate(saturated_ra.w(), @as(u32, std.math.maxInt(u32)) >> @intCast(32 - bits)), + 32 => {}, + }, + } + switch (bits) { + else => unreachable, + 2...16 => try isel.emit(.madd(unwrapped_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w(), .wzr)), + 17...32 => switch (int_info.signedness) { + .signed => try isel.emit(.smaddl(unwrapped_ra.x(), lhs_mat.ra.w(), rhs_mat.ra.w(), .xzr)), + .unsigned => try isel.emit(.umaddl(unwrapped_ra.x(), lhs_mat.ra.w(), rhs_mat.ra.w(), .xzr)), + }, + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 33...64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const saturated_ra = switch (int_info.signedness) { + .signed => try isel.allocIntReg(), + .unsigned => switch (bits) { + else => unreachable, + 33...63 => try isel.allocIntReg(), + 64 => .zr, + }, + }; + defer if (saturated_ra != .zr) isel.freeReg(saturated_ra); + const unwrapped_lo64_ra = try isel.allocIntReg(); + defer isel.freeReg(unwrapped_lo64_ra); + const unwrapped_hi64_ra = try isel.allocIntReg(); + defer isel.freeReg(unwrapped_hi64_ra); + try isel.emit(switch (saturated_ra) { + else => .csel(res_ra.x(), unwrapped_lo64_ra.x(), saturated_ra.x(), .eq), + .zr => .csinv(res_ra.x(), unwrapped_lo64_ra.x(), saturated_ra.x(), .eq), + }); + switch (int_info.signedness) { + .signed => switch (bits) { + else => unreachable, + 32...63 => { + const wrapped_lo64_ra = try isel.allocIntReg(); + defer isel.freeReg(wrapped_lo64_ra); + try isel.emit(.ccmp( + unwrapped_lo64_ra.x(), + .{ .register = wrapped_lo64_ra.x() }, + .{ .n = false, .z = false, .c = false, .v = false }, + .eq, + )); + try isel.emit(.subs(.xzr, unwrapped_hi64_ra.x(), .{ .shifted_register = .{ + .register = unwrapped_lo64_ra.x(), + .shift = .{ .asr = 63 }, + } })); + try isel.emit(.sbfm(wrapped_lo64_ra.x(), unwrapped_lo64_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + })); + }, + 64 => try isel.emit(.subs(.xzr, unwrapped_hi64_ra.x(), .{ .shifted_register = .{ + .register = unwrapped_lo64_ra.x(), + .shift = .{ .asr = @intCast(bits - 1) }, + } })), + }, + .unsigned => switch (bits) { + else => unreachable, + 32...63 => { + const overflow_ra = try isel.allocIntReg(); + defer isel.freeReg(overflow_ra); + try isel.emit(.subs(.xzr, overflow_ra.x(), .{ .immediate = 0 })); + try isel.emit(.orr(overflow_ra.x(), unwrapped_hi64_ra.x(), .{ .shifted_register = .{ + .register = unwrapped_lo64_ra.x(), + .shift = .{ .lsr = @intCast(bits) }, + } })); + }, + 64 => try isel.emit(.subs(.xzr, unwrapped_hi64_ra.x(), .{ .immediate = 0 })), + }, + } + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + switch (int_info.signedness) { + .signed => { + try isel.emit(.eor(saturated_ra.x(), saturated_ra.x(), .{ .immediate = .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1 - 1), + } })); + try isel.emit(.sbfm(saturated_ra.x(), saturated_ra.x(), .{ + .N = .doubleword, + .immr = @intCast(bits - 1), + .imms = @intCast(bits - 1 + 1 - 1), + })); + try isel.emit(.eor(saturated_ra.x(), lhs_mat.ra.x(), .{ .register = rhs_mat.ra.x() })); + try isel.emit(.madd(unwrapped_lo64_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x(), .xzr)); + try isel.emit(.smulh(unwrapped_hi64_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x())); + }, + .unsigned => { + switch (bits) { + else => unreachable, + 32...63 => try isel.movImmediate(saturated_ra.x(), @as(u64, std.math.maxInt(u64)) >> @intCast(64 - bits)), + 64 => {}, + } + try isel.emit(.madd(unwrapped_lo64_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x(), .xzr)); + try isel.emit(.umulh(unwrapped_hi64_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x())); + }, + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .div_float, .div_float_optimized => { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) + continue :bits 32 + else + .fdiv(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + 32 => .fdiv(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + 64 => .fdiv(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + }); + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__divhf3", + 32 => "__divsf3", + 64 => "__divdf3", + 80 => "__divxf3", + 128 => "__divtf3", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .div_trunc, .div_trunc_optimized, .div_floor, .div_floor_optimized, .div_exact, .div_exact_optimized => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) { + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1...64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const div_ra = div_ra: switch (air_tag) { + else => unreachable, + .div_trunc, .div_exact => res_ra, + .div_floor => switch (int_info.signedness) { + .signed => { + const div_ra = try isel.allocIntReg(); + errdefer isel.freeReg(div_ra); + const rem_ra = try isel.allocIntReg(); + defer isel.freeReg(rem_ra); + switch (bits) { + else => unreachable, + 1...32 => { + try isel.emit(.sub(res_ra.w(), div_ra.w(), .{ .register = rem_ra.w() })); + try isel.emit(.csinc(rem_ra.w(), .wzr, .wzr, .ge)); + try isel.emit(.ccmp( + rem_ra.w(), + .{ .immediate = 0 }, + .{ .n = false, .z = false, .c = false, .v = false }, + .ne, + )); + try isel.emit(.eor(rem_ra.w(), rem_ra.w(), .{ .register = rhs_mat.ra.w() })); + try isel.emit(.subs(.wzr, rem_ra.w(), .{ .immediate = 0 })); + try isel.emit(.msub(rem_ra.w(), div_ra.w(), rhs_mat.ra.w(), lhs_mat.ra.w())); + }, + 33...64 => { + try isel.emit(.sub(res_ra.x(), div_ra.x(), .{ .register = rem_ra.x() })); + try isel.emit(.csinc(rem_ra.x(), .xzr, .xzr, .ge)); + try isel.emit(.ccmp( + rem_ra.x(), + .{ .immediate = 0 }, + .{ .n = false, .z = false, .c = false, .v = false }, + .ne, + )); + try isel.emit(.eor(rem_ra.x(), rem_ra.x(), .{ .register = rhs_mat.ra.x() })); + try isel.emit(.subs(.xzr, rem_ra.x(), .{ .immediate = 0 })); + try isel.emit(.msub(rem_ra.x(), div_ra.x(), rhs_mat.ra.x(), lhs_mat.ra.x())); + }, + } + break :div_ra div_ra; + }, + .unsigned => res_ra, + }, + }; + defer if (div_ra != res_ra) isel.freeReg(div_ra); + try isel.emit(switch (bits) { + else => unreachable, + 1...32 => switch (int_info.signedness) { + .signed => .sdiv(div_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + .unsigned => .udiv(div_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + }, + 33...64 => switch (int_info.signedness) { + .signed => .sdiv(div_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + .unsigned => .udiv(div_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + }, + }); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 65...128 => { + switch (air_tag) { + else => unreachable, + .div_trunc, .div_exact => {}, + .div_floor => switch (int_info.signedness) { + .signed => return isel.fail("unimplemented {s}", .{@tagName(air_tag)}), + .unsigned => {}, + }, + } + + try call.prepareReturn(isel); + var res_hi64_it = res_vi.value.field(ty, 8, 8); + const res_hi64_vi = try res_hi64_it.only(isel); + try call.returnLiveIn(isel, res_hi64_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (int_info.signedness) { + .signed => "__divti3", + .unsigned => "__udivti3", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + var rhs_hi64_it = rhs_vi.field(ty, 8, 8); + const rhs_hi64_vi = try rhs_hi64_it.only(isel); + try call.paramLiveOut(isel, rhs_hi64_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi64_it = lhs_vi.field(ty, 8, 8); + const lhs_hi64_vi = try lhs_hi64_it.only(isel); + try call.paramLiveOut(isel, lhs_hi64_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + try call.finishParams(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) continue :bits 32 else { + switch (air_tag) { + else => unreachable, + .div_trunc, .div_trunc_optimized => try isel.emit(.frintz(res_ra.h(), res_ra.h())), + .div_floor, .div_floor_optimized => try isel.emit(.frintm(res_ra.h(), res_ra.h())), + .div_exact, .div_exact_optimized => {}, + } + try isel.emit(.fdiv(res_ra.h(), lhs_ra.h(), rhs_ra.h())); + }, + 32 => { + switch (air_tag) { + else => unreachable, + .div_trunc, .div_trunc_optimized => try isel.emit(.frintz(res_ra.s(), res_ra.s())), + .div_floor, .div_floor_optimized => try isel.emit(.frintm(res_ra.s(), res_ra.s())), + .div_exact, .div_exact_optimized => {}, + } + try isel.emit(.fdiv(res_ra.s(), lhs_ra.s(), rhs_ra.s())); + }, + 64 => { + switch (air_tag) { + else => unreachable, + .div_trunc, .div_trunc_optimized => try isel.emit(.frintz(res_ra.d(), res_ra.d())), + .div_floor, .div_floor_optimized => try isel.emit(.frintm(res_ra.d(), res_ra.d())), + .div_exact, .div_exact_optimized => {}, + } + try isel.emit(.fdiv(res_ra.d(), lhs_ra.d(), rhs_ra.d())); + }, + } + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + switch (air_tag) { + else => unreachable, + .div_trunc, .div_trunc_optimized => { + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__trunch", + 32 => "truncf", + 64 => "trunc", + 80 => "__truncx", + 128 => "truncq", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + }, + .div_floor, .div_floor_optimized => { + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__floorh", + 32 => "floorf", + 64 => "floor", + 80 => "__floorx", + 128 => "floorq", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + }, + .div_exact, .div_exact_optimized => {}, + } + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__divhf3", + 32 => "__divsf3", + 64 => "__divdf3", + 80 => "__divxf3", + 128 => "__divtf3", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .rem => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) { + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + if (int_info.bits > 64) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const div_ra = try isel.allocIntReg(); + defer isel.freeReg(div_ra); + switch (int_info.bits) { + else => unreachable, + 1...32 => { + try isel.emit(.msub(res_ra.w(), div_ra.w(), rhs_mat.ra.w(), lhs_mat.ra.w())); + try isel.emit(switch (int_info.signedness) { + .signed => .sdiv(div_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + .unsigned => .udiv(div_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + }); + }, + 33...64 => { + try isel.emit(.msub(res_ra.x(), div_ra.x(), rhs_mat.ra.x(), lhs_mat.ra.x())); + try isel.emit(switch (int_info.signedness) { + .signed => .sdiv(div_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + .unsigned => .udiv(div_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + }); + }, + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + } else { + const bits = ty.floatBits(isel.target); + + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__fmodh", + 32 => "fmodf", + 64 => "fmod", + 80 => "__fmodx", + 128 => "fmodq", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ptr_add, .ptr_sub => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + + const ty_pl = air.data(air.inst_index).ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + const elem_size = ty_pl.ty.toType().elemType2(zcu).abiSize(zcu); + + const base_vi = try isel.use(bin_op.lhs); + var base_part_it = base_vi.field(ty_pl.ty.toType(), 0, 8); + const base_part_vi = try base_part_it.only(isel); + const base_part_mat = try base_part_vi.?.matReg(isel); + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(res_ra, base_part_mat.ra, switch (air_tag) { + else => unreachable, + .ptr_add => .add, + .ptr_sub => .sub, + }, elem_size, index_vi); + try base_part_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .max, .min => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) { + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + if (int_info.bits > 64) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const cond: codegen.aarch64.encoding.ConditionCode = switch (air_tag) { + else => unreachable, + .max => switch (int_info.signedness) { + .signed => .ge, + .unsigned => .hs, + }, + .min => switch (int_info.signedness) { + .signed => .lt, + .unsigned => .lo, + }, + }; + switch (int_info.bits) { + else => unreachable, + 1...32 => { + try isel.emit(.csel(res_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w(), cond)); + try isel.emit(.subs(.wzr, lhs_mat.ra.w(), .{ .register = rhs_mat.ra.w() })); + }, + 33...64 => { + try isel.emit(.csel(res_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x(), cond)); + try isel.emit(.subs(.xzr, lhs_mat.ra.x(), .{ .register = rhs_mat.ra.x() })); + }, + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + } else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) continue :bits 32 else switch (air_tag) { + else => unreachable, + .max => .fmaxnm(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + .min => .fminnm(res_ra.h(), lhs_ra.h(), rhs_ra.h()), + }, + 32 => switch (air_tag) { + else => unreachable, + .max => .fmaxnm(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + .min => .fminnm(res_ra.s(), lhs_ra.s(), rhs_ra.s()), + }, + 64 => switch (air_tag) { + else => unreachable, + .max => .fmaxnm(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + .min => .fminnm(res_ra.d(), lhs_ra.d(), rhs_ra.d()), + }, + }); + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (air_tag) { + else => unreachable, + .max => switch (bits) { + else => unreachable, + 16 => "__fmaxh", + 32 => "fmaxf", + 64 => "fmax", + 80 => "__fmaxx", + 128 => "fmaxq", + }, + .min => switch (bits) { + else => unreachable, + 16 => "__fminh", + 32 => "fminf", + 64 => "fmin", + 80 => "__fminx", + 128 => "fminq", + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .add_with_overflow, .sub_with_overflow => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| { + defer res_vi.value.deref(isel); + + const ty_pl = air.data(air.inst_index).ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + const ty = isel.air.typeOf(bin_op.lhs, ip); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const ty_size = lhs_vi.size(isel); + var overflow_it = res_vi.value.field(ty_pl.ty.toType(), ty_size, 1); + const overflow_vi = try overflow_it.only(isel); + var wrapped_it = res_vi.value.field(ty_pl.ty.toType(), 0, ty_size); + const wrapped_vi = try wrapped_it.only(isel); + try wrapped_vi.?.addOrSubtract(isel, ty, lhs_vi, switch (air_tag) { + else => unreachable, + .add_with_overflow => .add, + .sub_with_overflow => .sub, + }, rhs_vi, .{ .wrap = true, .overflow_ra = try overflow_vi.?.defReg(isel) orelse .zr }); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .alloc, .ret_ptr => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |ptr_vi| unused: { + defer ptr_vi.value.deref(isel); + switch (air_tag) { + else => unreachable, + .alloc => {}, + .ret_ptr => if (isel.live_values.get(Block.main)) |ret_vi| switch (ret_vi.parent(isel)) { + .unallocated, .stack_slot => {}, + .value, .constant => unreachable, + .address => break :unused, + }, + } + const ptr_ra = try ptr_vi.value.defReg(isel) orelse break :unused; + + const ty = air.data(air.inst_index).ty; + const slot_size = ty.childType(zcu).abiSize(zcu); + const slot_align = ty.ptrAlignment(zcu); + const slot_offset = slot_align.forward(isel.stack_size); + isel.stack_size = @intCast(slot_offset + slot_size); + const lo12: u12 = @truncate(slot_offset >> 0); + const hi12: u12 = @intCast(slot_offset >> 12); + if (hi12 > 0) try isel.emit(.add( + ptr_ra.x(), + if (lo12 > 0) ptr_ra.x() else .sp, + .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } }, + )); + if (lo12 > 0 or hi12 == 0) try isel.emit(.add(ptr_ra.x(), .sp, .{ .immediate = lo12 })); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .assembly => { + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.Asm, ty_pl.payload); + var extra_index = extra.end; + const outputs: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra_index..][0..extra.data.flags.outputs_len]); + extra_index += outputs.len; + const inputs: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra_index..][0..extra.data.inputs_len]); + extra_index += inputs.len; + + var as: codegen.aarch64.Assemble = .{ + .source = undefined, + .operands = .empty, + }; + defer as.operands.deinit(gpa); + + for (outputs) |output| { + const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); + const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]), 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_index += (constraint.len + name.len + (2 + 3)) / 4; + + switch (output) { + else => return isel.fail("invalid constraint: '{s}'", .{constraint}), + .none => if (std.mem.startsWith(u8, constraint, "={") and std.mem.endsWith(u8, constraint, "}")) { + const output_reg = Register.parse(constraint["={".len .. constraint.len - "}".len]) orelse + return isel.fail("invalid constraint: '{s}'", .{constraint}); + const output_ra = output_reg.alias; + if (isel.live_values.fetchRemove(air.inst_index)) |output_vi| { + defer output_vi.value.deref(isel); + try output_vi.value.defLiveIn(isel, output_reg.alias, comptime &.initFill(.free)); + isel.freeReg(output_ra); + } + if (!std.mem.eql(u8, name, "_")) { + const operand_gop = try as.operands.getOrPut(gpa, name); + if (operand_gop.found_existing) return isel.fail("duplicate output name: '{s}'", .{name}); + operand_gop.value_ptr.* = .{ .register = switch (ty_pl.ty.toType().abiSize(zcu)) { + 0 => unreachable, + 1...4 => output_ra.w(), + 5...8 => output_ra.x(), + else => return isel.fail("too big output type: '{f}'", .{isel.fmtType(ty_pl.ty.toType())}), + } }; + } + } else if (std.mem.eql(u8, constraint, "=r")) { + const output_ra = if (isel.live_values.fetchRemove(air.inst_index)) |output_vi| output_ra: { + defer output_vi.value.deref(isel); + break :output_ra try output_vi.value.defReg(isel) orelse try isel.allocIntReg(); + } else try isel.allocIntReg(); + if (!std.mem.eql(u8, name, "_")) { + const operand_gop = try as.operands.getOrPut(gpa, name); + if (operand_gop.found_existing) return isel.fail("duplicate output name: '{s}'", .{name}); + operand_gop.value_ptr.* = .{ .register = switch (ty_pl.ty.toType().abiSize(zcu)) { + 0 => unreachable, + 1...4 => output_ra.w(), + 5...8 => output_ra.x(), + else => return isel.fail("too big output type: '{f}'", .{isel.fmtType(ty_pl.ty.toType())}), + } }; + } + } else return isel.fail("invalid constraint: '{s}'", .{constraint}), + } + } + + const input_mats = try gpa.alloc(Value.Materialize, inputs.len); + defer gpa.free(input_mats); + const inputs_extra_index = extra_index; + for (inputs, input_mats) |input, *input_mat| { + const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); + const constraint = std.mem.sliceTo(extra_bytes, 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_index += (constraint.len + name.len + (2 + 3)) / 4; + + if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) { + const input_reg = Register.parse(constraint["{".len .. constraint.len - "}".len]) orelse + return isel.fail("invalid constraint: '{s}'", .{constraint}); + input_mat.* = .{ .vi = try isel.use(input), .ra = input_reg.alias }; + if (!std.mem.eql(u8, name, "_")) { + const operand_gop = try as.operands.getOrPut(gpa, name); + if (operand_gop.found_existing) return isel.fail("duplicate input name: '{s}'", .{name}); + const input_ty = isel.air.typeOf(input, ip); + operand_gop.value_ptr.* = .{ .register = switch (input_ty.abiSize(zcu)) { + 0 => unreachable, + 1...4 => input_reg.alias.w(), + 5...8 => input_reg.alias.x(), + else => return isel.fail("too big input type: '{f}'", .{ + isel.fmtType(isel.air.typeOf(input, ip)), + }), + } }; + } + } else if (std.mem.eql(u8, constraint, "r")) { + const input_vi = try isel.use(input); + input_mat.* = try input_vi.matReg(isel); + if (!std.mem.eql(u8, name, "_")) { + const operand_gop = try as.operands.getOrPut(gpa, name); + if (operand_gop.found_existing) return isel.fail("duplicate input name: '{s}'", .{name}); + operand_gop.value_ptr.* = .{ .register = switch (input_vi.size(isel)) { + 0 => unreachable, + 1...4 => input_mat.ra.w(), + 5...8 => input_mat.ra.x(), + else => return isel.fail("too big input type: '{f}'", .{ + isel.fmtType(isel.air.typeOf(input, ip)), + }), + } }; + } + } else if (std.mem.eql(u8, name, "_")) { + input_mat.vi = try isel.use(input); + } else return isel.fail("invalid constraint: '{s}'", .{constraint}); + } + + const clobbers = ip.indexToKey(extra.data.clobbers).aggregate; + const clobbers_ty: ZigType = .fromInterned(clobbers.ty); + for (0..clobbers_ty.structFieldCount(zcu)) |field_index| { + switch (switch (clobbers.storage) { + .bytes => unreachable, + .elems => |elems| elems[field_index], + .repeated_elem => |repeated_elem| repeated_elem, + }) { + else => unreachable, + .bool_false => continue, + .bool_true => {}, + } + const clobber_name = clobbers_ty.structFieldName(field_index, zcu).toSlice(ip).?; + if (std.mem.eql(u8, clobber_name, "memory")) continue; + if (std.mem.eql(u8, clobber_name, "nzcv")) continue; + const clobber_reg = Register.parse(clobber_name) orelse + return isel.fail("unable to parse clobber: '{s}'", .{clobber_name}); + const live_vi = isel.live_registers.getPtr(clobber_reg.alias); + switch (live_vi.*) { + _ => {}, + .allocating => return isel.fail("clobbered twice: '{s}'", .{clobber_name}), + .free => live_vi.* = .allocating, + } + } + for (0..clobbers_ty.structFieldCount(zcu)) |field_index| { + switch (switch (clobbers.storage) { + .bytes => unreachable, + .elems => |elems| elems[field_index], + .repeated_elem => |repeated_elem| repeated_elem, + }) { + else => unreachable, + .bool_false => continue, + .bool_true => {}, + } + const clobber_name = clobbers_ty.structFieldName(field_index, zcu).toSlice(ip).?; + if (std.mem.eql(u8, clobber_name, "memory")) continue; + if (std.mem.eql(u8, clobber_name, "nzcv")) continue; + const clobber_ra = Register.parse(clobber_name).?.alias; + const live_vi = isel.live_registers.getPtr(clobber_ra); + switch (live_vi.*) { + _ => { + if (!try isel.fill(clobber_ra)) + return isel.fail("unable to clobber: '{s}'", .{clobber_name}); + assert(live_vi.* == .free); + live_vi.* = .allocating; + }, + .allocating => {}, + .free => unreachable, + } + } + + as.source = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..])[0..extra.data.source_len :0]; + const asm_start = isel.instructions.items.len; + while (as.nextInstruction() catch |err| switch (err) { + error.InvalidSyntax => { + const remaining_source = std.mem.span(as.source); + return isel.fail("unable to assemble: '{s}'", .{std.mem.trim( + u8, + as.source[0 .. std.mem.indexOfScalar(u8, remaining_source, '\n') orelse remaining_source.len], + &std.ascii.whitespace, + )}); + }, + }) |instruction| try isel.emit(instruction); + std.mem.reverse(codegen.aarch64.encoding.Instruction, isel.instructions.items[asm_start..]); + + extra_index = inputs_extra_index; + for (input_mats) |input_mat| { + const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); + const constraint = std.mem.sliceTo(extra_bytes, 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_index += (constraint.len + name.len + (2 + 3)) / 4; + + if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) { + try input_mat.vi.liveOut(isel, input_mat.ra); + } else if (std.mem.eql(u8, constraint, "r")) { + try input_mat.finish(isel); + } else if (std.mem.eql(u8, name, "_")) { + try input_mat.vi.mat(isel); + } else unreachable; + } + + for (0..clobbers_ty.structFieldCount(zcu)) |field_index| { + switch (switch (clobbers.storage) { + .bytes => unreachable, + .elems => |elems| elems[field_index], + .repeated_elem => |repeated_elem| repeated_elem, + }) { + else => unreachable, + .bool_false => continue, + .bool_true => {}, + } + const clobber_name = clobbers_ty.structFieldName(field_index, zcu).toSlice(ip).?; + if (std.mem.eql(u8, clobber_name, "memory")) continue; + if (std.mem.eql(u8, clobber_name, "cc")) continue; + isel.freeReg(Register.parse(clobber_name).?.alias); + } + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .bit_and, .bit_or, .xor, .bool_and, .bool_or => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + const int_info: std.builtin.Type.Int = if (ty.toIntern() == .bool_type) + .{ .signedness = .unsigned, .bits = 1 } + else if (ty.isAbiInt(zcu)) + ty.intInfo(zcu) + else + return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + if (int_info.bits > 128) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + var offset = res_vi.value.size(isel); + while (offset > 0) { + const size = @min(offset, 8); + offset -= size; + var res_part_it = res_vi.value.field(ty, offset, size); + const res_part_vi = try res_part_it.only(isel); + const res_part_ra = try res_part_vi.?.defReg(isel) orelse continue; + var lhs_part_it = lhs_vi.field(ty, offset, size); + const lhs_part_vi = try lhs_part_it.only(isel); + const lhs_part_mat = try lhs_part_vi.?.matReg(isel); + var rhs_part_it = rhs_vi.field(ty, offset, size); + const rhs_part_vi = try rhs_part_it.only(isel); + const rhs_part_mat = try rhs_part_vi.?.matReg(isel); + try isel.emit(switch (air_tag) { + else => unreachable, + .bit_and, .bool_and => switch (size) { + else => unreachable, + 1, 2, 4 => .@"and"(res_part_ra.w(), lhs_part_mat.ra.w(), .{ .register = rhs_part_mat.ra.w() }), + 8 => .@"and"(res_part_ra.x(), lhs_part_mat.ra.x(), .{ .register = rhs_part_mat.ra.x() }), + }, + .bit_or, .bool_or => switch (size) { + else => unreachable, + 1, 2, 4 => .orr(res_part_ra.w(), lhs_part_mat.ra.w(), .{ .register = rhs_part_mat.ra.w() }), + 8 => .orr(res_part_ra.x(), lhs_part_mat.ra.x(), .{ .register = rhs_part_mat.ra.x() }), + }, + .xor => switch (size) { + else => unreachable, + 1, 2, 4 => .eor(res_part_ra.w(), lhs_part_mat.ra.w(), .{ .register = rhs_part_mat.ra.w() }), + 8 => .eor(res_part_ra.x(), lhs_part_mat.ra.x(), .{ .register = rhs_part_mat.ra.x() }), + }, + }); + try rhs_part_mat.finish(isel); + try lhs_part_mat.finish(isel); + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .shr, .shr_exact, .shl, .shl_exact => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1...64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + switch (air_tag) { + else => unreachable, + .shr, .shr_exact, .shl_exact => {}, + .shl => switch (bits) { + else => unreachable, + 1...31 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }), + 32 => {}, + 33...63 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }), + 64 => {}, + }, + } + + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + try isel.emit(switch (air_tag) { + else => unreachable, + .shr, .shr_exact => switch (bits) { + else => unreachable, + 1...32 => switch (int_info.signedness) { + .signed => .asrv(res_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + .unsigned => .lsrv(res_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + }, + 33...64 => switch (int_info.signedness) { + .signed => .asrv(res_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + .unsigned => .lsrv(res_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + }, + }, + .shl, .shl_exact => switch (bits) { + else => unreachable, + 1...32 => .lslv(res_ra.w(), lhs_mat.ra.w(), rhs_mat.ra.w()), + 33...64 => .lslv(res_ra.x(), lhs_mat.ra.x(), rhs_mat.ra.x()), + }, + }); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 65...128 => |bits| { + var res_hi64_it = res_vi.value.field(ty, 8, 8); + const res_hi64_vi = try res_hi64_it.only(isel); + const res_hi64_ra = try res_hi64_vi.?.defReg(isel); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + const res_lo64_ra = try res_lo64_vi.?.defReg(isel); + if (res_hi64_ra == null and res_lo64_ra == null) break :unused; + if (res_hi64_ra) |res_ra| switch (air_tag) { + else => unreachable, + .shr, .shr_exact, .shl_exact => {}, + .shl => switch (bits) { + else => unreachable, + 65...127 => try isel.emit(switch (int_info.signedness) { + .signed => .sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 64 - 1), + }), + .unsigned => .ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 64 - 1), + }), + }), + 128 => {}, + }, + }; + + const lhs_vi = try isel.use(bin_op.lhs); + const lhs_hi64_mat = lhs_hi64_mat: { + const res_lock: RegLock = switch (air_tag) { + else => unreachable, + .shr, .shr_exact => switch (int_info.signedness) { + .signed => if (res_lo64_ra) |res_ra| isel.lockReg(res_ra) else .empty, + .unsigned => .empty, + }, + .shl, .shl_exact => .empty, + }; + defer res_lock.unlock(isel); + var lhs_hi64_it = lhs_vi.field(ty, 8, 8); + const lhs_hi64_vi = try lhs_hi64_it.only(isel); + break :lhs_hi64_mat try lhs_hi64_vi.?.matReg(isel); + }; + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + const lhs_lo64_mat = try lhs_lo64_vi.?.matReg(isel); + const rhs_vi = try isel.use(bin_op.rhs); + const rhs_mat = try rhs_vi.matReg(isel); + const lo64_ra = lo64_ra: { + const res_lock: RegLock = switch (air_tag) { + else => unreachable, + .shr, .shr_exact => switch (int_info.signedness) { + .signed => if (res_lo64_ra) |res_ra| isel.tryLockReg(res_ra) else .empty, + .unsigned => .empty, + }, + .shl, .shl_exact => if (res_hi64_ra) |res_ra| isel.tryLockReg(res_ra) else .empty, + }; + defer res_lock.unlock(isel); + break :lo64_ra try isel.allocIntReg(); + }; + defer isel.freeReg(lo64_ra); + const hi64_ra = hi64_ra: { + const res_lock: RegLock = switch (air_tag) { + else => unreachable, + .shr, .shr_exact => if (res_lo64_ra) |res_ra| isel.tryLockReg(res_ra) else .empty, + .shl, .shl_exact => .empty, + }; + defer res_lock.unlock(isel); + break :hi64_ra try isel.allocIntReg(); + }; + defer isel.freeReg(hi64_ra); + switch (air_tag) { + else => unreachable, + .shr, .shr_exact => { + if (res_hi64_ra) |res_ra| switch (int_info.signedness) { + .signed => { + try isel.emit(.csel(res_ra.x(), hi64_ra.x(), lo64_ra.x(), .eq)); + try isel.emit(.sbfm(lo64_ra.x(), lhs_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = @intCast(bits - 64 - 1), + .imms = @intCast(bits - 64 - 1), + })); + }, + .unsigned => try isel.emit(.csel(res_ra.x(), hi64_ra.x(), .xzr, .eq)), + }; + if (res_lo64_ra) |res_ra| try isel.emit(.csel(res_ra.x(), lo64_ra.x(), hi64_ra.x(), .eq)); + switch (int_info.signedness) { + .signed => try isel.emit(.asrv(hi64_ra.x(), lhs_hi64_mat.ra.x(), rhs_mat.ra.x())), + .unsigned => try isel.emit(.lsrv(hi64_ra.x(), lhs_hi64_mat.ra.x(), rhs_mat.ra.x())), + } + }, + .shl, .shl_exact => { + if (res_lo64_ra) |res_ra| try isel.emit(.csel(res_ra.x(), lo64_ra.x(), .xzr, .eq)); + if (res_hi64_ra) |res_ra| try isel.emit(.csel(res_ra.x(), hi64_ra.x(), lo64_ra.x(), .eq)); + try isel.emit(.lslv(lo64_ra.x(), lhs_lo64_mat.ra.x(), rhs_mat.ra.x())); + }, + } + try isel.emit(.ands(.wzr, rhs_mat.ra.w(), .{ .immediate = .{ .N = .word, .immr = 32 - 6, .imms = 0 } })); + switch (air_tag) { + else => unreachable, + .shr, .shr_exact => if (res_lo64_ra) |_| { + try isel.emit(.orr( + lo64_ra.x(), + lo64_ra.x(), + .{ .shifted_register = .{ .register = hi64_ra.x(), .shift = .{ .lsl = 1 } } }, + )); + try isel.emit(.lslv(hi64_ra.x(), lhs_hi64_mat.ra.x(), hi64_ra.x())); + try isel.emit(.lsrv(lo64_ra.x(), lhs_lo64_mat.ra.x(), rhs_mat.ra.x())); + try isel.emit(.orn(hi64_ra.w(), .wzr, .{ .register = rhs_mat.ra.w() })); + }, + .shl, .shl_exact => if (res_hi64_ra) |_| { + try isel.emit(.orr( + hi64_ra.x(), + hi64_ra.x(), + .{ .shifted_register = .{ .register = lo64_ra.x(), .shift = .{ .lsr = 1 } } }, + )); + try isel.emit(.lsrv(lo64_ra.x(), lhs_lo64_mat.ra.x(), lo64_ra.x())); + try isel.emit(.lslv(hi64_ra.x(), lhs_hi64_mat.ra.x(), rhs_mat.ra.x())); + try isel.emit(.orn(lo64_ra.w(), .wzr, .{ .register = rhs_mat.ra.w() })); + }, + } + try rhs_mat.finish(isel); + try lhs_lo64_mat.finish(isel); + try lhs_hi64_mat.finish(isel); + break :unused; + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .not => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = ty_op.ty.toType(); + const int_info: std.builtin.Type.Int = int_info: { + if (ty_op.ty == .bool_type) break :int_info .{ .signedness = .unsigned, .bits = 1 }; + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + break :int_info ty.intInfo(zcu); + }; + if (int_info.bits > 128) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const src_vi = try isel.use(ty_op.operand); + var offset = res_vi.value.size(isel); + while (offset > 0) { + const size = @min(offset, 8); + offset -= size; + var res_part_it = res_vi.value.field(ty, offset, size); + const res_part_vi = try res_part_it.only(isel); + const res_part_ra = try res_part_vi.?.defReg(isel) orelse continue; + var src_part_it = src_vi.field(ty, offset, size); + const src_part_vi = try src_part_it.only(isel); + const src_part_mat = try src_part_vi.?.matReg(isel); + try isel.emit(switch (int_info.signedness) { + .signed => switch (size) { + else => unreachable, + 1, 2, 4 => .orn(res_part_ra.w(), .wzr, .{ .register = src_part_mat.ra.w() }), + 8 => .orn(res_part_ra.x(), .xzr, .{ .register = src_part_mat.ra.x() }), + }, + .unsigned => switch (@min(int_info.bits - 8 * offset, 64)) { + else => unreachable, + 1...31 => |bits| .eor(res_part_ra.w(), src_part_mat.ra.w(), .{ .immediate = .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + } }), + 32 => .orn(res_part_ra.w(), .wzr, .{ .register = src_part_mat.ra.w() }), + 33...63 => |bits| .eor(res_part_ra.x(), src_part_mat.ra.x(), .{ .immediate = .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + } }), + 64 => .orn(res_part_ra.x(), .xzr, .{ .register = src_part_mat.ra.x() }), + }, + }); + try src_part_mat.finish(isel); + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .bitcast => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const dst_tag = dst_ty.zigTypeTag(zcu); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + const src_tag = src_ty.zigTypeTag(zcu); + if (dst_ty.isAbiInt(zcu) and (src_tag == .bool or src_ty.isAbiInt(zcu))) { + const dst_int_info = dst_ty.intInfo(zcu); + const src_int_info: std.builtin.Type.Int = if (src_tag == .bool) .{ .signedness = undefined, .bits = 1 } else src_ty.intInfo(zcu); + assert(dst_int_info.bits == src_int_info.bits); + if (dst_tag != .@"struct" and src_tag != .@"struct" and src_tag != .bool and dst_int_info.signedness == src_int_info.signedness) { + try dst_vi.value.move(isel, ty_op.operand); + } else switch (dst_int_info.bits) { + 0 => unreachable, + 1...31 => |dst_bits| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(switch (dst_int_info.signedness) { + .signed => .sbfm(dst_ra.w(), src_mat.ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(dst_bits - 1), + }), + .unsigned => .ubfm(dst_ra.w(), src_mat.ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(dst_bits - 1), + }), + }); + try src_mat.finish(isel); + }, + 32 => try dst_vi.value.move(isel, ty_op.operand), + 33...63 => |dst_bits| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(switch (dst_int_info.signedness) { + .signed => .sbfm(dst_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 1), + }), + .unsigned => .ubfm(dst_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 1), + }), + }); + try src_mat.finish(isel); + }, + 64 => try dst_vi.value.move(isel, ty_op.operand), + 65...127 => |dst_bits| { + const src_vi = try isel.use(ty_op.operand); + var dst_hi64_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi64_vi = try dst_hi64_it.only(isel); + if (try dst_hi64_vi.?.defReg(isel)) |dst_hi64_ra| { + var src_hi64_it = src_vi.field(src_ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + try isel.emit(switch (dst_int_info.signedness) { + .signed => .sbfm(dst_hi64_ra.x(), src_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 64 - 1), + }), + .unsigned => .ubfm(dst_hi64_ra.x(), src_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 64 - 1), + }), + }); + try src_hi64_mat.finish(isel); + } + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (try dst_lo64_vi.?.defReg(isel)) |dst_lo64_ra| { + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, dst_lo64_ra); + } + }, + 128 => try dst_vi.value.move(isel, ty_op.operand), + else => return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }), + } + } else if ((dst_ty.isPtrAtRuntime(zcu) or dst_ty.isAbiInt(zcu)) and (src_ty.isPtrAtRuntime(zcu) or src_ty.isAbiInt(zcu))) { + try dst_vi.value.move(isel, ty_op.operand); + } else if (dst_ty.isSliceAtRuntime(zcu) and src_ty.isSliceAtRuntime(zcu)) { + try dst_vi.value.move(isel, ty_op.operand); + } else if (dst_tag == .error_union and src_tag == .error_union) { + assert(dst_ty.errorUnionSet(zcu).hasRuntimeBitsIgnoreComptime(zcu) == + src_ty.errorUnionSet(zcu).hasRuntimeBitsIgnoreComptime(zcu)); + if (dst_ty.errorUnionPayload(zcu).toIntern() == src_ty.errorUnionPayload(zcu).toIntern()) { + try dst_vi.value.move(isel, ty_op.operand); + } else return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + } else if (dst_tag == .float and src_tag == .float) { + assert(dst_ty.floatBits(isel.target) == src_ty.floatBits(isel.target)); + try dst_vi.value.move(isel, ty_op.operand); + } else if (dst_ty.isAbiInt(zcu) and src_tag == .float) { + const dst_int_info = dst_ty.intInfo(zcu); + assert(dst_int_info.bits == src_ty.floatBits(isel.target)); + switch (dst_int_info.bits) { + else => unreachable, + 16 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + switch (dst_int_info.signedness) { + .signed => try isel.emit(.smov(dst_ra.w(), src_mat.ra.@"h[]"(0))), + .unsigned => try isel.emit(if (isel.target.cpu.has(.aarch64, .fullfp16)) + .fmov(dst_ra.w(), .{ .register = src_mat.ra.h() }) + else + .umov(dst_ra.w(), src_mat.ra.@"h[]"(0))), + } + try src_mat.finish(isel); + }, + 32 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fmov(dst_ra.w(), .{ .register = src_mat.ra.s() })); + try src_mat.finish(isel); + }, + 64 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fmov(dst_ra.x(), .{ .register = src_mat.ra.d() })); + try src_mat.finish(isel); + }, + 80 => switch (dst_int_info.signedness) { + .signed => { + const src_vi = try isel.use(ty_op.operand); + var dst_hi16_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi16_vi = try dst_hi16_it.only(isel); + if (try dst_hi16_vi.?.defReg(isel)) |dst_hi16_ra| { + var src_hi16_it = src_vi.field(src_ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + const src_hi16_mat = try src_hi16_vi.?.matReg(isel); + try isel.emit(.sbfm(dst_hi16_ra.x(), src_hi16_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = 16 - 1, + })); + try src_hi16_mat.finish(isel); + } + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (try dst_lo64_vi.?.defReg(isel)) |dst_lo64_ra| { + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, dst_lo64_ra); + } + }, + else => try dst_vi.value.move(isel, ty_op.operand), + }, + 128 => { + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + var dst_hi64_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi64_vi = try dst_hi64_it.only(isel); + if (try dst_hi64_vi.?.defReg(isel)) |dst_hi64_ra| try isel.emit(.fmov(dst_hi64_ra.x(), .{ .register = src_mat.ra.@"d[]"(1) })); + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (try dst_lo64_vi.?.defReg(isel)) |dst_lo64_ra| try isel.emit(.fmov(dst_lo64_ra.x(), .{ .register = src_mat.ra.d() })); + try src_mat.finish(isel); + }, + } + } else if (dst_tag == .float and src_ty.isAbiInt(zcu)) { + const src_int_info = src_ty.intInfo(zcu); + assert(dst_ty.floatBits(isel.target) == src_int_info.bits); + switch (src_int_info.bits) { + else => unreachable, + 16 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fmov( + if (isel.target.cpu.has(.aarch64, .fullfp16)) dst_ra.h() else dst_ra.s(), + .{ .register = src_mat.ra.w() }, + )); + try src_mat.finish(isel); + }, + 32 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fmov(dst_ra.s(), .{ .register = src_mat.ra.w() })); + try src_mat.finish(isel); + }, + 64 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fmov(dst_ra.d(), .{ .register = src_mat.ra.x() })); + try src_mat.finish(isel); + }, + 80 => switch (src_int_info.signedness) { + .signed => { + const src_vi = try isel.use(ty_op.operand); + var dst_hi16_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi16_vi = try dst_hi16_it.only(isel); + if (try dst_hi16_vi.?.defReg(isel)) |dst_hi16_ra| { + var src_hi16_it = src_vi.field(src_ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + const src_hi16_mat = try src_hi16_vi.?.matReg(isel); + try isel.emit(.ubfm(dst_hi16_ra.x(), src_hi16_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = 16 - 1, + })); + try src_hi16_mat.finish(isel); + } + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (try dst_lo64_vi.?.defReg(isel)) |dst_lo64_ra| { + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, dst_lo64_ra); + } + }, + else => try dst_vi.value.move(isel, ty_op.operand), + }, + 128 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + var src_hi64_it = src_vi.field(src_ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + try isel.emit(.fmov(dst_ra.@"d[]"(1), .{ .register = src_hi64_mat.ra.x() })); + try src_hi64_mat.finish(isel); + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + const src_lo64_mat = try src_lo64_vi.?.matReg(isel); + try isel.emit(.fmov(dst_ra.d(), .{ .register = src_lo64_mat.ra.x() })); + try src_lo64_mat.finish(isel); + }, + } + } else if (dst_ty.isAbiInt(zcu) and src_tag == .array and src_ty.childType(zcu).isAbiInt(zcu)) { + const dst_int_info = dst_ty.intInfo(zcu); + const src_child_int_info = src_ty.childType(zcu).intInfo(zcu); + const src_len = src_ty.arrayLenIncludingSentinel(zcu); + assert(dst_int_info.bits == src_child_int_info.bits * src_len); + const src_child_size = src_ty.childType(zcu).abiSize(zcu); + if (8 * src_child_size == src_child_int_info.bits) { + try dst_vi.value.defAddr(isel, dst_ty, dst_int_info, comptime &.initFill(.free)) orelse break :unused; + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memcpy", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(ty_op.operand); + try isel.movImmediate(.x2, src_child_size * src_len); + try call.paramAddress(isel, src_vi, .r1); + try call.paramAddress(isel, dst_vi.value, .r0); + try call.finishParams(isel); + } else return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + } else if (dst_tag == .array and dst_ty.childType(zcu).isAbiInt(zcu) and src_ty.isAbiInt(zcu)) { + const dst_child_int_info = dst_ty.childType(zcu).intInfo(zcu); + const src_int_info = src_ty.intInfo(zcu); + const dst_len = dst_ty.arrayLenIncludingSentinel(zcu); + assert(dst_child_int_info.bits * dst_len == src_int_info.bits); + const dst_child_size = dst_ty.childType(zcu).abiSize(zcu); + if (8 * dst_child_size == dst_child_int_info.bits) { + try dst_vi.value.defAddr(isel, dst_ty, null, comptime &.initFill(.free)) orelse break :unused; + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memcpy", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(ty_op.operand); + try isel.movImmediate(.x2, dst_child_size * dst_len); + try call.paramAddress(isel, src_vi, .r1); + try call.paramAddress(isel, dst_vi.value, .r0); + try call.finishParams(isel); + } else return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + } else return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .block => { + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.Block, ty_pl.payload); + + if (ty_pl.ty != .noreturn_type) { + isel.blocks.putAssumeCapacityNoClobber(air.inst_index, .{ + .live_registers = isel.live_registers, + .target_label = @intCast(isel.instructions.items.len), + }); + } + try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + if (ty_pl.ty != .noreturn_type) { + const block_entry = isel.blocks.pop().?; + assert(block_entry.key == air.inst_index); + if (isel.live_values.fetchRemove(air.inst_index)) |result_vi| result_vi.value.deref(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .loop => { + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.Block, ty_pl.payload); + const loops = isel.loops.values(); + const loop_index = isel.loops.getIndex(air.inst_index).?; + const loop = &loops[loop_index]; + + tracking_log.debug("{f}", .{ + isel.fmtDom(air.inst_index, loop.dom, @intCast(isel.blocks.count())), + }); + tracking_log.debug("{f}", .{isel.fmtLoopLive(air.inst_index)}); + assert(loop.depth == isel.blocks.count()); + + if (false) { + // loops are dumb... + for (isel.loop_live.list.items[loop.live..loops[loop_index + 1].live]) |live_inst| { + const live_vi = try isel.use(live_inst.toRef()); + try live_vi.mat(isel); + } + + // IT'S DOM TIME!!! + for (isel.blocks.values(), 0..) |*block, dom_index| { + if (@as(u1, @truncate(isel.dom.items[ + loop.dom + dom_index / @bitSizeOf(DomInt) + ] >> @truncate(dom_index))) == 0) continue; + var live_reg_it = block.live_registers.iterator(); + while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) { + _ => |live_vi| try live_vi.mat(isel), + .allocating => unreachable, + .free => {}, + }; + } + } + + loop.live_registers = isel.live_registers; + loop.repeat_list = Loop.empty_list; + try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.merge(&loop.live_registers, .{ .fill_extra = true }); + + var repeat_label = loop.repeat_list; + assert(repeat_label != Loop.empty_list); + while (repeat_label != Loop.empty_list) { + const instruction = &isel.instructions.items[repeat_label]; + const next_repeat_label = instruction.*; + instruction.* = .b(-@as(i28, @intCast((isel.instructions.items.len - 1 - repeat_label) << 2))); + repeat_label = @bitCast(next_repeat_label); + } + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .repeat => { + const repeat = air.data(air.inst_index).repeat; + try isel.loops.getPtr(repeat.loop_inst).?.branch(isel); + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .br => { + const br = air.data(air.inst_index).br; + const block = isel.blocks.getPtr(br.block_inst).?; + try block.branch(isel); + if (isel.live_values.get(br.block_inst)) |dst_vi| try dst_vi.move(isel, br.operand); + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .trap => { + try isel.emit(.brk(0x1)); + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .breakpoint => { + try isel.emit(.brk(0xf000)); + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .call => { + const pl_op = air.data(air.inst_index).pl_op; + const extra = isel.air.extraData(Air.Call, pl_op.payload); + const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]); + + try call.prepareReturn(isel); + const maybe_def_ret_vi = isel.live_values.fetchRemove(air.inst_index); + var maybe_ret_addr_vi: ?Value.Index = null; + if (maybe_def_ret_vi) |def_ret_vi| { + defer def_ret_vi.value.deref(isel); + + var ret_it: CallAbiIterator = .init; + const ret_vi = try ret_it.ret(isel, isel.air.typeOfIndex(air.inst_index, ip)); + defer ret_vi.?.deref(isel); + switch (ret_vi.?.parent(isel)) { + .unallocated, .stack_slot => if (ret_vi.?.hint(isel)) |ret_ra| { + try call.returnLiveIn(isel, def_ret_vi.value, ret_ra); + } else { + var def_ret_part_it = def_ret_vi.value.parts(isel); + var ret_part_it = ret_vi.?.parts(isel); + while (def_ret_part_it.next()) |ret_part_vi| { + try call.returnLiveIn(isel, ret_part_vi, ret_part_it.next().?.hint(isel).?); + } + }, + .value, .constant => unreachable, + .address => |address_vi| { + maybe_ret_addr_vi = address_vi; + _ = try def_ret_vi.value.defAddr( + isel, + isel.air.typeOfIndex(air.inst_index, ip), + null, + &call.caller_saved_regs, + ); + }, + } + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + if (pl_op.operand.toInterned()) |ct_callee| { + try isel.nav_relocs.append(gpa, switch (ip.indexToKey(ct_callee)) { + else => unreachable, + inline .@"extern", .func => |func| .{ + .nav = func.owner_nav, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }, + .ptr => |ptr| .{ + .nav = ptr.base_addr.nav, + .reloc = .{ + .label = @intCast(isel.instructions.items.len), + .addend = ptr.byte_offset, + }, + }, + }); + try isel.emit(.bl(0)); + } else { + const callee_vi = try isel.use(pl_op.operand); + const callee_mat = try callee_vi.matReg(isel); + try isel.emit(.blr(callee_mat.ra.x())); + try callee_mat.finish(isel); + } + try call.finishCallee(isel); + + try call.prepareParams(isel); + if (maybe_ret_addr_vi) |ret_addr_vi| try call.paramAddress( + isel, + maybe_def_ret_vi.?.value, + ret_addr_vi.hint(isel).?, + ); + var param_it: CallAbiIterator = .init; + for (args) |arg| { + const param_vi = try param_it.param(isel, isel.air.typeOf(arg, ip)) orelse continue; + defer param_vi.deref(isel); + const arg_vi = try isel.use(arg); + const passed_vi = switch (param_vi.parent(isel)) { + .unallocated, .stack_slot => param_vi, + .value, .constant => unreachable, + .address => |address_vi| { + try call.paramAddress(isel, arg_vi, address_vi.hint(isel).?); + continue; + }, + }; + if (passed_vi.hint(isel)) |param_ra| { + try call.paramLiveOut(isel, arg_vi, param_ra); + } else { + var param_part_it = passed_vi.parts(isel); + var arg_part_it = arg_vi.parts(isel); + if (arg_part_it.only()) |_| { + try isel.values.ensureUnusedCapacity(isel.pt.zcu.gpa, param_part_it.remaining); + arg_vi.setParts(isel, param_part_it.remaining); + while (param_part_it.next()) |param_part_vi| _ = arg_vi.addPart( + isel, + param_part_vi.get(isel).offset_from_parent, + param_part_vi.size(isel), + ); + param_part_it = passed_vi.parts(isel); + arg_part_it = arg_vi.parts(isel); + } + while (param_part_it.next()) |param_part_vi| { + const arg_part_vi = arg_part_it.next().?; + assert(arg_part_vi.get(isel).offset_from_parent == + param_part_vi.get(isel).offset_from_parent); + assert(arg_part_vi.size(isel) == param_part_vi.size(isel)); + try call.paramLiveOut(isel, arg_part_vi, param_part_vi.hint(isel).?); + } + } + } + try call.finishParams(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .clz => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = isel.air.typeOf(ty_op.operand, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1...64 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.clzLimb(res_ra, int_info, src_mat.ra); + try src_mat.finish(isel); + }, + 65...128 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + var src_hi64_it = src_vi.field(ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + const src_lo64_mat = try src_lo64_vi.?.matReg(isel); + const lo64_ra = try isel.allocIntReg(); + defer isel.freeReg(lo64_ra); + const hi64_ra = try isel.allocIntReg(); + defer isel.freeReg(hi64_ra); + try isel.emit(.csel(res_ra.w(), lo64_ra.w(), hi64_ra.w(), .eq)); + try isel.emit(.add(lo64_ra.w(), lo64_ra.w(), .{ .immediate = @intCast(bits - 64) })); + try isel.emit(.subs(.xzr, src_hi64_mat.ra.x(), .{ .immediate = 0 })); + try isel.clzLimb(hi64_ra, .{ .signedness = int_info.signedness, .bits = bits - 64 }, src_hi64_mat.ra); + try isel.clzLimb(lo64_ra, .{ .signedness = .unsigned, .bits = 64 }, src_lo64_mat.ra); + try src_hi64_mat.finish(isel); + try src_lo64_mat.finish(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ctz => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = isel.air.typeOf(ty_op.operand, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + switch (int_info.bits) { + 0 => unreachable, + 1...64 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.ctzLimb(res_ra, int_info, src_mat.ra); + try src_mat.finish(isel); + }, + 65...128 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + var src_hi64_it = src_vi.field(ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + const src_lo64_mat = try src_lo64_vi.?.matReg(isel); + const lo64_ra = try isel.allocIntReg(); + defer isel.freeReg(lo64_ra); + const hi64_ra = try isel.allocIntReg(); + defer isel.freeReg(hi64_ra); + try isel.emit(.csel(res_ra.w(), lo64_ra.w(), hi64_ra.w(), .ne)); + try isel.emit(.add(hi64_ra.w(), hi64_ra.w(), .{ .immediate = 64 })); + try isel.emit(.subs(.xzr, src_lo64_mat.ra.x(), .{ .immediate = 0 })); + try isel.ctzLimb(hi64_ra, .{ .signedness = .unsigned, .bits = 64 }, src_hi64_mat.ra); + try isel.ctzLimb(lo64_ra, .{ .signedness = int_info.signedness, .bits = bits - 64 }, src_lo64_mat.ra); + try src_hi64_mat.finish(isel); + try src_lo64_mat.finish(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .popcount => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = isel.air.typeOf(ty_op.operand, ip); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + if (int_info.bits > 64) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + const vec_ra = try isel.allocVecReg(); + defer isel.freeReg(vec_ra); + try isel.emit(.umov(res_ra.w(), vec_ra.@"b[]"(0))); + switch (int_info.bits) { + else => unreachable, + 1...8 => {}, + 9...16 => try isel.emit(.addp(vec_ra.@"8b"(), vec_ra.@"8b"(), .{ .vector = vec_ra.@"8b"() })), + 17...64 => try isel.emit(.addv(vec_ra.b(), vec_ra.@"8b"())), + } + try isel.emit(.cnt(vec_ra.@"8b"(), vec_ra.@"8b"())); + switch (int_info.bits) { + else => unreachable, + 1...31 => |bits| switch (int_info.signedness) { + .signed => { + try isel.emit(.fmov(vec_ra.s(), .{ .register = res_ra.w() })); + try isel.emit(.ubfm(res_ra.w(), src_mat.ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + })); + }, + .unsigned => try isel.emit(.fmov(vec_ra.s(), .{ .register = src_mat.ra.w() })), + }, + 32 => try isel.emit(.fmov(vec_ra.s(), .{ .register = src_mat.ra.w() })), + 33...63 => |bits| switch (int_info.signedness) { + .signed => { + try isel.emit(.fmov(vec_ra.d(), .{ .register = res_ra.x() })); + try isel.emit(.ubfm(res_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + })); + }, + .unsigned => try isel.emit(.fmov(vec_ra.d(), .{ .register = src_mat.ra.x() })), + }, + 64 => try isel.emit(.fmov(vec_ra.d(), .{ .register = src_mat.ra.x() })), + } + try src_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .byte_swap => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = ty_op.ty.toType(); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + if (int_info.bits > 64) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + if (int_info.bits == 8) break :unused try res_vi.value.move(isel, ty_op.operand); + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + switch (int_info.bits) { + else => unreachable, + 16 => switch (int_info.signedness) { + .signed => { + try isel.emit(.sbfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 32 - 16, + .imms = 32 - 1, + })); + try isel.emit(.rev(res_ra.w(), src_mat.ra.w())); + }, + .unsigned => try isel.emit(.rev16(res_ra.w(), src_mat.ra.w())), + }, + 24 => { + switch (int_info.signedness) { + .signed => try isel.emit(.sbfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 32 - 24, + .imms = 32 - 1, + })), + .unsigned => try isel.emit(.ubfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = 32 - 24, + .imms = 32 - 1, + })), + } + try isel.emit(.rev(res_ra.w(), src_mat.ra.w())); + }, + 32 => try isel.emit(.rev(res_ra.w(), src_mat.ra.w())), + 40, 48, 56 => |bits| { + switch (int_info.signedness) { + .signed => try isel.emit(.sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = @intCast(64 - bits), + .imms = 64 - 1, + })), + .unsigned => try isel.emit(.ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = @intCast(64 - bits), + .imms = 64 - 1, + })), + } + try isel.emit(.rev(res_ra.x(), src_mat.ra.x())); + }, + 64 => try isel.emit(.rev(res_ra.x(), src_mat.ra.x())), + } + try src_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .bit_reverse => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = ty_op.ty.toType(); + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + const int_info = ty.intInfo(zcu); + if (int_info.bits > 64) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + switch (int_info.bits) { + else => unreachable, + 1...31 => |bits| { + switch (int_info.signedness) { + .signed => try isel.emit(.sbfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = @intCast(32 - bits), + .imms = 32 - 1, + })), + .unsigned => try isel.emit(.ubfm(res_ra.w(), res_ra.w(), .{ + .N = .word, + .immr = @intCast(32 - bits), + .imms = 32 - 1, + })), + } + try isel.emit(.rbit(res_ra.w(), src_mat.ra.w())); + }, + 32 => try isel.emit(.rbit(res_ra.w(), src_mat.ra.w())), + 33...63 => |bits| { + switch (int_info.signedness) { + .signed => try isel.emit(.sbfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = @intCast(64 - bits), + .imms = 64 - 1, + })), + .unsigned => try isel.emit(.ubfm(res_ra.x(), res_ra.x(), .{ + .N = .doubleword, + .immr = @intCast(64 - bits), + .imms = 64 - 1, + })), + } + try isel.emit(.rbit(res_ra.x(), src_mat.ra.x())); + }, + 64 => try isel.emit(.rbit(res_ra.x(), src_mat.ra.x())), + } + try src_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .sqrt, .floor, .ceil, .round, .trunc_float => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const un_op = air.data(air.inst_index).un_op; + const ty = isel.air.typeOf(un_op, ip); + switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const src_vi = try isel.use(un_op); + const src_mat = try src_vi.matReg(isel); + const src_ra = if (need_fcvt) try isel.allocVecReg() else src_mat.ra; + defer if (need_fcvt) isel.freeReg(src_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) continue :bits 32 else switch (air_tag) { + else => unreachable, + .sqrt => .fsqrt(res_ra.h(), src_ra.h()), + .floor => .frintm(res_ra.h(), src_ra.h()), + .ceil => .frintp(res_ra.h(), src_ra.h()), + .round => .frinta(res_ra.h(), src_ra.h()), + .trunc_float => .frintz(res_ra.h(), src_ra.h()), + }, + 32 => switch (air_tag) { + else => unreachable, + .sqrt => .fsqrt(res_ra.s(), src_ra.s()), + .floor => .frintm(res_ra.s(), src_ra.s()), + .ceil => .frintp(res_ra.s(), src_ra.s()), + .round => .frinta(res_ra.s(), src_ra.s()), + .trunc_float => .frintz(res_ra.s(), src_ra.s()), + }, + 64 => switch (air_tag) { + else => unreachable, + .sqrt => .fsqrt(res_ra.d(), src_ra.d()), + .floor => .frintm(res_ra.d(), src_ra.d()), + .ceil => .frintp(res_ra.d(), src_ra.d()), + .round => .frinta(res_ra.d(), src_ra.d()), + .trunc_float => .frintz(res_ra.d(), src_ra.d()), + }, + }); + if (need_fcvt) try isel.emit(.fcvt(src_ra.s(), src_mat.ra.h())); + try src_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (air_tag) { + else => unreachable, + .sqrt => switch (bits) { + else => unreachable, + 16 => "__sqrth", + 32 => "sqrtf", + 64 => "sqrt", + 80 => "__sqrtx", + 128 => "sqrtq", + }, + .floor => switch (bits) { + else => unreachable, + 16 => "__floorh", + 32 => "floorf", + 64 => "floor", + 80 => "__floorx", + 128 => "floorq", + }, + .ceil => switch (bits) { + else => unreachable, + 16 => "__ceilh", + 32 => "ceilf", + 64 => "ceil", + 80 => "__ceilx", + 128 => "ceilq", + }, + .round => switch (bits) { + else => unreachable, + 16 => "__roundh", + 32 => "roundf", + 64 => "round", + 80 => "__roundx", + 128 => "roundq", + }, + .trunc_float => switch (bits) { + else => unreachable, + 16 => "__trunch", + 32 => "truncf", + 64 => "trunc", + 80 => "__truncx", + 128 => "truncq", + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(un_op); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.paramLiveOut(isel, src_vi, .v0), + 80 => { + var src_hi16_it = src_vi.field(ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + try call.paramLiveOut(isel, src_hi16_vi.?, .r1); + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try call.paramLiveOut(isel, src_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .sin, .cos, .tan, .exp, .exp2, .log, .log2, .log10 => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| { + defer res_vi.value.deref(isel); + + const un_op = air.data(air.inst_index).un_op; + const ty = isel.air.typeOf(un_op, ip); + const bits = ty.floatBits(isel.target); + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (air_tag) { + else => unreachable, + .sin => switch (bits) { + else => unreachable, + 16 => "__sinh", + 32 => "sinf", + 64 => "sin", + 80 => "__sinx", + 128 => "sinq", + }, + .cos => switch (bits) { + else => unreachable, + 16 => "__cosh", + 32 => "cosf", + 64 => "cos", + 80 => "__cosx", + 128 => "cosq", + }, + .tan => switch (bits) { + else => unreachable, + 16 => "__tanh", + 32 => "tanf", + 64 => "tan", + 80 => "__tanx", + 128 => "tanq", + }, + .exp => switch (bits) { + else => unreachable, + 16 => "__exph", + 32 => "expf", + 64 => "exp", + 80 => "__expx", + 128 => "expq", + }, + .exp2 => switch (bits) { + else => unreachable, + 16 => "__exp2h", + 32 => "exp2f", + 64 => "exp2", + 80 => "__exp2x", + 128 => "exp2q", + }, + .log => switch (bits) { + else => unreachable, + 16 => "__logh", + 32 => "logf", + 64 => "log", + 80 => "__logx", + 128 => "logq", + }, + .log2 => switch (bits) { + else => unreachable, + 16 => "__log2h", + 32 => "log2f", + 64 => "log2", + 80 => "__log2x", + 128 => "log2q", + }, + .log10 => switch (bits) { + else => unreachable, + 16 => "__log10h", + 32 => "log10f", + 64 => "log10", + 80 => "__log10x", + 128 => "log10q", + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(un_op); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.paramLiveOut(isel, src_vi, .v0), + 80 => { + var src_hi16_it = src_vi.field(ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + try call.paramLiveOut(isel, src_hi16_vi.?, .r1); + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try call.paramLiveOut(isel, src_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .abs => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const ty = ty_op.ty.toType(); + if (!ty.isRuntimeFloat()) { + if (!ty.isAbiInt(zcu)) return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + switch (ty.intInfo(zcu).bits) { + 0 => unreachable, + 1...32 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.csneg(res_ra.w(), src_mat.ra.w(), src_mat.ra.w(), .pl)); + try isel.emit(.subs(.wzr, src_mat.ra.w(), .{ .immediate = 0 })); + try src_mat.finish(isel); + }, + 33...64 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.csneg(res_ra.x(), src_mat.ra.x(), src_mat.ra.x(), .pl)); + try isel.emit(.subs(.xzr, src_mat.ra.x(), .{ .immediate = 0 })); + try src_mat.finish(isel); + }, + 65...128 => { + var res_hi64_it = res_vi.value.field(ty, 8, 8); + const res_hi64_vi = try res_hi64_it.only(isel); + const res_hi64_ra = try res_hi64_vi.?.defReg(isel); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + const res_lo64_ra = try res_lo64_vi.?.defReg(isel); + if (res_hi64_ra == null and res_lo64_ra == null) break :unused; + const src_ty = isel.air.typeOf(ty_op.operand, ip); + const src_vi = try isel.use(ty_op.operand); + var src_hi64_it = src_vi.field(src_ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + const src_lo64_mat = try src_lo64_vi.?.matReg(isel); + const lo64_ra = try isel.allocIntReg(); + defer isel.freeReg(lo64_ra); + const hi64_ra, const mask_ra = alloc_ras: { + const res_lo64_lock: RegLock = if (res_lo64_ra) |res_ra| isel.tryLockReg(res_ra) else .empty; + defer res_lo64_lock.unlock(isel); + break :alloc_ras .{ try isel.allocIntReg(), try isel.allocIntReg() }; + }; + defer { + isel.freeReg(hi64_ra); + isel.freeReg(mask_ra); + } + if (res_hi64_ra) |res_ra| try isel.emit(.sbc(res_ra.x(), hi64_ra.x(), mask_ra.x())); + try isel.emit(.subs( + if (res_lo64_ra) |res_ra| res_ra.x() else .xzr, + lo64_ra.x(), + .{ .register = mask_ra.x() }, + )); + if (res_hi64_ra) |_| try isel.emit(.eor(hi64_ra.x(), src_hi64_mat.ra.x(), .{ .register = mask_ra.x() })); + try isel.emit(.eor(lo64_ra.x(), src_lo64_mat.ra.x(), .{ .register = mask_ra.x() })); + try isel.emit(.sbfm(mask_ra.x(), src_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = 64 - 1, + .imms = 64 - 1, + })); + try src_lo64_mat.finish(isel); + try src_hi64_mat.finish(isel); + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }), + } + } else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(if (isel.target.cpu.has(.aarch64, .fullfp16)) + .fabs(res_ra.h(), src_mat.ra.h()) + else + .bic(res_ra.@"4h"(), res_ra.@"4h"(), .{ .shifted_immediate = .{ + .immediate = 0b10000000, + .lsl = 8, + } })); + try src_mat.finish(isel); + }, + 32 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fabs(res_ra.s(), src_mat.ra.s())); + try src_mat.finish(isel); + }, + 64 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fabs(res_ra.d(), src_mat.ra.d())); + try src_mat.finish(isel); + }, + 80 => { + const src_vi = try isel.use(ty_op.operand); + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + if (try res_hi16_vi.?.defReg(isel)) |res_hi16_ra| { + var src_hi16_it = src_vi.field(ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + const src_hi16_mat = try src_hi16_vi.?.matReg(isel); + try isel.emit(.@"and"(res_hi16_ra.w(), src_hi16_mat.ra.w(), .{ .immediate = .{ + .N = .word, + .immr = 0, + .imms = 15 - 1, + } })); + try src_hi16_mat.finish(isel); + } + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + if (try res_lo64_vi.?.defReg(isel)) |res_lo64_ra| { + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, res_lo64_ra); + } + }, + 128 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + const neg_zero_ra = try isel.allocVecReg(); + defer isel.freeReg(neg_zero_ra); + try isel.emit(.bic(res_ra.@"16b"(), src_mat.ra.@"16b"(), .{ .register = neg_zero_ra.@"16b"() })); + try isel.literals.appendNTimes(gpa, 0, -%isel.literals.items.len % 4); + try isel.literal_relocs.append(gpa, .{ + .label = @intCast(isel.instructions.items.len), + }); + try isel.emit(.ldr(neg_zero_ra.q(), .{ + .literal = @intCast((isel.instructions.items.len + 1 + isel.literals.items.len) << 2), + })); + try isel.emitLiteral(&(.{0} ** 15 ++ .{0x80})); + try src_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .neg, .neg_optimized => { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const un_op = air.data(air.inst_index).un_op; + const ty = isel.air.typeOf(un_op, ip); + switch (ty.floatBits(isel.target)) { + else => unreachable, + 16 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(un_op); + const src_mat = try src_vi.matReg(isel); + if (isel.target.cpu.has(.aarch64, .fullfp16)) { + try isel.emit(.fneg(res_ra.h(), src_mat.ra.h())); + } else { + const neg_zero_ra = try isel.allocVecReg(); + defer isel.freeReg(neg_zero_ra); + try isel.emit(.eor(res_ra.@"8b"(), res_ra.@"8b"(), .{ .register = neg_zero_ra.@"8b"() })); + try isel.emit(.movi(neg_zero_ra.@"4h"(), 0b10000000, .{ .lsl = 8 })); + } + try src_mat.finish(isel); + }, + 32 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(un_op); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fneg(res_ra.s(), src_mat.ra.s())); + try src_mat.finish(isel); + }, + 64 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(un_op); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fneg(res_ra.d(), src_mat.ra.d())); + try src_mat.finish(isel); + }, + 80 => { + const src_vi = try isel.use(un_op); + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + if (try res_hi16_vi.?.defReg(isel)) |res_hi16_ra| { + var src_hi16_it = src_vi.field(ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + const src_hi16_mat = try src_hi16_vi.?.matReg(isel); + try isel.emit(.eor(res_hi16_ra.w(), src_hi16_mat.ra.w(), .{ .immediate = .{ + .N = .word, + .immr = 32 - 15, + .imms = 1 - 1, + } })); + try src_hi16_mat.finish(isel); + } + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + if (try res_lo64_vi.?.defReg(isel)) |res_lo64_ra| { + var src_lo64_it = src_vi.field(ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, res_lo64_ra); + } + }, + 128 => { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(un_op); + const src_mat = try src_vi.matReg(isel); + const neg_zero_ra = try isel.allocVecReg(); + defer isel.freeReg(neg_zero_ra); + try isel.emit(.eor(res_ra.@"16b"(), src_mat.ra.@"16b"(), .{ .register = neg_zero_ra.@"16b"() })); + try isel.literals.appendNTimes(gpa, 0, -%isel.literals.items.len % 4); + try isel.literal_relocs.append(gpa, .{ + .label = @intCast(isel.instructions.items.len), + }); + try isel.emit(.ldr(neg_zero_ra.q(), .{ + .literal = @intCast((isel.instructions.items.len + 1 + isel.literals.items.len) << 2), + })); + try isel.emitLiteral(&(.{0} ** 15 ++ .{0x80})); + try src_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + var bin_op = air.data(air.inst_index).bin_op; + const ty = isel.air.typeOf(bin_op.lhs, ip); + if (!ty.isRuntimeFloat()) { + const int_info: std.builtin.Type.Int = if (ty.toIntern() == .bool_type) + .{ .signedness = .unsigned, .bits = 1 } + else if (ty.isAbiInt(zcu)) + ty.intInfo(zcu) + else if (ty.isPtrAtRuntime(zcu)) + .{ .signedness = .unsigned, .bits = 64 } + else + return isel.fail("bad {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + if (int_info.bits > 256) return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(ty) }); + + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + try isel.emit(.csinc(res_ra.w(), .wzr, .wzr, .invert(cond: switch (air_tag) { + else => unreachable, + .cmp_lt => switch (int_info.signedness) { + .signed => .lt, + .unsigned => .lo, + }, + .cmp_lte => switch (int_info.bits) { + else => unreachable, + 1...64 => switch (int_info.signedness) { + .signed => .le, + .unsigned => .ls, + }, + 65...128 => { + std.mem.swap(Air.Inst.Ref, &bin_op.lhs, &bin_op.rhs); + continue :cond .cmp_gte; + }, + }, + .cmp_eq => .eq, + .cmp_gte => switch (int_info.signedness) { + .signed => .ge, + .unsigned => .hs, + }, + .cmp_gt => switch (int_info.bits) { + else => unreachable, + 1...64 => switch (int_info.signedness) { + .signed => .gt, + .unsigned => .hi, + }, + 65...128 => { + std.mem.swap(Air.Inst.Ref, &bin_op.lhs, &bin_op.rhs); + continue :cond .cmp_lt; + }, + }, + .cmp_neq => .ne, + }))); + + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + var part_offset = lhs_vi.size(isel); + while (part_offset > 0) { + const part_size = @min(part_offset, 8); + part_offset -= part_size; + var lhs_part_it = lhs_vi.field(ty, part_offset, part_size); + const lhs_part_vi = try lhs_part_it.only(isel); + const lhs_part_mat = try lhs_part_vi.?.matReg(isel); + var rhs_part_it = rhs_vi.field(ty, part_offset, part_size); + const rhs_part_vi = try rhs_part_it.only(isel); + const rhs_part_mat = try rhs_part_vi.?.matReg(isel); + try isel.emit(switch (part_size) { + else => unreachable, + 1...4 => switch (part_offset) { + 0 => .subs(.wzr, lhs_part_mat.ra.w(), .{ .register = rhs_part_mat.ra.w() }), + else => switch (air_tag) { + else => unreachable, + .cmp_lt, .cmp_lte, .cmp_gte, .cmp_gt => .sbcs( + .wzr, + lhs_part_mat.ra.w(), + rhs_part_mat.ra.w(), + ), + .cmp_eq, .cmp_neq => .ccmp( + lhs_part_mat.ra.w(), + .{ .register = rhs_part_mat.ra.w() }, + .{ .n = false, .z = false, .c = false, .v = false }, + .eq, + ), + }, + }, + 5...8 => switch (part_offset) { + 0 => .subs(.xzr, lhs_part_mat.ra.x(), .{ .register = rhs_part_mat.ra.x() }), + else => switch (air_tag) { + else => unreachable, + .cmp_lt, .cmp_lte, .cmp_gte, .cmp_gt => .sbcs( + .xzr, + lhs_part_mat.ra.x(), + rhs_part_mat.ra.x(), + ), + .cmp_eq, .cmp_neq => .ccmp( + lhs_part_mat.ra.x(), + .{ .register = rhs_part_mat.ra.x() }, + .{ .n = false, .z = false, .c = false, .v = false }, + .eq, + ), + }, + }, + }); + try rhs_part_mat.finish(isel); + try lhs_part_mat.finish(isel); + } + } else switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + try isel.emit(.csinc(res_ra.w(), .wzr, .wzr, .invert(switch (air_tag) { + else => unreachable, + .cmp_lt => .lo, + .cmp_lte => .ls, + .cmp_eq => .eq, + .cmp_gte => .ge, + .cmp_gt => .gt, + .cmp_neq => .ne, + }))); + + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) + continue :bits 32 + else + .fcmp(lhs_ra.h(), .{ .register = rhs_ra.h() }), + 32 => .fcmp(lhs_ra.s(), .{ .register = rhs_ra.s() }), + 64 => .fcmp(lhs_ra.d(), .{ .register = rhs_ra.d() }), + }); + if (need_fcvt) { + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + + try call.prepareReturn(isel); + try call.returnFill(isel, .r0); + try isel.emit(.csinc(res_ra.w(), .wzr, .wzr, .invert(cond: switch (air_tag) { + else => unreachable, + .cmp_lt => .lt, + .cmp_lte => .le, + .cmp_eq => .eq, + .cmp_gte => { + std.mem.swap(Air.Inst.Ref, &bin_op.lhs, &bin_op.rhs); + continue :cond .cmp_lte; + }, + .cmp_gt => { + std.mem.swap(Air.Inst.Ref, &bin_op.lhs, &bin_op.rhs); + continue :cond .cmp_lt; + }, + .cmp_neq => .ne, + }))); + try isel.emit(.subs(.wzr, .w0, .{ .immediate = 0 })); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__cmphf2", + 32 => "__cmpsf2", + 64 => "__cmpdf2", + 80 => "__cmpxf2", + 128 => "__cmptf2", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .cond_br => { + const pl_op = air.data(air.inst_index).pl_op; + const extra = isel.air.extraData(Air.CondBr, pl_op.payload); + + try isel.body(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len])); + const else_label = isel.instructions.items.len; + const else_live_registers = isel.live_registers; + try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len])); + try isel.merge(&else_live_registers, .{}); + + const cond_vi = try isel.use(pl_op.operand); + const cond_mat = try cond_vi.matReg(isel); + try isel.emit(.tbz( + cond_mat.ra.x(), + 0, + @intCast((isel.instructions.items.len + 1 - else_label) << 2), + )); + try cond_mat.finish(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .switch_br => { + const switch_br = isel.air.unwrapSwitch(air.inst_index); + const cond_ty = isel.air.typeOf(switch_br.operand, ip); + const cond_int_info: std.builtin.Type.Int = if (cond_ty.toIntern() == .bool_type) + .{ .signedness = .unsigned, .bits = 1 } + else if (cond_ty.isAbiInt(zcu)) + cond_ty.intInfo(zcu) + else + return isel.fail("bad switch cond {f}", .{isel.fmtType(cond_ty)}); + + var final_case = true; + if (switch_br.else_body_len > 0) { + var cases_it = switch_br.iterateCases(); + while (cases_it.next()) |_| {} + try isel.body(cases_it.elseBody()); + assert(final_case); + final_case = false; + } + const zero_reg: Register = switch (cond_int_info.bits) { + else => unreachable, + 1...32 => .wzr, + 33...64 => .xzr, + }; + var cond_mat: ?Value.Materialize = null; + var cond_reg: Register = undefined; + var temp_reg: Register = undefined; + var cases_it = switch_br.iterateCases(); + while (cases_it.next()) |case| { + const next_label = isel.instructions.items.len; + const next_live_registers = isel.live_registers; + try isel.body(case.body); + if (final_case) { + final_case = false; + continue; + } + try isel.merge(&next_live_registers, .{}); + if (cond_mat == null) { + var cond_vi = try isel.use(switch_br.operand); + cond_mat = try cond_vi.matReg(isel); + const temp_ra = try isel.allocIntReg(); + cond_reg, temp_reg = switch (cond_int_info.bits) { + else => unreachable, + 1...32 => .{ cond_mat.?.ra.w(), temp_ra.w() }, + 33...64 => .{ cond_mat.?.ra.x(), temp_ra.x() }, + }; + } + if (case.ranges.len == 0 and case.items.len == 1 and Constant.fromInterned( + case.items[0].toInterned().?, + ).orderAgainstZero(zcu).compare(.eq)) { + try isel.emit(.cbnz( + cond_reg, + @intCast((isel.instructions.items.len + 1 - next_label) << 2), + )); + continue; + } + try isel.emit(.@"b."( + .invert(switch (case.ranges.len) { + 0 => .eq, + else => .ls, + }), + @intCast((isel.instructions.items.len + 1 - next_label) << 2), + )); + var case_range_index = case.ranges.len; + while (case_range_index > 0) { + case_range_index -= 1; + + const low_val: Constant = .fromInterned(case.ranges[case_range_index][0].toInterned().?); + var low_bigint_space: Constant.BigIntSpace = undefined; + const low_bigint = low_val.toBigInt(&low_bigint_space, zcu); + const low_int: i64 = if (low_bigint.positive) @bitCast( + low_bigint.toInt(u64) catch + return isel.fail("too big case range start: {f}", .{isel.fmtConstant(low_val)}), + ) else low_bigint.toInt(i64) catch + return isel.fail("too big case range start: {f}", .{isel.fmtConstant(low_val)}); + + const high_val: Constant = .fromInterned(case.ranges[case_range_index][1].toInterned().?); + var high_bigint_space: Constant.BigIntSpace = undefined; + const high_bigint = high_val.toBigInt(&high_bigint_space, zcu); + const high_int: i64 = if (high_bigint.positive) @bitCast( + high_bigint.toInt(u64) catch + return isel.fail("too big case range end: {f}", .{isel.fmtConstant(high_val)}), + ) else high_bigint.toInt(i64) catch + return isel.fail("too big case range end: {f}", .{isel.fmtConstant(high_val)}); + + const delta_int = high_int -% low_int; + if (case_range_index > 0) { + return isel.fail("case range", .{}); + } else if (case.items.len > 0) { + return isel.fail("case range", .{}); + } else { + const adjusted_reg = switch (low_int) { + 0 => cond_reg, + else => temp_reg, + }; + + if (std.math.cast(u12, delta_int)) |pos_imm| try isel.emit(.subs( + zero_reg, + adjusted_reg, + .{ .immediate = pos_imm }, + )) else if (std.math.cast(u12, -delta_int)) |neg_imm| try isel.emit(.adds( + zero_reg, + adjusted_reg, + .{ .immediate = neg_imm }, + )) else if (if (@as(i12, @truncate(delta_int)) == 0) + std.math.cast(u12, delta_int >> 12) + else + null) |pos_imm_lsr_12| try isel.emit(.subs( + zero_reg, + adjusted_reg, + .{ .shifted_immediate = .{ .immediate = pos_imm_lsr_12, .lsl = .@"12" } }, + )) else if (if (@as(i12, @truncate(-delta_int)) == 0) + std.math.cast(u12, -delta_int >> 12) + else + null) |neg_imm_lsr_12| try isel.emit(.adds( + zero_reg, + adjusted_reg, + .{ .shifted_immediate = .{ .immediate = neg_imm_lsr_12, .lsl = .@"12" } }, + )) else { + try isel.movImmediate(temp_reg, @bitCast(delta_int)); + try isel.emit(.subs(zero_reg, adjusted_reg, .{ .register = temp_reg })); + } + + switch (low_int) { + 0 => {}, + else => { + if (std.math.cast(u12, low_int)) |pos_imm| try isel.emit(.sub( + adjusted_reg, + cond_reg, + .{ .immediate = pos_imm }, + )) else if (std.math.cast(u12, -low_int)) |neg_imm| try isel.emit(.add( + adjusted_reg, + cond_reg, + .{ .immediate = neg_imm }, + )) else if (if (@as(i12, @truncate(low_int)) == 0) + std.math.cast(u12, low_int >> 12) + else + null) |pos_imm_lsr_12| try isel.emit(.sub( + adjusted_reg, + cond_reg, + .{ .shifted_immediate = .{ .immediate = pos_imm_lsr_12, .lsl = .@"12" } }, + )) else if (if (@as(i12, @truncate(-low_int)) == 0) + std.math.cast(u12, -low_int >> 12) + else + null) |neg_imm_lsr_12| try isel.emit(.add( + adjusted_reg, + cond_reg, + .{ .shifted_immediate = .{ .immediate = neg_imm_lsr_12, .lsl = .@"12" } }, + )) else { + try isel.movImmediate(temp_reg, @bitCast(low_int)); + try isel.emit(.subs(adjusted_reg, cond_reg, .{ .register = temp_reg })); + } + }, + } + } + } + var case_item_index = case.items.len; + while (case_item_index > 0) { + case_item_index -= 1; + + const item_val: Constant = .fromInterned(case.items[case_item_index].toInterned().?); + var item_bigint_space: Constant.BigIntSpace = undefined; + const item_bigint = item_val.toBigInt(&item_bigint_space, zcu); + const item_int: i64 = if (item_bigint.positive) @bitCast( + item_bigint.toInt(u64) catch + return isel.fail("too big case item: {f}", .{isel.fmtConstant(item_val)}), + ) else item_bigint.toInt(i64) catch + return isel.fail("too big case item: {f}", .{isel.fmtConstant(item_val)}); + + if (case_item_index > 0) { + if (std.math.cast(u5, item_int)) |pos_imm| try isel.emit(.ccmp( + cond_reg, + .{ .immediate = pos_imm }, + .{ .n = false, .z = true, .c = false, .v = false }, + .ne, + )) else if (std.math.cast(u5, -item_int)) |neg_imm| try isel.emit(.ccmn( + cond_reg, + .{ .immediate = neg_imm }, + .{ .n = false, .z = true, .c = false, .v = false }, + .ne, + )) else { + try isel.movImmediate(temp_reg, @bitCast(item_int)); + try isel.emit(.ccmp( + cond_reg, + .{ .register = temp_reg }, + .{ .n = false, .z = true, .c = false, .v = false }, + .ne, + )); + } + } else { + if (std.math.cast(u12, item_int)) |pos_imm| try isel.emit(.subs( + zero_reg, + cond_reg, + .{ .immediate = pos_imm }, + )) else if (std.math.cast(u12, -item_int)) |neg_imm| try isel.emit(.adds( + zero_reg, + cond_reg, + .{ .immediate = neg_imm }, + )) else if (if (@as(i12, @truncate(item_int)) == 0) + std.math.cast(u12, item_int >> 12) + else + null) |pos_imm_lsr_12| try isel.emit(.subs( + zero_reg, + cond_reg, + .{ .shifted_immediate = .{ .immediate = pos_imm_lsr_12, .lsl = .@"12" } }, + )) else if (if (@as(i12, @truncate(-item_int)) == 0) + std.math.cast(u12, -item_int >> 12) + else + null) |neg_imm_lsr_12| try isel.emit(.adds( + zero_reg, + cond_reg, + .{ .shifted_immediate = .{ .immediate = neg_imm_lsr_12, .lsl = .@"12" } }, + )) else { + try isel.movImmediate(temp_reg, @bitCast(item_int)); + try isel.emit(.subs(zero_reg, cond_reg, .{ .register = temp_reg })); + } + } + } + } + if (cond_mat) |mat| { + try mat.finish(isel); + isel.freeReg(temp_reg.alias); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .@"try", .try_cold => { + const pl_op = air.data(air.inst_index).pl_op; + const extra = isel.air.extraData(Air.Try, pl_op.payload); + const error_union_ty = isel.air.typeOf(pl_op.operand, ip); + const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; + const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); + + const error_union_vi = try isel.use(pl_op.operand); + if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| { + defer payload_vi.value.deref(isel); + + var payload_part_it = error_union_vi.field( + error_union_ty, + codegen.errUnionPayloadOffset(payload_ty, zcu), + payload_vi.value.size(isel), + ); + const payload_part_vi = try payload_part_it.only(isel); + try payload_vi.value.copy(isel, payload_ty, payload_part_vi.?); + } + + const cont_label = isel.instructions.items.len; + const cont_live_registers = isel.live_registers; + try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.merge(&cont_live_registers, .{}); + + var error_set_part_it = error_union_vi.field( + error_union_ty, + codegen.errUnionErrorOffset(payload_ty, zcu), + ZigType.fromInterned(error_union_info.error_set_type).abiSize(zcu), + ); + const error_set_part_vi = try error_set_part_it.only(isel); + const error_set_part_mat = try error_set_part_vi.?.matReg(isel); + try isel.emit(.cbz( + switch (error_set_part_vi.?.size(isel)) { + else => unreachable, + 1...4 => error_set_part_mat.ra.w(), + 5...8 => error_set_part_mat.ra.x(), + }, + @intCast((isel.instructions.items.len + 1 - cont_label) << 2), + )); + try error_set_part_mat.finish(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .dbg_stmt => { + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .dbg_empty_stmt => { + try isel.emit(.nop()); + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => { + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .is_null, .is_non_null => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |is_vi| unused: { + defer is_vi.value.deref(isel); + const is_ra = try is_vi.value.defReg(isel) orelse break :unused; + + const un_op = air.data(air.inst_index).un_op; + const opt_ty = isel.air.typeOf(un_op, ip); + const payload_ty = opt_ty.optionalChild(zcu); + const payload_size = payload_ty.abiSize(zcu); + const has_value_offset, const has_value_size = if (!opt_ty.optionalReprIsPayload(zcu)) + .{ payload_size, 1 } + else if (payload_ty.isSlice(zcu)) + .{ 0, 8 } + else + .{ 0, payload_size }; + + try isel.emit(.csinc(is_ra.w(), .wzr, .wzr, .invert(switch (air_tag) { + else => unreachable, + .is_null => .eq, + .is_non_null => .ne, + }))); + const opt_vi = try isel.use(un_op); + var has_value_part_it = opt_vi.field(opt_ty, has_value_offset, has_value_size); + const has_value_part_vi = try has_value_part_it.only(isel); + const has_value_part_mat = try has_value_part_vi.?.matReg(isel); + try isel.emit(switch (has_value_size) { + else => unreachable, + 1...4 => .subs(.wzr, has_value_part_mat.ra.w(), .{ .immediate = 0 }), + 5...8 => .subs(.xzr, has_value_part_mat.ra.x(), .{ .immediate = 0 }), + }); + try has_value_part_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .is_err, .is_non_err => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |is_vi| unused: { + defer is_vi.value.deref(isel); + const is_ra = try is_vi.value.defReg(isel) orelse break :unused; + + const un_op = air.data(air.inst_index).un_op; + const error_union_ty = isel.air.typeOf(un_op, ip); + const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; + const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type); + const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); + const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu); + const error_set_size = error_set_ty.abiSize(zcu); + + try isel.emit(.csinc(is_ra.w(), .wzr, .wzr, .invert(switch (air_tag) { + else => unreachable, + .is_err => .ne, + .is_non_err => .eq, + }))); + const error_union_vi = try isel.use(un_op); + var error_set_part_it = error_union_vi.field(error_union_ty, error_set_offset, error_set_size); + const error_set_part_vi = try error_set_part_it.only(isel); + const error_set_part_mat = try error_set_part_vi.?.matReg(isel); + try isel.emit(.ands(.wzr, error_set_part_mat.ra.w(), .{ .immediate = .{ + .N = .word, + .immr = 0, + .imms = @intCast(8 * error_set_size - 1), + } })); + try error_set_part_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .load => { + const ty_op = air.data(air.inst_index).ty_op; + const ptr_ty = isel.air.typeOf(ty_op.operand, ip); + const ptr_info = ptr_ty.ptrInfo(zcu); + if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed load", .{}); + + if (ptr_info.flags.is_volatile) _ = try isel.use(air.inst_index.toRef()); + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + switch (dst_vi.value.size(isel)) { + 0 => unreachable, + 1...Value.max_parts => { + const ptr_vi = try isel.use(ty_op.operand); + const ptr_mat = try ptr_vi.matReg(isel); + _ = try dst_vi.value.load(isel, ty_op.ty.toType(), ptr_mat.ra, .{ + .@"volatile" = ptr_info.flags.is_volatile, + }); + try ptr_mat.finish(isel); + }, + else => |size| { + try dst_vi.value.defAddr(isel, .fromInterned(ptr_info.child), null, comptime &.initFill(.free)) orelse break :unused; + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memcpy", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const ptr_vi = try isel.use(ty_op.operand); + try isel.movImmediate(.x2, size); + try call.paramLiveOut(isel, ptr_vi, .r1); + try call.paramAddress(isel, dst_vi.value, .r0); + try call.finishParams(isel); + }, + } + } + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ret, .ret_safe => { + assert(isel.blocks.keys()[0] == Block.main); + try isel.blocks.values()[0].branch(isel); + if (isel.live_values.get(Block.main)) |ret_vi| { + const un_op = air.data(air.inst_index).un_op; + const src_vi = try isel.use(un_op); + switch (ret_vi.parent(isel)) { + .unallocated, .stack_slot => if (ret_vi.hint(isel)) |ret_ra| { + try src_vi.liveOut(isel, ret_ra); + } else { + var ret_part_it = ret_vi.parts(isel); + var src_part_it = src_vi.parts(isel); + if (src_part_it.only()) |_| { + try isel.values.ensureUnusedCapacity(gpa, ret_part_it.remaining); + src_vi.setParts(isel, ret_part_it.remaining); + while (ret_part_it.next()) |ret_part_vi| { + const src_part_vi = src_vi.addPart( + isel, + ret_part_vi.get(isel).offset_from_parent, + ret_part_vi.size(isel), + ); + switch (ret_part_vi.signedness(isel)) { + .signed => src_part_vi.setSignedness(isel, .signed), + .unsigned => {}, + } + if (ret_part_vi.isVector(isel)) src_part_vi.setIsVector(isel); + } + ret_part_it = ret_vi.parts(isel); + src_part_it = src_vi.parts(isel); + } + while (ret_part_it.next()) |ret_part_vi| { + const src_part_vi = src_part_it.next().?; + assert(ret_part_vi.get(isel).offset_from_parent == src_part_vi.get(isel).offset_from_parent); + assert(ret_part_vi.size(isel) == src_part_vi.size(isel)); + try src_part_vi.liveOut(isel, ret_part_vi.hint(isel).?); + } + }, + .value, .constant => unreachable, + .address => |address_vi| { + const ptr_mat = try address_vi.matReg(isel); + try src_vi.store(isel, isel.air.typeOf(un_op, ip), ptr_mat.ra, .{}); + try ptr_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ret_load => { + const un_op = air.data(air.inst_index).un_op; + const ptr_ty = isel.air.typeOf(un_op, ip); + const ptr_info = ptr_ty.ptrInfo(zcu); + if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed load", .{}); + + assert(isel.blocks.keys()[0] == Block.main); + try isel.blocks.values()[0].branch(isel); + if (isel.live_values.get(Block.main)) |ret_vi| switch (ret_vi.parent(isel)) { + .unallocated, .stack_slot => { + var ret_part_it: Value.PartIterator = if (ret_vi.hint(isel)) |_| .initOne(ret_vi) else ret_vi.parts(isel); + while (ret_part_it.next()) |ret_part_vi| try ret_part_vi.liveOut(isel, ret_part_vi.hint(isel).?); + const ptr_vi = try isel.use(un_op); + const ptr_mat = try ptr_vi.matReg(isel); + _ = try ret_vi.load(isel, .fromInterned(ptr_info.child), ptr_mat.ra, .{}); + try ptr_mat.finish(isel); + }, + .value, .constant => unreachable, + .address => {}, + }; + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .store, .store_safe, .atomic_store_unordered => { + const bin_op = air.data(air.inst_index).bin_op; + const ptr_ty = isel.air.typeOf(bin_op.lhs, ip); + const ptr_info = ptr_ty.ptrInfo(zcu); + if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed store", .{}); + if (bin_op.rhs.toInterned()) |rhs_val| if (ip.isUndef(rhs_val)) { + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + break :air_tag; + }; + + const src_vi = try isel.use(bin_op.rhs); + const size = src_vi.size(isel); + if (ZigType.fromInterned(ptr_info.child).zigTypeTag(zcu) != .@"union") switch (size) { + 0 => unreachable, + 1...Value.max_parts => { + const ptr_vi = try isel.use(bin_op.lhs); + const ptr_mat = try ptr_vi.matReg(isel); + try src_vi.store(isel, isel.air.typeOf(bin_op.rhs, ip), ptr_mat.ra, .{ + .@"volatile" = ptr_info.flags.is_volatile, + }); + try ptr_mat.finish(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + break :air_tag; + }, + else => {}, + }; + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memcpy", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const ptr_vi = try isel.use(bin_op.lhs); + try isel.movImmediate(.x2, size); + try call.paramAddress(isel, src_vi, .r1); + try call.paramLiveOut(isel, ptr_vi, .r0); + try call.finishParams(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .unreach => if (air.next()) |next_air_tag| continue :air_tag next_air_tag, + .fptrunc, .fpext => { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const dst_bits = dst_ty.floatBits(isel.target); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + const src_bits = src_ty.floatBits(isel.target); + assert(dst_bits != src_bits); + switch (@max(dst_bits, src_bits)) { + else => unreachable, + 16, 32, 64 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.fcvt(switch (dst_bits) { + else => unreachable, + 16 => dst_ra.h(), + 32 => dst_ra.s(), + 64 => dst_ra.d(), + }, switch (src_bits) { + else => unreachable, + 16 => src_mat.ra.h(), + 32 => src_mat.ra.s(), + 64 => src_mat.ra.d(), + })); + try src_mat.finish(isel); + }, + 80, 128 => { + try call.prepareReturn(isel); + switch (dst_bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, dst_vi.value, .v0), + 80 => { + var dst_hi16_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi16_vi = try dst_hi16_it.only(isel); + try call.returnLiveIn(isel, dst_hi16_vi.?, .r1); + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + try call.returnLiveIn(isel, dst_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (dst_bits) { + else => unreachable, + 16 => switch (src_bits) { + else => unreachable, + 32 => "__truncsfhf2", + 64 => "__truncdfhf2", + 80 => "__truncxfhf2", + 128 => "__trunctfhf2", + }, + 32 => switch (src_bits) { + else => unreachable, + 16 => "__extendhfsf2", + 64 => "__truncdfsf2", + 80 => "__truncxfsf2", + 128 => "__trunctfsf2", + }, + 64 => switch (src_bits) { + else => unreachable, + 16 => "__extendhfdf2", + 32 => "__extendsfdf2", + 80 => "__truncxfdf2", + 128 => "__trunctfdf2", + }, + 80 => switch (src_bits) { + else => unreachable, + 16 => "__extendhfxf2", + 32 => "__extendsfxf2", + 64 => "__extenddfxf2", + 128 => "__trunctfxf2", + }, + 128 => switch (src_bits) { + else => unreachable, + 16 => "__extendhftf2", + 32 => "__extendsftf2", + 64 => "__extenddftf2", + 80 => "__extendxftf2", + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(ty_op.operand); + switch (src_bits) { + else => unreachable, + 16, 32, 64, 128 => try call.paramLiveOut(isel, src_vi, .v0), + 80 => { + var src_hi16_it = src_vi.field(src_ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + try call.paramLiveOut(isel, src_hi16_vi.?, .r1); + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try call.paramLiveOut(isel, src_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .intcast => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const dst_int_info = dst_ty.intInfo(zcu); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + const src_int_info = src_ty.intInfo(zcu); + const can_be_negative = dst_int_info.signedness == .signed and + src_int_info.signedness == .signed; + if ((dst_int_info.bits <= 8 and src_int_info.bits <= 8) or + (dst_int_info.bits > 8 and dst_int_info.bits <= 16 and + src_int_info.bits > 8 and src_int_info.bits <= 16) or + (dst_int_info.bits > 16 and dst_int_info.bits <= 32 and + src_int_info.bits > 16 and src_int_info.bits <= 32) or + (dst_int_info.bits > 32 and dst_int_info.bits <= 64 and + src_int_info.bits > 32 and src_int_info.bits <= 64) or + (dst_int_info.bits > 64 and src_int_info.bits > 64 and + (dst_int_info.bits - 1) / 128 == (src_int_info.bits - 1) / 128)) + { + try dst_vi.value.move(isel, ty_op.operand); + } else if (dst_int_info.bits <= 32 and src_int_info.bits <= 64) { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(.orr(dst_ra.w(), .wzr, .{ .register = src_mat.ra.w() })); + try src_mat.finish(isel); + } else if (dst_int_info.bits <= 64 and src_int_info.bits <= 32) { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + try isel.emit(if (can_be_negative) .sbfm(dst_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(src_int_info.bits - 1), + }) else .orr(dst_ra.w(), .wzr, .{ .register = src_mat.ra.w() })); + try src_mat.finish(isel); + } else if (dst_int_info.bits <= 32 and src_int_info.bits <= 128) { + assert(src_int_info.bits > 64); + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + const src_lo64_mat = try src_lo64_vi.?.matReg(isel); + try isel.emit(.orr(dst_ra.w(), .wzr, .{ .register = src_lo64_mat.ra.w() })); + try src_lo64_mat.finish(isel); + } else if (dst_int_info.bits <= 64 and src_int_info.bits <= 128) { + assert(dst_int_info.bits > 32 and src_int_info.bits > 64); + const src_vi = try isel.use(ty_op.operand); + + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try dst_vi.value.copy(isel, dst_ty, src_lo64_vi.?); + } else if (dst_int_info.bits <= 128 and src_int_info.bits <= 64) { + assert(dst_int_info.bits > 64); + const src_vi = try isel.use(ty_op.operand); + + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (src_int_info.bits <= 32) unused_lo64: { + const dst_lo64_ra = try dst_lo64_vi.?.defReg(isel) orelse break :unused_lo64; + const src_mat = try src_vi.matReg(isel); + try isel.emit(if (can_be_negative) .sbfm(dst_lo64_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(src_int_info.bits - 1), + }) else .orr(dst_lo64_ra.w(), .wzr, .{ .register = src_mat.ra.w() })); + try src_mat.finish(isel); + } else try dst_lo64_vi.?.copy(isel, src_ty, src_vi); + + var dst_hi64_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi64_vi = try dst_hi64_it.only(isel); + const dst_hi64_ra = try dst_hi64_vi.?.defReg(isel); + if (dst_hi64_ra) |dst_ra| switch (can_be_negative) { + false => try isel.emit(.orr(dst_ra.x(), .xzr, .{ .register = .xzr })), + true => { + const src_mat = try src_vi.matReg(isel); + try isel.emit(.sbfm(dst_ra.x(), src_mat.ra.x(), .{ + .N = .doubleword, + .immr = @intCast(src_int_info.bits - 1), + .imms = @intCast(src_int_info.bits - 1), + })); + try src_mat.finish(isel); + }, + }; + } else return isel.fail("too big {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .trunc => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + if (!dst_ty.isAbiInt(zcu) or !src_ty.isAbiInt(zcu)) return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + const dst_int_info = dst_ty.intInfo(zcu); + switch (dst_int_info.bits) { + 0 => unreachable, + 1...64 => |dst_bits| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + var src_part_it = src_vi.field(src_ty, 0, @min(src_vi.size(isel), 8)); + const src_part_vi = try src_part_it.only(isel); + const src_part_mat = try src_part_vi.?.matReg(isel); + try isel.emit(switch (dst_bits) { + else => unreachable, + 1...31 => |bits| switch (dst_int_info.signedness) { + .signed => .sbfm(dst_ra.w(), src_part_mat.ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(dst_ra.w(), src_part_mat.ra.w(), .{ + .N = .word, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }, + 32 => .orr(dst_ra.w(), .wzr, .{ .register = src_part_mat.ra.w() }), + 33...63 => |bits| switch (dst_int_info.signedness) { + .signed => .sbfm(dst_ra.x(), src_part_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + .unsigned => .ubfm(dst_ra.x(), src_part_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(bits - 1), + }), + }, + 64 => .orr(dst_ra.x(), .xzr, .{ .register = src_part_mat.ra.x() }), + }); + try src_part_mat.finish(isel); + }, + 65...128 => |dst_bits| switch (src_ty.intInfo(zcu).bits) { + 0 => unreachable, + 65...128 => { + const src_vi = try isel.use(ty_op.operand); + var dst_hi64_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi64_vi = try dst_hi64_it.only(isel); + if (try dst_hi64_vi.?.defReg(isel)) |dst_hi64_ra| { + var src_hi64_it = src_vi.field(src_ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + const src_hi64_mat = try src_hi64_vi.?.matReg(isel); + try isel.emit(switch (dst_int_info.signedness) { + .signed => .sbfm(dst_hi64_ra.x(), src_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 64 - 1), + }), + .unsigned => .ubfm(dst_hi64_ra.x(), src_hi64_mat.ra.x(), .{ + .N = .doubleword, + .immr = 0, + .imms = @intCast(dst_bits - 64 - 1), + }), + }); + try src_hi64_mat.finish(isel); + } + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + if (try dst_lo64_vi.?.defReg(isel)) |dst_lo64_ra| { + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try src_lo64_vi.?.liveOut(isel, dst_lo64_ra); + } + }, + else => return isel.fail("too big {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }), + }, + else => return isel.fail("too big {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .optional_payload_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| { + defer dst_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + try dst_vi.value.move(isel, ty_op.operand); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .optional_payload => { + if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| unused: { + defer payload_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const opt_ty = isel.air.typeOf(ty_op.operand, ip); + if (opt_ty.optionalReprIsPayload(zcu)) { + try payload_vi.value.move(isel, ty_op.operand); + break :unused; + } + + const opt_vi = try isel.use(ty_op.operand); + var payload_part_it = opt_vi.field(opt_ty, 0, payload_vi.value.size(isel)); + const payload_part_vi = try payload_part_it.only(isel); + try payload_vi.value.copy(isel, ty_op.ty.toType(), payload_part_vi.?); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .wrap_optional => { + if (isel.live_values.fetchRemove(air.inst_index)) |opt_vi| unused: { + defer opt_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + if (ty_op.ty.toType().optionalReprIsPayload(zcu)) { + try opt_vi.value.move(isel, ty_op.operand); + break :unused; + } + + const payload_size = isel.air.typeOf(ty_op.operand, ip).abiSize(zcu); + var payload_part_it = opt_vi.value.field(ty_op.ty.toType(), 0, payload_size); + const payload_part_vi = try payload_part_it.only(isel); + try payload_part_vi.?.move(isel, ty_op.operand); + var has_value_part_it = opt_vi.value.field(ty_op.ty.toType(), payload_size, 1); + const has_value_part_vi = try has_value_part_it.only(isel); + const has_value_part_ra = try has_value_part_vi.?.defReg(isel) orelse break :unused; + try isel.emit(.movz(has_value_part_ra.w(), 1, .{ .lsl = .@"0" })); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .unwrap_errunion_payload => { + if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| { + defer payload_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const error_union_ty = isel.air.typeOf(ty_op.operand, ip); + + const error_union_vi = try isel.use(ty_op.operand); + var payload_part_it = error_union_vi.field( + error_union_ty, + codegen.errUnionPayloadOffset(ty_op.ty.toType(), zcu), + payload_vi.value.size(isel), + ); + const payload_part_vi = try payload_part_it.only(isel); + try payload_vi.value.copy(isel, ty_op.ty.toType(), payload_part_vi.?); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .unwrap_errunion_err => { + if (isel.live_values.fetchRemove(air.inst_index)) |error_set_vi| { + defer error_set_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const error_union_ty = isel.air.typeOf(ty_op.operand, ip); + + const error_union_vi = try isel.use(ty_op.operand); + var error_set_part_it = error_union_vi.field( + error_union_ty, + codegen.errUnionErrorOffset(error_union_ty.errorUnionPayload(zcu), zcu), + error_set_vi.value.size(isel), + ); + const error_set_part_vi = try error_set_part_it.only(isel); + try error_set_vi.value.copy(isel, ty_op.ty.toType(), error_set_part_vi.?); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .wrap_errunion_payload => { + if (isel.live_values.fetchRemove(air.inst_index)) |error_union_vi| { + defer error_union_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const error_union_ty = ty_op.ty.toType(); + const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; + const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type); + const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); + const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu); + const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu); + const error_set_size = error_set_ty.abiSize(zcu); + const payload_size = payload_ty.abiSize(zcu); + + var payload_part_it = error_union_vi.value.field(error_union_ty, payload_offset, payload_size); + const payload_part_vi = try payload_part_it.only(isel); + try payload_part_vi.?.move(isel, ty_op.operand); + var error_set_part_it = error_union_vi.value.field(error_union_ty, error_set_offset, error_set_size); + const error_set_part_vi = try error_set_part_it.only(isel); + if (try error_set_part_vi.?.defReg(isel)) |error_set_part_ra| try isel.emit(switch (error_set_size) { + else => unreachable, + 1...4 => .orr(error_set_part_ra.w(), .wzr, .{ .register = .wzr }), + 5...8 => .orr(error_set_part_ra.x(), .xzr, .{ .register = .xzr }), + }); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .wrap_errunion_err => { + if (isel.live_values.fetchRemove(air.inst_index)) |error_union_vi| { + defer error_union_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const error_union_ty = ty_op.ty.toType(); + const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; + const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type); + const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); + const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu); + const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu); + const error_set_size = error_set_ty.abiSize(zcu); + const payload_size = payload_ty.abiSize(zcu); + + if (payload_size > 0) { + var payload_part_it = error_union_vi.value.field(error_union_ty, payload_offset, payload_size); + const payload_part_vi = try payload_part_it.only(isel); + if (try payload_part_vi.?.defReg(isel)) |payload_part_ra| try isel.emit(switch (payload_size) { + else => unreachable, + 1...4 => .orr(payload_part_ra.w(), .wzr, .{ .immediate = .{ + .N = .word, + .immr = 0b000001, + .imms = 0b111100, + } }), + 5...8 => .orr(payload_part_ra.x(), .xzr, .{ .immediate = .{ + .N = .word, + .immr = 0b000001, + .imms = 0b111100, + } }), + }); + } + var error_set_part_it = error_union_vi.value.field(error_union_ty, error_set_offset, error_set_size); + const error_set_part_vi = try error_set_part_it.only(isel); + try error_set_part_vi.?.move(isel, ty_op.operand); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .struct_field_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data; + switch (codegen.fieldOffset( + isel.air.typeOf(extra.struct_operand, ip), + ty_pl.ty.toType(), + extra.field_index, + zcu, + )) { + 0 => try dst_vi.value.move(isel, extra.struct_operand), + else => |field_offset| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(extra.struct_operand); + const src_mat = try src_vi.matReg(isel); + const lo12: u12 = @truncate(field_offset >> 0); + const hi12: u12 = @intCast(field_offset >> 12); + if (hi12 > 0) try isel.emit(.add( + dst_ra.x(), + if (lo12 > 0) dst_ra.x() else src_mat.ra.x(), + .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } }, + )); + if (lo12 > 0) try isel.emit(.add(dst_ra.x(), src_mat.ra.x(), .{ .immediate = lo12 })); + try src_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, + => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + switch (codegen.fieldOffset( + isel.air.typeOf(ty_op.operand, ip), + ty_op.ty.toType(), + switch (air_tag) { + else => unreachable, + .struct_field_ptr_index_0 => 0, + .struct_field_ptr_index_1 => 1, + .struct_field_ptr_index_2 => 2, + .struct_field_ptr_index_3 => 3, + }, + zcu, + )) { + 0 => try dst_vi.value.move(isel, ty_op.operand), + else => |field_offset| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + const lo12: u12 = @truncate(field_offset >> 0); + const hi12: u12 = @intCast(field_offset >> 12); + if (hi12 > 0) try isel.emit(.add( + dst_ra.x(), + if (lo12 > 0) dst_ra.x() else src_mat.ra.x(), + .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } }, + )); + if (lo12 > 0) try isel.emit(.add(dst_ra.x(), src_mat.ra.x(), .{ .immediate = lo12 })); + try src_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .struct_field_val => { + if (isel.live_values.fetchRemove(air.inst_index)) |field_vi| { + defer field_vi.value.deref(isel); + + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data; + const agg_ty = isel.air.typeOf(extra.struct_operand, ip); + const field_ty = ty_pl.ty.toType(); + const field_bit_offset, const field_bit_size, const is_packed = switch (agg_ty.containerLayout(zcu)) { + .auto, .@"extern" => .{ + 8 * agg_ty.structFieldOffset(extra.field_index, zcu), + 8 * field_ty.abiSize(zcu), + false, + }, + .@"packed" => .{ + if (zcu.typeToPackedStruct(agg_ty)) |loaded_struct| + zcu.structPackedFieldBitOffset(loaded_struct, extra.field_index) + else + 0, + field_ty.bitSize(zcu), + true, + }, + }; + if (is_packed) return isel.fail("packed field of {f}", .{ + isel.fmtType(agg_ty), + }); + + const agg_vi = try isel.use(extra.struct_operand); + var agg_part_it = agg_vi.field(agg_ty, @divExact(field_bit_offset, 8), @divExact(field_bit_size, 8)); + while (try agg_part_it.next(isel)) |agg_part| { + var field_part_it = field_vi.value.field(ty_pl.ty.toType(), agg_part.offset, agg_part.vi.size(isel)); + const field_part_vi = try field_part_it.only(isel); + if (field_part_vi.? == agg_part.vi) continue; + var field_subpart_it = field_part_vi.?.parts(isel); + const field_part_offset = if (field_subpart_it.only()) |field_subpart_vi| + field_subpart_vi.get(isel).offset_from_parent + else + 0; + while (field_subpart_it.next()) |field_subpart_vi| { + const field_subpart_ra = try field_subpart_vi.defReg(isel) orelse continue; + const field_subpart_offset, const field_subpart_size = field_subpart_vi.position(isel); + var agg_subpart_it = agg_part.vi.field( + field_ty, + agg_part.offset + field_subpart_offset - field_part_offset, + field_subpart_size, + ); + const agg_subpart_vi = try agg_subpart_it.only(isel); + try agg_subpart_vi.?.liveOut(isel, field_subpart_ra); + } + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .slice => { + if (isel.live_values.fetchRemove(air.inst_index)) |slice_vi| { + defer slice_vi.value.deref(isel); + const ty_pl = air.data(air.inst_index).ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + var ptr_part_it = slice_vi.value.field(ty_pl.ty.toType(), 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + try ptr_part_vi.?.move(isel, bin_op.lhs); + var len_part_it = slice_vi.value.field(ty_pl.ty.toType(), 8, 8); + const len_part_vi = try len_part_it.only(isel); + try len_part_vi.?.move(isel, bin_op.rhs); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .slice_len => { + if (isel.live_values.fetchRemove(air.inst_index)) |len_vi| { + defer len_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + const slice_vi = try isel.use(ty_op.operand); + var len_part_it = slice_vi.field(isel.air.typeOf(ty_op.operand, ip), 8, 8); + const len_part_vi = try len_part_it.only(isel); + try len_vi.value.copy(isel, ty_op.ty.toType(), len_part_vi.?); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .slice_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |ptr_vi| { + defer ptr_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + const slice_vi = try isel.use(ty_op.operand); + var ptr_part_it = slice_vi.field(isel.air.typeOf(ty_op.operand, ip), 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + try ptr_vi.value.copy(isel, ty_op.ty.toType(), ptr_part_vi.?); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .array_elem_val => { + if (isel.live_values.fetchRemove(air.inst_index)) |elem_vi| unused: { + defer elem_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const array_ty = isel.air.typeOf(bin_op.lhs, ip); + const elem_ty = array_ty.childType(zcu); + const elem_size = elem_ty.abiSize(zcu); + if (elem_size <= 16 and array_ty.arrayLenIncludingSentinel(zcu) <= Value.max_parts) if (bin_op.rhs.toInterned()) |index_val| { + const elem_offset = elem_size * Constant.fromInterned(index_val).toUnsignedInt(zcu); + const array_vi = try isel.use(bin_op.lhs); + var elem_part_it = array_vi.field(array_ty, elem_offset, elem_size); + const elem_part_vi = try elem_part_it.only(isel); + try elem_vi.value.copy(isel, elem_ty, elem_part_vi.?); + break :unused; + }; + switch (elem_size) { + 0 => unreachable, + 1, 2, 4, 8 => { + const elem_ra = try elem_vi.value.defReg(isel) orelse break :unused; + const array_ptr_ra = try isel.allocIntReg(); + defer isel.freeReg(array_ptr_ra); + const index_vi = try isel.use(bin_op.rhs); + const index_mat = try index_vi.matReg(isel); + try isel.emit(switch (elem_size) { + else => unreachable, + 1 => if (elem_vi.value.isVector(isel)) .ldr(elem_ra.b(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }) else switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsb(elem_ra.w(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + .unsigned => .ldrb(elem_ra.w(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + }, + 2 => if (elem_vi.value.isVector(isel)) .ldr(elem_ra.h(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }) else switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsh(elem_ra.w(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + .unsigned => .ldrh(elem_ra.w(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + }, + 4 => .ldr(if (elem_vi.value.isVector(isel)) elem_ra.s() else elem_ra.w(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 2 }, + } }), + 8 => .ldr(if (elem_vi.value.isVector(isel)) elem_ra.d() else elem_ra.x(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 3 }, + } }), + 16 => .ldr(elem_ra.q(), .{ .extended_register = .{ + .base = array_ptr_ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 4 }, + } }), + }); + try index_mat.finish(isel); + const array_vi = try isel.use(bin_op.lhs); + try array_vi.address(isel, 0, array_ptr_ra); + }, + else => { + const ptr_ra = try isel.allocIntReg(); + defer isel.freeReg(ptr_ra); + if (!try elem_vi.value.load(isel, elem_ty, ptr_ra, .{})) break :unused; + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(ptr_ra, ptr_ra, .add, elem_size, index_vi); + const array_vi = try isel.use(bin_op.lhs); + try array_vi.address(isel, 0, ptr_ra); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .slice_elem_val => { + if (isel.live_values.fetchRemove(air.inst_index)) |elem_vi| unused: { + defer elem_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const slice_ty = isel.air.typeOf(bin_op.lhs, ip); + const ptr_info = slice_ty.ptrInfo(zcu); + const elem_size = elem_vi.value.size(isel); + const elem_is_vector = elem_vi.value.isVector(isel); + if (switch (elem_size) { + 0 => unreachable, + 1, 2, 4, 8 => true, + 16 => elem_is_vector, + else => false, + }) { + const elem_ra = try elem_vi.value.defReg(isel) orelse break :unused; + const slice_vi = try isel.use(bin_op.lhs); + const index_vi = try isel.use(bin_op.rhs); + var ptr_part_it = slice_vi.field(slice_ty, 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + const base_mat = try ptr_part_vi.?.matReg(isel); + const index_mat = try index_vi.matReg(isel); + try isel.emit(switch (elem_size) { + else => unreachable, + 1 => if (elem_is_vector) .ldr(elem_ra.b(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }) else switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsb(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + .unsigned => .ldrb(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + }, + 2 => if (elem_is_vector) .ldr(elem_ra.h(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }) else switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsh(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + .unsigned => .ldrh(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + }, + 4 => .ldr(if (elem_is_vector) elem_ra.s() else elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 2 }, + } }), + 8 => .ldr(if (elem_is_vector) elem_ra.d() else elem_ra.x(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 3 }, + } }), + 16 => .ldr(elem_ra.q(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 4 }, + } }), + }); + try index_mat.finish(isel); + try base_mat.finish(isel); + break :unused; + } else { + const elem_ptr_ra = try isel.allocIntReg(); + defer isel.freeReg(elem_ptr_ra); + if (!try elem_vi.value.load(isel, slice_ty.elemType2(zcu), elem_ptr_ra, .{ + .@"volatile" = ptr_info.flags.is_volatile, + })) break :unused; + const slice_vi = try isel.use(bin_op.lhs); + var ptr_part_it = slice_vi.field(slice_ty, 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + const ptr_part_mat = try ptr_part_vi.?.matReg(isel); + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(elem_ptr_ra, ptr_part_mat.ra, .add, elem_size, index_vi); + try ptr_part_mat.finish(isel); + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .slice_elem_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |elem_ptr_vi| unused: { + defer elem_ptr_vi.value.deref(isel); + const elem_ptr_ra = try elem_ptr_vi.value.defReg(isel) orelse break :unused; + + const ty_pl = air.data(air.inst_index).ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + const elem_size = ty_pl.ty.toType().childType(zcu).abiSize(zcu); + + const slice_vi = try isel.use(bin_op.lhs); + var ptr_part_it = slice_vi.field(isel.air.typeOf(bin_op.lhs, ip), 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + const ptr_part_mat = try ptr_part_vi.?.matReg(isel); + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(elem_ptr_ra, ptr_part_mat.ra, .add, elem_size, index_vi); + try ptr_part_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ptr_elem_val => { + if (isel.live_values.fetchRemove(air.inst_index)) |elem_vi| unused: { + defer elem_vi.value.deref(isel); + + const bin_op = air.data(air.inst_index).bin_op; + const ptr_ty = isel.air.typeOf(bin_op.lhs, ip); + const ptr_info = ptr_ty.ptrInfo(zcu); + const elem_size = elem_vi.value.size(isel); + switch (elem_size) { + 0 => unreachable, + 1, 2, 4, 8 => { + const elem_ra = try elem_vi.value.defReg(isel) orelse break :unused; + const base_vi = try isel.use(bin_op.lhs); + const index_vi = try isel.use(bin_op.rhs); + const base_mat = try base_vi.matReg(isel); + const index_mat = try index_vi.matReg(isel); + try isel.emit(switch (elem_size) { + else => unreachable, + 1 => switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsb(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + .unsigned => .ldrb(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 0 }, + } }), + }, + 2 => switch (elem_vi.value.signedness(isel)) { + .signed => .ldrsh(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + .unsigned => .ldrh(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 1 }, + } }), + }, + 4 => .ldr(elem_ra.w(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 2 }, + } }), + 8 => .ldr(elem_ra.x(), .{ .extended_register = .{ + .base = base_mat.ra.x(), + .index = index_mat.ra.x(), + .extend = .{ .lsl = 3 }, + } }), + }); + try index_mat.finish(isel); + try base_mat.finish(isel); + }, + else => { + const elem_ptr_ra = try isel.allocIntReg(); + defer isel.freeReg(elem_ptr_ra); + if (!try elem_vi.value.load(isel, ptr_ty.elemType2(zcu), elem_ptr_ra, .{ + .@"volatile" = ptr_info.flags.is_volatile, + })) break :unused; + const base_vi = try isel.use(bin_op.lhs); + const base_mat = try base_vi.matReg(isel); + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(elem_ptr_ra, base_mat.ra, .add, elem_size, index_vi); + try base_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .ptr_elem_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |elem_ptr_vi| unused: { + defer elem_ptr_vi.value.deref(isel); + const elem_ptr_ra = try elem_ptr_vi.value.defReg(isel) orelse break :unused; + + const ty_pl = air.data(air.inst_index).ty_pl; + const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data; + const elem_size = ty_pl.ty.toType().childType(zcu).abiSize(zcu); + + const base_vi = try isel.use(bin_op.lhs); + const base_mat = try base_vi.matReg(isel); + const index_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(elem_ptr_ra, base_mat.ra, .add, elem_size, index_vi); + try base_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .array_to_slice => { + if (isel.live_values.fetchRemove(air.inst_index)) |slice_vi| { + defer slice_vi.value.deref(isel); + const ty_op = air.data(air.inst_index).ty_op; + var ptr_part_it = slice_vi.value.field(ty_op.ty.toType(), 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + try ptr_part_vi.?.move(isel, ty_op.operand); + var len_part_it = slice_vi.value.field(ty_op.ty.toType(), 8, 8); + const len_part_vi = try len_part_it.only(isel); + if (try len_part_vi.?.defReg(isel)) |len_ra| try isel.movImmediate( + len_ra.x(), + isel.air.typeOf(ty_op.operand, ip).childType(zcu).arrayLen(zcu), + ); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .int_from_float, .int_from_float_optimized => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + if (!dst_ty.isAbiInt(zcu)) return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + const dst_int_info = dst_ty.intInfo(zcu); + const src_bits = src_ty.floatBits(isel.target); + switch (@max(dst_int_info.bits, src_bits)) { + 0 => unreachable, + 1...64 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (src_bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + const src_ra = if (need_fcvt) try isel.allocVecReg() else src_mat.ra; + defer if (need_fcvt) isel.freeReg(src_ra); + const dst_reg = switch (dst_int_info.bits) { + else => unreachable, + 1...32 => dst_ra.w(), + 33...64 => dst_ra.x(), + }; + const src_reg = switch (src_bits) { + else => unreachable, + 16 => if (need_fcvt) src_ra.s() else src_ra.h(), + 32 => src_ra.s(), + 64 => src_ra.d(), + }; + try isel.emit(switch (dst_int_info.signedness) { + .signed => .fcvtzs(dst_reg, src_reg), + .unsigned => .fcvtzu(dst_reg, src_reg), + }); + if (need_fcvt) try isel.emit(.fcvt(src_reg, src_mat.ra.h())); + try src_mat.finish(isel); + }, + 65...128 => { + try call.prepareReturn(isel); + switch (dst_int_info.bits) { + else => unreachable, + 1...64 => try call.returnLiveIn(isel, dst_vi.value, .r0), + 65...128 => { + var dst_hi64_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi64_vi = try dst_hi64_it.only(isel); + try call.returnLiveIn(isel, dst_hi64_vi.?, .r1); + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + try call.returnLiveIn(isel, dst_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (dst_int_info.bits) { + else => unreachable, + 1...32 => switch (dst_int_info.signedness) { + .signed => switch (src_bits) { + else => unreachable, + 16 => "__fixhfsi", + 32 => "__fixsfsi", + 64 => "__fixdfsi", + 80 => "__fixxfsi", + 128 => "__fixtfsi", + }, + .unsigned => switch (src_bits) { + else => unreachable, + 16 => "__fixunshfsi", + 32 => "__fixunssfsi", + 64 => "__fixunsdfsi", + 80 => "__fixunsxfsi", + 128 => "__fixunstfsi", + }, + }, + 33...64 => switch (dst_int_info.signedness) { + .signed => switch (src_bits) { + else => unreachable, + 16 => "__fixhfdi", + 32 => "__fixsfdi", + 64 => "__fixdfdi", + 80 => "__fixxfdi", + 128 => "__fixtfdi", + }, + .unsigned => switch (src_bits) { + else => unreachable, + 16 => "__fixunshfdi", + 32 => "__fixunssfdi", + 64 => "__fixunsdfdi", + 80 => "__fixunsxfdi", + 128 => "__fixunstfdi", + }, + }, + 65...128 => switch (dst_int_info.signedness) { + .signed => switch (src_bits) { + else => unreachable, + 16 => "__fixhfti", + 32 => "__fixsfti", + 64 => "__fixdfti", + 80 => "__fixxfti", + 128 => "__fixtfti", + }, + .unsigned => switch (src_bits) { + else => unreachable, + 16 => "__fixunshfti", + 32 => "__fixunssfti", + 64 => "__fixunsdfti", + 80 => "__fixunsxfti", + 128 => "__fixunstfti", + }, + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(ty_op.operand); + switch (src_bits) { + else => unreachable, + 16, 32, 64, 128 => try call.paramLiveOut(isel, src_vi, .v0), + 80 => { + var src_hi16_it = src_vi.field(src_ty, 8, 8); + const src_hi16_vi = try src_hi16_it.only(isel); + try call.paramLiveOut(isel, src_hi16_vi.?, .r1); + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try call.paramLiveOut(isel, src_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + else => return isel.fail("too big {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .float_from_int => |air_tag| { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + + const ty_op = air.data(air.inst_index).ty_op; + const dst_ty = ty_op.ty.toType(); + const src_ty = isel.air.typeOf(ty_op.operand, ip); + const dst_bits = dst_ty.floatBits(isel.target); + if (!src_ty.isAbiInt(zcu)) return isel.fail("bad {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }); + const src_int_info = src_ty.intInfo(zcu); + switch (@max(dst_bits, src_int_info.bits)) { + 0 => unreachable, + 1...64 => { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (dst_bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(dst_ra.h(), dst_ra.s())); + const src_vi = try isel.use(ty_op.operand); + const src_mat = try src_vi.matReg(isel); + const dst_reg = switch (dst_bits) { + else => unreachable, + 16 => if (need_fcvt) dst_ra.s() else dst_ra.h(), + 32 => dst_ra.s(), + 64 => dst_ra.d(), + }; + const src_reg = switch (src_int_info.bits) { + else => unreachable, + 1...32 => src_mat.ra.w(), + 33...64 => src_mat.ra.x(), + }; + try isel.emit(switch (src_int_info.signedness) { + .signed => .scvtf(dst_reg, src_reg), + .unsigned => .ucvtf(dst_reg, src_reg), + }); + try src_mat.finish(isel); + }, + 65...128 => { + try call.prepareReturn(isel); + switch (dst_bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, dst_vi.value, .v0), + 80 => { + var dst_hi16_it = dst_vi.value.field(dst_ty, 8, 8); + const dst_hi16_vi = try dst_hi16_it.only(isel); + try call.returnLiveIn(isel, dst_hi16_vi.?, .r1); + var dst_lo64_it = dst_vi.value.field(dst_ty, 0, 8); + const dst_lo64_vi = try dst_lo64_it.only(isel); + try call.returnLiveIn(isel, dst_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (src_int_info.bits) { + else => unreachable, + 1...32 => switch (src_int_info.signedness) { + .signed => switch (dst_bits) { + else => unreachable, + 16 => "__floatsihf", + 32 => "__floatsisf", + 64 => "__floatsidf", + 80 => "__floatsixf", + 128 => "__floatsitf", + }, + .unsigned => switch (dst_bits) { + else => unreachable, + 16 => "__floatunsihf", + 32 => "__floatunsisf", + 64 => "__floatunsidf", + 80 => "__floatunsixf", + 128 => "__floatunsitf", + }, + }, + 33...64 => switch (src_int_info.signedness) { + .signed => switch (dst_bits) { + else => unreachable, + 16 => "__floatdihf", + 32 => "__floatdisf", + 64 => "__floatdidf", + 80 => "__floatdixf", + 128 => "__floatditf", + }, + .unsigned => switch (dst_bits) { + else => unreachable, + 16 => "__floatundihf", + 32 => "__floatundisf", + 64 => "__floatundidf", + 80 => "__floatundixf", + 128 => "__floatunditf", + }, + }, + 65...128 => switch (src_int_info.signedness) { + .signed => switch (dst_bits) { + else => unreachable, + 16 => "__floattihf", + 32 => "__floattisf", + 64 => "__floattidf", + 80 => "__floattixf", + 128 => "__floattitf", + }, + .unsigned => switch (dst_bits) { + else => unreachable, + 16 => "__floatuntihf", + 32 => "__floatuntisf", + 64 => "__floatuntidf", + 80 => "__floatuntixf", + 128 => "__floatuntitf", + }, + }, + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const src_vi = try isel.use(ty_op.operand); + switch (src_int_info.bits) { + else => unreachable, + 1...64 => try call.paramLiveOut(isel, src_vi, .r0), + 65...128 => { + var src_hi64_it = src_vi.field(src_ty, 8, 8); + const src_hi64_vi = try src_hi64_it.only(isel); + try call.paramLiveOut(isel, src_hi64_vi.?, .r1); + var src_lo64_it = src_vi.field(src_ty, 0, 8); + const src_lo64_vi = try src_lo64_it.only(isel); + try call.paramLiveOut(isel, src_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + else => return isel.fail("too big {s} {f} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty), isel.fmtType(src_ty) }), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .memset => |air_tag| { + const bin_op = air.data(air.inst_index).bin_op; + const dst_ty = isel.air.typeOf(bin_op.lhs, ip); + const dst_info = dst_ty.ptrInfo(zcu); + const fill_byte: union(enum) { constant: u8, value: Air.Inst.Ref } = fill_byte: { + if (bin_op.rhs.toInterned()) |fill_val| + if (try isel.hasRepeatedByteRepr(.fromInterned(fill_val))) |fill_byte| + break :fill_byte .{ .constant = fill_byte }; + switch (dst_ty.elemType2(zcu).abiSize(zcu)) { + 0 => unreachable, + 1 => break :fill_byte .{ .value = bin_op.rhs }, + 2, 4, 8 => |size| { + const dst_vi = try isel.use(bin_op.lhs); + const ptr_ra = try isel.allocIntReg(); + const fill_vi = try isel.use(bin_op.rhs); + const fill_mat = try fill_vi.matReg(isel); + const len_mat: Value.Materialize = len_mat: switch (dst_info.flags.size) { + .one => .{ .vi = undefined, .ra = try isel.allocIntReg() }, + .many => unreachable, + .slice => { + var dst_len_it = dst_vi.field(dst_ty, 8, 8); + const dst_len_vi = try dst_len_it.only(isel); + break :len_mat try dst_len_vi.?.matReg(isel); + }, + .c => unreachable, + }; + + const skip_label = isel.instructions.items.len; + _ = try isel.instructions.addOne(gpa); + try isel.emit(.sub(len_mat.ra.x(), len_mat.ra.x(), .{ .immediate = 1 })); + try isel.emit(switch (size) { + else => unreachable, + 2 => .strh(fill_mat.ra.w(), .{ .post_index = .{ .base = ptr_ra.x(), .index = 2 } }), + 4 => .str(fill_mat.ra.w(), .{ .post_index = .{ .base = ptr_ra.x(), .index = 4 } }), + 8 => .str(fill_mat.ra.x(), .{ .post_index = .{ .base = ptr_ra.x(), .index = 8 } }), + }); + isel.instructions.items[skip_label] = .cbnz( + len_mat.ra.x(), + -@as(i21, @intCast((isel.instructions.items.len - 1 - skip_label) << 2)), + ); + switch (dst_info.flags.size) { + .one => { + const len_imm = ZigType.fromInterned(dst_info.child).arrayLen(zcu); + assert(len_imm > 0); + try isel.movImmediate(len_mat.ra.x(), len_imm); + isel.freeReg(len_mat.ra); + try fill_mat.finish(isel); + isel.freeReg(ptr_ra); + try dst_vi.liveOut(isel, ptr_ra); + }, + .many => unreachable, + .slice => { + try isel.emit(.cbz( + len_mat.ra.x(), + @intCast((isel.instructions.items.len + 1 - skip_label) << 2), + )); + try len_mat.finish(isel); + try fill_mat.finish(isel); + isel.freeReg(ptr_ra); + var dst_ptr_it = dst_vi.field(dst_ty, 0, 8); + const dst_ptr_vi = try dst_ptr_it.only(isel); + try dst_ptr_vi.?.liveOut(isel, ptr_ra); + }, + .c => unreachable, + } + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + break :air_tag; + }, + else => return isel.fail("too big {s} {f}", .{ @tagName(air_tag), isel.fmtType(dst_ty) }), + } + }; + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memset", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const dst_vi = try isel.use(bin_op.lhs); + switch (dst_info.flags.size) { + .one => { + try isel.movImmediate(.x2, ZigType.fromInterned(dst_info.child).abiSize(zcu)); + switch (fill_byte) { + .constant => |byte| try isel.movImmediate(.w1, byte), + .value => |byte| try call.paramLiveOut(isel, try isel.use(byte), .r1), + } + try call.paramLiveOut(isel, dst_vi, .r0); + }, + .many => unreachable, + .slice => { + var dst_ptr_it = dst_vi.field(dst_ty, 0, 8); + const dst_ptr_vi = try dst_ptr_it.only(isel); + var dst_len_it = dst_vi.field(dst_ty, 8, 8); + const dst_len_vi = try dst_len_it.only(isel); + try isel.elemPtr(.r2, .zr, .add, ZigType.fromInterned(dst_info.child).abiSize(zcu), dst_len_vi.?); + switch (fill_byte) { + .constant => |byte| try isel.movImmediate(.w1, byte), + .value => |byte| try call.paramLiveOut(isel, try isel.use(byte), .r1), + } + try call.paramLiveOut(isel, dst_ptr_vi.?, .r0); + }, + .c => unreachable, + } + try call.finishParams(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .memcpy, .memmove => |air_tag| { + const bin_op = air.data(air.inst_index).bin_op; + const dst_ty = isel.air.typeOf(bin_op.lhs, ip); + const dst_info = dst_ty.ptrInfo(zcu); + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = @tagName(air_tag), + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + switch (dst_info.flags.size) { + .one => { + const dst_vi = try isel.use(bin_op.lhs); + const src_vi = try isel.use(bin_op.rhs); + try isel.movImmediate(.x2, ZigType.fromInterned(dst_info.child).abiSize(zcu)); + try call.paramLiveOut(isel, src_vi, .r1); + try call.paramLiveOut(isel, dst_vi, .r0); + }, + .many => unreachable, + .slice => { + const dst_vi = try isel.use(bin_op.lhs); + var dst_ptr_it = dst_vi.field(dst_ty, 0, 8); + const dst_ptr_vi = try dst_ptr_it.only(isel); + var dst_len_it = dst_vi.field(dst_ty, 8, 8); + const dst_len_vi = try dst_len_it.only(isel); + const src_vi = try isel.use(bin_op.rhs); + try isel.elemPtr(.r2, .zr, .add, ZigType.fromInterned(dst_info.child).abiSize(zcu), dst_len_vi.?); + try call.paramLiveOut(isel, src_vi, .r1); + try call.paramLiveOut(isel, dst_ptr_vi.?, .r0); + }, + .c => unreachable, + } + try call.finishParams(isel); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .atomic_load => { + const atomic_load = air.data(air.inst_index).atomic_load; + const ptr_ty = isel.air.typeOf(atomic_load.ptr, ip); + const ptr_info = ptr_ty.ptrInfo(zcu); + if (atomic_load.order != .unordered) return isel.fail("ordered atomic load", .{}); + if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed atomic load", .{}); + + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| { + defer dst_vi.value.deref(isel); + var ptr_mat: ?Value.Materialize = null; + var dst_part_it = dst_vi.value.parts(isel); + while (dst_part_it.next()) |dst_part_vi| { + const dst_ra = try dst_part_vi.defReg(isel) orelse continue; + if (ptr_mat == null) { + const ptr_vi = try isel.use(atomic_load.ptr); + ptr_mat = try ptr_vi.matReg(isel); + } + try isel.emit(switch (dst_part_vi.size(isel)) { + else => |size| return isel.fail("bad atomic load size of {d} from {f}", .{ + size, isel.fmtType(ptr_ty), + }), + 1 => switch (dst_part_vi.signedness(isel)) { + .signed => .ldrsb(dst_ra.w(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + .unsigned => .ldrb(dst_ra.w(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + }, + 2 => switch (dst_part_vi.signedness(isel)) { + .signed => .ldrsh(dst_ra.w(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + .unsigned => .ldrh(dst_ra.w(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + }, + 4 => .ldr(dst_ra.w(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + 8 => .ldr(dst_ra.x(), .{ .unsigned_offset = .{ + .base = ptr_mat.?.ra.x(), + .offset = @intCast(dst_part_vi.get(isel).offset_from_parent), + } }), + }); + } + if (ptr_mat) |mat| try mat.finish(isel); + } else if (ptr_info.flags.is_volatile) return isel.fail("volatile atomic load", .{}); + + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .aggregate_init => { + if (isel.live_values.fetchRemove(air.inst_index)) |agg_vi| { + defer agg_vi.value.deref(isel); + + const ty_pl = air.data(air.inst_index).ty_pl; + const agg_ty = ty_pl.ty.toType(); + switch (ip.indexToKey(agg_ty.toIntern())) { + .array_type => |array_type| { + const elems: []const Air.Inst.Ref = + @ptrCast(isel.air.extra.items[ty_pl.payload..][0..@intCast(array_type.len)]); + var elem_offset: u64 = 0; + const elem_size = ZigType.fromInterned(array_type.child).abiSize(zcu); + for (elems) |elem| { + var agg_part_it = agg_vi.value.field(agg_ty, elem_offset, elem_size); + const agg_part_vi = try agg_part_it.only(isel); + try agg_part_vi.?.move(isel, elem); + elem_offset += elem_size; + } + switch (array_type.sentinel) { + .none => {}, + else => |sentinel| { + var agg_part_it = agg_vi.value.field(agg_ty, elem_offset, elem_size); + const agg_part_vi = try agg_part_it.only(isel); + try agg_part_vi.?.move(isel, .fromIntern(sentinel)); + }, + } + }, + .struct_type => { + const loaded_struct = ip.loadStructType(agg_ty.toIntern()); + const elems: []const Air.Inst.Ref = + @ptrCast(isel.air.extra.items[ty_pl.payload..][0..loaded_struct.field_types.len]); + var field_offset: u64 = 0; + var field_it = loaded_struct.iterateRuntimeOrder(ip); + while (field_it.next()) |field_index| { + const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]); + field_offset = field_ty.structFieldAlignment( + loaded_struct.fieldAlign(ip, field_index), + loaded_struct.layout, + zcu, + ).forward(field_offset); + const field_size = field_ty.abiSize(zcu); + if (field_size == 0) continue; + var agg_part_it = agg_vi.value.field(agg_ty, field_offset, field_size); + const agg_part_vi = try agg_part_it.only(isel); + try agg_part_vi.?.move(isel, elems[field_index]); + field_offset += field_size; + } + assert(field_offset == agg_vi.value.size(isel)); + }, + .tuple_type => |tuple_type| { + const elems: []const Air.Inst.Ref = + @ptrCast(isel.air.extra.items[ty_pl.payload..][0..tuple_type.types.len]); + var field_offset: u64 = 0; + for ( + tuple_type.types.get(ip), + tuple_type.values.get(ip), + elems, + ) |field_ty_index, field_val, elem| { + if (field_val != .none) continue; + const field_ty: ZigType = .fromInterned(field_ty_index); + field_offset = field_ty.abiAlignment(zcu).forward(field_offset); + const field_size = field_ty.abiSize(zcu); + if (field_size == 0) continue; + var agg_part_it = agg_vi.value.field(agg_ty, field_offset, field_size); + const agg_part_vi = try agg_part_it.only(isel); + try agg_part_vi.?.move(isel, elem); + field_offset += field_size; + } + assert(field_offset == agg_vi.value.size(isel)); + }, + else => return isel.fail("aggregate init {f}", .{isel.fmtType(agg_ty)}), + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .union_init => { + if (isel.live_values.fetchRemove(air.inst_index)) |un_vi| unused: { + defer un_vi.value.deref(isel); + + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.UnionInit, ty_pl.payload).data; + const un_ty = ty_pl.ty.toType(); + if (un_ty.containerLayout(zcu) != .@"extern") return isel.fail("bad union init {f}", .{isel.fmtType(un_ty)}); + + try un_vi.value.defAddr(isel, un_ty, null, comptime &.initFill(.free)) orelse break :unused; + + try call.prepareReturn(isel); + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = "memcpy", + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const init_vi = try isel.use(extra.init); + try isel.movImmediate(.x2, init_vi.size(isel)); + try call.paramAddress(isel, init_vi, .r1); + try call.paramAddress(isel, un_vi.value, .r0); + try call.finishParams(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .prefetch => { + const prefetch = air.data(air.inst_index).prefetch; + if (!(prefetch.rw == .write and prefetch.cache == .instruction)) { + const maybe_slice_ty = isel.air.typeOf(prefetch.ptr, ip); + const maybe_slice_vi = try isel.use(prefetch.ptr); + const ptr_vi = if (maybe_slice_ty.isSlice(zcu)) ptr_vi: { + var ptr_part_it = maybe_slice_vi.field(maybe_slice_ty, 0, 8); + const ptr_part_vi = try ptr_part_it.only(isel); + break :ptr_vi ptr_part_vi.?; + } else maybe_slice_vi; + const ptr_mat = try ptr_vi.matReg(isel); + try isel.emit(.prfm(.{ + .policy = switch (prefetch.locality) { + 1, 2, 3 => .keep, + 0 => .strm, + }, + .target = switch (prefetch.locality) { + 0, 3 => .l1, + 2 => .l2, + 1 => .l3, + }, + .type = switch (prefetch.rw) { + .read => switch (prefetch.cache) { + .data => .pld, + .instruction => .pli, + }, + .write => switch (prefetch.cache) { + .data => .pst, + .instruction => unreachable, + }, + }, + }, .{ .base = ptr_mat.ra.x() })); + try ptr_mat.finish(isel); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .mul_add => { + if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: { + defer res_vi.value.deref(isel); + + const pl_op = air.data(air.inst_index).pl_op; + const bin_op = isel.air.extraData(Air.Bin, pl_op.payload).data; + const ty = isel.air.typeOf(pl_op.operand, ip); + switch (ty.floatBits(isel.target)) { + else => unreachable, + 16, 32, 64 => |bits| { + const res_ra = try res_vi.value.defReg(isel) orelse break :unused; + const need_fcvt = switch (bits) { + else => unreachable, + 16 => !isel.target.cpu.has(.aarch64, .fullfp16), + 32, 64 => false, + }; + if (need_fcvt) try isel.emit(.fcvt(res_ra.h(), res_ra.s())); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const addend_vi = try isel.use(pl_op.operand); + const lhs_mat = try lhs_vi.matReg(isel); + const rhs_mat = try rhs_vi.matReg(isel); + const addend_mat = try addend_vi.matReg(isel); + const lhs_ra = if (need_fcvt) try isel.allocVecReg() else lhs_mat.ra; + defer if (need_fcvt) isel.freeReg(lhs_ra); + const rhs_ra = if (need_fcvt) try isel.allocVecReg() else rhs_mat.ra; + defer if (need_fcvt) isel.freeReg(rhs_ra); + const addend_ra = if (need_fcvt) try isel.allocVecReg() else addend_mat.ra; + defer if (need_fcvt) isel.freeReg(addend_ra); + try isel.emit(bits: switch (bits) { + else => unreachable, + 16 => if (need_fcvt) + continue :bits 32 + else + .fmadd(res_ra.h(), lhs_ra.h(), rhs_ra.h(), addend_ra.h()), + 32 => .fmadd(res_ra.s(), lhs_ra.s(), rhs_ra.s(), addend_ra.s()), + 64 => .fmadd(res_ra.d(), lhs_ra.d(), rhs_ra.d(), addend_ra.d()), + }); + if (need_fcvt) { + try isel.emit(.fcvt(addend_ra.s(), addend_mat.ra.h())); + try isel.emit(.fcvt(rhs_ra.s(), rhs_mat.ra.h())); + try isel.emit(.fcvt(lhs_ra.s(), lhs_mat.ra.h())); + } + try addend_mat.finish(isel); + try rhs_mat.finish(isel); + try lhs_mat.finish(isel); + }, + 80, 128 => |bits| { + try call.prepareReturn(isel); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => try call.returnLiveIn(isel, res_vi.value, .v0), + 80 => { + var res_hi16_it = res_vi.value.field(ty, 8, 8); + const res_hi16_vi = try res_hi16_it.only(isel); + try call.returnLiveIn(isel, res_hi16_vi.?, .r1); + var res_lo64_it = res_vi.value.field(ty, 0, 8); + const res_lo64_vi = try res_lo64_it.only(isel); + try call.returnLiveIn(isel, res_lo64_vi.?, .r0); + }, + } + try call.finishReturn(isel); + + try call.prepareCallee(isel); + try isel.global_relocs.append(gpa, .{ + .global = switch (bits) { + else => unreachable, + 16 => "__fmah", + 32 => "fmaf", + 64 => "fma", + 80 => "__fmax", + 128 => "fmaq", + }, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.bl(0)); + try call.finishCallee(isel); + + try call.prepareParams(isel); + const lhs_vi = try isel.use(bin_op.lhs); + const rhs_vi = try isel.use(bin_op.rhs); + const addend_vi = try isel.use(pl_op.operand); + switch (bits) { + else => unreachable, + 16, 32, 64, 128 => { + try call.paramLiveOut(isel, addend_vi, .v2); + try call.paramLiveOut(isel, rhs_vi, .v1); + try call.paramLiveOut(isel, lhs_vi, .v0); + }, + 80 => { + var addend_hi16_it = addend_vi.field(ty, 8, 8); + const addend_hi16_vi = try addend_hi16_it.only(isel); + try call.paramLiveOut(isel, addend_hi16_vi.?, .r5); + var addend_lo64_it = addend_vi.field(ty, 0, 8); + const addend_lo64_vi = try addend_lo64_it.only(isel); + try call.paramLiveOut(isel, addend_lo64_vi.?, .r4); + var rhs_hi16_it = rhs_vi.field(ty, 8, 8); + const rhs_hi16_vi = try rhs_hi16_it.only(isel); + try call.paramLiveOut(isel, rhs_hi16_vi.?, .r3); + var rhs_lo64_it = rhs_vi.field(ty, 0, 8); + const rhs_lo64_vi = try rhs_lo64_it.only(isel); + try call.paramLiveOut(isel, rhs_lo64_vi.?, .r2); + var lhs_hi16_it = lhs_vi.field(ty, 8, 8); + const lhs_hi16_vi = try lhs_hi16_it.only(isel); + try call.paramLiveOut(isel, lhs_hi16_vi.?, .r1); + var lhs_lo64_it = lhs_vi.field(ty, 0, 8); + const lhs_lo64_vi = try lhs_lo64_it.only(isel); + try call.paramLiveOut(isel, lhs_lo64_vi.?, .r0); + }, + } + try call.finishParams(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .field_parent_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: { + defer dst_vi.value.deref(isel); + const ty_pl = air.data(air.inst_index).ty_pl; + const extra = isel.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; + switch (codegen.fieldOffset( + ty_pl.ty.toType(), + isel.air.typeOf(extra.field_ptr, ip), + extra.field_index, + zcu, + )) { + 0 => try dst_vi.value.move(isel, extra.field_ptr), + else => |field_offset| { + const dst_ra = try dst_vi.value.defReg(isel) orelse break :unused; + const src_vi = try isel.use(extra.field_ptr); + const src_mat = try src_vi.matReg(isel); + const lo12: u12 = @truncate(field_offset >> 0); + const hi12: u12 = @intCast(field_offset >> 12); + if (hi12 > 0) try isel.emit(.sub( + dst_ra.x(), + if (lo12 > 0) dst_ra.x() else src_mat.ra.x(), + .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } }, + )); + if (lo12 > 0) try isel.emit(.sub(dst_ra.x(), src_mat.ra.x(), .{ .immediate = lo12 })); + try src_mat.finish(isel); + }, + } + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .runtime_nav_ptr => { + if (isel.live_values.fetchRemove(air.inst_index)) |ptr_vi| unused: { + defer ptr_vi.value.deref(isel); + const ptr_ra = try ptr_vi.value.defReg(isel) orelse break :unused; + + const ty_nav = air.data(air.inst_index).ty_nav; + if (ZigType.fromInterned(ip.getNav(ty_nav.nav).typeOf(ip)).isFnOrHasRuntimeBits(zcu)) switch (true) { + false => { + try isel.nav_relocs.append(zcu.gpa, .{ + .nav = ty_nav.nav, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.adr(ptr_ra.x(), 0)); + }, + true => { + try isel.nav_relocs.append(zcu.gpa, .{ + .nav = ty_nav.nav, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.add(ptr_ra.x(), ptr_ra.x(), .{ .immediate = 0 })); + try isel.nav_relocs.append(zcu.gpa, .{ + .nav = ty_nav.nav, + .reloc = .{ .label = @intCast(isel.instructions.items.len) }, + }); + try isel.emit(.adrp(ptr_ra.x(), 0)); + }, + } else try isel.movImmediate(ptr_ra.x(), isel.pt.navAlignment(ty_nav.nav).forward(0xaaaaaaaaaaaaaaaa)); + } + if (air.next()) |next_air_tag| continue :air_tag next_air_tag; + }, + .add_safe, + .sub_safe, + .mul_safe, + .inferred_alloc, + .inferred_alloc_comptime, + .int_from_float_safe, + .int_from_float_optimized_safe, + .wasm_memory_size, + .wasm_memory_grow, + .work_item_id, + .work_group_size, + .work_group_id, + => unreachable, + } + assert(air.body_index == 0); +} + +pub fn verify(isel: *Select, check_values: bool) void { + if (!std.debug.runtime_safety) return; + assert(isel.blocks.count() == 1 and isel.blocks.keys()[0] == Select.Block.main); + assert(isel.active_loops.items.len == 0); + assert(isel.dom_start == 0 and isel.dom_len == 0); + var live_reg_it = isel.live_registers.iterator(); + while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) { + _ => { + isel.dumpValues(.all); + unreachable; + }, + .allocating, .free => {}, + }; + if (check_values) for (isel.values.items) |value| if (value.refs != 0) { + isel.dumpValues(.only_referenced); + unreachable; + }; +} + +/// Stack Frame Layout +/// +-+-----------------------------------+ +/// |R| allocated stack | +/// +-+-----------------------------------+ +/// |S| caller frame record | +---------------+ +/// +-+-----------------------------------+ <-| entry/exit FP | +/// |R| caller frame | +---------------+ +/// +-+-----------------------------------+ +/// |R| variable incoming stack arguments | +---------------+ +/// +-+-----------------------------------+ <-| __stack | +/// |S| named incoming stack arguments | +---------------+ +/// +-+-----------------------------------+ <-| entry/exit SP | +/// |S| incoming gr arguments | | __gr_top | +/// +-+-----------------------------------+ +---------------+ +/// |S| alignment gap | +/// +-+-----------------------------------+ +/// |S| frame record | +----------+ +/// +-+-----------------------------------+ <-| FP | +/// |S| incoming vr arguments | | __vr_top | +/// +-+-----------------------------------+ +----------+ +/// |L| alignment gap | +/// +-+-----------------------------------+ +/// |L| callee saved vr area | +/// +-+-----------------------------------+ +/// |L| callee saved gr area | +----------------------+ +/// +-+-----------------------------------+ <-| prologue/epilogue SP | +/// |R| realignment gap | +----------------------+ +/// +-+-----------------------------------+ +/// |L| locals | +/// +-+-----------------------------------+ +/// |S| outgoing stack arguments | +----+ +/// +-+-----------------------------------+ <-| SP | +/// |R| unallocated stack | +----+ +/// +-+-----------------------------------+ +/// [S] Size computed by `analyze`, can be used by the body. +/// [L] Size computed by `layout`, can be used by the prologue/epilogue. +/// [R] Size unknown until runtime, can vary from one call to the next. +/// +/// Constraints that led to this layout: +/// * FP to __stack/__gr_top/__vr_top must only pass through [S] +/// * SP to outgoing stack arguments/locals must only pass through [S] +/// * entry/exit SP to prologue/epilogue SP must only pass through [S/L] +/// * all save areas must be at a positive offset from prologue/epilogue SP +/// * the entry/exit SP to prologue/epilogue SP distance must +/// - be a multiple of 16 due to hardware restrictions on the value of SP +/// - conform to the limit from the first matching condition in the +/// following list due to instruction encoding limitations +/// 1. callee saved gr count >= 2: multiple of 8 of at most 504 bytes +/// 2. callee saved vr count >= 2: multiple of 8 of at most 504 bytes +/// 3. callee saved gr count >= 1: at most 255 bytes +/// 4. callee saved vr count >= 1: at most 255 bytes +/// 5. variable incoming vr argument count >= 2: multiple of 16 of at most 1008 bytes +/// 6. variable incoming vr argument count >= 1: at most 255 bytes +/// 7. have frame record: multiple of 8 of at most 504 bytes +pub fn layout( + isel: *Select, + incoming: CallAbiIterator, + have_va: bool, + saved_gra_len: u7, + saved_vra_len: u7, + mod: *const Package.Module, +) !usize { + const zcu = isel.pt.zcu; + const ip = &zcu.intern_pool; + const nav = ip.getNav(isel.nav_index); + wip_mir_log.debug("{f}
:\n", .{nav.fqn.fmt(ip)}); + + const stack_size: u24 = @intCast(InternPool.Alignment.@"16".forward(isel.stack_size)); + const stack_size_low: u12 = @truncate(stack_size >> 0); + const stack_size_high: u12 = @truncate(stack_size >> 12); + + var saves_buf: [10 + 8 + 8 + 2 + 8]struct { + class: enum { integer, vector }, + needs_restore: bool, + register: Register, + offset: u10, + size: u5, + } = undefined; + const saves, const saves_size, const frame_record_offset = saves: { + var saves_len: usize = 0; + var saves_size: u10 = 0; + var save_ra: Register.Alias = undefined; + + // callee saved gr area + save_ra = .r19; + while (save_ra != .r29) : (save_ra = @enumFromInt(@intFromEnum(save_ra) + 1)) { + if (!isel.saved_registers.contains(save_ra)) continue; + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = true, + .register = save_ra.x(), + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + } + var deferred_gr = if (saves_size == 8 or (saves_size % 16 != 0 and saved_gra_len % 2 != 0)) gr: { + saves_len -= 1; + saves_size -= 8; + break :gr saves_buf[saves_len].register; + } else null; + defer assert(deferred_gr == null); + + // callee saved vr area + save_ra = .v8; + while (save_ra != .v16) : (save_ra = @enumFromInt(@intFromEnum(save_ra) + 1)) { + if (!isel.saved_registers.contains(save_ra)) continue; + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .vector, + .needs_restore = true, + .register = save_ra.d(), + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + } + if (deferred_gr != null and saved_gra_len % 2 == 0) { + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = true, + .register = deferred_gr.?, + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + deferred_gr = null; + } + if (saves_size % 16 != 0 and saved_vra_len % 2 != 0) { + const prev_save = &saves_buf[saves_len - 1]; + switch (prev_save.class) { + .integer => {}, + .vector => { + prev_save.register = prev_save.register.alias.q(); + prev_save.size = 16; + saves_size += 8; + }, + } + } + + // incoming vr arguments + save_ra = if (mod.strip) incoming.nsrn else CallAbiIterator.nsrn_start; + while (save_ra != if (have_va) CallAbiIterator.nsrn_end else incoming.nsrn) : (save_ra = @enumFromInt(@intFromEnum(save_ra) + 1)) { + saves_size = std.mem.alignForward(u10, saves_size, 16); + saves_buf[saves_len] = .{ + .class = .vector, + .needs_restore = false, + .register = save_ra.q(), + .offset = saves_size, + .size = 16, + }; + saves_len += 1; + saves_size += 16; + } + + // frame record + saves_size = std.mem.alignForward(u10, saves_size, 16); + const frame_record_offset = saves_size; + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = true, + .register = .fp, + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = true, + .register = .lr, + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + + // incoming gr arguments + if (deferred_gr) |gr| { + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = true, + .register = gr, + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + deferred_gr = null; + } + save_ra = if (mod.strip) incoming.ngrn else CallAbiIterator.ngrn_start; + while (save_ra != if (have_va) CallAbiIterator.ngrn_end else incoming.ngrn) : (save_ra = @enumFromInt(@intFromEnum(save_ra) + 1)) { + saves_size = std.mem.alignForward(u10, saves_size, 8); + saves_buf[saves_len] = .{ + .class = .integer, + .needs_restore = false, + .register = save_ra.x(), + .offset = saves_size, + .size = 8, + }; + saves_len += 1; + saves_size += 8; + } + + assert(InternPool.Alignment.@"16".check(saves_size)); + break :saves .{ saves_buf[0..saves_len], saves_size, frame_record_offset }; + }; + + { + wip_mir_log.debug("{f}