Просмотр исходного кода

Treat constants with symbolic type as being symbolic. (#4082)

When constant evaluation produces a known non-symbolic value, treat the
result as a symbolic constant anyway if the type of the value is
symbolic.

We don't yet have many ways to produce a constant that has a known value
but a symbolic type. The added test case is one such way: an array `[T;
0]` initialized from `()` is a symbolic constant only because its type
is symbolic -- we know its value is always `()`. More ways to form such
constants will be appearing soon as we start to support generics: for
example, a method of a generic class has a symbolic type but a known
constant value of `{}`.

When substituting into a symbolic constant, also substitute into its
type.
Richard Smith 1 год назад
Родитель
Сommit
a699480dc9

+ 18 - 5
toolchain/check/eval.cpp

@@ -50,6 +50,15 @@ static auto GetPhase(SemIR::ConstantId constant_id) -> Phase {
   }
 }
 
+// Gets the earliest possible phase for a constant whose type is `type_id`. The
+// type of a constant is effectively treated as an operand of that constant when
+// determining its phase. For example, an empty struct with a symbolic type is a
+// symbolic constant, not a template constant.
+static auto GetTypePhase(Context& context, SemIR::TypeId type_id) -> Phase {
+  CARBON_CHECK(type_id.is_valid());
+  return GetPhase(context.types().GetConstantId(type_id));
+}
+
 // Returns the later of two phases.
 static auto LatestPhase(Phase a, Phase b) -> Phase {
   return static_cast<Phase>(
@@ -200,7 +209,11 @@ static auto RebuildAndValidateIfFieldsAreConstant(
   // Build a constant instruction by replacing each non-constant operand with
   // its constant value.
   auto typed_inst = inst.As<InstT>();
-  Phase phase = Phase::Template;
+  // Some instruction kinds don't have a `type_id` field. For those that do, the
+  // type contributes to the phase.
+  Phase phase = inst.type_id().is_valid()
+                    ? GetTypePhase(context, inst.type_id())
+                    : Phase::Template;
   if ((ReplaceFieldWithConstantValue(context, &typed_inst, each_field_id,
                                      &phase) &&
        ...)) {
@@ -227,7 +240,7 @@ static auto RebuildInitAsValue(Context& context, SemIR::Inst inst,
                                SemIR::InstKind value_kind)
     -> SemIR::ConstantId {
   auto init_inst = inst.As<SemIR::AnyAggregateInit>();
-  Phase phase = Phase::Template;
+  Phase phase = GetTypePhase(context, init_inst.type_id);
   auto elements_id = GetConstantValue(context, init_inst.elements_id, &phase);
   return MakeConstantResult(
       context,
@@ -1035,7 +1048,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
           context,
           SemIR::StructValue{.type_id = fn_decl.type_id,
                              .elements_id = SemIR::InstBlockId::Empty},
-          Phase::Template);
+          GetTypePhase(context, fn_decl.type_id));
     }
 
     case CARBON_KIND(SemIR::ClassDecl class_decl): {
@@ -1046,7 +1059,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
             context,
             SemIR::StructValue{.type_id = class_decl.type_id,
                                .elements_id = SemIR::InstBlockId::Empty},
-            Phase::Template);
+            GetTypePhase(context, class_decl.type_id));
       }
       // A non-generic class declaration evaluates to the class type.
       return MakeConstantResult(
@@ -1063,7 +1076,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
             context,
             SemIR::StructValue{.type_id = interface_decl.type_id,
                                .elements_id = SemIR::InstBlockId::Empty},
-            Phase::Template);
+            GetTypePhase(context, interface_decl.type_id));
       }
       // A non-generic interface declaration evaluates to the interface type.
       return MakeConstantResult(

+ 19 - 7
toolchain/check/subst.cpp

@@ -63,7 +63,9 @@ static auto PushOperand(Context& context, Worklist& worklist,
       worklist.Push(static_cast<SemIR::InstId>(arg));
       break;
     case SemIR::IdKind::For<SemIR::TypeId>:
-      worklist.Push(context.types().GetInstId(static_cast<SemIR::TypeId>(arg)));
+      if (auto type_id = static_cast<SemIR::TypeId>(arg); type_id.is_valid()) {
+        worklist.Push(context.types().GetInstId(type_id));
+      }
       break;
     case SemIR::IdKind::For<SemIR::InstBlockId>:
       for (auto inst_id :
@@ -88,6 +90,8 @@ static auto ExpandOperands(Context& context, Worklist& worklist,
                            SemIR::InstId inst_id) -> void {
   auto inst = context.insts().Get(inst_id);
   auto kinds = inst.ArgKinds();
+  PushOperand(context, worklist, SemIR::IdKind::For<SemIR::TypeId>,
+              inst.type_id().index);
   PushOperand(context, worklist, kinds.first, inst.arg0());
   PushOperand(context, worklist, kinds.second, inst.arg1());
 }
@@ -98,8 +102,13 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind,
   switch (kind) {
     case SemIR::IdKind::For<SemIR::InstId>:
       return worklist.Pop().index;
-    case SemIR::IdKind::For<SemIR::TypeId>:
+    case SemIR::IdKind::For<SemIR::TypeId>: {
+      auto type_id = static_cast<SemIR::TypeId>(arg);
+      if (!type_id.is_valid()) {
+        return arg;
+      }
       return context.GetTypeIdForTypeInst(worklist.Pop()).index;
+    }
     case SemIR::IdKind::For<SemIR::InstBlockId>: {
       auto old_inst_block_id = static_cast<SemIR::InstBlockId>(arg);
       auto size = context.inst_blocks().Get(old_inst_block_id).size();
@@ -136,14 +145,16 @@ static auto Rebuild(Context& context, Worklist& worklist, SemIR::InstId inst_id)
   // Note that we pop in reverse order because we pushed them in forwards order.
   int32_t arg1 = PopOperand(context, worklist, kinds.second, inst.arg1());
   int32_t arg0 = PopOperand(context, worklist, kinds.first, inst.arg0());
-  if (arg0 == inst.arg0() && arg1 == inst.arg1()) {
+  int32_t type_id =
+      PopOperand(context, worklist, SemIR::IdKind::For<SemIR::TypeId>,
+                 inst.type_id().index);
+  if (type_id == inst.type_id().index && arg0 == inst.arg0() &&
+      arg1 == inst.arg1()) {
     return inst_id;
   }
 
-  // TODO: Updating the arguments might result in the instruction having a
-  // different type. We should consider either recomputing the type or
-  // substituting into it. In the latter case, consider caching, as we may
-  // substitute into related types repeatedly.
+  // TODO: Do we need to require this type to be complete?
+  inst.SetType(SemIR::TypeId(type_id));
   inst.SetArgs(arg0, arg1);
   auto result_id = TryEvalInst(context, SemIR::InstId::Invalid, inst);
   CARBON_CHECK(result_id.is_constant())
@@ -165,6 +176,7 @@ auto SubstConstant(Context& context, SemIR::ConstantId const_id,
     return const_id;
   }
 
+  // TODO: Consider caching; we may perform the same substitutions repeatedly.
   Worklist worklist(context.constant_values().GetInstId(const_id));
 
   // For each instruction that forms part of the constant, we will visit it

+ 54 - 0
toolchain/check/testdata/array/generic_empty.carbon

@@ -0,0 +1,54 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/array/generic_empty.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/array/generic_empty.carbon
+
+fn G(T:! type) {
+  // We can initialize this without knowing T.
+  var arr: [T; 0] = ();
+}
+
+// CHECK:STDOUT: --- generic_empty.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.3: type = array_type %.2, %T [symbolic]
+// CHECK:STDOUT:   %.4: type = ptr_type %.3 [symbolic]
+// CHECK:STDOUT:   %array: %.3 = tuple_value () [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [template = constants.%G] {
+// CHECK:STDOUT:     %T.loc11_6.1: type = param T
+// CHECK:STDOUT:     @G.%T: type = bind_symbolic_name T 0, %T.loc11_6.1 [symbolic = constants.%T]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%T: type) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
+// CHECK:STDOUT:   %.loc13_16: i32 = int_literal 0 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc13_17: type = array_type %.loc13_16, %T [symbolic = constants.%.3]
+// CHECK:STDOUT:   %arr.var: ref %.3 = var arr
+// CHECK:STDOUT:   %arr: ref %.3 = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc13_22.1: %.1 = tuple_literal ()
+// CHECK:STDOUT:   %.loc13_22.2: init %.3 = array_init () to %arr.var [symbolic = constants.%array]
+// CHECK:STDOUT:   %.loc13_23: init %.3 = converted %.loc13_22.1, %.loc13_22.2 [symbolic = constants.%array]
+// CHECK:STDOUT:   assign %arr.var, %.loc13_23
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 0
toolchain/sem_ir/inst.h

@@ -237,6 +237,9 @@ class Inst : public Printable<Inst> {
   // such argument.
   auto arg1() const -> int32_t { return arg1_; }
 
+  // Sets the type of this instruction.
+  auto SetType(TypeId type_id) { type_id_ = type_id; }
+
   // Sets the arguments of this instruction.
   auto SetArgs(int32_t arg0, int32_t arg1) {
     arg0_ = arg0;