From 2fddd767ba20374e7677003c101e60f470c3804c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 20 Sep 2023 23:53:06 -0400 Subject: [PATCH 1/4] sema: add support for unions in readFromMemory and writeToMemory --- src/Module.zig | 28 ++++++++++++ src/Sema.zig | 16 +++++-- src/arch/wasm/CodeGen.zig | 5 ++- src/codegen.zig | 6 ++- src/codegen/c.zig | 5 ++- src/codegen/llvm.zig | 5 ++- src/codegen/spirv.zig | 5 ++- src/type.zig | 8 +++- src/value.zig | 73 +++++++++++++++++++++++++++++-- test/behavior/comptime_memory.zig | 23 ++++++++++ 10 files changed, 160 insertions(+), 14 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 17a97e6c6d..09e136cde8 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { const ip = &mod.intern_pool; + if (enum_tag.toIntern() == .undef) return null; assert(ip.typeOf(enum_tag.toIntern()) == u.enum_tag_ty); const enum_type = ip.indexToKey(u.enum_tag_ty).enum_type; return enum_type.tagValueIndex(ip, enum_tag.toIntern()); @@ -6672,3 +6673,30 @@ pub fn structPackedFieldBitOffset( } unreachable; // index out of bounds } + +pub fn unionLargestField(mod: *Module, u: InternPool.UnionType) struct { + ty: Type, + index: u32, + size: u64, +} { + const fields = u.field_types.get(&mod.intern_pool); + assert(fields.len != 0); + var largest_field_ty: Type = undefined; + var largest_field_size: u64 = 0; + var largest_field_index: u32 = 0; + for (fields, 0..) |union_field, i| { + const field_ty = union_field.toType(); + const size: u32 = @intCast(field_ty.abiSize(mod)); + if (size > largest_field_size) { + largest_field_ty = field_ty; + largest_field_size = size; + largest_field_index = @intCast(i); + } + } + + return .{ + .ty = largest_field_ty, + .index = largest_field_index, + .size = largest_field_size, + }; +} diff --git a/src/Sema.zig b/src/Sema.zig index a14ee6c63b..25aae1a1e7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -29740,10 +29740,15 @@ fn storePtrVal( error.OutOfMemory => return error.OutOfMemory, error.ReinterpretDeclRef => unreachable, error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), + error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), }; - reinterpret.val_ptr.* = (try (try Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena)).intern(mut_kit.ty, mod)).toValue(); + const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.IllDefinedMemoryLayout => unreachable, + error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), + }; + reinterpret.val_ptr.* = (try val.intern(mut_kit.ty, mod)).toValue(); }, .bad_decl_ty, .bad_ptr_ty => { // TODO show the decl declaration site in a note and explain whether the decl @@ -30655,7 +30660,12 @@ fn bitCastVal( error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), }; - return try Value.readFromMemory(new_ty, mod, buffer[buffer_offset..], sema.arena); + + return Value.readFromMemory(new_ty, mod, buffer[buffer_offset..], sema.arena) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.IllDefinedMemoryLayout => unreachable, + error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{new_ty.fmt(mod)}), + }; } fn coerceArrayPtrToSlice( diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 76a27ec718..f0b020d305 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3259,7 +3259,10 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { .un => |un| { // in this case we have a packed union which will not be passed by reference. const union_obj = mod.typeToUnion(ty).?; - const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; + const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { + assert(union_obj.getLayout(ip) == .Extern); + break :f mod.unionLargestField(union_obj).index; + }; const field_ty = union_obj.field_types.get(ip)[field_index].toType(); return func.lowerConstant(un.val.toValue(), field_ty); }, diff --git a/src/codegen.zig b/src/codegen.zig index 992b51c635..f34c97e6e5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -583,7 +583,11 @@ pub fn generateSymbol( } const union_obj = mod.typeToUnion(typed_value.ty).?; - const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod).?; + const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod) orelse f: { + assert(union_obj.getLayout(ip) == .Extern); + break :f mod.unionLargestField(union_obj).index; + }; + const field_ty = union_obj.field_types.get(ip)[field_index].toType(); if (!field_ty.hasRuntimeBits(mod)) { try code.appendNTimes(0xaa, math.cast(usize, layout.payload_size) orelse return error.Overflow); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 54e13db9a1..0b634e5038 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1439,7 +1439,10 @@ pub const DeclGen = struct { } const union_obj = mod.typeToUnion(ty).?; - const field_i = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; + const field_i = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { + assert(union_obj.getLayout(ip) == .Extern); + break :f mod.unionLargestField(union_obj).index; + }; const field_ty = union_obj.field_types.get(ip)[field_i].toType(); const field_name = union_obj.field_names.get(ip)[field_i]; if (union_obj.getLayout(ip) == .Packed) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index dc2e2f3859..ad26725d98 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4108,7 +4108,10 @@ pub const Object = struct { if (layout.payload_size == 0) return o.lowerValue(un.tag); const union_obj = mod.typeToUnion(ty).?; - const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; + const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { + assert(union_obj.getLayout(ip) == .Extern); + break :f mod.unionLargestField(union_obj).index; + }; const field_ty = union_obj.field_types.get(ip)[field_index].toType(); if (union_obj.getLayout(ip) == .Packed) { diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 33a864ea0a..8fb6153938 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -838,7 +838,10 @@ pub const DeclGen = struct { return dg.todo("packed union constants", .{}); } - const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module).?; + const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module) orelse f: { + assert(union_obj.getLayout(ip) == .Extern); + break :f mod.unionLargestField(union_obj).index; + }; const active_field_ty = union_obj.field_types.get(ip)[active_field].toType(); const has_tag = layout.tag_size != 0; diff --git a/src/type.zig b/src/type.zig index 0ed8c394fc..b440a23b9d 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1929,8 +1929,12 @@ pub const Type = struct { pub fn unionFieldType(ty: Type, enum_tag: Value, mod: *Module) Type { const ip = &mod.intern_pool; const union_obj = mod.typeToUnion(ty).?; - const index = mod.unionTagFieldIndex(union_obj, enum_tag).?; - return union_obj.field_types.get(ip)[index].toType(); + const union_fields = union_obj.field_types.get(ip); + if (mod.unionTagFieldIndex(union_obj, enum_tag)) |index| { + return union_fields[index].toType(); + } else { + return mod.unionLargestField(union_obj).ty; + } } pub fn unionTagFieldIndex(ty: Type, enum_tag: Value, mod: *Module) ?u32 { diff --git a/src/value.zig b/src/value.zig index 2d0523f986..cfac880110 100644 --- a/src/value.zig +++ b/src/value.zig @@ -704,7 +704,22 @@ pub const Value = struct { }, .Union => switch (ty.containerLayout(mod)) { .Auto => return error.IllDefinedMemoryLayout, - .Extern => return error.Unimplemented, + .Extern => { + const union_obj = mod.typeToUnion(ty).?; + const union_tag = val.unionTag(mod); + + const field_type, const field_index = if (mod.unionTagFieldIndex(union_obj, union_tag)) |field_index| .{ + union_obj.field_types.get(&mod.intern_pool)[field_index].toType(), + field_index, + } else f: { + const largest_field = mod.unionLargestField(union_obj); + break :f .{ largest_field.ty, largest_field.index }; + }; + + const field_val = try val.fieldValue(mod, field_index); + const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); + return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); + }, .Packed => { const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0); @@ -856,7 +871,11 @@ pub const Value = struct { mod: *Module, buffer: []const u8, arena: Allocator, - ) Allocator.Error!Value { + ) error{ + IllDefinedMemoryLayout, + Unimplemented, + OutOfMemory, + }!Value { const ip = &mod.intern_pool; const target = mod.getTarget(); const endian = target.cpu.arch.endian(); @@ -966,6 +985,26 @@ pub const Value = struct { .name = name, } })).toValue(); }, + .Union => switch (ty.containerLayout(mod)) { + .Auto => return error.IllDefinedMemoryLayout, + .Extern => { + const union_obj = mod.typeToUnion(ty).?; + const largest_field = mod.unionLargestField(union_obj); + const field_size: usize = @intCast(largest_field.size); + const val = try (try readFromMemory(largest_field.ty, mod, buffer[0..field_size], arena)).intern(largest_field.ty, mod); + return (try mod.intern(.{ + .un = .{ + .ty = ty.toIntern(), + .tag = .undef, + .val = val, + }, + })).toValue(); + }, + .Packed => { + const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; + return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena); + }, + }, .Pointer => { assert(!ty.isSlice(mod)); // No well defined layout. const int_val = try readFromMemory(Type.usize, mod, buffer, arena); @@ -987,7 +1026,7 @@ pub const Value = struct { }, } })).toValue(); }, - else => @panic("TODO implement readFromMemory for more types"), + else => return error.Unimplemented, } } @@ -1001,7 +1040,10 @@ pub const Value = struct { buffer: []const u8, bit_offset: usize, arena: Allocator, - ) Allocator.Error!Value { + ) error{ + IllDefinedMemoryLayout, + OutOfMemory, + }!Value { const ip = &mod.intern_pool; const target = mod.getTarget(); const endian = target.cpu.arch.endian(); @@ -1098,6 +1140,21 @@ pub const Value = struct { .storage = .{ .elems = field_vals }, } })).toValue(); }, + .Union => switch (ty.containerLayout(mod)) { + .Auto => return error.IllDefinedMemoryLayout, + .Extern => unreachable, // Handled by non-packed readFromMemory + .Packed => { + const union_obj = mod.typeToUnion(ty).?; + const largest_field = mod.unionLargestField(union_obj); + const un_tag_val = try mod.enumValueFieldIndex(union_obj.enum_tag_ty.toType(), largest_field.index); + const un_val = try (try readFromPackedMemory(largest_field.ty, mod, buffer, bit_offset, arena)).intern(largest_field.ty, mod); + return (try mod.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = un_tag_val.ip_index, + .val = un_val, + } })).toValue(); + }, + }, .Pointer => { assert(!ty.isSlice(mod)); // No well defined layout. return readFromPackedMemory(Type.usize, mod, buffer, bit_offset, arena); @@ -1713,6 +1770,14 @@ pub const Value = struct { }; } + pub fn unionValue(val: Value, mod: *Module) Value { + if (val.ip_index == .none) return val.castTag(.@"union").?.data.val; + return switch (mod.intern_pool.indexToKey(val.toIntern())) { + .un => |un| un.val.toValue(), + else => unreachable, + }; + } + /// Returns a pointer to the element value at the index. pub fn elemPtr( val: Value, diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index 639de0f5f2..24c062f8b7 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const builtin = @import("builtin"); const endian = builtin.cpu.arch.endian(); const testing = @import("std").testing; @@ -452,3 +453,25 @@ test "type pun null pointer-like optional" { // note that expectEqual hides the bug try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null); } + +test "reinterpret extern union" { + const U = extern union { + a: u32, + b: u64, + }; + + comptime var u: U = undefined; + comptime @memset(std.mem.asBytes(&u), 42); + try testing.expectEqual(@as(u64, 0x2a2a2a2a_2a2a2a2a), u.b); +} + +test "reinterpret packed union" { + const U = packed union { + a: u32, + b: u64, + }; + + comptime var u: U = undefined; + comptime @memset(std.mem.asBytes(&u), 42); + try testing.expectEqual(@as(u64, 0x2a2a2a2a_2a2a2a2a), u.b); +} From f2a24b48e1221a8954ddf16e9070e1470ee13e8d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 23 Sep 2023 13:03:03 -0400 Subject: [PATCH 2/4] sema: rework the comptime representation of comptime unions When the tag is not known, it's set to `.none`. In this case, the value is either an array of bytes (for extern unions) or an integer (for packed unions). --- src/InternPool.zig | 6 ++- src/Module.zig | 31 +-------------- src/Sema.zig | 16 +++++--- src/TypedValue.zig | 24 ++++++++---- src/arch/wasm/CodeGen.zig | 5 +-- src/codegen.zig | 5 +-- src/codegen/c.zig | 5 +-- src/codegen/llvm.zig | 58 ++++++++++++++++----------- src/codegen/spirv.zig | 5 +-- src/type.zig | 10 ++--- src/value.zig | 65 ++++++++++++++----------------- test/behavior/comptime_memory.zig | 57 ++++++++++++++++++++------- 12 files changed, 148 insertions(+), 139 deletions(-) diff --git a/src/InternPool.zig b/src/InternPool.zig index 79d4127787..7c8702a716 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -1105,7 +1105,10 @@ pub const Key = union(enum) { pub const Union = extern struct { /// This is the union type; not the field type. ty: Index, - /// Indicates the active field. + /// Indicates the active field. This could be `none`, which indicates the tag is not known. `none` is only a valid value for extern and packed unions. + /// In those cases, the type of `val` is: + /// extern: a u8 array of the same byte length as the union + /// packed: an unsigned integer with the same bit size as the union tag: Index, /// The value of the active field. val: Index, @@ -5130,7 +5133,6 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .un => |un| { assert(un.ty != .none); - assert(un.tag != .none); assert(un.val != .none); ip.items.appendAssumeCapacity(.{ .tag = .union_value, diff --git a/src/Module.zig b/src/Module.zig index 09e136cde8..349a7d4ba5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5823,7 +5823,7 @@ pub fn markReferencedDeclsAlive(mod: *Module, val: Value) Allocator.Error!void { .aggregate => |aggregate| for (aggregate.storage.values()) |elem| try mod.markReferencedDeclsAlive(elem.toValue()), .un => |un| { - try mod.markReferencedDeclsAlive(un.tag.toValue()); + if (un.tag != .none) try mod.markReferencedDeclsAlive(un.tag.toValue()); try mod.markReferencedDeclsAlive(un.val.toValue()); }, else => {}, @@ -6607,7 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { const ip = &mod.intern_pool; - if (enum_tag.toIntern() == .undef) return null; + if (enum_tag.toIntern() == .none) return null; assert(ip.typeOf(enum_tag.toIntern()) == u.enum_tag_ty); const enum_type = ip.indexToKey(u.enum_tag_ty).enum_type; return enum_type.tagValueIndex(ip, enum_tag.toIntern()); @@ -6673,30 +6673,3 @@ pub fn structPackedFieldBitOffset( } unreachable; // index out of bounds } - -pub fn unionLargestField(mod: *Module, u: InternPool.UnionType) struct { - ty: Type, - index: u32, - size: u64, -} { - const fields = u.field_types.get(&mod.intern_pool); - assert(fields.len != 0); - var largest_field_ty: Type = undefined; - var largest_field_size: u64 = 0; - var largest_field_index: u32 = 0; - for (fields, 0..) |union_field, i| { - const field_ty = union_field.toType(); - const size: u32 = @intCast(field_ty.abiSize(mod)); - if (size > largest_field_size) { - largest_field_ty = field_ty; - largest_field_size = size; - largest_field_index = @intCast(i); - } - } - - return .{ - .ty = largest_field_ty, - .index = largest_field_index, - .size = largest_field_size, - }; -} diff --git a/src/Sema.zig b/src/Sema.zig index 25aae1a1e7..f741c2356e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12124,7 +12124,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const analyze_body = if (union_originally) blk: { const item_val = sema.resolveConstLazyValue(block, .unneeded, item, undefined) catch unreachable; - const field_ty = maybe_union_ty.unionFieldType(item_val, mod); + const field_ty = maybe_union_ty.unionFieldType(item_val, mod).?; break :blk field_ty.zigTypeTag(mod) != .NoReturn; } else true; @@ -12250,7 +12250,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const analyze_body = if (union_originally) blk: { const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable; - const field_ty = maybe_union_ty.unionFieldType(item_val, mod); + const field_ty = maybe_union_ty.unionFieldType(item_val, mod).?; break :blk field_ty.zigTypeTag(mod) != .NoReturn; } else true; @@ -12304,7 +12304,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const analyze_body = if (union_originally) for (items) |item| { const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable; - const field_ty = maybe_union_ty.unionFieldType(item_val, mod); + const field_ty = maybe_union_ty.unionFieldType(item_val, mod).?; if (field_ty.zigTypeTag(mod) != .NoReturn) break true; } else false else @@ -12456,7 +12456,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r case_block.wip_capture_scope = child_block.wip_capture_scope; const analyze_body = if (union_originally) blk: { - const field_ty = maybe_union_ty.unionFieldType(item_val, mod); + const field_ty = maybe_union_ty.unionFieldType(item_val, mod).?; break :blk field_ty.zigTypeTag(mod) != .NoReturn; } else true; @@ -16496,7 +16496,7 @@ fn analyzeCmpUnionTag( if (try sema.resolveMaybeUndefVal(coerced_tag)) |enum_val| { if (enum_val.isUndef(mod)) return mod.undefRef(Type.bool); - const field_ty = union_ty.unionFieldType(enum_val, mod); + const field_ty = union_ty.unionFieldType(enum_val, mod).?; if (field_ty.zigTypeTag(mod) == .NoReturn) { return .bool_false; } @@ -27208,7 +27208,11 @@ fn unionFieldVal( if (tag_matches) { return Air.internedToRef(un.val); } else { - const old_ty = union_ty.unionFieldType(un.tag.toValue(), mod); + const old_ty = if (un.tag == .none) + ip.typeOf(un.val).toType() + else + union_ty.unionFieldType(un.tag.toValue(), mod).?; + if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| { return Air.internedToRef(new_val.toIntern()); } diff --git a/src/TypedValue.zig b/src/TypedValue.zig index c2be851d15..600b132011 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -92,10 +92,14 @@ pub fn print( .val = union_val.tag, }, writer, level - 1, mod); try writer.writeAll(" = "); - try print(.{ - .ty = ty.unionFieldType(union_val.tag, mod), - .val = union_val.val, - }, writer, level - 1, mod); + if (ty.unionFieldType(union_val.tag, mod)) |field_ty| { + try print(.{ + .ty = field_ty, + .val = union_val.val, + }, writer, level - 1, mod); + } else { + return writer.writeAll("(no tag)"); + } return writer.writeAll(" }"); }, @@ -409,10 +413,14 @@ pub fn print( .val = un.tag.toValue(), }, writer, level - 1, mod); try writer.writeAll(" = "); - try print(.{ - .ty = ty.unionFieldType(un.tag.toValue(), mod), - .val = un.val.toValue(), - }, writer, level - 1, mod); + if (ty.unionFieldType(un.tag.toValue(), mod)) |field_ty| { + try print(.{ + .ty = field_ty, + .val = un.val.toValue(), + }, writer, level - 1, mod); + } else { + try writer.writeAll("(no tag)"); + } } else try writer.writeAll("..."); return writer.writeAll(" }"); }, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index f0b020d305..76a27ec718 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3259,10 +3259,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { .un => |un| { // in this case we have a packed union which will not be passed by reference. const union_obj = mod.typeToUnion(ty).?; - const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { - assert(union_obj.getLayout(ip) == .Extern); - break :f mod.unionLargestField(union_obj).index; - }; + const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; const field_ty = union_obj.field_types.get(ip)[field_index].toType(); return func.lowerConstant(un.val.toValue(), field_ty); }, diff --git a/src/codegen.zig b/src/codegen.zig index f34c97e6e5..13aefaa8e5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -583,10 +583,7 @@ pub fn generateSymbol( } const union_obj = mod.typeToUnion(typed_value.ty).?; - const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod) orelse f: { - assert(union_obj.getLayout(ip) == .Extern); - break :f mod.unionLargestField(union_obj).index; - }; + const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod).?; const field_ty = union_obj.field_types.get(ip)[field_index].toType(); if (!field_ty.hasRuntimeBits(mod)) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 0b634e5038..54e13db9a1 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1439,10 +1439,7 @@ pub const DeclGen = struct { } const union_obj = mod.typeToUnion(ty).?; - const field_i = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { - assert(union_obj.getLayout(ip) == .Extern); - break :f mod.unionLargestField(union_obj).index; - }; + const field_i = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; const field_ty = union_obj.field_types.get(ip)[field_i].toType(); const field_name = union_obj.field_names.get(ip)[field_i]; if (union_obj.getLayout(ip) == .Packed) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ad26725d98..bfbcac1e73 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4108,28 +4108,28 @@ pub const Object = struct { if (layout.payload_size == 0) return o.lowerValue(un.tag); const union_obj = mod.typeToUnion(ty).?; - const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()) orelse f: { - assert(union_obj.getLayout(ip) == .Extern); - break :f mod.unionLargestField(union_obj).index; - }; + const container_layout = union_obj.getLayout(ip); - const field_ty = union_obj.field_types.get(ip)[field_index].toType(); - if (union_obj.getLayout(ip) == .Packed) { - if (!field_ty.hasRuntimeBits(mod)) return o.builder.intConst(union_ty, 0); - const small_int_val = try o.builder.castConst( - if (field_ty.isPtrAtRuntime(mod)) .ptrtoint else .bitcast, - try o.lowerValue(un.val), - try o.builder.intType(@intCast(field_ty.bitSize(mod))), - ); - return o.builder.convConst(.unsigned, small_int_val, union_ty); - } + var need_unnamed = false; + const payload = if (un.tag != .none) p: { + const field_index = mod.unionTagFieldIndex(union_obj, un.tag.toValue()).?; + const field_ty = union_obj.field_types.get(ip)[field_index].toType(); + if (container_layout == .Packed) { + if (!field_ty.hasRuntimeBits(mod)) return o.builder.intConst(union_ty, 0); + const small_int_val = try o.builder.castConst( + if (field_ty.isPtrAtRuntime(mod)) .ptrtoint else .bitcast, + try o.lowerValue(un.val), + try o.builder.intType(@intCast(field_ty.bitSize(mod))), + ); + return o.builder.convConst(.unsigned, small_int_val, union_ty); + } + + // Sometimes we must make an unnamed struct because LLVM does + // not support bitcasting our payload struct to the true union payload type. + // Instead we use an unnamed struct and every reference to the global + // must pointer cast to the expected type before accessing the union. + need_unnamed = layout.most_aligned_field != field_index; - // Sometimes we must make an unnamed struct because LLVM does - // not support bitcasting our payload struct to the true union payload type. - // Instead we use an unnamed struct and every reference to the global - // must pointer cast to the expected type before accessing the union. - var need_unnamed = layout.most_aligned_field != field_index; - const payload = p: { if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) { const padding_len = layout.payload_size; break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8)); @@ -4147,9 +4147,23 @@ pub const Object = struct { try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }), &.{ payload, try o.builder.undefConst(padding_ty) }, ); - }; - const payload_ty = payload.typeOf(&o.builder); + } else p: { + assert(layout.tag_size == 0); + const union_val = try o.lowerValue(un.val); + if (container_layout == .Packed) { + const bitcast_val = try o.builder.castConst( + .bitcast, + union_val, + try o.builder.intType(@intCast(ty.bitSize(mod))), + ); + return o.builder.convConst(.unsigned, bitcast_val, union_ty); + } + need_unnamed = true; + break :p union_val; + }; + + const payload_ty = payload.typeOf(&o.builder); if (layout.tag_size == 0) return o.builder.structConst(if (need_unnamed) try o.builder.structType(union_ty.structKind(&o.builder), &.{payload_ty}) else diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 8fb6153938..33a864ea0a 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -838,10 +838,7 @@ pub const DeclGen = struct { return dg.todo("packed union constants", .{}); } - const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module) orelse f: { - assert(union_obj.getLayout(ip) == .Extern); - break :f mod.unionLargestField(union_obj).index; - }; + const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module).?; const active_field_ty = union_obj.field_types.get(ip)[active_field].toType(); const has_tag = layout.tag_size != 0; diff --git a/src/type.zig b/src/type.zig index b440a23b9d..9eae7fca66 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1658,6 +1658,7 @@ pub const Type = struct { const field_ty = union_obj.field_types.get(ip)[field_index]; size = @max(size, try bitSizeAdvanced(field_ty.toType(), mod, opt_sema)); } + return size; }, .opaque_type => unreachable, @@ -1926,15 +1927,12 @@ pub const Type = struct { return union_obj.enum_tag_ty.toType(); } - pub fn unionFieldType(ty: Type, enum_tag: Value, mod: *Module) Type { + pub fn unionFieldType(ty: Type, enum_tag: Value, mod: *Module) ?Type { const ip = &mod.intern_pool; const union_obj = mod.typeToUnion(ty).?; const union_fields = union_obj.field_types.get(ip); - if (mod.unionTagFieldIndex(union_obj, enum_tag)) |index| { - return union_fields[index].toType(); - } else { - return mod.unionLargestField(union_obj).ty; - } + const index = mod.unionTagFieldIndex(union_obj, enum_tag) orelse return null; + return union_fields[index].toType(); } pub fn unionTagFieldIndex(ty: Type, enum_tag: Value, mod: *Module) ?u32 { diff --git a/src/value.zig b/src/value.zig index cfac880110..6dd6ce5d32 100644 --- a/src/value.zig +++ b/src/value.zig @@ -330,7 +330,7 @@ pub const Value = struct { return mod.intern(.{ .un = .{ .ty = ty.toIntern(), .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod), - .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod), mod), + .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod), } }); }, } @@ -703,22 +703,20 @@ pub const Value = struct { std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @as(Int, @intCast(int)), endian); }, .Union => switch (ty.containerLayout(mod)) { - .Auto => return error.IllDefinedMemoryLayout, + .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already .Extern => { const union_obj = mod.typeToUnion(ty).?; const union_tag = val.unionTag(mod); - - const field_type, const field_index = if (mod.unionTagFieldIndex(union_obj, union_tag)) |field_index| .{ - union_obj.field_types.get(&mod.intern_pool)[field_index].toType(), - field_index, - } else f: { - const largest_field = mod.unionLargestField(union_obj); - break :f .{ largest_field.ty, largest_field.index }; - }; - - const field_val = try val.fieldValue(mod, field_index); - const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); - return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); + if (mod.unionTagFieldIndex(union_obj, union_tag)) |field_index| { + const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); + const field_val = try val.fieldValue(mod, field_index); + const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); + return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); + } else { + const union_size = ty.abiSize(mod); + const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); + return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..union_size]); + } }, .Packed => { const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; @@ -832,13 +830,11 @@ pub const Value = struct { .Union => { const union_obj = mod.typeToUnion(ty).?; switch (union_obj.getLayout(ip)) { - .Auto => unreachable, // Sema is supposed to have emitted a compile error already - .Extern => unreachable, // Handled in non-packed writeToMemory + .Auto, .Extern => unreachable, // Handled in non-packed writeToMemory .Packed => { const field_index = mod.unionTagFieldIndex(union_obj, val.unionTag(mod)).?; const field_type = union_obj.field_types.get(ip)[field_index].toType(); const field_val = try val.fieldValue(mod, field_index); - return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); }, } @@ -988,17 +984,14 @@ pub const Value = struct { .Union => switch (ty.containerLayout(mod)) { .Auto => return error.IllDefinedMemoryLayout, .Extern => { - const union_obj = mod.typeToUnion(ty).?; - const largest_field = mod.unionLargestField(union_obj); - const field_size: usize = @intCast(largest_field.size); - const val = try (try readFromMemory(largest_field.ty, mod, buffer[0..field_size], arena)).intern(largest_field.ty, mod); - return (try mod.intern(.{ - .un = .{ - .ty = ty.toIntern(), - .tag = .undef, - .val = val, - }, - })).toValue(); + const union_size = ty.abiSize(mod); + const array_ty = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); + const val = try (try readFromMemory(array_ty, mod, buffer, arena)).intern(array_ty, mod); + return (try mod.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = .none, + .val = val, + } })).toValue(); }, .Packed => { const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; @@ -1141,17 +1134,17 @@ pub const Value = struct { } })).toValue(); }, .Union => switch (ty.containerLayout(mod)) { - .Auto => return error.IllDefinedMemoryLayout, - .Extern => unreachable, // Handled by non-packed readFromMemory + .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory .Packed => { - const union_obj = mod.typeToUnion(ty).?; - const largest_field = mod.unionLargestField(union_obj); - const un_tag_val = try mod.enumValueFieldIndex(union_obj.enum_tag_ty.toType(), largest_field.index); - const un_val = try (try readFromPackedMemory(largest_field.ty, mod, buffer, bit_offset, arena)).intern(largest_field.ty, mod); + const union_bits: u16 = @intCast(ty.bitSize(mod)); + // TODO: Remove after tests pass + assert(union_bits != 0); + const int_ty = try mod.intType(.unsigned, union_bits); + const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern(); return (try mod.intern(.{ .un = .{ .ty = ty.toIntern(), - .tag = un_tag_val.ip_index, - .val = un_val, + .tag = .none, + .val = val, } })).toValue(); }, }, diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index 24c062f8b7..42a7e0b7e6 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -455,23 +455,52 @@ test "type pun null pointer-like optional" { } test "reinterpret extern union" { - const U = extern union { - a: u32, - b: u64, - }; + { + const U = extern union { + a: u32, + b: u8 align(8), + }; - comptime var u: U = undefined; - comptime @memset(std.mem.asBytes(&u), 42); - try testing.expectEqual(@as(u64, 0x2a2a2a2a_2a2a2a2a), u.b); + comptime var u: U = undefined; + comptime @memset(std.mem.asBytes(&u), 42); + try comptime testing.expect(0x2a2a2a2a == u.a); + try comptime testing.expect(42 == u.b); + try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); + try testing.expectEqual(42, u.b); + } } test "reinterpret packed union" { - const U = packed union { - a: u32, - b: u64, - }; + { + const U = packed union { + a: u32, + b: u8 align(8), + }; - comptime var u: U = undefined; - comptime @memset(std.mem.asBytes(&u), 42); - try testing.expectEqual(@as(u64, 0x2a2a2a2a_2a2a2a2a), u.b); + comptime var u: U = undefined; + comptime @memset(std.mem.asBytes(&u), 42); + try comptime testing.expect(0x2a2a2a2a == u.a); + try comptime testing.expect(0x2a == u.b); + try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); + try testing.expectEqual(0x2a, u.b); + } + + { + const U = packed union { + a: u7, + b: u1, + }; + + const S = packed struct { + lsb: U, + msb: U, + }; + + comptime var s: S = undefined; + comptime @memset(std.mem.asBytes(&s), 0xaa); + try comptime testing.expectEqual(@as(u7, 0x2a), s.lsb.a); + try comptime testing.expectEqual(@as(u1, 0), s.lsb.b); + try comptime testing.expectEqual(@as(u7, 0x55), s.msb.a); + try comptime testing.expectEqual(@as(u1, 1), s.msb.b); + } } From 4e9f5f25c8226144eff8d9c1df79cfcffbae5492 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 23 Sep 2023 13:04:18 -0400 Subject: [PATCH 3/4] type: resolve packed union type layouts in bitSizeAdvanced Before this change, packed structs containing packed unions could make it to codegen without having their layout resolved. --- src/Sema.zig | 2 +- src/TypedValue.zig | 2 +- src/type.zig | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index f741c2356e..e43804b521 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4000,7 +4000,7 @@ fn resolveComptimeKnownAllocValue(sema: *Sema, block: *Block, alloc: Air.Inst.Re const air_ptr_inst = Air.refToIndex(bin_op.lhs).?; const tag_val = (try sema.resolveMaybeUndefVal(bin_op.rhs)).?; const union_ty = sema.typeOf(bin_op.lhs).childType(mod); - const payload_ty = union_ty.unionFieldType(tag_val, mod); + const payload_ty = union_ty.unionFieldType(tag_val, mod).?; if (try sema.typeHasOnePossibleValue(payload_ty)) |payload_val| { const new_ptr = ptr_mapping.get(air_ptr_inst).?; const store_val = try mod.unionValue(union_ty, tag_val, payload_val); diff --git a/src/TypedValue.zig b/src/TypedValue.zig index 600b132011..86f42813f7 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -417,7 +417,7 @@ pub fn print( try print(.{ .ty = field_ty, .val = un.val.toValue(), - }, writer, level - 1, mod); + }, writer, level - 1, mod); } else { try writer.writeAll("(no tag)"); } diff --git a/src/type.zig b/src/type.zig index 9eae7fca66..b6dbdd54f5 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1646,8 +1646,12 @@ pub const Type = struct { }, .union_type => |union_type| { - if (opt_sema) |sema| try sema.resolveTypeFields(ty); - if (ty.containerLayout(mod) != .Packed) { + const is_packed = ty.containerLayout(mod) == .Packed; + if (opt_sema) |sema| { + try sema.resolveTypeFields(ty); + if (is_packed) try sema.resolveTypeLayout(ty); + } + if (!is_packed) { return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8; } const union_obj = ip.loadUnionType(union_type); From 9f4649b197b720dbc168ced25eee0805d3b678b1 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 23 Sep 2023 14:33:31 -0400 Subject: [PATCH 4/4] codegen/sema: handle unions with unknown tags in more places --- src/Sema.zig | 2 +- src/TypedValue.zig | 30 ++++++++++++++++-------------- src/codegen.zig | 29 +++++++++++++++++++---------- src/value.zig | 25 +++++++++++++++---------- 4 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e43804b521..cb54843da7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -32879,7 +32879,7 @@ fn unionToTag( return Air.internedToRef(opv.toIntern()); } if (try sema.resolveMaybeUndefVal(un)) |un_val| { - return Air.internedToRef(un_val.unionTag(mod).toIntern()); + return Air.internedToRef(un_val.unionTag(mod).?.toIntern()); } try sema.requireRuntimeBlock(block, un_src, null); return block.addTyOp(.get_union_tag, enum_ty, un); diff --git a/src/TypedValue.zig b/src/TypedValue.zig index 86f42813f7..cf705cdf89 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -87,18 +87,19 @@ pub fn print( const union_val = val.castTag(.@"union").?.data; try writer.writeAll(".{ "); - try print(.{ - .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), - .val = union_val.tag, - }, writer, level - 1, mod); - try writer.writeAll(" = "); - if (ty.unionFieldType(union_val.tag, mod)) |field_ty| { + if (union_val.tag.toIntern() != .none) { + try print(.{ + .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), + .val = union_val.tag, + }, writer, level - 1, mod); + try writer.writeAll(" = "); + const field_ty = ty.unionFieldType(union_val.tag, mod).?; try print(.{ .ty = field_ty, .val = union_val.val, }, writer, level - 1, mod); } else { - return writer.writeAll("(no tag)"); + return writer.writeAll("(unknown tag)"); } return writer.writeAll(" }"); @@ -408,18 +409,19 @@ pub fn print( .un => |un| { try writer.writeAll(".{ "); if (level > 0) { - try print(.{ - .ty = ty.unionTagTypeHypothetical(mod), - .val = un.tag.toValue(), - }, writer, level - 1, mod); - try writer.writeAll(" = "); - if (ty.unionFieldType(un.tag.toValue(), mod)) |field_ty| { + if (un.tag != .none) { + try print(.{ + .ty = ty.unionTagTypeHypothetical(mod), + .val = un.tag.toValue(), + }, writer, level - 1, mod); + try writer.writeAll(" = "); + const field_ty = ty.unionFieldType(un.tag.toValue(), mod).?; try print(.{ .ty = field_ty, .val = un.val.toValue(), }, writer, level - 1, mod); } else { - try writer.writeAll("(no tag)"); + try writer.writeAll("(unknown tag)"); } } else try writer.writeAll("..."); return writer.writeAll(" }"); diff --git a/src/codegen.zig b/src/codegen.zig index 13aefaa8e5..738281cf55 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -583,24 +583,33 @@ pub fn generateSymbol( } const union_obj = mod.typeToUnion(typed_value.ty).?; - const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod).?; + if (un.tag != .none) { + const field_index = typed_value.ty.unionTagFieldIndex(un.tag.toValue(), mod).?; + const field_ty = union_obj.field_types.get(ip)[field_index].toType(); + if (!field_ty.hasRuntimeBits(mod)) { + try code.appendNTimes(0xaa, math.cast(usize, layout.payload_size) orelse return error.Overflow); + } else { + switch (try generateSymbol(bin_file, src_loc, .{ + .ty = field_ty, + .val = un.val.toValue(), + }, code, debug_output, reloc_info)) { + .ok => {}, + .fail => |em| return Result{ .fail = em }, + } - const field_ty = union_obj.field_types.get(ip)[field_index].toType(); - if (!field_ty.hasRuntimeBits(mod)) { - try code.appendNTimes(0xaa, math.cast(usize, layout.payload_size) orelse return error.Overflow); + const padding = math.cast(usize, layout.payload_size - field_ty.abiSize(mod)) orelse return error.Overflow; + if (padding > 0) { + try code.appendNTimes(0, padding); + } + } } else { switch (try generateSymbol(bin_file, src_loc, .{ - .ty = field_ty, + .ty = ip.typeOf(un.val).toType(), .val = un.val.toValue(), }, code, debug_output, reloc_info)) { .ok => {}, .fail => |em| return Result{ .fail = em }, } - - const padding = math.cast(usize, layout.payload_size - field_ty.abiSize(mod)) orelse return error.Overflow; - if (padding > 0) { - try code.appendNTimes(0, padding); - } } if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) { diff --git a/src/value.zig b/src/value.zig index 6dd6ce5d32..279f52e3e0 100644 --- a/src/value.zig +++ b/src/value.zig @@ -706,8 +706,8 @@ pub const Value = struct { .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already .Extern => { const union_obj = mod.typeToUnion(ty).?; - const union_tag = val.unionTag(mod); - if (mod.unionTagFieldIndex(union_obj, union_tag)) |field_index| { + if (val.unionTag(mod)) |union_tag| { + const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); const field_val = try val.fieldValue(mod, field_index); const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); @@ -715,7 +715,7 @@ pub const Value = struct { } else { const union_size = ty.abiSize(mod); const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); - return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..union_size]); + return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]); } }, .Packed => { @@ -832,10 +832,16 @@ pub const Value = struct { switch (union_obj.getLayout(ip)) { .Auto, .Extern => unreachable, // Handled in non-packed writeToMemory .Packed => { - const field_index = mod.unionTagFieldIndex(union_obj, val.unionTag(mod)).?; - const field_type = union_obj.field_types.get(ip)[field_index].toType(); - const field_val = try val.fieldValue(mod, field_index); - return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); + if (val.unionTag(mod)) |union_tag| { + const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; + const field_type = union_obj.field_types.get(ip)[field_index].toType(); + const field_val = try val.fieldValue(mod, field_index); + return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); + } else { + const union_bits: u16 = @intCast(ty.bitSize(mod)); + const int_ty = try mod.intType(.unsigned, union_bits); + return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset); + } }, } }, @@ -1137,7 +1143,6 @@ pub const Value = struct { .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory .Packed => { const union_bits: u16 = @intCast(ty.bitSize(mod)); - // TODO: Remove after tests pass assert(union_bits != 0); const int_ty = try mod.intType(.unsigned, union_bits); const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern(); @@ -1754,11 +1759,11 @@ pub const Value = struct { }; } - pub fn unionTag(val: Value, mod: *Module) Value { + pub fn unionTag(val: Value, mod: *Module) ?Value { if (val.ip_index == .none) return val.castTag(.@"union").?.data.tag; return switch (mod.intern_pool.indexToKey(val.toIntern())) { .undef, .enum_tag => val, - .un => |un| un.tag.toValue(), + .un => |un| if (un.tag != .none) un.tag.toValue() else return null, else => unreachable, }; }