diff --git a/src/Air.zig b/src/Air.zig index fb95d60d00..d202c079bc 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -503,7 +503,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { pub fn getRefType(air: Air, ref: Air.Inst.Ref) Type { const ref_int = @enumToInt(ref); if (ref_int < Air.Inst.Ref.typed_value_map.len) { - return Air.Inst.Ref.typed_value_map[ref_int].val.toType(undefined) catch unreachable; + var buffer: Value.ToTypeBuffer = undefined; + return Air.Inst.Ref.typed_value_map[ref_int].val.toType(&buffer); } const inst_index = ref_int - Air.Inst.Ref.typed_value_map.len; const air_tags = air.instructions.items(.tag); diff --git a/src/Module.zig b/src/Module.zig index 909e54ffa2..d87f20621c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4299,7 +4299,6 @@ pub fn simplePtrType( } pub fn ptrType( - mod: *Module, arena: *Allocator, elem_ty: Type, sentinel: ?Value, @@ -4311,7 +4310,6 @@ pub fn ptrType( @"volatile": bool, size: std.builtin.TypeInfo.Pointer.Size, ) Allocator.Error!Type { - _ = mod; assert(host_size == 0 or bit_offset < host_size * 8); // TODO check if type can be represented by simplePtrType @@ -4328,8 +4326,7 @@ pub fn ptrType( }); } -pub fn optionalType(mod: *Module, arena: *Allocator, child_type: Type) Allocator.Error!Type { - _ = mod; +pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type { switch (child_type.tag()) { .single_const_pointer => return Type.Tag.optional_single_const_pointer.create( arena, @@ -4344,16 +4341,14 @@ pub fn optionalType(mod: *Module, arena: *Allocator, child_type: Type) Allocator } pub fn arrayType( - mod: *Module, arena: *Allocator, len: u64, sentinel: ?Value, elem_type: Type, ) Allocator.Error!Type { - _ = mod; if (elem_type.eql(Type.initTag(.u8))) { if (sentinel) |some| { - if (some.eql(Value.initTag(.zero))) { + if (some.eql(Value.initTag(.zero), elem_type)) { return Type.Tag.array_u8_sentinel_0.create(arena, len); } } else { @@ -4376,12 +4371,10 @@ pub fn arrayType( } pub fn errorUnionType( - mod: *Module, arena: *Allocator, error_set: Type, payload: Type, ) Allocator.Error!Type { - _ = mod; assert(error_set.zigTypeTag() == .ErrorSet); if (error_set.eql(Type.initTag(.anyerror)) and payload.eql(Type.initTag(.void))) { return Type.initTag(.anyerror_void_error_union); diff --git a/src/RangeSet.zig b/src/RangeSet.zig index fd258d55b0..2a8a55a077 100644 --- a/src/RangeSet.zig +++ b/src/RangeSet.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Order = std.math.Order; +const Type = @import("type.zig").Type; const Value = @import("value.zig").Value; const RangeSet = @This(); const SwitchProngSrc = @import("Module.zig").SwitchProngSrc; @@ -22,9 +23,15 @@ pub fn deinit(self: *RangeSet) void { self.ranges.deinit(); } -pub fn add(self: *RangeSet, first: Value, last: Value, src: SwitchProngSrc) !?SwitchProngSrc { +pub fn add( + self: *RangeSet, + first: Value, + last: Value, + ty: Type, + src: SwitchProngSrc, +) !?SwitchProngSrc { for (self.ranges.items) |range| { - if (last.compare(.gte, range.first) and first.compare(.lte, range.last)) { + if (last.compare(.gte, range.first, ty) and first.compare(.lte, range.last, ty)) { return range.src; // They overlap. } } @@ -37,18 +44,18 @@ pub fn add(self: *RangeSet, first: Value, last: Value, src: SwitchProngSrc) !?Sw } /// Assumes a and b do not overlap -fn lessThan(_: void, a: Range, b: Range) bool { - return a.first.compare(.lt, b.first); +fn lessThan(ty: Type, a: Range, b: Range) bool { + return a.first.compare(.lt, b.first, ty); } -pub fn spans(self: *RangeSet, first: Value, last: Value) !bool { +pub fn spans(self: *RangeSet, first: Value, last: Value, ty: Type) !bool { if (self.ranges.items.len == 0) return false; - std.sort.sort(Range, self.ranges.items, {}, lessThan); + std.sort.sort(Range, self.ranges.items, ty, lessThan); - if (!self.ranges.items[0].first.eql(first) or - !self.ranges.items[self.ranges.items.len - 1].last.eql(last)) + if (!self.ranges.items[0].first.eql(first, ty) or + !self.ranges.items[self.ranges.items.len - 1].last.eql(last, ty)) { return false; } diff --git a/src/Sema.zig b/src/Sema.zig index 0da38d9a76..97a6f8323e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -634,7 +634,9 @@ fn analyzeAsType( const wanted_type = Type.initTag(.@"type"); const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src); const val = try sema.resolveConstValue(block, src, coerced_inst); - return val.toType(sema.arena); + var buffer: Value.ToTypeBuffer = undefined; + const ty = val.toType(&buffer); + return ty.copy(sema.arena); } /// May return Value Tags: `variable`, `undef`. @@ -1022,7 +1024,9 @@ fn zirEnumDecl( if (bag != 0) break true; } else false; if (any_values) { - try enum_obj.values.ensureCapacity(&new_decl_arena.allocator, fields_len); + try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ + .ty = tag_ty, + }); } { @@ -1100,10 +1104,10 @@ fn zirEnumDecl( // that points to this default value expression rather than the struct. // But only resolve the source location if we need to emit a compile error. const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val; - enum_obj.values.putAssumeCapacityNoClobber(tag_val, {}); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); } else if (any_values) { const tag_val = try Value.Tag.int_u64.create(&new_decl_arena.allocator, field_i); - enum_obj.values.putAssumeCapacityNoClobber(tag_val, {}); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); } } @@ -2516,7 +2520,7 @@ fn zirOptionalType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const child_type = try sema.resolveType(block, src, inst_data.operand); - const opt_type = try sema.mod.optionalType(sema.arena, child_type); + const opt_type = try Module.optionalType(sema.arena, child_type); return sema.addType(opt_type); } @@ -2547,11 +2551,10 @@ fn zirArrayType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE const tracy = trace(@src()); defer tracy.end(); - // TODO these should be lazily evaluated const bin_inst = sema.code.instructions.items(.data)[inst].bin; const len = try sema.resolveInstConst(block, .unneeded, bin_inst.lhs); const elem_type = try sema.resolveType(block, .unneeded, bin_inst.rhs); - const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), null, elem_type); + const array_ty = try Module.arrayType(sema.arena, len.val.toUnsignedInt(), null, elem_type); return sema.addType(array_ty); } @@ -2560,13 +2563,12 @@ fn zirArrayTypeSentinel(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) const tracy = trace(@src()); defer tracy.end(); - // TODO these should be lazily evaluated const inst_data = sema.code.instructions.items(.data)[inst].array_type_sentinel; const len = try sema.resolveInstConst(block, .unneeded, inst_data.len); const extra = sema.code.extraData(Zir.Inst.ArrayTypeSentinel, inst_data.payload_index).data; const sentinel = try sema.resolveInstConst(block, .unneeded, extra.sentinel); const elem_type = try sema.resolveType(block, .unneeded, extra.elem_type); - const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), sentinel.val, elem_type); + const array_ty = try Module.arrayType(sema.arena, len.val.toUnsignedInt(), sentinel.val, elem_type); return sema.addType(array_ty); } @@ -2599,7 +2601,7 @@ fn zirErrorUnionType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Com error_union.elemType(), }); } - const err_union_ty = try sema.mod.errorUnionType(sema.arena, error_union, payload); + const err_union_ty = try Module.errorUnionType(sema.arena, error_union, payload); return sema.addType(err_union_ty); } @@ -3890,6 +3892,7 @@ fn analyzeSwitch( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, ); @@ -3912,6 +3915,7 @@ fn analyzeSwitch( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, ); @@ -3929,6 +3933,7 @@ fn analyzeSwitch( &range_set, item_first, item_last, + operand_ty, src_node_offset, .{ .range = .{ .prong = multi_i, .item = range_i } }, ); @@ -3945,7 +3950,7 @@ fn analyzeSwitch( const min_int = try operand_ty.minInt(&arena, mod.getTarget()); const max_int = try operand_ty.maxInt(&arena, mod.getTarget()); - if (try range_set.spans(min_int, max_int)) { + if (try range_set.spans(min_int, max_int, operand_ty)) { if (special_prong == .@"else") { return mod.fail( &block.base, @@ -4050,7 +4055,7 @@ fn analyzeSwitch( ); } - var seen_values = ValueSrcMap.init(gpa); + var seen_values = ValueSrcMap.initContext(gpa, .{ .ty = operand_ty }); defer seen_values.deinit(); var extra_index: usize = special.end; @@ -4161,7 +4166,7 @@ fn analyzeSwitch( const item = sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val)) { + if (operand_val.eql(item_val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } } @@ -4183,7 +4188,7 @@ fn analyzeSwitch( const item = sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val)) { + if (operand_val.eql(item_val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } } @@ -4198,8 +4203,8 @@ fn analyzeSwitch( // Validation above ensured these will succeed. const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first) catch unreachable; const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last) catch unreachable; - if (Value.compare(operand_val, .gte, first_tv.val) and - Value.compare(operand_val, .lte, last_tv.val)) + if (Value.compare(operand_val, .gte, first_tv.val, operand_ty) and + Value.compare(operand_val, .lte, last_tv.val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } @@ -4450,12 +4455,13 @@ fn validateSwitchRange( range_set: *RangeSet, first_ref: Zir.Inst.Ref, last_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, ) CompileError!void { const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val; const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val; - const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src); + const maybe_prev_src = try range_set.add(first_val, last_val, operand_ty, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -4464,11 +4470,12 @@ fn validateSwitchItem( block: *Scope.Block, range_set: *RangeSet, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, ) CompileError!void { const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val; - const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src); + const maybe_prev_src = try range_set.add(item_val, item_val, operand_ty, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -5137,20 +5144,26 @@ fn zirCmp( const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - if (lhs_val.compare(op, rhs_val)) { - return Air.Inst.Ref.bool_true; + const runtime_src: LazySrcLoc = src: { + if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(resolved_type); + } + if (lhs_val.compare(op, rhs_val, resolved_type)) { + return Air.Inst.Ref.bool_true; + } else { + return Air.Inst.Ref.bool_false; + } } else { - return Air.Inst.Ref.bool_false; + break :src rhs_src; } + } else { + break :src lhs_src; } - } + }; + try sema.requireRuntimeBlock(block, runtime_src); - try sema.requireRuntimeBlock(block, src); const tag: Air.Inst.Tag = switch (op) { .lt => .cmp_lt, .lte => .cmp_lte, @@ -5626,7 +5639,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple; const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type); - const ty = try sema.mod.ptrType( + const ty = try Module.ptrType( sema.arena, elem_type, null, @@ -5680,7 +5693,7 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type); - const ty = try sema.mod.ptrType( + const ty = try Module.ptrType( sema.arena, elem_type, sentinel, @@ -6569,7 +6582,7 @@ fn panicWithMsg( const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); const ptr_stack_trace_ty = try Module.simplePtrType(arena, stack_trace_ty, true, .One); const null_stack_trace = try sema.addConstant( - try mod.optionalType(arena, ptr_stack_trace_ty), + try Module.optionalType(arena, ptr_stack_trace_ty), Value.initTag(.null_value), ); const args = try arena.create([2]Air.Inst.Ref); @@ -6713,7 +6726,8 @@ fn fieldVal( }, .Type => { const val = (try sema.resolveDefinedValue(block, object_src, object)).?; - const child_type = try val.toType(arena); + var to_type_buffer: Value.ToTypeBuffer = undefined; + const child_type = val.toType(&to_type_buffer); switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -6733,7 +6747,7 @@ fn fieldVal( } else (try mod.getErrorValue(field_name)).key; return sema.addConstant( - child_type, + try child_type.copy(arena), try Value.Tag.@"error".create(arena, .{ .name = name }), ); }, @@ -6781,7 +6795,7 @@ fn fieldVal( }; const field_index_u32 = @intCast(u32, field_index); const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); - return sema.addConstant(child_type, enum_val); + return sema.addConstant(try child_type.copy(arena), enum_val); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } @@ -6805,7 +6819,6 @@ fn fieldPtr( // in `fieldVal`. This function takes a pointer and returns a pointer. const mod = sema.mod; - const arena = sema.arena; const object_ptr_src = src; // TODO better source location const object_ptr_ty = sema.typeOf(object_ptr); const object_ty = switch (object_ptr_ty.zigTypeTag()) { @@ -6887,7 +6900,8 @@ fn fieldPtr( _ = try sema.resolveConstValue(block, object_ptr_src, object_ptr); const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); const val = (sema.resolveDefinedValue(block, src, result) catch unreachable).?; - const child_type = try val.toType(arena); + var to_type_buffer: Value.ToTypeBuffer = undefined; + const child_type = val.toType(&to_type_buffer); switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -6902,15 +6916,14 @@ fn fieldPtr( } } return mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{ - field_name, - child_type, + field_name, child_type, }); } else (try mod.getErrorValue(field_name)).key; var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - child_type, + try child_type.copy(anon_decl.arena()), try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), )); }, @@ -6960,7 +6973,7 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - child_type, + try child_type.copy(anon_decl.arena()), try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), )); }, @@ -7352,7 +7365,7 @@ fn coerce( if (src_sentinel) |src_s| { if (dst_sentinel) |dst_s| { - if (src_s.eql(dst_s)) { + if (src_s.eql(dst_s, dst_elem_type)) { return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } } @@ -7474,7 +7487,7 @@ fn coerceNum( } } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(sema.arena, dest_type, target) catch |err| switch (err) { + const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { error.Overflow => return sema.mod.fail( &block.base, inst_src, @@ -7813,12 +7826,12 @@ fn analyzeSlice( array_type.sentinel() else slice_sentinel; - return_elem_type = try sema.mod.arrayType(sema.arena, len, array_sentinel, elem_type); + return_elem_type = try Module.arrayType(sema.arena, len, array_sentinel, elem_type); return_ptr_size = .One; } } } - const return_type = try sema.mod.ptrType( + const return_type = try Module.ptrType( sema.arena, return_elem_type, if (end_opt == .none) slice_sentinel else null, @@ -7858,39 +7871,42 @@ fn cmpNumeric( if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), - rhs_ty.arrayLen(), + lhs_ty.arrayLen(), rhs_ty.arrayLen(), }); } return sema.mod.fail(&block.base, src, "TODO implement support for vectors in cmpNumeric", .{}); } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) { return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ - lhs_ty, - rhs_ty, + lhs_ty, rhs_ty, }); } - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(Type.initTag(.bool)); - } - if (Value.compare(lhs_val, op, rhs_val)) { - return Air.Inst.Ref.bool_true; + const runtime_src: LazySrcLoc = src: { + if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(Type.initTag(.bool)); + } + if (Value.compareHetero(lhs_val, op, rhs_val)) { + return Air.Inst.Ref.bool_true; + } else { + return Air.Inst.Ref.bool_false; + } } else { - return Air.Inst.Ref.bool_false; + break :src rhs_src; } + } else { + break :src lhs_src; } - } + }; // TODO handle comparisons against lazy zero values // Some values can be compared against zero without being runtime known or without forcing // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout // of this function if we don't need to. + try sema.requireRuntimeBlock(block, runtime_src); - // It must be a runtime comparison. - try sema.requireRuntimeBlock(block, src); // For floats, emit a float comparison instruction. const lhs_is_float = switch (lhs_ty_tag) { .Float, .ComptimeFloat => true, diff --git a/src/type.zig b/src/type.zig index d828df550b..82c28ef398 100644 --- a/src/type.zig +++ b/src/type.zig @@ -426,7 +426,7 @@ pub const Type = extern union { const sentinel_b = info_b.sentinel; if (sentinel_a) |sa| { if (sentinel_b) |sb| { - if (!sa.eql(sb)) + if (!sa.eql(sb, info_a.pointee_type)) return false; } else { return false; @@ -455,13 +455,14 @@ pub const Type = extern union { .Array, .Vector => { if (a.arrayLen() != b.arrayLen()) return false; - if (!a.elemType().eql(b.elemType())) + const elem_ty = a.elemType(); + if (!elem_ty.eql(b.elemType())) return false; const sentinel_a = a.sentinel(); const sentinel_b = b.sentinel(); if (sentinel_a) |sa| { if (sentinel_b) |sb| { - return sa.eql(sb); + return sa.eql(sb, elem_ty); } else { return false; } @@ -2744,29 +2745,37 @@ pub const Type = extern union { return @as(usize, payload.data); } const S = struct { - fn fieldWithRange(int_val: Value, end: usize) ?usize { + fn fieldWithRange(int_ty: Type, int_val: Value, end: usize) ?usize { if (int_val.compareWithZero(.lt)) return null; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (int_val.compare(.gte, end_val)) return null; + if (int_val.compare(.gte, end_val, int_ty)) return null; return @intCast(usize, int_val.toUnsignedInt()); } }; switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { const enum_full = ty.cast(Payload.EnumFull).?.data; + const tag_ty = enum_full.tag_ty; if (enum_full.values.count() == 0) { - return S.fieldWithRange(enum_tag, enum_full.fields.count()); + return S.fieldWithRange(tag_ty, enum_tag, enum_full.fields.count()); } else { - return enum_full.values.getIndex(enum_tag); + return enum_full.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); } }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - return S.fieldWithRange(enum_tag, enum_simple.fields.count()); + const fields_len = enum_simple.fields.count(); + const bits = std.math.log2_int_ceil(usize, fields_len); + var buffer: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + const tag_ty = Type.initPayload(&buffer.base); + return S.fieldWithRange(tag_ty, enum_tag, fields_len); }, .atomic_ordering, .atomic_rmw_op, @@ -2875,14 +2884,14 @@ pub const Type = extern union { /// Asserts the type is an enum. pub fn enumHasInt(ty: Type, int: Value, target: Target) bool { const S = struct { - fn intInRange(int_val: Value, end: usize) bool { + fn intInRange(tag_ty: Type, int_val: Value, end: usize) bool { if (int_val.compareWithZero(.lt)) return false; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (int_val.compare(.gte, end_val)) return false; + if (int_val.compare(.gte, end_val, tag_ty)) return false; return true; } }; @@ -2890,15 +2899,23 @@ pub const Type = extern union { .enum_nonexhaustive => return int.intFitsInType(ty, target), .enum_full => { const enum_full = ty.castTag(.enum_full).?.data; + const tag_ty = enum_full.tag_ty; if (enum_full.values.count() == 0) { - return S.intInRange(int, enum_full.fields.count()); + return S.intInRange(tag_ty, int, enum_full.fields.count()); } else { - return enum_full.values.contains(int); + return enum_full.values.containsContext(int, .{ .ty = tag_ty }); } }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - return S.intInRange(int, enum_simple.fields.count()); + const fields_len = enum_simple.fields.count(); + const bits = std.math.log2_int_ceil(usize, fields_len); + var buffer: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + const tag_ty = Type.initPayload(&buffer.base); + return S.intInRange(tag_ty, int, fields_len); }, .atomic_ordering, .atomic_rmw_op, diff --git a/src/value.zig b/src/value.zig index 32be34ee2c..93d6625e7c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -653,8 +653,10 @@ pub const Value = extern union { unreachable; } + pub const ToTypeBuffer = Type.Payload.Bits; + /// Asserts that the value is representable as a type. - pub fn toType(self: Value, allocator: *Allocator) !Type { + pub fn toType(self: Value, buffer: *ToTypeBuffer) Type { return switch (self.tag()) { .ty => self.castTag(.ty).?.data, .u1_type => Type.initTag(.u1), @@ -714,14 +716,13 @@ pub const Value = extern union { .int_type => { const payload = self.castTag(.int_type).?.data; - const new = try allocator.create(Type.Payload.Bits); - new.* = .{ + buffer.* = .{ .base = .{ .tag = if (payload.signed) .int_signed else .int_unsigned, }, .data = payload.bits, }; - return Type.initPayload(&new.base); + return Type.initPayload(&buffer.base); }, .undef, @@ -958,9 +959,8 @@ pub const Value = extern union { /// Converts an integer or a float to a float. /// Returns `error.Overflow` if the value does not fit in the new type. - pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value { - _ = target; - switch (ty.tag()) { + pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value { + switch (dest_ty.tag()) { .f16 => { @panic("TODO add __trunctfhf2 to compiler-rt"); //const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); @@ -970,13 +970,13 @@ pub const Value = extern union { }, .f32 => { const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32)); - if (!self.eql(res)) + if (!self.eql(res, dest_ty)) return error.Overflow; return res; }, .f64 => { const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64)); - if (!self.eql(res)) + if (!self.eql(res, dest_ty)) return error.Overflow; return res; }, @@ -1083,12 +1083,18 @@ pub const Value = extern union { return lhs_bigint.order(rhs_bigint); } - /// Asserts the value is comparable. - pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool { + /// Asserts the value is comparable. Does not take a type parameter because it supports + /// comparisons between heterogeneous types. + pub fn compareHetero(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool { + return order(lhs, rhs).compare(op); + } + + /// Asserts the value is comparable. Both operands have type `ty`. + pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, ty: Type) bool { return switch (op) { - .eq => lhs.eql(rhs), - .neq => !lhs.eql(rhs), - else => order(lhs, rhs).compare(op), + .eq => lhs.eql(rhs, ty), + .neq => !lhs.eql(rhs, ty), + else => compareHetero(lhs, op, rhs), }; } @@ -1097,11 +1103,11 @@ pub const Value = extern union { return orderAgainstZero(lhs).compare(op); } - /// TODO we can't compare value equality without also knowing the type to treat - /// the values as - pub fn eql(a: Value, b: Value) bool { + pub fn eql(a: Value, b: Value, ty: Type) bool { const a_tag = a.tag(); const b_tag = b.tag(); + assert(a_tag != .undef); + assert(b_tag != .undef); if (a_tag == b_tag) { switch (a_tag) { .void_value, .null_value => return true, @@ -1118,230 +1124,106 @@ pub const Value = extern union { else => {}, } } - if (a.isType() and b.isType()) { - // 128 bytes should be enough to hold both types - var buf: [128]u8 = undefined; - var fib = std.heap.FixedBufferAllocator.init(&buf); - const a_type = a.toType(&fib.allocator) catch unreachable; - const b_type = b.toType(&fib.allocator) catch unreachable; + if (ty.zigTypeTag() == .Type) { + var buf_a: ToTypeBuffer = undefined; + var buf_b: ToTypeBuffer = undefined; + const a_type = a.toType(&buf_a); + const b_type = b.toType(&buf_b); return a_type.eql(b_type); } return order(a, b).compare(.eq); } - pub fn hash_u32(self: Value) u32 { - return @truncate(u32, self.hash()); - } + pub const ArrayHashContext = struct { + ty: Type, - /// TODO we can't hash without also knowing the type of the value. - /// we have to hash as if there were a canonical value memory layout. - pub fn hash(self: Value) u64 { - var hasher = std.hash.Wyhash.init(0); + pub fn hash(self: @This(), v: Value) u32 { + const other_context: HashContext = .{ .ty = self.ty }; + return @truncate(u32, other_context.hash(v)); + } + pub fn eql(self: @This(), a: Value, b: Value) bool { + return a.eql(b, self.ty); + } + }; - switch (self.tag()) { - .u1_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .anyframe_type, - .const_slice_u8_type, - .enum_literal_type, - .ty, - .abi_align_default, - => { - // Directly return Type.hash, toType can only fail for .int_type. - var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{}); - return (self.toType(&allocator.allocator) catch unreachable).hash(); - }, - .int_type => { - const payload = self.castTag(.int_type).?.data; - var int_payload = Type.Payload.Bits{ - .base = .{ - .tag = if (payload.signed) .int_signed else .int_unsigned, - }, - .data = payload.bits, - }; - return Type.initPayload(&int_payload.base).hash(); - }, + pub const HashContext = struct { + ty: Type, - .empty_struct_value, - .empty_array, - => {}, + pub fn hash(self: @This(), v: Value) u64 { + var hasher = std.hash.Wyhash.init(0); - .undef, - .null_value, - .void_value, - .unreachable_value, - => std.hash.autoHash(&hasher, self.tag()), + switch (self.ty.zigTypeTag()) { + .BoundFn => unreachable, // TODO remove this from the language - .zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)), - .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)), + .Void, + .NoReturn, + .Undefined, + .Null, + => {}, - .float_16, .float_32, .float_64, .float_128 => { - @panic("TODO implement Value.hash for floats"); - }, - - .enum_literal => { - const payload = self.castTag(.enum_literal).?; - hasher.update(payload.data); - }, - .enum_field_index => { - const payload = self.castTag(.enum_field_index).?; - std.hash.autoHash(&hasher, payload.data); - }, - .bytes => { - const payload = self.castTag(.bytes).?; - hasher.update(payload.data); - }, - .repeated => { - @panic("TODO Value.hash for repeated"); - }, - .array => { - @panic("TODO Value.hash for array"); - }, - .slice => { - @panic("TODO Value.hash for slice"); - }, - .eu_payload_ptr => { - @panic("TODO Value.hash for eu_payload_ptr"); - }, - .int_u64 => { - const payload = self.castTag(.int_u64).?; - std.hash.autoHash(&hasher, payload.data); - }, - .int_i64 => { - const payload = self.castTag(.int_i64).?; - std.hash.autoHash(&hasher, payload.data); - }, - .comptime_alloc => { - const payload = self.castTag(.comptime_alloc).?; - std.hash.autoHash(&hasher, payload.data.val.hash()); - }, - .int_big_positive, .int_big_negative => { - var space: BigIntSpace = undefined; - const big = self.toBigInt(&space); - if (big.limbs.len == 1) { - // handle like {u,i}64 to ensure same hash as with Int{i,u}64 - if (big.positive) { - std.hash.autoHash(&hasher, @as(u64, big.limbs[0])); - } else { - std.hash.autoHash(&hasher, @as(u64, @bitCast(usize, -@bitCast(isize, big.limbs[0])))); - } - } else { + .Type => { + var buf: ToTypeBuffer = undefined; + return v.toType(&buf).hash(); + }, + .Bool => { + std.hash.autoHash(&hasher, v.toBool()); + }, + .Int, .ComptimeInt => { + var space: BigIntSpace = undefined; + const big = v.toBigInt(&space); std.hash.autoHash(&hasher, big.positive); for (big.limbs) |limb| { std.hash.autoHash(&hasher, limb); } - } - }, - .elem_ptr => { - const payload = self.castTag(.elem_ptr).?.data; - std.hash.autoHash(&hasher, payload.array_ptr.hash()); - std.hash.autoHash(&hasher, payload.index); - }, - .field_ptr => { - const payload = self.castTag(.field_ptr).?.data; - std.hash.autoHash(&hasher, payload.container_ptr.hash()); - std.hash.autoHash(&hasher, payload.field_index); - }, - .decl_ref => { - const decl = self.castTag(.decl_ref).?.data; - std.hash.autoHash(&hasher, decl); - }, - .function => { - const func = self.castTag(.function).?.data; - std.hash.autoHash(&hasher, func); - }, - .extern_fn => { - const decl = self.castTag(.extern_fn).?.data; - std.hash.autoHash(&hasher, decl); - }, - .variable => { - const variable = self.castTag(.variable).?.data; - std.hash.autoHash(&hasher, variable); - }, - .@"error" => { - const payload = self.castTag(.@"error").?.data; - hasher.update(payload.name); - }, - .error_union => { - const payload = self.castTag(.error_union).?.data; - std.hash.autoHash(&hasher, payload.hash()); - }, - .inferred_alloc => unreachable, + }, + .Float, .ComptimeFloat => { + @panic("TODO implement hashing float values"); + }, + .Pointer => { + @panic("TODO implement hashing pointer values"); + }, + .Array, .Vector => { + @panic("TODO implement hashing array/vector values"); + }, + .Struct => { + @panic("TODO implement hashing struct values"); + }, + .Optional => { + @panic("TODO implement hashing optional values"); + }, + .ErrorUnion => { + @panic("TODO implement hashing error union values"); + }, + .ErrorSet => { + @panic("TODO implement hashing error set values"); + }, + .Enum => { + @panic("TODO implement hashing enum values"); + }, + .Union => { + @panic("TODO implement hashing union values"); + }, + .Fn => { + @panic("TODO implement hashing function values"); + }, + .Opaque => { + @panic("TODO implement hashing opaque values"); + }, + .Frame => { + @panic("TODO implement hashing frame values"); + }, + .AnyFrame => { + @panic("TODO implement hashing anyframe values"); + }, + .EnumLiteral => { + @panic("TODO implement hashing enum literal values"); + }, + } + return hasher.final(); + } - .manyptr_u8_type, - .manyptr_const_u8_type, - .atomic_ordering_type, - .atomic_rmw_op_type, - .calling_convention_type, - .float_mode_type, - .reduce_op_type, - .call_options_type, - .export_options_type, - .extern_options_type, - .@"struct", - .@"union", - => @panic("TODO this hash function looks pretty broken. audit it"), - } - return hasher.final(); - } - - pub const ArrayHashContext = struct { - pub fn hash(self: @This(), v: Value) u32 { - _ = self; - return v.hash_u32(); - } pub fn eql(self: @This(), a: Value, b: Value) bool { - _ = self; - return a.eql(b); - } - }; - pub const HashContext = struct { - pub fn hash(self: @This(), v: Value) u64 { - _ = self; - return v.hash(); - } - pub fn eql(self: @This(), a: Value, b: Value) bool { - _ = self; - return a.eql(b); + return a.eql(b, self.ty); } }; @@ -1508,111 +1390,6 @@ pub const Value = extern union { }; } - /// Valid for all types. Asserts the value is not undefined. - /// TODO this function is a code smell and should be deleted - fn isType(self: Value) bool { - return switch (self.tag()) { - .ty, - .int_type, - .u1_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .anyframe_type, - .const_slice_u8_type, - .enum_literal_type, - .manyptr_u8_type, - .manyptr_const_u8_type, - .atomic_ordering_type, - .atomic_rmw_op_type, - .calling_convention_type, - .float_mode_type, - .reduce_op_type, - .call_options_type, - .export_options_type, - .extern_options_type, - => true, - - .zero, - .one, - .empty_array, - .bool_true, - .bool_false, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .comptime_alloc, - .decl_ref, - .elem_ptr, - .field_ptr, - .bytes, - .repeated, - .array, - .slice, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .enum_literal, - .enum_field_index, - .@"error", - .error_union, - .empty_struct_value, - .@"struct", - .@"union", - .null_value, - .abi_align_default, - .eu_payload_ptr, - => false, - - .undef => unreachable, - .unreachable_value => unreachable, - .inferred_alloc => unreachable, - }; - } - /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, @@ -1806,27 +1583,3 @@ pub const Value = extern union { limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, }; }; - -test "hash same value different representation" { - const zero_1 = Value.initTag(.zero); - var payload_1 = Value.Payload.U64{ - .base = .{ .tag = .int_u64 }, - .data = 0, - }; - const zero_2 = Value.initPayload(&payload_1.base); - try std.testing.expectEqual(zero_1.hash(), zero_2.hash()); - - var payload_2 = Value.Payload.I64{ - .base = .{ .tag = .int_i64 }, - .data = 0, - }; - const zero_3 = Value.initPayload(&payload_2.base); - try std.testing.expectEqual(zero_2.hash(), zero_3.hash()); - - var payload_3 = Value.Payload.BigInt{ - .base = .{ .tag = .int_big_negative }, - .data = &[_]std.math.big.Limb{0}, - }; - const zero_4 = Value.initPayload(&payload_3.base); - try std.testing.expectEqual(zero_3.hash(), zero_4.hash()); -}