Commit d28fc5bacb

Jacob Young <jacobly0@users.noreply.github.com>
2023-05-20 03:35:55
InternPool: add repeated aggregate storage
1 parent 9ff514b
src/InternPool.zig
@@ -506,7 +506,12 @@ pub const Key = union(enum) {
 
     pub const Aggregate = struct {
         ty: Index,
-        fields: []const Index,
+        storage: Storage,
+
+        pub const Storage = union(enum) {
+            elems: []const Index,
+            repeated_elem: Index,
+        };
     };
 
     pub fn hash32(key: Key) u32 {
@@ -578,7 +583,14 @@ pub const Key = union(enum) {
 
             .aggregate => |aggregate| {
                 std.hash.autoHash(hasher, aggregate.ty);
-                for (aggregate.fields) |field| std.hash.autoHash(hasher, field);
+                std.hash.autoHash(hasher, @as(
+                    @typeInfo(Key.Aggregate.Storage).Union.tag_type.?,
+                    aggregate.storage,
+                ));
+                switch (aggregate.storage) {
+                    .elems => |elems| for (elems) |elem| std.hash.autoHash(hasher, elem),
+                    .repeated_elem => |elem| std.hash.autoHash(hasher, elem),
+                }
             },
 
             .error_set_type => |error_set_type| {
@@ -756,7 +768,14 @@ pub const Key = union(enum) {
             .aggregate => |a_info| {
                 const b_info = b.aggregate;
                 if (a_info.ty != b_info.ty) return false;
-                return std.mem.eql(Index, a_info.fields, b_info.fields);
+
+                const StorageTag = @typeInfo(Key.Aggregate.Storage).Union.tag_type.?;
+                if (@as(StorageTag, a_info.storage) != @as(StorageTag, b_info.storage)) return false;
+
+                return switch (a_info.storage) {
+                    .elems => |a_elems| std.mem.eql(Index, a_elems, b_info.storage.elems),
+                    .repeated_elem => |a_elem| a_elem == b_info.storage.repeated_elem,
+                };
             },
             .anon_struct_type => |a_info| {
                 const b_info = b.anon_struct_type;
@@ -1407,6 +1426,9 @@ pub const Tag = enum(u8) {
     /// An instance of a struct, array, or vector.
     /// data is extra index to `Aggregate`.
     aggregate,
+    /// An instance of an array or vector with every element being the same value.
+    /// data is extra index to `Repeated`.
+    repeated,
 };
 
 /// Trailing:
@@ -1446,6 +1468,13 @@ pub const Aggregate = struct {
     ty: Index,
 };
 
+pub const Repeated = struct {
+    /// The type of the aggregate.
+    ty: Index,
+    /// The value of every element.
+    elem_val: Index,
+};
+
 /// Trailing:
 /// 0. type: Index for each fields_len
 /// 1. value: Index for each fields_len
@@ -2049,13 +2078,13 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
                 // as the tuple case below).
                 .struct_type => .{ .aggregate = .{
                     .ty = ty,
-                    .fields = &.{},
+                    .storage = .{ .elems = &.{} },
                 } },
                 // There is only one possible value precisely due to the
                 // fact that this values slice is fully populated!
                 .anon_struct_type => |anon_struct_type| .{ .aggregate = .{
                     .ty = ty,
-                    .fields = anon_struct_type.values,
+                    .storage = .{ .elems = anon_struct_type.values },
                 } },
                 else => unreachable,
             };
@@ -2066,7 +2095,14 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
             const fields = @ptrCast([]const Index, ip.extra.items[extra.end..][0..len]);
             return .{ .aggregate = .{
                 .ty = extra.data.ty,
-                .fields = fields,
+                .storage = .{ .elems = fields },
+            } };
+        },
+        .repeated => {
+            const extra = ip.extraData(Repeated, data);
+            return .{ .aggregate = .{
+                .ty = extra.ty,
+                .storage = .{ .repeated_elem = extra.elem_val },
             } };
         },
         .union_value => .{ .un = ip.extraData(Key.Union, data) },
@@ -2663,10 +2699,18 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
 
         .aggregate => |aggregate| {
             assert(aggregate.ty != .none);
-            for (aggregate.fields) |elem| assert(elem != .none);
-            assert(aggregate.fields.len == ip.aggregateTypeLen(aggregate.ty));
+            const aggregate_len = ip.aggregateTypeLen(aggregate.ty);
+            switch (aggregate.storage) {
+                .elems => |elems| {
+                    assert(elems.len == aggregate_len);
+                    for (elems) |elem| assert(elem != .none);
+                },
+                .repeated_elem => |elem| {
+                    assert(elem != .none);
+                },
+            }
 
-            if (aggregate.fields.len == 0) {
+            if (aggregate_len == 0) {
                 ip.items.appendAssumeCapacity(.{
                     .tag = .only_possible_value,
                     .data = @enumToInt(aggregate.ty),
@@ -2676,7 +2720,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
 
             switch (ip.indexToKey(aggregate.ty)) {
                 .anon_struct_type => |anon_struct_type| {
-                    if (std.mem.eql(Index, anon_struct_type.values, aggregate.fields)) {
+                    if (switch (aggregate.storage) {
+                        .elems => |elems| std.mem.eql(Index, anon_struct_type.values, elems),
+                        .repeated_elem => |elem| for (anon_struct_type.values) |value| {
+                            if (value != elem) break false;
+                        } else true,
+                    }) {
                         // This encoding works thanks to the fact that, as we just verified,
                         // the type itself contains a slice of values that can be provided
                         // in the aggregate fields.
@@ -2690,9 +2739,33 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
                 else => {},
             }
 
+            if (switch (aggregate.storage) {
+                .elems => |elems| for (elems[1..]) |elem| {
+                    if (elem != elems[0]) break false;
+                } else true,
+                .repeated_elem => true,
+            }) {
+                try ip.extra.ensureUnusedCapacity(
+                    gpa,
+                    @typeInfo(Repeated).Struct.fields.len,
+                );
+
+                ip.items.appendAssumeCapacity(.{
+                    .tag = .repeated,
+                    .data = ip.addExtraAssumeCapacity(Repeated{
+                        .ty = aggregate.ty,
+                        .elem_val = switch (aggregate.storage) {
+                            .elems => |elems| elems[0],
+                            .repeated_elem => |elem| elem,
+                        },
+                    }),
+                });
+                return @intToEnum(Index, ip.items.len - 1);
+            }
+
             try ip.extra.ensureUnusedCapacity(
                 gpa,
-                @typeInfo(Aggregate).Struct.fields.len + aggregate.fields.len,
+                @typeInfo(Aggregate).Struct.fields.len + aggregate_len,
             );
 
             ip.items.appendAssumeCapacity(.{
@@ -2701,7 +2774,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
                     .ty = aggregate.ty,
                 }),
             });
-            ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, aggregate.fields));
+            ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, aggregate.storage.elems));
         },
 
         .un => |un| {
@@ -3417,6 +3490,7 @@ fn dumpFallible(ip: InternPool, arena: Allocator) anyerror!void {
                 const fields_len = @intCast(u32, ip.aggregateTypeLen(info.ty));
                 break :b @sizeOf(Aggregate) + (@sizeOf(u32) * fields_len);
             },
+            .repeated => @sizeOf(Repeated),
 
             .float_f16 => 0,
             .float_f32 => 0,
src/Sema.zig
@@ -12671,7 +12671,7 @@ fn analyzeTupleCat(
     const runtime_src = opt_runtime_src orelse {
         const tuple_val = try mod.intern(.{ .aggregate = .{
             .ty = tuple_ty,
-            .fields = values,
+            .storage = .{ .elems = values },
         } });
         return sema.addConstant(tuple_ty.toType(), tuple_val.toValue());
     };
@@ -12989,7 +12989,7 @@ fn analyzeTupleMul(
     const runtime_src = opt_runtime_src orelse {
         const tuple_val = try mod.intern(.{ .aggregate = .{
             .ty = tuple_ty,
-            .fields = values,
+            .storage = .{ .elems = values },
         } });
         return sema.addConstant(tuple_ty.toType(), tuple_val.toValue());
     };
@@ -18227,7 +18227,7 @@ fn zirStructInitAnon(
     const runtime_index = opt_runtime_index orelse {
         const tuple_val = try mod.intern(.{ .aggregate = .{
             .ty = tuple_ty,
-            .fields = values,
+            .storage = .{ .elems = values },
         } });
         return sema.addConstantMaybeRef(block, tuple_ty.toType(), tuple_val.toValue(), is_ref);
     };
@@ -18442,7 +18442,7 @@ fn zirArrayInitAnon(
     const runtime_src = opt_runtime_src orelse {
         const tuple_val = try mod.intern(.{ .aggregate = .{
             .ty = tuple_ty,
-            .fields = values,
+            .storage = .{ .elems = values },
         } });
         return sema.addConstantMaybeRef(block, tuple_ty.toType(), tuple_val.toValue(), is_ref);
     };
@@ -29176,7 +29176,7 @@ fn coerceTupleToTuple(
         tuple_ty,
         (try mod.intern(.{ .aggregate = .{
             .ty = tuple_ty.ip_index,
-            .fields = field_vals,
+            .storage = .{ .elems = field_vals },
         } })).toValue(),
     );
 }
@@ -33085,7 +33085,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 // one-possible-value in type.zig.
                 const empty = try mod.intern(.{ .aggregate = .{
                     .ty = ty.ip_index,
-                    .fields = &.{},
+                    .storage = .{ .elems = &.{} },
                 } });
                 return empty.toValue();
             },
@@ -33098,7 +33098,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 // therefore has one possible value.
                 return (try mod.intern(.{ .aggregate = .{
                     .ty = ty.ip_index,
-                    .fields = tuple.values,
+                    .storage = .{ .elems = tuple.values },
                 } })).toValue();
             },
 
src/type.zig
@@ -2738,7 +2738,7 @@ pub const Type = struct {
                     // one-possible-value logic in Sema.zig.
                     const empty = try mod.intern(.{ .aggregate = .{
                         .ty = ty.ip_index,
-                        .fields = &.{},
+                        .storage = .{ .elems = &.{} },
                     } });
                     return empty.toValue();
                 },
@@ -2751,7 +2751,7 @@ pub const Type = struct {
                     // therefore has one possible value.
                     return (try mod.intern(.{ .aggregate = .{
                         .ty = ty.ip_index,
-                        .fields = tuple.values,
+                        .storage = .{ .elems = tuple.values },
                     } })).toValue();
                 },
 
src/value.zig
@@ -2665,7 +2665,10 @@ pub const Value = struct {
                 else => unreachable,
             },
             else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
-                .aggregate => |aggregate| aggregate.fields[index].toValue(),
+                .aggregate => |aggregate| switch (aggregate.storage) {
+                    .elems => |elems| elems[index],
+                    .repeated_elem => |elem| elem,
+                }.toValue(),
                 else => unreachable,
             },
         }
@@ -2753,8 +2756,11 @@ pub const Value = struct {
             else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
                 .undef => return true,
                 .simple_value => |v| if (v == .undefined) return true,
-                .aggregate => |aggregate| for (aggregate.fields) |field| {
-                    if (try anyUndef(field.toValue(), mod)) return true;
+                .aggregate => |aggregate| switch (aggregate.storage) {
+                    .elems => |elems| for (elems) |elem| {
+                        if (try anyUndef(elem.toValue(), mod)) return true;
+                    },
+                    .repeated_elem => |elem| if (try anyUndef(elem.toValue(), mod)) return true,
                 },
                 else => {},
             },