diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 47d1e1f4d3..45d9767320 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2098,17 +2098,72 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; - _ = bin_op; - return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch}); + const ptr_ty = self.air.typeOf(bin_op.lhs); + const union_ty = ptr_ty.childType(); + const tag_ty = self.air.typeOf(bin_op.rhs); + const layout = union_ty.unionGetLayout(self.target.*); + + if (layout.tag_size == 0) { + return self.finishAir(inst, .none, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + const ptr = try self.resolveInst(bin_op.lhs); + ptr.freezeIfRegister(&self.register_manager); + defer ptr.unfreezeIfRegister(&self.register_manager); + + const tag = try self.resolveInst(bin_op.rhs); + tag.freezeIfRegister(&self.register_manager); + defer tag.unfreezeIfRegister(&self.register_manager); + + const adjusted_ptr: MCValue = if (layout.payload_size > 0 and layout.tag_align < layout.payload_align) blk: { + // TODO reusing the operand + const reg = try self.copyToTmpRegister(ptr_ty, ptr); + try self.genBinMathOpMir(.add, ptr_ty, .{ .register = reg }, .{ .immediate = layout.payload_size }); + break :blk MCValue{ .register = reg }; + } else ptr; + + try self.store(adjusted_ptr, tag, ptr_ty, tag_ty); + + return self.finishAir(inst, .none, .{ bin_op.lhs, bin_op.rhs, .none }); } fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) - .dead - else - return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch}); - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + if (self.liveness.isUnused(inst)) { + return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); + } + + const tag_ty = self.air.typeOfIndex(inst); + const union_ty = self.air.typeOf(ty_op.operand); + const layout = union_ty.unionGetLayout(self.target.*); + + if (layout.tag_size == 0) { + return self.finishAir(inst, .none, .{ ty_op.operand, .none, .none }); + } + + // TODO reusing the operand + const operand = try self.resolveInst(ty_op.operand); + operand.freezeIfRegister(&self.register_manager); + defer operand.unfreezeIfRegister(&self.register_manager); + + const tag_abi_size = tag_ty.abiSize(self.target.*); + const offset: i32 = if (layout.tag_align < layout.payload_align) @intCast(i32, layout.payload_size) else 0; + const dst_mcv: MCValue = blk: { + switch (operand) { + .stack_offset => |off| { + if (tag_abi_size <= 8) { + break :blk try self.copyToRegisterWithInstTracking(inst, tag_ty, .{ + .stack_offset = off - offset, + }); + } + + return self.fail("TODO implement get_union_tag for ABI larger than 8 bytes and operand {}", .{operand}); + }, + else => return self.fail("TODO implement get_union_tag for {}", .{operand}), + } + }; + + return self.finishAir(inst, dst_mcv, .{ ty_op.operand, .none, .none }); } fn airClz(self: *Self, inst: Air.Inst.Index) !void { @@ -2429,8 +2484,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type }); }, .stack_offset => { - const tmp_reg = try self.copyToTmpRegister(value_ty, value); - return self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); + if (abi_size <= 8) { + const tmp_reg = try self.copyToTmpRegister(value_ty, value); + return self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); + } + + try self.genInlineMemcpy(0, value_ty, value, .{ + .source_stack_base = .rbp, + .dest_stack_base = reg.to64(), + }); }, else => |other| { return self.fail("TODO implement set pointee with {}", .{other}); @@ -3905,36 +3967,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u .dead, .unreach => unreachable, .immediate => |imm| { _ = try self.addInst(.{ - .tag = .@"test", + .tag = .xor, .ops = (Mir.Ops{ .reg1 = registerAlias(cond_reg, abi_size), }).encode(), .data = .{ .imm = @intCast(u32, imm) }, }); - return self.addInst(.{ - .tag = .cond_jmp_eq_ne, - .ops = (Mir.Ops{ - .flags = 0b00, - }).encode(), - .data = .{ .inst = undefined }, - }); }, .register => |reg| { _ = try self.addInst(.{ - .tag = .@"test", + .tag = .xor, .ops = (Mir.Ops{ .reg1 = registerAlias(cond_reg, abi_size), .reg2 = registerAlias(reg, abi_size), }).encode(), .data = undefined, }); - return self.addInst(.{ - .tag = .cond_jmp_eq_ne, - .ops = (Mir.Ops{ - .flags = 0b00, - }).encode(), - .data = .{ .inst = undefined }, - }); }, .stack_offset => { if (abi_size <= 8) { @@ -3948,6 +3996,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u return self.fail("TODO implement switch mir when case is {}", .{case}); }, } + + _ = try self.addInst(.{ + .tag = .@"test", + .ops = (Mir.Ops{ + .reg1 = registerAlias(cond_reg, abi_size), + .reg2 = registerAlias(cond_reg, abi_size), + }).encode(), + .data = undefined, + }); + return self.addInst(.{ + .tag = .cond_jmp_eq_ne, + .ops = (Mir.Ops{ + .flags = 0b00, + }).encode(), + .data = .{ .inst = undefined }, + }); }, .stack_offset => { try self.spillCompareFlagsIfOccupied(); @@ -5408,24 +5472,14 @@ 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); - }, + .Slice => {}, else => { switch (typed_value.val.tag()) { .int_u64 => { return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; }, - .slice => { - return self.lowerUnnamedConst(typed_value); - }, - else => { - return self.fail("TODO codegen more kinds of const pointers: {}", .{typed_value.val.tag()}); - }, + else => {}, } }, }, @@ -5434,10 +5488,9 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { if (info.bits <= ptr_bits and info.signedness == .signed) { return MCValue{ .immediate = @bitCast(u64, typed_value.val.toSignedInt()) }; } - if (info.bits > ptr_bits or info.signedness == .signed) { - return self.fail("TODO const int bigger than ptr and signed int", .{}); + if (!(info.bits > ptr_bits or info.signedness == .signed)) { + return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; } - return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; }, .Bool => { return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; @@ -5457,7 +5510,6 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { } else if (typed_value.ty.abiSize(self.target.*) == 1) { return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; } - return self.fail("TODO non pointer optionals", .{}); }, .Enum => { if (typed_value.val.castTag(.enum_field_index)) |field_index| { @@ -5504,13 +5556,11 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val }); } } - return self.lowerUnnamedConst(typed_value); }, - .Struct => { - return self.lowerUnnamedConst(typed_value); - }, - else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}), + else => {}, } + + return self.lowerUnnamedConst(typed_value); } const CallMCValues = struct { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index fc43e61c17..f4d365dc4f 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1859,6 +1859,9 @@ fn lowerToRmEnc( switch (reg_or_mem) { .register => |src_reg| { const encoder = try Encoder.init(code, 4); + if (reg.size() == 16) { + encoder.prefix16BitMode(); + } encoder.rex(.{ .w = setRexWRegister(reg) or setRexWRegister(src_reg), .r = reg.isExtended(), @@ -1902,6 +1905,9 @@ fn lowerToMrEnc( switch (reg_or_mem) { .register => |dst_reg| { const encoder = try Encoder.init(code, 3); + if (dst_reg.size() == 16) { + encoder.prefix16BitMode(); + } encoder.rex(.{ .w = setRexWRegister(dst_reg) or setRexWRegister(reg), .r = reg.isExtended(), diff --git a/src/codegen.zig b/src/codegen.zig index 26a4478fb2..2484cb0e59 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -466,10 +466,74 @@ pub fn generateSymbol( return Result{ .appended = {} }; }, .Union => { - // TODO generateSymbol for unions + // TODO generate debug info for unions const target = bin_file.options.target; - const abi_size = try math.cast(usize, typed_value.ty.abiSize(target)); - try code.writer().writeByteNTimes(0xaa, abi_size); + const union_obj = typed_value.val.castTag(.@"union").?.data; + const layout = typed_value.ty.unionGetLayout(target); + + if (layout.payload_size == 0) { + switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{ + .ty = typed_value.ty.unionTagType().?, + .val = union_obj.tag, + }, code, debug_output)) { + .appended => {}, + .externally_managed => |external_slice| { + code.appendSliceAssumeCapacity(external_slice); + }, + .fail => |em| return Result{ .fail = em }, + } + } + + // Check if we should store the tag first. + if (layout.tag_align >= layout.payload_align) { + switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{ + .ty = typed_value.ty.unionTagType().?, + .val = union_obj.tag, + }, code, debug_output)) { + .appended => {}, + .externally_managed => |external_slice| { + code.appendSliceAssumeCapacity(external_slice); + }, + .fail => |em| return Result{ .fail = em }, + } + } + + const union_ty = typed_value.ty.cast(Type.Payload.Union).?.data; + const field_index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag).?; + assert(union_ty.haveFieldTypes()); + const field_ty = union_ty.fields.values()[field_index].ty; + if (!field_ty.hasRuntimeBits()) { + try code.writer().writeByteNTimes(0xaa, try math.cast(usize, layout.payload_size)); + } else { + switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{ + .ty = field_ty, + .val = union_obj.val, + }, code, debug_output)) { + .appended => {}, + .externally_managed => |external_slice| { + code.appendSliceAssumeCapacity(external_slice); + }, + .fail => |em| return Result{ .fail = em }, + } + + const padding = try math.cast(usize, layout.payload_size - field_ty.abiSize(target)); + if (padding > 0) { + try code.writer().writeByteNTimes(0, padding); + } + } + + if (layout.tag_size > 0) { + switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{ + .ty = union_ty.tag_ty, + .val = union_obj.tag, + }, code, debug_output)) { + .appended => {}, + .externally_managed => |external_slice| { + code.appendSliceAssumeCapacity(external_slice); + }, + .fail => |em| return Result{ .fail = em }, + } + } return Result{ .appended = {} }; }, diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 6044bf983c..391de2e901 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -4,68 +4,100 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const Tag = std.meta.Tag; -const Foo = union { +const FooWithFloats = union { float: f64, int: i32, }; -test "basic unions" { +test "basic unions with floats" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + var foo = FooWithFloats{ .int = 1 }; + try expect(foo.int == 1); + foo = FooWithFloats{ .float = 12.34 }; + try expect(foo.float == 12.34); +} + +fn setFloat(foo: *FooWithFloats, x: f64) void { + foo.* = FooWithFloats{ .float = x }; +} + +test "init union with runtime value - floats" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + + var foo: FooWithFloats = undefined; + + setFloat(&foo, 12.34); + try expect(foo.float == 12.34); +} + +test "basic unions" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + var foo = Foo{ .int = 1 }; try expect(foo.int == 1); - foo = Foo{ .float = 12.34 }; - try expect(foo.float == 12.34); + foo = Foo{ .str = .{ .slice = "Hello!" } }; + try expect(std.mem.eql(u8, foo.str.slice, "Hello!")); } +const Foo = union { + int: i32, + str: struct { + slice: []const u8, + }, +}; + test "init union with runtime value" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; var foo: Foo = undefined; - setFloat(&foo, 12.34); - try expect(foo.float == 12.34); - setInt(&foo, 42); try expect(foo.int == 42); -} -fn setFloat(foo: *Foo, x: f64) void { - foo.* = Foo{ .float = x }; + setStr(&foo, "Hello!"); + try expect(std.mem.eql(u8, foo.str.slice, "Hello!")); } fn setInt(foo: *Foo, x: i32) void { foo.* = Foo{ .int = x }; } +fn setStr(foo: *Foo, slice: []const u8) void { + foo.* = Foo{ .str = .{ .slice = slice } }; +} + test "comptime union field access" { comptime { - var foo = Foo{ .int = 0 }; + var foo = FooWithFloats{ .int = 0 }; try expect(foo.int == 0); - foo = Foo{ .float = 42.42 }; - try expect(foo.float == 42.42); + foo = FooWithFloats{ .float = 12.34 }; + try expect(foo.float == 12.34); } } const FooExtern = extern union { - float: f64, int: i32, + str: struct { + slice: []const u8, + }, }; test "basic extern unions" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; var foo = FooExtern{ .int = 1 }; try expect(foo.int == 1); - foo.float = 12.34; - try expect(foo.float == 12.34); + foo.str.slice = "Well"; + try expect(std.mem.eql(u8, foo.str.slice, "Well")); } const ExternPtrOrInt = extern union { @@ -129,7 +161,6 @@ test "access a member of tagged union with conflicting enum tag name" { } test "constant tagged union with payload" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -227,7 +258,6 @@ fn testComparison() !void { } test "comparison between union and enum literal" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -297,7 +327,6 @@ pub const PackThis = union(enum) { }; test "constant packed union" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -457,7 +486,6 @@ test "update the tag value for zero-sized unions" { } test "union initializer generates padding only if needed" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -470,7 +498,6 @@ test "union initializer generates padding only if needed" { } test "runtime tag name with single field" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -483,7 +510,6 @@ test "runtime tag name with single field" { } test "method call on an empty union" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -547,7 +573,6 @@ test "tagged union type" { } test "tagged union as return value" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -628,7 +653,6 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void { } test "switch on union with only 1 field" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -658,7 +682,6 @@ const PartialInstWithPayload = union(enum) { }; test "union with only 1 field casted to its enum type which has enum value specified" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -796,7 +819,6 @@ test "@unionInit stored to a const" { } test "@unionInit can modify a union type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -839,7 +861,6 @@ test "@unionInit can modify a pointer value" { } test "union no tag with struct member" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO