authorgravatar for joachim.schmidt557@outlook.comJoachim Schmidt <joachim.schmidt557@outlook.com> 2022-02-11 19:06:46+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-02-12 00:01:05+01:00
log2262640e8bf4cbcc4e6a46405d7a5a20562b8e47
tree20d751d8326a7e22665d1edecb4cd7fd680ec84e
parenta5a7f0ff0069d68ad60391dadce52321dd316138

stage2 ARM: lower const slices

Follow-up to e1a535360fb9ed08fc48018571b9702ab12a5876 for ARM This also fixes some stack offset calculation bugs

3 files changed, 119 insertions(+), 108 deletions(-)

src/arch/arm/CodeGen.zig+119-88
......@@ -1170,10 +1170,10 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
11701170 .dead, .unreach => unreachable,
11711171 .register => unreachable, // a slice doesn't fit in one register
11721172 .stack_argument_offset => |off| {
1173 break :result MCValue{ .stack_argument_offset = off };
1173 break :result MCValue{ .stack_argument_offset = off + 4 };
11741174 },
11751175 .stack_offset => |off| {
1176 break :result MCValue{ .stack_offset = off };
1176 break :result MCValue{ .stack_offset = off + 4 };
11771177 },
11781178 .memory => |addr| {
11791179 break :result MCValue{ .memory = addr };
......@@ -1192,10 +1192,10 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
11921192 .dead, .unreach => unreachable,
11931193 .register => unreachable, // a slice doesn't fit in one register
11941194 .stack_argument_offset => |off| {
1195 break :result MCValue{ .stack_argument_offset = off + 4 };
1195 break :result MCValue{ .stack_argument_offset = off };
11961196 },
11971197 .stack_offset => |off| {
1198 break :result MCValue{ .stack_offset = off + 4 };
1198 break :result MCValue{ .stack_offset = off };
11991199 },
12001200 .memory => |addr| {
12011201 break :result MCValue{ .memory = addr + 4 };
......@@ -1260,7 +1260,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
12601260 defer if (index_is_register) self.register_manager.unfreezeRegs(&.{index_mcv.register});
12611261
12621262 const base_mcv: MCValue = switch (slice_mcv) {
1263 .stack_offset => .{ .register = try self.copyToTmpRegister(slice_ptr_field_type, slice_mcv) },
1263 .stack_offset => |off| .{ .register = try self.copyToTmpRegister(slice_ptr_field_type, .{ .stack_offset = off + 4 }) },
12641264 else => return self.fail("TODO slice_elem_val when slice is {}", .{slice_mcv}),
12651265 };
12661266 self.register_manager.freezeRegs(&.{base_mcv.register});
......@@ -1471,36 +1471,6 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
14711471
14721472 try self.load(.{ .register = tmp_reg }, ptr, ptr_ty);
14731473 try self.genSetStack(elem_ty, off, MCValue{ .register = tmp_reg });
1474 } else if (elem_size == 8) {
1475 // TODO generalize this: maybe add a
1476 // genArmMemcpy function which manually copies
1477 // data if the size is below a certain
1478 // threshold and calls "memcpy" if the size is
1479 // larger
1480
1481 const usize_ty = Type.initTag(.usize);
1482 const tmp_regs = try self.register_manager.allocRegs(2, .{ null, null });
1483 self.register_manager.freezeRegs(&tmp_regs);
1484 defer self.register_manager.unfreezeRegs(&tmp_regs);
1485
1486 _ = try self.addInst(.{
1487 .tag = .ldr,
1488 .data = .{ .rr_offset = .{
1489 .rt = tmp_regs[0],
1490 .rn = reg,
1491 .offset = .{ .offset = Instruction.Offset.none },
1492 } },
1493 });
1494 _ = try self.addInst(.{
1495 .tag = .ldr,
1496 .data = .{ .rr_offset = .{
1497 .rt = tmp_regs[1],
1498 .rn = reg,
1499 .offset = .{ .offset = Instruction.Offset.imm(4) },
1500 } },
1501 });
1502 try self.genSetStack(usize_ty, off, MCValue{ .register = tmp_regs[0] });
1503 try self.genSetStack(usize_ty, off + 4, MCValue{ .register = tmp_regs[1] });
15041474 } else {
15051475 // TODO optimize the register allocation
15061476 const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null });
......@@ -1677,7 +1647,7 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
16771647 .ptr_stack_offset => |off| {
16781648 break :result MCValue{ .ptr_stack_offset = off + struct_size - struct_field_offset - struct_field_size };
16791649 },
1680 .stack_argument_offset => {
1650 else => {
16811651 const offset_reg = try self.copyToTmpRegister(ptr_ty, .{
16821652 .immediate = struct_field_offset,
16831653 });
......@@ -1699,7 +1669,6 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
16991669 );
17001670 break :result MCValue{ .register = dst_reg };
17011671 },
1702 else => return self.fail("TODO implement codegen struct_field_ptr for {}", .{mcv}),
17031672 }
17041673 };
17051674}
......@@ -3200,9 +3169,9 @@ fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void {
32003169}
32013170
32023171fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
3172 const abi_size = @intCast(u32, ty.abiSize(self.target.*));
32033173 switch (mcv) {
32043174 .dead => unreachable,
3205 .ptr_embedded_in_code => unreachable,
32063175 .unreach, .none => return, // Nothing to do.
32073176 .undef => {
32083177 if (!self.wantSafety())
......@@ -3215,23 +3184,16 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
32153184 else => return self.fail("TODO implement memset", .{}),
32163185 }
32173186 },
3218 .ptr_stack_offset => {
3219 const reg = try self.copyToTmpRegister(ty, mcv);
3220 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
3221 },
32223187 .compare_flags_unsigned,
32233188 .compare_flags_signed,
32243189 .immediate,
3190 .ptr_stack_offset,
3191 .ptr_embedded_in_code,
32253192 => {
32263193 const reg = try self.copyToTmpRegister(ty, mcv);
32273194 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
32283195 },
3229 .embedded_in_code => |code_offset| {
3230 _ = code_offset;
3231 return self.fail("TODO implement set stack variable from embedded_in_code", .{});
3232 },
32333196 .register => |reg| {
3234 const abi_size = @intCast(u32, ty.abiSize(self.target.*));
32353197 const adj_off = stack_offset + abi_size;
32363198
32373199 switch (abi_size) {
......@@ -3279,24 +3241,23 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
32793241 }
32803242 },
32813243 .memory,
3244 .embedded_in_code,
32823245 .stack_argument_offset,
3246 .stack_offset,
32833247 => {
3284 if (ty.abiSize(self.target.*) <= 4) {
3285 const reg = try self.copyToTmpRegister(ty, mcv);
3286 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
3287 } else {
3288 return self.fail("TODO implement memcpy", .{});
3248 switch (mcv) {
3249 .stack_offset => |off| {
3250 if (stack_offset == off)
3251 return; // Copy stack variable to itself; nothing to do.
3252 },
3253 else => {},
32893254 }
3290 },
3291 .stack_offset => |off| {
3292 if (stack_offset == off)
3293 return; // Copy stack variable to itself; nothing to do.
32943255
3295 if (ty.abiSize(self.target.*) <= 4) {
3256 if (abi_size <= 4) {
32963257 const reg = try self.copyToTmpRegister(ty, mcv);
32973258 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
32983259 } else {
3299 // TODO optimize the register allocation
3260 // TODO call extern memcpy
33003261 const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null });
33013262 const src_reg = regs[0];
33023263 const dst_reg = regs[1];
......@@ -3304,22 +3265,31 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
33043265 const count_reg = regs[3];
33053266 const tmp_reg = regs[4];
33063267
3307 // sub src_reg, fp, #off
3308 const adj_src_offset = off + @intCast(u32, ty.abiSize(self.target.*));
3309 const src_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(adj_src_offset)) |x| x else {
3310 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
3311 };
3312 _ = try self.addInst(.{
3313 .tag = .sub,
3314 .data = .{ .rr_op = .{
3315 .rd = src_reg,
3316 .rn = .fp,
3317 .op = src_offset_op,
3318 } },
3319 });
3268 switch (mcv) {
3269 .stack_offset => |off| {
3270 // sub src_reg, fp, #off
3271 const adj_src_offset = off + @intCast(u32, abi_size);
3272 const src_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(adj_src_offset)) |x| x else {
3273 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
3274 };
3275 _ = try self.addInst(.{
3276 .tag = .sub,
3277 .data = .{ .rr_op = .{
3278 .rd = src_reg,
3279 .rn = .fp,
3280 .op = src_offset_op,
3281 } },
3282 });
3283 },
3284 .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = @intCast(u32, addr) }),
3285 .embedded_in_code,
3286 .stack_argument_offset,
3287 => return self.fail("TODO genSetStack with src={}", .{mcv}),
3288 else => unreachable,
3289 }
33203290
33213291 // sub dst_reg, fp, #stack_offset
3322 const adj_dst_offset = stack_offset + @intCast(u32, ty.abiSize(self.target.*));
3292 const adj_dst_offset = stack_offset + abi_size;
33233293 const dst_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(adj_dst_offset)) |x| x else {
33243294 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
33253295 };
......@@ -3332,9 +3302,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
33323302 } },
33333303 });
33343304
3335 // mov len, #elem_size
3336 const elem_size = @intCast(u32, ty.abiSize(self.target.*));
3337 const len_op: Instruction.Operand = if (Instruction.Operand.fromU32(elem_size)) |x| x else {
3305 // mov len, #abi_size
3306 const len_op: Instruction.Operand = if (Instruction.Operand.fromU32(abi_size)) |x| x else {
33383307 return self.fail("TODO load: set reg to elem_size with all possible sizes", .{});
33393308 };
33403309 _ = try self.addInst(.{
......@@ -3619,6 +3588,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
36193588}
36203589
36213590fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
3591 const abi_size = @intCast(u32, ty.abiSize(self.target.*));
36223592 switch (mcv) {
36233593 .dead => unreachable,
36243594 .none, .unreach => return,
......@@ -3634,7 +3604,6 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I
36343604 }
36353605 },
36363606 .register => |reg| {
3637 const abi_size = @intCast(u32, ty.abiSize(self.target.*));
36383607 const adj_off = stack_offset - abi_size;
36393608
36403609 switch (abi_size) {
......@@ -3675,28 +3644,86 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I
36753644 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
36763645 }
36773646 },
3678 .immediate,
3679 .compare_flags_signed,
3680 .compare_flags_unsigned,
36813647 .stack_offset,
36823648 .memory,
36833649 .stack_argument_offset,
36843650 .embedded_in_code,
36853651 => {
3686 if (ty.abiSize(self.target.*) <= 4) {
3652 if (abi_size <= 4) {
36873653 const reg = try self.copyToTmpRegister(ty, mcv);
36883654 return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg });
36893655 } else {
3690 return self.fail("TODO implement memcpy", .{});
3656 // TODO call extern memcpy
3657 const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null });
3658 const src_reg = regs[0];
3659 const dst_reg = regs[1];
3660 const len_reg = regs[2];
3661 const count_reg = regs[3];
3662 const tmp_reg = regs[4];
3663
3664 switch (mcv) {
3665 .stack_offset => |off| {
3666 // sub src_reg, fp, #off
3667 const adj_src_offset = off + abi_size;
3668 const src_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(adj_src_offset)) |x| x else {
3669 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
3670 };
3671 _ = try self.addInst(.{
3672 .tag = .sub,
3673 .data = .{ .rr_op = .{
3674 .rd = src_reg,
3675 .rn = .fp,
3676 .op = src_offset_op,
3677 } },
3678 });
3679 },
3680 .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = @intCast(u32, addr) }),
3681 .stack_argument_offset,
3682 .embedded_in_code,
3683 => return self.fail("TODO genSetStackArgument src={}", .{mcv}),
3684 else => unreachable,
3685 }
3686
3687 // add dst_reg, sp, #stack_offset
3688 const adj_dst_offset = stack_offset - abi_size;
3689 const dst_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(adj_dst_offset)) |x| x else {
3690 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
3691 };
3692 _ = try self.addInst(.{
3693 .tag = .add,
3694 .data = .{ .rr_op = .{
3695 .rd = dst_reg,
3696 .rn = .sp,
3697 .op = dst_offset_op,
3698 } },
3699 });
3700
3701 // mov len, #abi_size
3702 const len_op: Instruction.Operand = if (Instruction.Operand.fromU32(abi_size)) |x| x else {
3703 return self.fail("TODO load: set reg to elem_size with all possible sizes", .{});
3704 };
3705 _ = try self.addInst(.{
3706 .tag = .mov,
3707 .data = .{ .rr_op = .{
3708 .rd = len_reg,
3709 .rn = .r0,
3710 .op = len_op,
3711 } },
3712 });
3713
3714 // memcpy(src, dst, len)
3715 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
36913716 }
36923717 },
3693 .ptr_stack_offset => {
3718 .compare_flags_unsigned,
3719 .compare_flags_signed,
3720 .immediate,
3721 .ptr_stack_offset,
3722 .ptr_embedded_in_code,
3723 => {
36943724 const reg = try self.copyToTmpRegister(ty, mcv);
36953725 return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg });
36963726 },
3697 .ptr_embedded_in_code => {
3698 return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
3699 },
37003727 }
37013728}
37023729
......@@ -4114,9 +4141,13 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
41144141 var stack_offset: u32 = 0;
41154142
41164143 for (param_types) |ty, i| {
4117 stack_offset = std.mem.alignForwardGeneric(u32, stack_offset, ty.abiAlignment(self.target.*));
4118 result.args[i] = .{ .stack_argument_offset = stack_offset };
4119 stack_offset += @intCast(u32, ty.abiSize(self.target.*));
4144 if (ty.abiSize(self.target.*) > 0) {
4145 stack_offset = std.mem.alignForwardGeneric(u32, stack_offset, ty.abiAlignment(self.target.*));
4146 result.args[i] = .{ .stack_argument_offset = stack_offset };
4147 stack_offset += @intCast(u32, ty.abiSize(self.target.*));
4148 } else {
4149 result.args[i] = .{ .none = {} };
4150 }
41204151 }
41214152
41224153 result.stack_byte_count = stack_offset;
test/behavior/basic.zig-3
......@@ -216,7 +216,6 @@ test "compile time global reinterpret" {
216216}
217217
218218test "cast undefined" {
219 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
220219 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
221220
222221 const array: [100]u8 = undefined;
......@@ -678,7 +677,6 @@ test "string concatenation" {
678677}
679678
680679test "thread local variable" {
681 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
682680 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
683681 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
684682
......@@ -704,7 +702,6 @@ fn maybe(x: bool) anyerror!?u32 {
704702}
705703
706704test "pointer to thread local array" {
707 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
708705 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
709706 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
710707
test/behavior/slice.zig-17
......@@ -48,7 +48,6 @@ test "slicing" {
4848
4949test "const slice" {
5050 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
51 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
5251
5352 comptime {
5453 const a = "1234567890";
......@@ -61,7 +60,6 @@ test "const slice" {
6160
6261test "comptime slice of undefined pointer of length 0" {
6362 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
64 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
6563
6664 const slice1 = @as([*]i32, undefined)[0..0];
6765 try expect(slice1.len == 0);
......@@ -83,7 +81,6 @@ fn assertLenIsZero(msg: []const u8) !void {
8381
8482test "access len index of sentinel-terminated slice" {
8583 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
86 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
8784
8885 const S = struct {
8986 fn doTheTest() !void {
......@@ -99,7 +96,6 @@ test "access len index of sentinel-terminated slice" {
9996
10097test "comptime slice of slice preserves comptime var" {
10198 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
102 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
10399
104100 comptime {
105101 var buff: [10]u8 = undefined;
......@@ -110,7 +106,6 @@ test "comptime slice of slice preserves comptime var" {
110106
111107test "slice of type" {
112108 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
113 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
114109
115110 comptime {
116111 var types_array = [_]type{ i32, f64, type };
......@@ -151,7 +146,6 @@ fn memFree(comptime T: type, memory: []T) void {
151146
152147test "slice of hardcoded address to pointer" {
153148 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
154 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
155149
156150 const S = struct {
157151 fn doTheTest() !void {
......@@ -168,7 +162,6 @@ test "slice of hardcoded address to pointer" {
168162
169163test "comptime slice of pointer preserves comptime var" {
170164 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
171 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
172165
173166 comptime {
174167 var buff: [10]u8 = undefined;
......@@ -180,7 +173,6 @@ test "comptime slice of pointer preserves comptime var" {
180173
181174test "comptime pointer cast array and then slice" {
182175 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
183 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
184176
185177 const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
186178
......@@ -239,7 +231,6 @@ test "slice string literal has correct type" {
239231
240232test "result location zero sized array inside struct field implicit cast to slice" {
241233 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
242 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
243234
244235 const E = struct {
245236 entries: []u32,
......@@ -287,8 +278,6 @@ test "C pointer slice access" {
287278}
288279
289280test "comptime slices are disambiguated" {
290 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
291
292281 try expect(sliceSum(&[_]u8{ 1, 2 }) == 3);
293282 try expect(sliceSum(&[_]u8{ 3, 4 }) == 7);
294283}
......@@ -343,7 +332,6 @@ test "obtaining a null terminated slice" {
343332
344333test "empty array to slice" {
345334 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
346 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
347335
348336 const S = struct {
349337 fn doTheTest() !void {
......@@ -380,7 +368,6 @@ test "@ptrCast slice to pointer" {
380368
381369test "slice syntax resulting in pointer-to-array" {
382370 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
383 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
384371
385372 const S = struct {
386373 fn doTheTest() !void {
......@@ -529,7 +516,6 @@ test "slice syntax resulting in pointer-to-array" {
529516
530517test "type coercion of pointer to anon struct literal to pointer to slice" {
531518 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
532 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
533519
534520 const S = struct {
535521 const U = union {
......@@ -563,7 +549,6 @@ test "type coercion of pointer to anon struct literal to pointer to slice" {
563549
564550test "array concat of slices gives slice" {
565551 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
566 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
567552
568553 comptime {
569554 var a: []const u8 = "aoeu";
......@@ -575,7 +560,6 @@ test "array concat of slices gives slice" {
575560
576561test "slice bounds in comptime concatenation" {
577562 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
578 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
579563
580564 const bs = comptime blk: {
581565 const b = "........1........";
......@@ -592,7 +576,6 @@ test "slice bounds in comptime concatenation" {
592576
593577test "slice sentinel access at comptime" {
594578 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
595 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
596579
597580 {
598581 const str0 = &[_:0]u8{ '1', '2', '3' };