Commit 68b95a39b1

Andrew Kelley <andrew@ziglang.org>
2023-05-08 20:51:32
InternPool: add ptr-to-int value
Also modify coercion in Sema to be InternPool-aware by calling getCoerced. The unnecessary comptime logic in mod.intValue is deleted too
1 parent fd674d9
src/InternPool.zig
@@ -55,6 +55,7 @@ pub const Key = union(enum) {
         lib_name: u32,
     },
     int: Key.Int,
+    ptr: Key.Ptr,
     enum_tag: struct {
         ty: Index,
         tag: BigIntConst,
@@ -140,6 +141,16 @@ pub const Key = union(enum) {
         };
     };
 
+    pub const Ptr = struct {
+        ty: Index,
+        addr: Addr,
+
+        pub const Addr = union(enum) {
+            decl: DeclIndex,
+            int: Index,
+        };
+    };
+
     pub fn hash32(key: Key) u32 {
         return @truncate(u32, key.hash64());
     }
@@ -176,6 +187,16 @@ pub const Key = union(enum) {
                 for (big_int.limbs) |limb| std.hash.autoHash(hasher, limb);
             },
 
+            .ptr => |ptr| {
+                std.hash.autoHash(hasher, ptr.ty);
+                // Int-to-ptr pointers are hashed separately than decl-referencing pointers.
+                // This is sound due to pointer province rules.
+                switch (ptr.addr) {
+                    .int => |int| std.hash.autoHash(hasher, int),
+                    .decl => @panic("TODO"),
+                }
+            },
+
             .enum_tag => |enum_tag| {
                 std.hash.autoHash(hasher, enum_tag.ty);
                 std.hash.autoHash(hasher, enum_tag.tag.positive);
@@ -237,8 +258,30 @@ pub const Key = union(enum) {
                 return std.meta.eql(a_info, b_info);
             },
 
+            .ptr => |a_info| {
+                const b_info = b.ptr;
+
+                if (a_info.ty != b_info.ty)
+                    return false;
+
+                return switch (a_info.addr) {
+                    .int => |a_int| switch (b_info.addr) {
+                        .int => |b_int| a_int == b_int,
+                        .decl => false,
+                    },
+                    .decl => |a_decl| switch (b_info.addr) {
+                        .int => false,
+                        .decl => |b_decl| a_decl == b_decl,
+                    },
+                };
+            },
+
             .int => |a_info| {
                 const b_info = b.int;
+
+                if (a_info.ty != b_info.ty)
+                    return false;
+
                 return switch (a_info.storage) {
                     .u64 => |aa| switch (b_info.storage) {
                         .u64 => |bb| aa == bb,
@@ -298,9 +341,11 @@ pub const Key = union(enum) {
             .union_type,
             => return .type_type,
 
-            .int => |x| return x.ty,
-            .extern_func => |x| return x.ty,
-            .enum_tag => |x| return x.ty,
+            inline .ptr,
+            .int,
+            .extern_func,
+            .enum_tag,
+            => |x| return x.ty,
 
             .simple_value => |s| switch (s) {
                 .undefined => return .undefined_type,
@@ -724,6 +769,9 @@ pub const Tag = enum(u8) {
     /// only an enum tag, but will be presented via the API with a different Key.
     /// data is SimpleInternal enum value.
     simple_internal,
+    /// A pointer to an integer value.
+    /// data is extra index of PtrInt, which contains the type and address.
+    ptr_int,
     /// Type: u8
     /// data is integer value
     int_u8,
@@ -897,16 +945,13 @@ pub const Array = struct {
     child: Index,
     sentinel: Index,
 
-    pub const Length = packed struct(u64) {
-        len0: u32,
-        len1: u32,
-    };
+    pub const Length = PackedU64;
 
     pub fn getLength(a: Array) u64 {
-        return @bitCast(u64, Length{
-            .len0 = a.len0,
-            .len1 = a.len1,
-        });
+        return (PackedU64{
+            .a = a.len0,
+            .b = a.len1,
+        }).get();
     }
 };
 
@@ -929,6 +974,24 @@ pub const EnumSimple = struct {
     fields_len: u32,
 };
 
+pub const PackedU64 = packed struct(u64) {
+    a: u32,
+    b: u32,
+
+    pub fn get(x: PackedU64) u64 {
+        return @bitCast(u64, x);
+    }
+
+    pub fn init(x: u64) PackedU64 {
+        return @bitCast(PackedU64, x);
+    }
+};
+
+pub const PtrInt = struct {
+    ty: Index,
+    addr: Index,
+};
+
 /// Trailing: Limb for every limbs_len
 pub const Int = struct {
     ty: Index,
@@ -1066,6 +1129,13 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
                 .fields_len = 0,
             } },
         },
+        .ptr_int => {
+            const info = ip.extraData(PtrInt, data);
+            return .{ .ptr = .{
+                .ty = info.ty,
+                .addr = .{ .int = info.addr },
+            } };
+        },
         .int_u8 => .{ .int = .{
             .ty = .u8_type,
             .storage = .{ .u64 = data },
@@ -1188,12 +1258,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
                 }
             }
 
-            const length = @bitCast(Array.Length, array_type.len);
+            const length = Array.Length.init(array_type.len);
             ip.items.appendAssumeCapacity(.{
                 .tag = .type_array_big,
                 .data = try ip.addExtra(gpa, Array{
-                    .len0 = length.len0,
-                    .len1 = length.len1,
+                    .len0 = length.a,
+                    .len1 = length.b,
                     .child = array_type.child,
                     .sentinel = array_type.sentinel,
                 }),
@@ -1237,6 +1307,20 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
         },
         .extern_func => @panic("TODO"),
 
+        .ptr => |ptr| switch (ptr.addr) {
+            .decl => @panic("TODO"),
+            .int => |int| {
+                assert(ptr.ty != .none);
+                ip.items.appendAssumeCapacity(.{
+                    .tag = .ptr_int,
+                    .data = try ip.addExtra(gpa, PtrInt{
+                        .ty = ptr.ty,
+                        .addr = int,
+                    }),
+                });
+            },
+        },
+
         .int => |int| b: {
             switch (int.ty) {
                 .none => unreachable,
@@ -1620,38 +1704,43 @@ pub fn slicePtrType(ip: InternPool, i: Index) Index {
     }
 }
 
-/// Given an existing integer value, returns the same numerical value but with
-/// the supplied type.
-pub fn getCoercedInt(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
-    const key = ip.indexToKey(val);
-    // The key cannot be passed directly to `get`, otherwise in the case of
-    // big_int storage, the limbs would be invalidated before they are read.
-    // Here we pre-reserve the limbs to ensure that the logic in `addInt` will
-    // not use an invalidated limbs pointer.
-    switch (key.int.storage) {
-        .u64 => |x| return ip.get(gpa, .{ .int = .{
-            .ty = new_ty,
-            .storage = .{ .u64 = x },
-        } }),
-        .i64 => |x| return ip.get(gpa, .{ .int = .{
-            .ty = new_ty,
-            .storage = .{ .i64 = x },
-        } }),
-
-        .big_int => |big_int| {
-            const positive = big_int.positive;
-            const limbs = ip.limbsSliceToIndex(big_int.limbs);
-            // This line invalidates the limbs slice, but the indexes computed in the
-            // previous line are still correct.
-            try reserveLimbs(ip, gpa, @typeInfo(Int).Struct.fields.len + big_int.limbs.len);
-            return ip.get(gpa, .{ .int = .{
-                .ty = new_ty,
-                .storage = .{ .big_int = .{
-                    .limbs = ip.limbsIndexToSlice(limbs),
-                    .positive = positive,
-                } },
-            } });
+/// Given an existing value, returns the same value but with the supplied type.
+/// Only some combinations are allowed:
+/// * int to int
+pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
+    switch (ip.indexToKey(val)) {
+        .int => |int| {
+            // The key cannot be passed directly to `get`, otherwise in the case of
+            // big_int storage, the limbs would be invalidated before they are read.
+            // Here we pre-reserve the limbs to ensure that the logic in `addInt` will
+            // not use an invalidated limbs pointer.
+            switch (int.storage) {
+                .u64 => |x| return ip.get(gpa, .{ .int = .{
+                    .ty = new_ty,
+                    .storage = .{ .u64 = x },
+                } }),
+                .i64 => |x| return ip.get(gpa, .{ .int = .{
+                    .ty = new_ty,
+                    .storage = .{ .i64 = x },
+                } }),
+
+                .big_int => |big_int| {
+                    const positive = big_int.positive;
+                    const limbs = ip.limbsSliceToIndex(big_int.limbs);
+                    // This line invalidates the limbs slice, but the indexes computed in the
+                    // previous line are still correct.
+                    try reserveLimbs(ip, gpa, @typeInfo(Int).Struct.fields.len + big_int.limbs.len);
+                    return ip.get(gpa, .{ .int = .{
+                        .ty = new_ty,
+                        .storage = .{ .big_int = .{
+                            .limbs = ip.limbsIndexToSlice(limbs),
+                            .positive = positive,
+                        } },
+                    } });
+                },
+            }
         },
+        else => unreachable,
     }
 }
 
@@ -1708,6 +1797,7 @@ fn dumpFallible(ip: InternPool, arena: Allocator) anyerror!void {
             .simple_type => 0,
             .simple_value => 0,
             .simple_internal => 0,
+            .ptr_int => @sizeOf(PtrInt),
             .int_u8 => 0,
             .int_u16 => 0,
             .int_u32 => 0,
src/Module.zig
@@ -6887,17 +6887,23 @@ pub fn singleConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
     return ptrType(mod, .{ .elem_type = child_type.ip_index, .is_const = true });
 }
 
+pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
+    assert(ty.zigTypeTag(mod) == .Pointer);
+    const i = try intern(mod, .{ .ptr = .{
+        .ty = ty.ip_index,
+        .addr = .{ .int = try intern(mod, .{ .int = .{
+            .ty = ty.ip_index,
+            .storage = .{ .u64 = x },
+        } }) },
+    } });
+    return i.toValue();
+}
+
 pub fn intValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value {
     if (std.debug.runtime_safety) {
-        // TODO: decide if this also works for ABI int types like enums
         const tag = ty.zigTypeTag(mod);
         assert(tag == .Int or tag == .ComptimeInt);
     }
-    if (@TypeOf(x) == comptime_int) {
-        if (comptime std.math.cast(u64, x)) |casted| return intValue_u64(mod, ty, casted);
-        if (comptime std.math.cast(i64, x)) |casted| return intValue_i64(mod, ty, casted);
-        @compileError("Out-of-range comptime_int passed to Module.intValue");
-    }
     if (std.math.cast(u64, x)) |casted| return intValue_u64(mod, ty, casted);
     if (std.math.cast(i64, x)) |casted| return intValue_i64(mod, ty, casted);
     var limbs_buffer: [4]usize = undefined;
src/Sema.zig
@@ -15096,7 +15096,7 @@ fn analyzePtrArithmetic(
                         .ptr_sub => addr - elem_size * offset_int,
                         else => unreachable,
                     };
-                    const new_ptr_val = try mod.intValue(new_ptr_ty, new_addr);
+                    const new_ptr_val = try mod.ptrIntValue(new_ptr_ty, new_addr);
                     return sema.addConstant(new_ptr_ty, new_ptr_val);
                 }
                 if (air_tag == .ptr_sub) {
@@ -19931,7 +19931,7 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         if (addr != 0 and ptr_align != 0 and addr % ptr_align != 0)
             return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{ptr_ty.fmt(sema.mod)});
 
-        return sema.addConstant(ptr_ty, try mod.intValue(ptr_ty, addr));
+        return sema.addConstant(ptr_ty, try mod.ptrIntValue(ptr_ty, addr));
     }
 
     try sema.requireRuntimeBlock(block, src, operand_src);
@@ -25640,8 +25640,13 @@ fn coerceExtra(
     var in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src);
     if (in_memory_result == .ok) {
         if (maybe_inst_val) |val| {
-            // Keep the comptime Value representation; take the new type.
-            return sema.addConstant(dest_ty, val);
+            if (val.ip_index == .none or val.ip_index == .null_value) {
+                // Keep the comptime Value representation; take the new type.
+                return sema.addConstant(dest_ty, val);
+            } else {
+                const new_val = try mod.intern_pool.getCoerced(mod.gpa, val.ip_index, dest_ty.ip_index);
+                return sema.addConstant(dest_ty, new_val.toValue());
+            }
         }
         try sema.requireRuntimeBlock(block, inst_src, null);
         return block.addBitCast(dest_ty, inst);
@@ -26014,7 +26019,7 @@ fn coerceExtra(
                         if (!opts.report_err) return error.NotCoercible;
                         return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) });
                     }
-                    const new_val = try mod.intern_pool.getCoercedInt(sema.gpa, val.ip_index, dest_ty.ip_index);
+                    const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.ip_index, dest_ty.ip_index);
                     return try sema.addConstant(dest_ty, new_val.toValue());
                 }
                 if (dest_ty.zigTypeTag(mod) == .ComptimeInt) {
@@ -31673,10 +31678,13 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             },
             .struct_type => @panic("TODO"),
             .union_type => @panic("TODO"),
+
+            // values, not types
             .simple_value => unreachable,
             .extern_func => unreachable,
             .int => unreachable,
-            .enum_tag => unreachable, // it's a value, not a type
+            .ptr => unreachable,
+            .enum_tag => unreachable,
         },
     };
 }
@@ -33193,10 +33201,13 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
             },
             .struct_type => @panic("TODO"),
             .union_type => @panic("TODO"),
+
+            // values, not types
             .simple_value => unreachable,
             .extern_func => unreachable,
             .int => unreachable,
-            .enum_tag => unreachable, // it's a value, not a type
+            .ptr => unreachable,
+            .enum_tag => unreachable,
         },
     }
 }
@@ -33253,7 +33264,14 @@ pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref {
         const result = Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
         // This assertion can be removed when the `ty` parameter is removed from
         // this function thanks to the InternPool transition being complete.
-        assert(Type.eql(sema.typeOf(result), ty, sema.mod));
+        if (std.debug.runtime_safety) {
+            const val_ty = sema.typeOf(result);
+            if (!Type.eql(val_ty, ty, sema.mod)) {
+                std.debug.panic("addConstant type mismatch: '{}' vs '{}'\n", .{
+                    ty.fmt(sema.mod), val_ty.fmt(sema.mod),
+                });
+            }
+        }
         return result;
     }
     const ty_inst = try sema.addType(ty);
@@ -33752,10 +33770,13 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             },
             .struct_type => @panic("TODO"),
             .union_type => @panic("TODO"),
+
+            // values, not types
             .simple_value => unreachable,
             .extern_func => unreachable,
             .int => unreachable,
-            .enum_tag => unreachable, // it's a value, not a type
+            .ptr => unreachable,
+            .enum_tag => unreachable,
         },
     };
 }
src/type.zig
@@ -142,11 +142,12 @@ pub const Type = struct {
                     .var_args_param => unreachable,
                 },
 
-                .extern_func,
-                .int,
-                .enum_tag,
-                .simple_value,
-                => unreachable, // it's a value, not a type
+                // values, not types
+                .extern_func => unreachable,
+                .int => unreachable,
+                .ptr => unreachable,
+                .enum_tag => unreachable,
+                .simple_value => unreachable,
             },
         }
     }
@@ -1576,6 +1577,7 @@ pub const Type = struct {
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
+                .ptr => unreachable,
                 .enum_tag => unreachable,
             },
         }
@@ -1842,10 +1844,13 @@ pub const Type = struct {
                 },
                 .struct_type => @panic("TODO"),
                 .union_type => @panic("TODO"),
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
-                .enum_tag => unreachable, // it's a value, not a type
+                .ptr => unreachable,
+                .enum_tag => unreachable,
             },
         }
     }
@@ -1950,10 +1955,13 @@ pub const Type = struct {
                 },
                 .struct_type => @panic("TODO"),
                 .union_type => @panic("TODO"),
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
-                .enum_tag => unreachable, // it's a value, not a type
+                .ptr => unreachable,
+                .enum_tag => unreachable,
             },
         };
     }
@@ -2348,10 +2356,13 @@ pub const Type = struct {
                 },
                 .struct_type => @panic("TODO"),
                 .union_type => @panic("TODO"),
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
-                .enum_tag => unreachable, // it's a value, not a type
+                .ptr => unreachable,
+                .enum_tag => unreachable,
             },
         }
     }
@@ -2759,10 +2770,13 @@ pub const Type = struct {
                 },
                 .struct_type => @panic("TODO"),
                 .union_type => @panic("TODO"),
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
-                .enum_tag => unreachable, // it's a value, not a type
+                .ptr => unreachable,
+                .enum_tag => unreachable,
             },
         }
     }
@@ -2926,10 +2940,13 @@ pub const Type = struct {
             },
             .struct_type => @panic("TODO"),
             .union_type => @panic("TODO"),
+
+            // values, not types
             .simple_value => unreachable,
             .extern_func => unreachable,
             .int => unreachable,
-            .enum_tag => unreachable, // it's a value, not a type
+            .ptr => unreachable,
+            .enum_tag => unreachable,
         };
 
         const strat: AbiAlignmentAdvancedStrat = if (opt_sema) |sema| .{ .sema = sema } else .eager;
@@ -3780,9 +3797,12 @@ pub const Type = struct {
                 .simple_type => unreachable, // handled via Index enum tag above
                 .struct_type => @panic("TODO"),
                 .union_type => unreachable,
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
+                .ptr => unreachable,
                 .enum_tag => unreachable,
             },
         };
@@ -4152,10 +4172,13 @@ pub const Type = struct {
                 },
                 .struct_type => @panic("TODO"),
                 .union_type => @panic("TODO"),
+
+                // values, not types
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
-                .enum_tag => unreachable, // it's a value, not a type
+                .ptr => unreachable,
+                .enum_tag => unreachable,
             },
         };
     }
@@ -4319,6 +4342,7 @@ pub const Type = struct {
                 .simple_value => unreachable,
                 .extern_func => unreachable,
                 .int => unreachable,
+                .ptr => unreachable,
                 .enum_tag => unreachable, // it's a value, not a type
             },
         };