Commit c21f046a8b

Andrew Kelley <andrew@ziglang.org>
2022-03-30 09:47:55
Sema: enhance is_non_err to be comptime more often
* Sema: store the precomputed monomorphed_funcs hash inside Module.Fn. This is important because it may be accessed when resizing monomorphed_funcs while this Fn has already been added to the set, but does not have the owner_decl, comptime_args, or other fields populated yet. * Sema: in `analyzeIsNonErr`, take advantage of the AIR tag being `wrap_errunion_payload` to infer that `is_non_err` is comptime true without performing any error set resolution. - Also add some code to check for empty inferred error sets in this function. If necessary we do resolve the inferred error set. * Sema: queue full type resolution of payload type when `wrap_errunion_payload` AIR instruction is emitted. This ensures the backend may check the alignment of it. * Sema: resolveTypeFully now additionally resolves comptime-only status. closes #11306
1 parent 05947ea
Changed files (4)
src
test
behavior
src/arch/wasm/CodeGen.zig
@@ -2627,12 +2627,12 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     const op_ty = self.air.typeOf(ty_op.operand);
     if (!op_ty.hasRuntimeBitsIgnoreComptime()) return operand;
-    const err_ty = self.air.getRefType(ty_op.ty);
-    const err_align = err_ty.abiAlignment(self.target);
-    const set_size = err_ty.errorUnionSet().abiSize(self.target);
+    const err_union_ty = self.air.getRefType(ty_op.ty);
+    const err_align = err_union_ty.abiAlignment(self.target);
+    const set_size = err_union_ty.errorUnionSet().abiSize(self.target);
     const offset = mem.alignForwardGeneric(u64, set_size, err_align);
 
-    const err_union = try self.allocStack(err_ty);
+    const err_union = try self.allocStack(err_union_ty);
     const payload_ptr = try self.buildPointerOffset(err_union, offset, .new);
     try self.store(payload_ptr, operand, op_ty, 0);
 
src/Module.zig
@@ -146,8 +146,6 @@ const MonomorphedFuncsSet = std.HashMapUnmanaged(
 );
 
 const MonomorphedFuncsContext = struct {
-    target: Target,
-
     pub fn eql(ctx: @This(), a: *Fn, b: *Fn) bool {
         _ = ctx;
         return a == b;
@@ -155,25 +153,8 @@ const MonomorphedFuncsContext = struct {
 
     /// Must match `Sema.GenericCallAdapter.hash`.
     pub fn hash(ctx: @This(), key: *Fn) u64 {
-        var hasher = std.hash.Wyhash.init(0);
-
-        // The generic function Decl is guaranteed to be the first dependency
-        // of each of its instantiations.
-        const generic_owner_decl = key.owner_decl.dependencies.keys()[0];
-        const generic_func: *const Fn = generic_owner_decl.val.castTag(.function).?.data;
-        std.hash.autoHash(&hasher, generic_func);
-
-        // This logic must be kept in sync with the logic in `analyzeCall` that
-        // computes the hash.
-        const comptime_args = key.comptime_args.?;
-        const generic_ty_info = generic_owner_decl.ty.fnInfo();
-        for (generic_ty_info.param_types) |param_ty, i| {
-            if (generic_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) {
-                comptime_args[i].val.hash(param_ty, &hasher, ctx.target);
-            }
-        }
-
-        return hasher.final();
+        _ = ctx;
+        return key.hash;
     }
 };
 
@@ -1427,6 +1408,12 @@ pub const Fn = struct {
     /// determine param names rather than redundantly storing them here.
     param_names: []const [:0]const u8,
 
+    /// Precomputed hash for monomorphed_funcs.
+    /// This is important because it may be accessed when resizing monomorphed_funcs
+    /// while this Fn has already been added to the set, but does not have the
+    /// owner_decl, comptime_args, or other fields populated yet.
+    hash: u64,
+
     /// Relative to owner Decl.
     lbrace_line: u32,
     /// Relative to owner Decl.
src/Sema.zig
@@ -4671,22 +4671,6 @@ const GenericCallAdapter = struct {
     }
 };
 
-const GenericRemoveAdapter = struct {
-    precomputed_hash: u64,
-
-    pub fn eql(ctx: @This(), adapted_key: *Module.Fn, other_key: *Module.Fn) bool {
-        _ = ctx;
-        return adapted_key == other_key;
-    }
-
-    /// The implementation of the hash is in semantic analysis of function calls, so
-    /// that any errors when computing the hash can be properly reported.
-    pub fn hash(ctx: @This(), adapted_key: *Module.Fn) u64 {
-        _ = adapted_key;
-        return ctx.precomputed_hash;
-    }
-};
-
 fn analyzeCall(
     sema: *Sema,
     block: *Block,
@@ -5200,15 +5184,15 @@ fn instantiateGenericCall(
         .comptime_tvs = comptime_tvs,
         .target = target,
     };
-    const gop = try mod.monomorphed_funcs.getOrPutContextAdapted(gpa, {}, adapter, .{ .target = target });
+    const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
     const callee = if (!gop.found_existing) callee: {
         const new_module_func = try gpa.create(Module.Fn);
+        // This ensures that we can operate on the hash map before the Module.Fn
+        // struct is fully initialized.
+        new_module_func.hash = precomputed_hash;
         gop.key_ptr.* = new_module_func;
         errdefer gpa.destroy(new_module_func);
-        const remove_adapter: GenericRemoveAdapter = .{
-            .precomputed_hash = precomputed_hash,
-        };
-        errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter));
+        errdefer assert(mod.monomorphed_funcs.remove(new_module_func));
 
         try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);
 
@@ -6494,12 +6478,14 @@ fn funcCommon(
         param_name.* = try sema.gpa.dupeZ(u8, block.params.items[i].name);
     }
 
+    const hash = new_func.hash;
     const fn_payload = try sema.arena.create(Value.Payload.Function);
     new_func.* = .{
         .state = anal_state,
         .zir_body_inst = func_inst,
         .owner_decl = sema.owner_decl,
         .comptime_args = comptime_args,
+        .hash = hash,
         .lbrace_line = src_locs.lbrace_line,
         .rbrace_line = src_locs.rbrace_line,
         .lbrace_column = @truncate(u16, src_locs.columns),
@@ -19987,18 +19973,39 @@ fn analyzeIsNonErr(
     if (ot == .ErrorSet) return Air.Inst.Ref.bool_false;
     assert(ot == .ErrorUnion);
 
+    if (Air.refToIndex(operand)) |operand_inst| {
+        const air_tags = sema.air_instructions.items(.tag);
+        if (air_tags[operand_inst] == .wrap_errunion_payload) {
+            return Air.Inst.Ref.bool_true;
+        }
+    }
+
+    const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);
+
     // exception if the error union error set is known to be empty,
     // we allow the comparison but always make it comptime known.
     const set_ty = operand_ty.errorUnionSet();
     switch (set_ty.tag()) {
-        .anyerror, .error_set_inferred => {},
+        .anyerror => {},
+        .error_set_inferred => blk: {
+            // If the error set is empty, we must return a comptime true or false.
+            // However we want to avoid unnecessarily resolving an inferred error set
+            // in case it is already non-empty.
+            const ies = set_ty.castTag(.error_set_inferred).?.data;
+            if (ies.is_anyerror) break :blk;
+            if (ies.errors.count() != 0) break :blk;
+            if (maybe_operand_val == null) {
+                try sema.resolveInferredErrorSet(block, src, ies);
+                if (ies.is_anyerror) break :blk;
+                if (ies.errors.count() == 0) return Air.Inst.Ref.bool_true;
+            }
+        },
         else => if (set_ty.errorSetNames().len == 0) return Air.Inst.Ref.bool_true,
     }
 
-    const result_ty = Type.bool;
-    if (try sema.resolveMaybeUndefVal(block, src, operand)) |err_union| {
+    if (maybe_operand_val) |err_union| {
         if (err_union.isUndef()) {
-            return sema.addConstUndef(result_ty);
+            return sema.addConstUndef(Type.bool);
         }
         if (err_union.getError() == null) {
             return Air.Inst.Ref.bool_true;
@@ -20583,6 +20590,7 @@ fn wrapErrorUnionPayload(
         return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val));
     }
     try sema.requireRuntimeBlock(block, inst_src);
+    try sema.queueFullTypeResolution(dest_payload_ty);
     return block.addTyOp(.wrap_errunion_payload, dest_ty, coerced);
 }
 
@@ -21372,6 +21380,9 @@ fn resolveStructFully(
         try sema.resolveTypeFully(block, src, field.ty);
     }
     struct_obj.status = .fully_resolved;
+
+    // And let's not forget comptime-only status.
+    _ = try sema.typeRequiresComptime(block, src, ty);
 }
 
 fn resolveUnionFully(
@@ -21395,6 +21406,9 @@ fn resolveUnionFully(
         try sema.resolveTypeFully(block, src, field.ty);
     }
     union_obj.status = .fully_resolved;
+
+    // And let's not forget comptime-only status.
+    _ = try sema.typeRequiresComptime(block, src, ty);
 }
 
 pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!Type {
test/behavior/error.zig
@@ -251,6 +251,13 @@ fn testErrToIntWithOnePossibleValue(
     }
 }
 
+test "inferred empty error set comptime catch" {
+    const S = struct {
+        fn foo() !void {}
+    };
+    S.foo() catch @compileError("fail");
+}
+
 test "error union peer type resolution" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO