From 9aeac329a25ddf43bf3afdfe8c381682226dafdf Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Tue, 2 Jun 2026 16:36:15 +0200
Subject: [PATCH 1/7] Sema: require `noreturn` as backing integer of empty
enums
Empty enums are uninstantiable, so their backing integer should be too.
This avoids a bunch of weird special cases during semantic analysis.
If you were previously using `enum {}`, your code should continue to work
fine.
If you were previously using `enum(uN) {}` (where `uN` is any integer type),
you'll probably want to use `enum(uN) { _ }` instead.
---
doc/langref.html.in | 2 +-
doc/langref/test_enums.zig | 6 ++++
lib/std/meta.zig | 6 ++--
lib/std/zig.zig | 2 --
lib/std/zig/parser_test.zig | 2 +-
src/InternPool.zig | 9 ++----
src/Sema.zig | 29 ++---------------
src/Sema/type_resolution.zig | 28 ++++++++++++----
src/Type.zig | 12 +++++--
src/link/Dwarf.zig | 1 +
test/behavior/enum.zig | 23 -------------
test/behavior/type.zig | 2 +-
.../enum_noreturn_backing_type.zig | 16 ++++++++++
...non-extern-compatible_integer_tag_type.zig | 30 ++++++++++-------
.../compile_errors/initialize_empty_union.zig | 4 +--
.../compile_errors/instantiate_empty_enum.zig | 32 +++++++++++++++++++
.../int_from_enum_undefined.zig | 11 -------
...truct_with_fields_of_not_allowed_types.zig | 8 +++++
...union_with_fields_of_not_allowed_types.zig | 8 +++++
...e_for_tagged_union_with_no_enum_fields.zig | 2 +-
.../sizeof_alignof_empty_union.zig | 4 +--
21 files changed, 137 insertions(+), 100 deletions(-)
create mode 100644 test/cases/compile_errors/enum_noreturn_backing_type.zig
create mode 100644 test/cases/compile_errors/instantiate_empty_enum.zig
delete mode 100644 test/cases/compile_errors/int_from_enum_undefined.zig
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 4766e8d508d31b76b6258501ef1226f7d809c1e1..7930468882d7eeae945dfeb77f62ac74dcf0e16a 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -2401,7 +2401,7 @@ or
{#header_open|enum#}
{#code|test_enums.zig#}
- {#see_also|@typeInfo|@tagName|@sizeOf#}
+ {#see_also|@typeInfo|@tagName|@sizeOf|noreturn#}
{#header_open|extern enum#}
diff --git a/doc/langref/test_enums.zig b/doc/langref/test_enums.zig
index 9502e7ae74c05c9b786c69847c3cfd5aa08888c3..9da7c160edcabd10a2e8fc07a6e7e480edaffb2c 100644
--- a/doc/langref/test_enums.zig
+++ b/doc/langref/test_enums.zig
@@ -111,4 +111,10 @@ test "@tagName" {
try expectEqualStrings(@tagName(Small.three), "three");
}
+// Empty enums are uninstantiable, their tag type is always noreturn.
+const Empty = enum {};
+test "empty enum" {
+ try expectEqual(noreturn, @typeInfo(Empty).@"enum".tag_type);
+}
+
// test
diff --git a/lib/std/meta.zig b/lib/std/meta.zig
index 14270ba9d86763f72a5a75eb5f846dee00759954..78e26d6c8f4f99dd04dcf0dcf3e79e8ae14e20d2 100644
--- a/lib/std/meta.zig
+++ b/lib/std/meta.zig
@@ -408,7 +408,8 @@ pub fn FieldEnum(comptime T: type) type {
else => {},
}
- const IntTag = std.math.IntFittingRange(0, field_names.len -| 1);
+ if (field_names.len == 0) return enum {};
+ const IntTag = std.math.IntFittingRange(0, field_names.len - 1);
return @Enum(IntTag, .exhaustive, field_names, &std.simd.iota(IntTag, field_names.len));
}
@@ -469,7 +470,8 @@ test FieldEnum {
pub fn DeclEnum(comptime T: type) type {
const decl_names = declarations(T);
- const IntTag = std.math.IntFittingRange(0, decl_names.len -| 1);
+ if (decl_names.len == 0) return enum {};
+ const IntTag = std.math.IntFittingRange(0, decl_names.len - 1);
return @Enum(IntTag, .exhaustive, decl_names, &std.simd.iota(IntTag, decl_names.len));
}
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
index 3568ecf4d32886675b4b075112c285acbed84329..424effacc3e7f1b38a977ce9e685dc3ced6dd83f 100644
--- a/lib/std/zig.zig
+++ b/lib/std/zig.zig
@@ -906,7 +906,6 @@ pub const SimpleComptimeReason = enum(u32) {
slice_single_item_ptr_bounds,
stored_to_comptime_field,
stored_to_comptime_var,
- casted_to_comptime_enum,
casted_to_comptime_int,
casted_to_comptime_float,
std_lang_decl,
@@ -989,7 +988,6 @@ pub const SimpleComptimeReason = enum(u32) {
.slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds",
.stored_to_comptime_field => "value stored to a comptime field must be comptime-known",
.stored_to_comptime_var => "value stored to a comptime variable must be comptime-known",
- .casted_to_comptime_enum => "value casted to enum with 'comptime_int' tag type must be comptime-known",
.casted_to_comptime_int => "value casted to 'comptime_int' must be comptime-known",
.casted_to_comptime_float => "value casted to 'comptime_float' must be comptime-known",
.std_lang_decl => "'std.lang' declaration values must be comptime-known",
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index e46ac087648f95f8ac4b40c42c33ad248ecac645..c471a2d20f1c5d9d8a7d75ce9e2bde4502418e43 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -1120,7 +1120,7 @@ test "zig fmt: empty enum decls" {
\\const A = enum {};
\\const B = enum(u32) {};
\\const C = extern enum(c_int) {};
- \\const D = packed enum(u8) {};
+ \\const D = packed enum(noreturn) {};
\\
);
}
diff --git a/src/InternPool.zig b/src/InternPool.zig
index eae7f32c7f17782b694a21d06c52bfe456049dcf..632bd75ca0f5ec2b1198f4bd8fac0bdc76fdbe0b 100644
--- a/src/InternPool.zig
+++ b/src/InternPool.zig
@@ -5040,7 +5040,6 @@ pub const Tag = enum(u8) {
/// The set of values that are encoded this way is:
/// * An array or vector which has length 0.
/// * A struct which has all fields comptime-known.
- /// * An empty enum or union. TODO: this value's existence is strange, because such a type in reality has no values. See #15909
/// data is Index of the type, which is known to be zero bits at runtime.
only_possible_value,
/// data is extra index to Key.Union.
@@ -7740,12 +7739,8 @@ pub fn get(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, key:
}),
.enum_tag => |enum_tag| {
- assert(ip.isEnumType(enum_tag.ty));
- switch (ip.indexToKey(enum_tag.ty)) {
- .simple_type => assert(ip.isIntegerType(ip.typeOf(enum_tag.int))),
- .enum_type => assert(ip.typeOf(enum_tag.int) == ip.loadEnumType(enum_tag.ty).int_tag_type),
- else => unreachable,
- }
+ const enum_obj = ip.loadEnumType(enum_tag.ty);
+ assert(ip.typeOf(enum_tag.int) == enum_obj.int_tag_type);
items.appendAssumeCapacity(.{
.tag = .enum_tag,
.data = try addExtra(extra, enum_tag),
diff --git a/src/Sema.zig b/src/Sema.zig
index 4b29027056be47f0bccaa0250f7b30a3a8980667..12fc7cb5fc145b79ab9da6f9da193ed78aa1d338 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -7854,14 +7854,7 @@ fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
};
const enum_tag_ty = sema.typeOf(enum_tag);
const int_tag_ty = enum_tag_ty.intTagType(zcu);
-
- // TODO: use correct solution
- // https://github.com/ziglang/zig/issues/15909
- if (enum_tag_ty.enumFieldCount(zcu) == 0 and !enum_tag_ty.isNonexhaustiveEnum(zcu)) {
- return sema.fail(block, operand_src, "cannot use @intFromEnum on empty enum '{f}'", .{
- enum_tag_ty.fmt(pt),
- });
- }
+ assert(int_tag_ty.classify(zcu) != .no_possible_value);
if (sema.resolveValue(enum_tag)) |enum_tag_val| {
if (enum_tag_val.isUndef(zcu)) return pt.undefRef(int_tag_ty);
@@ -7910,10 +7903,6 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
}
- if (dest_ty.intTagType(zcu).zigTypeTag(zcu) == .comptime_int) {
- return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_enum });
- }
-
if (try dest_ty.onePossibleValue(pt)) |opv| {
if (block.wantSafety()) {
// The operand is runtime-known but the result is comptime-known. In
@@ -9940,13 +9929,6 @@ fn analyzeSwitchBlock(
operand_ty.containerLayout(zcu) != .@"packed";
const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
- if (item_ty.zigTypeTag(zcu) == .@"enum" and
- validated_switch.seen_enum_fields.len == 0 and
- !operand_ty.isNonexhaustiveEnum(zcu))
- {
- return .void_value; // switch on empty enum/union
- }
-
const cond_ref = switch (operand) {
.simple => |s| s.cond,
.loop => |l| l.init_cond,
@@ -19567,14 +19549,6 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
operand_ty.fmt(pt),
}),
};
- if (enum_ty.enumFieldCount(zcu) == 0) {
- // TODO I don't think this is the correct way to handle this but
- // it prevents a crash.
- // https://github.com/ziglang/zig/issues/15909
- return sema.fail(block, operand_src, "cannot get @tagName of empty enum '{f}'", .{
- enum_ty.fmt(pt),
- });
- }
const casted_operand = try sema.coerce(block, enum_ty, operand, operand_src);
if (try sema.resolveDefinedValue(block, operand_src, casted_operand)) |val| {
const field_index = enum_ty.enumTagFieldIndex(val, zcu) orelse {
@@ -33985,6 +33959,7 @@ fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool {
// The `tagValueIndex` function call below relies on the type being the integer tag type.
// `getCoerced` assumes the value will fit the new type.
const int_tag_ty: Type = .fromInterned(enum_type.int_tag_type);
+ if (int_tag_ty.classify(zcu) == .no_possible_value) return false;
if (!int.intFitsInType(int_tag_ty, null, zcu)) return false;
const int_coerced = try pt.getCoerced(int, int_tag_ty);
return enum_type.tagValueIndex(&zcu.intern_pool, int_coerced.toIntern()) != null;
diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig
index 116ee444f05dc2c575f6b2481378c8f11c8d9fd0..5c4211d22980d2d5daedb08b80e1f151990bddcc 100644
--- a/src/Sema/type_resolution.zig
+++ b/src/Sema/type_resolution.zig
@@ -1325,15 +1325,31 @@ pub fn resolveEnumLayout(sema: *Sema, enum_ty: Type) CompileError!void {
const type_ref = try sema.resolveInlineBody(&block, tag_type_body, zir_index);
break :ty try sema.analyzeAsType(&block, tag_type_src, .enum_int_tag_type, type_ref);
};
+ const empty_exhaustive = enum_obj.field_names.len == 0 and !enum_obj.nonexhaustive;
const int_tag_ty: Type = if (explicit_int_tag_ty) |int_tag_ty| ty: {
- if (int_tag_ty.zigTypeTag(zcu) != .int) return sema.fail(
- &block,
- block.src(.container_arg),
- "expected integer tag type, found '{f}'",
- .{int_tag_ty.fmt(pt)},
- );
+ switch (int_tag_ty.zigTypeTag(zcu)) {
+ .int => if (empty_exhaustive) return sema.fail(
+ &block,
+ block.src(.container_arg),
+ "empty exhaustive enums must be backed by 'noreturn'",
+ .{},
+ ),
+ .noreturn => if (!empty_exhaustive) return sema.fail(
+ &block,
+ block.src(.container_arg),
+ "non-empty enums cannot be backed by 'noreturn'",
+ .{},
+ ),
+ else => return sema.fail(
+ &block,
+ block.src(.container_arg),
+ "expected integer tag type, found '{f}'",
+ .{int_tag_ty.fmt(pt)},
+ ),
+ }
break :ty int_tag_ty;
} else ty: {
+ if (empty_exhaustive) break :ty .noreturn;
// Infer the int tag type from the field count
const bits = Type.smallestUnsignedBits(enum_obj.field_names.len -| 1);
break :ty try pt.intType(.unsigned, bits);
diff --git a/src/Type.zig b/src/Type.zig
index 6372c1e157172f6a7b4ee97f3d441af76e9a500b..b56a4868bc587ca5473001779e107fd221068975 100644
--- a/src/Type.zig
+++ b/src/Type.zig
@@ -3058,9 +3058,15 @@ pub fn unpackable(ty: Type, zcu: *const Zcu) ?UnpackableReason {
.one, .many, .c => .pointer,
},
- .@"enum" => switch (zcu.intern_pool.loadEnumType(ty.toIntern()).int_tag_mode) {
- .explicit => null,
- .auto => .{ .enum_inferred_int_tag = ty },
+ .@"enum" => {
+ const enum_obj = zcu.intern_pool.loadEnumType(ty.toIntern());
+ return switch (enum_obj.int_tag_mode) {
+ .explicit => if (enum_obj.int_tag_type != .noreturn_type)
+ null
+ else
+ .other,
+ .auto => .{ .enum_inferred_int_tag = ty },
+ };
},
.@"struct" => switch (ty.containerLayout(zcu)) {
diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig
index b6ff6c754bc97b0efc78522b30dcf302d9ad072d..baa5be93e937a72c22193b3e159bd4df3b960ac1 100644
--- a/src/link/Dwarf.zig
+++ b/src/link/Dwarf.zig
@@ -5098,6 +5098,7 @@ fn DeclValEnum(comptime T: type) type {
if (min_value == null or min_value.? > value) min_value = value;
if (max_value == null or max_value.? < value) max_value = value;
}
+ if (fields_len == 0) return enum {};
const TagInt = std.math.IntFittingRange(min_value orelse 0, max_value orelse 0);
var field_vals: [fields_len]TagInt = undefined;
for (field_names[0..fields_len], &field_vals) |name, *val| val.* = @field(T, name);
diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig
index ca253a102ff5351d730e5ad6f004755e0c225c5f..aa664a46ff2a5f96f7b9b8175394676df235b379 100644
--- a/test/behavior/enum.zig
+++ b/test/behavior/enum.zig
@@ -1451,29 +1451,6 @@ test "comptime @enumFromInt with signed arithmetic" {
comptime assert(@intFromEnum(x) == 0);
}
-test "switch on empty enum" {
- const E = enum {};
- var e: E = undefined;
- _ = &e;
- switch (e) {}
-}
-
-test "switch on empty enum with a specified tag type" {
- const E = enum(u8) {};
- var e: E = undefined;
- _ = &e;
- switch (e) {}
-}
-
-test "empty enum passed as argument" {
- const E = enum {
- fn f(e: @This()) void {
- switch (e) {}
- }
- };
- E.f(@as(E, undefined));
-}
-
test "enum int tag type uses declaration inside the enum" {
const static = struct {
const E = enum(E.IntTag) {
diff --git a/test/behavior/type.zig b/test/behavior/type.zig
index c2d1fc2f4fbf36674bdf3eab2b506fd4acd1fdb4..bf012e0061f86384fa985e74353fe4e16c12492b 100644
--- a/test/behavior/type.zig
+++ b/test/behavior/type.zig
@@ -270,7 +270,7 @@ test "Type.Union from empty regular enum" {
}
test "Type.Union from empty Type.Enum" {
- const E = @Enum(u0, .exhaustive, &.{}, &.{});
+ const E = @Enum(noreturn, .exhaustive, &.{}, &.{});
const U = @Union(.auto, E, &.{}, &.{}, &.{});
try testing.expectEqual(@typeInfo(U).@"union".field_names.len, 0);
}
diff --git a/test/cases/compile_errors/enum_noreturn_backing_type.zig b/test/cases/compile_errors/enum_noreturn_backing_type.zig
new file mode 100644
index 0000000000000000000000000000000000000000..d2ca3252ecb427ddf1d52ac76648967438e8ae1c
--- /dev/null
+++ b/test/cases/compile_errors/enum_noreturn_backing_type.zig
@@ -0,0 +1,16 @@
+const E1 = enum(u32) {};
+export fn entry1() void {
+ const e: E1 = undefined;
+ _ = e;
+}
+
+const E2 = enum(noreturn) { a, b, c };
+export fn entry2() void {
+ const e: E2 = undefined;
+ _ = e;
+}
+
+// error
+//
+// :1:17: error: empty exhaustive enums must be backed by 'noreturn'
+// :7:17: error: non-empty enums cannot be backed by 'noreturn'
diff --git a/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig b/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig
index fdf0f9aadd08021eb5a34367e80b93ade4ea1b3b..1dd3dd5b517ae85f4a077c90dbf65697c3b72bf4 100644
--- a/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig
+++ b/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig
@@ -1,15 +1,23 @@
-pub const E = enum(u31) { A, B, C };
-pub const S = extern struct {
- e: E,
-};
-export fn entry() void {
- const s: S = undefined;
- _ = s;
+export fn entry1() void {
+ const E = enum(u31) { A, B, C };
+ _ = @sizeOf(extern struct {
+ x: E,
+ });
+}
+export fn entry2() void {
+ const E = enum(noreturn) {};
+ _ = @sizeOf(extern struct {
+ x: E,
+ });
}
// error
//
-// :3:8: error: extern structs cannot contain fields of type 'tmp.E'
-// :1:15: note: enum tag type 'u31' is not extern compatible
-// :1:15: note: only integers with 0 or power of two bits are extern compatible
-// :1:15: note: enum declared here
+// :4:12: error: extern structs cannot contain fields of type 'tmp.entry1.E'
+// :2:15: note: enum tag type 'u31' is not extern compatible
+// :2:15: note: only integers with 0 or power of two bits are extern compatible
+// :2:15: note: enum declared here
+// :10:12: error: extern structs cannot contain fields of type 'tmp.entry2.E'
+// :8:15: note: enum tag type 'noreturn' is not extern compatible
+// :8:15: note: 'noreturn' is only allowed as a return type
+// :8:15: note: enum declared here
diff --git a/test/cases/compile_errors/initialize_empty_union.zig b/test/cases/compile_errors/initialize_empty_union.zig
index 2847446b2516a1eb4f968a8bb31b24abc7bd132d..a7945a105b6d973411f6d2e31810ae6e5f7e595e 100644
--- a/test/cases/compile_errors/initialize_empty_union.zig
+++ b/test/cases/compile_errors/initialize_empty_union.zig
@@ -1,10 +1,10 @@
const EnumInferred = enum {};
-const EnumExplicit = enum(u8) {};
+const EnumExplicit = enum(noreturn) {};
const EnumNonexhaustive = enum(u8) { _ };
const U0 = union {};
const U1 = union(enum) {};
-const U2 = union(enum(u8)) {};
+const U2 = union(enum(noreturn)) {};
const U3 = union(EnumInferred) {};
const U4 = union(EnumExplicit) {};
const U5 = union(EnumNonexhaustive) {};
diff --git a/test/cases/compile_errors/instantiate_empty_enum.zig b/test/cases/compile_errors/instantiate_empty_enum.zig
new file mode 100644
index 0000000000000000000000000000000000000000..bc469024c841db6a5cc95dc33785046fe9dc317c
--- /dev/null
+++ b/test/cases/compile_errors/instantiate_empty_enum.zig
@@ -0,0 +1,32 @@
+const E = enum {};
+
+export fn entry1() void {
+ const e: E = undefined;
+ _ = e;
+}
+
+export fn entry2() void {
+ const e: E = @enumFromInt(@as(u8, undefined));
+ _ = e;
+}
+
+export fn entry3() void {
+ const e: E = .a;
+ _ = e;
+}
+
+export fn entry4() void {
+ const e: E = @enumFromInt(0);
+ _ = e;
+}
+
+// error
+//
+// :4:18: error: expected type 'tmp.E', found '@TypeOf(undefined)'
+// :4:18: note: cannot coerce to uninstantiable type 'tmp.E'
+// :1:11: note: enum declared here
+// :9:31: error: use of undefined value here causes illegal behavior
+// :14:19: error: enum 'tmp.E' has no member named 'a'
+// :1:11: note: enum declared here
+// :19:18: error: enum 'tmp.E' has no tag with value '0'
+// :1:11: note: enum declared here
diff --git a/test/cases/compile_errors/int_from_enum_undefined.zig b/test/cases/compile_errors/int_from_enum_undefined.zig
deleted file mode 100644
index 96aaa48c280064a7f886fd583ad97a0359b86477..0000000000000000000000000000000000000000
--- a/test/cases/compile_errors/int_from_enum_undefined.zig
+++ /dev/null
@@ -1,11 +0,0 @@
-export fn a() void {
- const E = enum {};
- var e: E = undefined;
- _ = &e;
- _ = @intFromEnum(e);
-}
-
-// error
-//
-// :5:22: error: cannot use @intFromEnum on empty enum 'tmp.a.E'
-// :2:15: note: enum declared here
diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig
index d7a5d355c9d26a565b179d3610255a55bafaaab7..efe1f9157f0c9e6b5552e71cc17fed01b8fe1df6 100644
--- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig
+++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig
@@ -81,6 +81,12 @@ export fn entry15() void {
x: *const u32,
});
}
+export fn entry16() void {
+ const E = enum(noreturn) {};
+ _ = @sizeOf(packed struct {
+ x: E,
+ });
+}
// error
//
@@ -114,3 +120,5 @@ export fn entry15() void {
// :81:12: error: packed structs cannot contain fields of type '*const u32'
// :81:12: note: pointers cannot be directly bitpacked
// :81:12: note: consider using 'usize' and '@intFromPtr'
+// :87:12: error: packed structs cannot contain fields of type 'tmp.entry16.E'
+// :87:12: note: type does not have a bit-packed representation
diff --git a/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig
index d0e09742b5080e165af9fc105ff62fad177ff993..a479141e5a4ab67e6809cad5c460459bc8babac4 100644
--- a/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig
+++ b/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig
@@ -10,6 +10,12 @@ export fn entry1() void {
x: *const u32,
});
}
+export fn entry2() void {
+ const E = enum(noreturn) {};
+ _ = @sizeOf(packed union {
+ x: E,
+ });
+}
// error
//
@@ -19,3 +25,5 @@ export fn entry1() void {
// :10:12: error: packed unions cannot contain fields of type '*const u32'
// :10:12: note: pointers cannot be directly bitpacked
// :10:12: note: consider using 'usize' and '@intFromPtr'
+// :16:12: error: packed unions cannot contain fields of type 'tmp.entry2.E'
+// :16:12: note: type does not have a bit-packed representation
diff --git a/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig b/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig
index e9c27b7eeafa92db4b461173e196c3ce91eb26a2..d204d18f195dddca616550c57b10465f38c06218 100644
--- a/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig
+++ b/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig
@@ -1,4 +1,4 @@
-const Tag = @Enum(u0, .exhaustive, &.{}, &.{});
+const Tag = @Enum(noreturn, .exhaustive, &.{}, &.{});
const Tagged = @Union(.auto, Tag, &.{ "signed", "unsigned" }, &.{ i32, u32 }, &@splat(.{}));
export fn entry() void {
const tagged: Tagged = undefined;
diff --git a/test/cases/compile_errors/sizeof_alignof_empty_union.zig b/test/cases/compile_errors/sizeof_alignof_empty_union.zig
index 58e6aa635088c30cab0c1d7306f088406fcb34d1..1144670907582660a5e8b03224d5aceb61f6de3e 100644
--- a/test/cases/compile_errors/sizeof_alignof_empty_union.zig
+++ b/test/cases/compile_errors/sizeof_alignof_empty_union.zig
@@ -1,10 +1,10 @@
const EnumInferred = enum {};
-const EnumExplicit = enum(u8) {};
+const EnumExplicit = enum(noreturn) {};
const EnumNonexhaustive = enum(u8) { _ };
const U0 = union {};
const U1 = union(enum) {};
-const U2 = union(enum(u8)) {};
+const U2 = union(enum(noreturn)) {};
const U3 = union(EnumInferred) {};
const U4 = union(EnumExplicit) {};
const U5 = union(EnumNonexhaustive) {};
--
2.54.0
From cb4c344e19a269ac227489a96e2ef53dd077ece0 Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Wed, 3 Jun 2026 15:27:46 +0200
Subject: [PATCH 2/7] compiler: add `@backingInt` and `@fromBackingInt`
This commit implements the `@backingInt` and `@fromBackingInt` builtins.
These builtins are meant to replace the now-deprecated `@intFromEnum` and
`@enumFromInt` builtins.
`@backingInt` works with all enums and with bitpacks with *explicit* backing
integer types only. It also works with tagged unions, returning the backing
integer of the active tag value.
An `undefined` enum or bitpack yields an `undefined` backing integer.
The builtin is lowered to `bit_cast`.
`@fromBackingInt` infers its result type, which may be any enum or a bitpack
with an *explicit* backing integer type. It takes a parameter of exactly
that backing integer type.
For enums, passing a backing integer that is either `undefined` or would
yield an invalid tag value results in safety-checked Illegal Behavior.
For bitpacks, passing an `undefined` backing integer yields an `undefined`
bitpack.
The builtin is lowered to `bit_cast` or `bit_cast_safe` (more details below).
`@bitCast` now also performs a safety check for invalid tag values if its
destination type is an `enum`.
This commit also introduces a new AIR instruction, `bit_cast_safe`.
Similarly to `int_cast_safe`, it includes a safety check for invalid enum
tag values. When `@enumFromInt` is eventually removed, `int_cast_safe` won't
need to perform this safety check anymore.
Legalize only implements `expand_bit_cast_safe`, and no additional insts
to scalarize `bit_cast_safe` with vector/array operands. That is because
it can just reuse the existing scalarizations for regular `bit_cast`.
The safety check mandated by `bit_cast_safe` is only desired if the result
type is a scalar enum tag value, so the scalarization process is exactly
equivalent. The only difference is that we have to perform a safety check
on the result of the scalarization.
This means that there's no reason for a backend to want scalarization of
regular `bit_cast`, but not of `bit_cast_safe` (or the other way round).
If a backend is having trouble with the `bit_cast_safe` safety check it
should just use `expand_bit_cast_safe` instead.
Also adds a `std.meta.BackingInt()` function to conveniently get the result
type of `@backingInt` and for use in the langref.
---
doc/langref.html.in | 76 +++-
doc/langref/runtime_invalid_enum_cast.zig | 12 +-
.../test_comptime_invalid_enum_cast.zig | 4 +-
doc/langref/test_enums.zig | 22 +-
doc/langref/test_inline_else.zig | 4 +-
.../test_packed_struct_backing_int.zig | 21 +
.../test_tagged_union_with_tag_values.zig | 4 +-
lib/std/meta.zig | 32 +-
lib/std/zig/AstGen.zig | 18 +
lib/std/zig/AstRlAnnotate.zig | 2 +
lib/std/zig/BuiltinFn.zig | 16 +
lib/std/zig/Zir.zig | 30 +-
src/Air.zig | 6 +
src/Air/Legalize.zig | 114 +++++-
src/Air/Liveness.zig | 1 +
src/Air/Liveness/Verify.zig | 1 +
src/Air/Verify.zig | 2 +-
src/Air/print.zig | 1 +
src/InternPool.zig | 1 +
src/Sema.zig | 320 +++++++++++----
src/Sema/reinterpret.zig | 8 +-
src/Sema/type_resolution.zig | 2 +-
src/Type.zig | 54 +--
src/Value.zig | 17 +-
src/Zcu/PerThread.zig | 35 +-
src/codegen.zig | 2 +-
src/codegen/aarch64.zig | 6 +-
src/codegen/aarch64/Select.zig | 2 +
src/codegen/c.zig | 6 +-
src/codegen/c/type.zig | 3 +-
src/codegen/c/type/render_defs.zig | 4 +-
src/codegen/llvm.zig | 2 +-
src/codegen/llvm/FuncGen.zig | 29 +-
src/codegen/riscv64/CodeGen.zig | 5 +-
src/codegen/sparc64/CodeGen.zig | 3 +-
src/codegen/spirv/CodeGen.zig | 21 +-
src/codegen/wasm/CodeGen.zig | 8 +-
src/codegen/x86_64/CodeGen.zig | 2 +
src/link/Wasm/Flush.zig | 2 +-
src/print_value.zig | 30 +-
src/print_zir.zig | 14 +
test/behavior.zig | 1 +
test/behavior/backing_int.zig | 378 ++++++++++++++++++
test/behavior/enum.zig | 15 +
test/behavior/packed-struct.zig | 31 +-
test/behavior/packed-union.zig | 14 +
test/behavior/switch.zig | 4 +-
test/behavior/type.zig | 12 +-
.../backing_int_invalid_arg_type.zig | 38 ++
...itCast_with_invalid_array_element_type.zig | 23 --
.../compile_errors/bitcast_invalid_types.zig | 205 ++++++++++
.../bitcast_to_enum_invalid_tag_value.zig | 16 +
.../empty_enum_from_backing_int.zig | 18 +
.../from_backing_int_invalid_dest_type.zig | 37 ++
.../from_backing_int_type_mismatch.zig | 23 ++
.../compile_errors/from_backing_int_undef.zig | 22 +
.../invalid_non-exhaustive_enum_to_union.zig | 2 +-
...n_invalid_value_of_non-exhaustive_enum.zig | 2 +-
.../backing_int_no_matching_tag_value.zig | 25 ++
.../bitcast_to_enum_no_matching_tag_value.zig | 25 ++
60 files changed, 1578 insertions(+), 255 deletions(-)
create mode 100644 doc/langref/test_packed_struct_backing_int.zig
create mode 100644 test/behavior/backing_int.zig
create mode 100644 test/cases/compile_errors/backing_int_invalid_arg_type.zig
delete mode 100644 test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig
create mode 100644 test/cases/compile_errors/bitcast_invalid_types.zig
create mode 100644 test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig
create mode 100644 test/cases/compile_errors/empty_enum_from_backing_int.zig
create mode 100644 test/cases/compile_errors/from_backing_int_invalid_dest_type.zig
create mode 100644 test/cases/compile_errors/from_backing_int_type_mismatch.zig
create mode 100644 test/cases/compile_errors/from_backing_int_undef.zig
create mode 100644 test/cases/safety/backing_int_no_matching_tag_value.zig
create mode 100644 test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 7930468882d7eeae945dfeb77f62ac74dcf0e16a..9be898fc0499bc091f9020bd053ad3f4a2a1e851 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -2271,6 +2271,7 @@ or
This even works at {#link|comptime#}:
{#code|test_packed_structs.zig#}
+
The backing integer can be inferred or explicitly provided. When
inferred, it will be unsigned. When explicitly provided, its bit width
@@ -2279,6 +2280,12 @@ or
{#code|test_missized_packed_struct.zig#}
+
+ A {#syntax#}packed struct{#endsyntax#} can be converted to and from its backing
+ integer using {#link|@backingInt#} and {#link|@fromBackingInt#}:
+
+ {#code|test_packed_struct_backing_int.zig#}
+
Zig allows the address to be taken of a non-byte-aligned field:
@@ -2401,7 +2408,7 @@ or
{#header_open|enum#}
{#code|test_enums.zig#}
- {#see_also|@typeInfo|@tagName|@sizeOf|noreturn#}
+ {#see_also|@backingInt|@fromBackingInt|@typeInfo|@tagName|@sizeOf|noreturn#}
{#header_open|extern enum#}
@@ -2431,9 +2438,7 @@ or
The enum must specify a tag type and cannot consume every enumeration value.
- {#link|@enumFromInt#} on a non-exhaustive enum involves the safety semantics
- of {#link|@intCast#} to the integer tag type, but beyond that always results in
- a well-defined enum value.
+ {#link|@fromBackingInt#} on a non-exhaustive enum always results in a valid enum value.
A switch on a non-exhaustive enum can include a {#syntax#}_{#endsyntax#} prong as an alternative to an {#syntax#}else{#endsyntax#} prong.
@@ -2461,7 +2466,8 @@ or
{#code|test_simple_union.zig#}
- In order to use {#link|switch#} with a union, it must be a {#link|Tagged union#}.
+ In order to use {#link|switch#} with a union, it must be a {#link|tagged union|Tagged union#}
+ or a {#link|packed union#}.
To initialize a union when the tag is a {#link|comptime#}-known name, see {#link|@unionInit#}.
@@ -2490,7 +2496,7 @@ or
Unions with inferred enum tag types can also assign ordinal values to their inferred tag.
This requires the tag to specify an explicit integer type.
- {#link|@intFromEnum#} can be used to access the ordinal value corresponding to the active field.
+ {#link|@backingInt#} can be used to access the ordinal value corresponding to the active field.
{#code|test_tagged_union_with_tag_values.zig#}
@@ -2521,6 +2527,7 @@ or
{#code|test_packed_union_equality.zig#}
+ {#see_also|@backingInt|@fromBackingInt#}
{#header_close#}
{#header_open|Anonymous Union Literals#}
@@ -3586,14 +3593,14 @@ void do_a_thing(struct Foo *foo) {
- {#link|@bitCast#} - change type but maintain bit representation
- {#link|@alignCast#} - make a pointer have more alignment
- - {#link|@enumFromInt#} - obtain an enum value based on its integer tag value
+ - {#link|@fromBackingInt#} - obtain an enum or a packed struct/union value based on its backing integer
- {#link|@errorFromInt#} - obtain an error code based on its integer value
- {#link|@errorCast#} - convert to a smaller error set
- {#link|@floatCast#} - convert a larger float to a smaller float
- {#link|@floatFromInt#} - convert an integer to a float value
- {#link|@intCast#} - convert between integer types
- {#link|@intFromBool#} - convert true to 1 and false to 0
- - {#link|@intFromEnum#} - obtain the integer tag value of an enum or tagged union
+ - {#link|@backingInt#} - obtain the backing integer value of an enum or a packed struct/union
- {#link|@intFromError#} - obtain the integer value of an error code
- {#link|@round#}, {#link|@floor#}, {#link|@ceil#}, {#link|@trunc#} - float to integer conversion
- {#link|@intFromPtr#} - obtain the address of a pointer
@@ -4432,6 +4439,18 @@ comptime {
{#see_also|@atomicLoad|@atomicRmw|@cmpxchgWeak|@cmpxchgStrong#}
{#header_close#}
+ {#header_open|@backingInt#}
+
{#syntax#}@backingInt(enum_or_bitpack: T) BackingInt(T){#endsyntax#}
+
+ Converts an {#link|enum#}, a {#link|packed struct#} or a {#link|packed union#} value
+ to its backing integer.
+
+
+ Also works with {#link|tagged unions|Tagged union#}, acting on the active enum tag value.
+
+ {#see_also|@fromBackingInt|@bitCast#}
+ {#header_close#}
+
{#header_open|@bitCast#}
{#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}
@@ -4452,8 +4471,18 @@ comptime {
Convert {#syntax#}i32{#endsyntax#} to {#syntax#}u32{#endsyntax#} preserving twos complement
- Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation.
+ Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time.
+ It's a compile error to bitcast a value of undefined layout; this means that,
+ besides the restriction from types which possess dedicated casting builtins
+ (pointers, error sets), bare structs, error unions, slices, optionals, and any
+ other type without a well-defined memory layout, also cannot be used in this
+ operation.
+
+ Attempting to convert an integer with no corresponding tag value to an
+ {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}.
+
+ {#see_also|@ptrCast|@intFromPtr|@ptrFromInt|@errorCast|@intFromError|@errorFromInt|@backingInt|@fromBackingInt#}
{#header_close#}
{#header_open|@bitOffsetOf#}
@@ -4798,6 +4827,9 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#header_open|@enumFromInt#}
{#syntax#}@enumFromInt(integer: anytype) anytype{#endsyntax#}
+ Deprecated. Use {#link|@fromBackingInt#} or {#link|@bitCast#} instead.
+
+
Converts an integer into an {#link|enum#} value. The return type is the inferred result type.
@@ -4953,6 +4985,22 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#header_close#}
+ {#header_open|@fromBackingInt#}
+ {#syntax#}@fromBackingInt(backing_int: BackingInt(T)) T{#endsyntax#}
+
+ Converts an integer into a {#link|enum#}, a {#link|packed struct#} or a
+ {#link|packed union#} value. The return type is the inferred result type.
+
+
+ Attempting to convert an integer with no corresponding tag value to an
+ {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}.
+ Note that a {#link|non-exhaustive enum|Non-exhaustive enum#} has corresponding values for
+ all integers in the enum's integer tag type: the {#syntax#}_{#endsyntax#} value represents
+ all the remaining unnamed integers in the enum's tag type.
+
+ {#see_also|@backingInt|@bitCast#}
+ {#header_close#}
+
{#header_open|@hasDecl#}
{#syntax#}@hasDecl(comptime Namespace: type, comptime name: []const u8) bool{#endsyntax#}
Returns whether or not a {#link|Namespace#} has a declaration matching {#syntax#}name{#endsyntax#}.
@@ -5035,13 +5083,12 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#header_open|@intFromEnum#}
{#syntax#}@intFromEnum(enum_or_tagged_union: anytype) anytype{#endsyntax#}
+ Deprecated. Use {#link|@backingInt#} or {#link|@bitCast#} instead.
+
+
Converts an enumeration value into its integer tag type. When a tagged union is passed,
the tag value is used as the enumeration value.
-
- If there is only one possible enum value, the result is a {#syntax#}comptime_int{#endsyntax#}
- known at {#link|comptime#}.
-
{#see_also|@enumFromInt#}
{#header_close#}
@@ -5077,7 +5124,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
Converts {#syntax#}value{#endsyntax#} to a {#syntax#}usize{#endsyntax#} which is the address of the pointer.
{#syntax#}value{#endsyntax#} can be {#syntax#}*T{#endsyntax#} or {#syntax#}?*T{#endsyntax#}.
- To convert the other way, use {#link|@ptrFromInt#}
+ {#see_also|@ptrFromInt#}
{#header_close#}
{#header_open|@max#}
@@ -5281,6 +5328,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
If the destination pointer type does not allow address zero and {#syntax#}address{#endsyntax#}
is zero, this invokes safety-checked {#link|Illegal Behavior#}.
+ {#see_also|@intFromPtr#}
{#header_close#}
{#header_open|@rem#}
diff --git a/doc/langref/runtime_invalid_enum_cast.zig b/doc/langref/runtime_invalid_enum_cast.zig
index f4a44645d55a38fe5d9e8dac4a7abdc2e9877b9e..6d3c48febf69800674da4aa237a365aa7af45406 100644
--- a/doc/langref/runtime_invalid_enum_cast.zig
+++ b/doc/langref/runtime_invalid_enum_cast.zig
@@ -1,16 +1,18 @@
const std = @import("std");
-const Foo = enum {
+const Foo = enum(u2) {
a,
b,
c,
};
-pub fn main() void {
- var a: u2 = 3;
- _ = &a;
- const b: Foo = @enumFromInt(a);
+fn foo(a: u2) void {
+ const b: Foo = @fromBackingInt(a);
std.debug.print("value: {s}\n", .{@tagName(b)});
}
+pub fn main() void {
+ foo(3);
+}
+
// exe=fail
diff --git a/doc/langref/test_comptime_invalid_enum_cast.zig b/doc/langref/test_comptime_invalid_enum_cast.zig
index 8371091fe1f26bddb2d90e699c65ae65a3f7aa9e..6e3471eed2aa30bca7e6fc3144c551a123cb575d 100644
--- a/doc/langref/test_comptime_invalid_enum_cast.zig
+++ b/doc/langref/test_comptime_invalid_enum_cast.zig
@@ -1,11 +1,11 @@
-const Foo = enum {
+const Foo = enum(u2) {
a,
b,
c,
};
comptime {
const a: u2 = 3;
- const b: Foo = @enumFromInt(a);
+ const b: Foo = @fromBackingInt(a);
_ = b;
}
diff --git a/doc/langref/test_enums.zig b/doc/langref/test_enums.zig
index 9da7c160edcabd10a2e8fc07a6e7e480edaffb2c..10c1a7264c9a0b859900ee782715188f34198714 100644
--- a/doc/langref/test_enums.zig
+++ b/doc/langref/test_enums.zig
@@ -22,9 +22,9 @@ const Value = enum(u2) {
// Now you can cast between u2 and Value.
// The ordinal value starts from 0, counting up by 1 from the previous member.
test "enum ordinal value" {
- try expectEqual(0, @intFromEnum(Value.zero));
- try expectEqual(1, @intFromEnum(Value.one));
- try expectEqual(2, @intFromEnum(Value.two));
+ try expectEqual(0, @backingInt(Value.zero));
+ try expectEqual(1, @backingInt(Value.one));
+ try expectEqual(2, @backingInt(Value.two));
}
// You can override the ordinal value for an enum.
@@ -34,9 +34,9 @@ const Value2 = enum(u32) {
million = 1000000,
};
test "set enum ordinal value" {
- try expectEqual(100, @intFromEnum(Value2.hundred));
- try expectEqual(1000, @intFromEnum(Value2.thousand));
- try expectEqual(1000000, @intFromEnum(Value2.million));
+ try expectEqual(100, @backingInt(Value2.hundred));
+ try expectEqual(1000, @backingInt(Value2.thousand));
+ try expectEqual(1000000, @backingInt(Value2.million));
}
// You can also override only some values.
@@ -48,11 +48,11 @@ const Value3 = enum(u4) {
e,
};
test "enum implicit ordinal values and overridden values" {
- try expectEqual(0, @intFromEnum(Value3.a));
- try expectEqual(8, @intFromEnum(Value3.b));
- try expectEqual(9, @intFromEnum(Value3.c));
- try expectEqual(4, @intFromEnum(Value3.d));
- try expectEqual(5, @intFromEnum(Value3.e));
+ try expectEqual(0, @backingInt(Value3.a));
+ try expectEqual(8, @backingInt(Value3.b));
+ try expectEqual(9, @backingInt(Value3.c));
+ try expectEqual(4, @backingInt(Value3.d));
+ try expectEqual(5, @backingInt(Value3.e));
}
// Enums can have methods, the same as structs and unions.
diff --git a/doc/langref/test_inline_else.zig b/doc/langref/test_inline_else.zig
index c500bd685d3dc192f9273bdd59091a38e0b08501..5bfdca6c426d807964657a04964468b96cb59fa7 100644
--- a/doc/langref/test_inline_else.zig
+++ b/doc/langref/test_inline_else.zig
@@ -9,7 +9,7 @@ const SliceTypeB = extern struct {
ptr: [*]SliceTypeA,
len: usize,
};
-const AnySlice = union(enum) {
+const AnySlice = union(enum(u8)) {
a: SliceTypeA,
b: SliceTypeB,
c: []const u8,
@@ -23,7 +23,7 @@ fn withFor(any: AnySlice) usize {
// With `inline for` the function gets generated as
// a series of `if` statements relying on the optimizer
// to convert it to a switch.
- if (field_value == @intFromEnum(any)) {
+ if (field_value == @backingInt(any)) {
return @field(any, field_name).len;
}
}
diff --git a/doc/langref/test_packed_struct_backing_int.zig b/doc/langref/test_packed_struct_backing_int.zig
new file mode 100644
index 0000000000000000000000000000000000000000..434abb0c4ee78e8826294c8f485d0be19d5d30df
--- /dev/null
+++ b/doc/langref/test_packed_struct_backing_int.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const expectEqual = std.testing.expectEqual;
+
+const PackedStruct = packed struct(u8) {
+ lo: u4,
+ hi: u4,
+};
+
+test "convert to and from backing integer" {
+ const original: PackedStruct = .{ .lo = 0b1100, .hi = 0b0101 };
+
+ const backing_int = @backingInt(original);
+ comptime assert(@TypeOf(backing_int) == u8);
+ try expectEqual(0b0101_1100, backing_int);
+
+ const reconstructed: PackedStruct = @fromBackingInt(backing_int);
+ try expectEqual(original, reconstructed);
+}
+
+// test
diff --git a/doc/langref/test_tagged_union_with_tag_values.zig b/doc/langref/test_tagged_union_with_tag_values.zig
index 229a31d6780b213a76cd9d895a6ed838de777f0d..b18db3e81ec0658b78cf23da310c8f3680b80891 100644
--- a/doc/langref/test_tagged_union_with_tag_values.zig
+++ b/doc/langref/test_tagged_union_with_tag_values.zig
@@ -8,10 +8,10 @@ const Tagged = union(enum(u32)) {
test "tag values" {
const int: Tagged = .{ .int = -40 };
- try expectEqual(123, @intFromEnum(int));
+ try expectEqual(123, @backingInt(int));
const boolean: Tagged = .{ .boolean = false };
- try expectEqual(67, @intFromEnum(boolean));
+ try expectEqual(67, @backingInt(boolean));
}
// test
diff --git a/lib/std/meta.zig b/lib/std/meta.zig
index 78e26d6c8f4f99dd04dcf0dcf3e79e8ae14e20d2..e891e4320d5bdc9faa01be96cd8fd1ba1d8b2165 100644
--- a/lib/std/meta.zig
+++ b/lib/std/meta.zig
@@ -506,10 +506,40 @@ pub fn BareUnion(comptime T: type) type {
.@"union" => |u| u,
else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"),
};
-
return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
}
+/// For enums, packed unions and packed structs, returns their backing integer type.
+/// For tagged unions, returns the backing integer type of their enum tag type.
+pub fn BackingInt(comptime T: type) type {
+ switch (@typeInfo(T)) {
+ .@"enum" => |info| return info.tag_type,
+ .@"struct" => |info| if (info.backing_integer) |Int| return Int,
+ .@"union" => |info| switch (info.layout) {
+ .@"packed" => return info.backing_integer.?,
+ .auto => if (info.tag_type) |EnumTag|
+ return @typeInfo(EnumTag).@"enum".tag_type,
+ .@"extern" => {},
+ },
+ else => {},
+ }
+ @compileError("expected enum, tagged union, packed union or packed struct type, found '" ++ @typeName(T) ++ "'");
+}
+
+test BackingInt {
+ const E = enum(u8) { a, b, c };
+ try testing.expect(BackingInt(E) == u8);
+
+ const S = packed struct(u16) { x: u8, y: i8 };
+ try testing.expect(BackingInt(S) == u16);
+
+ const U = packed union(i32) { a: u32, b: enum(i32) { _ } };
+ try testing.expect(BackingInt(U) == i32);
+
+ const T = union(enum(i8)) { a, b, c };
+ try testing.expect(BackingInt(T) == i8);
+}
+
pub fn Tag(comptime T: type) type {
return switch (@typeInfo(T)) {
.@"enum" => |info| info.tag_type,
diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig
index 16e0616ede4ac141162b054a48a36694f12e222f..1b158188c832b05ee3027df3d31fde60268e14df 100644
--- a/lib/std/zig/AstGen.zig
+++ b/lib/std/zig/AstGen.zig
@@ -2703,6 +2703,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.elem_type,
.indexable_ptr_elem_type,
.splat_op_result_ty,
+ .from_backing_int_arg_ty,
.reify_int,
.vector_type,
.indexable_ptr_len,
@@ -2803,6 +2804,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.error_set_decl,
.enum_from_int,
.int_from_enum,
+ .backing_int,
+ .from_backing_int,
.type_info,
.size_of,
.bit_size_of,
@@ -9168,6 +9171,7 @@ fn builtinCall(
.set_eval_branch_quota => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .u32_type } }, params[0], .set_eval_branch_quota),
.int_from_enum => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .int_from_enum),
.int_from_bool => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .int_from_bool),
+ .backing_int => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .backing_int),
.embed_file => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[0], .embed_file),
.error_name => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .anyerror_type } }, params[0], .error_name),
.set_runtime_safety => return simpleUnOp(gz, scope, ri, node, coerced_bool_ri, params[0], .set_runtime_safety),
@@ -9199,6 +9203,20 @@ fn builtinCall(
.truncate => return typeCast(gz, scope, ri, node, params[0], .truncate, builtin_name),
// zig fmt: on
+ .from_backing_int => {
+ const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
+ const result_ty = try ri.rl.resultTypeForCast(gz, node, builtin_name);
+ const backing_int_ty = try gz.addUnNode(.from_backing_int_arg_ty, result_ty, node);
+ const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = backing_int_ty } }, params[0]);
+
+ try emitDbgStmt(gz, cursor);
+ const result = try gz.addPlNode(.from_backing_int, node, Zir.Inst.Bin{
+ .lhs = result_ty,
+ .rhs = operand,
+ });
+ return rvalue(gz, ri, result, node);
+ },
+
.in_comptime => if (gz.is_comptime) {
return astgen.failNode(node, "redundant '@inComptime' in comptime scope", .{});
} else {
diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig
index c42528e3ee2cf8d91509e86fd4a444994027be81..4e010dee5a66e96daec28d6ad641970455ac16fa 100644
--- a/lib/std/zig/AstRlAnnotate.zig
+++ b/lib/std/zig/AstRlAnnotate.zig
@@ -889,6 +889,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
.int_from_bool,
.int_from_error,
.error_from_int,
+ .from_backing_int,
.embed_file,
.error_name,
.set_runtime_safety,
@@ -916,6 +917,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
.float_from_int,
.ptr_from_int,
.enum_from_int,
+ .backing_int,
.float_cast,
.int_cast,
.truncate,
diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig
index 7ff834487ce43f5700e96de0f58b5f12664343ac..99ac5ce5d6c7d58506895a2b66cc42871adac8cb 100644
--- a/lib/std/zig/BuiltinFn.zig
+++ b/lib/std/zig/BuiltinFn.zig
@@ -56,6 +56,8 @@ pub const Tag = enum {
import,
in_comptime,
int_cast,
+ backing_int,
+ from_backing_int,
enum_from_int,
error_from_int,
float_from_int,
@@ -564,6 +566,20 @@ pub const list = list: {
.param_count = 1,
},
},
+ .{
+ "@backingInt",
+ .{
+ .tag = .backing_int,
+ .param_count = 1,
+ },
+ },
+ .{
+ "@fromBackingInt",
+ .{
+ .tag = .from_backing_int,
+ .param_count = 1,
+ },
+ },
.{
"@enumFromInt",
.{
diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig
index 6e14e5ee9096b1e6ef9f190d6cb5dfd7e23de5a0..f118e24a216eed245c10faf6e36a16958937d83a 100644
--- a/lib/std/zig/Zir.zig
+++ b/lib/std/zig/Zir.zig
@@ -283,6 +283,14 @@ pub const Inst = struct {
///
/// Uses the `un_node` field.
splat_op_result_ty,
+ /// Given a type, strips away any error unions or optionals stacked
+ /// on top, validates it for usage with `@fromBackingInt` and returns
+ /// its backing integer type.
+ ///
+ /// `E!?enum(T) { _ }` -> `T`
+ ///
+ /// Uses the `un_node` field.
+ from_backing_int_arg_ty,
/// Given a pointer to an indexable object, returns the len property. This is
/// used by for loops. This instruction also emits a for-loop specific compile
/// error if the indexable object is not indexable.
@@ -872,6 +880,9 @@ pub const Inst = struct {
/// Converts an enum value into an integer. Resulting type will be the tag type
/// of the enum. Uses `un_node`.
int_from_enum,
+ /// Implements the `@backingInt` builtin.
+ /// Uses `un_node`.
+ backing_int,
/// Implement builtin `@alignOf`. Uses `un_node`.
align_of,
/// Implement builtin `@intFromBool`. Uses `un_node`.
@@ -934,6 +945,9 @@ pub const Inst = struct {
/// Converts an integer into an enum value.
/// Uses `pl_node` with payload `Bin`. `lhs` is dest type, `rhs` is operand.
enum_from_int,
+ /// Implements the `@fromBackingInt` builtin.
+ /// Uses `pl_node` with payload `Bin`. `lhs` is dest type, `rhs` is operand.
+ from_backing_int,
/// Convert a larger float type to any other float type, possibly causing
/// a loss of precision.
/// Uses the `pl_node` field. AST is the `@floatCast` syntax.
@@ -1115,6 +1129,7 @@ pub const Inst = struct {
.elem_type,
.indexable_ptr_elem_type,
.splat_op_result_ty,
+ .from_backing_int_arg_ty,
.indexable_ptr_len,
.anyframe_type,
.as_node,
@@ -1229,6 +1244,8 @@ pub const Inst = struct {
.field_type_ref,
.enum_from_int,
.int_from_enum,
+ .backing_int,
+ .from_backing_int,
.type_info,
.size_of,
.bit_size_of,
@@ -1409,6 +1426,7 @@ pub const Inst = struct {
.elem_type,
.indexable_ptr_elem_type,
.splat_op_result_ty,
+ .from_backing_int_arg_ty,
.indexable_ptr_len,
.anyframe_type,
.as_node,
@@ -1511,6 +1529,8 @@ pub const Inst = struct {
.field_type_ref,
.enum_from_int,
.int_from_enum,
+ .backing_int,
+ .from_backing_int,
.type_info,
.size_of,
.bit_size_of,
@@ -1645,6 +1665,7 @@ pub const Inst = struct {
.elem_type = .un_node,
.indexable_ptr_elem_type = .un_node,
.splat_op_result_ty = .un_node,
+ .from_backing_int_arg_ty = .un_node,
.indexable_ptr_len = .un_node,
.anyframe_type = .un_node,
.as_node = .pl_node,
@@ -1774,6 +1795,7 @@ pub const Inst = struct {
.compile_error = .un_node,
.set_eval_branch_quota = .un_node,
.int_from_enum = .un_node,
+ .backing_int = .un_node,
.align_of = .un_node,
.int_from_bool = .un_node,
.embed_file = .un_node,
@@ -1803,6 +1825,7 @@ pub const Inst = struct {
.float_from_int = .pl_node,
.ptr_from_int = .pl_node,
.enum_from_int = .pl_node,
+ .from_backing_int = .pl_node,
.float_cast = .pl_node,
.int_cast = .pl_node,
.ptr_cast = .pl_node,
@@ -2160,8 +2183,8 @@ pub const Inst = struct {
astgen_error,
/// Given a type, strips away any error unions or optionals stacked
/// on top and returns the base type. That base type must be a float.
- /// For example: Provided with error{Foo}!?f64, returns f64.
- /// `operand` is `operand: Air.Inst.Ref`.
+ /// For example: Provided with `error{Foo}!?f64`, returns `f64`.
+ /// `operand` is payload index to `UnNode`.
float_op_result_ty,
pub const InstData = struct {
@@ -4141,6 +4164,7 @@ fn findTrackableInner(
.elem_type,
.indexable_ptr_elem_type,
.splat_op_result_ty,
+ .from_backing_int_arg_ty,
.indexable_ptr_len,
.anyframe_type,
.as_node,
@@ -4296,6 +4320,8 @@ fn findTrackableInner(
.float_from_int,
.ptr_from_int,
.enum_from_int,
+ .backing_int,
+ .from_backing_int,
.float_cast,
.int_cast,
.ptr_cast,
diff --git a/src/Air.zig b/src/Air.zig
index cdae23d2f5cf1b4ff470ee5f4bb0584b07e26a52..141ed8da170572cc6e27d7cd5bd6f5111c756b2e 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -288,6 +288,10 @@ pub const Inst = struct {
///
/// Uses the `ty_op` field.
bit_cast,
+ /// Like `bit_cast`, but triggers a safety panic if the destination type is an exhaustive
+ /// enum and the operand is not a valid value of this type;
+ /// i.e. equivalent to a safety check based on `.is_named_enum_value`
+ bit_cast_safe,
/// Cast a pointer to a different pointer type. The result type is a slice iff the operand
/// type is a slice (the length of the slice does not change). All other pointer attributes
/// except for the address space may change.
@@ -1717,6 +1721,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.not,
.bit_cast,
+ .bit_cast_safe,
.ptr_cast,
.ptr_from_int,
.int_from_ptr,
@@ -1969,6 +1974,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.add_safe,
.sub_safe,
.mul_safe,
+ .bit_cast_safe,
.int_cast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig
index 81df10c169d0f358adc769d0681ce27bf45e6484..bd4f88158161b993768e5af3fb8369d5d49e422f 100644
--- a/src/Air/Legalize.zig
+++ b/src/Air/Legalize.zig
@@ -156,6 +156,10 @@ pub const Feature = enum {
/// Legalize splat to a one element vector to a bitcast.
splat_one_elem_to_bit_cast,
+ /// Replace `bit_cast_safe` with an explicit safety check which `call`s the panic function on failure.
+ /// `scalarize_*` variants for `bit_cast_safe` do not exist since the safety check is only desired if the result
+ /// type is a scalar enum type, so the scalarizatins for regular `bit_cast` are exactly equivalent.
+ expand_bit_cast_safe,
/// Replace `int_cast_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_int_cast_safe`.
expand_int_cast_safe,
@@ -600,6 +604,17 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
continue :inst l.replaceInst(inst, .block, payload);
}
},
+ .bit_cast_safe => if (l.features.has(.expand_bit_cast_safe)) {
+ continue :inst l.replaceInst(inst, .block, try l.safeBitcastBlockPayload(inst));
+ } else if (l.features.hasAny(&.{
+ .scalarize_bit_cast_array,
+ .scalarize_bit_cast_vector_non_elementwise,
+ .scalarize_bit_cast_padded_elems,
+ })) {
+ if (try l.scalarizeBitcastBlockPayload(inst)) |payload| {
+ continue :inst l.replaceInst(inst, .block, payload);
+ }
+ },
.int_cast_safe => if (l.features.has(.expand_int_cast_safe)) {
assert(!l.features.has(.scalarize_int_cast_safe)); // it doesn't make sense to do both
continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
@@ -1006,7 +1021,10 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, form: Scalariz
if (result_is_array) {
// This is only allowed when legalizing an elementwise bitcast.
- assert(orig.tag == .bit_cast);
+ switch (orig.tag) {
+ .bit_cast, .bit_cast_safe => {},
+ else => unreachable,
+ }
assert(form == .ty_op);
}
@@ -1076,7 +1094,11 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, form: Scalariz
orig_operand,
index_val,
).toRef();
- break :elem loop.block.addTyOp(l, orig.tag, res_elem_ty, operand).toRef();
+ const scalar_tag: Air.Inst.Tag = switch (orig.tag) {
+ .bit_cast_safe => .bit_cast, // safety check is not supposed to be elementwise
+ else => orig.tag,
+ };
+ break :elem loop.block.addTyOp(l, scalar_tag, res_elem_ty, operand).toRef();
},
.bin_op => elem: {
const orig_bin = orig.data.bin_op;
@@ -1700,9 +1722,23 @@ fn scalarizeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!?
// Now convert `uint_ty` (`uN`) to `dest_ty`.
+ // We omit the safety check when casting to an array or a vector since it's
+ // not supposed to be elementwise.
+ if (dest_ty.zigTypeTag(zcu) == .@"enum") assert(int_to_dest_ok);
+
if (int_to_dest_ok) {
_ = main_block.stealCapacity(17);
- const result = main_block.addBitCast(l, dest_ty, uint_val);
+ const result = switch (l.air_instructions.items(.tag)[@intFromEnum(orig_inst)]) {
+ .bit_cast => main_block.addBitCast(l, dest_ty, uint_val),
+ .bit_cast_safe => main_block.add(l, .{
+ .tag = .bit_cast_safe,
+ .data = .{ .ty_op = .{
+ .ty = .fromType(dest_ty),
+ .operand = uint_val,
+ } },
+ }).toRef(),
+ else => unreachable,
+ };
main_block.addBr(l, orig_inst, result);
} else if (dest_ty.arrayLenIncludingSentinel(zcu) == 1) {
_ = main_block.stealCapacity(16);
@@ -2089,6 +2125,76 @@ fn scalarizeReduceBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimize
} };
}
+fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
+ const pt = l.pt;
+ const zcu = pt.zcu;
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
+
+ const operand_ref = ty_op.operand;
+ const dest_ty = ty_op.ty.toType();
+
+ // The worst case is a bitcast to an exhaustive enum and looks like this:
+ //
+ // %x = block({
+ // %1 = bit_cast(@res_ty, %y)
+ // %2 = is_named_enum_value(%1)
+ // %3 = cond_br(%2, {
+ // %4 = br(%x, %1)
+ // }, {
+ // %5 = call(@panic.invalidEnumValue, [])
+ // %6 = unreach()
+ // })
+ // })
+
+ var inst_buf: [6]Air.Inst.Index = undefined;
+ try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
+ var opt_condbr: ?CondBr = null;
+
+ var main_block: Block = .init(&inst_buf);
+ var cur_block: *Block = &main_block;
+
+ const cast_inst = cur_block.addBitCast(l, dest_ty, operand_ref);
+
+ if (dest_ty.zigTypeTag(zcu) == .@"enum" and
+ !dest_ty.isNonexhaustiveEnum(zcu) and
+ zcu.backendSupportsFeature(.is_named_enum_value))
+ {
+ // We are building this:
+ // %1 = is_named_enum_value(%cast_inst)
+ // %2 = cond_br(%1, {
+ //
+ // }, {
+ //
+ // })
+ const is_named_inst = cur_block.add(l, .{
+ .tag = .is_named_enum_value,
+ .data = .{ .un_op = cast_inst },
+ });
+ opt_condbr = .init(l, is_named_inst.toRef(), cur_block, .{ .false = .cold });
+ const condbr = &(opt_condbr.?);
+ condbr.else_block = .init(cur_block.stealRemainingCapacity());
+ try condbr.else_block.addPanic(l, .invalid_enum_value);
+ condbr.then_block = .init(condbr.else_block.stealRemainingCapacity());
+ cur_block = &condbr.then_block;
+ }
+ // Finally, just `br` to our outer `block`.
+ _ = cur_block.add(l, .{
+ .tag = .br,
+ .data = .{ .br = .{
+ .block_inst = orig_inst,
+ .operand = cast_inst,
+ } },
+ });
+ // We might not have used all of the instructions; that's intentional.
+ _ = cur_block.stealRemainingCapacity();
+
+ if (opt_condbr) |condbr| try condbr.finish(l);
+ return .{ .ty_pl = .{
+ .ty = .fromType(dest_ty),
+ .payload = try l.addBlockBody(main_block.body()),
+ } };
+}
+
fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
@@ -2172,7 +2278,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds;
if (have_min_check or have_max_check) {
- const dest_int_ty = if (dest_is_enum) dest_ty.intTagType(zcu) else dest_ty;
+ const dest_int_ty = if (dest_is_enum) dest_ty.backingIntType(zcu) else dest_ty;
const condbr = &condbr_buf[condbr_idx];
condbr_idx += 1;
const below_min_inst: Air.Inst.Index = if (have_min_check) inst: {
diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig
index fbfd74a772c0ec3028b56b22201d33d8895d166b..fcdf0017cee39cbfe6836f3e6fe33d550ee77726 100644
--- a/src/Air/Liveness.zig
+++ b/src/Air/Liveness.zig
@@ -491,6 +491,7 @@ fn analyzeInst(
.not,
.bit_cast,
+ .bit_cast_safe,
.ptr_cast,
.ptr_from_int,
.int_from_ptr,
diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig
index 24737ebedf6e14446e5d699af845e7c8648f1833..dd7c189bdab31cd31f09969e8d9a697176be33a0 100644
--- a/src/Air/Liveness/Verify.zig
+++ b/src/Air/Liveness/Verify.zig
@@ -79,6 +79,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
// unary
.not,
.bit_cast,
+ .bit_cast_safe,
.ptr_cast,
.ptr_from_int,
.int_from_ptr,
diff --git a/src/Air/Verify.zig b/src/Air/Verify.zig
index b2812f8660d136ce40a4b088b5c30b3bb138e385..b3f9f002295e671b24c7e4f0558012e42384d933 100644
--- a/src/Air/Verify.zig
+++ b/src/Air/Verify.zig
@@ -118,7 +118,7 @@ fn body(verify: *Verify, body_insts: []const Air.Inst.Index) Error!void {
if (ptr_ty.childType(zcu).toIntern() != verify.ret_ty.toIntern()) return verify.fail("bad return type");
},
- .bit_cast => {
+ .bit_cast, .bit_cast_safe => {
const ty_op = data[@intFromEnum(inst)].ty_op;
const operand_ty = air.typeOf(ty_op.operand, ip);
const result_ty = ty_op.ty.toType();
diff --git a/src/Air/print.zig b/src/Air/print.zig
index b263c20634f3ea5a0442e6fa14af24ef77b9b64e..eccc5619967b588ccc87f54cb35f7fe4e87f78e7 100644
--- a/src/Air/print.zig
+++ b/src/Air/print.zig
@@ -231,6 +231,7 @@ const Writer = struct {
.not,
.bit_cast,
+ .bit_cast_safe,
.ptr_cast,
.ptr_from_int,
.int_from_ptr,
diff --git a/src/InternPool.zig b/src/InternPool.zig
index 632bd75ca0f5ec2b1198f4bd8fac0bdc76fdbe0b..bb68c6c5a07161773911af2c740b6ff68d5cfa72 100644
--- a/src/InternPool.zig
+++ b/src/InternPool.zig
@@ -10192,6 +10192,7 @@ pub fn getCoerced(
.enum_type => {
const enum_type = ip.loadEnumType(new_ty);
const index = enum_type.nameIndex(ip, enum_literal).?;
+ assert(enum_type.int_tag_type != .noreturn_type);
return ip.get(gpa, io, tid, .{ .enum_tag = .{
.ty = new_ty,
.int = if (enum_type.field_values.len != 0)
diff --git a/src/Sema.zig b/src/Sema.zig
index 12fc7cb5fc145b79ab9da6f9da193ed78aa1d338..e20dfdc99c595493ab66ebc23265bd75553cac2f 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1191,11 +1191,14 @@ fn analyzeBodyInner(
.elem_type => try sema.zirElemType(block, inst),
.indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst),
.splat_op_result_ty => try sema.zirSplatOpResultType(block, inst),
+ .from_backing_int_arg_ty => try sema.zirFromBackingIntArgTy(block, inst),
.enum_literal => try sema.zirEnumLiteral(block, inst),
.decl_literal => try sema.zirDeclLiteral(block, inst, true),
.decl_literal_no_coerce => try sema.zirDeclLiteral(block, inst, false),
.int_from_enum => try sema.zirIntFromEnum(block, inst),
.enum_from_int => try sema.zirEnumFromInt(block, inst),
+ .backing_int => try sema.zirBackingInt(block, inst),
+ .from_backing_int => try sema.zirFromBackingInt(block, inst),
.err_union_code => try sema.zirErrUnionCode(block, inst),
.err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
.err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst),
@@ -2428,6 +2431,28 @@ fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src:
});
}
+fn failWithAmbiguousBackingIntType(
+ sema: *Sema,
+ block: *Block,
+ src: LazySrcLoc,
+ int_backed_ty: Type,
+ builtin_name: []const u8,
+) CompileError {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(src, "{s} is ambiguous for type '{f}'", .{
+ builtin_name, int_backed_ty.fmt(pt),
+ });
+ errdefer msg.destroy(sema.gpa);
+ try sema.errNote(int_backed_ty.srcLoc(zcu), msg, "backing integer type of {t} is inferred", .{
+ int_backed_ty.zigTypeTag(zcu),
+ });
+ try sema.errNote(int_backed_ty.srcLoc(zcu), msg, "consider explicitly specifying the backing integer type", .{});
+ break :msg msg;
+ });
+}
+
fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
const pt = sema.pt;
const msg = msg: {
@@ -4782,7 +4807,7 @@ fn failWithBadUnionFieldAccess(
return sema.failWithOwnedErrorMsg(block, msg);
}
-pub fn addDeclaredHereNote(sema: *Sema, parent: *Zcu.ErrorMsg, decl_ty: Type) !void {
+pub fn addDeclaredHereNote(sema: *Sema, parent: *Zcu.ErrorMsg, decl_ty: Type) Allocator.Error!void {
const zcu = sema.pt.zcu;
const src_loc = decl_ty.srcLocOrNull(zcu) orelse return;
const category = switch (decl_ty.zigTypeTag(zcu)) {
@@ -7853,12 +7878,12 @@ fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
},
};
const enum_tag_ty = sema.typeOf(enum_tag);
- const int_tag_ty = enum_tag_ty.intTagType(zcu);
+ const int_tag_ty = enum_tag_ty.backingIntType(zcu);
assert(int_tag_ty.classify(zcu) != .no_possible_value);
if (sema.resolveValue(enum_tag)) |enum_tag_val| {
if (enum_tag_val.isUndef(zcu)) return pt.undefRef(int_tag_ty);
- return .fromValue(enum_tag_val.intFromEnum(zcu));
+ return .fromValue(enum_tag_val.backingInt(zcu));
}
try sema.requireRuntimeBlock(block, src, operand_src);
@@ -7884,7 +7909,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (sema.resolveValue(operand)) |int_val| {
if (dest_ty.isNonexhaustiveEnum(zcu)) {
- const int_tag_ty = dest_ty.intTagType(zcu);
+ const int_tag_ty = dest_ty.backingIntType(zcu);
if (int_val.intFitsInType(int_tag_ty, null, zcu)) {
return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
}
@@ -7907,7 +7932,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (block.wantSafety()) {
// The operand is runtime-known but the result is comptime-known. In
// this case we still need a safety check.
- const expect_int = try pt.getCoerced(opv.intFromEnum(zcu), operand_ty);
+ const expect_int = try pt.getCoerced(opv.backingInt(zcu), operand_ty);
const ok = try block.addBinOp(.cmp_eq, operand, .fromValue(expect_int));
try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
}
@@ -9361,7 +9386,6 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
.pointer => try sema.errNote(src, msg, "use @ptrCast to cast from '{f}'", .{operand_ty.fmt(pt)}),
else => {},
}
-
break :msg msg;
}),
.array => switch (dest_ty.arrayBase(zcu)[0].zigTypeTag(zcu)) {
@@ -9396,7 +9420,183 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
return sema.fail(block, operand_src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)});
}
- return sema.bitCast(block, dest_ty, operand, block.nodeOffset(inst_data.src_node));
+ operand_ty.assertHasLayout(zcu);
+ try sema.ensureLayoutResolved(dest_ty, src, .init);
+
+ const operand_bits = operand_ty.bitSize(zcu);
+ const dest_bits = dest_ty.bitSize(zcu);
+ if (operand_bits != dest_bits) {
+ return sema.fail(block, src, "@bitCast size mismatch: destination type '{f}' has {d} bits but source type '{f}' has {d} bits", .{
+ dest_ty.fmt(pt),
+ dest_bits,
+ operand_ty.fmt(pt),
+ operand_bits,
+ });
+ }
+
+ if (sema.resolveValue(operand)) |operand_val| {
+ const dest_is_exhaustive_enum = dest_ty.zigTypeTag(zcu) == .@"enum" and
+ !dest_ty.isNonexhaustiveEnum(zcu);
+ if (dest_is_exhaustive_enum and operand_val.isUndef(zcu)) {
+ return sema.failWithUseOfUndef(block, operand_src, null);
+ }
+
+ const result_val = try sema.bitCastVal(operand_val, dest_ty);
+
+ if (dest_is_exhaustive_enum and
+ dest_ty.enumTagFieldIndex(result_val, zcu) == null)
+ {
+ return sema.fail(block, src, "enum '{f}' has no tag with value '{f}'", .{
+ dest_ty.fmt(pt), result_val.backingInt(zcu).fmtValueSema(pt, sema),
+ });
+ }
+
+ return .fromValue(result_val);
+ }
+
+ try sema.validateRuntimeValue(block, src, operand);
+
+ if (block.wantSafety()) {
+ try sema.preparePanicId(src, .invalid_enum_value);
+ return block.addTyOp(.bit_cast_safe, dest_ty, operand);
+ }
+ return block.addTyOp(.bit_cast, dest_ty, operand);
+}
+
+fn zirBackingInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const gpa = zcu.comp.gpa;
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
+ const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
+
+ const operand = sema.resolveInst(inst_data.operand);
+ const operand_ty = sema.typeOf(operand);
+ operand_ty.assertHasLayout(zcu);
+ const int_backed_ref: Air.Inst.Ref = ref: switch (operand_ty.zigTypeTag(zcu)) {
+ .@"enum" => operand,
+ .@"union" => {
+ const union_obj = zcu.intern_pool.loadUnionType(operand_ty.toIntern());
+ if (union_obj.tag_usage == .tagged) break :ref try sema.unionToTag(block, operand);
+ if (union_obj.layout == .@"packed") break :ref operand;
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(operand_src, "non-packed union '{f}' does not have a backing integer", .{operand_ty.fmt(pt)});
+ errdefer msg.deinit(gpa);
+ try sema.errNote(operand_src, msg, "untagged union '{f}' does not have an enum tag with a backing integer", .{operand_ty.fmt(pt)});
+ try sema.addDeclaredHereNote(msg, operand_ty);
+ break :msg msg;
+ });
+ },
+ .@"struct" => {
+ if (operand_ty.containerLayout(zcu) != .@"packed") {
+ return sema.fail(block, operand_src, "non-packed struct '{f}' does not have a backing integer", .{
+ operand_ty.fmt(pt),
+ });
+ }
+ break :ref operand;
+ },
+ else => {
+ return sema.fail(block, operand_src, "expected enum, tagged union, packed union or packed struct, found '{f}'", .{
+ operand_ty.fmt(pt),
+ });
+ },
+ };
+ const int_backed_ty = sema.typeOf(int_backed_ref);
+ if (int_backed_ty.backingIntMode(zcu) != .explicit and int_backed_ty.zigTypeTag(zcu) != .@"enum")
+ return sema.failWithAmbiguousBackingIntType(block, operand_src, int_backed_ty, "@backingInt");
+ const backing_int_ty = int_backed_ty.backingIntType(zcu);
+
+ if (sema.resolveValue(int_backed_ref)) |int_backed_val| {
+ if (int_backed_val.isUndef(zcu)) return pt.undefRef(backing_int_ty);
+ return .fromValue(int_backed_val.backingInt(zcu));
+ }
+
+ switch (backing_int_ty.classify(zcu)) {
+ .partially_comptime, .fully_comptime => unreachable, // does not apply to integers
+ .no_possible_value => unreachable, // enum also NPV, cannot instantiate NPV types
+ .one_possible_value => unreachable, // enum or bitpack also OPV, should have been resolve above
+ .runtime => {},
+ }
+
+ return block.addTyOp(.bit_cast, backing_int_ty, int_backed_ref);
+}
+
+fn zirFromBackingIntArgTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
+ const src = block.nodeOffset(inst_data.src_node);
+
+ const dest_ty = try sema.resolveDestType(block, src, inst_data.operand, .remove_eu_opt, "@fromBackingInt");
+ try sema.ensureLayoutResolved(dest_ty, src, .init);
+ switch (dest_ty.zigTypeTag(zcu)) {
+ .@"enum" => {},
+ .@"struct", .@"union" => |type_tag| if (dest_ty.containerLayout(zcu) != .@"packed") {
+ return sema.fail(block, src, "non-packed {t} '{f}' does not have a backing integer", .{
+ type_tag, dest_ty.fmt(pt),
+ });
+ },
+ else => {
+ return sema.fail(block, src, "expected enum, packed union or packed struct, found '{f}'", .{
+ dest_ty.fmt(pt),
+ });
+ },
+ }
+ if (dest_ty.backingIntMode(zcu) != .explicit and dest_ty.zigTypeTag(zcu) != .@"enum")
+ return sema.failWithAmbiguousBackingIntType(block, src, dest_ty, "@fromBackingInt");
+ return .fromType(dest_ty.backingIntType(zcu));
+}
+
+fn zirFromBackingInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const src = block.nodeOffset(inst_data.src_node);
+ const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
+
+ // Type has already been validated and layout-resolved by `zirFromBackingIntArgTy`.
+ const dest_ty = try sema.resolveDestType(block, .unneeded, extra.lhs, .remove_eu_opt, undefined);
+ dest_ty.assertHasLayout(zcu);
+
+ const operand = sema.resolveInst(extra.rhs);
+ const backing_int_ref = try sema.coerce(block, dest_ty.backingIntType(zcu), operand, operand_src);
+
+ if (sema.resolveValue(backing_int_ref)) |backing_int_val| {
+ if (dest_ty.zigTypeTag(zcu) != .@"enum") {
+ // we don't do any safety checks for bitpacks
+ if (backing_int_val.isUndef(zcu)) return pt.undefRef(dest_ty);
+ return .fromValue(try pt.bitpackValue(dest_ty, backing_int_val));
+ }
+ const enum_obj = ip.loadEnumType(dest_ty.toIntern());
+ if (backing_int_val.isUndef(zcu)) {
+ if (enum_obj.nonexhaustive) return pt.undefRef(dest_ty);
+ return sema.failWithUseOfUndef(block, operand_src, null);
+ }
+ if (!enum_obj.nonexhaustive and
+ enum_obj.tagValueIndex(ip, backing_int_val.toIntern()) == null)
+ {
+ return sema.fail(block, src, "enum '{f}' has no tag with value '{f}'", .{
+ dest_ty.fmt(pt), backing_int_val.fmtValueSema(pt, sema),
+ });
+ }
+ return .fromValue(try pt.enumValue(dest_ty, backing_int_val));
+ }
+
+ switch (dest_ty.classify(zcu)) {
+ .partially_comptime, .fully_comptime => unreachable, // does not apply to enums or bitpacks
+ .no_possible_value => unreachable, // backing int also NPV, cannot coerce to NPV type
+ .one_possible_value => unreachable, // backing int also OPV, should have been resolve above
+ .runtime => {},
+ }
+
+ if (block.wantSafety()) {
+ try sema.preparePanicId(src, .invalid_enum_value);
+ return block.addTyOp(.bit_cast_safe, dest_ty, backing_int_ref);
+ }
+ return block.addTyOp(.bit_cast, dest_ty, backing_int_ref);
}
fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -9929,6 +10129,9 @@ fn analyzeSwitchBlock(
operand_ty.containerLayout(zcu) != .@"packed";
const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
+ // TODO audit the `err_set` special case (https://github.com/ziglang/zig/issues/15909)
+ if (!err_set) assert(validated_switch.case_vals.len != 0 or has_else or zir_switch.has_under); // NPV types cannot be instantiated
+
const cond_ref = switch (operand) {
.simple => |s| s.cond,
.loop => |l| l.init_cond,
@@ -10433,26 +10636,11 @@ fn finishSwitchBr(
}
var prev_result_overflowed = false;
- while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
- const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
- .int => .{ item, operand_ty },
- .@"enum" => b: {
- const int_val: Value = .fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
- break :b .{ int_val, int_val.typeOf(zcu) };
- },
- else => unreachable,
- };
+ while (item.compareScalar(.lte, item_last, item_ty, zcu)) : ({
assert(!prev_result_overflowed);
- const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
+ const result = try arith.incrementDefinedInt(sema, item_ty, item);
prev_result_overflowed = result.overflow;
- item = switch (operand_ty.zigTypeTag(zcu)) {
- .int => result.val,
- .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
- .ty = operand_ty.toIntern(),
- .int = result.val.toIntern(),
- } })),
- else => unreachable,
- };
+ item = result.val;
}) {
cases_len += 1;
case_block.instructions.clearRetainingCapacity();
@@ -10585,7 +10773,7 @@ fn finishSwitchBr(
break :check_enumerable .{ undefined, min_int };
},
.@"union", .@"struct" => {
- const backing_int_ty = item_ty.bitpackBackingInt(zcu);
+ const backing_int_ty = item_ty.backingIntType(zcu);
const min_backing_int = try backing_int_ty.minInt(pt, backing_int_ty);
break :check_enumerable .{ undefined, min_backing_int };
},
@@ -10886,7 +11074,7 @@ const ValidatedSwitchBlock = struct {
var cur_val = it.next_val orelse return null;
const int_ty = switch (type_tag) {
.int => item_ty,
- .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu),
+ .@"union", .@"struct" => item_ty.backingIntType(zcu),
else => unreachable,
};
while (it.next_idx < it.seen_ranges.len and
@@ -11316,7 +11504,7 @@ fn validateSwitchBlock(
check_range: {
const int_ty = switch (type_tag) {
.int => item_ty,
- .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu),
+ .@"union", .@"struct" => item_ty.backingIntType(zcu),
else => unreachable,
};
const min_int = try int_ty.minInt(pt, int_ty);
@@ -11938,14 +12126,7 @@ fn analyzeSwitchCaptures(
try sema.analyzeUnreachable(case_block, operand_src, false);
break :payload_ref .unreachable_value;
};
- if (sema.resolveValue(loaded_operand)) |err_val| {
- break :payload_ref .fromIntern(try pt.intern(.{ .err = .{
- .ty = capture_err_ty.toIntern(),
- .name = zcu.intern_pool.indexToKey(err_val.toIntern()).err.name,
- } }));
- } else {
- break :payload_ref try case_block.addTyOp(.error_cast, capture_err_ty, loaded_operand);
- }
+ break :payload_ref try sema.errorCastUnchecked(case_block, capture_err_ty, loaded_operand);
},
.item_refs => |item_refs| {
var names: InferredErrorSet.NameMap = .{};
@@ -11955,14 +12136,7 @@ fn analyzeSwitchCaptures(
names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {});
}
const capture_err_ty = try pt.errorSetFromUnsortedNames(names.keys());
- if (sema.resolveValue(loaded_operand)) |err_val| {
- break :payload_ref .fromIntern(try pt.intern(.{ .err = .{
- .ty = capture_err_ty.toIntern(),
- .name = zcu.intern_pool.indexToKey(err_val.toIntern()).err.name,
- } }));
- } else {
- break :payload_ref try case_block.addTyOp(.error_cast, capture_err_ty, loaded_operand);
- }
+ break :payload_ref try sema.errorCastUnchecked(case_block, capture_err_ty, loaded_operand);
},
}
}
@@ -12443,7 +12617,7 @@ fn validateSwitchItemOrRange(
.first = .fromInterned(backing_int_val),
.last = .fromInterned(backing_int_val),
.src = item_src,
- }, item_ty.bitpackBackingInt(zcu), zcu);
+ }, item_ty.backingIntType(zcu), zcu);
},
.enum_literal, .@"fn", .pointer, .type => {
break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
@@ -18400,7 +18574,7 @@ fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
const payload = try sema.coerce(block, field_ty, sema.resolveInst(extra.init), payload_src);
if (union_ty.containerLayout(zcu) == .@"packed") {
- return sema.bitCast(block, union_ty, payload, block.nodeOffset(inst_data.src_node));
+ return sema.bitCastUnchecked(block, union_ty, payload);
}
if (sema.resolveValue(payload)) |payload_val| {
@@ -18537,7 +18711,7 @@ fn zirStructInit(
const init_inst = try sema.coerce(block, field_ty, uncoerced_init_inst, field_src);
if (resolved_ty.containerLayout(zcu) == .@"packed") {
- const union_val = try sema.bitCast(block, resolved_ty, init_inst, src);
+ const union_val = try sema.bitCastUnchecked(block, resolved_ty, init_inst);
const result_val = try sema.coerce(block, result_ty, union_val, src);
if (is_ref) {
return sema.analyzeRef(block, src, result_val, .none);
@@ -29716,41 +29890,29 @@ fn storePtrVal(
}
}
-fn bitCast(
+/// Asserts that the layout of `dest_ty` is already resolved.
+fn bitCastUnchecked(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
- inst_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
- const pt = sema.pt;
- const zcu = pt.zcu;
+ const zcu = sema.pt.zcu;
const old_ty = sema.typeOf(inst);
old_ty.assertHasLayout(zcu);
- try sema.ensureLayoutResolved(dest_ty, inst_src, .init);
+ dest_ty.assertHasLayout(zcu);
assert(old_ty.hasBitRepresentation(zcu));
assert(dest_ty.hasBitRepresentation(zcu));
assert(old_ty.scalarType(zcu).zigTypeTag(zcu) != .pointer);
assert(dest_ty.scalarType(zcu).zigTypeTag(zcu) != .pointer);
-
- const dest_bits = dest_ty.bitSize(zcu);
- const old_bits = old_ty.bitSize(zcu);
-
- if (old_bits != dest_bits) {
- return sema.fail(block, inst_src, "@bitCast size mismatch: destination type '{f}' has {d} bits but source type '{f}' has {d} bits", .{
- dest_ty.fmt(pt),
- dest_bits,
- old_ty.fmt(pt),
- old_bits,
- });
- }
+ assert(old_ty.bitSize(zcu) == dest_ty.bitSize(zcu));
if (sema.resolveValue(inst)) |val| {
return .fromValue(try sema.bitCastVal(val, dest_ty));
}
- try sema.validateRuntimeValue(block, inst_src, inst);
+
return block.addTyOp(.bit_cast, dest_ty, inst);
}
@@ -29774,6 +29936,26 @@ pub fn bitCastVal(
}
}
+fn errorCastUnchecked(
+ sema: *Sema,
+ block: *Block,
+ dest_ty: Type,
+ inst: Air.Inst.Ref,
+) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ assert(dest_ty.zigTypeTag(zcu) == .error_set);
+ assert(sema.typeOf(inst).zigTypeTag(zcu) == .error_set);
+ if (sema.resolveValue(inst)) |val| {
+ if (val.isUndef(zcu)) return pt.undefRef(dest_ty);
+ return .fromIntern(try pt.intern(.{ .err = .{
+ .ty = dest_ty.toIntern(),
+ .name = zcu.intern_pool.indexToKey(val.toIntern()).err.name,
+ } }));
+ }
+ return block.addTyOp(.error_cast, dest_ty, inst);
+}
+
fn checkSpirvSliceAllowed(
sema: *Sema,
block: *Block,
@@ -33942,14 +34124,6 @@ fn intFromFloatScalar(
return pt.getCoerced(cti_result, int_ty);
}
-fn intInRange(sema: *Sema, tag_ty: Type, int_val: Value, end: usize) !bool {
- const pt = sema.pt;
- if (!int_val.compareAllWithZero(.gte, pt.zcu)) return false;
- const end_val = try pt.intValue(tag_ty, end);
- if (!(try sema.compareAll(int_val, .lt, end_val, tag_ty))) return false;
- return true;
-}
-
/// Asserts the type is an exhaustive enum.
fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool {
const pt = sema.pt;
diff --git a/src/Sema/reinterpret.zig b/src/Sema/reinterpret.zig
index 21a5bad1abd72afc1a6c8982e9caadd1825ea824..59e2626221c8f22c4be7bfb1e4c5086b9bf20aae 100644
--- a/src/Sema/reinterpret.zig
+++ b/src/Sema/reinterpret.zig
@@ -353,7 +353,7 @@ const PackValueBytes = struct {
return pt.aggregateValue(ty, elems);
},
.@"packed" => {
- const backing_int_val = try pack.primitive(ty.bitpackBackingInt(zcu));
+ const backing_int_val = try pack.primitive(ty.backingIntType(zcu));
if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty);
return pt.bitpackValue(ty, backing_int_val);
},
@@ -424,15 +424,15 @@ const PackValueBytes = struct {
}
},
.@"packed" => {
- const backing_int_val = try pack.primitive(ty.bitpackBackingInt(zcu));
+ const backing_int_val = try pack.primitive(ty.backingIntType(zcu));
if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty);
return pt.bitpackValue(ty, backing_int_val);
},
},
.@"enum" => {
- const tag_int_val = try pack.primitive(ty.intTagType(zcu));
+ const tag_int_val = try pack.primitive(ty.backingIntType(zcu));
if (tag_int_val.isUndef(zcu)) return pt.undefValue(ty);
- return pt.enumValue(ty, tag_int_val.toIntern());
+ return pt.enumValue(ty, tag_int_val);
},
else => return pack.primitive(ty),
}
diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig
index 5c4211d22980d2d5daedb08b80e1f151990bddcc..6cdd65667aae567a7939c7a1a7b95831cc39672e 100644
--- a/src/Sema/type_resolution.zig
+++ b/src/Sema/type_resolution.zig
@@ -63,7 +63,7 @@ pub const LayoutResolveReason = enum {
.@"export" => "for export here",
.@"extern" => "for extern declaration here",
.asm_out_type => "for inline assembly output type declared here",
- .std_lang_type => "from 'std.lang'",
+ .std_lang_type => "from 'std.lang'",
// zig fmt: on
};
}
diff --git a/src/Type.zig b/src/Type.zig
index b56a4868bc587ca5473001779e107fd221068975..37476ceb060a007d1b21d0038307ddff842bbe87 100644
--- a/src/Type.zig
+++ b/src/Type.zig
@@ -1580,15 +1580,28 @@ pub fn containerLayout(ty: Type, zcu: *const Zcu) std.lang.Type.ContainerLayout
};
}
-pub fn bitpackBackingInt(ty: Type, zcu: *const Zcu) Type {
+/// Asserts that the type is either an enum or a bitpack.
+pub fn backingIntType(ty: Type, zcu: *const Zcu) Type {
const ip = &zcu.intern_pool;
return switch (ip.indexToKey(ty.toIntern())) {
+ .enum_type => .fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type),
.struct_type => .fromInterned(ip.loadStructType(ty.toIntern()).packed_backing_int_type),
.union_type => .fromInterned(ip.loadUnionType(ty.toIntern()).packed_backing_int_type),
else => unreachable,
};
}
+/// For unions, returns the *backing int* mode, not the *enum tag* mode.
+pub fn backingIntMode(ty: Type, zcu: *const Zcu) InternPool.BackingTypeMode {
+ const ip = &zcu.intern_pool;
+ return switch (ip.indexToKey(ty.toIntern())) {
+ .enum_type => ip.loadEnumType(ty.toIntern()).int_tag_mode,
+ .struct_type => ip.loadStructType(ty.toIntern()).packed_backing_mode,
+ .union_type => ip.loadUnionType(ty.toIntern()).packed_backing_mode,
+ else => unreachable,
+ };
+}
+
/// Asserts that the type is an error union.
pub fn errorUnionPayload(ty: Type, zcu: *const Zcu) Type {
return Type.fromInterned(zcu.intern_pool.indexToKey(ty.toIntern()).error_union_type.payload_type);
@@ -2093,11 +2106,8 @@ pub fn onePossibleValue(ty: Type, pt: Zcu.PerThread) !?Value {
return try pt.unionValue(ty, tag_val, payload_val);
} else unreachable;
},
- .enum_type => if (try ty.intTagType(zcu).onePossibleValue(pt)) |int_tag_opv| {
- return .fromInterned(try pt.intern(.{ .enum_tag = .{
- .ty = ty.toIntern(),
- .int = int_tag_opv.toIntern(),
- } }));
+ .enum_type => if (try ty.backingIntType(zcu).onePossibleValue(pt)) |int_tag_opv| {
+ return try pt.enumValue(ty, int_tag_opv);
} else null,
// values, not types
@@ -2274,17 +2284,6 @@ pub fn maxIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {
return pt.intValue_big(dest_ty, res.toConst());
}
-/// Asserts the type is an enum or a union.
-pub fn intTagType(ty: Type, zcu: *const Zcu) Type {
- const ip = &zcu.intern_pool;
- const enum_ty: Type = switch (ip.indexToKey(ty.toIntern())) {
- .union_type => .fromInterned(ip.loadUnionType(ty.toIntern()).enum_tag_type),
- .enum_type => ty,
- else => unreachable,
- };
- return .fromInterned(ip.loadEnumType(enum_ty.toIntern()).int_tag_type);
-}
-
pub fn isNonexhaustiveEnum(ty: Type, zcu: *const Zcu) bool {
const ip = &zcu.intern_pool;
return switch (ip.indexToKey(ty.toIntern())) {
@@ -3058,15 +3057,12 @@ pub fn unpackable(ty: Type, zcu: *const Zcu) ?UnpackableReason {
.one, .many, .c => .pointer,
},
- .@"enum" => {
- const enum_obj = zcu.intern_pool.loadEnumType(ty.toIntern());
- return switch (enum_obj.int_tag_mode) {
- .explicit => if (enum_obj.int_tag_type != .noreturn_type)
- null
- else
- .other,
- .auto => .{ .enum_inferred_int_tag = ty },
- };
+ .@"enum" => switch (ty.backingIntMode(zcu)) {
+ .explicit => switch (ty.backingIntType(zcu).toIntern()) {
+ else => null,
+ .noreturn_type => .other,
+ },
+ .auto => .{ .enum_inferred_int_tag = ty },
},
.@"struct" => switch (ty.containerLayout(zcu)) {
@@ -3231,7 +3227,11 @@ pub fn hasBitRepresentation(ty: Type, zcu: *const Zcu) bool {
.float,
=> true,
- .@"enum" => zcu.intern_pool.loadEnumType(ty.toIntern()).int_tag_mode == .explicit,
+ .@"enum" => {
+ const enum_obj = zcu.intern_pool.loadEnumType(ty.toIntern());
+ return enum_obj.int_tag_mode == .explicit and
+ enum_obj.int_tag_type != .noreturn_type;
+ },
.pointer, .optional => ty.isPtrAtRuntime(zcu),
.@"struct", .@"union" => ty.containerLayout(zcu) == .@"packed",
diff --git a/src/Value.zig b/src/Value.zig
index 7db732c60e38d5a10353a230a1552530f58e7592..adcd7abfce33e2a3c40e5d405e3dc49c67e0769d 100644
--- a/src/Value.zig
+++ b/src/Value.zig
@@ -146,8 +146,13 @@ pub fn toType(self: Value) Type {
return Type.fromInterned(self.toIntern());
}
-pub fn intFromEnum(val: Value, zcu: *const Zcu) Value {
- return .fromInterned(zcu.intern_pool.indexToKey(val.toIntern()).enum_tag.int);
+/// Asserts that value is defined and of enum or bitpack type.
+pub fn backingInt(val: Value, zcu: *const Zcu) Value {
+ return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
+ .enum_tag => |enum_tag| .fromInterned(enum_tag.int),
+ .bitpack => |bitpack| .fromInterned(bitpack.backing_int_val),
+ else => unreachable,
+ };
}
/// Asserts that `val` is an integer.
@@ -415,7 +420,7 @@ pub fn writeToPackedMemory(
}
},
.@"enum" => {
- const int_val = val.intFromEnum(zcu);
+ const int_val = val.backingInt(zcu);
int_val.writeToPackedMemory(zcu, buffer, bit_offset);
},
.int => {
@@ -564,7 +569,7 @@ pub fn readFromPackedMemory(
return pt.intValue_big(ty, bigint.toConst());
},
.@"enum" => {
- const int_ty = ty.intTagType(zcu);
+ const int_ty = ty.backingIntType(zcu);
const int_val: Value = try .readFromPackedMemory(int_ty, pt, buffer, bit_offset);
return pt.getCoerced(int_val, ty);
},
@@ -581,7 +586,7 @@ pub fn readFromPackedMemory(
} })),
.@"struct", .@"union" => {
assert(ty.containerLayout(zcu) == .@"packed");
- const int_val: Value = try .readFromPackedMemory(ty.bitpackBackingInt(zcu), pt, buffer, bit_offset);
+ const int_val: Value = try .readFromPackedMemory(ty.backingIntType(zcu), pt, buffer, bit_offset);
return pt.bitpackValue(ty, int_val);
},
.array, .vector => {
@@ -2368,7 +2373,7 @@ pub fn uninterpret(val: anytype, ty: Type, pt: Zcu.PerThread) error{ OutOfMemory
try pt.nullValue(ty),
.@"enum" => switch (interpret_mode) {
- .direct => try pt.enumValue(ty, (try uninterpret(@intFromEnum(val), ty.intTagType(zcu), pt)).toIntern()),
+ .direct => try pt.enumValue(ty, try uninterpret(@intFromEnum(val), ty.backingIntType(zcu), pt)),
.by_name => {
const field_name_ip = try ip.getOrPutString(zcu.gpa, io, pt.tid, @tagName(val), .no_embedded_nulls);
const field_idx = ty.enumFieldIndex(field_name_ip, zcu) orelse return error.TypeMismatch;
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig
index 62f11a83f671cc7b2ea3b20c7b6e0e94f342d354..a839c03685ae6b64389e7b72751ed5e109a87399 100644
--- a/src/Zcu/PerThread.zig
+++ b/src/Zcu/PerThread.zig
@@ -3956,7 +3956,7 @@ pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!V
}
pub fn intType(pt: Zcu.PerThread, signedness: std.lang.Signedness, bits: u16) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .int_type = .{
+ return .fromInterned(try pt.intern(.{ .int_type = .{
.signedness = signedness,
.bits = bits,
} }));
@@ -3967,15 +3967,15 @@ pub fn errorIntType(pt: Zcu.PerThread) std.mem.Allocator.Error!Type {
}
pub fn arrayType(pt: Zcu.PerThread, info: InternPool.Key.ArrayType) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .array_type = info }));
+ return .fromInterned(try pt.intern(.{ .array_type = info }));
}
pub fn vectorType(pt: Zcu.PerThread, info: InternPool.Key.VectorType) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .vector_type = info }));
+ return .fromInterned(try pt.intern(.{ .vector_type = info }));
}
pub fn optionalType(pt: Zcu.PerThread, child_type: InternPool.Index) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .opt_type = child_type }));
+ return .fromInterned(try pt.intern(.{ .opt_type = child_type }));
}
pub fn ptrType(pt: Zcu.PerThread, info: InternPool.Key.PtrType) Allocator.Error!Type {
@@ -3997,7 +3997,7 @@ pub fn ptrType(pt: Zcu.PerThread, info: InternPool.Key.PtrType) Allocator.Error!
_ => assert(@intFromEnum(info.flags.vector_index) < info.packed_offset.host_size),
}
- return Type.fromInterned(try pt.intern(.{ .ptr_type = canon_info }));
+ return .fromInterned(try pt.intern(.{ .ptr_type = canon_info }));
}
pub fn singleMutPtrType(pt: Zcu.PerThread, child_type: Type) Allocator.Error!Type {
@@ -4037,11 +4037,11 @@ pub fn funcType(pt: Zcu.PerThread, key: InternPool.GetFuncTypeKey) Allocator.Err
/// Use this for `anyframe->T` only.
/// For `anyframe`, use the `InternPool.Index.anyframe` tag directly.
pub fn anyframeType(pt: Zcu.PerThread, payload_ty: Type) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .anyframe_type = payload_ty.toIntern() }));
+ return .fromInterned(try pt.intern(.{ .anyframe_type = payload_ty.toIntern() }));
}
pub fn errorUnionType(pt: Zcu.PerThread, error_set_ty: Type, payload_ty: Type) Allocator.Error!Type {
- return Type.fromInterned(try pt.intern(.{ .error_union_type = .{
+ return .fromInterned(try pt.intern(.{ .error_union_type = .{
.error_set_type = error_set_ty.toIntern(),
.payload_type = payload_ty.toIntern(),
} }));
@@ -4050,7 +4050,7 @@ pub fn errorUnionType(pt: Zcu.PerThread, error_set_ty: Type, payload_ty: Type) A
pub fn singleErrorSetType(pt: Zcu.PerThread, name: InternPool.NullTerminatedString) Allocator.Error!Type {
const names: *const [1]InternPool.NullTerminatedString = &name;
const comp = pt.zcu.comp;
- return Type.fromInterned(try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names));
+ return .fromInterned(try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names));
}
/// Sorts `names` in place.
@@ -4066,7 +4066,7 @@ pub fn errorSetFromUnsortedNames(
);
const comp = pt.zcu.comp;
const new_ty = try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names);
- return Type.fromInterned(new_ty);
+ return .fromInterned(new_ty);
}
/// Supports only pointers, not pointer-like optionals.
@@ -4074,7 +4074,7 @@ pub fn ptrIntValue(pt: Zcu.PerThread, ty: Type, x: u64) Allocator.Error!Value {
const zcu = pt.zcu;
assert(ty.zigTypeTag(zcu) == .pointer and !ty.isSlice(zcu));
assert(x != 0 or ty.isAllowzeroPtr(zcu));
- return Value.fromInterned(try pt.intern(.{ .ptr = .{
+ return .fromInterned(try pt.intern(.{ .ptr = .{
.ty = ty.toIntern(),
.base_addr = .int,
.byte_offset = x,
@@ -4082,14 +4082,11 @@ pub fn ptrIntValue(pt: Zcu.PerThread, ty: Type, x: u64) Allocator.Error!Value {
}
/// Creates an enum tag value based on the integer tag value.
-pub fn enumValue(pt: Zcu.PerThread, ty: Type, tag_int: InternPool.Index) Allocator.Error!Value {
- if (std.debug.runtime_safety) {
- const tag = ty.zigTypeTag(pt.zcu);
- assert(tag == .@"enum");
- }
- return Value.fromInterned(try pt.intern(.{ .enum_tag = .{
+pub fn enumValue(pt: Zcu.PerThread, ty: Type, tag_int: Value) Allocator.Error!Value {
+ if (std.debug.runtime_safety) assert(ty.zigTypeTag(pt.zcu) == .@"enum");
+ return .fromInterned(try pt.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
- .int = tag_int,
+ .int = tag_int.toIntern(),
} }));
}
@@ -4103,7 +4100,7 @@ pub fn enumValueFieldIndex(pt: Zcu.PerThread, ty: Type, field_index: u32) Alloca
if (enum_type.field_values.len == 0) {
// Auto-numbered fields.
- return Value.fromInterned(try pt.intern(.{ .enum_tag = .{
+ return .fromInterned(try pt.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
.int = try pt.intern(.{ .int = .{
.ty = enum_type.int_tag_type,
@@ -4262,7 +4259,7 @@ pub fn floatValue(pt: Zcu.PerThread, ty: Type, x: anytype) Allocator.Error!Value
/// Create a value whose type is a `packed struct` or `packed union`, from the backing integer value.
pub fn bitpackValue(pt: Zcu.PerThread, ty: Type, backing_int_val: Value) Allocator.Error!Value {
- assert(backing_int_val.typeOf(pt.zcu).toIntern() == ty.bitpackBackingInt(pt.zcu).toIntern());
+ assert(backing_int_val.typeOf(pt.zcu).toIntern() == ty.backingIntType(pt.zcu).toIntern());
return .fromInterned(try pt.intern(.{ .bitpack = .{
.ty = ty.toIntern(),
.backing_int_val = backing_int_val.toIntern(),
diff --git a/src/codegen.zig b/src/codegen.zig
index 55d73b476a7a9a9a89a18fc4c8be9b8e139ac6c3..6872fbe84c4d0f1d3889bab30e3d65b6c9308968 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -415,7 +415,7 @@ pub fn generateSymbol(
}
},
.enum_tag => |enum_tag| {
- const int_tag_ty = ty.intTagType(zcu);
+ const int_tag_ty = ty.backingIntType(zcu);
try generateSymbol(bin_file, pt, try pt.getCoerced(Value.fromInterned(enum_tag.int), int_tag_ty), w, reloc_parent);
},
.float => |float| storage: switch (float.storage) {
diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig
index 14a718b871b5a48b00a81f92a235ea3eebd303f1..690c9a49747c292c8a25df1983fc1074b91f8b39 100644
--- a/src/codegen/aarch64.zig
+++ b/src/codegen/aarch64.zig
@@ -5,8 +5,10 @@ pub const encoding = @import("aarch64/encoding.zig");
pub const Mir = @import("aarch64/Mir.zig");
pub const Select = @import("aarch64/Select.zig");
-pub fn legalizeFeatures(_: *const std.Target) ?*Air.Legalize.Features {
- return null;
+pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
+ return comptime &.initMany(&.{
+ .expand_bit_cast_safe,
+ });
}
pub fn generate(
diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig
index b74c7fe9ec580071a887498cd8ab0ef04e287257..9a832f2c858698f1f790c82f138cd583ecf91a95 100644
--- a/src/codegen/aarch64/Select.zig
+++ b/src/codegen/aarch64/Select.zig
@@ -362,6 +362,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void {
air_inst_index = air_body[air_body_index];
continue :air_tag air_tags[@intFromEnum(air_inst_index)];
},
+ .bit_cast_safe => unreachable, // legalized
inline .block, .dbg_inline_block => |air_tag| {
const air_body_block = switch (air_tag) {
else => comptime unreachable,
@@ -3201,6 +3202,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
},
.bit_cast,
+ .bit_cast_safe, // TODO safety check
.ptr_cast,
.ptr_from_int,
.int_from_ptr,
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 3cf7d9c2fbc2921c3f7745c8f020520f464e380e..bf1d0ab00fb16a94f4144f3d6d6dd5f7a54fac67 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -27,6 +27,7 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
return comptime switch (dev.env.supports(.legalize)) {
inline false, true => |supports_legalize| &.init(.{
// we don't currently ask zig1 to use safe optimization modes
+ .expand_bit_cast_safe = supports_legalize,
.expand_int_cast_safe = supports_legalize,
.expand_int_from_float_safe = supports_legalize,
.expand_int_from_float_optimized_safe = supports_legalize,
@@ -1460,7 +1461,7 @@ pub const DeclGen = struct {
}
return w.writeByte('}');
},
- .@"packed" => return dg.renderUndefValue(w, ty.bitpackBackingInt(zcu), location),
+ .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location),
}
},
.tuple_type => |tuple_info| {
@@ -1520,7 +1521,7 @@ pub const DeclGen = struct {
if (loaded_union.has_runtime_tag) try w.writeByte(' ');
if (loaded_union.layout == .auto) try w.writeByte('}');
},
- .@"packed" => return dg.renderUndefValue(w, ty.bitpackBackingInt(zcu), location),
+ .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location),
}
},
.error_union_type => |error_union| {
@@ -2876,6 +2877,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void {
.add_safe,
.sub_safe,
.mul_safe,
+ .bit_cast_safe,
.int_cast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig
index 2944d1092e2c119cfd91eeb2e7221b67437cd398..8588c1dd030849f586a2c5ad719087ab6e4412e7 100644
--- a/src/codegen/c/type.zig
+++ b/src/codegen/c/type.zig
@@ -484,8 +484,7 @@ pub const CType = union(enum) {
pub fn classifyInt(ty: Type, zcu: *const Zcu) IntClass {
const int_ty: Type = switch (ty.zigTypeTag(zcu)) {
.error_set => return classifyBitInt(.unsigned, zcu.errorSetBits(), zcu),
- .@"enum" => ty.intTagType(zcu),
- .@"struct", .@"union" => ty.bitpackBackingInt(zcu),
+ .@"enum", .@"struct", .@"union" => ty.backingIntType(zcu),
.int => ty,
else => unreachable,
};
diff --git a/src/codegen/c/type/render_defs.zig b/src/codegen/c/type/render_defs.zig
index c8607d748ccf0d3c1f0860c1a3ce3ea38f0479f4..03aae41ee0ec47cca9b560bd358263a5b0cb2352 100644
--- a/src/codegen/c/type/render_defs.zig
+++ b/src/codegen/c/type/render_defs.zig
@@ -212,7 +212,7 @@ pub fn defineComplete(
},
.@"enum" => {
const name_cty: CType = .{ .@"enum" = ty };
- const cty: CType = try .lower(ty.intTagType(zcu), deps, arena, zcu);
+ const cty: CType = try .lower(ty.backingIntType(zcu), deps, arena, zcu);
try w.print("typedef {f}{f}{f}; /* {f} */\n", .{
cty.fmtDeclaratorPrefix(zcu),
name_cty.fmtTypeName(zcu),
@@ -343,7 +343,7 @@ fn defineBitpack(
) (Allocator.Error || Writer.Error)!void {
const zcu = pt.zcu;
const name_cty: CType = .{ .bitpack = ty };
- const cty: CType = try .lower(ty.bitpackBackingInt(zcu), deps, arena, zcu);
+ const cty: CType = try .lower(ty.backingIntType(zcu), deps, arena, zcu);
try w.print("typedef {f}{f}{f}; /* {f} */\n", .{
cty.fmtDeclaratorPrefix(zcu),
name_cty.fmtTypeName(zcu),
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index a93ac0853f5d579e053207a1bd535a178a6a6a88..59a27d8b408f62cd189bbad84ca30e68bd1b5235 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -3252,7 +3252,7 @@ pub const Object = struct {
return ty;
},
.opaque_type, .spirv_type => unreachable, // no runtime bits
- .enum_type => try o.lowerType(t.intTagType(zcu), repr),
+ .enum_type => try o.lowerType(t.backingIntType(zcu), repr),
.func_type => |func_type| try o.lowerFnType(t, func_type),
.error_set_type, .inferred_error_set_type => try o.errorIntType(repr),
// values, not types
diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig
index 602277a91188d72994ac83b025ecf5c580f5ce2b..6e737fa482f6eb121062e9516b7028f65aefd576 100644
--- a/src/codegen/llvm/FuncGen.zig
+++ b/src/codegen/llvm/FuncGen.zig
@@ -465,7 +465,8 @@ fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.Cov
.alloc => try self.airAlloc(inst),
.ret_ptr => try self.airRetPtr(inst),
.arg => try self.airArg(inst),
- .bit_cast => try self.airBitCast(inst),
+ .bit_cast => try self.airBitCast(inst, false),
+ .bit_cast_safe => try self.airBitCast(inst, true),
.ptr_cast => try self.airNopCast(inst),
.ptr_from_int => try self.airPtrFromInt(inst),
.int_from_ptr => try self.airIntFromPtr(inst),
@@ -1254,7 +1255,6 @@ fn cmp(
const zcu = o.zcu;
const scalar_ty = operand_ty.scalarType(zcu);
const int_ty = switch (scalar_ty.zigTypeTag(zcu)) {
- .@"enum" => scalar_ty.intTagType(zcu),
.int, .bool, .pointer, .error_set => scalar_ty,
.optional => blk: {
const payload_ty = operand_ty.optionalChild(zcu);
@@ -1328,7 +1328,7 @@ fn cmp(
return phi.toValue();
},
.float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),
- .@"struct", .@"union" => scalar_ty.bitpackBackingInt(zcu),
+ .@"enum", .@"struct", .@"union" => scalar_ty.backingIntType(zcu),
else => unreachable,
};
const is_signed = int_ty.isSignedInt(zcu);
@@ -4539,7 +4539,7 @@ fn airFpext(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value
}
}
-fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
+fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!Builder.Value {
const o = fg.object;
const zcu = o.zcu;
@@ -4564,7 +4564,26 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value
assert(!isByRef(dest_ty, zcu));
const llvm_dest_ty = try o.lowerType(dest_ty, .by_value);
- return fg.wip.cast(.bitcast, operand, llvm_dest_ty, "");
+ const result = try fg.wip.cast(.bitcast, operand, llvm_dest_ty, "");
+ if (safety and dest_ty.zigTypeTag(zcu) == .@"enum" and !dest_ty.isNonexhaustiveEnum(zcu)) {
+ const llvm_fn = try o.getIsNamedEnumValueFunction(dest_ty);
+ const is_valid_enum_val = try fg.wip.call(
+ .normal,
+ .fastcc,
+ .none,
+ llvm_fn.typeOf(&o.builder),
+ llvm_fn.toValue(&o.builder),
+ &.{result},
+ "",
+ );
+ const fail_block = try fg.wip.block(1, "ValidEnumFail");
+ const ok_block = try fg.wip.block(1, "ValidEnumOk");
+ _ = try fg.wip.brCond(is_valid_enum_val, ok_block, fail_block, .none);
+ fg.wip.cursor = .{ .block = fail_block };
+ try fg.buildSimplePanic(.invalid_enum_value);
+ fg.wip.cursor = .{ .block = ok_block };
+ }
+ return result;
}
fn airNopCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig
index b11b898556c0de6479e0aeaf2fe25d47d31e8445..4e5aac5f4d33cc85a30893b0973ab5cbb01488a3 100644
--- a/src/codegen/riscv64/CodeGen.zig
+++ b/src/codegen/riscv64/CodeGen.zig
@@ -51,6 +51,7 @@ const InnerError = codegen.Error || error{OutOfRegisters};
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
+ .expand_bit_cast_safe,
.expand_int_cast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
@@ -1454,6 +1455,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.add_safe,
.sub_safe,
.mul_safe,
+ .bit_cast_safe,
.int_cast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
@@ -5128,7 +5130,6 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
.@"struct",
=> {
const int_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) {
- .@"enum" => lhs_ty.intTagType(zcu),
.int => lhs_ty,
.bool => .u1,
.pointer => .u64,
@@ -5143,7 +5144,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
return func.fail("TODO riscv cmp non-pointer optionals", .{});
}
},
- .@"struct", .@"union" => lhs_ty.bitpackBackingInt(zcu),
+ .@"enum", .@"struct", .@"union" => lhs_ty.backingIntType(zcu),
else => unreachable,
};
diff --git a/src/codegen/sparc64/CodeGen.zig b/src/codegen/sparc64/CodeGen.zig
index 2d67e7cc54576394bd88d2dcbacab8330b94b21f..23a69bab00cb5c7c83a0cdfc4aad4c4b63a60fd7 100644
--- a/src/codegen/sparc64/CodeGen.zig
+++ b/src/codegen/sparc64/CodeGen.zig
@@ -697,6 +697,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.add_safe,
.sub_safe,
.mul_safe,
+ .bit_cast_safe,
.int_cast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
@@ -1374,7 +1375,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
const int_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) {
.vector => unreachable, // Handled by cmp_vector.
- .@"enum" => lhs_ty.intTagType(zcu),
+ .@"enum" => lhs_ty.backingIntType(zcu),
.int => lhs_ty,
.bool => .u1,
.pointer => .usize,
diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig
index a12d07072983ef74bf4e45e510803c7b4da1e0a1..d6b82ea773dc8ccf41dd9aa38794197cd255a573 100644
--- a/src/codegen/spirv/CodeGen.zig
+++ b/src/codegen/spirv/CodeGen.zig
@@ -121,6 +121,7 @@ const StructType = struct {
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
+ .expand_bit_cast_safe,
.expand_int_cast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
@@ -1353,7 +1354,7 @@ fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo {
const target = cg.zcu.getTarget();
var scalar_ty = ty.scalarType(zcu);
if (scalar_ty.zigTypeTag(zcu) == .@"enum") {
- scalar_ty = scalar_ty.intTagType(zcu);
+ scalar_ty = scalar_ty.backingIntType(zcu);
}
const vector_len = if (ty.isVector(zcu)) ty.vectorLen(zcu) else null;
return switch (scalar_ty.zigTypeTag(zcu)) {
@@ -1732,8 +1733,8 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id {
return try cg.constructComposite(comp_ty_id, &constituents);
},
.enum_tag => {
- const int_val = val.intFromEnum(zcu);
- const int_ty = ty.intTagType(zcu);
+ const int_val = val.backingInt(zcu);
+ const int_ty = ty.backingIntType(zcu);
break :cache try cg.constant(int_ty, int_val, repr);
},
.ptr => return cg.constantPtr(val),
@@ -2213,7 +2214,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
const int_info = ty.intInfo(zcu);
return try cg.intType(int_info.signedness, int_info.bits);
},
- .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr),
+ .@"enum" => return try cg.resolveType(ty.backingIntType(zcu), repr),
.float => {
const bits = ty.floatBits(target);
const supported = switch (bits) {
@@ -5807,7 +5808,7 @@ fn cmp(
.int, .bool, .float => {},
.@"enum" => {
assert(!is_vector);
- const ty = lhs.ty.intTagType(zcu);
+ const ty = lhs.ty.backingIntType(zcu);
return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty));
},
.@"struct" => {
@@ -6887,7 +6888,7 @@ fn unionInit(
const tag_int = if (layout.tag_size != 0) blk: {
const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field);
- const tag_int_val = tag_val.intFromEnum(zcu);
+ const tag_int_val = tag_val.backingInt(zcu);
break :blk tag_int_val.toUnsignedInt(zcu);
} else 0;
@@ -8164,7 +8165,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
break :blk if (backing_bits <= 32) 1 else 2;
},
.@"enum" => blk: {
- const int_ty = cond_ty.intTagType(zcu);
+ const int_ty = cond_ty.backingIntType(zcu);
const int_info = int_ty.intInfo(zcu);
const backing_bits, const big_int = cg.backingIntBits(int_info.bits);
if (big_int) return cg.todo("implement composite int switch", .{});
@@ -8224,7 +8225,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
const value: Value = .fromInterned(item.toInterned().?);
const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
.bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
- .@"enum" => value.intFromEnum(zcu).toUnsignedInt(zcu),
+ .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu),
.error_set => value.getErrorInt(zcu),
.pointer => value.toUnsignedInt(zcu),
else => unreachable,
@@ -8378,7 +8379,7 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
break :blk if (backing_bits <= 32) 1 else 2;
},
.@"enum" => blk: {
- const int_ty = cond_ty.intTagType(zcu);
+ const int_ty = cond_ty.backingIntType(zcu);
const int_info = int_ty.intInfo(zcu);
const backing_bits, const big_int = cg.backingIntBits(int_info.bits);
if (big_int) return cg.todo("implement composite int loop switch", .{});
@@ -8464,7 +8465,7 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
const value: Value = .fromInterned(item.toInterned().?);
const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
.bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
- .@"enum" => value.intFromEnum(zcu).toUnsignedInt(zcu),
+ .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu),
.error_set => value.getErrorInt(zcu),
.pointer => value.toUnsignedInt(zcu),
else => unreachable,
diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig
index 6d42d8d1ff69c9f5270106b8c9c311de3f309586..8d203139b8854c4913902087f033d26af520c60d 100644
--- a/src/codegen/wasm/CodeGen.zig
+++ b/src/codegen/wasm/CodeGen.zig
@@ -32,6 +32,7 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{
+ .expand_bit_cast_safe,
.expand_int_cast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
@@ -615,7 +616,7 @@ pub fn typeToValtype(ty: Type, zcu: *const Zcu, target: *const std.Target) std.w
.unrolled => .i32,
},
.@"union", .@"struct" => switch (ty.containerLayout(zcu)) {
- .@"packed" => typeToValtype(ty.bitpackBackingInt(zcu), zcu, target),
+ .@"packed" => typeToValtype(ty.backingIntType(zcu), zcu, target),
.auto, .@"extern" => .i32,
},
else => .i32, // all represented as reference/immediate
@@ -1226,7 +1227,7 @@ fn isByRef(ty: Type, zcu: *const Zcu, target: *const std.Target) bool {
.frame,
=> return ty.hasRuntimeBits(zcu),
.@"struct", .@"union" => switch (ty.containerLayout(zcu)) {
- .@"packed" => return isByRef(ty.bitpackBackingInt(zcu), zcu, target),
+ .@"packed" => return isByRef(ty.backingIntType(zcu), zcu, target),
.@"extern", .auto => return ty.hasRuntimeBits(zcu),
},
.vector => return determineSimdStoreStrategy(ty, zcu, target) == .unrolled,
@@ -1905,6 +1906,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.add_safe,
.sub_safe,
.mul_safe,
+ .bit_cast_safe,
.int_cast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
@@ -5134,7 +5136,7 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue {
return .{ .imm32 = 0xaaaaaaaa };
},
.@"struct", .@"union" => {
- const backing_int_ty = ty.bitpackBackingInt(zcu);
+ const backing_int_ty = ty.backingIntType(zcu);
return cg.emitUndefined(backing_int_ty);
},
else => return cg.fail("Wasm TODO: emitUndefined for type: {t}\n", .{ty.zigTypeTag(zcu)}),
diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig
index dbb942eaed207109e57a2f5a39bf7d5d4f21c0d7..10adf9b858ee9ab1f977218652a1dfa0d37b89ff 100644
--- a/src/codegen/x86_64/CodeGen.zig
+++ b/src/codegen/x86_64/CodeGen.zig
@@ -63,6 +63,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
.reduce_one_elem_to_bit_cast,
.splat_one_elem_to_bit_cast,
+ .expand_bit_cast_safe,
.expand_int_cast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
@@ -67446,6 +67447,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
.int_from_error,
.union_from_enum,
=> try cg.airBitCast(inst),
+ .bit_cast_safe => unreachable,
.block => {
const block = cg.air.unwrapBlock(inst);
if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_enter_block_none);
diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig
index 959814d1bcb5e1248fc6b7c840277e195be41081..a19fd7a57f38f88b727ba883595bc4f434fdc6ea 100644
--- a/src/link/Wasm/Flush.zig
+++ b/src/link/Wasm/Flush.zig
@@ -151,7 +151,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
assert(ip.indexToKey(data.ip_index) == .enum_type);
const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index);
if (!gop.found_existing) {
- const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
+ const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).backingIntType(zcu);
gop.value_ptr.* = .{ .tag_name = .{
.symbol_name = try wasm.internStringFmt("__zig_tag_index_{d}", .{data.ip_index}),
.type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .u32, false, target),
diff --git a/src/print_value.zig b/src/print_value.zig
index 54d81d50d26abba08d8bde2d60820c8b652f02f9..b659b53df4031797d4b8cf4212c7365ad0ca5f4f 100644
--- a/src/print_value.zig
+++ b/src/print_value.zig
@@ -94,15 +94,14 @@ pub fn print(
enum_literal.fmt(ip),
}),
.enum_tag => |enum_tag| {
- const enum_type = ip.loadEnumType(val.typeOf(zcu).toIntern());
- if (enum_type.tagValueIndex(ip, enum_tag.int)) |tag_index| {
- return writer.print(".{f}", .{enum_type.field_names.get(ip)[tag_index].fmt(ip)});
+ const ty: Type = .fromInterned(enum_tag.ty);
+ const enum_obj = ip.loadEnumType(ty.toIntern());
+ if (enum_obj.tagValueIndex(ip, enum_tag.int)) |tag_index| {
+ return writer.print(".{f}", .{enum_obj.field_names.get(ip)[tag_index].fmt(ip)});
}
- if (level == 0) {
- return writer.writeAll("@enumFromInt(...)");
- }
- try writer.writeAll("@enumFromInt(");
- try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema);
+ try writer.writeAll("@fromBackingInt(");
+ if (level == 0) return writer.writeAll("...)");
+ try print(.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema);
try writer.writeAll(")");
},
.float => |float| switch (float.storage) {
@@ -190,10 +189,17 @@ pub fn print(
try writer.writeAll(" }");
return;
},
- .@"union" => {
- try writer.print("@bitCast(@as({f}, ", .{ty.bitpackBackingInt(zcu).fmt(pt)});
- try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema);
- try writer.writeAll("))");
+ .@"union" => switch (ty.backingIntMode(zcu)) {
+ .auto => {
+ try writer.print("@bitCast(@as({f}, ", .{ty.backingIntType(zcu).fmt(pt)});
+ try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema);
+ try writer.writeAll("))");
+ },
+ .explicit => {
+ try writer.writeAll("@fromBackingInt(");
+ try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema);
+ try writer.writeAll(")");
+ },
},
else => unreachable,
}
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 74aaf0bda3e6406fefa28610ed092dce5bc583c8..375210445b0a3efcc9615c180d5e4c721b8b3b09 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -193,6 +193,7 @@ const Writer = struct {
.elem_type,
.indexable_ptr_elem_type,
.splat_op_result_ty,
+ .from_backing_int_arg_ty,
.indexable_ptr_len,
.anyframe_type,
.bit_not,
@@ -232,6 +233,7 @@ const Writer = struct {
.compile_error,
.set_eval_branch_quota,
.int_from_enum,
+ .backing_int,
.align_of,
.int_from_bool,
.embed_file,
@@ -418,6 +420,8 @@ const Writer = struct {
.for_len => try self.writePlNodeMultiOp(stream, inst),
+ .from_backing_int => try self.writePlNodeBin(stream, inst),
+
.elem_val_imm => try self.writeElemValImm(stream, inst),
.@"export" => try self.writePlNodeExport(stream, inst),
@@ -985,6 +989,16 @@ const Writer = struct {
try self.writeSrcNode(stream, inst_data.src_node);
}
+ fn writeFromBackingInt(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+ const extra = self.code.extraData(Zir.Inst.FromBackingInt, inst_data.payload_index);
+ try self.writeInstRef(stream, extra.data.result_type);
+ try stream.writeAll(", ");
+ try self.writeBracedBody(stream, self.code.bodySlice(extra.end, extra.data.body_len));
+ try stream.writeAll(") ");
+ try self.writeSrcNode(stream, inst_data.src_node);
+ }
+
fn writeBuiltinCall(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = self.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;
diff --git a/test/behavior.zig b/test/behavior.zig
index 25c39e172eb6d27c12defc046b5513bb32cff7ac..b64c883d9a6489eeba791ad50254555e4a27214c 100644
--- a/test/behavior.zig
+++ b/test/behavior.zig
@@ -6,6 +6,7 @@ test {
_ = @import("behavior/alignof.zig");
_ = @import("behavior/array.zig");
_ = @import("behavior/atomics.zig");
+ _ = @import("behavior/backing_int.zig");
_ = @import("behavior/basic.zig");
_ = @import("behavior/bit_shifting.zig");
_ = @import("behavior/bitcast.zig");
diff --git a/test/behavior/backing_int.zig b/test/behavior/backing_int.zig
new file mode 100644
index 0000000000000000000000000000000000000000..a11976c72063f5f662ecab540e8b3e1ff08e2c47
--- /dev/null
+++ b/test/behavior/backing_int.zig
@@ -0,0 +1,378 @@
+const builtin = @import("builtin");
+const std = @import("std");
+const expect = std.testing.expect;
+const assert = std.debug.assert;
+
+const E1 = enum(u8) {
+ a,
+ b,
+ c,
+ d,
+ const expected = .{
+ .val = @as(E1, .b),
+ .int = @as(@typeInfo(E1).@"enum".tag_type, 1),
+ };
+};
+const E2 = enum(i20) {
+ x,
+ y,
+ z = -5,
+ const expected = .{
+ .val = @as(E2, .z),
+ .int = @as(@typeInfo(E2).@"enum".tag_type, -5),
+ };
+};
+const E3 = enum(i32) {
+ _,
+ const zero: E3 = @bitCast(@as(i32, 0));
+ const expected = .{
+ .val = @as(E3, .zero),
+ .int = @as(@typeInfo(E3).@"enum".tag_type, 0),
+ };
+};
+const E4 = enum(i200) {
+ min = -(1 << 199),
+ const expected = .{
+ .val = @as(E4, .min),
+ .int = @as(@typeInfo(E4).@"enum".tag_type, -(1 << 199)),
+ };
+};
+const E5 = enum(u0) {
+ a,
+ const expected = .{
+ .val = @as(E5, .a),
+ .int = @as(@typeInfo(E5).@"enum".tag_type, 0),
+ };
+};
+
+test "@backingInt with enums" {
+ const static = struct {
+ fn doTheTest(v1: E1, v2: E2, v3: E3, v4: E4, v5: E5) !void {
+ const b1 = @backingInt(v1);
+ comptime assert(@TypeOf(b1) == @typeInfo(E1).@"enum".tag_type);
+ try expect(b1 == E1.expected.int);
+
+ const b2 = @backingInt(v2);
+ comptime assert(@TypeOf(b2) == @typeInfo(E2).@"enum".tag_type);
+ try expect(b2 == E2.expected.int);
+
+ const b3 = @backingInt(v3);
+ comptime assert(@TypeOf(b3) == @typeInfo(E3).@"enum".tag_type);
+ try expect(b3 == E3.expected.int);
+
+ const b4 = @backingInt(v4);
+ comptime assert(@TypeOf(b4) == @typeInfo(E4).@"enum".tag_type);
+ try expect(b4 == E4.expected.int);
+
+ const b5 = @backingInt(v5);
+ comptime assert(@TypeOf(b5) == @typeInfo(E5).@"enum".tag_type);
+ try expect(b5 == E5.expected.int);
+ }
+ };
+ try static.doTheTest(E1.expected.val, E2.expected.val, E3.expected.val, E4.expected.val, E5.expected.val);
+ try comptime static.doTheTest(E1.expected.val, E2.expected.val, E3.expected.val, E4.expected.val, E5.expected.val);
+}
+
+test "@fromBackingInt with enums" {
+ const static = struct {
+ fn doTheTest(
+ b1: @typeInfo(E1).@"enum".tag_type,
+ b2: @typeInfo(E2).@"enum".tag_type,
+ b3: @typeInfo(E3).@"enum".tag_type,
+ b4: @typeInfo(E4).@"enum".tag_type,
+ b5: @typeInfo(E5).@"enum".tag_type,
+ ) !void {
+ const v1: E1 = @fromBackingInt(b1);
+ try expect(v1 == E1.expected.val);
+
+ const v2: E2 = @fromBackingInt(b2);
+ try expect(v2 == E2.expected.val);
+
+ const v3: E3 = @fromBackingInt(b3);
+ try expect(v3 == E3.expected.val);
+
+ const v4: E4 = @fromBackingInt(b4);
+ try expect(v4 == E4.expected.val);
+
+ const v5: E5 = @fromBackingInt(b5);
+ try expect(v5 == E5.expected.val);
+ }
+ };
+ try static.doTheTest(E1.expected.int, E2.expected.int, E3.expected.int, E4.expected.int, E5.expected.int);
+ try comptime static.doTheTest(E1.expected.int, E2.expected.int, E3.expected.int, E4.expected.int, E5.expected.int);
+}
+
+const T1 = union(E1) {
+ a: u8,
+ b: []const u16,
+ c: []const u8,
+ d: i32,
+ const expected = .{
+ .val = @unionInit(T1, @tagName(E1.expected.val), &.{ 1, 2, 3 }),
+ .int = E1.expected.int,
+ };
+};
+const T2 = union(E2) {
+ x,
+ y: i32,
+ z,
+ const expected = .{
+ .val = @unionInit(T2, @tagName(E2.expected.val), {}),
+ .int = E2.expected.int,
+ };
+};
+const T4 = union(E4) {
+ min: f32,
+ const expected = .{
+ .val = @unionInit(T4, @tagName(E4.expected.val), 0.123),
+ .int = E4.expected.int,
+ };
+};
+const T5 = union(E5) {
+ a: u0,
+ const expected = .{
+ .val = @unionInit(T5, @tagName(E5.expected.val), 0),
+ .int = E5.expected.int,
+ };
+};
+
+test "@backingInt with tagged unions" {
+ const static = struct {
+ fn doTheTest(v1: T1, v2: T2, v4: T4, v5: T5) !void {
+ const b1 = @backingInt(v1);
+ comptime assert(@TypeOf(b1) == @typeInfo(@typeInfo(T1).@"union".tag_type.?).@"enum".tag_type);
+ try expect(b1 == E1.expected.int);
+
+ const b2 = @backingInt(v2);
+ comptime assert(@TypeOf(b2) == @typeInfo(@typeInfo(T2).@"union".tag_type.?).@"enum".tag_type);
+ try expect(b2 == E2.expected.int);
+
+ const b4 = @backingInt(v4);
+ comptime assert(@TypeOf(b4) == @typeInfo(@typeInfo(T4).@"union".tag_type.?).@"enum".tag_type);
+ try expect(b4 == E4.expected.int);
+
+ const b5 = @backingInt(v5);
+ comptime assert(@TypeOf(b5) == @typeInfo(@typeInfo(T5).@"union".tag_type.?).@"enum".tag_type);
+ try expect(b5 == E5.expected.int);
+ }
+ };
+ try static.doTheTest(T1.expected.val, T2.expected.val, T4.expected.val, T5.expected.val);
+ try comptime static.doTheTest(T1.expected.val, T2.expected.val, T4.expected.val, T5.expected.val);
+}
+
+const S1 = packed struct(u8) {
+ a: u4,
+ b: i4,
+ const expected = .{
+ .val = @as(S1, .{ .a = 0b1000, .b = 0b0010 }),
+ .int = @as(@typeInfo(S1).@"struct".backing_integer.?, 0b0010_1000),
+ };
+};
+const S2 = packed struct(i20) {
+ a: u10,
+ b: enum(i10) { x, y, z },
+ const expected = .{
+ .val = @as(S2, .{ .a = 0b0011001100, .b = .z }),
+ .int = @as(@typeInfo(S2).@"struct".backing_integer.?, 0b0000000010_0011001100),
+ };
+};
+const S3 = packed struct(i32) {
+ a: packed struct(u12) {
+ x: u8,
+ y: i4,
+ },
+ b: packed union(i20) {
+ x: i20,
+ y: enum(u20) { u, v },
+ },
+ const expected = .{
+ .val = @as(S3, .{ .a = .{ .x = 0b10010001, .y = 0b0110 }, .b = .{ .y = .v } }),
+ .int = @as(@typeInfo(S3).@"struct".backing_integer.?, 0b00000000000000000001_0110_10010001),
+ };
+};
+const S4 = packed struct(i200) {
+ a: u200,
+ const expected = .{
+ .val = @as(S4, .{ .a = (1 << 199) + 10 }),
+ .int = @as(@typeInfo(S4).@"struct".backing_integer.?, @bitCast(@as(u200, (1 << 199) + 10))),
+ };
+};
+const S5 = packed struct(u0) {
+ a: u0,
+ const expected = .{
+ .val = @as(S5, .{ .a = 0 }),
+ .int = @as(@typeInfo(S5).@"struct".backing_integer.?, 0),
+ };
+};
+
+test "@backingInt with packed structs" {
+ const static = struct {
+ fn doTheTest(v1: S1, v2: S2, v3: S3, v4: S4, v5: S5) !void {
+ const b1 = @backingInt(v1);
+ comptime assert(@TypeOf(b1) == @typeInfo(S1).@"struct".backing_integer.?);
+ try expect(b1 == S1.expected.int);
+
+ const b2 = @backingInt(v2);
+ comptime assert(@TypeOf(b2) == @typeInfo(S2).@"struct".backing_integer.?);
+ try expect(b2 == S2.expected.int);
+
+ const b3 = @backingInt(v3);
+ comptime assert(@TypeOf(b3) == @typeInfo(S3).@"struct".backing_integer.?);
+ try expect(b3 == S3.expected.int);
+
+ const b4 = @backingInt(v4);
+ comptime assert(@TypeOf(b4) == @typeInfo(S4).@"struct".backing_integer.?);
+ try expect(b4 == S4.expected.int);
+
+ const b5 = @backingInt(v5);
+ comptime assert(@TypeOf(b5) == @typeInfo(S5).@"struct".backing_integer.?);
+ try expect(b5 == S5.expected.int);
+ }
+ };
+ try static.doTheTest(S1.expected.val, S2.expected.val, S3.expected.val, S4.expected.val, S5.expected.val);
+ try comptime static.doTheTest(S1.expected.val, S2.expected.val, S3.expected.val, S4.expected.val, S5.expected.val);
+}
+
+test "@fromBackingInt with packed structs" {
+ const static = struct {
+ fn doTheTest(
+ b1: @typeInfo(S1).@"struct".backing_integer.?,
+ b2: @typeInfo(S2).@"struct".backing_integer.?,
+ b3: @typeInfo(S3).@"struct".backing_integer.?,
+ b4: @typeInfo(S4).@"struct".backing_integer.?,
+ b5: @typeInfo(S5).@"struct".backing_integer.?,
+ ) !void {
+ const v1: S1 = @fromBackingInt(b1);
+ try expect(v1 == S1.expected.val);
+
+ const v2: S2 = @fromBackingInt(b2);
+ try expect(v2 == S2.expected.val);
+
+ const v3: S3 = @fromBackingInt(b3);
+ try expect(v3 == S3.expected.val);
+
+ const v4: S4 = @fromBackingInt(b4);
+ try expect(v4 == S4.expected.val);
+
+ const v5: S5 = @fromBackingInt(b5);
+ try expect(v5 == S5.expected.val);
+ }
+ };
+ try static.doTheTest(S1.expected.int, S2.expected.int, S3.expected.int, S4.expected.int, S5.expected.int);
+ try comptime static.doTheTest(S1.expected.int, S2.expected.int, S3.expected.int, S4.expected.int, S5.expected.int);
+}
+
+const U1 = packed union(u8) {
+ a: u8,
+ b: i8,
+ const expected = .{
+ .val = @as(U1, .{ .b = -123 }),
+ .int = @as(@typeInfo(U1).@"union".backing_integer.?, @bitCast(@as(i8, -123))),
+ };
+};
+const U2 = packed union(i20) {
+ a: u20,
+ b: enum(i20) { x, y, z },
+ const expected = .{
+ .val = @as(U2, .{ .b = .z }),
+ .int = @as(@typeInfo(U2).@"union".backing_integer.?, 0b00000000000000000000000000000010),
+ };
+};
+const U3 = packed union(i32) {
+ a: packed struct(u32) {
+ x: u18,
+ y: i14,
+ },
+ b: packed union(u32) {
+ x: i32,
+ y: enum(u32) { u, v },
+ },
+ const expected = .{
+ .val = @as(U3, .{ .b = .{ .y = .v } }),
+ .int = @as(@typeInfo(U3).@"union".backing_integer.?, 0b00000000000000000000000000000001),
+ };
+};
+const U4 = packed union(i200) {
+ a: u200,
+ const expected = .{
+ .val = @as(U4, .{ .a = (1 << 199) + 10 }),
+ .int = @as(@typeInfo(U4).@"union".backing_integer.?, @bitCast(@as(u200, (1 << 199) + 10))),
+ };
+};
+const U5 = packed union(u0) {
+ a: u0,
+ const expected = .{
+ .val = @as(U5, .{ .a = 0 }),
+ .int = @as(@typeInfo(U5).@"union".backing_integer.?, 0),
+ };
+};
+
+test "@backingInt with packed unions" {
+ const static = struct {
+ fn doTheTest(v1: U1, v2: U2, v3: U3, v4: U4, v5: U5) !void {
+ const b1 = @backingInt(v1);
+ comptime assert(@TypeOf(b1) == @typeInfo(U1).@"union".backing_integer.?);
+ try expect(b1 == U1.expected.int);
+
+ const b2 = @backingInt(v2);
+ comptime assert(@TypeOf(b2) == @typeInfo(U2).@"union".backing_integer.?);
+ try expect(b2 == U2.expected.int);
+
+ const b3 = @backingInt(v3);
+ comptime assert(@TypeOf(b3) == @typeInfo(U3).@"union".backing_integer.?);
+ try expect(b3 == U3.expected.int);
+
+ const b4 = @backingInt(v4);
+ comptime assert(@TypeOf(b4) == @typeInfo(U4).@"union".backing_integer.?);
+ try expect(b4 == U4.expected.int);
+
+ const b5 = @backingInt(v5);
+ comptime assert(@TypeOf(b5) == @typeInfo(U5).@"union".backing_integer.?);
+ try expect(b5 == U5.expected.int);
+ }
+ };
+ try static.doTheTest(U1.expected.val, U2.expected.val, U3.expected.val, U4.expected.val, U5.expected.val);
+ try comptime static.doTheTest(U1.expected.val, U2.expected.val, U3.expected.val, U4.expected.val, U5.expected.val);
+}
+
+test "@fromBackingInt with packed unions" {
+ const static = struct {
+ fn doTheTest(
+ b1: @typeInfo(U1).@"union".backing_integer.?,
+ b2: @typeInfo(U2).@"union".backing_integer.?,
+ b3: @typeInfo(U3).@"union".backing_integer.?,
+ b4: @typeInfo(U4).@"union".backing_integer.?,
+ b5: @typeInfo(U5).@"union".backing_integer.?,
+ ) !void {
+ const v1: U1 = @fromBackingInt(b1);
+ try expect(v1 == U1.expected.val);
+
+ const v2: U2 = @fromBackingInt(b2);
+ try expect(v2 == U2.expected.val);
+
+ const v3: U3 = @fromBackingInt(b3);
+ try expect(v3 == U3.expected.val);
+
+ const v4: U4 = @fromBackingInt(b4);
+ try expect(v4 == U4.expected.val);
+
+ const v5: U5 = @fromBackingInt(b5);
+ try expect(v5 == U5.expected.val);
+ }
+ };
+ try static.doTheTest(U1.expected.int, U2.expected.int, U3.expected.int, U4.expected.int, U5.expected.int);
+ try comptime static.doTheTest(U1.expected.int, U2.expected.int, U3.expected.int, U4.expected.int, U5.expected.int);
+}
+
+test "@fromBackingInt provides result type to its argument" {
+ const E = enum(u32) { a, b, c };
+ const static = struct {
+ fn doTheTest(x: u8) !void {
+ const e: E = @fromBackingInt(x);
+ try expect(e == .b);
+ try expect(@backingInt(e) == x);
+ }
+ };
+ try static.doTheTest(1);
+ try comptime static.doTheTest(1);
+}
diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig
index aa664a46ff2a5f96f7b9b8175394676df235b379..93365bb889555b722b4ff83f4ad30d24110a293d 100644
--- a/test/behavior/enum.zig
+++ b/test/behavior/enum.zig
@@ -1465,3 +1465,18 @@ test "enum int tag type uses declaration inside the enum" {
try expect(val == .b);
try expect(@intFromEnum(val) == 1);
}
+
+test "convert from/to backing int" {
+ const E = enum(u33) {
+ a,
+ b,
+ c,
+ fn doTheTest(s: @This()) !void {
+ const backing_int = @backingInt(s);
+ const reconstructed: @This() = @fromBackingInt(backing_int);
+ try expect(reconstructed == s);
+ }
+ };
+ try E.doTheTest(.b);
+ try comptime E.doTheTest(.b);
+}
diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig
index 103d6a21775fdb3590e7ed873ccb07b5210907bd..986c15415486bf9c23b7b879a9d305e1a3cfaed1 100644
--- a/test/behavior/packed-struct.zig
+++ b/test/behavior/packed-struct.zig
@@ -122,7 +122,7 @@ test "correct sizeOf and offsets in packed structs" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
- const PStruct = packed struct {
+ const PStruct = packed struct(u32) {
bool_a: bool,
bool_b: bool,
bool_c: bool,
@@ -162,7 +162,7 @@ test "correct sizeOf and offsets in packed structs" {
try expectEqual(22, @bitOffsetOf(PStruct, "u10_b"));
try expectEqual(4, @sizeOf(PStruct));
- const s1 = @as(PStruct, @bitCast(@as(u32, 0x12345678)));
+ const s1: PStruct = @fromBackingInt(0x12345678);
try expectEqual(false, s1.bool_a);
try expectEqual(false, s1.bool_b);
try expectEqual(false, s1.bool_c);
@@ -176,7 +176,7 @@ test "correct sizeOf and offsets in packed structs" {
try expectEqual(0b1101000101, s1.u10_a);
try expectEqual(0b0001001000, s1.u10_b);
- const s2 = @as(packed struct { x: u1, y: u7, z: u24 }, @bitCast(@as(u32, 0xd5c71ff4)));
+ const s2: packed struct(u32) { x: u1, y: u7, z: u24 } = @fromBackingInt(0xd5c71ff4);
try expectEqual(0, s2.x);
try expectEqual(0b1111010, s2.y);
try expectEqual(0xd5c71f, s2.z);
@@ -191,7 +191,7 @@ test "nested packed structs" {
const S2 = packed struct { d: u8, e: u8, f: u8 };
const S3 = packed struct { x: S1, y: S2 };
- const S3Padded = packed struct { s3: S3, pad: u16 };
+ const S3Padded = packed struct(u64) { s3: S3, pad: u16 };
try expectEqual(48, @bitSizeOf(S3));
try expectEqual(@sizeOf(u48), @sizeOf(S3));
@@ -199,7 +199,7 @@ test "nested packed structs" {
try expectEqual(3, @offsetOf(S3, "y"));
try expectEqual(24, @bitOffsetOf(S3, "y"));
- const s3 = @as(S3Padded, @bitCast(@as(u64, 0xe952d5c71ff4))).s3;
+ const s3 = @as(S3Padded, @fromBackingInt(0xe952d5c71ff4)).s3;
try expectEqual(0xf4, s3.x.a);
try expectEqual(0x1f, s3.x.b);
try expectEqual(0xc7, s3.x.c);
@@ -558,7 +558,7 @@ test "packed struct fields modification" {
// Originally reported at https://github.com/ziglang/zig/issues/16615
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
- const Small = packed struct {
+ const Small = packed struct(u16) {
val: u8 = 0,
lo: u4 = 0,
hi: u4 = 0,
@@ -570,12 +570,12 @@ test "packed struct fields modification" {
.lo = 3,
.hi = 4,
};
- try expect(@as(u16, @bitCast(Small.p)) == 0x4312);
+ try expect(@backingInt(Small.p) == 0x4312);
Small.p.val -= Small.p.lo;
Small.p.val += Small.p.hi;
Small.p.hi -= Small.p.lo;
- try expect(@as(u16, @bitCast(Small.p)) == 0x1313);
+ try expect(@backingInt(Small.p) == 0x1313);
}
test "nested packed struct field access test" {
@@ -1246,3 +1246,18 @@ test "initialize packed struct field to undefined at comptime" {
const val: S = .{ .x = undefined };
_ = val;
}
+
+test "convert from/to backing int" {
+ const S = packed struct(u33) {
+ a: u7,
+ b: enum(u10) { x, y, z },
+ c: f16,
+ fn doTheTest(s: @This()) !void {
+ const backing_int = @backingInt(s);
+ const reconstructed: @This() = @fromBackingInt(backing_int);
+ try expect(reconstructed == s);
+ }
+ };
+ try S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 });
+ try comptime S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 });
+}
diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig
index 20732eab98a312ba077ffde4494e8deec179225d..44090943d487e3b52beae89a32baf5033d034a3d 100644
--- a/test/behavior/packed-union.zig
+++ b/test/behavior/packed-union.zig
@@ -227,3 +227,17 @@ test "initialize packed union field to undefined at comptime" {
const val: U = .{ .x = undefined };
_ = val;
}
+
+test "convert from/to backing int" {
+ const U = packed union(u10) {
+ a: i10,
+ b: enum(u10) { x, y, z },
+ fn doTheTest(u: @This()) !void {
+ const backing_int = @backingInt(u);
+ const reconstructed: @This() = @fromBackingInt(backing_int);
+ try expect(reconstructed == u);
+ }
+ };
+ try U.doTheTest(.{ .a = 123 });
+ try comptime U.doTheTest(.{ .a = 123 });
+}
diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig
index 555eda7f30ef354061a1c30038532641be591b7e..c7323ed1d623ff502da131454bcd55ade52eafb8 100644
--- a/test/behavior/switch.zig
+++ b/test/behavior/switch.zig
@@ -1132,13 +1132,13 @@ test "decl literals as switch cases" {
try comptime E.doTheTest(.foo);
}
-// TODO audit after #15909 and/or #19855 are decided/implemented.
+// TODO audit after https://github.com/ziglang/zig/issues/15909 is fully decided.
// When we do that, consider adding an 'error{}' case if possible.
test "switch with uninstantiable union fields" {
const U = union(enum) {
ok: void,
a: noreturn,
- b: noreturn,
+ b: enum {},
fn doTheTest(u: @This()) void {
switch (u) {
diff --git a/test/behavior/type.zig b/test/behavior/type.zig
index bf012e0061f86384fa985e74353fe4e16c12492b..d4b8a60aa697f80d32aceadfaaeec9f172b9d409 100644
--- a/test/behavior/type.zig
+++ b/test/behavior/type.zig
@@ -266,13 +266,21 @@ test "Type.Union from regular enum" {
test "Type.Union from empty regular enum" {
const E = enum {};
const U = @Union(.auto, E, &.{}, &.{}, &.{});
- try testing.expectEqual(@typeInfo(U).@"union".field_names.len, 0);
+
+ const info = @typeInfo(U).@"union";
+ try testing.expect(info.field_names.len == 0);
+ try testing.expect(info.tag_type != null);
+ try testing.expect(@typeInfo(info.tag_type.?).@"enum".tag_type == noreturn);
}
test "Type.Union from empty Type.Enum" {
const E = @Enum(noreturn, .exhaustive, &.{}, &.{});
const U = @Union(.auto, E, &.{}, &.{}, &.{});
- try testing.expectEqual(@typeInfo(U).@"union".field_names.len, 0);
+
+ const info = @typeInfo(U).@"union";
+ try testing.expect(info.field_names.len == 0);
+ try testing.expect(info.tag_type != null);
+ try testing.expect(@typeInfo(info.tag_type.?).@"enum".tag_type == noreturn);
}
test "Type.Fn" {
diff --git a/test/cases/compile_errors/backing_int_invalid_arg_type.zig b/test/cases/compile_errors/backing_int_invalid_arg_type.zig
new file mode 100644
index 0000000000000000000000000000000000000000..8bc9de52797b13a648165962c38912ec6d965597
--- /dev/null
+++ b/test/cases/compile_errors/backing_int_invalid_arg_type.zig
@@ -0,0 +1,38 @@
+const S1 = extern struct { x: u32 };
+export fn entry1(x: S1) u32 {
+ return @backingInt(x);
+}
+
+const U1 = extern union { x: u32 };
+export fn entry2(x: U1) u32 {
+ return @backingInt(x);
+}
+
+export fn entry3(x: u32) u32 {
+ return @backingInt(x);
+}
+
+const S2 = packed struct { x: u32 };
+export fn entry4(x: u32) u32 {
+ return @backingInt(@as(S2, .{ .x = x }));
+}
+
+const U2 = packed union { x: u32 };
+export fn entry5(x: u32) u32 {
+ return @backingInt(@as(U2, .{ .x = x }));
+}
+
+// error
+//
+// :3:24: error: non-packed struct 'tmp.S1' does not have a backing integer
+// :1:19: note: struct declared here
+// :8:24: error: non-packed union 'tmp.U1' does not have a backing integer
+// :8:24: note: untagged union 'tmp.U1' does not have an enum tag with a backing integer
+// :6:19: note: union declared here
+// :12:24: error: expected enum, tagged union, packed union or packed struct, found 'u32'
+// :17:24: error: @backingInt is ambiguous for type 'tmp.S2'
+// :15:19: note: backing integer type of struct is inferred
+// :15:19: note: consider explicitly specifying the backing integer type
+// :22:24: error: @backingInt is ambiguous for type 'tmp.U2'
+// :20:19: note: backing integer type of union is inferred
+// :20:19: note: consider explicitly specifying the backing integer type
diff --git a/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig b/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig
deleted file mode 100644
index bc04258d26921f4543a7b3b962ee57cab82c9aaa..0000000000000000000000000000000000000000
--- a/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig
+++ /dev/null
@@ -1,23 +0,0 @@
-export fn foo() void {
- const S = struct {
- f: u8,
- };
- _ = @as([@sizeOf(S)]u8, @bitCast([1]S{undefined}));
-}
-
-export fn bar() void {
- const S = struct {
- f: u8,
- };
- _ = @as([1]S, @bitCast(@as([@sizeOf(S)]u8, undefined)));
-}
-
-export fn baz() void {
- _ = @as([1]u32, @bitCast([1]comptime_int{0}));
-}
-
-// error
-//
-// :5:42: error: cannot @bitCast from '[1]tmp.foo.S'
-// :12:19: error: cannot @bitCast to '[1]tmp.bar.S'
-// :16:45: error: cannot @bitCast from '[1]comptime_int'
diff --git a/test/cases/compile_errors/bitcast_invalid_types.zig b/test/cases/compile_errors/bitcast_invalid_types.zig
new file mode 100644
index 0000000000000000000000000000000000000000..fdf25aade9326f1cffb22c8be76956c00511663b
--- /dev/null
+++ b/test/cases/compile_errors/bitcast_invalid_types.zig
@@ -0,0 +1,205 @@
+const y: u32 = 0;
+
+export fn entry1() void {
+ _ = @as(comptime_float, @bitCast(y));
+}
+export fn entry2() void {
+ const x: comptime_float = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry3() void {
+ _ = @as(comptime_int, @bitCast(y));
+}
+export fn entry4() void {
+ const x: comptime_int = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry5() void {
+ _ = @as(@EnumLiteral(), @bitCast(y));
+}
+export fn entry6() void {
+ const x: @EnumLiteral() = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry7() void {
+ _ = @as(error{}, @bitCast(y));
+}
+export fn entry8() void {
+ const x: error{} = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry9() void {
+ _ = @as(?(anyerror!u32), @bitCast(y));
+}
+export fn entry10() void {
+ const x: anyerror!u32 = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry11() void {
+ _ = @as(fn () void, @bitCast(y));
+}
+export fn entry12() void {
+ const x: fn () void = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry13() void {
+ _ = @as(noreturn, @bitCast(y));
+}
+
+export fn entry14() void {
+ _ = @as(@TypeOf(null), @bitCast(y));
+}
+export fn entry15() void {
+ const x: @TypeOf(null) = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry16() void {
+ const O = opaque {};
+ _ = @as(O, @bitCast(y));
+}
+
+export fn entry17() void {
+ _ = @as(??u32, @bitCast(y));
+}
+export fn entry18() void {
+ const x: ?u32 = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry19() void {
+ _ = @as(type, @bitCast(y));
+}
+export fn entry20() void {
+ const x: type = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry21() void {
+ _ = @as(@TypeOf(undefined), @bitCast(y));
+}
+export fn entry22() void {
+ const x: @TypeOf(undefined) = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry23() void {
+ _ = @as(void, @bitCast(y));
+}
+export fn entry24() void {
+ const x: void = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry25() void {
+ _ = @as(*u8, @bitCast(y));
+}
+export fn entry26() void {
+ const x: *u8 = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry27() void {
+ _ = @as(*u8, @bitCast(@as(*u32, @ptrFromInt(y))));
+}
+export fn entry28() void {
+ const x: *u8 = undefined;
+ _ = @as(*u32, @bitCast(x));
+}
+
+export fn entry29() void {
+ const S = struct { x: u32 };
+ _ = @as(S, @bitCast(y));
+}
+export fn entry30() void {
+ const S = struct { x: u32 };
+ const x: S = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry31() void {
+ const U = union { x: u32 };
+ _ = @as(U, @bitCast(y));
+}
+export fn entry32() void {
+ const U = union { x: u32 };
+ const x: U = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry33() void {
+ const S = struct { x: u32 };
+ _ = @as([10]S, @bitCast(y));
+}
+export fn entry34() void {
+ const S = struct { x: u32 };
+ const x: [10]S = undefined;
+ _ = @as(u32, @bitCast(x));
+}
+
+export fn entry35() void {
+ const E = enum {};
+ _ = @as(E, @bitCast(y));
+}
+
+export fn entry36() void {
+ const E = enum { a, b, c };
+ _ = @as(E, @bitCast(y));
+}
+
+// error
+//
+// :4:29: error: cannot @bitCast to 'comptime_float'
+// :8:27: error: cannot @bitCast from 'comptime_float'
+// :12:27: error: cannot @bitCast to 'comptime_int'
+// :16:27: error: cannot @bitCast from 'comptime_int'
+// :20:29: error: cannot @bitCast to '@EnumLiteral()'
+// :24:27: error: cannot @bitCast from '@EnumLiteral()'
+// :28:22: error: cannot @bitCast to 'error{}'
+// :32:27: error: cannot @bitCast from 'error{}'
+// :36:30: error: cannot @bitCast to 'anyerror!u32'
+// :40:27: error: cannot @bitCast from 'anyerror!u32'
+// :44:25: error: cannot @bitCast to 'fn () void'
+// :48:27: error: cannot @bitCast from 'fn () void'
+// :52:23: error: cannot @bitCast to 'noreturn'
+// :56:28: error: cannot @bitCast to '@TypeOf(null)'
+// :60:27: error: cannot @bitCast from '@TypeOf(null)'
+// :65:16: error: cannot @bitCast to 'tmp.entry16.O'
+// :64:15: note: opaque declared here
+// :69:20: error: cannot @bitCast to '?u32'
+// :69:20: note: use @ptrFromInt to cast from 'u32'
+// :73:27: error: cannot @bitCast from '?u32'
+// :73:27: note: use @intFromPtr to cast to 'u32'
+// :77:19: error: cannot @bitCast to 'type'
+// :81:27: error: cannot @bitCast from 'type'
+// :85:33: error: cannot @bitCast to '@TypeOf(undefined)'
+// :89:27: error: cannot @bitCast from '@TypeOf(undefined)'
+// :93:19: error: @bitCast size mismatch: destination type 'void' has 0 bits but source type 'u32' has 32 bits
+// :97:18: error: @bitCast size mismatch: destination type 'u32' has 32 bits but source type 'void' has 0 bits
+// :101:18: error: cannot @bitCast to '*u8'
+// :101:18: note: use @ptrFromInt to cast from 'u32'
+// :105:27: error: cannot @bitCast from '*u8'
+// :105:27: note: use @intFromPtr to cast to 'u32'
+// :109:49: error: pointer type '*u32' does not allow address zero
+// :113:19: error: cannot @bitCast to '*u32'
+// :113:19: note: use @ptrCast to cast from '*u8'
+// :118:16: error: cannot @bitCast to 'tmp.entry29.S'
+// :117:15: note: struct declared here
+// :123:27: error: cannot @bitCast from 'tmp.entry30.S'
+// :121:15: note: struct declared here
+// :128:16: error: cannot @bitCast to 'tmp.entry31.U'
+// :127:15: note: union declared here
+// :133:27: error: cannot @bitCast from 'tmp.entry32.U'
+// :131:15: note: union declared here
+// :138:20: error: cannot @bitCast to '[10]tmp.entry33.S'
+// :143:27: error: cannot @bitCast from '[10]tmp.entry34.S'
+// :148:16: error: cannot @bitCast to 'tmp.entry35.E'
+// :147:15: note: enum declared here
+// :153:16: error: cannot @bitCast to 'tmp.entry36.E'
+// :152:15: note: enum declared here
diff --git a/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig b/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig
new file mode 100644
index 0000000000000000000000000000000000000000..456cb226c1861860424c41483ef42297496d9023
--- /dev/null
+++ b/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig
@@ -0,0 +1,16 @@
+const E = enum(u8) { a, b, c };
+export fn entry1() void {
+ const x: E = @bitCast(@as(u8, 3));
+ _ = x;
+}
+
+export fn entry2() void {
+ const x: E = @bitCast(@as(u8, undefined));
+ _ = x;
+}
+
+// error
+//
+// :3:18: error: enum 'tmp.E' has no tag with value '3'
+// :1:11: note: enum declared here
+// :8:27: error: use of undefined value here causes illegal behavior
diff --git a/test/cases/compile_errors/empty_enum_from_backing_int.zig b/test/cases/compile_errors/empty_enum_from_backing_int.zig
new file mode 100644
index 0000000000000000000000000000000000000000..939ed64765c32791258e2e19b2a1d4b3bfbe5f35
--- /dev/null
+++ b/test/cases/compile_errors/empty_enum_from_backing_int.zig
@@ -0,0 +1,18 @@
+const E = enum(noreturn) {};
+
+export fn entry1() void {
+ const e: E = @fromBackingInt(undefined);
+ _ = e;
+}
+
+export fn entry2() void {
+ const e: E = @fromBackingInt(0);
+ _ = e;
+}
+
+// error
+//
+// :4:34: error: expected type 'noreturn', found '@TypeOf(undefined)'
+// :4:34: note: cannot coerce to uninstantiable type 'noreturn'
+// :9:34: error: expected type 'noreturn', found 'comptime_int'
+// :9:34: note: cannot coerce to uninstantiable type 'noreturn'
diff --git a/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig b/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig
new file mode 100644
index 0000000000000000000000000000000000000000..e24ef72f6e4fa7afe8c21f6c85982fe0b8c73340
--- /dev/null
+++ b/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig
@@ -0,0 +1,37 @@
+const S1 = extern struct { x: u32 };
+export fn entry1(x: u32) void {
+ _ = @as(S1, @fromBackingInt(x));
+}
+
+const U1 = extern union { x: u32 };
+export fn entry2(x: u32) void {
+ _ = @as(U1, @fromBackingInt(x));
+}
+
+export fn entry3(x: u32) void {
+ _ = @as(u32, @fromBackingInt(x));
+}
+
+const S2 = packed struct { x: u32 };
+export fn entry4(x: u32) void {
+ _ = @as(S2, @fromBackingInt(x));
+}
+
+const U2 = packed union { x: u32 };
+export fn entry5(x: u32) u32 {
+ _ = @as(U2, @fromBackingInt(x));
+}
+
+// error
+//
+// :3:17: error: non-packed struct 'tmp.S1' does not have a backing integer
+// :1:19: note: struct declared here
+// :8:17: error: non-packed union 'tmp.U1' does not have a backing integer
+// :6:19: note: union declared here
+// :12:18: error: expected enum, packed union or packed struct, found 'u32'
+// :17:17: error: @fromBackingInt is ambiguous for type 'tmp.S2'
+// :15:19: note: backing integer type of struct is inferred
+// :15:19: note: consider explicitly specifying the backing integer type
+// :22:17: error: @fromBackingInt is ambiguous for type 'tmp.U2'
+// :20:19: note: backing integer type of union is inferred
+// :20:19: note: consider explicitly specifying the backing integer type
diff --git a/test/cases/compile_errors/from_backing_int_type_mismatch.zig b/test/cases/compile_errors/from_backing_int_type_mismatch.zig
new file mode 100644
index 0000000000000000000000000000000000000000..945d483da0adaae103732bdbf46752324de6f986
--- /dev/null
+++ b/test/cases/compile_errors/from_backing_int_type_mismatch.zig
@@ -0,0 +1,23 @@
+const E = enum(u32) { a, b, c };
+export fn entry1(x: u64) E {
+ return @fromBackingInt(x);
+}
+
+const S = packed struct(u32) { x: u32 };
+export fn entry2(x: u64) S {
+ return @fromBackingInt(x);
+}
+
+const U = packed union(u32) { x: u32 };
+export fn entry3(x: u64) U {
+ return @fromBackingInt(x);
+}
+
+// error
+//
+// :3:28: error: expected type 'u32', found 'u64'
+// :3:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values
+// :8:28: error: expected type 'u32', found 'u64'
+// :8:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values
+// :13:28: error: expected type 'u32', found 'u64'
+// :13:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values
diff --git a/test/cases/compile_errors/from_backing_int_undef.zig b/test/cases/compile_errors/from_backing_int_undef.zig
new file mode 100644
index 0000000000000000000000000000000000000000..a45e2a7f9ec3bfaafbbdf2fd3594dee7d8c52057
--- /dev/null
+++ b/test/cases/compile_errors/from_backing_int_undef.zig
@@ -0,0 +1,22 @@
+const E = enum(u32) { x };
+export fn entry1() void {
+ @compileLog(@as(E, @fromBackingInt(undefined)));
+}
+
+const S = packed struct(u32) { x: u32 };
+export fn entry2() void {
+ @compileLog(@as(S, @fromBackingInt(undefined)));
+}
+
+const U = packed union(u32) { x: u32 };
+export fn entry3() void {
+ @compileLog(@as(U, @fromBackingInt(undefined)));
+}
+
+// error
+//
+// :3:40: error: use of undefined value here causes illegal behavior
+//
+// Compile Log Output:
+// @as(tmp.S, undefined)
+// @as(tmp.U, undefined)
diff --git a/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig b/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig
index a6f10668b628fab7b71ee06ce9ca107960ff4686..439b80e13de47f3bcb9f8722ad7576ed5e342edc 100644
--- a/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig
+++ b/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig
@@ -22,5 +22,5 @@ export fn bar() void {
//
// :12:16: error: runtime coercion to union 'tmp.U' from non-exhaustive enum
// :1:11: note: enum declared here
-// :17:16: error: union 'tmp.U' has no tag with value '@enumFromInt(15)'
+// :17:16: error: union 'tmp.U' has no tag with value '@fromBackingInt(15)'
// :6:11: note: union declared here
diff --git a/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig b/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig
index f35817bda75d92bb710da6703b0cdbc5ee81a763..73af4fde6cacddf5ca1a6685c4a0d2601d6b47df 100644
--- a/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig
+++ b/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig
@@ -6,5 +6,5 @@ test "enum" {
// error
// is_test=true
//
-// :3:9: error: no field with value '@enumFromInt(5)' in enum 'tmp.test.enum.E'
+// :3:9: error: no field with value '@fromBackingInt(5)' in enum 'tmp.test.enum.E'
// :2:15: note: declared here
diff --git a/test/cases/safety/backing_int_no_matching_tag_value.zig b/test/cases/safety/backing_int_no_matching_tag_value.zig
new file mode 100644
index 0000000000000000000000000000000000000000..fa359c489242e7e59baa0f3e0de47040ee1eb497
--- /dev/null
+++ b/test/cases/safety/backing_int_no_matching_tag_value.zig
@@ -0,0 +1,25 @@
+const std = @import("std");
+
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "invalid enum value")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+const Foo = enum(u8) {
+ a,
+ b,
+ c,
+};
+pub fn main() !void {
+ _ = bar(3);
+ return error.TestFailed;
+}
+fn bar(a: u8) Foo {
+ return @fromBackingInt(a);
+}
+
+// run
+// backend=selfhosted,llvm
+// target=x86_64-linux,aarch64-linux
diff --git a/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig b/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig
new file mode 100644
index 0000000000000000000000000000000000000000..d1393575db10d1a285245ffae003d5e9635a2bf2
--- /dev/null
+++ b/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig
@@ -0,0 +1,25 @@
+const std = @import("std");
+
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
+ _ = stack_trace;
+ if (std.mem.eql(u8, message, "invalid enum value")) {
+ std.process.exit(0);
+ }
+ std.process.exit(1);
+}
+const Foo = enum(u8) {
+ a,
+ b,
+ c,
+};
+pub fn main() !void {
+ _ = bar(3);
+ return error.TestFailed;
+}
+fn bar(a: u8) Foo {
+ return @bitCast(a);
+}
+
+// run
+// backend=selfhosted,llvm
+// target=x86_64-linux,aarch64-linux
--
2.54.0
From 4be6e74b5aeed95f4ed9f2a1c3377149c37c781d Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Thu, 16 Jul 2026 19:05:15 +0200
Subject: [PATCH 3/7] Legalize: improve
`expand_bit_cast_safe`/`expand_int_cast_safe` codegen if no checks are
necessary
Legalize would previously emit something like this for expansions of
`bit_cast_safe`/`int_cast_safe` if it determined that the requested safety
checks were superfluous (e.g. `bit_cast_safe` to anything but an enum):
```
%1 = block({
%2 = bit_cast(%dest_ty, %operand)
%3 = %br(%1, %2)
})
```
The `block` does absolutely nothing here, ideally we'd just want a plain
`bit_cast` (or `int_cast`) instead:
```
%1 = bit_cast(%dest_ty, %operand)
```
which is exactly what this commit implements by checking whether a safety
check is even necessary before emitting anything and replacing the `_safe`
variant of each inst with the corresponding 'unsafe' variant directly if
it isn't.
---
src/Air/Legalize.zig | 98 +++++++++++++++++++++-----------------------
1 file changed, 47 insertions(+), 51 deletions(-)
diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig
index bd4f88158161b993768e5af3fb8369d5d49e422f..0feca2b747ac0203bb340ac1475e6c37d5afa9ec 100644
--- a/src/Air/Legalize.zig
+++ b/src/Air/Legalize.zig
@@ -605,7 +605,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
}
},
.bit_cast_safe => if (l.features.has(.expand_bit_cast_safe)) {
- continue :inst l.replaceInst(inst, .block, try l.safeBitcastBlockPayload(inst));
+ if (try l.safeBitcastBlockPayload(inst)) |payload| {
+ continue :inst l.replaceInst(inst, .block, payload);
+ }
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
+ continue :inst l.replaceInst(inst, .bit_cast, .{ .ty_op = ty_op });
} else if (l.features.hasAny(&.{
.scalarize_bit_cast_array,
.scalarize_bit_cast_vector_non_elementwise,
@@ -617,7 +621,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
},
.int_cast_safe => if (l.features.has(.expand_int_cast_safe)) {
assert(!l.features.has(.scalarize_int_cast_safe)); // it doesn't make sense to do both
- continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
+ if (try l.safeIntcastBlockPayload(inst)) |payload| {
+ continue :inst l.replaceInst(inst, .block, payload);
+ }
+ const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
+ continue :inst l.replaceInst(inst, .int_cast, .{ .ty_op = ty_op });
} else if (l.features.has(.scalarize_int_cast_safe)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) {
@@ -2125,7 +2133,7 @@ fn scalarizeReduceBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimize
} };
}
-fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
+fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!?Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
@@ -2133,7 +2141,14 @@ fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
const operand_ref = ty_op.operand;
const dest_ty = ty_op.ty.toType();
- // The worst case is a bitcast to an exhaustive enum and looks like this:
+ if (dest_ty.zigTypeTag(zcu) != .@"enum" or
+ dest_ty.isNonexhaustiveEnum(zcu) or
+ !zcu.backendSupportsFeature(.is_named_enum_value))
+ {
+ return null;
+ }
+
+ // We are building this:
//
// %x = block({
// %1 = bit_cast(@res_ty, %y)
@@ -2148,54 +2163,32 @@ fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
var inst_buf: [6]Air.Inst.Index = undefined;
try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
- var opt_condbr: ?CondBr = null;
- var main_block: Block = .init(&inst_buf);
- var cur_block: *Block = &main_block;
+ var block: Block = .init(&inst_buf);
- const cast_inst = cur_block.addBitCast(l, dest_ty, operand_ref);
-
- if (dest_ty.zigTypeTag(zcu) == .@"enum" and
- !dest_ty.isNonexhaustiveEnum(zcu) and
- zcu.backendSupportsFeature(.is_named_enum_value))
- {
- // We are building this:
- // %1 = is_named_enum_value(%cast_inst)
- // %2 = cond_br(%1, {
- //
- // }, {
- //
- // })
- const is_named_inst = cur_block.add(l, .{
- .tag = .is_named_enum_value,
- .data = .{ .un_op = cast_inst },
- });
- opt_condbr = .init(l, is_named_inst.toRef(), cur_block, .{ .false = .cold });
- const condbr = &(opt_condbr.?);
- condbr.else_block = .init(cur_block.stealRemainingCapacity());
- try condbr.else_block.addPanic(l, .invalid_enum_value);
- condbr.then_block = .init(condbr.else_block.stealRemainingCapacity());
- cur_block = &condbr.then_block;
- }
- // Finally, just `br` to our outer `block`.
- _ = cur_block.add(l, .{
- .tag = .br,
- .data = .{ .br = .{
- .block_inst = orig_inst,
- .operand = cast_inst,
- } },
+ const cast_inst = block.addBitCast(l, dest_ty, operand_ref);
+ const is_named_inst = block.add(l, .{
+ .tag = .is_named_enum_value,
+ .data = .{ .un_op = cast_inst },
});
- // We might not have used all of the instructions; that's intentional.
- _ = cur_block.stealRemainingCapacity();
- if (opt_condbr) |condbr| try condbr.finish(l);
+ var condbr: CondBr = .init(l, is_named_inst.toRef(), &block, .{ .false = .cold });
+
+ condbr.then_block = .init(block.stealRemainingCapacity());
+ condbr.then_block.addBr(l, orig_inst, cast_inst);
+
+ condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
+ try condbr.else_block.addPanic(l, .invalid_enum_value);
+
+ try condbr.finish(l);
+
return .{ .ty_pl = .{
.ty = .fromType(dest_ty),
- .payload = try l.addBlockBody(main_block.body()),
+ .payload = try l.addBlockBody(block.body()),
} };
}
-fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
+fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!?Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
@@ -2214,6 +2207,9 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
.@"enum" => true,
else => unreachable,
};
+ const have_enum_value_check = dest_is_enum and
+ !dest_ty.isNonexhaustiveEnum(zcu) and
+ zcu.backendSupportsFeature(.is_named_enum_value);
const operand_info = operand_scalar_ty.intInfo(zcu);
const dest_info = dest_scalar_ty.intInfo(zcu);
@@ -2229,6 +2225,10 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
};
};
+ if (!have_enum_value_check and !have_min_check and !have_max_check) {
+ return null;
+ }
+
// The worst-case scenario in terms of total instructions and total condbrs is the case where
// the result type is an exhaustive enum whose tag type is smaller than the operand type:
//
@@ -2324,7 +2324,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
} },
});
// For ints we're already done, but for exhaustive enums we must check this is a valid tag.
- if (dest_is_enum and !dest_ty.isNonexhaustiveEnum(zcu) and zcu.backendSupportsFeature(.is_named_enum_value)) {
+ if (have_enum_value_check) {
assert(!is_vector); // vectors of enums don't exist
// We are building this:
// %1 = is_named_enum_value(%cast_inst)
@@ -2346,16 +2346,12 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
cur_block = &condbr.then_block;
}
// Finally, just `br` to our outer `block`.
- _ = cur_block.add(l, .{
- .tag = .br,
- .data = .{ .br = .{
- .block_inst = orig_inst,
- .operand = cast_inst.toRef(),
- } },
- });
+ cur_block.addBr(l, orig_inst, cast_inst.toRef());
+
// We might not have used all of the instructions; that's intentional.
_ = cur_block.stealRemainingCapacity();
+ assert(condbr_idx != 0); // should have already returned `null`
for (condbr_buf[0..condbr_idx]) |*condbr| try condbr.finish(l);
return .{ .ty_pl = .{
.ty = Air.internedToRef(dest_ty.toIntern()),
--
2.54.0
From 919a5dd8ce8c64d7b9a924ea12215fc5f0227e51 Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Mon, 29 Jun 2026 19:53:11 +0200
Subject: [PATCH 4/7] test/behavior: skip comparisons of bitpacks >128bits for
x86_64 backend
---
test/behavior/backing_int.zig | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/test/behavior/backing_int.zig b/test/behavior/backing_int.zig
index a11976c72063f5f662ecab540e8b3e1ff08e2c47..1ab8b5970e1d93eaf7b7b1196cee5cf51ab5622f 100644
--- a/test/behavior/backing_int.zig
+++ b/test/behavior/backing_int.zig
@@ -251,8 +251,11 @@ test "@fromBackingInt with packed structs" {
const v3: S3 = @fromBackingInt(b3);
try expect(v3 == S3.expected.val);
- const v4: S4 = @fromBackingInt(b4);
- try expect(v4 == S4.expected.val);
+ // https://codeberg.org/ziglang/zig/issues/35982
+ if (builtin.zig_backend != .stage2_x86_64) {
+ const v4: S4 = @fromBackingInt(b4);
+ try expect(v4 == S4.expected.val);
+ }
const v5: S5 = @fromBackingInt(b5);
try expect(v5 == S5.expected.val);
@@ -353,8 +356,11 @@ test "@fromBackingInt with packed unions" {
const v3: U3 = @fromBackingInt(b3);
try expect(v3 == U3.expected.val);
- const v4: U4 = @fromBackingInt(b4);
- try expect(v4 == U4.expected.val);
+ // https://codeberg.org/ziglang/zig/issues/35982
+ if (builtin.zig_backend != .stage2_x86_64) {
+ const v4: U4 = @fromBackingInt(b4);
+ try expect(v4 == U4.expected.val);
+ }
const v5: U5 = @fromBackingInt(b5);
try expect(v5 == U5.expected.val);
--
2.54.0
From 274949fdbb7a98d374d5e994110dbfd639c70acf Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Mon, 29 Jun 2026 22:12:49 +0200
Subject: [PATCH 5/7] wasm: lower `packed union` backing integer type correctly
`IntType.fromType` would previously set the signedness of any backing int
of a `packed union` to `unsigned` unconditionally which caused comparisons
of packed unions with large, signed backing integers to miscompile.
Packed unions are now lowered correctly to their actual backing int type.
---
src/codegen/wasm/CodeGen.zig | 16 ++++------------
test/behavior/packed-struct.zig | 13 +++++++++++++
test/behavior/packed-union.zig | 13 +++++++++++++
3 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig
index 8d203139b8854c4913902087f033d26af520c60d..284bce953a0590c6c9a602db78714aa51ad349b4 100644
--- a/src/codegen/wasm/CodeGen.zig
+++ b/src/codegen/wasm/CodeGen.zig
@@ -2527,18 +2527,10 @@ const IntType = struct {
.f16, .f32, .f64, .f80, .f128, .c_longdouble => unreachable,
.anyopaque, .void, .type, .comptime_int, .comptime_float, .noreturn, .null, .undefined, .enum_literal, .generic_poison => unreachable,
},
- .struct_type => {
- const loaded_struct = ip.loadStructType(ty_index);
- switch (loaded_struct.layout) {
- .auto, .@"extern" => unreachable,
- .@"packed" => ty_index = loaded_struct.packed_backing_int_type,
- }
- },
- .union_type => return switch (ip.loadUnionType(ty_index).layout) {
- .auto, .@"extern" => unreachable,
- .@"packed" => .{ .is_signed = false, .bits = @intCast(ty.bitSize(zcu)) },
- },
- .enum_type => ty_index = ip.loadEnumType(ty_index).int_tag_type,
+ .enum_type,
+ .struct_type,
+ .union_type,
+ => ty_index = Type.fromInterned(ty_index).backingIntType(zcu).toIntern(),
.error_set_type, .inferred_error_set_type => return .{ .is_signed = false, .bits = zcu.errorSetBits() },
else => unreachable,
};
diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig
index 986c15415486bf9c23b7b879a9d305e1a3cfaed1..96585d056cdf4c13b1aff3610ad08088fc0bf61c 100644
--- a/test/behavior/packed-struct.zig
+++ b/test/behavior/packed-struct.zig
@@ -1261,3 +1261,16 @@ test "convert from/to backing int" {
try S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 });
try comptime S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 });
}
+
+test "equality with wide backing integer" {
+ if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35982
+
+ const S = packed struct(i200) {
+ x: u200,
+ fn doTheTest(s: @This(), int: i200) !void {
+ try expect(s == @as(@This(), @bitCast(int)));
+ }
+ };
+ try S.doTheTest(.{ .x = (1 << 200) - 1 }, -1);
+ try comptime S.doTheTest(.{ .x = (1 << 200) - 1 }, -1);
+}
diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig
index 44090943d487e3b52beae89a32baf5033d034a3d..4fd25526ce4a37deef7233b5b71120742604ce80 100644
--- a/test/behavior/packed-union.zig
+++ b/test/behavior/packed-union.zig
@@ -241,3 +241,16 @@ test "convert from/to backing int" {
try U.doTheTest(.{ .a = 123 });
try comptime U.doTheTest(.{ .a = 123 });
}
+
+test "equality with wide backing integer" {
+ if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35982
+
+ const U = packed union(i200) {
+ x: u200,
+ fn doTheTest(s: @This(), int: i200) !void {
+ try expect(s == @as(@This(), @bitCast(int)));
+ }
+ };
+ try U.doTheTest(.{ .x = (1 << 200) - 1 }, -1);
+ try comptime U.doTheTest(.{ .x = (1 << 200) - 1 }, -1);
+}
--
2.54.0
From 0c6153d82773f47efb5d7d7ce3746f741919a6fc Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Fri, 3 Jul 2026 12:22:31 +0200
Subject: [PATCH 6/7] std.zig.Ast.Render: canonicalize `@intFromEnum(x)` and
`@enumFromInt(x)` to `@backingInt(x)` and `@fromBackingInt(@intCast(x))`
In other words, implements a `zig fmt` fixup.
---
lib/std/zig/Ast/Render.zig | 21 +++++++++++--
lib/std/zig/parser_test.zig | 60 +++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig
index 8f5b2e2c276f5b8680fb56173154bcbdbf8966e6..2c430ca716fcaace9e76ad67d80348ff3a88fb66 100644
--- a/lib/std/zig/Ast/Render.zig
+++ b/lib/std/zig/Ast/Render.zig
@@ -1652,7 +1652,17 @@ fn renderBuiltinCall(
const tree = r.tree;
const ais = r.ais;
- try renderToken(r, builtin_token, .none); // @name
+ // remove before 0.18.0 is released
+ const builtin_token_slice = tree.tokenSlice(builtin_token); // @name
+ const lexeme: []const u8, const have_int_cast: bool = lexeme: {
+ if (mem.eql(u8, builtin_token_slice, "@intFromEnum"))
+ break :lexeme .{ "@backingInt", false };
+ if (mem.eql(u8, builtin_token_slice, "@enumFromInt"))
+ break :lexeme .{ "@fromBackingInt(@intCast", true };
+ break :lexeme .{ builtin_token_slice, false };
+ };
+ try ais.writeAll(lexeme);
+ try renderSpace(r, builtin_token, builtin_token_slice.len, .none);
if (r.fixups.rebase_imported_paths) |prefix| {
const slice = tree.tokenSlice(builtin_token);
@@ -1675,7 +1685,14 @@ fn renderBuiltinCall(
}
}
- return renderParamList(r, builtin_token + 1, params, space);
+ try renderParamList(r, builtin_token + 1, params, .skip); // space is rendered below
+ if (have_int_cast) try ais.writeAll(")");
+ const rparen: Ast.TokenIndex = rparen: {
+ if (params.len == 0) break :rparen builtin_token + 1 + 1;
+ const after_last_param_tok = tree.lastToken(params[params.len - 1]) + 1;
+ break :rparen after_last_param_tok + @intFromBool(tree.tokenTag(after_last_param_tok) == .comma);
+ };
+ return renderSpace(r, rparen, tokenSliceForRender(tree, rparen).len, space);
}
fn fnProtoRparen(tree: Ast, fn_proto: Ast.full.FnProto, maybe_bang: Ast.TokenIndex) Ast.TokenIndex {
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index c471a2d20f1c5d9d8a7d75ce9e2bde4502418e43..7e149d14fdee9105ac1b17c9e70f46197c81f638 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -6938,6 +6938,66 @@ test "zig fmt: inner over-indented if expressions becoming multiline" {
);
}
+test "zig fmt: canonicalize @intFromEnum(x) to @backingInt(x)" {
+ try testTransform(
+ \\const a = @intFromEnum(x);
+ \\
+ \\const b = @intFromEnum(
+ \\ x,
+ \\);
+ \\
+ \\const c = @intFromEnum(x); // comment preserved
+ \\
+ \\const d = @intFromEnum( // comment 1 preserved
+ \\ x, // comment 2 preserved
+ \\); // comment 3 preserved
+ \\
+ ,
+ \\const a = @backingInt(x);
+ \\
+ \\const b = @backingInt(
+ \\ x,
+ \\);
+ \\
+ \\const c = @backingInt(x); // comment preserved
+ \\
+ \\const d = @backingInt( // comment 1 preserved
+ \\ x, // comment 2 preserved
+ \\); // comment 3 preserved
+ \\
+ );
+}
+
+test "zig fmt: canonicalize @enumFromInt(x) to @fromBackingInt(@intCast(x))" {
+ try testTransform(
+ \\const a: E = @enumFromInt(x);
+ \\
+ \\const b: E = @enumFromInt(
+ \\ x,
+ \\);
+ \\
+ \\const c: E = @enumFromInt(x); // comment preserved
+ \\
+ \\const d: E = @enumFromInt( // comment 1 preserved
+ \\ x, // comment 2 preserved
+ \\); // comment 3 preserved
+ \\
+ ,
+ \\const a: E = @fromBackingInt(@intCast(x));
+ \\
+ \\const b: E = @fromBackingInt(@intCast(
+ \\ x,
+ \\));
+ \\
+ \\const c: E = @fromBackingInt(@intCast(x)); // comment preserved
+ \\
+ \\const d: E = @fromBackingInt(@intCast( // comment 1 preserved
+ \\ x, // comment 2 preserved
+ \\)); // comment 3 preserved
+ \\
+ );
+}
+
test "recovery: top level" {
try testError(
\\test "" {inline}
--
2.54.0
From b203181a8a12c3cef0d9e11269c777bf677cb5aa Mon Sep 17 00:00:00 2001
From: Matthew Lugg
Date: Fri, 17 Jul 2026 10:00:47 +0100
Subject: [PATCH 7/7] stage1: update zig1.wasm
Necessary because of the introduction of `@backingInt`/`@fromBackingInt`.
Generated with wasm-opt version 131.
Signed-off-by: Matthew Lugg
---
stage1/zig1.wasm | Bin 3212254 -> 3223935 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/stage1/zig1.wasm b/stage1/zig1.wasm
index 6a222c0c6138663c1a36630a48e164f351fd17b7..1664bd4095df86103a6e4bacfc682054a0cb8fc1 100644
GIT binary patch
delta 679652
zcmcG%2YeJa^FMsj?zx@Ixt<$vXPOPB_s)_B0wD=0q>=(@BtQ!3dGaJS-4ugi2{PUE
zUJaZ>HN6vx3C)1%gc3|QVBr1E?wx&iHYxu9Uif^h8)+nsMx&858fi}q+Qc58-ozFM
z`bcx4W@QHZ^yTVkziyeg{9fj2yMUXruVi3q)4&rl^Y=EW<$_|=MnSD2&E6)1iJ6QY
zjlOXPFDbzItd}v+n=^HIPz@%jtAY+PgPLYO>Ce5Gm&xQMNnS>-{x`TqE6FHJ5|?FJ
z;v9h&{gh?=^rAQ!pccb{NAda}Kcf%Re`ekZ{)waPufl$;l^=0E;zq>Hh+7f2Bkn}p
zjVOq?7jZx0K}2E1!-z)_k0bh6CiqVBz3p=_Vu0U3zu{`R@H2I98*_c;@FPBleFpNo
zmb;c={Hm`SIVy5wvErWdh`K@@r1O4Z)S!&&g?|4>*Ixphqdb2Yo
zSP~>dBdI{P`YDS6#dAYLo@})GS^a7lW*VCs#u=5pE2mqmtRXi5CtZEZ62sFoGJmk7
z@!)ZhHipP;#vWFiwSsbga%Ob&2aNC9keM9wfr*b;ub!ONh5tEU?fSojfWpZWEK0Pw
zNwi6syIac2!v|Otqq(+KQuKwv2~xf>ZWwRG4NCS(
zby~ad*8Z%{(
zMcHLgW*;7DF=sG?NfBcav(+_b)I`K=afvb4HJHrxfP0c~T~m~+;}EmM1y?yjx!q#s
zilSUej!LIbDvEN%T&5T|!md-c-L6GvjYrH**O>kj5VP4O##|qyHUOzRD8;o|R*KP}z$=<^fE3{8;J@=5pmM#WE6Y{|+YJ+UjHVR*ue91MAzu_HKcUb921H
zdbgGJluPPU^=sAJv~nn9W)d^M9Oti>)Y88dGCQnLETYU#>c%{&M$0#qW++&o*en
z@6J>!G<+{?)3`nsCBUt+2kI{k&$>6w>+Cp;#8@?WU@SGSMRGZQcDnjT<2wAvTyJkX-(UxE^cCB3)O8+s<0dCwIV~Jt6)d9^~N8L)t@b+%T8f^$D>x0UTb+RVZ#*lsaEaT
zQgv9X+I;;Ib#1Fx!bZ*nmd-{{Otj^!P^-4?!Un7TTEE4&?Nm>--a=d|rh#7=t+r}o
z4J{lEQYvbafch!xK=sQuRrv8)nd92bVf@})we5e}@WV^h!T+fhT>v>XzwPM%wgGkg
zTk3gMELHa-`Ti48lAKlZ2{KF8V)gUo?5eGG+V58=olMue_;yE
z7BrFqYCA?r)=>4wj*}uE=0d4Cg-J2kKGh07pR3k+Hp;Sp1j-L_mhXiLgGyejc6+u8
zzqCXh{Om@4W{LXzb2VfCq=IiIGbvwPmw=GjsJGJBY#;FH8&1Tos
zpI_+A=Pp%qUg*n4s;|9Rm+xMXIrK$4XLHo2I=5p()ghfbuz6}u=jOa1Qw@2gAwQO>
zKJ!Xl4oUm@l?LpBy6lzukyoILY>^-}+8St+tg?fu1EXFQJnMow_SG8fl)ClRXV^(K
zPv^Q;b9O8)!M^P>eo&p}!1@p{Zek*}tMLsK?dM
z-$>&Z*Q)Hz7=CZ9TK>%#tT39s*@T}xtA6ulUB2+PI_J$g?fNWs={x9vaINS;6zk*c
z8S0hP1x0vNnbp|LFsG<8Rke1BWs}qvT`IC+YPT-2{Q6Wi2|;11I};kKtWs-{dvY`8Pl;D{YQS#AGztfgRiF@H#z
z)zGpGRDXQCJUge3dpo+^-L=Rb=4{kZZX3p}Mq8qWH&osCc6iK~Edz^qTpMXMtz8}I
z+g0{XI6I+Q-YFM-@DI$+{?2j?O0q+f9bTb6|4xO-Q;$%MtS8q2?X9i;R#^c<2D~%C
zgA?<$<`X9(x4Loy(x*>Ir~gooz8f3)=Lpp3=d97oR#&6>-Uv0YYdQYwA+<)=D8BQM
z+P>@aZ)_Y5Hh5{Mj(%bpMg4S@5`Bvky_&fs?(j%7He=~UIdev<*Sf~=HPcl8ZZUkv
zG&P}HO!F1z&{CZdPMMfhlw%_$>RqTMdNnf?C!Ri~CZn87r__lE?ha8`c8e)LZYWw=
zxrjQF=QTyeO;c}nYrqDn3GeOnP-_E;b5&f_TBw^^(^$1!J@DRX`$gLf>vmGjv_WLS6hBqgKyoUe)?f8Hd~$eVM8`q&H1n<&m5!5
zAJt;#)%qVb1_3S+2yQ+|Gh+Ti0lm_NIJT%TV$txo*73jgDzy7}WO?4Ww(<5(2$`$=W~aF|;A
zlPVpjJwSJP7mHX7@urs6R68XVNgp--$g#n$a-^_kY=zlBT-Q8H)hVA;e&+rHWO8F@
z>O#agM=u6fBkNpdtESa(WDj~COJ8jQLB}+r%JrnndsK6e*ra_lExODjA8Yx}QEY@G
zALg%5tl!!6l;yXO;_PY+r%yEe%@kWil$WiX^?OIkg<Oy!&1zQAT9PX#YQ`8Vz$Os8!{1*>-S=5>1-70X{^QOsAGGz
zq8B3
z6U91YqYk*REkYjyxy#kFpJJL_u10<~#a*>=6#nApVWA!iaw8Vx>i3_IVwY6Y7qJaj
zp8$)B*vD+2S<{$pguV<|;F^X?lP!9s+cVX-zo^SsWvU~;=*0hAuAT;TI#Z44-74nh
zX)visQfd08MGG-$mik5S@USt||3wR4jd?TGvAwI?#;w5YW-@oO8sd3|F;0?cu`dxc
z-5AG|ml5jE
zKUiJ0{c|R(cMU_{2zAL1joDcB(hmdOvm~4yj=3Vz+{C#-G+G%<9Hj|qtsnPO-eM~W
zUp7`9`oC0)EZ&QHt;42BiE){HXv0?U)2AMJC#I{{e)i0}@|RNbzLo5WHrW#`(q(>Y
zV^T)ba#FJ`s3-e;;!*LhY3lyI3WorP_p?S8oE&C}GsE`a>)gVbX$v<&eXd_?e&wV(
zqMu6ni#JaRg~YI^f5Na+{XGchr$?v(1D*SH>)-9uGrJEw%{{oAMLU*gCwH;i|Jzd3
zHbXoXSqbxK7~eZxwU4ls
z_sHw3jNCPz%nuG@-JKD~eYG8nud;Qnnw!>?Z=IVNIdUNno!cL(Nz)soHpmt?S3RQI
zO#M#}v-GfGpeV_i#?h-J?2gup;qFcH`aKnGNuY%(hK9xK4!SO7y*jeKcPS?#$`+`PgOflZpB7tj-H(8
znKp5#`plHis8E*-zoe>drv5~c>!)hOl%H0cpB|>ZFs(dSQ`Ik~d5*m6L)Grnb0|ZL
z8Cr&MGrF~!J`ByX7I#%ZX=)%q*+ghC9yzr|fK|4_=sI0(I@9Jkvjr%dhpC^(XKXWvx{2
z%^l{^^lL-Zp!q{7vUq%Yof-lX1GSFYIYjNez%zYKQF{5E^fN-+YsJuXv#CLkIEFAK
zriYUKr@DM$JC>>zF093`99L^Bio$~Q=|x#=^r*$Zxv;zDlG=RHV72QK%`%m=q`ift
z&uWf|Gg`eA1N~suAO%I7SQ0}eJy_BSLFThdH);J_XHTeV*%PF0UfzVP&-*Ths^0>P
zTTzv7Oi??oh~s?+t0$IKWV=+pd@@!1_c?w~Q9oZnIn))&9_-1OhQZ=Z?5RJCepg!UY;|NQ4fRw@
zm6SAf(T4Dr9vY{Ta;7h6FcmkelC_q#W{KR^de*u$Hu%P+j;bm~QbwpMZT!%>rxI2)
zL4o#m4)9ZF96
zIu6F*znW|UU5vssMBm)!9JtZPJ6eS3rAX%N%SXO|CL!~-wXyj&942A!(BU@
zddR4ZOdge)cOa0Jk~AWDl$v*-tNQ-Ig<&N#Zhow`Kh#Yf_Rp6K&ps$*y{DE@H7qbh)@*jwawpDp};TtNf&K^gPwGG74fa7q1I$N#u
z$A1YS;}1=Cq)XNL%s)}gk6nHK=SNq796uk
zh{i8OlTtOOV}C(;9~SI14z*R{B{$KXWj55U>|Oo8)1Zr^
zsyK>Ojh74j)}6%aTsxu4^uF>Ei(QwFMZzH$Q>3zojEdUPllslo6n0d7bhR-nR2%*2
zF|*X)MTdsLZd@&WDeXLfp|izd*QOG?YUgY0QuFJ3OqV(NXS5+w+jxTahXB48DneJ;5*;aq3hr~9VE6&J#f#)aE5v>wOyYKFy8g3Fw~%IrMNvVoq@Ql
z$%xzL9=GR=vlG>A_eYtwVRfmk?VgeaTA
z-xp`5`Y#`olB56fHYwTqFE5j_QvU`1EYp91EwYU(=X3}hA?TRr&|=6Y?owq+m}_d-wR;D
zj1L?o{DW8wA27p(iCqIa7CO1`E+=;5=JH9c)CC8C=N%1RqY5V|>(P)BMI
zx_O6I%XkfhxK~Ev?9}mMSd<_CCq5*9iszKm{8PM(78U#iJ
z0@|qO10#X}ZO5q;N&y=J0Ilk*gWv-t?$Rp+A7TKF8>XRx4dnqHovT4$LkOVzdi7uf
zs)aqhP7n#MfF(rGS3|(l4Ds^
zU^-?jP4K&750)lo#j
zLE^=REKD4#z|z=I@v)Uv!thJAvNxmBaZIfFWk~v#mK-p!>%TR2jE%M8qsIxw#*#eh
zY~(ec4wuV|I+a+gyOP%H85_qcMis&|QA){&f6+=V_@&g6`QkC6MkVGkMe3z~R*A*1
zm10OGQ2&d4Q6+{Q5$jX5H?KI;kSr~0g6WWgv0C4O#g
z94#JO8B80aN~a`UErk+0_&7s~*-xiD-24n|(sFAW*JSfEVCk@GiwJJY!aDR{0>hk+
zfVC&3q+dW5Nz3Ah8UGNf+AZc31P^9ED%OFMCO&J*Dkhv*fHOj80_hH|$lPp9$8H7m
z6)S{1=?1`Aiv^-(P9EJcFsKGS}+C=3f7z`h*gDs`+KzO)U+aW2W8?a
zYW8wv)ga;1oW0E-_7&Zmvo9OL9wgOb&jw&&j-@N?c!nNU8H38ttfzDi;3lh4OFE1s
zqt!=C66O{xA!&EACvUQ^p+091XxyJf6c`;8kQ&iq?L$GkTgPu3rtdh=F~`C*q|SsoiJlOm@J!9g-=VUiBwUgC8GtW_+*@}9v-)3
zE%}@@(W(`eH`!}Mw^pp2`*5`nZN(;QqbN~fU-_}$MMg&!8R&2&qB)sCk8SD5dh!EC
zVSA2M6K_AuItI>$4})WVW=e)w@hrO>xOO~rLYOiafw^ClebIC5Gv@c}T^O>p<7|7?
zPAr85(c%@R053)E0qe!5am;y%J=0M$MHm&Scbq|CPX`#Hl}R*n8@;24h_RjZep%I-m9XWNs=otYVTr`7=C81=9$aeVbz?9p41J4#
z4ttZGv3GcvO=3vA*p*%8+x`#-yRpGUe%JR{Bs(j9c#plnHi;eYu{~`o*~s-P2pzlj
z7KWvMIv!v^oU8=&RKD#HU{Gq}H~$*z*9YS*asD8(raqB`;wMd(1>*SoX!-BIit-;Y
zH7e0yF!=cy^#Azn_{R)l`v=U%hKS1_ur^_XA|Y}GQ8>7Y=k4jwv*r|`e8`?+gT#Oj
zSq+vVGCyP;fd249)~11mzE?-bX<&S%FXm3V>RKrcZc#WRj_OIWYwsDy7a`-2=eqjU^du3
zttZQ8WH1uhKd|zND6Ib|SV>*1uG|^`o$gTumW*pe-lwd@2_w1u3GdGsSs6sfpP06Y
zVqdaG3G>Ks4*RE5pTIp3lzNnFfPGzz`JA<5gT?XB+3dfetP~z;1nmE!1ObiXss6x!
zQh&EE*^7}kA%`$VDn7a#%J>O6oPR^?_!2#PLzuq85ZNl4e#O3X5}ArezNWePxsOwYMPwWvex9J_s(G$X>XITFXXo%C0AC
zuD{{%oz?%W8%vpyb{|DsVaO^?OaD!+m*$i?YaeYxR;F4D?PzP+`gO<+WG$Nld_)7q
zzf1(5E`&!|*$7@V3$>O_5O>WivkYIGi(0XwErUpi0Wh1Cjo{@Z{{KJ~l_JA4rlYN8
z8+233E{L0Gb}Cz~>vFM4_bn5L&&@+y%a--s>)XD&4Xp`oO`rjDO;_#
zW|XkE%YPUyj7gt#AdS^B6Ty14h;_r4-XFs2?|1>{|mEcZDuyf
zYi>VWi1QcMx3O?>Ya6>PO*;!ojA&_Xk@N42DMR1hJ`~1ClhRJ#aAXsoz+Ia*JURve7jUrGH$hw3UC$
zr2IzInO*rfiiB4)&YKnQc5pwg+wRLaa|~u9av1a+9VTnI<|O83#4#1OCSWgXW4>~`
zAGkzkU6ReDpab6B%bM}Nkz&GL*1^ADr2os*1t#i(>w8&U@05ek9002AV>at;T5f9e
zm|}1@`8BjBB_2jvCFPKzhq-^G=(&$Y@ysJ);6C;=?-MD0+sCSTJ93TL&&pdJ#liEj
zsDvTaTuW%VpGCQJ;YE>fIg+Fd5G9|fQ4$Fnh?KWC8)O|L+XsOmq|H&SUfnu6017IBS`UiIso@d6mem&
z1x~6>4Hro!MgSrSk;KkiG+&8=LEph&lGfrfgUxV>*dT`Gu*ht9b-^MxJ0A`I*Lzsj78cpx}*U1gJUk#wDlE!yNJb|xidphI;WI>hR*0mAn%jP_3TbB8j!$!w|Wr=PQo
z{)gFH&br}nfx3n92&-69qZTQw7ml!o)jgO=M;?Er*WU|)PKD%ps>|}&faDUz-o>NL
zq)c6}=>rVlAdH^4pco|p$_RnS9@Iteq#wnBO@`QVly#u0%pO(Qjxp;;Q%IiW;);kd
zCDzhnG1kXF>&PHzWK1dk80GDAS<|4tSBsRJ
zHuOj4vWQ~kABds+Zhp8om0t`FO`grB{#ZmJ<;dhRebBIv5XnBtL)*aA31NAp&I;$O2>ZO%YAcph_&4dLh)RoP35=
zEtl`sg%{nrFi)I0!>Vhz!DoTH!3}q%8}3r^%2~F^YZ%sk{|Eg%7rF)g943aHW9=J|
z1_5)uo(^EqB*D)0j{Q%)rTThji38HtJ9C8Zc~(2#Gpn~_v^ZxN!vRg{-m`Z*&$1bN
zDB>=%dQpjtF&`i9Bl$2N{B-^^F!AX{HWt20-hbdY_JCOO2TKk(02KhiZ5P1&xk;G+
zfaAr-mslGl&%4Aru>m&^lvS`ye0Lki?HfeiZ5B?0?e1;1#^(V12$d%0lVbH9_Bk6U67RAa?ei>gep<`h
z25{PD`6^k#J(TlsM^tKbe3Q1#y?>p-$Q_
zEkSd*PCM5M|Kp@cy3eYJ`UR|gJ%fdDyB<(8k;K8e0ZejD)Wzs
z#s8wl@l+#SEOO)aU(~q6Sy_Lte_feMm6fnd{zYX~#T9R7^M3x9Ez6|JN}K}yWo2eR
zXJwIMm*2l`UN+Te{ujKgD(d?e*O>4BuWOw5uNYb$x%G4%0HB`-XR*5%|rQrFcI_#{mar5KO{2a
zikF14vdfMmyi$a0V^*4*bCe9-`;Uj(^)QX7)!`aZEaJ&Tf`V$)D5_5REo4b7poO)J+J2hj{w#adNSH`?RM@fIvhAFpm{+4ir>^OlUwvoEc{Uy_Le7w7(XF|-o5#1pUM
ziv@tZ?@AotGV*z=^GE!@n_~hh^Jhz4VfCuaW5X`vHX~*mTtJ*}q`!-eSVB#x%su@`
z^-0dFU@hoO5TC!qqTIcV#hxm>QLy7O9<8^ML$H`h7ZFvtHE8*G{fgdVipyPYZ(Egj
zBiGGY@w^SYDlWzIWI#O=ctZqJ6ZlZ}r>I$tCjnYmO@o?N=iehTvpRo;T@${Ed^m#i
zME(qe0u7IE#0C{4sOB|0%lQ}HJqNe5q0^O
zB~WfH14=z7O1XNapd26+O`XxqS*d<0D1uP%+JV+yQN2MaDEBgf0#{ERrEbGgP}UPl
zV<$?JMx~(qLMVw&l-7+)pj7i;3XFzMjHjEFz(@$3Oeopf^=4{Or=~p3l`pvb(y0F3
zCBSj+X34u0W22Pr7)(WJ*W0P2In7Ge7O;UzYTzVjadUT+J}r1Pf5Ol%iBlo=7Va3-
zlYxg91LZ&w$?IB{#0khCoFevZX;li!qeVc0%GWz#ck5D6P7#Wxj)<5Xr4*F)go1|*
z^iqzsDTxxBN+`}-#QLR2TsYP4LB^IUtARTgBFBcJ7GC;K$x;J;A)KPVz23GIlyeJ!
zQrk&eLAz2=W)VtDC(7feOF_9l-?bvI4yB+hBa{}-QUabS1?BcUptN$Lgmo+h#ZD+i
zw8T7H5+!ypq11Gg;?By@(pkB6E^-vJvfA?{OAVMzIMDriAJ*wq3QEBopj36DG=8BJ
zl-~#?!HLr9#Zpk_5=vz!O1qazLAf&YSR*}xU>-!x|~l3Xy&65XgaxIQ+88E7dQUD%;+F#
z)jL?d3Gwc`{}&{k*(h3e<6nyJy7KS;JzaY4wUO9@!z?C|$!Bp`5B$v!>k(%%3la7n
zmO)cR$M<-th6Z0FwZEar&W{gk`7dk@?&=DLxv5jdqWAgJFhAUVpVN~@uEV@h{XaIK
z8t6KwKs{mBEf!>m?yT27#8d0;B{gQ##I^3+7C@R|ebJ;+?qhe=pj~sFCR%;MZT_>U
zfleLL>yuIuQKU6+C}I9qROad)PecvaKNF};oQ0ZtJrOhT!3M?g_kJ>F24NO0GQ^ip#=JvM-@x3Z%`o=!Ut#;l
z4vOmEaC+ItwcE9;KruXHn#lN;+l-XWDi%%To;QH6eaquGR8{DAe5J&Wi#`8^5$S~J
z@*}T`px=+Y3WB*m@}Y2jus8Z2@66e7`>>xuA^Ac?_Teog+$s>=CVO=gt#`3#y1VZy
za{KV7lIY3+u0J*<3$dioEdW4GV8jPRj{Sfd)1kklkgqr;$fJpayyx
zImrX6ucwjUJD_@c8o9Cqs_TF-vrX>HqwA6fIihRfbp$ws<5x|(myb6|;NuObhE5Q<
zZ39X)DHraE%gC2Re$j|Yp!?yHLaxq$;&q6;mjU72no1&vWI$DPh%}?3J7l~5JWDZfbfI@wTiqt0pUD~(#TO05MEFqzf1hW
z>j@N3u9SfAdIEVy;ul^{Kzx!y-i`pZrxPUQmEYO>UR{{u^T=5W(R|3eh4!`h10F^)dPLfsuo(Ld*
zkpmnOp$!QlpEf{VIz(=2fN)en!bYBFfMh*QbM)c#Py$s({$WVN3jtILxqJa)dKZx2
zmJNq8xNJtBX)sttg>PbwqR9Z2Dk0qPVj0f@nUFM+edUC&YqCdX%D{_;4Uk
zV5%4~kiQ&V%_=o@Ll)4(45%#bXVC=FxLU
zTc4no0pH_Z+JB6A~mVN(WHx7lWGe7b4khB
zj84rKd=fh<-c5%I`LIyac}I`Zc5#%J%XJ3r;>4gw>9BJi6Aj1WS&w7l^Rc`Mf;nUP
zTAGSoZ}m&(lSc{f@x0_SXH+PjIm2T+9*^$0oqo7}`-1ETJ;(EyKyA5JTNwhD-~zuG
zJ)Y0RjMr=e1m@9H(PaXs=b7AB1Oq4XN*tujpUAEL$>@%vC5QIj$d!pajt#R1P2y8H
z%NI*B_*%A8d@+URuwA0
zm_ve)7DAH}0)4IsqDe^tbo`Kbeg+@tr77_sv3{K~R9v0GpKhwn?Lm~u1T<}a)-uU}
zwD~!RW;1U<+T0dIvl&6RW{A&b@@T8>@JW*w<)B-cAjpgl7HL?Uj*8hc(XMmz#HE?M
zXF0k^iFC|1gssivLHcYHqU;i1&f@*d+PVcUb6yJk5sl_tVVuod*e=5f1z(ErqX|c2
zhgp#^-B@E33tl-;9v%?y&*raruckX$%eRR2vw1ljI-H!%D-uuTIXor)5uQME@%J)f
zFB{_A%Q~0%MrC7`ur1(W2_9pX65Nj+j8?cm(OhtS4il2%!yUqIS
zYtp;gOtUY#x!HV=sWgfvx+1SiOW-#%md;m+SFFkJJt6hrLnIYA2LO(58H`
zAd^R72yV^fPq96A(=r~)%H4A0C?J_icS6bjfHH20X3P2OY>ybdoPUQw=)Zy=?zsKz
zAW%Ht3ZxBcY!YvDl
zi^I%Fuvd79O4W&CXjP2+HCS&W8r^^&iK}dVX|IylED1nn({8`k9-!Bw20N@=k`k
zL{pB8EMDaU$^lP5m!v1>&}}d-s7fwnhi%0r&gx@c65;|=P1SzoNBO=j!fPdO!1LFN
z7AyJkfQOgioe*W71P2@Q@Ak(lc~1r$d#}|TuPxpZA#3?EcFVqXE!JdguDHIA4-cca
zN5Sj}u*|z5pG{$gUCrhrd1C+J!}axAh79AtFhDw+3CdlUszX@><*XAgY~(ShqSr>=
z@#(vc4uR*suaXUjUPt7)1nqJ{7YWL7V&)Tc*qJun4^ZA+@n|FOWuBem$iJ9k7w3ro
zZsOIFoEhhn_2O(%MuJk-YRk;Hj~emC1g8N8;c-UD62bXS?9~JnI7`|@P@ywz8$taG
z91Yn^P?{6U_p{2Vra)BP%>PTrMVt8voDB8Z!Xsj|)rrBpL$Z3+VprlAXM{5w#s@lMATZ|D_TYUShJ+mP>VDqNvKJnb%>Ddb*qA~O@Hg2k@!0`Bi)Gp)a(G+rMWurS-G1j)9l!0vf9Dl^9o53Y
z3#nNr`8al8yqw1exsh`&kI$F=s0iXF5XYb6qu3#P&GUROtDqCEk!it{0t+eLWYiji
z5d+f8UF5RSwLFn{mA}M}+mo*HNGbF}3N})~c<>26YV=c5Ok&{;9s#QxuaJ0tu$F4D
zg()F6f7-$46vqT32D|vl6ceeW3e$C-Sl$5*7@}n~q1ci1Q^Ld-3Y?BNc$E0;Iye0z
z#Kr#*G1S-3L?#5+TE(mzyj((Xus>N73~<2td&;anu5Gb}1_T7cExtslm6C2#lR{`c
zwpss$WmR81m4!HL{q*;z@X3R}8zwBjafL7>7UD%42&-A0v3+i(mBQ)C4_EY`KLk<}Pvd7Vn3k
z_ig@$9@G^NZ}Ud6mD&Z+Cln*w1<*5nxTxc8jWX|o&j;KK#lG7-QT*=?tZbFU^gFz%
zPQ!HS_{BPBbQ9n!U?^FQ@YltOxB5)={$zCiMSIv7vz1W*f!L0^ZlM
zYwt)4^kp6eK6((S?Aj}a-Q(42qG0nri`A=!VQV#8Acb410ZYphp(8u8;?s$H#o2pM
zGU+1zKJSBI?tT8WUVJI2J`bQs(&0VG=OeiPfPdOlD>Ym>MUhM&d`0qf1IU?)PmI#xSW>pyMsADzp&CyG}2KNi`Z%oXuAoCP0qtVKtj`
z{piFm(&kG+tO@rfP>TKpW?XPXzRe48ny`mS^|iy4Kf|PUp8F40V*i1;6JGt;f4m(o
zWjgc4`G7jBw|S#QOtMIo#JXt7TYMZLb@bh;e}Ao?S*(tbDzWY2RD|@tGh2l?tj1gG
zZNs--cJ9X*c6n4B&u*DFiWG~~$9D)7BbiZ5NTjovNYN=$s*df}kC9Sel>8`CssSi5
zO6nVS9bbpBVQEJW%97$kJGu>GLzJYyDutHD*+)c6qd7ZiuT)Oz%%hHJysB@55h9+~
z#&d%b?E|ZuSK|Fosa5^uczoO@V@HYq*nW}sIIeLq1NJ^f)N|+Di5q+o&*OOzyy_wf!k6iaibg}zJc#+7
za9nPTlI6FaJUdn1&U@>B%uvA`f^$p+_@BbR_$~ZsdJShr;!PU8`suonOwSU
z)pT7aSX5K$CBCmFr4*IO
zlTV47)ujqN7doywuLBA?<>dT=6OlwOCzy{E*B6!tqI
zdM8R(iz`SyA%@lXKd3+_zqsHwImgj{T?!pSNLs0g`M8>abeFf&nbpfJU8l5{$gd+A
zt0|4~oet$>F77-1X)t$iZbcdCtQv+>QXD5QL_E!ROw6b)r4~1D@o~|$juh!>P-n_+
zs@_|4ttzz_hw4c9NbD97URSb~pEn)q+ly=|CL2>06~L^ft#}B!c1gTkSBjS|jqO7R
zcTqyrl`Q4Q;PZGm?ZEMhmrYhq=_gg1olaIZpA)<4O3_V^|AJDrlN7?4b3{Av(N0oi
zrSD8=D(xhN((W(PFg1N9D+SZ+)_PK-q7e}$KC3UyNIG)_)O)B2Pgxd6fm7XG-g7-h
z-3G#mbj1mD(-cQ3-Q}JZUM!~cq@w5lZxMo<)26>$0TYfP-OZ2C*DZY&rMpRq$u0c|
zrH8r_=!f*AVllcx2yO)UxD_xu7wK+i_}*^m8z|k4KrgrSQy%#xdJ#aRHIypWa8;ip
zNSxr6SVS1+IaigtNaQt?e!!CT%|^xIsj0V0Z8O%v+x*xR9Es5NB%F%u)#f+ouGvNs
z`-RlmaM>#^+oA1T6qi>1XJ)$kRKw5-V?n}@QIf^k#!@_<01aq@M}mcD(M0+wc$@`C
zaj-%xfy`U~9y;&7`hP)(}z+!!947;FEutw;4^|3?K3^JJPP`
zX?xw%L{M`n2BlO-;4CGdDl#ZJbO_*C$|icY7;pQ_ZfPP(FJ(M}a*q0Py)?{hd}-9B
z6mg)rRF#bt51ONA#)=j#v>>U4)Dh6m7Sad=U0OACV_j&S~T
zlJCzI4N6ASo^iYqj``^G6CY#8k)9zox58Q^Z=F4+we$@)=dDPkZ*${*s2RzpSJ*S!
zNU;Gtf1g;}N$Sb7QpA!Mu-I(<0=PU?eDQ*0L6Gu-RMnA$ujXnfmBr^TN?AyYe@Uv%
zrrO_lN$Sq{k$qyr%Tkq+)>k}r^WbIZHiQ*AOAGkSefB+_r43q+QLkzV_7$&6+gM2g
zhrce><9x_IvGGkQE}`U$rSw6sQa%!659}iKGjc4})vnTVyuoXKrkga6@$F~rci)qq
z<9_?;8{V7fTMYK+Ka_s-#;ff+dP(~ky-+^sGih~D$jHg;&NkUwkCGCYXf;amvwx*ZaajM`$BdRf@WWVnFiCm?TZUIABiLeJG+FvZ
z=II&scGIQf?D?GQX}bA&F|FBi0cj?qEQ0d%w7BOQN%`b~chLcNY$O#BobQ02foFIm
z;DYP+-ZP~XIV2hFp(}bFkTC=aDL3qk@Ktc
zT3{NPUCG*Ku;P0;qRC1rm*=h#mQ~Vf+_JSFT!oz))J4P^sR|osZ?Q&tKwy(~QeWCy
ztyw2sLl4*&W)tHO(RQ
zKinq;N*I@86mozk*b%BpCKllk;?0z0>MX^9G?Oa7+2-QrOr|nd*E5@a6!Bn
zb544O@5&XY&q?Vt#Q%35>obHC&PzL@9z^0!0#A_4aUCd#jO*Cutf7s;Tp*GzNJj~+
z%S9diM}(2?&!gf4Q&w*)-KQ2yE=m#j?wo!5MQJQYZ@qt6YHZ}oC)y|GOJA_c&Mya{
zt3#bTQj#O=x*F1ZD_mPXCtBpn@ndKfa#<=!FesKd_K}q7BI$-y!sk*_mTiaKP5PqM
zXx%uD%cuvjEm0=uwsGu%HL&0Sj-5HV1SlK#;KiXz=1j1m7Ari!;=J+n{%G;)P3bMD
zkj$IXx16QgtKE?rGT+&UH4_QO{U>*&PVlq(^{%9H(gPWVQkea{`&i$ROaf|6>cM{c
zvGgD9N4zBeXbg6m_@(6iMAYIO`!XN-St)omO#j;W&er0W<=BJ$>PVXv%Fva7fj~&4v0^}?)Lj(u>r{PVx*Ag==l&_SW%({MT3
z=e+jY+(b+amxtTEBjvR+9@yRzEBE1hPl^uZ6;@L16&Y&w*ely)IXbB5(;gQo
zJxw%jCSQcP$JSgvN=Ofz%Xpi2&rwmQrCg8ydO&>CQhtu-?-$Ek%5jPLgK&OCM;Oq8
zaA1~VlyJphMc_967#0jTfs$Iu&xANWVx-vT;)pFeUXhi&>7N&-1Iz)C=nqPZoJnL)u>iMbIO9^zrRQPPfKzhNbxL7Zl5n2f$L
zwTtrXS}AN#$@LR*GjW3E@~nnzHDGz-MlM|&1GzsxCI1(W2KSzlo5!awpDvR7ZMun@
zr$9G1M1yMMt4t<1DQHfr&N}RG|405xX6g2Q?d2KFx6eW}5;sN8+pDez9L+-#Ph_!FX
z8=~Qb6=XpAULflxFefa=y(!;8FOKXYS25+&vlBPyPk*tYi(Cy8_0=x&??L^SJC5Y2
zj>2X3{cp)zBs}jvwyV4Z#}^&D$uX=zbnhm2iM|g4tp-wWUKB*ONgjPUS0epp&+R5x
zWnpj)rh&%{_?i?vu8ta%NmGU8eYvU`Dar);lcE0-&%ZDK95gY?1lt^V37KQ&iS}df
z%L`3-sbt9Ka@&x_cC;toKsG&c8NiCqe(H01KJ&d#?@(5v#mt!A@(sopFSif*O716z
zI*es>0UO2A`yNB>*6-!oQs>pKja&vmpSNJ%N4eWv6SIpGL;fl;*d(&wlB1JggGUj0
zu0h__#~QZ;@RRvd|3eBL32&aFHQ`g#Qs>pYMM;zo8m-&N=&1~Ban
zu?)*TlWWiN;bAMaWNbU$OBm*#hrG(W;*u9wlQClQ|MTfUYcCwp<+hNv;AZ=ReDz
z@Vph`)1T!Z;ePPP&vGiixWfK@5>yvU6Z4Yg4uHj-Wck&&i>O|S*F+1B>;}aJ)
zpY@SDMqG!tB0gcK-D%8cc!B2I8iwl^#czG&r-F*M7xZaW>@IA5<)>NFlUj+l$MD8W
zGv)(NoRV!Q>bw6Jfkd%6V$)l4#Q(EUKB*ryp?s3WYC@?=Y>`kF7K^MVl$ywvBC(oK
zK1pIVq0E3XC>jar035Qe38kkvCpl9!p?s1&YeG5SwcP2&
z{yRVp5AqOG`=kN#Vg}{bX^`Buo2zousLaSJ)^-|Ykd?0RB0^1RzZNHHRVS}3j?l!{
zHA0i${FUPNAo&*0TV=nTB2V=Sy0Xf_GSVbxSJ@|wkf*YcTZq+%fj(v2S|wf>DeuO<
z!CqmM+>hbTl6|@=e&_-B{FqUM+o&g``;jNKlzxHWY4KqH6e&bS^
zjv1j%b-*O_B4=1rd&WdwM71!nbfVlI8zFn)L^%yhvo&J)WO*h+d&3O*01GTYVcIf*
z=t{O*rpj-4wJBQ5Qoj~0;%MzqwAl;;;BaPX$YTLG7xTD2jd?;Woh?6Sovp9vZP*Gp
ze$hu|=qt52Kv<>iqQ1)T#
zqu{!r(>O@0l~Nv-UAA45x<>2dI>LLcy#H_Ny0cbJaP#*iD+|WhVNq|LycyrtySGkm
zU;Q9sauN(^dAw(w1OpmV>i4jDL3UXU!`GBUQv6}8gN<>P=#?!ebf&M)<}>YV)o9M}
zg`XUj`ArRDu-!x>S#MZY>AWwU_lTn;oSkAHg&$+!HoOprr?uE&u6+ea%a+UO(xTXt
zEk}saKjN!YceCY0_~KMrFL%Oe-`DHqt47y$!uiiBCO%k_6HkBMDp%r*4~ywr^y7jl3!rI+LL!-Nx@dwXYH23u07(X-{cl-g;@0)x_yPc*IsA~fc^Gi
z%d$e$+mBVb-9CT6jHk0!h~)?5_hE>L%0c-nL`sg_50L*s`BS#s{@;W08peJVjSpdC
zU>7G2$#vLbyZJCi4V!5H;uyAcUJEVOaL{KwArEH@#pn}q5AVgW>oW5qyYZxq2XB{%
zck-|`0<~Fr@)WjIym<;=&s)1){B%m5%#Mh1r!iEQiteY;hQ(s|X}La3QX5apQOuRE
zcv!NSuU;BoTc4Bb3hPBVC}_TZx;FC0@1QT%oxVH|`tml3FD}cy
z#8a2#o@K4)5Y^*ywo7~{y`Fv$gj<*856W88`Gcs*`G%IZn|e}By~Mz)5aR_R>#F?j
z-*6v{t1CYGQ*PqMy&S5`#W%UMRxR8k_WkK1M%JrxEfI4~9$VH1UWAp%^$4l7N}XL6
zeM1fq*Yf3;{-$|P7(l~*7Ynb;Ys!{+>F+f9dI^)gvY&YVrW_nL6Ru23A&zC>nS7SV
zc|$eK5?|evyRZ!VZ#U(Y4EBm4x8*HtqImI+JU0aRGITuI;$wkr0CvD#`Bf}MKe#K`
zid*LJl$E{*T9NH6nOV!XNqPx>sM|K$m*17QQ>jV!}qZkL6Zu-g0kas01j<%UD&kk&MY~o_(Wa^e3O>y|QsCz^{x(
z6M;aP4KUu@_%VSaeT;3{JaNd!SO?HJUt?ua)7SWv_dG1=dI!kD>1zXxS*{Xdbk)zgLxdV+
zJjrH=ugu2r2!eu*vqP5Z#vhvW6bWxuWJU>FlwZ94dayA^ikw8RD{`fFFKs^WY1Uw7
zo+OTk8y~YJ_Tv%8dHTUiwDD5azGmJU%K+HXeB$VQ4?p}d*}JUM=!=_aVtkD83bac~
ztg&gefn>7uDq2|wkRgrJBOOr9&UG~6ls*T8HVfW$m#J@%9+nh%^R|qO#_D8jiwOP`j
zML0LK!qNr@$_!Zbl-V8;F!LQdi*xX;WWseIAui<%;yk_)Kbr8ixZ~YBRWk4T(?xip
zdaWpMNfDwwyDr#>D6odomR$1jCNMF6yvkSR{F(sJvB`XQH=3yq{vi^Jrk4B;vx%4!9Is4zX3ju
z7-k-c;+ZzMN7WxWbTbMthV2(Wv@v$!TUGn-ZP54equ>f$i{&x$=1qVnsVxRo;$9&>
zq+l`YiTSK;fR|X`-iY%9*V@zei`V{RtXv{;Xlaq_5E-AR|fNp$;$`o?AhP8Gj{y{XnPZ|s;cjA{GM|;BOq5mnc-ek1{H^L9@1l}
zS(<5n8*FeW(=hF8gUx}7ii%23n@khS63qeAI%sA%q-a){l%!~=l$fSyR+Om!&)WN5
zylTJy`+MH!c`5HYYtMV_dF{2<2DZJAQgBoBm;3%p^fi5z=Wycl+^Nh9nh(;X$OTaJ
z26K`McvBL;-l^PmljFap`(i((`As1U`zdR%gMaKUw9*Zvv1i?-#7NR=w(@Ree8_Tt
zvM>IQCG}Ujgyi@;W_`z=>95SV(b3iiC``J^b?rS$A4%H4{urn%2AF%V@-CDf+7421
zYFTZc8?5Bfx@=D$qI@Ywe?^Z>;JMSlzBBkcQ6AVbBK!0}eMwSO+hAfijy{EuKe0OS_f
zSrHG+7xLR&Od-+DF~;l7YX!CiU#5wJ^?hAyU^jSBIVjOoeexkCQ?AKmPYqRC0@`e-
z@@xQ>cXOEHi9cI~vXMz+$dEa#;{rn-0l=V~*l!pn9N4SgI!w7gwF;8l*Na&BdVnndg;mQ)Ipo|}(
zw2Z(?f0MF(5R~oLk5Gn6b6C(w<*o2sqLDDoOcHb0p^=y;bM3!J
zDoJus#c?6-AzMYoti>qoi_Wn}M=6%qqaEl7k5$5>VwBP*Zr;Ah5D<9;9wsS(%humK
zi6QvfT+C9RRBk~Mk36Z|A)h(U-g{E%47B1&1v-t#S?Xw|t8~sD1)#{85%zmeE6Zf*
zAN!7H6dhN7+4epERi2O_zF98;ZXcVVWCTMF!5*2RjF9D`uk9}}0kQYY&)#$&0m}fzP*RDsRXnd=WKUnbNYJO*5Q$T4S)W
z@ne{c0lNZ+&RmGA@9L^^non?og=Q
zW(Is36}W}nJ3OVAy?L_|?W76hhtE+#YMLuwC@=XpjY3UhV5zRJ+ZY^n&@_2ajpI%8
zUNP!+VU_(yu@Vdh^-3179lP*%Sju)~Dv_yhyYeJCNA7_09J|{N#U;btzUND9EGh5F
zyOlaVi%r_2G?b*J_Po7HQysAAJ7tIzdvV(AYll%{fGF0UHMqbfBfWM?nPP`xvsn3y
z5-A^2SX!BqWV=t&sbi|$vKl>9Lv80tl9RRlRcT@GK81Q>vOaqn1EXS^-R=y=3B*Y3
zF~2Dfaf3rU&M6~qyTJK;e!CUx$X8kaVu@I?&h96Upnphk=10~jW8|IF*uEM}HD%LS
z4d7ijOOH7qJ!aKjO~51b|Njd3H&}?YA33jlpmDZtz~8jl+P}(f`j4_4GwNZs_ma{~
zF8q$2zofL1e>luMb;|URTv#vXdy#BX1XbP+;`F<$WJw>gZ!RkdkZxh;E-Sm_
zZ;mkg3fP9kG(L1ic|($BEqF*xka8EijNb(d-d3A{C%#~-+E`lh$@l6EnL@IWPIWZY
z?Dsm==F$xN52t!DfV-d3)j@LU4!ef|51ii8O?9aJ)giXaRGUYAa|pEayH1gUOyV+l1L`)`OEX-XDM-AhPuCMyh58_Smu+a*Ej1}XJ_nZt=aYs@Xp
zAO%~?#)qm2^2QQ2Csb`Fe^SCe4OJg7{wTwNB$=-M8;7Z%$uj251C7+a4a;}Tib#Z>
z0j^@*P#!7UVYg_k-YscoSI&&EKK;mkJwgqWTGi4M@P1rGI_vm`1Q%4C1=dEr^q0Lg
zQq{Q3*X}5Fr4VQgpsO1QG`<$C_K^4OW}io+32=xw7OjR8O%SEhJ{Y6+b4qX9o&>do
zPAY(5_GPy^5{`s4u_rfGvCXpWz0K4vl8g&wH5ru!Oiotgr6Rjevf4dZF5JeZv{RG8
zj$YGFeM82swPi=O0O0$MY8rsEliE(M-p)FAQYV@7kE8G!bV|W0j97NItJ;N+#}?gGh*v<0GT}D$QHc&)Z}d>_
z!X&zf?X}cYbcwSKjCi$fDrU?$mNQ(5``3L;oISFadL&3bGu5^RsvQ*L^JS110uQO2
zWe!pMTtn?|hp4|gF%xfoP<@A{Xj-<~v-oz&5%&8-)!z)TUfy~_{S@HNQRu{9*~>?%
zEBRbLaI|_g2ue%?pI0NoN$EtQRUX>gt~#t^Y0L&U2yr!Q
z7#bNCj=x6u7eSD+n@=*_xO91Sq8c~4X`q{GLD>cM32uT-;t3r2uR=rKLNOO9|xvD8OGJSCE@sov0=~
zk%(zXTk$lwG@gFY8+U+tT^m6Bumsxl1X!8!-FR#&z?vit3{C(ncHA8tYq{~IrK3Dh
z6B|7isUaK5YS=Fr*1i;&WN<(Qg(njOzXUe@MKvZPo?=m6*T;;fhHrOLW
z1MlS4g%q~tWi`4Z#4^BAfr=Mh^>HNu*FV&3bEdOYpOw$Ws55Fikx?RgIPQvX`c+m!;io;xsieW=m9H2YzI=TwcWn>=5{X
z`<`u>rgo5~*}qOx8)5f#h)L67PWX>E?f%o%lg^0R?aS!g3TXrkMPyUww2HAFGV6ud
z#tZjjt{XE;9UzyK*!C>-9r=!$AF&F;V5;wQf@8C_Z8yYrfBJBWQ2$QEb#06lWWiuX
zy$q*+Xvi@y%vJl#$5*ndTy}oPp-t!sptf^b7uEpxKmU;9VmtpW^+Q?yEQ&pp
zpE8^k%vZk$i{Xt0>b-5>o6nzf>5O)UKo2ywYQO^|E-_iS1Ah1uEc*gSmyxU`?CJt_
znq0M;WiM3IXyLUNs`2u^Z2RCs^%&OdJ#2HHdcR!#9SeP1ZAGxx+vw*@?U&wGTVRW6
zFJGiSEyeS0V_F~bZW9%VEu&AA1LJWqYqeNyF2DO7YrR;FU=tRr%cBZcp=L1No2c{s
zi`W;UKp(MIOJE`+hds7Ly&cQydrQ!vFv@yX;bl!x>Po;Tu
zITY#$=0oCvg}%hl6BiLmK26lx7EmNa2*1|vxa_|r{MYGxb6IDKG3i|G2UpcW?ShQKZfO2Q^2735dp*Dx@Hqn88n2xBJ%rk~zNTOy7ks9IIv|#d;(kbg}Pm?+t0=q
zt1aX+``J6iIE4T{xD6Zrv-{aE#p>@TH5Y#6FhUwnQwD<2)
zpOCx$rApx>(NpRW50t`e6bney=~RR->})ap$j_6B4G+Y2pmy)@NNni}HHOXH
zqei#o+Ora0NZ_L)1g$98I^zc`k>dK1F89k}Dxr^gb*6(YzSwZGqWST!7Xc5w_Nf7%;DO|lKXX&ZC(sy2aZ1mmMmVn7$iBS;p;Kd|N`!r~Ya
zhNsaVLPY_HN0}=Yb`pfT)$~LN1Vfq)w-W9Ejx?oA@a@f~rQ69C>{VN4aQg+CwKXV%
z)cPFmU{dRIxEpl9*4$yiaR@MM-|htfC3^xuO`;nBI!q^kM()NFI>_zt6)qYr)Ex$g
zmT*8cYH`@WAesZChIjGia6^5N8lMwV?t@m%;8+N@@(>iGj-XiqFmjLTp$lj~9StGFggU(h3l1%^QhIFK4z}>7`BX7HB6SPOW2j~
z)uX{UfuI-~r}G;7^bcyL9PpegJl&KfgMzr4)`{M4HBv*&j5H@
za=%~~dS9o$!40u^f-F`@2DhXUn2_M{goPz0dJ;pMi5ON#7}$u8!RqY|DFVjDB_zbg
zy4?xx7_8UVdBw)Y$CGz_LITcw{^i`s%XytsmDj1Qe|2_Jb*vLJak}qt?LPSWcsp66
z!)j}J=T5uNVf7aYM~}24YP1x{?mVJC)p9vc!YyOM_Afw{aQ1gvzJu*OqGm`<*j-1}
zEIDfzD?O@qle50G8~mismgK7i?A=PWbGvx{)O)}(!v4$pik8L=nc9efV#m*c<>CZ9kxSAIC
z7fdGeeXlP@09|c~7z@~-#rlMb1qLrl0uo$>&Vg=05oix<
zenR~iJ6-$e33Zr~@h-i|7$P1(bYKWT-<({$&p8U0_ps^00hK@SC|p*Oi{bCG;U>rEZUX;gs6JDX*W#n*XLAZe72w
zi{opsPE)Gia_@EaZVqPH?aqP^o9x>pcpP-JmaKX2;gEn$18`X4!a>pc1;+#r^Zcsa
z%I-d|8f^19wVP|se(ZysWc@T0LF!7GTBD|9yd_*=V#@{BE2TUP_GKEd85^+Ua+0xb
zj0fwSz_2eP?B|W#5BoC0w&NUvkl33MHdlm1ry#6Cgv8#Au$?8?@H&Nc3E7&NA%bFa
zMj6%!4Er;}ei0F|M5{Ep=c-eZ1O
z_sTmDuyz*({`5t)rTp0ew&TH2yY)*1lV9diApu4;RFJI=M#XCxTJN7p>#-FO0TMR3T2DRo*s{@iF1gx7U%!(LKDhhGv`7f;~8jH;+YM0x{|i
zu*WW`Lm<_#;S!oImtDD}M#%phU=ej{hJ5k>%c~Ql+ES+$MOA(*3gfLyZ(N~rEtz-N
ztjpN@A3MN4zN|h>lnuF}4x#9yt|0p918h1$xUadQc0xn^az(v8bmcOz6L5Ax)i`gd
z_5ka86)bef0bEtv!u;qvSFxKvbbwV}Md!P;=aUB7W#}v%V8OEXy7YxTQ`YX5Ae3{^
zskM>nY&}4`O>*Y`FiSiGQL|2ci6yGqON|SEfXxFr=qWsFD89x7S~WkgLRE{Bi+8YX
zn%0J0QlVh$Lk4Jm$kJ=bz4ak(*Zo2)@JCMiBb^Pcd3~G(KE%+5);kU`{SsR0Lrl#t
zt3baH3;dCj{z&H_zhE={kt6;{YncC|%m)SgWpTjk$SKjRfnP!|`XhV%k(dy_VB`Fe
zFTBXH5Wg%QcllA`3zvv{%;g`Vp?`>m-VhD_Xf-a>FUv2y$gxoW5MllyyvVUI{}AE+
zA-u@3aQ|{N@(b~aKcY4E^LfZ0`N$u+?2inL5OF3%_+{~Xgcjw_J;-}yTb$^{R~z(C
z@zomr^B`*(>DNDBjP%QUk3SL<<>xal3hRTf8yEW{VbOj*Px>R9{E?s-zhJ}sk&pcm
zCDt$4gZ{`z{>T+C;)&A+)fa1doL}6({E@rjwHEckR>b=S`^O&{kl^Q&?~nZMkMwc-
z1$)~cIpdG?@c3oA*y9)HoIlbl(a&drKT_?FbZw%!FpKtYqD9x&BoA&0h2^}t!4)bM
zU)Xfih%an7a@p=CT47M(k30|-@6sP}8>x+uQfqNLUay5-<3mW3tlEdrnY{9l9h{`?
z;8GmlrD}FV%%U^8Y6h&-$_h#fZHu?GQe(2
z%a*p;_x93uNYYk2{0?oD1TO9?y|ou0URl!{)BbzR(?@Fya9OY}|6<`F{Yr;>1ZHLji6jaEc^&{DrJ4+H^lkwdiIUV;hjU
zIyxTWk_D$z;^KeEc@)Em1?D7yYVJt)Gq~Os%zmrN>-s%+&69EV1{thE8F_^SI#}cY
z?S#BjVYjsl>dHFZqcxGg
zbrRP}*5_WWd*p{c$~)aK_6}YTQC@a^$lkeEiI(3SNZHKoM5a(w0MHY4
z*DsOa+ed@DKwhvC;QED0gS1o#fV3E-CFlicHe8CXWP=B31JH5u2Wj&mzTJMX)>pdB
z*4`c8gyj#`p2W9ypY}w+Uy-gq*faMb*FRX!eOiq4J1e+Pi<91EyYADTg4AT2AsX2C
zYuJDOqYZ~h%BTO)lCV8K@*k}f``NYkYtKMFDEBASlBg)NJgO20nzK
z8`#8$v|bPt-}I2y4=QBNp&D*5RSn3Kv^?mdZ6Bst00G0b4p2qwI$Uc4Fl@NiDseN7BSZjStS=Fr
zs0S@wIB}lHqRnQH)CQw~$3|-5`W8x2
zMZC)+wE@FcDxAO8myZ}7iNoHF%NGP8Nv#k%rG2T>gWHAXK_C%;N`(NB1mgivxSIp8
zm5*x&K)%Do9<3K!G03aQxMKUO;`0Tzx!jRBv1MuSbBTINY@9?zOTtG$NrRw;qe?2YDu
z;jDau)|k1U(*oGJXSMzqOWmH+8b}Cou4P~=_VV*cxt7gL!}q;<;FI`vw+almb6?ON
zcWNczY=U>lX5a4V%ihfNq_XsvwPZZ6aI9@|CJTK91rQKntysCwf&SJOM(7t=wbFXQm({KSP)AWfdy1vCXlzNGj!k_|1
zVz+eH9yoLVZcq85X8c+JQ$bF
z4!k~)ldWlZ0hGB~6f{}1d0O|>ERte@
z3ZE}XJbHs-RjXW+#TyN7Rz4dyPkRtg-|U))QFfSV^EtRvfa&wK*X8P|OkbdN3aJp<
ziO_wGM8vgh-~z3&`U~YFvyg=vx(^)uLV*W`P-_=x9Wh<}vH-I))XxJKUYC*xGk75e
zbQybTA%d2%!iCx`04Ic_!QeNKwPuN;FU
zsoR5Ok@SQ5!xQH{$;yF15$Ue`=mFj}^`L7J;6UBTcLC%j5vg+Hek37Jds*XrpR!GC
zX&&B)xU`BL%;TlLoTo*SP+r8_m^CWcy>Dx`OLN)$x3y-0Wz>>5HcHmJZ0FnBixJgy
zhl-Q7uN2^iN>=T1Hh7WtsC;1!TfazaA9W*tsiEL6A;+tWv_=tLq6AN0R#mMEW?bIb`v1Z48N%jsN8}u}4=JIn%4;sr|SmwK0AL#_!
zEkJ{%9Q)5wtVSo;5&?b|AT1vY)(Q4%zBX2>VkZT-ZyCqFSjPQ=-{at60agm|mjJ!r
z=YI19I3_^r<=pXg0lpF->I3dLT7XXkaDK=g2MMrLfZqh@x`O*nU!grL?^?xvS)nC?
z46H$c7TM`%9~FZ8gLq#GOV#znBR3xx$45|dL+r;2wCDz8^QVJc3{HbP5DWR#0_|n#
z#KM(4yL(om>L=M|f$6I__WUaDw_O0&M{ulWuYJTF_X-fXnmcB#=8oS9;9kSA@d9iS
zz+B569~9t20WJ#AYaRE?72xPPQT48cT2e;&YV>OGPJ9(=k0A&HPl4&KYs7%tO~fF#
z8;L<~H>ZcCw1d2p_%A*J;es88@y~&w7r-hDwaA2v`{)}>t);-Hh^~*h3GM)3KcAn>
zUMtd4S|=
z1IOMG;FJIzH*&`*0vr&)^9gr+Mu5)+2>g^gJ|w^j0sau+c3V(9-~Jbh$NdY%;dT?n
z$?ZmpliQ6Hj{~J|qBv1<$6@fO16c1OY!E+SBa5_mK!Ynqyx9FWaj-~$Y5_WI=8jnc
zd?$eWGf|htj{p%{wErw)stz5Gx6=er2P#Y4HuGkWdeAHWwV%ESbz6-J+$o3iy{2liQVC
zPzL)4UxCRD^YCxmw8Z8&){y*`EJt{Hlge8`Z}`sJwJ!c^9~sfUzVWz549y^U+p&Ga
zTDxUCPD1n8iS3%FG2bJguW0T-x>Tmu9ca4{y+eyfkj^`_l&04tBx!F#^hip_
zOMDKxUX`r#tJ#ztTGNDUAx*d*g=h?2zJavVpYXs<$qubq&uf8<>jP02v({%J608>>
z5*J=u|0DWTILEp0THP8uZKw9MBwe-_eW@MQuxEVvYpu8ZWi~7RT5Htit!%+J_fd&!
z3qk9coc-1adYBct{j-KC-=JC7uViW8XpfLo%*=1JrgG&}w*DKfEA1H1egmQb_Wl+x
z{y8ky#6*?u=jhZWkwGK4gA=fcrGKj>WPDHCQ9o%w+VoKmCPsuGv*NMQl|BZJgX!+_|2mg4XHY-=0l2?MfE)(g&;U5Kt%S3t(nXX*mhs#8IX80Y<
zAHZ;#NQI!AC}6luB#iESjc3Vyi7qe9SK7BEaAk-#o2#iJR>H^BjNfO(Mz_LkyxKI=us*vR1itz&l&WHz`hjN!{TCh
z+M!JH8%hP2tq(Tv93K*X8$_B93cv4`v)muG=#1$f@znk!T-S)4hKLxKMcVg?>){hw
z{4RB{P)mJ_BqyCggCIMIoU%mP_ljD)EwF(ih8d|2z<;scRkityYbFz8Fr{qU6)Ji{bW-+Xp=g%;zQFDlbS
zPzZ0lOsvpSVC86O1!}RB?Lip%lP&C*3T-$v=L5N<@v7vhzb}h8hT8?g
zrYz$a<|V=w{LJT8%u7_@CExNiF)tBTl5(1t4b&ub^BMF>J~7dxwGEaxfq|M|RZroQ
zQiCu$NQ)bMEJtkEG0mb~j`g#a6j8rH^V$|9c^BFBGyCb7_Jl7GojAuG*COb|c`ewr
zy_ug|%@pihNY2~O4Y9Wz*J6Yrt9-oGa!iY6@xN#qyLw!^r5ERenJhxUEqEPzCoJvxbd;j{Lh`xlDhnIp7%{oBfJlOfn%IRvUP&qyyqQ`
zCg1aud>xLk6V)ttsvOQvp3r*vYJAWCK$ibw{b&C!c=lIo
z5m7!}ghZ!T42+*FWa}`KPH7QxO#zEOrOk}0f^MNJU#<`3RqEcC&9x+Z1qgNjLQ
zBN?&|^ms*Oz}Q5EE`;bh$3f=f@l
zQ0%Cn>qR@k>l1++CGz5FaJK(sJ5Pg$^%wi=w3gP$aMcobo?Z$>Jah@%)cnmlpV3+m
z{zrkp78I)#dJk$oN@~rgS3^K~@PWw>T2_+vPG%-QhylmMM@K7PKX>pm7BEZ&WS8OK
zF}C@P7Soc~BB-O2D(x-lB}xvORf-$#nG$y=Q$%%4`VFjX%o(2Fz}aSO&~Fgv$YVEFPI^*0Vhy#5AZw5qn2t8+zdJH->-a6fi8{y@Y
zVu2GE7uIt8I$h_ORimZ$zUtfg2g6nXQ4{aiqR9~CU#UsQw&TS7CRsQK=L}fWSF&Gg
zG>-+^CyEYxjrwHgs($M9wkOU(l+jMgXs5_Xv>9u7UQ6*%k3b!8&FDHCK%&{Ys0pH!
zP?Qoj^t=|=cRC%5=v3!T)m!|1I9t#K$vz~kx@Mqu7)@1L3rE-GUwWmt-&aDV5@RFN0Rn!zP<(PQb7&2PrwgB$}39=H)ILsXhl
zDkurym4rKBCFnM}9BKt{EO(~6CQ+JKGa&OBY{DM{yNbeBBTL1q!Y@f3)*vrDX6#IK
z@IW9=H5QB-c+u2Ly}t$_CATTa=xb4&=L4yQcug)+vKJ9db-hVk7BQN
z<~#6YGM`=kWk_9jcITadXHiR8)FBHDaGue-q^zg3m!UZ9Y%e={xu<4fHW9N-CMMPT
zzSk0(j<})hAm>oNIsa3>)R~SiW{JORtvq5Az&jzN0wn8gn)bxJfIBeBn#?Br4mQR+
zEcbUUt>Lu}$vyXGvu}Ra?hE~k20IrEK-Z`SlkAY
zfU&-9hX;O`8SJ+%V(x|&m7Rak?c@b^;7{!p`Qjut@Gosi+V9x2S|y+bIC&W2G&ty)
zw~Js1U~s}58NA?R;Widm-%+(%B~tpQR{KE0-FN;!T6^ht`-gvU(+TvBI;|(rDRtUb
zn6+APhyIbYVnK~Q0@8GYCA}NK97+ELV4|!a0vP1fzs9Y0LV*6Z6Y(}_`rH7#J>EY^
zAEii_?T;Jk4}g)v%rO0@a5NNbQlc=p1EoHMyX1@Pa+v--A{+?UQ>2J$ENZUp^*TOS
zz22Gw2Nv2`|3$bYV3tE2P}r1NZWw0#Gt0w{b
zC{~X~8oOfkyQ8v+`3QMX-zH1QIY0}FHILJ~AkL^bo#xHCae6=b?5FHVoE|TiZ?`YU
z=~E>mmx2A*41?OvNzhCAn>~*;)tiykmnltk9d>mtvpLQ5Uh%bW&W3FSA+aIjK?MvZ
z$U*dSxValU*Gx}}ufxMNq&`759R5o`FG0uHEseFVIwrU-vs;t&TjY`~HabZ^C}&?~
zJ(KnBao48{Nf60D*+t1Z*+$%&tjD0e4khciNQ>>@6n&K}pRw&t&Glwdi@JGJd?xuE
z);~a6I~-lTm(o@)PLpX4OhGcH!r9DdsdrSfP!(Lz_i3rm>_tsUygDEC#E$=()j^s;
zzk=kFkWr-thxXfUDKJy=iQq&?TKrJAe1ixMgttKm2@4xmeR#f3V@+Tj{f$i4D`)@mBf+PIp*3>(*KyD}~zyt@RURg*vCL-rOm@YuoMgD3TM|
z-(Ft`Igv3Pkk1A-qk|sP@j@iAE{%;Yd|(zeRuEItSh1CGup^BxT<0U{U2y~Vku{NI
zV4l9tvWgCRdkC3?cGMwc!XD|UPn2sWv9gYO6M}zt)JIDz?Gc^Onj~P+Szm;FBfIc?
zyL8c$oBS2&%}C_(sV|Rn^?CfpvbyN)NRXuq&%@bOpCsqaVpF>!55SMQ>Te>CR^2dc
zkjF#a_4kp-gxmDwrr7;ZCgzH*pHVL67nBJ&yV%%JMd<4cOTUd*B>y(b#Qya*
zJq^+_c4SZ8qEv@k`iDqW>cvxS(o4T%^hSl4Xq)mO^4Aw|+|UcjDzHcP*S7>oM_9r`m?1CN|2(80RYJau3S9M!
z9Gs!hX`&|0J~>Q(L}IxOqFWHJ?iJQE-V9(hBlK6KXV}=0dVgs=D1-zjdWtoCLa*vm9xImkGJ;s`N(qK_be0ed$CFTiRu^m)
z2!{X*Hi8x8R2)ldYDH{1~!$WU}2&w
zOYz{fN{vPe!oUpVYfc
zkFXtfzbv~
zvwHMJL>?)hz4w&fG;ugB!TB)A8Q{uSp>Zgbi|&Ub9?){VtD+2T#_EgMo9TMEbU$P1
z`cUaHb|IZt4L%8M<Ori0p&rIE$Lj`LHx2|G#P*HT?~)#*I4`r@@z|>jWb4Nxnf`45cs;er
zJ<>f?+`??isyfMm5wL`9_c+dPyZg^pbLd=q05Dp(9#H
z5c|MdKr1tpx(H~2-`a-ht`*o&!5l*lqfI?t5x||>ikxvkX$RmhCvx
zUjVk5!M&nIzyfc;<=%k#Y1SOtTcP2hX`;KnrqI0++$vc!hm@{4n=iOykcBeSKDv~0
zM2YqhM2SiWqC~|6QKBM1+Ot&<9*Rvi;YFM*<)M7!pn5!|S_*-bE)qmaH3X4TH9@3Q
z8S2j9No7H!l#v?_U0nPEJse%_?=DFl*QOb|)s5kykC1d&uW!T$}Jx?fMG4B3xP
znVDN?TP!H$@=>aLv{i#y#z(03LEXG%Dxo}4rkw;)recC9QxQRwsSwa=g#!xVs9h=H
zg<;flN=5DZ24=tuG_Ro`Na_qhBvnliNmUX=Qssb@R2|_+s+RC_o>Z+!s*kr&dEp3w
zl;#seO1T7)QZ_-Plt~bTDj*ozF~gaU534Yo_v-L;*J|`+9rAnSsM$jGJq%s>J2nt?D
zL6KA~K_pc}5J^>2R3w!{;gD1|pjFM2%5KC9bvvbms^#$z=o=IwzY*0e7a`Wr(Fe3H
zCU0c4gMxqx9BGDRxNVJ+@cn10yKCHG~99a=?K&qL5
zRwbm@(dH36)gFGS1|tws&8NUfbumGtnnw_+<`P6vvjHj5)r2F_0>ZN+DA59u=xsdF
z3{h6lnNlr*L?5NPlOR$pCWusv2qM)&KWsRHXV46$ug*2@(~lez$^(
zgwrw-iGfRQ**whxQRl9{I_s~)%GK4Y&pb%$K2^P#YJ{q9A&B&g2qOJLf=It03iA(T
zP(lu<`c6QrfLDE|sCpOgpems5f-2=xZz<%xBOiTERDEOP-oY)s0n2z&D-untApDc>
zMX+L(QrhJ_?R`jF#LTFO@)fq0LZh%338Jty1X0*(f+%byK@g!l3VTeN#ldXL1hlfD
zT7jnGOLAtkZ%NLHPSo&f5`4UCay^+aHZaW-eF~##{zaavDLv#_KoEK66GWbQ1VNV~
zg2-zm4_z74!Uvx8cnXl&c?$-u|5#gJh6h9VLhCYNN;E1NqgE1M;gTi2SlC
zAo9xsv~psu)h{c?SHIjCsvoUa-uybF0jzdi9P=p+&?!3_eI4IcC8v3AG&?p|kB~aD
zB`@hsTDH3uv7zGDJgOYra$q81K2@-qCptfd7cc{|d0?IL
zIxmh9`KYB{t_3-OR|QlL-oxMG)o5B#83VddpK2!}+RC
z+RIVqv`?K+VIa|$3uxuVrgKJ{7#6uW(ukevV!Wq;7cx3|_?|G9cB6?z8pL1ijb9K;
z+p@yz5-6xoV53L?nyJX8h$rBo7W2{>a+LMoZRxuTXDZZ<)LBATBtqYX*pi`qQgG72
z2zjFciijVN`fenscLrumAczLYXM^TokKdL}n4`C1&8Na%Qr9kyo#cxu7Xw=P
zaWqbf<2ZfOs8UF{l0qQi3W6YHIYFdbN)YLm09xJ9T7-wL;71#@7rB=3^r}UAe0oEA
z+3^TR4HM70%+X_R?T!IQP8hVgfL0BUlpD_r(E?Mz3J?lS>;g+1Px-1&=vl0JRd2~E
zr+_h$#+JXP_m0^@DWDofltNxSRij9xkjgH8KDHwH>=|@&ri0+?0l
zSGF=_AZS;0t!x?mklUOJw+)3mLrKucM&YU{fg+wjwJ2MXw`?$rE_w<<8o~rowpxmf
zvei(`HW(bZB6I!0SNK$Cr^GOjlZO=Kq)#Oy)!BwTa<6&J$ETGKMvZUf%XRyRy4-y|
zQB$eh+fnY-JTYu&-B{@;ehn{o0b+ut&AjCZ6ugKWQ1C*E+H6b=3`}rcTI9eTL^xqP2tiD@8{E
z^D`1bLQ0^9m#_d)K|)$~QsjM!#BocJOADiTM&vk}bt|0lq8wH)@0)QBB`4l2t{%}ovp{R+A+xfi7dTm_gp%kptI!=L}$w;h^EXWh^DMXO4c`^{9=d6{c9#9XUsNc$0S
zzYpSdah#?69bKg}1ka?Udxn!h7jl5bY@ahA4@TZ3T#AsHUP6}9B_yuk&r>5(sY-}c
zG};K#Xd_6YjUbJ-mVy%-Ucb#~^i&!7(EtRr3OOxGTk;Nw-V}N--+dwJ!e$7Dq*oI}
z(gg&ObUr~Ood*be!{Drt1Mier@X0Z+8PT_>8Q=c42_@@VoP$i}UX8xK-blSY)rnr-
zi25!4N1?VPvU5{$g;tcv8c)-Q#1|tibSq*_QyR@EjfR{LoaWfqjAc*5eOz%fRxnK;
z*egE?1wn}y6GVyg2%^Nf1X1E_Kwje28A$vf0g@-ByB4T)a;{C{
z6NO0}XBa0&k{6Ts-m|Vgcz|mO4>~{j`ZqOHY8?O0O74i8L;hKp)X_Nx
zpVW@YfYgozsT~PYI})UJ1jJwnnW4vY<$^mXMk%^S3HehCVU!kozl-r*MBf;tr35ib
z_W@c($*lNET!@9Uj2U_(%ug(2svg6>U8sZoJwrEctD(>+#TkMqMKwW`qLLs=Q4VPB
z1M%v}0om7*LwPc_OKq|^>4mbGnR-&s>=cBM>D44>CVdJ+ivhS>M3a*P9_Vu5d%pPY
zITj~~6grGN?I8uqOg3qz?!K<7g%r|T)dKG~RW+Z!QPm=XsOm;QD?f!-HIit>dkLR+
z{rYckj^59enMw_=dHd~_RQ6h&E6z`V-=_H;Ftt_Jw1he
zo`Xxd;uLl~M{g+?r!v5
zZzGGw4$+18APVN|LEdlPgXo)j5JBoefL0Nbfq-o~JO7m4nEiK-9`+QHDa1l4%1PDr
zma~T9QC}iReTg9TC4$tK0Ijd6FOdWFC2}a|<*aQ^GaYJ<>!%?wbAdcC)$E_G$GfmV
z^4Z_JXok>Y<7Qva(W5hJaMG1&iRSWMAXOzX#889^f+#{cK@_2sAc{}|NG!NBgd>k)
zC`eY5TM1{fRST9OmkL8_rNoOuYWoNxwGx6zt(YKEyQ>Hv#C@zF2O>*jWJzvCoN-ky
zn59@}QM`f6gXV_qsC=6W_~t{1SvT)lDfM!K-TZTv(Mk3%~GwQ@t#@mnZv
zJaEN}&87WYuKu;^s!1a$S?>#JlIu%*FVn}#(wBCpMfw1GLdKr9SZ~8$DcH0`H~7cS
zclA{U^tzuZ(1#fGE}h4)aI?CvKzIA0e-uFQieiD<{`Z&OsL#_NWVR#
z4w>_r6Ku}_E6ZMTNN+5;%PwHI}2;K-X?8T$??CDCqR^t_4y4fCkOdm*ZRoGij=n)EJQOc_IPXT70
z(q{rh{HmWeAol{np;TuvOZZifvYVd93(isjOFE->musuos55$%D&7-dkNpObfy*rO
zH+{Zbc9}Ikt3NBx*=El;tKUgc4xH0p2N+bNx0lPS?Ccu7T9(WA+0Xut5A7VPZe6Id
zNBpTbubI39L?NU5d2h?
zJ>iOuM>s^aqZb@B25P@h^5_9+lJO@`s9AtfLLtrt7_Z8eRSZ$W>-u-6Akb;m7ZryXZ*i)<#iRi~v@*|Dc| NxTVWT`o0X_5*l8AbFA9^`^8QrYe|NPKP
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 |