[ir] Convert materialized nodes to constants

This CL updates the IR builder to convert materialized call expressions
directly into the resulting constant values.

Bug: tint:1718
Change-Id: I184478996afdd11b00ca946775eab6801b777f3c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/122605
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 9ecf1fb..07a1fdb 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -56,6 +56,7 @@
 #include "src/tint/program.h"
 #include "src/tint/sem/builtin.h"
 #include "src/tint/sem/call.h"
+#include "src/tint/sem/materialize.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/switch_statement.h"
 #include "src/tint/sem/value_constructor.h"
@@ -751,6 +752,19 @@
 }
 
 utils::Result<Value*> BuilderImpl::EmitCall(const ast::CallExpression* expr) {
+    // If this is a materialized semantic node, just use the constant value.
+    if (auto* mat = program_->Sem().Get(expr)) {
+        if (mat->ConstantValue()) {
+            auto* cv = mat->ConstantValue()->Clone(clone_ctx_);
+            if (!cv) {
+                add_error(expr->source, "failed to get constant value for call " +
+                                            std::string(expr->TypeInfo().name));
+                return utils::Failure;
+            }
+            return builder.Constant(cv);
+        }
+    }
+
     utils::Vector<Value*, 8> args;
     args.Reserve(expr->args.Length());
 
@@ -758,7 +772,7 @@
     for (const auto* arg : expr->args) {
         auto value = EmitExpression(arg);
         if (!value) {
-            add_error(arg->source, "Failed to convert arguments");
+            add_error(arg->source, "failed to convert arguments");
             return utils::Failure;
         }
         args.Push(value.Get());
@@ -766,7 +780,7 @@
 
     auto* sem = program_->Sem().Get<sem::Call>(expr);
     if (!sem) {
-        add_error(expr->source, "Failed to get semantic information for call " +
+        add_error(expr->source, "failed to get semantic information for call " +
                                     std::string(expr->TypeInfo().name));
         return utils::Failure;
     }
@@ -778,14 +792,14 @@
     // If this is a builtin function, emit the specific builtin value
     if (sem->Target()->As<sem::Builtin>()) {
         // TODO(dsinclair): .. something ...
-        add_error(expr->source, "Missing builtin function support");
+        add_error(expr->source, "missing builtin function support");
     } else if (sem->Target()->As<sem::ValueConstructor>()) {
         instr = builder.Construct(ty, std::move(args));
     } else if (auto* conv = sem->Target()->As<sem::ValueConversion>()) {
         auto* from = conv->Source()->Clone(clone_ctx_.type_ctx);
         instr = builder.Convert(ty, from, std::move(args));
     } else if (expr->target->identifier->Is<ast::TemplatedIdentifier>()) {
-        TINT_UNIMPLEMENTED(IR, diagnostics_) << "Missing templated ident support";
+        TINT_UNIMPLEMENTED(IR, diagnostics_) << "missing templated ident support";
         return utils::Failure;
     } else {
         // Not a builtin and not a templated call, so this is a user function.
@@ -802,7 +816,7 @@
 utils::Result<Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     auto* sem = program_->Sem().Get(lit);
     if (!sem) {
-        add_error(lit->source, "Failed to get semantic information for node " +
+        add_error(lit->source, "failed to get semantic information for node " +
                                    std::string(lit->TypeInfo().name));
         return utils::Failure;
     }
@@ -810,7 +824,7 @@
     auto* cv = sem->ConstantValue()->Clone(clone_ctx_);
     if (!cv) {
         add_error(lit->source,
-                  "Failed to get constant value for node " + std::string(lit->TypeInfo().name));
+                  "failed to get constant value for node " + std::string(lit->TypeInfo().name));
         return utils::Failure;
     }
     return builder.Constant(cv);
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index be94e65..1705084 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -1876,9 +1876,12 @@
 )");
 }
 
-TEST_F(IR_BuilderImplTest, EmitExpression_ConstructEmpty) {
+// TODO(dsinclair): This needs assignment in order to output correctly. The empty constructor ends
+// up materializing, so there is no expression to emit until there is a usage. When assigment is
+// implemented this can be enabled (and the output updated).
+TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_ConstructEmpty) {
     auto* expr = vec3(ty.f32());
-    WrapInFunction(expr);
+    GlobalVar("i", builtin::AddressSpace::kPrivate, expr);
 
     auto& b = CreateBuilder();
     InjectFlowBlock();
@@ -1923,5 +1926,22 @@
 )");
 }
 
+TEST_F(IR_BuilderImplTest, EmitExpression_MaterializedCall) {
+    auto* expr = Return(Call("trunc", 2.5_f));
+
+    Func("test_function", {}, ty.f32(), expr, utils::Empty);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%bb0 = Function test_function
+  %bb1 = Block
+  Return (2.0)
+FunctionEnd
+
+)");
+}
+
 }  // namespace
 }  // namespace tint::ir