diff --git a/src/Module.zig b/src/Module.zig index 4164c2659c..7b27546f52 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -177,6 +177,12 @@ const MonomorphedFuncsContext = struct { } }; +pub const WipAnalysis = struct { + sema: *Sema, + block: *Sema.Block, + src: Module.LazySrcLoc, +}; + pub const MemoizedCallSet = std.HashMapUnmanaged( MemoizedCall.Key, MemoizedCall.Result, diff --git a/src/Sema.zig b/src/Sema.zig index 5e344bb7d0..2428db5b0f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1591,7 +1591,7 @@ fn resolveInt( const coerced = try sema.coerce(block, dest_ty, air_inst, src); const val = try sema.resolveConstValue(block, src, coerced); const target = sema.mod.getTarget(); - return val.toUnsignedInt(target); + return (try val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?; } // Returns a compile error if the value has tag `variable`. See `resolveInstValue` for @@ -9926,7 +9926,7 @@ fn analyzePtrArithmetic( const offset_int = try sema.usizeCast(block, offset_src, offset_val.toUnsignedInt(target)); // TODO I tried to put this check earlier but it the LLVM backend generate invalid instructinons if (offset_int == 0) return ptr; - if (ptr_val.getUnsignedInt(target)) |addr| { + if (try ptr_val.getUnsignedIntAdvanced(target, sema.kit(block, ptr_src))) |addr| { const ptr_child_ty = ptr_ty.childType(); const elem_ty = if (ptr_ty.isSinglePointer() and ptr_child_ty.zigTypeTag() == .Array) ptr_child_ty.childType() @@ -11863,6 +11863,8 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const elem_ty_src: LazySrcLoc = .unneeded; const inst_data = sema.code.instructions.items(.data)[inst].ptr_type; const extra = sema.code.extraData(Zir.Inst.PtrType, inst_data.payload_index); + const unresolved_elem_ty = try sema.resolveType(block, elem_ty_src, extra.data.elem_type); + const target = sema.mod.getTarget(); var extra_i = extra.end; @@ -11872,10 +11874,19 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air break :blk (try sema.resolveInstConst(block, .unneeded, ref)).val; } else null; - const abi_align = if (inst_data.flags.has_align) blk: { + const abi_align: u32 = if (inst_data.flags.has_align) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; - const abi_align = try sema.resolveInt(block, .unneeded, ref, Type.u32); + const coerced = try sema.coerce(block, Type.u32, sema.resolveInst(ref), src); + const val = try sema.resolveConstValue(block, src, coerced); + // Check if this happens to be the lazy alignment of our element type, in + // which case we can make this 0 without resolving it. + if (val.castTag(.lazy_align)) |payload| { + if (payload.data.eql(unresolved_elem_ty, target)) { + break :blk 0; + } + } + const abi_align = (try val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?; break :blk @intCast(u32, abi_align); } else 0; @@ -11903,7 +11914,6 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air return sema.fail(block, src, "bit offset starts after end of host integer", .{}); } - const unresolved_elem_ty = try sema.resolveType(block, elem_ty_src, extra.data.elem_type); const elem_ty = if (abi_align == 0) unresolved_elem_ty else t: { @@ -11911,7 +11921,6 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air try sema.resolveTypeLayout(block, elem_ty_src, elem_ty); break :t elem_ty; }; - const target = sema.mod.getTarget(); const ty = try Type.ptr(sema.arena, target, .{ .pointee_type = elem_ty, .sentinel = sentinel, @@ -18390,15 +18399,15 @@ fn storePtrVal( operand_val: Value, operand_ty: Type, ) !void { - var kit = try beginComptimePtrMutation(sema, block, src, ptr_val); - try sema.checkComptimeVarStore(block, src, kit.decl_ref_mut); + var mut_kit = try beginComptimePtrMutation(sema, block, src, ptr_val); + try sema.checkComptimeVarStore(block, src, mut_kit.decl_ref_mut); - const bitcasted_val = try sema.bitCastVal(block, src, operand_val, operand_ty, kit.ty, 0); + const bitcasted_val = try sema.bitCastVal(block, src, operand_val, operand_ty, mut_kit.ty, 0); - const arena = kit.beginArena(sema.gpa); - defer kit.finishArena(); + const arena = mut_kit.beginArena(sema.gpa); + defer mut_kit.finishArena(); - kit.val.* = try bitcasted_val.copy(arena); + mut_kit.val.* = try bitcasted_val.copy(arena); } const ComptimePtrMutationKit = struct { @@ -19891,7 +19900,7 @@ fn cmpNumeric( return Air.Inst.Ref.bool_false; } } - if (Value.compareHetero(lhs_val, op, rhs_val, target)) { + if (try Value.compareHeteroAdvanced(lhs_val, op, rhs_val, target, sema.kit(block, src))) { return Air.Inst.Ref.bool_true; } else { return Air.Inst.Ref.bool_false; @@ -20758,7 +20767,7 @@ pub fn resolveFnTypes( } } -fn resolveTypeLayout( +pub fn resolveTypeLayout( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -20929,7 +20938,7 @@ fn resolveUnionFully( union_obj.status = .fully_resolved; } -fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!Type { +pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!Type { switch (ty.tag()) { .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; @@ -22209,7 +22218,9 @@ fn typePtrOrOptionalPtrTy( /// This function returns false negatives when structs and unions are having their /// field types resolved. /// TODO assert the return value matches `ty.comptimeOnly` -fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { +/// TODO merge these implementations together with the "advanced"/sema_kit pattern seen +/// elsewhere in value.zig +pub fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { return switch (ty.tag()) { .u1, .u8, @@ -22415,6 +22426,7 @@ fn typeAbiSize(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u64 { return ty.abiSize(target); } +/// TODO merge with Type.abiAlignmentAdvanced fn typeAbiAlignment(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u32 { try sema.resolveTypeLayout(block, src, ty); const target = sema.mod.getTarget(); @@ -22498,3 +22510,7 @@ fn anonStructFieldIndex( struct_ty.fmt(target), field_name, }); } + +fn kit(sema: *Sema, block: *Block, src: LazySrcLoc) Module.WipAnalysis { + return .{ .sema = sema, .block = block, .src = src }; +} diff --git a/src/type.zig b/src/type.zig index 0b49eac0a8..8668b36dca 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1483,20 +1483,29 @@ pub const Type = extern union { @compileError("do not format types directly; use either ty.fmtDebug() or ty.fmt()"); } - pub fn fmt(ty: Type, target: Target) std.fmt.Formatter(TypedValue.format) { - var ty_payload: Value.Payload.Ty = .{ - .base = .{ .tag = .ty }, - .data = ty, - }; + pub fn fmt(ty: Type, target: Target) std.fmt.Formatter(format2) { return .{ .data = .{ - .tv = .{ - .ty = Type.type, - .val = Value.initPayload(&ty_payload.base), - }, + .ty = ty, .target = target, } }; } + const FormatContext = struct { + ty: Type, + target: Target, + }; + + fn format2( + ctx: FormatContext, + comptime unused_format_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + comptime assert(unused_format_string.len == 0); + _ = options; + return print(ctx.ty, writer, ctx.target); + } + pub fn fmtDebug(ty: Type) std.fmt.Formatter(dump) { return .{ .data = ty }; } @@ -2241,8 +2250,12 @@ pub const Type = extern union { /// * the type has only one possible value, making its ABI size 0. /// When `ignore_comptime_only` is true, then types that are comptime only /// may return false positives. - pub fn hasRuntimeBitsAdvanced(ty: Type, ignore_comptime_only: bool) bool { - return switch (ty.tag()) { + pub fn hasRuntimeBitsAdvanced( + ty: Type, + ignore_comptime_only: bool, + sema_kit: ?Module.WipAnalysis, + ) Module.CompileError!bool { + switch (ty.tag()) { .u1, .u8, .i8, @@ -2296,7 +2309,7 @@ pub const Type = extern union { .@"anyframe", .anyopaque, .@"opaque", - => true, + => return true, // These are false because they are comptime-only types. .single_const_pointer_to_comptime_int, @@ -2320,7 +2333,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, - => false, + => return false, // These types have more than one possible value, so the result is the same as // asking whether they are comptime-only types. @@ -2337,20 +2350,34 @@ pub const Type = extern union { .const_slice, .mut_slice, .pointer, - => if (ignore_comptime_only) true else !comptimeOnly(ty), + => { + if (ignore_comptime_only) { + return true; + } else if (sema_kit) |sk| { + return !(try sk.sema.typeRequiresComptime(sk.block, sk.src, ty)); + } else { + return !comptimeOnly(ty); + } + }, .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; + if (sema_kit) |sk| { + _ = try sk.sema.typeRequiresComptime(sk.block, sk.src, ty); + } switch (struct_obj.requires_comptime) { .wip => unreachable, .yes => return false, .no => if (struct_obj.known_non_opv) return true, .unknown => {}, } + if (sema_kit) |sk| { + _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); + } assert(struct_obj.haveFieldTypes()); for (struct_obj.fields.values()) |value| { if (value.is_comptime) continue; - if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) + if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) return true; } else { return false; @@ -2368,14 +2395,17 @@ pub const Type = extern union { .enum_numbered, .enum_nonexhaustive => { var buffer: Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&buffer); - return int_tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only); + return int_tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit); }, .@"union" => { const union_obj = ty.castTag(.@"union").?.data; + if (sema_kit) |sk| { + _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); + } assert(union_obj.haveFieldTypes()); for (union_obj.fields.values()) |value| { - if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) + if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) return true; } else { return false; @@ -2383,29 +2413,32 @@ pub const Type = extern union { }, .union_tagged => { const union_obj = ty.castTag(.union_tagged).?.data; - if (union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) { + if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { return true; } + if (sema_kit) |sk| { + _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); + } assert(union_obj.haveFieldTypes()); for (union_obj.fields.values()) |value| { - if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) + if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) return true; } else { return false; } }, - .array, .vector => ty.arrayLen() != 0 and - ty.elemType().hasRuntimeBitsAdvanced(ignore_comptime_only), - .array_u8 => ty.arrayLen() != 0, - .array_sentinel => ty.childType().hasRuntimeBitsAdvanced(ignore_comptime_only), + .array, .vector => return ty.arrayLen() != 0 and + try ty.elemType().hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit), + .array_u8 => return ty.arrayLen() != 0, + .array_sentinel => return ty.childType().hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit), - .int_signed, .int_unsigned => ty.cast(Payload.Bits).?.data != 0, + .int_signed, .int_unsigned => return ty.cast(Payload.Bits).?.data != 0, .error_union => { const payload = ty.castTag(.error_union).?.data; - return payload.error_set.hasRuntimeBitsAdvanced(ignore_comptime_only) or - payload.payload.hasRuntimeBitsAdvanced(ignore_comptime_only); + return (try payload.error_set.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) or + (try payload.payload.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)); }, .tuple, .anon_struct => { @@ -2413,7 +2446,7 @@ pub const Type = extern union { for (tuple.types) |field_ty, i| { const val = tuple.values[i]; if (val.tag() != .unreachable_value) continue; // comptime field - if (field_ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) return true; + if (try field_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) return true; } return false; }, @@ -2422,7 +2455,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .var_args_param => unreachable, .generic_poison => unreachable, - }; + } } /// true if and only if the type has a well-defined memory layout @@ -2548,11 +2581,11 @@ pub const Type = extern union { } pub fn hasRuntimeBits(ty: Type) bool { - return hasRuntimeBitsAdvanced(ty, false); + return hasRuntimeBitsAdvanced(ty, false, null) catch unreachable; } pub fn hasRuntimeBitsIgnoreComptime(ty: Type) bool { - return hasRuntimeBitsAdvanced(ty, true); + return hasRuntimeBitsAdvanced(ty, true, null) catch unreachable; } pub fn isFnOrHasRuntimeBits(ty: Type) bool { @@ -4538,6 +4571,8 @@ pub const Type = extern union { /// During semantic analysis, instead call `Sema.typeRequiresComptime` which /// resolves field types rather than asserting they are already resolved. + /// TODO merge these implementations together with the "advanced" pattern seen + /// elsewhere in this file. pub fn comptimeOnly(ty: Type) bool { return switch (ty.tag()) { .u1, diff --git a/src/value.zig b/src/value.zig index b54f296a24..fb9a2f826a 100644 --- a/src/value.zig +++ b/src/value.zig @@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator; const Module = @import("Module.zig"); const Air = @import("Air.zig"); const TypedValue = @import("TypedValue.zig"); +const Sema = @import("Sema.zig"); /// This is the raw data, with no bookkeeping, no memory awareness, /// no de-duplication, and no type system awareness. @@ -990,6 +991,16 @@ pub const Value = extern union { /// Asserts the value is an integer. pub fn toBigInt(val: Value, space: *BigIntSpace, target: Target) BigIntConst { + return val.toBigIntAdvanced(space, target, null) catch unreachable; + } + + /// Asserts the value is an integer. + pub fn toBigIntAdvanced( + val: Value, + space: *BigIntSpace, + target: Target, + sema_kit: ?Module.WipAnalysis, + ) !BigIntConst { switch (val.tag()) { .zero, .bool_false, @@ -1008,7 +1019,11 @@ pub const Value = extern union { .undef => unreachable, .lazy_align => { - const x = val.castTag(.lazy_align).?.data.abiAlignment(target); + const ty = val.castTag(.lazy_align).?.data; + if (sema_kit) |sk| { + try sk.sema.resolveTypeLayout(sk.block, sk.src, ty); + } + const x = ty.abiAlignment(target); return BigIntMutable.init(&space.limbs, x).toConst(); }, @@ -1019,6 +1034,12 @@ pub const Value = extern union { /// If the value fits in a u64, return it, otherwise null. /// Asserts not undefined. pub fn getUnsignedInt(val: Value, target: Target) ?u64 { + return getUnsignedIntAdvanced(val, target, null) catch unreachable; + } + + /// If the value fits in a u64, return it, otherwise null. + /// Asserts not undefined. + pub fn getUnsignedIntAdvanced(val: Value, target: Target, sema_kit: ?Module.WipAnalysis) !?u64 { switch (val.tag()) { .zero, .bool_false, @@ -1036,7 +1057,13 @@ pub const Value = extern union { .undef => unreachable, - .lazy_align => return val.castTag(.lazy_align).?.data.abiAlignment(target), + .lazy_align => { + const ty = val.castTag(.lazy_align).?.data; + if (sema_kit) |sk| { + try sk.sema.resolveTypeLayout(sk.block, sk.src, ty); + } + return ty.abiAlignment(target); + }, else => return null, } @@ -1777,6 +1804,10 @@ pub const Value = extern union { } pub fn orderAgainstZero(lhs: Value) std.math.Order { + return orderAgainstZeroAdvanced(lhs, null) catch unreachable; + } + + pub fn orderAgainstZeroAdvanced(lhs: Value, sema_kit: ?Module.WipAnalysis) !std.math.Order { return switch (lhs.tag()) { .zero, .bool_false, @@ -1799,7 +1830,7 @@ pub const Value = extern union { .lazy_align => { const ty = lhs.castTag(.lazy_align).?.data; - if (ty.hasRuntimeBitsIgnoreComptime()) { + if (try ty.hasRuntimeBitsAdvanced(false, sema_kit)) { return .gt; } else { return .eq; @@ -1818,10 +1849,16 @@ pub const Value = extern union { /// Asserts the value is comparable. pub fn order(lhs: Value, rhs: Value, target: Target) std.math.Order { + return orderAdvanced(lhs, rhs, target, null) catch unreachable; + } + + /// Asserts the value is comparable. + /// If sema_kit is null then this function asserts things are resolved and cannot fail. + pub fn orderAdvanced(lhs: Value, rhs: Value, target: Target, sema_kit: ?Module.WipAnalysis) !std.math.Order { const lhs_tag = lhs.tag(); const rhs_tag = rhs.tag(); - const lhs_against_zero = lhs.orderAgainstZero(); - const rhs_against_zero = rhs.orderAgainstZero(); + const lhs_against_zero = try lhs.orderAgainstZeroAdvanced(sema_kit); + const rhs_against_zero = try rhs.orderAgainstZeroAdvanced(sema_kit); switch (lhs_against_zero) { .lt => if (rhs_against_zero != .lt) return .lt, .eq => return rhs_against_zero.invert(), @@ -1855,14 +1892,24 @@ pub const Value = extern union { var lhs_bigint_space: BigIntSpace = undefined; var rhs_bigint_space: BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_bigint_space, target); - const rhs_bigint = rhs.toBigInt(&rhs_bigint_space, target); + const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_bigint_space, target, sema_kit); + const rhs_bigint = try rhs.toBigIntAdvanced(&rhs_bigint_space, target, sema_kit); return lhs_bigint.order(rhs_bigint); } /// 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, target: Target) bool { + return compareHeteroAdvanced(lhs, op, rhs, target, null) catch unreachable; + } + + pub fn compareHeteroAdvanced( + lhs: Value, + op: std.math.CompareOperator, + rhs: Value, + target: Target, + sema_kit: ?Module.WipAnalysis, + ) !bool { if (lhs.pointerDecl()) |lhs_decl| { if (rhs.pointerDecl()) |rhs_decl| { switch (op) { @@ -1884,7 +1931,7 @@ pub const Value = extern union { else => {}, } } - return order(lhs, rhs, target).compare(op); + return (try orderAdvanced(lhs, rhs, target, sema_kit)).compare(op); } /// Asserts the values are comparable. Both operands have type `ty`. diff --git a/test/behavior/struct_contains_slice_of_itself.zig b/test/behavior/struct_contains_slice_of_itself.zig index 11b838a758..cbff2514ea 100644 --- a/test/behavior/struct_contains_slice_of_itself.zig +++ b/test/behavior/struct_contains_slice_of_itself.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const expect = @import("std").testing.expect; const Node = struct { @@ -11,6 +12,10 @@ const NodeAligned = struct { }; test "struct contains slice of itself" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + var other_nodes = [_]Node{ Node{ .payload = 31, @@ -48,6 +53,10 @@ test "struct contains slice of itself" { } test "struct contains aligned slice of itself" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + var other_nodes = [_]NodeAligned{ NodeAligned{ .payload = 31,