From 68b95a39b1fe734b938ec02fa2b16bbb63170f87 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 8 May 2023 11:51:32 -0700 Subject: [PATCH] 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 --- src/InternPool.zig | 178 ++++++++++++++++++++++++++++++++++----------- src/Module.zig | 18 +++-- src/Sema.zig | 39 +++++++--- src/type.zig | 46 +++++++++--- 4 files changed, 211 insertions(+), 70 deletions(-) diff --git a/src/InternPool.zig b/src/InternPool.zig index a49b98bd50..27d0fb9445 100644 --- a/src/InternPool.zig +++ b/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 }, - } }), +/// 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, - } }, - } }); + .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, diff --git a/src/Module.zig b/src/Module.zig index d06d22402a..a5e61fa4f9 100644 --- a/src/Module.zig +++ b/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; diff --git a/src/Sema.zig b/src/Sema.zig index 204fb79885..58c87db371 100644 --- a/src/Sema.zig +++ b/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, }, }; } diff --git a/src/type.zig b/src/type.zig index 4ef5da57ea..8fc4c20c45 100644 --- a/src/type.zig +++ b/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 }, };