tint/resolver: Track evaluation stage

Keep track of the earliest evaluation point for an expression.
Required to properly track what can be assigned to a `const`, `override`, `let`, `var`.

Bug: tint:1601
Bug: chromium:1343242
Change-Id: I301eec21b71e9036dc1bf6c9af8079317d724762
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/95949
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 8d09d19..7fc09cc 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -367,10 +367,11 @@
     sem::Variable* sem = nullptr;
     if (is_global) {
         sem = builder_->create<sem::GlobalVariable>(
-            v, ty, ast::StorageClass::kNone, ast::Access::kUndefined, /* constant_value */ nullptr,
-            sem::BindingPoint{});
+            v, ty, sem::EvaluationStage::kRuntime, ast::StorageClass::kNone,
+            ast::Access::kUndefined, /* constant_value */ nullptr, sem::BindingPoint{});
     } else {
-        sem = builder_->create<sem::LocalVariable>(v, ty, ast::StorageClass::kNone,
+        sem = builder_->create<sem::LocalVariable>(v, ty, sem::EvaluationStage::kRuntime,
+                                                   ast::StorageClass::kNone,
                                                    ast::Access::kUndefined, current_statement_,
                                                    /* constant_value */ nullptr);
     }
@@ -421,8 +422,8 @@
     }
 
     auto* sem = builder_->create<sem::GlobalVariable>(
-        v, ty, ast::StorageClass::kNone, ast::Access::kUndefined, /* constant_value */ nullptr,
-        sem::BindingPoint{});
+        v, ty, sem::EvaluationStage::kOverride, ast::StorageClass::kNone, ast::Access::kUndefined,
+        /* constant_value */ nullptr, sem::BindingPoint{});
 
     if (auto* id = ast::GetAttribute<ast::IdAttribute>(v->attributes)) {
         sem->SetConstantId(static_cast<uint16_t>(id->value));
@@ -482,11 +483,11 @@
     }
 
     auto* sem = is_global ? static_cast<sem::Variable*>(builder_->create<sem::GlobalVariable>(
-                                c, ty, ast::StorageClass::kNone, ast::Access::kUndefined, value,
-                                sem::BindingPoint{}))
+                                c, ty, sem::EvaluationStage::kConstant, ast::StorageClass::kNone,
+                                ast::Access::kUndefined, value, sem::BindingPoint{}))
                           : static_cast<sem::Variable*>(builder_->create<sem::LocalVariable>(
-                                c, ty, ast::StorageClass::kNone, ast::Access::kUndefined,
-                                current_statement_, value));
+                                c, ty, sem::EvaluationStage::kConstant, ast::StorageClass::kNone,
+                                ast::Access::kUndefined, current_statement_, value));
 
     sem->SetConstructor(rhs);
     builder_->Sem().Add(c, sem);
@@ -567,12 +568,14 @@
         if (auto bp = var->BindingPoint()) {
             binding_point = {bp.group->value, bp.binding->value};
         }
-        sem = builder_->create<sem::GlobalVariable>(var, var_ty, storage_class, access,
+        sem = builder_->create<sem::GlobalVariable>(var, var_ty, sem::EvaluationStage::kRuntime,
+                                                    storage_class, access,
                                                     /* constant_value */ nullptr, binding_point);
 
     } else {
-        sem = builder_->create<sem::LocalVariable>(
-            var, var_ty, storage_class, access, current_statement_, /* constant_value */ nullptr);
+        sem = builder_->create<sem::LocalVariable>(var, var_ty, sem::EvaluationStage::kRuntime,
+                                                   storage_class, access, current_statement_,
+                                                   /* constant_value */ nullptr);
     }
 
     sem->SetConstructor(rhs);
@@ -1270,6 +1273,7 @@
             [&](const ast::UnaryOpExpression* unary) -> sem::Expression* { return UnaryOp(unary); },
             [&](const ast::PhonyExpression*) -> sem::Expression* {
                 return builder_->create<sem::Expression>(expr, builder_->create<sem::Void>(),
+                                                         sem::EvaluationStage::kRuntime,
                                                          current_statement_,
                                                          /* constant_value */ nullptr,
                                                          /* has_side_effects */ false);
@@ -1423,10 +1427,11 @@
         ty = builder_->create<sem::Reference>(ty, ref->StorageClass(), ref->Access());
     }
 
+    auto stage = sem::EarliestStage(obj->Stage(), idx->Stage());
     auto val = const_eval_.Index(obj, idx);
     bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
     auto* sem = builder_->create<sem::IndexAccessorExpression>(
-        expr, ty, obj, idx, current_statement_, std::move(val), has_side_effects,
+        expr, ty, stage, obj, idx, current_statement_, std::move(val), has_side_effects,
         obj->SourceVariable());
     sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
     return sem;
@@ -1443,8 +1448,9 @@
     }
 
     auto val = const_eval_.Bitcast(ty, inner);
-    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, std::move(val),
-                                                  inner->HasSideEffects());
+    auto stage = sem::EvaluationStage::kRuntime;  // TODO(crbug.com/tint/1581)
+    auto* sem = builder_->create<sem::Expression>(expr, ty, stage, current_statement_,
+                                                  std::move(val), inner->HasSideEffects());
 
     sem->Behaviors() = inner->Behaviors();
 
@@ -1464,6 +1470,7 @@
 
     // Resolve all of the arguments, their types and the set of behaviors.
     std::vector<const sem::Expression*> args(expr->args.size());
+    auto args_stage = sem::EvaluationStage::kConstant;
     sem::Behaviors arg_behaviors;
     for (size_t i = 0; i < expr->args.size(); i++) {
         auto* arg = sem_.Get(expr->args[i]);
@@ -1471,6 +1478,7 @@
             return nullptr;
         }
         args[i] = arg;
+        args_stage = sem::EarliestStage(args_stage, arg->Stage());
         arg_behaviors.Add(arg->Behaviors());
     }
     arg_behaviors.Remove(sem::Behavior::kNext);
@@ -1491,14 +1499,38 @@
             return nullptr;
         }
         const sem::Constant* value = nullptr;
-        if (ctor_or_conv.const_eval_fn) {
+        auto stage = sem::EarliestStage(ctor_or_conv.target->Stage(), args_stage);
+        if (stage == sem::EvaluationStage::kConstant) {
             value = (const_eval_.*ctor_or_conv.const_eval_fn)(ctor_or_conv.target->ReturnType(),
                                                               args.data(), args.size());
         }
-        return builder_->create<sem::Call>(expr, ctor_or_conv.target, std::move(args),
+        return builder_->create<sem::Call>(expr, ctor_or_conv.target, stage, std::move(args),
                                            current_statement_, value, has_side_effects);
     };
 
+    // ct_ctor_or_conv is a helper for building a sem::TypeConstructor for an array or structure
+    // constructor call target.
+    auto arr_or_str_ctor = [&](const sem::Type* ty,
+                               const sem::CallTarget* call_target) -> sem::Call* {
+        if (!MaterializeArguments(args, call_target)) {
+            return nullptr;
+        }
+
+        auto stage = args_stage;               // The evaluation stage of the call
+        const sem::Constant* value = nullptr;  // The constant value for the call
+        if (stage == sem::EvaluationStage::kConstant) {
+            value = const_eval_.ArrayOrStructCtor(ty, args);
+            if (!value) {
+                // Constant evaluation failed.
+                // Can happen for expressions that will fail validation (later).
+                stage = sem::EvaluationStage::kRuntime;
+            }
+        }
+
+        return builder_->create<sem::Call>(expr, call_target, stage, std::move(args),
+                                           current_statement_, std::move(value), has_side_effects);
+    };
+
     // ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
     // call for the given semantic type.
     auto ty_ctor_or_conv = [&](const sem::Type* ty) {
@@ -1517,7 +1549,7 @@
             [&](const sem::Bool*) { return ct_ctor_or_conv(CtorConvIntrinsic::kBool, nullptr); },
             [&](const sem::Array* arr) -> sem::Call* {
                 auto* call_target = utils::GetOrCreate(
-                    array_ctors_, ArrayConstructorSig{{arr, args.size()}},
+                    array_ctors_, ArrayConstructorSig{{arr, args.size(), args_stage}},
                     [&]() -> sem::TypeConstructor* {
                         sem::ParameterList params(args.size());
                         for (size_t i = 0; i < args.size(); i++) {
@@ -1528,18 +1560,14 @@
                                 ast::StorageClass::kNone,  // storage_class
                                 ast::Access::kUndefined);  // access
                         }
-                        return builder_->create<sem::TypeConstructor>(arr, std::move(params));
+                        return builder_->create<sem::TypeConstructor>(arr, std::move(params),
+                                                                      args_stage);
                     });
-                if (!MaterializeArguments(args, call_target)) {
-                    return nullptr;
-                }
-                auto val = const_eval_.ArrayOrStructCtor(arr, args);
-                return builder_->create<sem::Call>(expr, call_target, std::move(args),
-                                                   current_statement_, val, has_side_effects);
+                return arr_or_str_ctor(arr, call_target);
             },
             [&](const sem::Struct* str) -> sem::Call* {
                 auto* call_target = utils::GetOrCreate(
-                    struct_ctors_, StructConstructorSig{{str, args.size()}},
+                    struct_ctors_, StructConstructorSig{{str, args.size(), args_stage}},
                     [&]() -> sem::TypeConstructor* {
                         sem::ParameterList params(std::min(args.size(), str->Members().size()));
                         for (size_t i = 0, n = params.size(); i < n; i++) {
@@ -1550,15 +1578,10 @@
                                 ast::StorageClass::kNone,   // storage_class
                                 ast::Access::kUndefined);   // access
                         }
-                        return builder_->create<sem::TypeConstructor>(str, std::move(params));
+                        return builder_->create<sem::TypeConstructor>(str, std::move(params),
+                                                                      args_stage);
                     });
-                if (!MaterializeArguments(args, call_target)) {
-                    return nullptr;
-                }
-                auto val = const_eval_.ArrayOrStructCtor(str, args);
-                return builder_->create<sem::Call>(expr, call_target, std::move(args),
-                                                   current_statement_, std::move(val),
-                                                   has_side_effects);
+                return arr_or_str_ctor(str, call_target);
             },
             [&](Default) {
                 AddError("type is not constructible", expr->source);
@@ -1686,9 +1709,16 @@
         AddWarning("use of deprecated builtin", expr->source);
     }
 
+    auto stage = builtin.sem->Stage();
+    if (stage == sem::EvaluationStage::kConstant) {
+        for (auto* arg : args) {
+            stage = sem::EarliestStage(stage, arg->Stage());
+        }
+    }
+
     // If the builtin is @const, and all arguments have constant values, evaluate the builtin now.
     const sem::Constant* value = nullptr;
-    if (builtin.const_eval_fn) {
+    if (stage == sem::EvaluationStage::kConstant && builtin.const_eval_fn) {
         value = (const_eval_.*builtin.const_eval_fn)(builtin.sem->ReturnType(), args.data(),
                                                      args.size());
     }
@@ -1696,8 +1726,8 @@
     bool has_side_effects =
         builtin.sem->HasSideEffects() ||
         std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
-    auto* call = builder_->create<sem::Call>(expr, builtin.sem, std::move(args), current_statement_,
-                                             value, has_side_effects);
+    auto* call = builder_->create<sem::Call>(expr, builtin.sem, stage, std::move(args),
+                                             current_statement_, value, has_side_effects);
 
     if (current_function_) {
         current_function_->AddDirectlyCalledBuiltin(builtin.sem);
@@ -1755,7 +1785,8 @@
     // TODO(crbug.com/tint/1420): For now, assume all function calls have side
     // effects.
     bool has_side_effects = true;
-    auto* call = builder_->create<sem::Call>(expr, target, std::move(args), current_statement_,
+    auto* call = builder_->create<sem::Call>(expr, target, sem::EvaluationStage::kRuntime,
+                                             std::move(args), current_statement_,
                                              /* constant_value */ nullptr, has_side_effects);
 
     target->AddCallSite(call);
@@ -1851,7 +1882,8 @@
     }
 
     auto val = const_eval_.Literal(ty, literal);
-    return builder_->create<sem::Expression>(literal, ty, current_statement_, std::move(val),
+    return builder_->create<sem::Expression>(literal, ty, sem::EvaluationStage::kConstant,
+                                             current_statement_, std::move(val),
                                              /* has_side_effects */ false);
 }
 
@@ -2054,6 +2086,7 @@
     const auto* rhs = sem_.Get(expr->rhs);
     auto* lhs_ty = lhs->Type()->UnwrapRef();
     auto* rhs_ty = rhs->Type()->UnwrapRef();
+    auto stage = sem::EvaluationStage::kRuntime;  // TODO(crbug.com/tint/1581)
 
     auto op = intrinsic_table_->Lookup(expr->op, lhs_ty, rhs_ty, expr->source, false);
     if (!op.result) {
@@ -2079,7 +2112,7 @@
     }
 
     bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
-    auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, value,
+    auto* sem = builder_->create<sem::Expression>(expr, op.result, stage, current_statement_, value,
                                                   has_side_effects);
     sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
 
@@ -2096,6 +2129,7 @@
     const sem::Type* ty = nullptr;
     const sem::Variable* source_var = nullptr;
     const sem::Constant* value = nullptr;
+    auto stage = sem::EvaluationStage::kRuntime;  // TODO(crbug.com/tint/1581)
 
     switch (unary->op) {
         case ast::UnaryOp::kAddressOf:
@@ -2155,7 +2189,7 @@
         }
     }
 
-    auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, value,
+    auto* sem = builder_->create<sem::Expression>(unary, ty, stage, current_statement_, value,
                                                   expr->HasSideEffects(), source_var);
     sem->Behaviors() = expr->Behaviors();
     return sem;