Commit 0a931393c0

Carl Åstholm <carl@astholm.se>
2024-03-12 00:01:38
std.enums: Increase eval branch quotas
1 parent 4f782d1
Changed files (1)
lib
lib/std/enums.zig
@@ -5,14 +5,18 @@ const assert = std.debug.assert;
 const testing = std.testing;
 const EnumField = std.builtin.Type.EnumField;
 
+/// Increment this value when adding APIs that add single backwards branches.
+const eval_branch_quota_cushion = 5;
+
 /// Returns a struct with a field matching each unique named enum element.
 /// If the enum is extern and has multiple names for the same value, only
 /// the first name is used.  Each field is of type Data and has the provided
 /// default, which may be undefined.
 pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_default: ?Data) type {
+    @setEvalBranchQuota(@typeInfo(E).Enum.fields.len + eval_branch_quota_cushion);
     const StructField = std.builtin.Type.StructField;
     var fields: []const StructField = &[_]StructField{};
-    for (std.meta.fields(E)) |field| {
+    for (@typeInfo(E).Enum.fields) |field| {
         fields = fields ++ &[_]StructField{.{
             .name = field.name ++ "",
             .type = Data,
@@ -76,7 +80,7 @@ test tagName {
 pub fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int {
     var max_value: comptime_int = -1;
     const max_usize: comptime_int = ~@as(usize, 0);
-    const fields = std.meta.fields(E);
+    const fields = @typeInfo(E).Enum.fields;
     for (fields) |f| {
         if (f.value < 0) {
             @compileError("Cannot create a direct enum array for " ++ @typeName(E) ++ ", field ." ++ f.name ++ " has a negative value.");
@@ -258,6 +262,7 @@ pub fn EnumSet(comptime E: type) type {
 
         /// Initializes the set using a struct of bools
         pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self {
+            @setEvalBranchQuota(2 * @typeInfo(E).Enum.fields.len);
             var result: Self = .{};
             inline for (0..Self.len) |i| {
                 const key = comptime Indexer.keyForIndex(i);
@@ -438,6 +443,7 @@ pub fn EnumMap(comptime E: type, comptime V: type) type {
 
         /// Initializes the map using a sparse struct of optionals
         pub fn init(init_values: EnumFieldStruct(E, ?Value, null)) Self {
+            @setEvalBranchQuota(2 * @typeInfo(E).Enum.fields.len);
             var result: Self = .{};
             inline for (0..Self.len) |i| {
                 const key = comptime Indexer.keyForIndex(i);
@@ -447,6 +453,7 @@ pub fn EnumMap(comptime E: type, comptime V: type) type {
                     result.values[i] = v.*;
                 }
             }
+            return result;
         }
 
         /// Initializes a full mapping with all keys set to value.
@@ -469,6 +476,7 @@ pub fn EnumMap(comptime E: type, comptime V: type) type {
         /// Initializes a full mapping with a provided default.
         /// Consider using EnumArray instead if the map will remain full.
         pub fn initFullWithDefault(comptime default: ?Value, init_values: EnumFieldStruct(E, Value, default)) Self {
+            @setEvalBranchQuota(2 * @typeInfo(E).Enum.fields.len);
             var result: Self = .{
                 .bits = Self.BitSet.initFull(),
                 .values = undefined,
@@ -641,6 +649,7 @@ pub fn BoundedEnumMultiset(comptime E: type, comptime CountSize: type) type {
 
         /// Initializes the multiset using a struct of counts.
         pub fn init(init_counts: EnumFieldStruct(E, CountSize, 0)) Self {
+            @setEvalBranchQuota(2 * @typeInfo(E).Enum.fields.len);
             var self = initWithCount(0);
             inline for (@typeInfo(E).Enum.fields) |field| {
                 const c = @field(init_counts, field.name);
@@ -1044,6 +1053,7 @@ pub fn EnumArray(comptime E: type, comptime V: type) type {
 
         /// Initializes values in the enum array, with the specified default.
         pub fn initDefault(comptime default: ?Value, init_values: EnumFieldStruct(E, Value, default)) Self {
+            @setEvalBranchQuota(2 * @typeInfo(E).Enum.fields.len);
             var result: Self = .{ .values = undefined };
             inline for (0..Self.len) |i| {
                 const key = comptime Indexer.keyForIndex(i);
@@ -1214,6 +1224,10 @@ test "EnumSet const iterator" {
 }
 
 pub fn EnumIndexer(comptime E: type) type {
+    // Assumes that the enum fields are sorted in ascending order (optimistic).
+    // Unsorted enums may require the user to manually increase the quota.
+    @setEvalBranchQuota(3 * @typeInfo(E).Enum.fields.len + eval_branch_quota_cushion);
+
     if (!@typeInfo(E).Enum.is_exhaustive) {
         const BackingInt = @typeInfo(E).Enum.tag_type;
         if (@bitSizeOf(BackingInt) > @bitSizeOf(usize))
@@ -1247,7 +1261,7 @@ pub fn EnumIndexer(comptime E: type) type {
         };
     }
 
-    const const_fields = std.meta.fields(E);
+    const const_fields = @typeInfo(E).Enum.fields;
     var fields = const_fields[0..const_fields.len].*;
     const fields_len = fields.len;
 
@@ -1294,7 +1308,7 @@ pub fn EnumIndexer(comptime E: type) type {
                 // gives up some safety to avoid artificially limiting
                 // the range of signed enum values to max_isize.
                 const enum_value = if (min < 0) @as(isize, @bitCast(i)) +% min else i + min;
-                return @as(E, @enumFromInt(@as(std.meta.Tag(E), @intCast(enum_value))));
+                return @as(E, @enumFromInt(@as(@typeInfo(E).Enum.tag_type, @intCast(enum_value))));
             }
         };
     }
@@ -1440,3 +1454,45 @@ test values {
     };
     try testing.expectEqualSlices(E, &.{ .X, .Y, .Z }, values(E));
 }
+
+test "big enums should not hit the eval branch quota" {
+    const big = struct {
+        const Big = @Type(@as(std.builtin.Type, .{
+            .Enum = .{
+                .tag_type = u16,
+                .fields = make_fields: {
+                    var fields: []const std.builtin.Type.EnumField = &.{};
+                    for (0..1999) |i| {
+                        fields = fields ++ &[_]std.builtin.Type.EnumField{.{
+                            .name = std.fmt.comptimePrint("field_{d}", .{i}),
+                            .value = i,
+                        }};
+                    }
+                    fields = fields ++ &[_]std.builtin.Type.EnumField{.{
+                        .name = "field_9999",
+                        .value = 9999,
+                    }};
+                    break :make_fields fields;
+                },
+                .decls = &.{},
+                .is_exhaustive = true,
+            },
+        }));
+    };
+
+    var set = EnumSet(big.Big).init(.{});
+    _ = &set;
+
+    var map = EnumMap(big.Big, u8).init(undefined);
+    map = EnumMap(big.Big, u8).initFullWith(undefined);
+    map = EnumMap(big.Big, u8).initFullWithDefault(123, .{});
+
+    var multiset = EnumMultiset(big.Big).init(.{});
+    _ = &multiset;
+
+    var bounded_multiset = BoundedEnumMultiset(big.Big, u8).init(.{});
+    _ = &bounded_multiset;
+
+    var array = EnumArray(big.Big, u8).init(undefined);
+    array = EnumArray(big.Big, u8).initDefault(123, .{});
+}