diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f9235512a7..aba98276c3 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -582,10 +582,10 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { switch (air_tags[inst]) { // zig fmt: off - .add, .ptr_add => try self.airAdd(inst), + .add => try self.airAdd(inst), .addwrap => try self.airAddWrap(inst), .add_sat => try self.airAddSat(inst), - .sub, .ptr_sub => try self.airSub(inst), + .sub => try self.airSub(inst), .subwrap => try self.airSubWrap(inst), .sub_sat => try self.airSubSat(inst), .mul => try self.airMul(inst), @@ -597,6 +597,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .shl_sat => try self.airShlSat(inst), .min => try self.airMin(inst), .max => try self.airMax(inst), + .ptr_add => try self.airPtrAdd(inst), + .ptr_sub => try self.airPtrSub(inst), .slice => try self.airSlice(inst), .sqrt, @@ -808,8 +810,8 @@ fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u if (abi_align > self.stack_align) self.stack_align = abi_align; // TODO find a free slot instead of always appending - const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align); - self.next_stack_offset = offset + abi_size; + const offset = mem.alignForwardGeneric(u32, self.next_stack_offset + abi_size, abi_align); + self.next_stack_offset = offset; if (self.next_stack_offset > self.max_end_stack) self.max_end_stack = self.next_stack_offset; try self.stack.putNoClobber(self.gpa, offset, .{ @@ -821,7 +823,8 @@ fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u /// Use a pointer instruction as the basis for allocating stack memory. fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 { - const elem_ty = self.air.typeOfIndex(inst).elemType(); + const ptr_ty = self.air.typeOfIndex(inst); + const elem_ty = ptr_ty.elemType(); if (!elem_ty.hasRuntimeBits()) { return self.allocMem(inst, 8, 8); @@ -831,7 +834,7 @@ fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 { return self.fail("type '{}' too big to fit into stack frame", .{elem_ty}); }; // TODO swap this for inst.ty.ptrAlign - const abi_align = elem_ty.abiAlignment(self.target.*); + const abi_align = ptr_ty.ptrAlignment(self.target.*); return self.allocMem(inst, abi_size, abi_align); } @@ -964,7 +967,7 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { const reg: Register = blk: { if (operand.isRegister()) { if (self.reuseOperand(inst, ty_op.operand, 0, operand)) { - break :blk operand.register; + break :blk operand.register.to64(); } } const mcv = try self.copyToNewRegister(inst, src_ty, operand); @@ -1068,13 +1071,81 @@ fn airMax(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } +fn genPtrBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref) !MCValue { + const dst_ty = self.air.typeOfIndex(inst); + const elem_size = dst_ty.elemType2().abiSize(self.target.*); + const ptr = try self.resolveInst(op_lhs); + const offset = try self.resolveInst(op_rhs); + const offset_ty = self.air.typeOf(op_rhs); + + ptr.freezeIfRegister(&self.register_manager); + defer ptr.unfreezeIfRegister(&self.register_manager); + + offset.freezeIfRegister(&self.register_manager); + defer offset.unfreezeIfRegister(&self.register_manager); + + const dst_mcv = blk: { + if (self.reuseOperand(inst, op_lhs, 0, ptr)) { + if (ptr.isMemory() or ptr.isRegister()) break :blk ptr; + } + break :blk try self.copyToNewRegister(inst, dst_ty, ptr); + }; + + const offset_mcv = blk: { + if (self.reuseOperand(inst, op_rhs, 1, offset)) { + if (offset.isRegister()) break :blk offset; + } + break :blk MCValue{ .register = try self.copyToTmpRegister(offset_ty, offset) }; + }; + + try self.genIMulOpMir(offset_ty, offset_mcv, .{ .immediate = elem_size }); + + const tag = self.air.instructions.items(.tag)[inst]; + switch (tag) { + .ptr_add => try self.genBinMathOpMir(.add, dst_ty, dst_mcv, offset_mcv), + .ptr_sub => try self.genBinMathOpMir(.sub, dst_ty, dst_mcv, offset_mcv), + else => unreachable, + } + + return dst_mcv; +} + +fn airPtrAdd(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result = if (self.liveness.isUnused(inst)) + .dead + else + try self.genPtrBinMathOp(inst, bin_op.lhs, bin_op.rhs); + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); +} + +fn airPtrSub(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result = if (self.liveness.isUnused(inst)) + .dead + else + try self.genPtrBinMathOp(inst, bin_op.lhs, bin_op.rhs); + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); +} + fn airSlice(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; - const result: MCValue = if (self.liveness.isUnused(inst)) - .dead - else - return self.fail("TODO implement slice for {}", .{self.target.cpu.arch}); + + if (self.liveness.isUnused(inst)) { + return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + const ptr = try self.resolveInst(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + const len = try self.resolveInst(bin_op.rhs); + const len_ty = self.air.typeOf(bin_op.rhs); + + const stack_offset = @intCast(i32, try self.allocMem(inst, 16, 16)); + try self.genSetStack(ptr_ty, stack_offset, ptr); + try self.genSetStack(len_ty, stack_offset - 8, len); + const result = MCValue{ .stack_offset = stack_offset }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } @@ -1378,7 +1449,7 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const dst_mcv: MCValue = blk: { switch (operand) { .stack_offset => |off| { - break :blk MCValue{ .stack_offset = off + 8 }; + break :blk MCValue{ .stack_offset = off }; }, else => return self.fail("TODO implement slice_ptr for {}", .{operand}), } @@ -1395,7 +1466,7 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { const dst_mcv: MCValue = blk: { switch (operand) { .stack_offset => |off| { - break :blk MCValue{ .stack_offset = off }; + break :blk MCValue{ .stack_offset = off - 8 }; }, else => return self.fail("TODO implement slice_len for {}", .{operand}), } @@ -1463,7 +1534,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg2 = .rbp, .flags = 0b01, }).encode(), - .data = .{ .imm = @bitCast(u32, -@intCast(i32, off + 16)) }, + .data = .{ .imm = @bitCast(u32, -@intCast(i32, off)) }, }); }, else => return self.fail("TODO implement slice_elem_val when slice is {}", .{slice_mcv}), @@ -1494,7 +1565,6 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const array_ty = self.air.typeOf(bin_op.lhs); - const array_abi_size = array_ty.abiSize(self.target.*); const array = try self.resolveInst(bin_op.lhs); array.freezeIfRegister(&self.register_manager); defer array.unfreezeIfRegister(&self.register_manager); @@ -1520,7 +1590,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg1 = addr_reg.to64(), .reg2 = .rbp, }).encode(), - .data = .{ .imm = @bitCast(u32, -(off + @intCast(i32, array_abi_size))) }, + .data = .{ .imm = @bitCast(u32, -off) }, }); }, else => return self.fail("TODO implement array_elem_val when array is {}", .{array}), @@ -1705,36 +1775,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo return self.genSetStack(elem_ty, off, MCValue{ .register = tmp_reg }); } - self.register_manager.freezeRegs(&.{ .rax, .rcx }); - defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx }); - - const regs = try self.register_manager.allocRegs(3, .{ null, null, null }); - const addr_reg = regs[0]; - const count_reg = regs[1]; - const tmp_reg = regs[2]; - - _ = try self.addInst(.{ - .tag = .mov, - .ops = (Mir.Ops{ - .reg1 = registerAlias(addr_reg, @divExact(reg.size(), 8)), - .reg2 = reg, - }).encode(), - .data = undefined, - }); - - try self.register_manager.getReg(.rax, null); - try self.register_manager.getReg(.rcx, null); - - // TODO allow for abi size to be u64 - try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); - - return self.genInlineMemcpy( - -(off + @intCast(i32, abi_size)), - .rbp, - registerAlias(addr_reg, @divExact(reg.size(), 8)), - count_reg.to64(), - tmp_reg.to8(), - ); + try self.genInlineMemcpy(off, .rbp, elem_ty, ptr); }, else => return self.fail("TODO implement loading from register into {}", .{dst_mcv}), } @@ -2016,10 +2057,7 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde const mcv = try self.resolveInst(operand); const ptr_ty = self.air.typeOf(operand); const struct_ty = ptr_ty.childType(); - const struct_size = @intCast(u32, struct_ty.abiSize(self.target.*)); const struct_field_offset = @intCast(u32, struct_ty.structFieldOffset(index, self.target.*)); - const struct_field_ty = struct_ty.structFieldType(index); - const struct_field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*)); const dst_mcv: MCValue = result: { switch (mcv) { @@ -2035,8 +2073,7 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde break :result dst_mcv; }, .ptr_stack_offset => |off| { - const offset_to_field = struct_size - struct_field_offset - struct_field_size; - const ptr_stack_offset = off + @intCast(i32, offset_to_field); + const ptr_stack_offset = off - @intCast(i32, struct_field_offset); break :result MCValue{ .ptr_stack_offset = ptr_stack_offset }; }, .register => |reg| { @@ -2076,15 +2113,12 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const mcv = try self.resolveInst(operand); const struct_ty = self.air.typeOf(operand); - const struct_size = struct_ty.abiSize(self.target.*); const struct_field_offset = struct_ty.structFieldOffset(index, self.target.*); const struct_field_ty = struct_ty.structFieldType(index); - const struct_field_size = struct_field_ty.abiSize(self.target.*); switch (mcv) { .stack_offset => |off| { - const offset_to_field = struct_size - struct_field_offset - struct_field_size; - const stack_offset = off + @intCast(i32, offset_to_field); + const stack_offset = off - @intCast(i32, struct_field_offset); break :result MCValue{ .stack_offset = stack_offset }; }, .register => |reg| { @@ -2141,14 +2175,6 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { /// Currently, the following ops are supported: /// ADD, SUB, XOR, OR, AND fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref) !MCValue { - // We'll handle these ops in two steps. - // 1) Prepare an output location (register or memory) - // This location will be the location of the operand that dies (if one exists) - // or just a temporary register (if one doesn't exist) - // 2) Perform the op with the other argument - // 3) Sometimes, the output location is memory but the op doesn't support it. - // In this case, copy that location to a register, then perform the op to that register instead. - // // TODO: make this algorithm less bad const lhs = try self.resolveInst(op_lhs); const rhs = try self.resolveInst(op_rhs); @@ -2161,6 +2187,7 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: const dst_ty = self.air.typeOfIndex(inst); var dst_mcv: MCValue = undefined; var src_mcv: MCValue = undefined; + if (self.reuseOperand(inst, op_lhs, 0, lhs)) { // LHS dies; use it as the destination. // Both operands cannot be memory. @@ -2207,17 +2234,15 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: dst_mcv.freezeIfRegister(&self.register_manager); defer dst_mcv.unfreezeIfRegister(&self.register_manager); - const tmp_reg = try self.copyToTmpRegister(Type.u64, src_mcv); - src_mcv = MCValue{ .register = tmp_reg }; + src_mcv = try self.copyToNewRegister(inst, Type.u64, src_mcv); } }, else => {}, } - // Now for step 2, we assing an MIR instruction - const air_tags = self.air.instructions.items(.tag); - switch (air_tags[inst]) { - .add, .addwrap, .ptr_add => try self.genBinMathOpMir(.add, dst_ty, dst_mcv, src_mcv), + const tag = self.air.instructions.items(.tag)[inst]; + switch (tag) { + .add, .addwrap => try self.genBinMathOpMir(.add, dst_ty, dst_mcv, src_mcv), .bool_or, .bit_or => try self.genBinMathOpMir(.@"or", dst_ty, dst_mcv, src_mcv), .bool_and, .bit_and => try self.genBinMathOpMir(.@"and", dst_ty, dst_mcv, src_mcv), .sub, .subwrap => try self.genBinMathOpMir(.sub, dst_ty, dst_mcv, src_mcv), @@ -2225,7 +2250,6 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: .mul, .mulwrap => try self.genIMulOpMir(dst_ty, dst_mcv, src_mcv), else => unreachable, } - return dst_mcv; } @@ -2244,7 +2268,12 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC .none => unreachable, .undef => try self.genSetReg(dst_ty, dst_reg, .undef), .dead, .unreach => unreachable, - .ptr_stack_offset => unreachable, + .ptr_stack_offset => { + self.register_manager.freezeRegs(&.{dst_reg}); + defer self.register_manager.unfreezeRegs(&.{dst_reg}); + const reg = try self.copyToTmpRegister(dst_ty, src_mcv); + return self.genBinMathOpMir(mir_tag, dst_ty, dst_mcv, .{ .register = reg }); + }, .ptr_embedded_in_code => unreachable, .register => |src_reg| { _ = try self.addInst(.{ @@ -2265,21 +2294,21 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC .data = .{ .imm = @truncate(u32, imm) }, }); }, - .embedded_in_code, .memory => { + .embedded_in_code, + .memory, + .got_load, + .direct_load, + => { assert(abi_size <= 8); self.register_manager.freezeRegs(&.{dst_reg}); defer self.register_manager.unfreezeRegs(&.{dst_reg}); const reg = try self.copyToTmpRegister(dst_ty, src_mcv); return self.genBinMathOpMir(mir_tag, dst_ty, dst_mcv, .{ .register = reg }); }, - .got_load, .direct_load => { - return self.fail("TODO implement x86 ADD/SUB/CMP source symbol at index in linker", .{}); - }, .stack_offset => |off| { if (off > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - const adj_off = off + @intCast(i32, abi_size); _ = try self.addInst(.{ .tag = mir_tag, .ops = (Mir.Ops{ @@ -2287,7 +2316,7 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC .reg2 = .rbp, .flags = 0b01, }).encode(), - .data = .{ .imm = @bitCast(u32, -adj_off) }, + .data = .{ .imm = @bitCast(u32, -off) }, }); }, .compare_flags_unsigned => { @@ -2305,7 +2334,6 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC if (abi_size > 8) { return self.fail("TODO implement ADD/SUB/CMP for stack dst with large ABI", .{}); } - const adj_off = off + @intCast(i32, abi_size); switch (src_mcv) { .none => unreachable, @@ -2321,7 +2349,7 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), .flags = 0b10, }).encode(), - .data = .{ .imm = @bitCast(u32, -adj_off) }, + .data = .{ .imm = @bitCast(u32, -off) }, }); }, .immediate => |imm| { @@ -2342,7 +2370,7 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC else => unreachable, }; const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -adj_off), + .dest_off = @bitCast(u32, -off), .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -2493,9 +2521,16 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { self.arg_index += 1; const mcv = self.args[arg_index]; + const max_stack = loop: for (self.args) |arg| { + switch (arg) { + .stack_offset => |last| break :loop last, + else => {}, + } + } else 0; const payload = try self.addExtra(Mir.ArgDbgInfo{ .air_inst = inst, .arg_index = arg_index, + .max_stack = @intCast(u32, max_stack), }); _ = try self.addInst(.{ .tag = .arg_dbg_info, @@ -2511,11 +2546,9 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { self.register_manager.getRegAssumeFree(reg.to64(), inst); break :blk mcv; }, - .stack_offset => { - const ty = self.air.typeOfIndex(inst); - const abi_size = ty.abiSize(self.target.*); - const off = @intCast(i32, (arg_index + 1) * abi_size) + 16; - break :blk MCValue{ .stack_offset = -off }; + .stack_offset => |off| { + const offset = max_stack - off + 16; + break :blk MCValue{ .stack_offset = -offset }; }, else => return self.fail("TODO implement arg for {}", .{mcv}), } @@ -2558,7 +2591,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { var info = try self.resolveCallingConventionValues(fn_ty); defer info.deinit(self); - var stack_adjustment: u32 = 0; + var stack_adjustment: ?u32 = null; for (args) |arg, arg_i| { const mc_arg = info.args[arg_i]; const arg_ty = self.air.typeOf(arg); @@ -2572,9 +2605,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { try self.genSetReg(arg_ty, reg, arg_mcv); }, .stack_offset => |off| { - const abi_size = @intCast(u32, arg_ty.abiSize(self.target.*)); try self.genSetStackArg(arg_ty, off, arg_mcv); - stack_adjustment += abi_size; + if (stack_adjustment == null) { + stack_adjustment = @intCast(u32, off); + } }, .ptr_stack_offset => { return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{}); @@ -2595,14 +2629,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { } } - if (stack_adjustment > 0) { + if (stack_adjustment) |off| { // Adjust the stack _ = try self.addInst(.{ .tag = .sub, .ops = (Mir.Ops{ .reg1 = .rsp, }).encode(), - .data = .{ .imm = stack_adjustment }, + .data = .{ .imm = off }, }); } @@ -2730,14 +2764,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { } } else unreachable; - if (stack_adjustment > 0) { + if (stack_adjustment) |off| { // Readjust the stack _ = try self.addInst(.{ .tag = .add, .ops = (Mir.Ops{ .reg1 = .rsp, }).encode(), - .data = .{ .imm = stack_adjustment }, + .data = .{ .imm = off }, }); } @@ -3493,14 +3527,13 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, .{ .register = reg }); }, .immediate => |imm| { - const off = stack_offset + @intCast(i32, abi_size); switch (abi_size) { 1, 2, 4 => { // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -off), + .dest_off = @bitCast(u32, -stack_offset), .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -3540,62 +3573,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); } - self.register_manager.freezeRegs(&.{ .rax, .rcx }); - defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx }); - - const addr_reg: Register = blk: { - switch (mcv) { - .got_load, - .direct_load, - => |sym_index| { - const flags: u2 = switch (mcv) { - .got_load => 0b00, - .direct_load => 0b01, - else => unreachable, - }; - const addr_reg = try self.register_manager.allocReg(null); - _ = try self.addInst(.{ - .tag = .lea_pie, - .ops = (Mir.Ops{ - .reg1 = addr_reg.to64(), - .flags = flags, - }).encode(), - .data = .{ - .load_reloc = .{ - .atom_index = self.mod_fn.owner_decl.link.macho.local_sym_index, - .sym_index = sym_index, - }, - }, - }); - break :blk addr_reg; - }, - .memory => |addr| { - const addr_reg = try self.copyToTmpRegister(Type.usize, .{ .immediate = addr }); - break :blk addr_reg; - }, - else => unreachable, - } - }; - - self.register_manager.freezeRegs(&.{addr_reg}); - defer self.register_manager.unfreezeRegs(&.{addr_reg}); - - const regs = try self.register_manager.allocRegs(2, .{ null, null }); - const count_reg = regs[0]; - const tmp_reg = regs[1]; - - try self.register_manager.getReg(.rax, null); - try self.register_manager.getReg(.rcx, null); - - // TODO allow for abi_size to be u64 - try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); - try self.genInlineMemcpy( - -(stack_offset + @intCast(i32, abi_size)), - .rsp, - addr_reg.to64(), - count_reg.to64(), - tmp_reg.to8(), - ); + try self.genInlineMemcpy(stack_offset, .rsp, ty, mcv); }, .register => |reg| { _ = try self.addInst(.{ @@ -3605,48 +3583,20 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .reg2 = registerAlias(reg, @intCast(u32, abi_size)), .flags = 0b10, }).encode(), - .data = .{ .imm = @bitCast(u32, -(stack_offset + @intCast(i32, abi_size))) }, + .data = .{ .imm = @bitCast(u32, -stack_offset) }, }); }, .ptr_stack_offset => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, - .stack_offset => |unadjusted_off| { + .stack_offset => { if (abi_size <= 8) { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); } - self.register_manager.freezeRegs(&.{ .rax, .rcx }); - defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx }); - - const regs = try self.register_manager.allocRegs(3, .{ null, null, null }); - const addr_reg = regs[0]; - const count_reg = regs[1]; - const tmp_reg = regs[2]; - - try self.register_manager.getReg(.rax, null); - try self.register_manager.getReg(.rcx, null); - - _ = try self.addInst(.{ - .tag = .lea, - .ops = (Mir.Ops{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }).encode(), - .data = .{ .imm = @bitCast(u32, -(unadjusted_off + @intCast(i32, abi_size))) }, - }); - - // TODO allow for abi_size to be u64 - try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); - try self.genInlineMemcpy( - -(stack_offset + @intCast(i32, abi_size)), - .rsp, - addr_reg.to64(), - count_reg.to64(), - tmp_reg.to8(), - ); + try self.genInlineMemcpy(stack_offset, .rsp, ty, mcv); }, else => return self.fail("TODO implement args on stack for {}", .{mcv}), } @@ -3677,17 +3627,13 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro return self.genSetStack(ty, stack_offset, .{ .register = reg }); }, .immediate => |x_big| { - const adj_off = stack_offset + @intCast(i32, abi_size); - if (adj_off > 128) { + if (stack_offset > 128) { return self.fail("TODO implement set stack variable with large stack offset", .{}); } switch (abi_size) { 1, 2, 4 => { - // We have a positive stack offset value but we want a twos complement negative - // offset from rbp, which is at the top of the stack frame. - // mov [rbp+offset], immediate const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -adj_off), + .dest_off = @bitCast(u32, -stack_offset), .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -3705,15 +3651,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro }); }, 8 => { - // We have a positive stack offset value but we want a twos complement negative - // offset from rbp, which is at the top of the stack frame. - const negative_offset = -adj_off; - // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, negative_offset + 4), + .dest_off = @bitCast(u32, -stack_offset + 4), .operand = @truncate(u32, x_big >> 32), }); _ = try self.addInst(.{ @@ -3727,7 +3669,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro } { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, negative_offset), + .dest_off = @bitCast(u32, -stack_offset), .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -3749,16 +3691,54 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro if (stack_offset > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - const adj_off = stack_offset + @intCast(i32, abi_size); - _ = try self.addInst(.{ - .tag = .mov, - .ops = (Mir.Ops{ - .reg1 = .rbp, - .reg2 = registerAlias(reg, @intCast(u32, abi_size)), - .flags = 0b10, - }).encode(), - .data = .{ .imm = @bitCast(u32, -adj_off) }, - }); + + const is_power_of_two = (abi_size % 2) == 0; + if (!is_power_of_two) { + self.register_manager.freezeRegs(&.{reg}); + defer self.register_manager.unfreezeRegs(&.{reg}); + + const tmp_reg = try self.copyToTmpRegister(ty, mcv); + + var next_offset = stack_offset; + var remainder = abi_size; + while (remainder > 0) { + const closest_power_of_two = @as(u6, 1) << @intCast(u3, math.log2(remainder)); + + _ = try self.addInst(.{ + .tag = .mov, + .ops = (Mir.Ops{ + .reg1 = .rbp, + .reg2 = registerAlias(tmp_reg, closest_power_of_two), + .flags = 0b10, + }).encode(), + .data = .{ .imm = @bitCast(u32, -next_offset) }, + }); + + if (closest_power_of_two > 1) { + _ = try self.addInst(.{ + .tag = .shr, + .ops = (Mir.Ops{ + .reg1 = tmp_reg, + .flags = 0b10, + }).encode(), + .data = .{ .imm = closest_power_of_two * 8 }, + }); + } + + remainder -= closest_power_of_two; + next_offset -= closest_power_of_two; + } + } else { + _ = try self.addInst(.{ + .tag = .mov, + .ops = (Mir.Ops{ + .reg1 = .rbp, + .reg2 = registerAlias(reg, @intCast(u32, abi_size)), + .flags = 0b10, + }).encode(), + .data = .{ .imm = @bitCast(u32, -stack_offset) }, + }); + } }, .memory, .embedded_in_code, @@ -3770,65 +3750,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); } - try self.register_manager.getReg(.rax, null); - try self.register_manager.getReg(.rcx, null); - - self.register_manager.freezeRegs(&.{ .rax, .rcx, .rbp }); - defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx, .rbp }); - - const addr_reg: Register = blk: { - switch (mcv) { - .memory => |addr| { - const reg = try self.copyToTmpRegister(Type.usize, .{ .immediate = addr }); - break :blk reg; - }, - .direct_load, - .got_load, - => |sym_index| { - const flags: u2 = switch (mcv) { - .got_load => 0b00, - .direct_load => 0b01, - else => unreachable, - }; - const addr_reg = try self.register_manager.allocReg(null); - _ = try self.addInst(.{ - .tag = .lea_pie, - .ops = (Mir.Ops{ - .reg1 = addr_reg.to64(), - .flags = flags, - }).encode(), - .data = .{ - .load_reloc = .{ - .atom_index = self.mod_fn.owner_decl.link.macho.local_sym_index, - .sym_index = sym_index, - }, - }, - }); - break :blk addr_reg; - }, - else => { - return self.fail("TODO implement memcpy for setting stack from {}", .{mcv}); - }, - } - }; - - self.register_manager.freezeRegs(&.{addr_reg}); - defer self.register_manager.unfreezeRegs(&.{addr_reg}); - - const regs = try self.register_manager.allocRegs(2, .{ null, null }); - const count_reg = regs[0]; - const tmp_reg = regs[1]; - - // TODO allow for abi_size to be u64 - try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); - - return self.genInlineMemcpy( - -(stack_offset + @intCast(i32, abi_size)), - .rbp, - addr_reg.to64(), - count_reg.to64(), - tmp_reg.to8(), - ); + try self.genInlineMemcpy(stack_offset, .rbp, ty, mcv); }, .ptr_stack_offset => { const reg = try self.copyToTmpRegister(ty, mcv); @@ -3845,48 +3767,89 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerErro return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); } - self.register_manager.freezeRegs(&.{ .rax, .rcx, .rbp }); - defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx, .rbp }); - - const regs = try self.register_manager.allocRegs(3, .{ null, null, null }); - const addr_reg = regs[0]; - const count_reg = regs[1]; - const tmp_reg = regs[2]; - - try self.register_manager.getReg(.rax, null); - try self.register_manager.getReg(.rcx, null); - - _ = try self.addInst(.{ - .tag = .lea, - .ops = (Mir.Ops{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }).encode(), - .data = .{ .imm = @bitCast(u32, -(off + @intCast(i32, abi_size))) }, - }); - - // TODO allow for abi_size to be u64 - try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); - - return self.genInlineMemcpy( - -(stack_offset + @intCast(i32, abi_size)), - .rbp, - addr_reg.to64(), - count_reg.to64(), - tmp_reg.to8(), - ); + try self.genInlineMemcpy(stack_offset, .rbp, ty, mcv); }, } } -fn genInlineMemcpy( - self: *Self, - stack_offset: i32, - stack_reg: Register, - addr_reg: Register, - count_reg: Register, - tmp_reg: Register, -) InnerError!void { +fn genInlineMemcpy(self: *Self, stack_offset: i32, stack_reg: Register, ty: Type, val: MCValue) InnerError!void { + const abi_size = ty.abiSize(self.target.*); + + try self.register_manager.getReg(.rax, null); + try self.register_manager.getReg(.rcx, null); + + self.register_manager.freezeRegs(&.{ .rax, .rcx, .rbp }); + defer self.register_manager.unfreezeRegs(&.{ .rax, .rcx, .rbp }); + + const addr_reg: Register = blk: { + switch (val) { + .memory => |addr| { + const reg = try self.copyToTmpRegister(Type.usize, .{ .immediate = addr }); + break :blk reg; + }, + .direct_load, + .got_load, + => |sym_index| { + const flags: u2 = switch (val) { + .got_load => 0b00, + .direct_load => 0b01, + else => unreachable, + }; + const addr_reg = (try self.register_manager.allocReg(null)).to64(); + _ = try self.addInst(.{ + .tag = .lea_pie, + .ops = (Mir.Ops{ + .reg1 = addr_reg, + .flags = flags, + }).encode(), + .data = .{ + .load_reloc = .{ + .atom_index = self.mod_fn.owner_decl.link.macho.local_sym_index, + .sym_index = sym_index, + }, + }, + }); + break :blk addr_reg; + }, + .stack_offset => |off| { + const addr_reg = (try self.register_manager.allocReg(null)).to64(); + _ = try self.addInst(.{ + .tag = .lea, + .ops = (Mir.Ops{ + .reg1 = addr_reg, + .reg2 = .rbp, + }).encode(), + .data = .{ .imm = @bitCast(u32, -off) }, + }); + break :blk addr_reg; + }, + .register => |reg| { + const addr_reg = try self.register_manager.allocReg(null); + _ = try self.addInst(.{ + .tag = .mov, + .ops = (Mir.Ops{ + .reg1 = registerAlias(addr_reg, @divExact(reg.size(), 8)), + .reg2 = reg, + }).encode(), + .data = undefined, + }); + break :blk addr_reg.to64(); + }, + else => { + return self.fail("TODO implement memcpy for setting stack from {}", .{val}); + }, + } + }; + + self.register_manager.freezeRegs(&.{addr_reg}); + defer self.register_manager.unfreezeRegs(&.{addr_reg}); + + const regs = try self.register_manager.allocRegs(2, .{ null, null }); + const count_reg = regs[0].to64(); + const tmp_reg = regs[1].to8(); + + try self.genSetReg(Type.u32, count_reg, .{ .immediate = @intCast(u32, abi_size) }); + // mov rcx, 0 _ = try self.addInst(.{ .tag = .mov, @@ -3939,7 +3902,7 @@ fn genInlineMemcpy( .reg1 = stack_reg, .reg2 = tmp_reg.to8(), }).encode(), - .data = .{ .imm = @bitCast(u32, stack_offset) }, + .data = .{ .imm = @bitCast(u32, -stack_offset) }, }); // add rcx, 1 @@ -3983,11 +3946,10 @@ fn genInlineMemcpy( fn genInlineMemset(self: *Self, ty: Type, stack_offset: i32, value: MCValue) InnerError!void { try self.register_manager.getReg(.rax, null); const abi_size = ty.abiSize(self.target.*); - const adj_off = stack_offset + @intCast(i32, abi_size); - if (adj_off > 128) { + if (stack_offset > 128) { return self.fail("TODO inline memset with large stack offset", .{}); } - const negative_offset = @bitCast(u32, -adj_off); + const negative_offset = @bitCast(u32, -stack_offset); // We are actually counting `abi_size` bytes; however, we reuse the index register // as both the counter and offset scaler, hence we need to subtract one from `abi_size` @@ -4075,10 +4037,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void const abi_size = ty.abiSize(self.target.*); switch (mcv) { .dead => unreachable, - .ptr_stack_offset => |unadjusted_off| { - const elem_ty = ty.childType(); - const elem_abi_size = elem_ty.abiSize(self.target.*); - const off = unadjusted_off + @intCast(i32, elem_abi_size); + .ptr_stack_offset => |off| { if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } @@ -4301,8 +4260,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } } }, - .stack_offset => |unadjusted_off| { - const off = unadjusted_off + @intCast(i32, abi_size); + .stack_offset => |off| { if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } @@ -4379,8 +4337,8 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void { const array_len = array_ty.arrayLenIncludingSentinel(); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: { const stack_offset = @intCast(i32, try self.allocMem(inst, 16, 16)); - try self.genSetStack(ptr_ty, stack_offset + 8, ptr); - try self.genSetStack(Type.initTag(.u64), stack_offset, .{ .immediate = array_len }); + try self.genSetStack(ptr_ty, stack_offset, ptr); + try self.genSetStack(Type.initTag(.u64), stack_offset - 8, .{ .immediate = array_len }); break :blk .{ .stack_offset = stack_offset }; }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); @@ -4620,6 +4578,9 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { } switch (typed_value.ty.zigTypeTag()) { + .Array => { + return self.lowerUnnamedConst(typed_value); + }, .Pointer => switch (typed_value.ty.ptrSize()) { .Slice => { return self.lowerUnnamedConst(typed_value); @@ -4790,7 +4751,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { var next_stack_offset: u32 = 0; var count: usize = param_types.len; while (count > 0) : (count -= 1) { - // for (param_types) |ty, i| { const i = count - 1; const ty = param_types[i]; if (!ty.hasRuntimeBits()) { @@ -4799,6 +4759,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { continue; } const param_size = @intCast(u32, ty.abiSize(self.target.*)); + const param_align = @intCast(u32, ty.abiAlignment(self.target.*)); if (by_reg.get(i)) |int_reg| { const aliased_reg = registerAlias(c_abi_int_param_regs[int_reg], param_size); result.args[i] = .{ .register = aliased_reg }; @@ -4809,8 +4770,9 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { // such as ptr and len of slices as separate registers. // TODO: also we need to honor the C ABI for relevant types rather than passing on // the stack here. - result.args[i] = .{ .stack_offset = @intCast(i32, next_stack_offset) }; - next_stack_offset += param_size; + const offset = mem.alignForwardGeneric(u32, next_stack_offset + param_size, param_align); + result.args[i] = .{ .stack_offset = @intCast(i32, offset) }; + next_stack_offset = offset; } } @@ -4883,8 +4845,8 @@ fn parseRegName(name: []const u8) ?Register { return std.meta.stringToEnum(Register, name); } +/// Returns register wide enough to hold at least `size_bytes`. fn registerAlias(reg: Register, size_bytes: u32) Register { - // For x86_64 we have to pick a smaller register alias depending on abi size. if (size_bytes == 0) { unreachable; // should be comptime known } else if (size_bytes <= 1) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 128ea52847..32877cb6f8 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -931,16 +931,15 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data; const mcv = emit.mir.function.args[arg_dbg_info.arg_index]; - try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv, arg_dbg_info.arg_index); + try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv, arg_dbg_info.max_stack); } -fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32) !void { +fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32) !void { const ty_str = emit.mir.function.air.instructions.items(.data)[inst].ty_str; const zir = &emit.mir.function.mod_fn.owner_decl.getFileScope().zir; const name = zir.nullTerminatedString(ty_str.str); const name_with_null = name.ptr[0 .. name.len + 1]; const ty = emit.mir.function.air.getRefType(ty_str.ty); - const abi_size = ty.abiSize(emit.bin_file.options.target); switch (mcv) { .register => |reg| { @@ -960,7 +959,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32 .none => {}, } }, - .stack_offset => { + .stack_offset => |off| { switch (emit.debug_output) { .dwarf => |dbg_out| { // we add here +16 like we do in airArg in CodeGen since we refer directly to @@ -968,7 +967,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32 // prologue, and 8 bytes for return address. // TODO we need to make this more generic if we don't use rbp as the frame pointer // for example when -fomit-frame-pointer is set. - const disp = @intCast(i32, arg_index * abi_size + 16); + const disp = @intCast(i32, max_stack) - off + 16; try dbg_out.dbg_info.ensureUnusedCapacity(8); dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); const fixup = dbg_out.dbg_info.items.len; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 046cb0e9f6..83922d03a2 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -413,6 +413,7 @@ pub const DbgLineColumn = struct { pub const ArgDbgInfo = struct { air_inst: Air.Inst.Index, arg_index: u32, + max_stack: u32, }; pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { diff --git a/test/behavior/align.zig b/test/behavior/align.zig index a8d8fcd206..22cbce8261 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -273,7 +273,6 @@ fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) u8 { test "runtime known array index has best alignment possible" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // take full advantage of over-alignment diff --git a/test/behavior/array.zig b/test/behavior/array.zig index e93f0f3e90..7828963a1c 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -8,6 +8,7 @@ const expectEqual = testing.expectEqual; test "array to slice" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const a: u32 align(4) = 3; const b: u32 align(8) = 4; diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 70b17a55a1..75d470b21f 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -199,7 +199,7 @@ fn MakeType(comptime T: type) type { test "implicit cast from *[N]T to [*c]T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; var x: [4]u16 = [4]u16{ 0, 1, 2, 3 }; var y: [*c]u16 = &x; @@ -274,7 +274,7 @@ test "*const ?[*]const T to [*c]const [*c]const T" { test "array coersion to undefined at runtime" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; @setRuntimeSafety(true); @@ -339,7 +339,6 @@ test "peer type unsigned int to signed" { test "expected [*c]const u8, found [*:0]const u8" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; var a: [*:0]const u8 = "hello"; var b: [*c]const u8 = a; diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 4b73a3a140..e64e82d474 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -29,7 +29,6 @@ comptime { test "slicing" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO var array: [20]i32 = undefined; @@ -223,7 +222,6 @@ test "compile time slice of pointer to hard coded address" { test "slice string literal has correct type" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; comptime { try expect(@TypeOf("aoeu"[0..]) == *const [4:0]u8); @@ -365,7 +363,6 @@ test "empty array to slice" { test "@ptrCast slice to pointer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const S = struct { fn doTheTest() !void {