ソースを参照

Initial support for builtin functions. (#3803)

For now, a builtin function is defined by specifying a string literal
initializer in a function declaration:

```carbon
fn MyBuiltin(a: i32) -> i32 = "builtin.name";
```

End-to-end support is included for a sample `"int.add"` builtin
performing integer addition, covering constant evaluation and code
generation.

The implementation here needs substantial refactoring before we'll be
ready to start adding more builtins. That refactoring work will be
coming next. This change is aiming to checkpoint some incremental
progress.
Richard Smith 2 年 前
コミット
f0e940ddfd
43 ファイル変更1588 行追加86 行削除
  1. 2 2
      common/error.h
  2. 100 1
      toolchain/check/eval.cpp
  3. 49 0
      toolchain/check/handle_function.cpp
  4. 2 1
      toolchain/check/import_ref.cpp
  5. 2 0
      toolchain/check/node_stack.h
  6. 301 0
      toolchain/check/testdata/builtins/int_add.carbon
  7. 3 3
      toolchain/check/testdata/class/fail_addr_self.carbon
  8. 2 2
      toolchain/check/testdata/class/fail_incomplete.carbon
  9. 2 2
      toolchain/check/testdata/class/fail_memaccess_category.carbon
  10. 3 3
      toolchain/check/testdata/class/fail_method.carbon
  11. 1 1
      toolchain/check/testdata/class/fail_self.carbon
  12. 43 0
      toolchain/check/testdata/function/builtin/call.carbon
  13. 172 0
      toolchain/check/testdata/function/builtin/call_from_operator.carbon
  14. 25 0
      toolchain/check/testdata/function/builtin/definition.carbon
  15. 99 0
      toolchain/check/testdata/function/builtin/fail_redefined.carbon
  16. 22 0
      toolchain/check/testdata/function/builtin/fail_unknown.carbon
  17. 91 0
      toolchain/check/testdata/function/builtin/import.carbon
  18. 97 0
      toolchain/check/testdata/function/builtin/method.carbon
  19. 6 6
      toolchain/check/testdata/function/call/fail_param_count.carbon
  20. 1 1
      toolchain/check/testdata/function/call/fail_param_type.carbon
  21. 23 17
      toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon
  22. 89 0
      toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon
  23. 76 0
      toolchain/check/testdata/interface/todo_define_not_default.carbon
  24. 3 3
      toolchain/check/testdata/operators/overloaded/eq.carbon
  25. 2 2
      toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon
  26. 2 2
      toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon
  27. 3 0
      toolchain/diagnostics/diagnostic_kind.def
  28. 6 0
      toolchain/lower/file_context.cpp
  29. 1 2
      toolchain/lower/file_context.h
  30. 69 3
      toolchain/lower/handle.cpp
  31. 16 0
      toolchain/lower/testdata/builtins/int.carbon
  32. 24 0
      toolchain/lower/testdata/builtins/method_vs_nonmethod.carbon
  33. 41 0
      toolchain/lower/testdata/builtins/overloaded_operator.carbon
  34. 25 16
      toolchain/parse/extract.cpp
  35. 24 0
      toolchain/parse/handle_function.cpp
  36. 6 5
      toolchain/parse/node_ids.h
  37. 27 11
      toolchain/parse/node_kind.def
  38. 37 0
      toolchain/parse/testdata/function/definition/builtin.carbon
  39. 50 0
      toolchain/parse/testdata/function/definition/fail_builtin.carbon
  40. 3 3
      toolchain/parse/tree.h
  41. 14 0
      toolchain/parse/typed_nodes.h
  42. 10 0
      toolchain/sem_ir/formatter.cpp
  43. 14 0
      toolchain/sem_ir/function.h

+ 2 - 2
common/error.h

@@ -141,14 +141,14 @@ class ErrorBuilder {
   // Accumulates string message to a temporary `ErrorBuilder`. After streaming,
   // the builder must be converted to an `Error` or `ErrorOr`.
   template <typename T>
-  auto operator<<(const T& message) && -> ErrorBuilder&& {
+  auto operator<<(T&& message) && -> ErrorBuilder&& {
     *out_ << message;
     return std::move(*this);
   }
 
   // Accumulates string message for an lvalue error builder.
   template <typename T>
-  auto operator<<(const T& message) & -> ErrorBuilder& {
+  auto operator<<(T&& message) & -> ErrorBuilder& {
     *out_ << message;
     return *this;
   }

+ 100 - 1
toolchain/check/eval.cpp

@@ -4,6 +4,8 @@
 
 #include "toolchain/check/eval.h"
 
+#include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -283,6 +285,101 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
   return context.constant_values().Get(elements[index_val.getZExtValue()]);
 }
 
+static auto PerformBuiltinCall(Context& context, SemIRLocation loc,
+                               SemIR::Call call,
+                               SemIR::BuiltinFunctionKind builtin_kind,
+                               llvm::ArrayRef<SemIR::InstId> arg_ids,
+                               Phase phase) -> SemIR::ConstantId {
+  switch (builtin_kind) {
+    case SemIR::BuiltinFunctionKind::None:
+      CARBON_FATAL() << "Not a builtin function.";
+
+    case SemIR::BuiltinFunctionKind::IntAdd: {
+      if (phase != Phase::Template) {
+        break;
+      }
+      if (arg_ids.size() != 2) {
+        break;
+      }
+      auto lhs = context.insts().TryGetAs<SemIR::IntLiteral>(arg_ids[0]);
+      auto rhs = context.insts().TryGetAs<SemIR::IntLiteral>(arg_ids[1]);
+      // TODO: Move type checking to the point where we make the call.
+      if (!lhs || !rhs || lhs->type_id != rhs->type_id ||
+          call.type_id != lhs->type_id) {
+        break;
+      }
+      // TODO: Integer values should be stored in the correct bit width for
+      // their types. For now we assume i32.
+      auto lhs_val = context.ints().Get(lhs->int_id).sextOrTrunc(32);
+      auto rhs_val = context.ints().Get(rhs->int_id).sextOrTrunc(32);
+      bool overflow = false;
+      auto result = context.ints().Add(lhs_val.sadd_ov(rhs_val, overflow));
+      if (overflow) {
+        CARBON_DIAGNOSTIC(CompileTimeIntegerOverflow, Error,
+                          "Integer overflow in calculation {0} + {1}.",
+                          llvm::APSInt, llvm::APSInt);
+        context.emitter().Emit(loc, CompileTimeIntegerOverflow,
+                               llvm::APSInt(lhs_val, false),
+                               llvm::APSInt(rhs_val, false));
+      }
+      return MakeConstantResult(context,
+                                SemIR::IntLiteral{lhs->type_id, result}, phase);
+    }
+  }
+
+  return SemIR::ConstantId::NotConstant;
+}
+
+// Extracts the callee function from a callee constant. Returns
+// FunctionId::Invalid if the callee is not known.
+static auto GetCalleeFunctionId(Context& context, SemIR::InstId callee_id)
+    -> SemIR::FunctionId {
+  if (auto bound_method =
+          context.insts().TryGetAs<SemIR::BoundMethod>(callee_id)) {
+    callee_id = bound_method->function_id;
+  }
+  if (auto callee = context.insts().TryGetAs<SemIR::FunctionDecl>(callee_id)) {
+    return {callee->function_id};
+  }
+  return {SemIR::FunctionId::Invalid};
+}
+
+static auto PerformCall(Context& context, SemIRLocation loc, SemIR::Call call)
+    -> SemIR::ConstantId {
+  Phase phase = Phase::Template;
+
+  // A call with an invalid argument list is used to represent an erroneous
+  // call.
+  //
+  // TODO: Use a better representation for this.
+  if (call.args_id == SemIR::InstBlockId::Invalid) {
+    return SemIR::ConstantId::Error;
+  }
+
+  // If the callee isn't constant, this is not a constant call.
+  if (!ReplaceFieldWithConstantValue(context, &call, &SemIR::Call::callee_id,
+                                     &phase)) {
+    return SemIR::ConstantId::NotConstant;
+  }
+
+  auto function_id = GetCalleeFunctionId(context, call.callee_id);
+
+  // Handle calls to builtins.
+  auto& function = context.functions().Get(function_id);
+  if (function.builtin_kind != SemIR::BuiltinFunctionKind::None) {
+    if (!ReplaceFieldWithConstantValue(context, &call, &SemIR::Call::args_id,
+                                       &phase)) {
+      return SemIR::ConstantId::NotConstant;
+    }
+    if (phase == Phase::UnknownDueToError) {
+      return SemIR::ConstantId::Error;
+    }
+    return PerformBuiltinCall(context, loc, call, function.builtin_kind,
+                              context.inst_blocks().Get(call.args_id), phase);
+  }
+  return SemIR::ConstantId::NotConstant;
+}
+
 auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     -> SemIR::ConstantId {
   // TODO: Ensure we have test coverage for each of these cases that can result
@@ -425,9 +522,11 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::TupleIndex::Kind:
       return PerformAggregateIndex(context, inst);
 
+    case SemIR::Call::Kind:
+      return PerformCall(context, inst_id, inst.As<SemIR::Call>());
+
     // TODO: These need special handling.
     case SemIR::BindValue::Kind:
-    case SemIR::Call::Kind:
     case SemIR::Deref::Kind:
     case SemIR::ImportRefUsed::Kind:
     case SemIR::Temporary::Kind:

+ 49 - 0
toolchain/check/handle_function.cpp

@@ -284,4 +284,53 @@ auto HandleFunctionDefinition(Context& context,
   return true;
 }
 
+auto HandleBuiltinFunctionDefinitionStart(
+    Context& context, Parse::BuiltinFunctionDefinitionStartId node_id) -> bool {
+  // Process the declaration portion of the function.
+  auto [function_id, _] =
+      BuildFunctionDecl(context, node_id, /*is_definition=*/true);
+  context.node_stack().Push(node_id, function_id);
+  return true;
+}
+
+auto HandleBuiltinName(Context& context, Parse::BuiltinNameId node_id) -> bool {
+  context.node_stack().Push(node_id);
+  return true;
+}
+
+// Looks up a builtin function kind given its name as a string.
+// TODO: Move this out to another file.
+static auto LookupBuiltinFunctionKind(Context& context,
+                                      Parse::BuiltinNameId name_id)
+    -> SemIR::BuiltinFunctionKind {
+  auto builtin_name = context.string_literal_values().Get(
+      context.tokens().GetStringLiteralValue(
+          context.parse_tree().node_token(name_id)));
+  auto kind = llvm::StringSwitch<SemIR::BuiltinFunctionKind>(builtin_name)
+                  .Case("int.add", SemIR::BuiltinFunctionKind::IntAdd)
+                  .Default(SemIR::BuiltinFunctionKind::None);
+  if (kind == SemIR::BuiltinFunctionKind::None) {
+    CARBON_DIAGNOSTIC(UnknownBuiltinFunctionName, Error,
+                      "Unknown builtin function name \"{0}\".", std::string);
+    context.emitter().Emit(name_id, UnknownBuiltinFunctionName,
+                           builtin_name.str());
+  }
+  return kind;
+}
+
+auto HandleBuiltinFunctionDefinition(
+    Context& context, Parse::BuiltinFunctionDefinitionId /*node_id*/) -> bool {
+  auto name_id =
+      context.node_stack().PopForSoloNodeId<Parse::NodeKind::BuiltinName>();
+  auto function_id =
+      context.node_stack()
+          .Pop<Parse::NodeKind::BuiltinFunctionDefinitionStart>();
+
+  auto& function = context.functions().Get(function_id);
+  function.builtin_kind = LookupBuiltinFunctionKind(context, name_id);
+
+  context.decl_name_stack().PopScope();
+  return true;
+}
+
 }  // namespace Carbon::Check

+ 2 - 1
toolchain/check/import_ref.cpp

@@ -706,7 +706,8 @@ class ImportRefResolver {
          .param_refs_id =
              GetLocalParamRefsId(function.param_refs_id, param_const_ids),
          .return_type_id = new_return_type_id,
-         .return_slot_id = new_return_slot});
+         .return_slot_id = new_return_slot,
+         .builtin_kind = function.builtin_kind});
     // Write the function ID into the FunctionDecl.
     context_.ReplaceInstBeforeConstantUse(
         function_decl_id, {Parse::NodeId::Invalid, function_decl});

+ 2 - 0
toolchain/check/node_stack.h

@@ -424,6 +424,7 @@ class NodeStack {
         case Parse::NodeKind::WhileConditionStart:
           return Id::KindFor<SemIR::InstBlockId>();
         case Parse::NodeKind::FunctionDefinitionStart:
+        case Parse::NodeKind::BuiltinFunctionDefinitionStart:
           return Id::KindFor<SemIR::FunctionId>();
         case Parse::NodeKind::ClassDefinitionStart:
           return Id::KindFor<SemIR::ClassId>();
@@ -434,6 +435,7 @@ class NodeStack {
         case Parse::NodeKind::SelfValueName:
           return Id::KindFor<SemIR::NameId>();
         case Parse::NodeKind::ArrayExprSemi:
+        case Parse::NodeKind::BuiltinName:
         case Parse::NodeKind::ClassIntroducer:
         case Parse::NodeKind::CodeBlockStart:
         case Parse::NodeKind::ExprOpenParen:

+ 301 - 0
toolchain/check/testdata/builtins/int_add.carbon

@@ -0,0 +1,301 @@
+// 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
+
+// --- int_add.carbon
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+
+var arr: [i32; Add(1, 2)];
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return Add(a, b);
+}
+
+// --- fail_bad_decl.carbon
+
+package FailBadDecl api;
+
+fn TooFew(a: i32) -> i32 = "int.add";
+fn TooMany(a: i32, b: i32, c: i32) -> i32 = "int.add";
+fn BadReturnType(a: i32, b: i32) -> bool = "int.add";
+fn JustRight(a: i32, b: i32) -> i32 = "int.add";
+
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+3]]:20: ERROR: Array bound is not a constant.
+// CHECK:STDERR: var too_few: [i32; TooFew(1)];
+// CHECK:STDERR:                    ^~~~~~~
+var too_few: [i32; TooFew(1)];
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+3]]:21: ERROR: Array bound is not a constant.
+// CHECK:STDERR: var too_many: [i32; TooMany(1, 2, 3)];
+// CHECK:STDERR:                     ^~~~~~~~
+var too_many: [i32; TooMany(1, 2, 3)];
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+3]]:28: ERROR: Array bound is not a constant.
+// CHECK:STDERR: var bad_return_type: [i32; BadReturnType(1, 2)];
+// CHECK:STDERR:                            ^~~~~~~~~~~~~~
+var bad_return_type: [i32; BadReturnType(1, 2)];
+
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+6]]:21: ERROR: 3 argument(s) passed to function expecting 2 argument(s).
+// CHECK:STDERR: var bad_call: [i32; JustRight(1, 2, 3)];
+// CHECK:STDERR:                     ^~~~~~~~~~
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE-18]]:1: Calling function declared here.
+// CHECK:STDERR: fn JustRight(a: i32, b: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+var bad_call: [i32; JustRight(1, 2, 3)];
+
+// TODO: We should diagnose these in check rather than failing in lower.
+fn RuntimeCallTooFew(a: i32) -> i32 {
+  return TooFew(a);
+}
+
+fn RuntimeCallTooMany(a: i32, b: i32, c: i32) -> i32 {
+  return TooMany(a, b, c);
+}
+
+fn RuntimeCallBadReturnType(a: i32, b: i32) -> bool {
+  return BadReturnType(a, b);
+}
+
+// --- fail_overflow.carbon
+
+package FailOverflow api;
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+
+let a: i32 = Add(0x7FFFFFFF, 0);
+// CHECK:STDERR: fail_overflow.carbon:[[@LINE+3]]:14: ERROR: Integer overflow in calculation 2147483647 + 1.
+// CHECK:STDERR: let b: i32 = Add(0x7FFFFFFF, 1);
+// CHECK:STDERR:              ^~~~
+let b: i32 = Add(0x7FFFFFFF, 1);
+
+// CHECK:STDOUT: --- int_add.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add: <function> = fn_decl @Add [template] {
+// CHECK:STDOUT:     %a.loc2_8.1: i32 = param a
+// CHECK:STDOUT:     @Add.%a: i32 = bind_name a, %a.loc2_8.1
+// CHECK:STDOUT:     %b.loc2_16.1: i32 = param b
+// CHECK:STDOUT:     @Add.%b: i32 = bind_name b, %b.loc2_16.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add.ref: <function> = name_ref Add, %Add [template = %Add]
+// CHECK:STDOUT:   %.loc4_20: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_23: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_19: init i32 = call %Add.ref(%.loc4_20, %.loc4_23) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_25: type = array_type %.loc4_19, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 3] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 3] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc6_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc6_16.1
+// CHECK:STDOUT:     %b.loc6_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc6_24.1
+// CHECK:STDOUT:     %return.var.loc6: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Add.ref: <function> = name_ref Add, file.%Add [template = file.%Add]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc7_13.1: init i32 = call %Add.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc7_19: i32 = value_of_initializer %.loc7_13.1
+// CHECK:STDOUT:   %.loc7_13.2: i32 = converted %.loc7_13.1, %.loc7_19
+// CHECK:STDOUT:   return %.loc7_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 3 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .TooFew = %TooFew
+// CHECK:STDOUT:     .TooMany = %TooMany
+// CHECK:STDOUT:     .BadReturnType = %BadReturnType
+// CHECK:STDOUT:     .JustRight = %JustRight
+// CHECK:STDOUT:     .too_few = %too_few
+// CHECK:STDOUT:     .too_many = %too_many
+// CHECK:STDOUT:     .bad_return_type = %bad_return_type
+// CHECK:STDOUT:     .bad_call = %bad_call
+// CHECK:STDOUT:     .RuntimeCallTooFew = %RuntimeCallTooFew
+// CHECK:STDOUT:     .RuntimeCallTooMany = %RuntimeCallTooMany
+// CHECK:STDOUT:     .RuntimeCallBadReturnType = %RuntimeCallBadReturnType
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %TooFew: <function> = fn_decl @TooFew [template] {
+// CHECK:STDOUT:     %a.loc4_11.1: i32 = param a
+// CHECK:STDOUT:     @TooFew.%a: i32 = bind_name a, %a.loc4_11.1
+// CHECK:STDOUT:     %return.var.loc4: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %TooMany: <function> = fn_decl @TooMany [template] {
+// CHECK:STDOUT:     %a.loc5_12.1: i32 = param a
+// CHECK:STDOUT:     @TooMany.%a: i32 = bind_name a, %a.loc5_12.1
+// CHECK:STDOUT:     %b.loc5_20.1: i32 = param b
+// CHECK:STDOUT:     @TooMany.%b: i32 = bind_name b, %b.loc5_20.1
+// CHECK:STDOUT:     %c.loc5_28.1: i32 = param c
+// CHECK:STDOUT:     @TooMany.%c: i32 = bind_name c, %c.loc5_28.1
+// CHECK:STDOUT:     %return.var.loc5: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %BadReturnType: <function> = fn_decl @BadReturnType [template] {
+// CHECK:STDOUT:     %a.loc6_18.1: i32 = param a
+// CHECK:STDOUT:     @BadReturnType.%a: i32 = bind_name a, %a.loc6_18.1
+// CHECK:STDOUT:     %b.loc6_26.1: i32 = param b
+// CHECK:STDOUT:     @BadReturnType.%b: i32 = bind_name b, %b.loc6_26.1
+// CHECK:STDOUT:     %return.var.loc6: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %JustRight: <function> = fn_decl @JustRight [template] {
+// CHECK:STDOUT:     %a.loc7_14.1: i32 = param a
+// CHECK:STDOUT:     @JustRight.%a: i32 = bind_name a, %a.loc7_14.1
+// CHECK:STDOUT:     %b.loc7_22.1: i32 = param b
+// CHECK:STDOUT:     @JustRight.%b: i32 = bind_name b, %b.loc7_22.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %TooFew.ref: <function> = name_ref TooFew, %TooFew [template = %TooFew]
+// CHECK:STDOUT:   %.loc12_27: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc12_26: init i32 = call %TooFew.ref(%.loc12_27)
+// CHECK:STDOUT:   %too_few.var: ref <error> = var too_few
+// CHECK:STDOUT:   %too_few: ref <error> = bind_name too_few, %too_few.var
+// CHECK:STDOUT:   %TooMany.ref: <function> = name_ref TooMany, %TooMany [template = %TooMany]
+// CHECK:STDOUT:   %.loc16_29: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc16_32: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc16_35: i32 = int_literal 3 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc16_28: init i32 = call %TooMany.ref(%.loc16_29, %.loc16_32, %.loc16_35)
+// CHECK:STDOUT:   %too_many.var: ref <error> = var too_many
+// CHECK:STDOUT:   %too_many: ref <error> = bind_name too_many, %too_many.var
+// CHECK:STDOUT:   %BadReturnType.ref: <function> = name_ref BadReturnType, %BadReturnType [template = %BadReturnType]
+// CHECK:STDOUT:   %.loc20_42: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc20_45: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc20_41: init bool = call %BadReturnType.ref(%.loc20_42, %.loc20_45)
+// CHECK:STDOUT:   %bad_return_type.var: ref <error> = var bad_return_type
+// CHECK:STDOUT:   %bad_return_type: ref <error> = bind_name bad_return_type, %bad_return_type.var
+// CHECK:STDOUT:   %JustRight.ref: <function> = name_ref JustRight, %JustRight [template = %JustRight]
+// CHECK:STDOUT:   %.loc28_31: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc28_34: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc28_37: i32 = int_literal 3 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc28_30: init i32 = call %JustRight.ref(<invalid>) [template = <error>]
+// CHECK:STDOUT:   %.loc28_39: type = array_type %.loc28_30, i32 [template = <error>]
+// CHECK:STDOUT:   %bad_call.var: ref <error> = var bad_call
+// CHECK:STDOUT:   %bad_call: ref <error> = bind_name bad_call, %bad_call.var
+// CHECK:STDOUT:   %RuntimeCallTooFew: <function> = fn_decl @RuntimeCallTooFew [template] {
+// CHECK:STDOUT:     %a.loc31_22.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCallTooFew.%a: i32 = bind_name a, %a.loc31_22.1
+// CHECK:STDOUT:     %return.var.loc31: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCallTooMany: <function> = fn_decl @RuntimeCallTooMany [template] {
+// CHECK:STDOUT:     %a.loc35_23.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCallTooMany.%a: i32 = bind_name a, %a.loc35_23.1
+// CHECK:STDOUT:     %b.loc35_31.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCallTooMany.%b: i32 = bind_name b, %b.loc35_31.1
+// CHECK:STDOUT:     %c.loc35_39.1: i32 = param c
+// CHECK:STDOUT:     @RuntimeCallTooMany.%c: i32 = bind_name c, %c.loc35_39.1
+// CHECK:STDOUT:     %return.var.loc35: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCallBadReturnType: <function> = fn_decl @RuntimeCallBadReturnType [template] {
+// CHECK:STDOUT:     %a.loc39_29.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCallBadReturnType.%a: i32 = bind_name a, %a.loc39_29.1
+// CHECK:STDOUT:     %b.loc39_37.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCallBadReturnType.%b: i32 = bind_name b, %b.loc39_37.1
+// CHECK:STDOUT:     %return.var.loc39: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TooFew(%a: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TooMany(%a: i32, %b: i32, %c: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @BadReturnType(%a: i32, %b: i32) -> bool = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @JustRight(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCallTooFew(%a: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %TooFew.ref: <function> = name_ref TooFew, file.%TooFew [template = file.%TooFew]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %.loc32_16.1: init i32 = call %TooFew.ref(%a.ref)
+// CHECK:STDOUT:   %.loc32_19: i32 = value_of_initializer %.loc32_16.1
+// CHECK:STDOUT:   %.loc32_16.2: i32 = converted %.loc32_16.1, %.loc32_19
+// CHECK:STDOUT:   return %.loc32_16.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCallTooMany(%a: i32, %b: i32, %c: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %TooMany.ref: <function> = name_ref TooMany, file.%TooMany [template = file.%TooMany]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %c.ref: i32 = name_ref c, %c
+// CHECK:STDOUT:   %.loc36_17.1: init i32 = call %TooMany.ref(%a.ref, %b.ref, %c.ref)
+// CHECK:STDOUT:   %.loc36_26: i32 = value_of_initializer %.loc36_17.1
+// CHECK:STDOUT:   %.loc36_17.2: i32 = converted %.loc36_17.1, %.loc36_26
+// CHECK:STDOUT:   return %.loc36_17.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCallBadReturnType(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %BadReturnType.ref: <function> = name_ref BadReturnType, file.%BadReturnType [template = file.%BadReturnType]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc40_23.1: init bool = call %BadReturnType.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc40_29: bool = value_of_initializer %.loc40_23.1
+// CHECK:STDOUT:   %.loc40_23.2: bool = converted %.loc40_23.1, %.loc40_29
+// CHECK:STDOUT:   return %.loc40_23.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_overflow.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 2147483647 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 2147483648 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add: <function> = fn_decl @Add [template] {
+// CHECK:STDOUT:     %a.loc4_8.1: i32 = param a
+// CHECK:STDOUT:     @Add.%a: i32 = bind_name a, %a.loc4_8.1
+// CHECK:STDOUT:     %b.loc4_16.1: i32 = param b
+// CHECK:STDOUT:     @Add.%b: i32 = bind_name b, %b.loc4_16.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add.ref.loc6: <function> = name_ref Add, %Add [template = %Add]
+// CHECK:STDOUT:   %.loc6_18: i32 = int_literal 2147483647 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc6_30: i32 = int_literal 0 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc6_17.1: init i32 = call %Add.ref.loc6(%.loc6_18, %.loc6_30) [template = constants.%.1]
+// CHECK:STDOUT:   %.loc6_32: i32 = value_of_initializer %.loc6_17.1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc6_17.2: i32 = converted %.loc6_17.1, %.loc6_32 [template = constants.%.1]
+// CHECK:STDOUT:   %a.loc6: i32 = bind_name a, %.loc6_17.2
+// CHECK:STDOUT:   %Add.ref.loc10: <function> = name_ref Add, %Add [template = %Add]
+// CHECK:STDOUT:   %.loc10_18: i32 = int_literal 2147483647 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_30: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc10_17.1: init i32 = call %Add.ref.loc10(%.loc10_18, %.loc10_30) [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_32: i32 = value_of_initializer %.loc10_17.1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_17.2: i32 = converted %.loc10_17.1, %.loc10_32 [template = constants.%.4]
+// CHECK:STDOUT:   %b.loc10: i32 = bind_name b, %.loc10_17.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/class/fail_addr_self.carbon

@@ -95,11 +95,11 @@ fn F(c: Class, p: Class*) {
 // CHECK:STDOUT:   %c.ref.loc19: Class = name_ref c, %c
 // CHECK:STDOUT:   %F.ref.loc19: <function> = name_ref F, @Class.%F [template = @Class.%F]
 // CHECK:STDOUT:   %.loc19_4: <bound method> = bound_method %c.ref.loc19, %F.ref.loc19
-// CHECK:STDOUT:   %.loc19_6: init () = call %.loc19_4(<invalid>)
+// CHECK:STDOUT:   %.loc19_6: init () = call %.loc19_4(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %c.ref.loc27: Class = name_ref c, %c
 // CHECK:STDOUT:   %G.ref.loc27: <function> = name_ref G, @Class.%G [template = @Class.%G]
 // CHECK:STDOUT:   %.loc27_4: <bound method> = bound_method %c.ref.loc27, %G.ref.loc27
-// CHECK:STDOUT:   %.loc27_6: init () = call %.loc27_4(<invalid>)
+// CHECK:STDOUT:   %.loc27_6: init () = call %.loc27_4(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %p.ref.loc30: Class* = name_ref p, %p
 // CHECK:STDOUT:   %.loc30_4.1: ref Class = deref %p.ref.loc30
 // CHECK:STDOUT:   %F.ref.loc30: <function> = name_ref F, @Class.%F [template = @Class.%F]
@@ -111,7 +111,7 @@ fn F(c: Class, p: Class*) {
 // CHECK:STDOUT:   %G.ref.loc38: <function> = name_ref G, @Class.%G [template = @Class.%G]
 // CHECK:STDOUT:   %.loc38_7: <bound method> = bound_method %.loc38_4.1, %G.ref.loc38
 // CHECK:STDOUT:   %.loc38_4.2: Class* = addr_of %.loc38_4.1
-// CHECK:STDOUT:   %.loc38_9: init () = call %.loc38_7(<invalid>)
+// CHECK:STDOUT:   %.loc38_9: init () = call %.loc38_7(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -259,10 +259,10 @@ fn CallReturnIncomplete() {
 // CHECK:STDOUT:   %TakeIncomplete.ref.loc105: <function> = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete]
 // CHECK:STDOUT:   %p.ref: Class* = name_ref p, %p
 // CHECK:STDOUT:   %.loc105_18: ref Class = deref %p.ref
-// CHECK:STDOUT:   %.loc105_17: init () = call %TakeIncomplete.ref.loc105(<invalid>)
+// CHECK:STDOUT:   %.loc105_17: init () = call %TakeIncomplete.ref.loc105(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %TakeIncomplete.ref.loc116: <function> = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete]
 // CHECK:STDOUT:   %.loc116_19: {} = struct_literal ()
-// CHECK:STDOUT:   %.loc116_17: init () = call %TakeIncomplete.ref.loc116(<invalid>)
+// CHECK:STDOUT:   %.loc116_17: init () = call %TakeIncomplete.ref.loc116(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/fail_memaccess_category.carbon

@@ -98,14 +98,14 @@ fn F(s: {.a: A}, b: B) {
 // CHECK:STDOUT:   %.loc23_4: A = struct_access %s.ref, element0
 // CHECK:STDOUT:   %F.ref.loc23: <function> = name_ref F, @A.%F [template = @A.%F]
 // CHECK:STDOUT:   %.loc23_6: <bound method> = bound_method %.loc23_4, %F.ref.loc23
-// CHECK:STDOUT:   %.loc23_8: init () = call %.loc23_6(<invalid>)
+// CHECK:STDOUT:   %.loc23_8: init () = call %.loc23_6(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %b.ref: B = name_ref b, %b
 // CHECK:STDOUT:   %a.ref: <unbound element of class B> = name_ref a, @B.%.loc12 [template = @B.%.loc12]
 // CHECK:STDOUT:   %.loc33_4.1: ref A = class_element_access %b.ref, element0
 // CHECK:STDOUT:   %.loc33_4.2: A = bind_value %.loc33_4.1
 // CHECK:STDOUT:   %F.ref.loc33: <function> = name_ref F, @A.%F [template = @A.%F]
 // CHECK:STDOUT:   %.loc33_6: <bound method> = bound_method %.loc33_4.2, %F.ref.loc33
-// CHECK:STDOUT:   %.loc33_8: init () = call %.loc33_6(<invalid>)
+// CHECK:STDOUT:   %.loc33_8: init () = call %.loc33_6(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/class/fail_method.carbon

@@ -98,13 +98,13 @@ fn F(c: Class) {
 // CHECK:STDOUT:   %.loc18: init () = call %NoSelf.ref.loc18()
 // CHECK:STDOUT:   %Class.ref.loc25: type = name_ref Class, file.%Class.decl [template = constants.%Class]
 // CHECK:STDOUT:   %WithSelf.ref.loc25: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
-// CHECK:STDOUT:   %.loc25: init () = call %WithSelf.ref.loc25(<invalid>)
+// CHECK:STDOUT:   %.loc25: init () = call %WithSelf.ref.loc25(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Class.ref.loc32: type = name_ref Class, file.%Class.decl [template = constants.%Class]
 // CHECK:STDOUT:   %WithSelf.ref.loc32: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
 // CHECK:STDOUT:   %c.ref.loc32: Class = name_ref c, %c
-// CHECK:STDOUT:   %.loc32: init () = call %WithSelf.ref.loc32(<invalid>)
+// CHECK:STDOUT:   %.loc32: init () = call %WithSelf.ref.loc32(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %A.ref: <function> = name_ref A, file.%A [template = @Class.%WithSelf]
-// CHECK:STDOUT:   %.loc40: init () = call %A.ref(<invalid>)
+// CHECK:STDOUT:   %.loc40: init () = call %A.ref(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/fail_self.carbon

@@ -137,7 +137,7 @@ fn CallWrongSelf(ws: WrongSelf) {
 // CHECK:STDOUT:   %ws.ref: WrongSelf = name_ref ws, %ws
 // CHECK:STDOUT:   %F.ref: <function> = name_ref F, @WrongSelf.%F [template = @WrongSelf.%F]
 // CHECK:STDOUT:   %.loc50_5: <bound method> = bound_method %ws.ref, %F.ref
-// CHECK:STDOUT:   %.loc50_7: init () = call %.loc50_5(<invalid>)
+// CHECK:STDOUT:   %.loc50_7: init () = call %.loc50_5(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 43 - 0
toolchain/check/testdata/function/builtin/call.carbon

@@ -0,0 +1,43 @@
+// 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
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+
+var arr: [i32; Add(1, 2)];
+
+// CHECK:STDOUT: --- call.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add: <function> = fn_decl @Add [template] {
+// CHECK:STDOUT:     %a.loc7_8.1: i32 = param a
+// CHECK:STDOUT:     @Add.%a: i32 = bind_name a, %a.loc7_8.1
+// CHECK:STDOUT:     %b.loc7_16.1: i32 = param b
+// CHECK:STDOUT:     @Add.%b: i32 = bind_name b, %b.loc7_16.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add.ref: <function> = name_ref Add, %Add [template = %Add]
+// CHECK:STDOUT:   %.loc9_20: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc9_23: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc9_19: init i32 = call %Add.ref(%.loc9_20, %.loc9_23) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc9_25: type = array_type %.loc9_19, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 3] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 3] = bind_name arr, %arr.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 172 - 0
toolchain/check/testdata/function/builtin/call_from_operator.carbon

@@ -0,0 +1,172 @@
+// 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
+
+// --- core.carbon
+
+package Core api;
+
+interface Add {
+  fn Op[self: Self](other: Self) -> Self;
+}
+
+// --- user.carbon
+
+import Core;
+
+// TODO: This should be in `Core`, but currently impl lookup only looks in the
+// current file.
+impl i32 as Core.Add {
+  fn Op[self: i32](other: i32) -> i32 = "int.add";
+}
+
+var arr: [i32; 1 + 2] = (3, 4, 3 + 4);
+
+// CHECK:STDOUT: --- core.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Add [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Add, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Add> = assoc_entity element0, @Add.%Op [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add.decl: type = interface_decl @Add [template = constants.%.1] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Add {
+// CHECK:STDOUT:   %Self: Add = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %Op: <function> = fn_decl @Op [template] {
+// CHECK:STDOUT:     %Self.ref.loc5_15: Add = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_15.1: type = facet_type_access %Self.ref.loc5_15 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_15.2: type = converted %Self.ref.loc5_15, %.loc5_15.1 [symbolic = %Self]
+// CHECK:STDOUT:     %self.loc5_9.1: Self = param self
+// CHECK:STDOUT:     %self.loc5_9.2: Self = bind_name self, %self.loc5_9.1
+// CHECK:STDOUT:     %Self.ref.loc5_28: Add = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_28.1: type = facet_type_access %Self.ref.loc5_28 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_28.2: type = converted %Self.ref.loc5_28, %.loc5_28.1 [symbolic = %Self]
+// CHECK:STDOUT:     %other.loc5_21.1: Self = param other
+// CHECK:STDOUT:     %other.loc5_21.2: Self = bind_name other, %other.loc5_21.1
+// CHECK:STDOUT:     %Self.ref.loc5_37: Add = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_37.1: type = facet_type_access %Self.ref.loc5_37 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc5_37.2: type = converted %Self.ref.loc5_37, %.loc5_37.1 [symbolic = %Self]
+// CHECK:STDOUT:     %return.var: ref Self = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc5_41: <associated <function> in Add> = assoc_entity element0, %Op [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .Op = %.loc5_41
+// CHECK:STDOUT:   witness = (%Op)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Op[@Add.%self.loc5_9.2: Self](@Add.%other.loc5_21.2: Self) -> Self;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- user.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Add [template]
+// CHECK:STDOUT:   %.2: <witness> = interface_witness (@impl.%Op) [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.5: type = assoc_entity_type @Add, <function> [template]
+// CHECK:STDOUT:   %.6: <associated <function> in Add> = assoc_entity element0, file.%import_ref.6 [template]
+// CHECK:STDOUT:   %.7: <bound method> = bound_method %.3, @impl.%Op [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.9: type = array_type %.8, i32 [template]
+// CHECK:STDOUT:   %.10: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT:   %.11: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.12: i32 = int_literal 4 [template]
+// CHECK:STDOUT:   %.13: <bound method> = bound_method %.11, @impl.%Op [template]
+// CHECK:STDOUT:   %.14: i32 = int_literal 7 [template]
+// CHECK:STDOUT:   %.15: type = tuple_type (i32, i32, i32) [template]
+// CHECK:STDOUT:   %.16: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.17: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.18: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.19: [i32; 3] = tuple_value (%.11, %.12, %.14) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, used [template = constants.%.1]
+// CHECK:STDOUT:   %import_ref.2: <associated <function> in Add> = import_ref ir1, inst+20, used [template = constants.%.6]
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+3, unused
+// CHECK:STDOUT:   %import_ref.4: <function> = import_ref ir1, inst+18, used [template = imports.%Op]
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %Core.ref: <namespace> = name_ref Core, %Core [template = %Core]
+// CHECK:STDOUT:     %Add.decl: invalid = interface_decl @Add [template = constants.%.1] {}
+// CHECK:STDOUT:     %Add.ref: type = name_ref Add, %import_ref.1 [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc10_16: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc10_20: i32 = int_literal 2 [template = constants.%.4]
+// CHECK:STDOUT:   %import_ref.5: type = import_ref ir1, inst+1, used [template = constants.%.1]
+// CHECK:STDOUT:   %import_ref.6 = import_ref ir1, inst+18, unused
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%Op]
+// CHECK:STDOUT:   %.loc10_18.1: <bound method> = bound_method %.loc10_16, %.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_18.2: init i32 = call %.loc10_18.1(%.loc10_16, %.loc10_20) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc10_21: type = array_type %.loc10_18.2, i32 [template = constants.%.9]
+// CHECK:STDOUT:   %arr.var: ref [i32; 3] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 3] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %import_ref.7: type = import_ref ir1, inst+1, used [template = constants.%.1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Add {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Op = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT:   witness = (file.%import_ref.4)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: i32 as Add {
+// CHECK:STDOUT:   %Op: <function> = fn_decl @Op.1 [template] {
+// CHECK:STDOUT:     %self.loc7_9.1: i32 = param self
+// CHECK:STDOUT:     %self.loc7_9.2: i32 = bind_name self, %self.loc7_9.1
+// CHECK:STDOUT:     %other.loc7_20.1: i32 = param other
+// CHECK:STDOUT:     %other.loc7_20.2: i32 = bind_name other, %other.loc7_20.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%Op) [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Op = %Op
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Op.1[@impl.%self.loc7_9.2: i32](@impl.%other.loc7_20.2: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Op.2[%self: Self](%other: Self) -> Self;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc10_26: i32 = int_literal 3 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc10_29: i32 = int_literal 4 [template = constants.%.12]
+// CHECK:STDOUT:   %.loc10_32: i32 = int_literal 3 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc10_36: i32 = int_literal 4 [template = constants.%.12]
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%Op]
+// CHECK:STDOUT:   %.loc10_34.1: <bound method> = bound_method %.loc10_32, %.1 [template = constants.%.13]
+// CHECK:STDOUT:   %.loc10_34.2: init i32 = call %.loc10_34.1(%.loc10_32, %.loc10_36) [template = constants.%.14]
+// CHECK:STDOUT:   %.loc10_37.1: (i32, i32, i32) = tuple_literal (%.loc10_26, %.loc10_29, %.loc10_34.2)
+// CHECK:STDOUT:   %.loc10_37.2: i32 = int_literal 0 [template = constants.%.16]
+// CHECK:STDOUT:   %.loc10_37.3: ref i32 = array_index file.%arr.var, %.loc10_37.2
+// CHECK:STDOUT:   %.loc10_37.4: init i32 = initialize_from %.loc10_26 to %.loc10_37.3 [template = constants.%.11]
+// CHECK:STDOUT:   %.loc10_37.5: i32 = int_literal 1 [template = constants.%.17]
+// CHECK:STDOUT:   %.loc10_37.6: ref i32 = array_index file.%arr.var, %.loc10_37.5
+// CHECK:STDOUT:   %.loc10_37.7: init i32 = initialize_from %.loc10_29 to %.loc10_37.6 [template = constants.%.12]
+// CHECK:STDOUT:   %.loc10_37.8: i32 = int_literal 2 [template = constants.%.18]
+// CHECK:STDOUT:   %.loc10_37.9: ref i32 = array_index file.%arr.var, %.loc10_37.8
+// CHECK:STDOUT:   %.loc10_37.10: init i32 = initialize_from %.loc10_34.2 to %.loc10_37.9 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc10_37.11: init [i32; 3] = array_init (%.loc10_37.4, %.loc10_37.7, %.loc10_37.10) to file.%arr.var [template = constants.%.19]
+// CHECK:STDOUT:   %.loc10_37.12: init [i32; 3] = converted %.loc10_37.1, %.loc10_37.11 [template = constants.%.19]
+// CHECK:STDOUT:   assign file.%arr.var, %.loc10_37.12
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 25 - 0
toolchain/check/testdata/function/builtin/definition.carbon

@@ -0,0 +1,25 @@
+// 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
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+
+// CHECK:STDOUT: --- definition.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add: <function> = fn_decl @Add [template] {
+// CHECK:STDOUT:     %a.loc7_8.1: i32 = param a
+// CHECK:STDOUT:     @Add.%a: i32 = bind_name a, %a.loc7_8.1
+// CHECK:STDOUT:     %b.loc7_16.1: i32 = param b
+// CHECK:STDOUT:     @Add.%b: i32 = bind_name b, %b.loc7_16.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 99 - 0
toolchain/check/testdata/function/builtin/fail_redefined.carbon

@@ -0,0 +1,99 @@
+// 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
+
+fn A(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function A.
+// CHECK:STDERR: fn A(n: i32, m: i32) -> i32 { return n; }
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE-4]]:1: Previously defined here.
+// CHECK:STDERR: fn A(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn A(n: i32, m: i32) -> i32 { return n; }
+
+fn B(n: i32, m: i32) -> i32 { return n; }
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function B.
+// CHECK:STDERR: fn B(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE-4]]:1: Previously defined here.
+// CHECK:STDERR: fn B(n: i32, m: i32) -> i32 { return n; }
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn B(n: i32, m: i32) -> i32 = "int.add";
+
+fn C(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function C.
+// CHECK:STDERR: fn C(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_redefined.carbon:[[@LINE-4]]:1: Previously defined here.
+// CHECK:STDERR: fn C(n: i32, m: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn C(n: i32, m: i32) -> i32 = "int.add";
+
+// CHECK:STDOUT: --- fail_redefined.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.loc7
+// CHECK:STDOUT:     .B = %B.loc16
+// CHECK:STDOUT:     .C = %C.loc25
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.loc7: <function> = fn_decl @A [template] {
+// CHECK:STDOUT:     %n.loc7_6.1: i32 = param n
+// CHECK:STDOUT:     @A.%n: i32 = bind_name n, %n.loc7_6.1
+// CHECK:STDOUT:     %m.loc7_14.1: i32 = param m
+// CHECK:STDOUT:     @A.%m: i32 = bind_name m, %m.loc7_14.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.loc14: <function> = fn_decl @A [template] {
+// CHECK:STDOUT:     %n.loc14_6.1: i32 = param n
+// CHECK:STDOUT:     %n.loc14_6.2: i32 = bind_name n, %n.loc14_6.1
+// CHECK:STDOUT:     %m.loc14_14.1: i32 = param m
+// CHECK:STDOUT:     %m.loc14_14.2: i32 = bind_name m, %m.loc14_14.1
+// CHECK:STDOUT:     %return.var.loc14: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.loc16: <function> = fn_decl @B [template] {
+// CHECK:STDOUT:     %n.loc16_6.1: i32 = param n
+// CHECK:STDOUT:     @B.%n: i32 = bind_name n, %n.loc16_6.1
+// CHECK:STDOUT:     %m.loc16_14.1: i32 = param m
+// CHECK:STDOUT:     @B.%m: i32 = bind_name m, %m.loc16_14.1
+// CHECK:STDOUT:     %return.var.loc16: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.loc23: <function> = fn_decl @B [template] {
+// CHECK:STDOUT:     %n.loc23_6.1: i32 = param n
+// CHECK:STDOUT:     %n.loc23_6.2: i32 = bind_name n, %n.loc23_6.1
+// CHECK:STDOUT:     %m.loc23_14.1: i32 = param m
+// CHECK:STDOUT:     %m.loc23_14.2: i32 = bind_name m, %m.loc23_14.1
+// CHECK:STDOUT:     %return.var.loc23: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.loc25: <function> = fn_decl @C [template] {
+// CHECK:STDOUT:     %n.loc25_6.1: i32 = param n
+// CHECK:STDOUT:     @C.%n: i32 = bind_name n, %n.loc25_6.1
+// CHECK:STDOUT:     %m.loc25_14.1: i32 = param m
+// CHECK:STDOUT:     @C.%m: i32 = bind_name m, %m.loc25_14.1
+// CHECK:STDOUT:     %return.var.loc25: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.loc32: <function> = fn_decl @C [template] {
+// CHECK:STDOUT:     %n.loc32_6.1: i32 = param n
+// CHECK:STDOUT:     %n.loc32_6.2: i32 = bind_name n, %n.loc32_6.1
+// CHECK:STDOUT:     %m.loc32_14.1: i32 = param m
+// CHECK:STDOUT:     %m.loc32_14.2: i32 = bind_name m, %m.loc32_14.1
+// CHECK:STDOUT:     %return.var.loc32: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A(%n: i32, %m: i32) -> i32 = "int.add" {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i32 = name_ref n, file.%n.loc14_6.2
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @B(%n: i32, %m: i32) -> i32 = "int.add" {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i32 = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @C(%n: i32, %m: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 22 - 0
toolchain/check/testdata/function/builtin/fail_unknown.carbon

@@ -0,0 +1,22 @@
+// 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
+
+// CHECK:STDERR: fail_unknown.carbon:[[@LINE+3]]:23: ERROR: Unknown builtin function name "unknown.builtin.name".
+// CHECK:STDERR: fn UnknownBuiltin() = "unknown.builtin.name";
+// CHECK:STDERR:                       ^~~~~~~~~~~~~~~~~~~~~~
+fn UnknownBuiltin() = "unknown.builtin.name";
+
+// CHECK:STDOUT: --- fail_unknown.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UnknownBuiltin = %UnknownBuiltin
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %UnknownBuiltin: <function> = fn_decl @UnknownBuiltin [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UnknownBuiltin();
+// CHECK:STDOUT:

+ 91 - 0
toolchain/check/testdata/function/builtin/import.carbon

@@ -0,0 +1,91 @@
+// 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
+
+// --- core.carbon
+
+package Core api;
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+
+// --- use.carbon
+
+import Core;
+
+var arr: [i32; Core.Add(1, 2)] = (1, 2, 3);
+
+// CHECK:STDOUT: --- core.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Add = %Add
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Add: <function> = fn_decl @Add [template] {
+// CHECK:STDOUT:     %a.loc4_8.1: i32 = param a
+// CHECK:STDOUT:     @Add.%a: i32 = bind_name a, %a.loc4_8.1
+// CHECK:STDOUT:     %b.loc4_16.1: i32 = param b
+// CHECK:STDOUT:     @Add.%b: i32 = bind_name b, %b.loc4_16.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.7: type = tuple_type (i32, i32, i32) [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.10: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.11: [i32; 3] = tuple_value (%.1, %.2, %.6) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, %Core [template = %Core]
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir1, inst+6, used [template = imports.%Add]
+// CHECK:STDOUT:   %Add.ref: <function> = name_ref Add, %import_ref [template = imports.%Add]
+// CHECK:STDOUT:   %.loc4_25: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_28: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_24: init i32 = call %Add.ref(%.loc4_25, %.loc4_28) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_30: type = array_type %.loc4_24, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 3] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 3] = bind_name arr, %arr.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Add(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc4_35: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_38: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_41: i32 = int_literal 3 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc4_42.1: (i32, i32, i32) = tuple_literal (%.loc4_35, %.loc4_38, %.loc4_41)
+// CHECK:STDOUT:   %.loc4_42.2: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc4_42.3: ref i32 = array_index file.%arr.var, %.loc4_42.2
+// CHECK:STDOUT:   %.loc4_42.4: init i32 = initialize_from %.loc4_35 to %.loc4_42.3 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_42.5: i32 = int_literal 1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc4_42.6: ref i32 = array_index file.%arr.var, %.loc4_42.5
+// CHECK:STDOUT:   %.loc4_42.7: init i32 = initialize_from %.loc4_38 to %.loc4_42.6 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_42.8: i32 = int_literal 2 [template = constants.%.10]
+// CHECK:STDOUT:   %.loc4_42.9: ref i32 = array_index file.%arr.var, %.loc4_42.8
+// CHECK:STDOUT:   %.loc4_42.10: init i32 = initialize_from %.loc4_41 to %.loc4_42.9 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc4_42.11: init [i32; 3] = array_init (%.loc4_42.4, %.loc4_42.7, %.loc4_42.10) to file.%arr.var [template = constants.%.11]
+// CHECK:STDOUT:   %.loc4_42.12: init [i32; 3] = converted %.loc4_42.1, %.loc4_42.11 [template = constants.%.11]
+// CHECK:STDOUT:   assign file.%arr.var, %.loc4_42.12
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 97 - 0
toolchain/check/testdata/function/builtin/method.carbon

@@ -0,0 +1,97 @@
+// 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
+
+interface I {
+  fn F[self: Self](other: Self) -> Self;
+}
+
+impl i32 as I {
+  fn F[self: i32](other: i32) -> i32 = "int.add";
+}
+
+var arr: [i32; 1.(I.F)(2)];
+
+// CHECK:STDOUT: --- method.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @I, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in I> = assoc_entity element0, @I.%F [template]
+// CHECK:STDOUT:   %.4: <witness> = interface_witness (@impl.%F) [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.6: <bound method> = bound_method %.5, @impl.%F [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.9: type = array_type %.8, i32 [template]
+// CHECK:STDOUT:   %.10: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %I.ref.loc11: type = name_ref I, %I.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc15_16: i32 = int_literal 1 [template = constants.%.5]
+// CHECK:STDOUT:   %I.ref.loc15: type = name_ref I, %I.decl [template = constants.%.1]
+// CHECK:STDOUT:   %F.ref: <associated <function> in I> = name_ref F, @I.%.loc8_40 [template = constants.%.3]
+// CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%F]
+// CHECK:STDOUT:   %.loc15_17: <bound method> = bound_method %.loc15_16, %.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc15_24: i32 = int_literal 2 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc15_23: init i32 = call %.loc15_17(%.loc15_16, %.loc15_24) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc15_26: type = array_type %.loc15_23, i32 [template = constants.%.9]
+// CHECK:STDOUT:   %arr.var: ref [i32; 3] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 3] = bind_name arr, %arr.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {
+// CHECK:STDOUT:     %Self.ref.loc8_14: I = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_14.1: type = facet_type_access %Self.ref.loc8_14 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_14.2: type = converted %Self.ref.loc8_14, %.loc8_14.1 [symbolic = %Self]
+// CHECK:STDOUT:     %self.loc8_8.1: Self = param self
+// CHECK:STDOUT:     %self.loc8_8.2: Self = bind_name self, %self.loc8_8.1
+// CHECK:STDOUT:     %Self.ref.loc8_27: I = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_27.1: type = facet_type_access %Self.ref.loc8_27 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_27.2: type = converted %Self.ref.loc8_27, %.loc8_27.1 [symbolic = %Self]
+// CHECK:STDOUT:     %other.loc8_20.1: Self = param other
+// CHECK:STDOUT:     %other.loc8_20.2: Self = bind_name other, %other.loc8_20.1
+// CHECK:STDOUT:     %Self.ref.loc8_36: I = name_ref Self, %Self [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_36.1: type = facet_type_access %Self.ref.loc8_36 [symbolic = %Self]
+// CHECK:STDOUT:     %.loc8_36.2: type = converted %Self.ref.loc8_36, %.loc8_36.1 [symbolic = %Self]
+// CHECK:STDOUT:     %return.var: ref Self = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8_40: <associated <function> in I> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc8_40
+// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: i32 as I {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %self.loc12_8.1: i32 = param self
+// CHECK:STDOUT:     %self.loc12_8.2: i32 = bind_name self, %self.loc12_8.1
+// CHECK:STDOUT:     %other.loc12_19.1: i32 = param other
+// CHECK:STDOUT:     %other.loc12_19.2: i32 = bind_name other, %other.loc12_19.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F) [template = constants.%.4]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1[@I.%self.loc8_8.2: Self](@I.%other.loc8_20.2: Self) -> Self;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2[@impl.%self.loc12_8.2: i32](@impl.%other.loc12_19.2: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 6 - 6
toolchain/check/testdata/function/call/fail_param_count.carbon

@@ -103,22 +103,22 @@ fn Main() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Run0.ref.loc18: <function> = name_ref Run0, file.%Run0 [template = file.%Run0]
 // CHECK:STDOUT:   %.loc18_8: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc18_7: init () = call %Run0.ref.loc18(<invalid>)
+// CHECK:STDOUT:   %.loc18_7: init () = call %Run0.ref.loc18(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Run0.ref.loc25: <function> = name_ref Run0, file.%Run0 [template = file.%Run0]
 // CHECK:STDOUT:   %.loc25_8: i32 = int_literal 0 [template = constants.%.3]
 // CHECK:STDOUT:   %.loc25_11: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc25_7: init () = call %Run0.ref.loc25(<invalid>)
+// CHECK:STDOUT:   %.loc25_7: init () = call %Run0.ref.loc25(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Run1.ref.loc33: <function> = name_ref Run1, file.%Run1 [template = file.%Run1]
-// CHECK:STDOUT:   %.loc33: init () = call %Run1.ref.loc33(<invalid>)
+// CHECK:STDOUT:   %.loc33: init () = call %Run1.ref.loc33(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Run1.ref.loc40: <function> = name_ref Run1, file.%Run1 [template = file.%Run1]
 // CHECK:STDOUT:   %.loc40_8: i32 = int_literal 0 [template = constants.%.3]
 // CHECK:STDOUT:   %.loc40_11: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc40_7: init () = call %Run1.ref.loc40(<invalid>)
+// CHECK:STDOUT:   %.loc40_7: init () = call %Run1.ref.loc40(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Run2.ref.loc48: <function> = name_ref Run2, file.%Run2 [template = file.%Run2]
-// CHECK:STDOUT:   %.loc48: init () = call %Run2.ref.loc48(<invalid>)
+// CHECK:STDOUT:   %.loc48: init () = call %Run2.ref.loc48(<invalid>) [template = <error>]
 // CHECK:STDOUT:   %Run2.ref.loc55: <function> = name_ref Run2, file.%Run2 [template = file.%Run2]
 // CHECK:STDOUT:   %.loc55_8: i32 = int_literal 0 [template = constants.%.3]
-// CHECK:STDOUT:   %.loc55_7: init () = call %Run2.ref.loc55(<invalid>)
+// CHECK:STDOUT:   %.loc55_7: init () = call %Run2.ref.loc55(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/function/call/fail_param_type.carbon

@@ -44,7 +44,7 @@ fn F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %G.ref: <function> = name_ref G, file.%G [template = file.%G]
 // CHECK:STDOUT:   %.loc16_5: f64 = real_literal 10e-1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc16_4: init () = call %G.ref(<invalid>)
+// CHECK:STDOUT:   %.loc16_4: init () = call %G.ref(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 23 - 17
toolchain/check/testdata/interface/fail_todo_define_out_of_line.carbon → toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon

@@ -5,26 +5,24 @@
 // AUTOUPDATE
 
 interface Interface {
-  // CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
-  // CHECK:STDERR:   default fn F();
+  // CHECK:STDERR: fail_todo_define_default_fn_inline.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
+  // CHECK:STDERR:   default fn F() {}
   // CHECK:STDERR:   ^~~~~~~
-  default fn F();
-}
+  default fn F() {}
 
-// CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
-// CHECK:STDERR: fn Interface.F() {}
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
-// CHECK:STDERR: fail_todo_define_out_of_line.carbon:[[@LINE-6]]:3: Name is previously declared here.
-// CHECK:STDERR:   default fn F();
-// CHECK:STDERR:   ^~~~~~~~~~~~~~~
-fn Interface.F() {}
+  // CHECK:STDERR: fail_todo_define_default_fn_inline.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
+  // CHECK:STDERR:   default fn G(a: i32, b: i32) -> i32 = "int.add";
+  // CHECK:STDERR:   ^~~~~~~
+  default fn G(a: i32, b: i32) -> i32 = "int.add";
+}
 
-// CHECK:STDOUT: --- fail_todo_define_out_of_line.carbon
+// CHECK:STDOUT: --- fail_todo_define_default_fn_inline.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
 // CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
 // CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
+// CHECK:STDOUT:   %.4: <associated <function> in Interface> = assoc_entity element1, @Interface.%G [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -32,24 +30,32 @@ fn Interface.F() {}
 // CHECK:STDOUT:     .Interface = %Interface.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Interface.decl: type = interface_decl @Interface [template = constants.%.1] {}
-// CHECK:STDOUT:   %.loc20: <function> = fn_decl @.1 [template] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @Interface {
 // CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {}
 // CHECK:STDOUT:   %.loc11: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     %a.loc16_16.2: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     %b.loc16_24.2: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc16: <associated <function> in Interface> = assoc_entity element1, %G [template = constants.%.4]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .F = %.loc11
-// CHECK:STDOUT:   witness = (%F)
+// CHECK:STDOUT:   .G = %.loc16
+// CHECK:STDOUT:   witness = (%F, %G)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(@Interface.%a.loc16_16.2: i32, @Interface.%b.loc16_24.2: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 89 - 0
toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon

@@ -0,0 +1,89 @@
+// 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
+
+interface Interface {
+  // CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
+  // CHECK:STDERR:   default fn F();
+  // CHECK:STDERR:   ^~~~~~~
+  default fn F();
+
+  // CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `interface modifier`.
+  // CHECK:STDERR:   default fn G(a: i32, b: i32) -> i32;
+  // CHECK:STDERR:   ^~~~~~~
+  default fn G(a: i32, b: i32) -> i32;
+}
+
+// CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fn Interface.F() {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE-11]]:3: Name is previously declared here.
+// CHECK:STDERR:   default fn F();
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~
+fn Interface.F() {}
+
+// CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fn Interface.G(a: i32, b: i32) -> i32 = "int.add";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_define_default_fn_out_of_line.carbon:[[@LINE-14]]:3: Name is previously declared here.
+// CHECK:STDERR:   default fn G(a: i32, b: i32) -> i32;
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn Interface.G(a: i32, b: i32) -> i32 = "int.add";
+
+// CHECK:STDOUT: --- fail_todo_define_default_fn_out_of_line.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Interface, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in Interface> = assoc_entity element0, @Interface.%F [template]
+// CHECK:STDOUT:   %.4: <associated <function> in Interface> = assoc_entity element1, @Interface.%G [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Interface = %Interface.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Interface.decl: type = interface_decl @Interface [template = constants.%.1] {}
+// CHECK:STDOUT:   %.loc25: <function> = fn_decl @.1 [template] {}
+// CHECK:STDOUT:   %.loc33: <function> = fn_decl @.2 [template] {
+// CHECK:STDOUT:     %a.loc33_16.1: i32 = param a
+// CHECK:STDOUT:     @.2.%a: i32 = bind_name a, %a.loc33_16.1
+// CHECK:STDOUT:     %b.loc33_24.1: i32 = param b
+// CHECK:STDOUT:     @.2.%b: i32 = bind_name b, %b.loc33_24.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %Self: Interface = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {}
+// CHECK:STDOUT:   %.loc11: <associated <function> in Interface> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     %a.loc16_16.2: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     %b.loc16_24.2: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc16: <associated <function> in Interface> = assoc_entity element1, %G [template = constants.%.4]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc11
+// CHECK:STDOUT:   .G = %.loc16
+// CHECK:STDOUT:   witness = (%F, %G)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(@Interface.%a.loc16_16.2: i32, @Interface.%b.loc16_24.2: i32) -> i32;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.2(%a: i32, %b: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 76 - 0
toolchain/check/testdata/interface/todo_define_not_default.carbon

@@ -0,0 +1,76 @@
+// 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
+
+interface I {
+  // TODO: A definition without `default` in an interface should be rejected.
+  fn F() {}
+  fn G(a: i32, b: i32) -> i32 = "int.add";
+
+  // TODO: An associated constant with an initializer without `default` in an
+  // interface should be rejected.
+  let T:! type = (i32, i32);
+  let N:! i32 = 42;
+}
+
+// CHECK:STDOUT: --- todo_define_not_default.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @I, <function> [template]
+// CHECK:STDOUT:   %.3: <associated <function> in I> = assoc_entity element0, @I.%F [template]
+// CHECK:STDOUT:   %.4: <associated <function> in I> = assoc_entity element1, @I.%G [template]
+// CHECK:STDOUT:   %.5: type = tuple_type (type, type) [template]
+// CHECK:STDOUT:   %.6: type = tuple_type (i32, i32) [template]
+// CHECK:STDOUT:   %.7: type = assoc_entity_type @I, type [template]
+// CHECK:STDOUT:   %.8: <associated type in I> = assoc_entity element2, @I.%T [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal 42 [template]
+// CHECK:STDOUT:   %.10: type = assoc_entity_type @I, i32 [template]
+// CHECK:STDOUT:   %.11: <associated i32 in I> = assoc_entity element3, @I.%N [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {}
+// CHECK:STDOUT:   %.loc9: <associated <function> in I> = assoc_entity element0, %F [template = constants.%.3]
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %a.loc10_8.1: i32 = param a
+// CHECK:STDOUT:     %a.loc10_8.2: i32 = bind_name a, %a.loc10_8.1
+// CHECK:STDOUT:     %b.loc10_16.1: i32 = param b
+// CHECK:STDOUT:     %b.loc10_16.2: i32 = bind_name b, %b.loc10_16.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc10: <associated <function> in I> = assoc_entity element1, %G [template = constants.%.4]
+// CHECK:STDOUT:   %.loc14_27.1: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc14_27.2: type = converted %.loc14_27.1, constants.%.6 [template = constants.%.6]
+// CHECK:STDOUT:   %T: type = assoc_const_decl T [template]
+// CHECK:STDOUT:   %.loc14_28: <associated type in I> = assoc_entity element2, %T [template = constants.%.8]
+// CHECK:STDOUT:   %.loc15_17: i32 = int_literal 42 [template = constants.%.9]
+// CHECK:STDOUT:   %N: i32 = assoc_const_decl N [template]
+// CHECK:STDOUT:   %.loc15_19: <associated i32 in I> = assoc_entity element3, %N [template = constants.%.11]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .F = %.loc9
+// CHECK:STDOUT:   .G = %.loc10
+// CHECK:STDOUT:   .T = %.loc14_28
+// CHECK:STDOUT:   .N = %.loc15_19
+// CHECK:STDOUT:   witness = (%F, %G, %T, %N)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(@I.%a.loc10_8.2: i32, @I.%b.loc10_16.2: i32) -> i32 = "int.add";
+// CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/operators/overloaded/eq.carbon

@@ -476,9 +476,9 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   %b.ref: D = name_ref b, %b
 // CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.%.1, element0 [template = @impl.%Equal]
 // CHECK:STDOUT:   %.loc21_12.1: <bound method> = bound_method %a.ref, %.1
-// CHECK:STDOUT:   %.loc21_12.2: init bool = call %.loc21_12.1(<invalid>)
-// CHECK:STDOUT:   %.loc21_16: bool = value_of_initializer %.loc21_12.2
-// CHECK:STDOUT:   %.loc21_12.3: bool = converted %.loc21_12.2, %.loc21_16
+// CHECK:STDOUT:   %.loc21_12.2: init bool = call %.loc21_12.1(<invalid>) [template = <error>]
+// CHECK:STDOUT:   %.loc21_16: bool = value_of_initializer %.loc21_12.2 [template = <error>]
+// CHECK:STDOUT:   %.loc21_12.3: bool = converted %.loc21_12.2, %.loc21_16 [template = <error>]
 // CHECK:STDOUT:   return %.loc21_12.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon

@@ -252,7 +252,7 @@ fn TestAddAssignNonRef(a: C, b: C) {
 // CHECK:STDOUT:   %a.ref: C = name_ref a, %a
 // CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.1.%.1, element0 [template = @impl.1.%Op]
 // CHECK:STDOUT:   %.loc22_3.1: <bound method> = bound_method %a.ref, %.1
-// CHECK:STDOUT:   %.loc22_3.2: init () = call %.loc22_3.1(<invalid>)
+// CHECK:STDOUT:   %.loc22_3.2: init () = call %.loc22_3.1(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -262,7 +262,7 @@ fn TestAddAssignNonRef(a: C, b: C) {
 // CHECK:STDOUT:   %b.ref: C = name_ref b, %b
 // CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.2.%.1, element0 [template = @impl.2.%Op]
 // CHECK:STDOUT:   %.loc32_5.1: <bound method> = bound_method %a.ref, %.1
-// CHECK:STDOUT:   %.loc32_5.2: init () = call %.loc32_5.1(<invalid>)
+// CHECK:STDOUT:   %.loc32_5.2: init () = call %.loc32_5.1(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon

@@ -275,7 +275,7 @@ fn TestAssign(b: D) {
 // CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.1.%.1, element0 [template = @impl.1.%Op]
 // CHECK:STDOUT:   %.loc23_12.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc23_12.2: ref C = temporary_storage
-// CHECK:STDOUT:   %.loc23_12.3: init C = call %.loc23_12.1(<invalid>)
+// CHECK:STDOUT:   %.loc23_12.3: init C = call %.loc23_12.1(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return %.loc23_12.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -293,7 +293,7 @@ fn TestAssign(b: D) {
 // CHECK:STDOUT:   %.1: <function> = interface_witness_access @impl.2.%.1, element0 [template = @impl.2.%Op]
 // CHECK:STDOUT:   %.loc34_5.1: <bound method> = bound_method %a.ref, %.1
 // CHECK:STDOUT:   %.loc34_3: C* = addr_of %a.ref
-// CHECK:STDOUT:   %.loc34_5.2: init () = call %.loc34_5.1(<invalid>)
+// CHECK:STDOUT:   %.loc34_5.2: init () = call %.loc34_5.1(<invalid>) [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -114,6 +114,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedDeclSemiOrDefinition)
 CARBON_DIAGNOSTIC_KIND(ParamsRequiredAfterImplicit)
 CARBON_DIAGNOSTIC_KIND(ParamsRequiredByIntroducer)
 CARBON_DIAGNOSTIC_KIND(ExpectedAfterBase)
+CARBON_DIAGNOSTIC_KIND(ExpectedBuiltinName)
 CARBON_DIAGNOSTIC_KIND(ImplExpectedAfterForall)
 CARBON_DIAGNOSTIC_KIND(ImplExpectedAs)
 
@@ -170,6 +171,7 @@ CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePrevious)
 CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePreviousNoReturn)
 CARBON_DIAGNOSTIC_KIND(InvalidMainRunSignature)
 CARBON_DIAGNOSTIC_KIND(MissingReturnStatement)
+CARBON_DIAGNOSTIC_KIND(UnknownBuiltinFunctionName)
 
 // Class checking.
 CARBON_DIAGNOSTIC_KIND(BaseIsFinal)
@@ -232,6 +234,7 @@ CARBON_DIAGNOSTIC_KIND(ArrayInitFromExprArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(ArrowOperatorOfNonPointer)
 CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable)
 CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
+CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerOverflow)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType)
 CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer)

+ 6 - 0
toolchain/lower/file_context.cpp

@@ -10,6 +10,7 @@
 #include "toolchain/lower/function_context.h"
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -95,6 +96,11 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id)
     return nullptr;
   }
 
+  // Don't lower builtins.
+  if (function.builtin_kind != SemIR::BuiltinFunctionKind::None) {
+    return nullptr;
+  }
+
   const bool has_return_slot = function.return_slot_id.is_valid();
   auto implicit_param_refs =
       sem_ir().inst_blocks().Get(function.implicit_param_refs_id);

+ 1 - 2
toolchain/lower/file_context.h

@@ -23,9 +23,8 @@ class FileContext {
   // the main execution loop.
   auto Run() -> std::unique_ptr<llvm::Module>;
 
-  // Gets a callable's function.
+  // Gets a callable's function. Returns nullptr for a builtin.
   auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* {
-    CARBON_CHECK(functions_[function_id.index] != nullptr) << function_id;
     return functions_[function_id.index];
   }
 

+ 69 - 3
toolchain/lower/handle.cpp

@@ -11,6 +11,7 @@
 #include "llvm/IR/Value.h"
 #include "llvm/Support/Casting.h"
 #include "toolchain/lower/function_context.h"
+#include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -166,14 +167,79 @@ auto HandleBuiltin(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
   CARBON_FATAL() << "TODO: Add support: " << inst;
 }
 
+// Returns the builtin function kind of the callee in a function call, or None
+// if the call is not to a builtin.
+static auto GetCalleeBuiltinFunctionKind(const SemIR::File& sem_ir,
+                                         SemIR::InstId callee_id)
+    -> SemIR::BuiltinFunctionKind {
+  if (auto bound_method =
+          sem_ir.insts().TryGetAs<SemIR::BoundMethod>(callee_id)) {
+    callee_id = bound_method->function_id;
+  }
+  callee_id = sem_ir.constant_values().Get(callee_id).inst_id();
+  if (!callee_id.is_valid()) {
+    return SemIR::BuiltinFunctionKind::None;
+  }
+  if (auto callee = sem_ir.insts().TryGetAs<SemIR::FunctionDecl>(callee_id)) {
+    const auto& function = sem_ir.functions().Get(callee->function_id);
+    return function.builtin_kind;
+  }
+  return SemIR::BuiltinFunctionKind::None;
+}
+
+// Handles a call to a builtin function.
+static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
+                              SemIR::BuiltinFunctionKind builtin_kind,
+                              llvm::ArrayRef<SemIR::InstId> arg_ids) -> void {
+  switch (builtin_kind) {
+    case SemIR::BuiltinFunctionKind::None:
+      CARBON_FATAL() << "No callee in function call.";
+
+    case SemIR::BuiltinFunctionKind::IntAdd: {
+      // TODO: Move type checking to the point where we make the call.
+      if (arg_ids.size() != 2) {
+        break;
+      }
+      auto lhs_type = context.sem_ir().insts().Get(arg_ids[0]).type_id();
+      auto rhs_type = context.sem_ir().insts().Get(arg_ids[1]).type_id();
+      auto result_type = context.sem_ir().insts().Get(inst_id).type_id();
+      if (lhs_type != rhs_type || lhs_type != result_type ||
+          context.sem_ir().types().GetInstId(lhs_type) !=
+              SemIR::InstId::BuiltinIntType) {
+        break;
+      }
+      constexpr bool SignedOverflowIsUB = false;
+      context.SetLocal(inst_id, context.builder().CreateAdd(
+                                    context.GetValue(arg_ids[0]),
+                                    context.GetValue(arg_ids[1]), "add",
+                                    /*HasNUW=*/false,
+                                    /*HasNSW=*/SignedOverflowIsUB));
+      return;
+    }
+  }
+
+  CARBON_FATAL() << "Unsupported builtin call.";
+}
+
 auto HandleCall(FunctionContext& context, SemIR::InstId inst_id,
                 SemIR::Call inst) -> void {
-  auto* callee = llvm::cast<llvm::Function>(context.GetValue(inst.callee_id));
-
-  std::vector<llvm::Value*> args;
   llvm::ArrayRef<SemIR::InstId> arg_ids =
       context.sem_ir().inst_blocks().Get(inst.args_id);
 
+  auto* callee_value = context.GetValue(inst.callee_id);
+
+  // A null callee pointer value indicates this isn't a real function.
+  if (!callee_value) {
+    auto builtin_kind =
+        GetCalleeBuiltinFunctionKind(context.sem_ir(), inst.callee_id);
+    HandleBuiltinCall(context, inst_id, builtin_kind, arg_ids);
+    return;
+  }
+
+  auto* callee = llvm::cast<llvm::Function>(callee_value);
+
+  std::vector<llvm::Value*> args;
+
   if (SemIR::GetInitRepr(context.sem_ir(), inst.type_id).has_return_slot()) {
     args.push_back(context.GetValue(arg_ids.back()));
     arg_ids = arg_ids.drop_back();

+ 16 - 0
toolchain/lower/testdata/builtins/int.carbon

@@ -0,0 +1,16 @@
+// 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
+
+fn Add(a: i32, b: i32) -> i32 = "int.add";
+fn TestAdd(a: i32, b: i32) -> i32 { return Add(a, b); }
+
+// CHECK:STDOUT: ; ModuleID = 'int.carbon'
+// CHECK:STDOUT: source_filename = "int.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestAdd(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %add = add i32 %a, %b
+// CHECK:STDOUT:   ret i32 %add
+// CHECK:STDOUT: }

+ 24 - 0
toolchain/lower/testdata/builtins/method_vs_nonmethod.carbon

@@ -0,0 +1,24 @@
+// 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
+
+fn AddNonmethod(a: i32, b: i32) -> i32 = "int.add";
+fn AddMethod[self: i32](b: i32) -> i32 = "int.add";
+
+fn TestAddNonmethod(a: i32, b: i32) -> i32 { return AddNonmethod(a, b); }
+fn TestAddMethod(a: i32, b: i32) -> i32 { return a.(AddMethod)(b); }
+
+// CHECK:STDOUT: ; ModuleID = 'method_vs_nonmethod.carbon'
+// CHECK:STDOUT: source_filename = "method_vs_nonmethod.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestAddNonmethod(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %add = add i32 %a, %b
+// CHECK:STDOUT:   ret i32 %add
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestAddMethod(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %add = add i32 %a, %b
+// CHECK:STDOUT:   ret i32 %add
+// CHECK:STDOUT: }

+ 41 - 0
toolchain/lower/testdata/builtins/overloaded_operator.carbon

@@ -0,0 +1,41 @@
+// 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
+
+// --- prelude.carbon
+
+package Core api;
+
+interface Add {
+  fn Op[self: Self](other: Self) -> Self;
+}
+
+// --- user.carbon
+
+import Core;
+
+// TODO: Move this into prelude.carbon when possible. For now, impl lookup only
+// looks in the current file.
+impl i32 as Core.Add {
+  fn Op[self: i32](other: i32) -> i32 = "int.add";
+}
+
+fn AddThreeIntegers(a: i32, b: i32, c: i32) -> i32 {
+  return a + b + c;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'prelude.carbon'
+// CHECK:STDOUT: source_filename = "prelude.carbon"
+// CHECK:STDOUT: ; ModuleID = 'user.carbon'
+// CHECK:STDOUT: source_filename = "user.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @AddThreeIntegers(i32 %a, i32 %b, i32 %c) {
+// CHECK:STDOUT:   %add = add i32 %a, %b
+// CHECK:STDOUT:   %temp = alloca i32, align 4
+// CHECK:STDOUT:   store i32 %add, ptr %temp, align 4
+// CHECK:STDOUT:   %1 = load i32, ptr %temp, align 4
+// CHECK:STDOUT:   %add1 = add i32 %1, %c
+// CHECK:STDOUT:   ret i32 %add1
+// CHECK:STDOUT: }

+ 25 - 16
toolchain/parse/extract.cpp

@@ -128,39 +128,48 @@ struct Extractable<NodeIdInCategory<Category>> {
   }
 };
 
-static auto NodeIdForKindAccept(NodeKind kind1, NodeKind kind2,
-                                const Tree* tree,
-                                const Tree::SiblingIterator& it,
-                                Tree::SiblingIterator end, ErrorBuilder* trace)
+static auto NodeIdOneOfAccept(std::initializer_list<NodeKind> kinds,
+                              const Tree* tree, const Tree::SiblingIterator& it,
+                              Tree::SiblingIterator end, ErrorBuilder* trace)
     -> bool {
+  auto trace_kinds = [&] {
+    llvm::ListSeparator sep(" or ");
+    for (auto kind : kinds) {
+      *trace << sep << kind;
+    }
+  };
   auto kind = tree->node_kind(*it);
-  if (it == end || (kind != kind1 && kind != kind2)) {
+  if (it == end || std::find(kinds.begin(), kinds.end(), kind) == kinds.end()) {
     if (trace) {
       if (it == end) {
-        *trace << "NodeIdOneOf error: no more children, expected " << kind1
-               << " or " << kind2 << "\n";
+        *trace << "NodeIdOneOf error: no more children, expected ";
+        trace_kinds();
+        *trace << "\n";
       } else {
         *trace << "NodeIdOneOf error: wrong kind " << tree->node_kind(*it)
-               << ", expected " << kind1 << " or " << kind2 << "\n";
+               << ", expected ";
+        trace_kinds();
+        *trace << "\n";
       }
     }
     return false;
   }
   if (trace) {
-    *trace << "NodeIdOneOf " << kind1 << " or " << kind2 << ": "
-           << tree->node_kind(*it) << " consumed\n";
+    *trace << "NodeIdOneOf ";
+    trace_kinds();
+    *trace << ": " << tree->node_kind(*it) << " consumed\n";
   }
   return true;
 }
 
-// Extract a `NodeIdOneOf<T, U>` as a single required child.
-template <typename T, typename U>
-struct Extractable<NodeIdOneOf<T, U>> {
+// Extract a `NodeIdOneOf<T...>` as a single required child.
+template <typename... T>
+struct Extractable<NodeIdOneOf<T...>> {
   static auto Extract(const Tree* tree, Tree::SiblingIterator& it,
                       Tree::SiblingIterator end, ErrorBuilder* trace)
-      -> std::optional<NodeIdOneOf<T, U>> {
-    if (NodeIdForKindAccept(T::Kind, U::Kind, tree, it, end, trace)) {
-      return NodeIdOneOf<T, U>(*it++);
+      -> std::optional<NodeIdOneOf<T...>> {
+    if (NodeIdOneOfAccept({T::Kind...}, tree, it, end, trace)) {
+      return NodeIdOneOf<T...>(*it++);
     } else {
       return std::nullopt;
     }

+ 24 - 0
toolchain/parse/handle_function.cpp

@@ -52,6 +52,30 @@ auto HandleFunctionSignatureFinish(Context& context) -> void {
       context.PushState(State::StatementScopeLoop);
       break;
     }
+    case Lex::TokenKind::Equal: {
+      context.AddNode(NodeKind::BuiltinFunctionDefinitionStart,
+                      context.Consume(), state.subtree_start, state.has_error);
+      if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::StringLiteral,
+                                           NodeKind::BuiltinName)) {
+        CARBON_DIAGNOSTIC(ExpectedBuiltinName, Error,
+                          "Expected builtin function name after `=`.");
+        context.emitter().Emit(*context.position(), ExpectedBuiltinName);
+        state.has_error = true;
+      }
+      auto semi = context.ConsumeIf(Lex::TokenKind::Semi);
+      if (!semi && !state.has_error) {
+        context.EmitExpectedDeclSemi(context.tokens().GetKind(state.token));
+        state.has_error = true;
+      }
+      if (state.has_error) {
+        context.RecoverFromDeclError(state, NodeKind::BuiltinFunctionDefinition,
+                                     /*skip_past_likely_end=*/true);
+      } else {
+        context.AddNode(NodeKind::BuiltinFunctionDefinition, *semi,
+                        state.subtree_start, state.has_error);
+      }
+      break;
+    }
     default: {
       if (!state.has_error) {
         context.EmitExpectedDeclSemiOrDefinition(Lex::TokenKind::Fn);

+ 6 - 5
toolchain/parse/node_ids.h

@@ -77,14 +77,15 @@ using AnyNameComponentId = NodeIdInCategory<NodeCategory::NameComponent>;
 using AnyPatternId = NodeIdInCategory<NodeCategory::Pattern>;
 using AnyStatementId = NodeIdInCategory<NodeCategory::Statement>;
 
-// NodeId with kind that matches either T::Kind or U::Kind.
-template <typename T, typename U>
+// NodeId with kind that matches one of the `T::Kind`s.
+template <typename... T>
 struct NodeIdOneOf : public NodeId {
+  static_assert(sizeof...(T) >= 2, "Expected at least two types.");
   constexpr explicit NodeIdOneOf(NodeId node_id) : NodeId(node_id) {}
   template <const NodeKind& Kind>
   // NOLINTNEXTLINE(google-explicit-constructor)
   NodeIdOneOf(NodeIdForKind<Kind> node_id) : NodeId(node_id) {
-    static_assert(T::Kind == Kind || U::Kind == Kind);
+    static_assert(((T::Kind == Kind) || ...));
   }
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr NodeIdOneOf(InvalidNodeId /*invalid*/)
@@ -92,8 +93,8 @@ struct NodeIdOneOf : public NodeId {
 };
 
 using AnyClassDeclId = NodeIdOneOf<ClassDeclId, ClassDefinitionStartId>;
-using AnyFunctionDeclId =
-    NodeIdOneOf<FunctionDeclId, FunctionDefinitionStartId>;
+using AnyFunctionDeclId = NodeIdOneOf<FunctionDeclId, FunctionDefinitionStartId,
+                                      BuiltinFunctionDefinitionStartId>;
 using AnyImplDeclId = NodeIdOneOf<ImplDeclId, ImplDefinitionStartId>;
 using AnyInterfaceDeclId =
     NodeIdOneOf<InterfaceDeclId, InterfaceDefinitionStartId>;

+ 27 - 11
toolchain/parse/node_kind.def

@@ -232,21 +232,31 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(CodeBlockStart, 0,
 CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart,
                                CARBON_IF_VALID(CloseCurlyBrace))
 
-// `fn`:
-//     FunctionIntroducer
-//     _repeated_ _external_: modifier
-//     _external_: IdentifierName or QualifiedName
-//     _optional_ _external_: ImplicitParamList
-//     _external_: TuplePattern
-//       _external_: type expression
-//     ReturnType
+// `fn` declarations start with a function signature:
+//
+//   FunctionIntroducer
+//   _repeated_ _external_: modifier
+//   _external_: IdentifierName or QualifiedName
+//   _optional_ _external_: ImplicitParamList
+//   _external_: TuplePattern
+//     _external_: type expression
+//   ReturnType
+// _function signature_
+//
+// There are three forms of function declaration:
+//
+//   _function signature_
+// FunctionDecl
+//
+//     _function signature_
 //   FunctionDefinitionStart
 //   _repeated_ _external_: statement
 // FunctionDefinition
 //
-// The above is the structure for a definition; for a declaration,
-// FunctionDefinitionStart and later nodes are removed and replaced by
-// FunctionDecl.
+//     _function signature_
+//   BuiltinFunctionDefinitionStart
+//   BuiltinName
+// BuiltinFunctionDefinition
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(FunctionIntroducer, 0, Fn)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnType, 1, MinusGreater)
 CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinitionStart, FunctionIntroducer,
@@ -255,6 +265,12 @@ CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinition, FunctionDefinitionStart,
                                CloseCurlyBrace)
 CARBON_PARSE_NODE_KIND_BRACKET(FunctionDecl, FunctionIntroducer,
                                CARBON_IF_VALID(Semi))
+CARBON_PARSE_NODE_KIND_BRACKET(BuiltinFunctionDefinitionStart,
+                               FunctionIntroducer, Equal)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(BuiltinName, 0, StringLiteral)
+CARBON_PARSE_NODE_KIND_BRACKET(BuiltinFunctionDefinition,
+                               BuiltinFunctionDefinitionStart,
+                               CARBON_IF_VALID(Semi))
 
 // `alias`:
 //   AliasIntroducer

+ 37 - 0
toolchain/parse/testdata/function/definition/builtin.carbon

@@ -0,0 +1,37 @@
+// 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
+
+fn F() = "builtin";
+
+impl T as I {
+  fn Op() = "builtin";
+}
+
+// CHECK:STDOUT: - filename: builtin.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'BuiltinFunctionDefinitionStart', text: '=', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'BuiltinName', text: '"builtin"'},
+// CHECK:STDOUT:     {kind: 'BuiltinFunctionDefinition', text: ';', subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'ImplIntroducer', text: 'impl'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'T'},
+// CHECK:STDOUT:         {kind: 'TypeImplAs', text: 'as', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'I'},
+// CHECK:STDOUT:       {kind: 'ImplDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'Op'},
+// CHECK:STDOUT:             {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'BuiltinFunctionDefinitionStart', text: '=', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'BuiltinName', text: '"builtin"'},
+// CHECK:STDOUT:       {kind: 'BuiltinFunctionDefinition', text: ';', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'ImplDefinition', text: '}', subtree_size: 13},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 50 - 0
toolchain/parse/testdata/function/definition/fail_builtin.carbon

@@ -0,0 +1,50 @@
+// 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
+
+// CHECK:STDERR: fail_builtin.carbon:[[@LINE+3]]:18: ERROR: Expected builtin function name after `=`.
+// CHECK:STDERR: fn NotString() = banana;
+// CHECK:STDERR:                  ^~~~~~
+fn NotString() = banana;
+// CHECK:STDERR: fail_builtin.carbon:[[@LINE+3]]:32: ERROR: `fn` declarations must end with a `;`.
+// CHECK:STDERR: fn JunkAfterString() = "hello" "world";
+// CHECK:STDERR:                                ^~~~~~~
+fn JunkAfterString() = "hello" "world";
+
+// CHECK:STDERR: fail_builtin.carbon:[[@LINE+3]]:23: ERROR: Expected builtin function name after `=`.
+// CHECK:STDERR: fn SpuriousEquals() = { }
+// CHECK:STDERR:                       ^
+fn SpuriousEquals() = { }
+fn TestRecoveryFromSpuriousEquals();
+
+// CHECK:STDOUT: - filename: fail_builtin.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'NotString'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'BuiltinFunctionDefinitionStart', text: '=', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'BuiltinFunctionDefinition', text: ';', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'JunkAfterString'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'BuiltinFunctionDefinitionStart', text: '=', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'BuiltinName', text: '"hello"'},
+// CHECK:STDOUT:     {kind: 'BuiltinFunctionDefinition', text: ';', has_error: yes, subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'SpuriousEquals'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'BuiltinFunctionDefinitionStart', text: '=', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'BuiltinFunctionDefinition', text: '}', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'TestRecoveryFromSpuriousEquals'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 3 - 3
toolchain/parse/tree.h

@@ -492,10 +492,10 @@ struct Tree::ConvertTo<NodeIdInCategory<C>> {
   }
 };
 
-template <typename T, typename U>
-struct Tree::ConvertTo<NodeIdOneOf<T, U>> {
+template <typename... T>
+struct Tree::ConvertTo<NodeIdOneOf<T...>> {
   static auto AllowedFor(NodeKind kind) -> bool {
-    return kind == T::Kind || kind == U::Kind;
+    return ((kind == T::Kind) || ...);
   }
 };
 

+ 14 - 0
toolchain/parse/typed_nodes.h

@@ -312,6 +312,20 @@ struct FunctionDefinition {
   llvm::SmallVector<AnyStatementId> body;
 };
 
+using BuiltinFunctionDefinitionStart =
+    FunctionSignature<NodeKind::BuiltinFunctionDefinitionStart,
+                      NodeCategory::None>;
+using BuiltinName = LeafNode<NodeKind::BuiltinName>;
+
+// A builtin function definition: `fn F() -> i32 = "builtin name";`
+struct BuiltinFunctionDefinition {
+  static constexpr auto Kind =
+      NodeKind::BuiltinFunctionDefinition.Define(NodeCategory::Decl);
+
+  BuiltinFunctionDefinitionStartId signature;
+  BuiltinNameId builtin_name;
+};
+
 // `alias` nodes
 // -------------
 

+ 10 - 0
toolchain/sem_ir/formatter.cpp

@@ -12,6 +12,7 @@
 #include "toolchain/base/value_store.h"
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/tree.h"
+#include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -795,6 +796,15 @@ class Formatter {
       FormatType(fn.return_type_id);
     }
 
+    // TODO: Move this conversion of kind to string elsewhere.
+    switch (fn.builtin_kind) {
+      case BuiltinFunctionKind::None:
+        break;
+      case BuiltinFunctionKind::IntAdd:
+        out_ << " = \"int.add\"";
+        break;
+    }
+
     if (!fn.body_block_ids.empty()) {
       out_ << ' ';
       OpenBrace();

+ 14 - 0
toolchain/sem_ir/function.h

@@ -10,6 +10,15 @@
 
 namespace Carbon::SemIR {
 
+// A builtin function.
+// TODO: Move out to another file.
+enum class BuiltinFunctionKind : std::uint8_t {
+  // Not a builtin function.
+  None,
+  // "int.add", integer addition.
+  IntAdd,
+};
+
 // A function.
 struct Function : public Printable<Function> {
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -59,6 +68,11 @@ struct Function : public Printable<Function> {
   // Whether the declaration is extern.
   bool is_extern;
 
+  // The following members are set at the end of a builtin function definition.
+
+  // If this is a builtin function, the corresponding builtin kind.
+  BuiltinFunctionKind builtin_kind = BuiltinFunctionKind::None;
+
   // The following members are set at the `{` of the function definition.
 
   // The definition, if the function has been defined or is currently being