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

Add builtins for integer bitwise, bit-shift, and relational comparison operators. (#3853)

Richard Smith 2 лет назад
Родитель
Сommit
762d07fa3e
27 измененных файлов с 2367 добавлено и 234 удалено
  1. 1 0
      .codespell_ignore
  2. 112 30
      toolchain/check/eval.cpp
  3. 0 0
      toolchain/check/testdata/builtins/int/add.carbon
  4. 75 0
      toolchain/check/testdata/builtins/int/and.carbon
  5. 86 0
      toolchain/check/testdata/builtins/int/complement.carbon
  6. 0 0
      toolchain/check/testdata/builtins/int/div.carbon
  7. 162 0
      toolchain/check/testdata/builtins/int/eq.carbon
  8. 215 0
      toolchain/check/testdata/builtins/int/greater.carbon
  9. 215 0
      toolchain/check/testdata/builtins/int/greater_eq.carbon
  10. 215 0
      toolchain/check/testdata/builtins/int/left_shift.carbon
  11. 215 0
      toolchain/check/testdata/builtins/int/less.carbon
  12. 215 0
      toolchain/check/testdata/builtins/int/less_eq.carbon
  13. 0 0
      toolchain/check/testdata/builtins/int/mod.carbon
  14. 0 0
      toolchain/check/testdata/builtins/int/mul.carbon
  15. 21 19
      toolchain/check/testdata/builtins/int/negate.carbon
  16. 136 0
      toolchain/check/testdata/builtins/int/neq.carbon
  17. 75 0
      toolchain/check/testdata/builtins/int/or.carbon
  18. 269 0
      toolchain/check/testdata/builtins/int/right_shift.carbon
  19. 0 0
      toolchain/check/testdata/builtins/int/sub.carbon
  20. 75 0
      toolchain/check/testdata/builtins/int/xor.carbon
  21. 0 101
      toolchain/check/testdata/builtins/int_eq.carbon
  22. 0 75
      toolchain/check/testdata/builtins/int_neq.carbon
  23. 1 0
      toolchain/diagnostics/diagnostic_kind.def
  24. 81 7
      toolchain/lower/handle.cpp
  25. 136 0
      toolchain/lower/testdata/builtins/int.carbon
  26. 46 2
      toolchain/sem_ir/builtin_function_kind.cpp
  27. 16 0
      toolchain/sem_ir/builtin_function_kind.def

+ 1 - 0
.codespell_ignore

@@ -7,6 +7,7 @@ circularly
 compiletime
 copyable
 crate
+createor
 crossreference
 falsy
 forin

+ 112 - 30
toolchain/check/eval.cpp

@@ -74,6 +74,22 @@ static auto MakeNonConstantResult(Phase phase) -> SemIR::ConstantId {
                                            : SemIR::ConstantId::NotConstant;
 }
 
+// Converts a bool value into a ConstantId.
+static auto MakeBoolResult(Context& context, SemIR::TypeId bool_type_id,
+                           bool result) -> SemIR::ConstantId {
+  return MakeConstantResult(
+      context, SemIR::BoolLiteral{bool_type_id, SemIR::BoolValue::From(result)},
+      Phase::Template);
+}
+
+// Converts an APInt value into a ConstantId.
+static auto MakeIntResult(Context& context, SemIR::TypeId type_id,
+                          llvm::APInt value) -> SemIR::ConstantId {
+  auto result = context.ints().Add(std::move(value));
+  return MakeConstantResult(context, SemIR::IntLiteral{type_id, result},
+                            Phase::Template);
+}
+
 // `GetConstantValue` checks to see whether the provided ID describes a value
 // with constant phase, and if so, returns the corresponding constant value.
 // Overloads are provided for different kinds of ID.
@@ -297,23 +313,28 @@ static auto PerformBuiltinUnaryIntOp(Context& context, SemIRLoc loc,
                                      SemIR::BuiltinFunctionKind builtin_kind,
                                      SemIR::InstId arg_id)
     -> SemIR::ConstantId {
-  CARBON_CHECK(builtin_kind == SemIR::BuiltinFunctionKind::IntNegate)
-      << "Unexpected builtin kind";
-
   auto op = context.insts().GetAs<SemIR::IntLiteral>(arg_id);
   auto op_val = context.ints().Get(op.int_id);
 
-  if (context.types().IsSignedInt(op.type_id) && op_val.isMinSignedValue()) {
-    CARBON_DIAGNOSTIC(CompileTimeIntegerNegateOverflow, Error,
-                      "Integer overflow in negation of {0}.", TypedInt);
-    context.emitter().Emit(loc, CompileTimeIntegerNegateOverflow,
-                           TypedInt{op.type_id, op_val});
+  switch (builtin_kind) {
+    case SemIR::BuiltinFunctionKind::IntNegate:
+      if (context.types().IsSignedInt(op.type_id) &&
+          op_val.isMinSignedValue()) {
+        CARBON_DIAGNOSTIC(CompileTimeIntegerNegateOverflow, Error,
+                          "Integer overflow in negation of {0}.", TypedInt);
+        context.emitter().Emit(loc, CompileTimeIntegerNegateOverflow,
+                               TypedInt{op.type_id, op_val});
+      }
+      op_val.negate();
+      break;
+    case SemIR::BuiltinFunctionKind::IntComplement:
+      op_val.flipAllBits();
+      break;
+    default:
+      CARBON_FATAL() << "Unexpected builtin kind";
   }
-  op_val.negate();
 
-  auto result = context.ints().Add(op_val);
-  return MakeConstantResult(context, SemIR::IntLiteral{op.type_id, result},
-                            Phase::Template);
+  return MakeIntResult(context, op.type_id, std::move(op_val));
 }
 
 // Performs a builtin binary integer -> integer operation.
@@ -324,14 +345,15 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
     -> SemIR::ConstantId {
   auto lhs = context.insts().GetAs<SemIR::IntLiteral>(lhs_id);
   auto rhs = context.insts().GetAs<SemIR::IntLiteral>(rhs_id);
-  auto lhs_val = context.ints().Get(lhs.int_id);
-  auto rhs_val = context.ints().Get(rhs.int_id);
+  const auto& lhs_val = context.ints().Get(lhs.int_id);
+  const auto& rhs_val = context.ints().Get(rhs.int_id);
 
   bool is_signed = context.types().IsSignedInt(lhs.type_id);
   bool overflow = false;
   llvm::APInt result_val;
   llvm::StringLiteral op_str = "<error>";
   switch (builtin_kind) {
+    // Arithmetic.
     case SemIR::BuiltinFunctionKind::IntAdd:
       result_val =
           is_signed ? lhs_val.sadd_ov(rhs_val, overflow) : lhs_val + rhs_val;
@@ -368,6 +390,49 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
       op_str = "%";
       break;
 
+    // Bitwise.
+    case SemIR::BuiltinFunctionKind::IntAnd:
+      result_val = lhs_val & rhs_val;
+      op_str = "&";
+      break;
+    case SemIR::BuiltinFunctionKind::IntOr:
+      result_val = lhs_val | rhs_val;
+      op_str = "|";
+      break;
+    case SemIR::BuiltinFunctionKind::IntXor:
+      result_val = lhs_val ^ rhs_val;
+      op_str = "^";
+      break;
+
+    // Bit shift.
+    case SemIR::BuiltinFunctionKind::IntLeftShift:
+    case SemIR::BuiltinFunctionKind::IntRightShift:
+      op_str = (builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift)
+                   ? llvm::StringLiteral("<<")
+                   : llvm::StringLiteral(">>");
+      if (rhs_val.uge(lhs_val.getBitWidth()) ||
+          (rhs_val.isNegative() && context.types().IsSignedInt(rhs.type_id))) {
+        CARBON_DIAGNOSTIC(
+            CompileTimeShiftOutOfRange, Error,
+            "Shift distance not in range [0, {0}) in {1} {2} {3}.", unsigned,
+            TypedInt, llvm::StringLiteral, TypedInt);
+        context.emitter().Emit(loc, CompileTimeShiftOutOfRange,
+                               lhs_val.getBitWidth(),
+                               TypedInt{lhs.type_id, lhs_val}, op_str,
+                               TypedInt{rhs.type_id, rhs_val});
+        // TODO: Is it useful to recover by returning 0 or -1?
+        return SemIR::ConstantId::Error;
+      }
+
+      if (builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift) {
+        result_val = lhs_val.shl(rhs_val);
+      } else if (is_signed) {
+        result_val = lhs_val.ashr(rhs_val);
+      } else {
+        result_val = lhs_val.lshr(rhs_val);
+      }
+      break;
+
     default:
       CARBON_FATAL() << "Unexpected operation kind.";
   }
@@ -381,9 +446,7 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
                            TypedInt{rhs.type_id, rhs_val});
   }
 
-  auto result = context.ints().Add(result_val);
-  return MakeConstantResult(context, SemIR::IntLiteral{lhs.type_id, result},
-                            Phase::Template);
+  return MakeIntResult(context, lhs.type_id, std::move(result_val));
 }
 
 // Performs a builtin integer comparison.
@@ -393,10 +456,11 @@ static auto PerformBuiltinIntComparison(Context& context,
                                         SemIR::InstId rhs_id,
                                         SemIR::TypeId bool_type_id)
     -> SemIR::ConstantId {
-  auto lhs_val = context.ints().Get(
-      context.insts().GetAs<SemIR::IntLiteral>(lhs_id).int_id);
-  auto rhs_val = context.ints().Get(
+  auto lhs = context.insts().GetAs<SemIR::IntLiteral>(lhs_id);
+  const auto& lhs_val = context.ints().Get(lhs.int_id);
+  const auto& rhs_val = context.ints().Get(
       context.insts().GetAs<SemIR::IntLiteral>(rhs_id).int_id);
+  bool is_signed = context.types().IsSignedInt(lhs.type_id);
 
   bool result;
   switch (builtin_kind) {
@@ -406,13 +470,23 @@ static auto PerformBuiltinIntComparison(Context& context,
     case SemIR::BuiltinFunctionKind::IntNeq:
       result = (lhs_val != rhs_val);
       break;
+    case SemIR::BuiltinFunctionKind::IntLess:
+      result = is_signed ? lhs_val.slt(rhs_val) : lhs_val.ult(rhs_val);
+      break;
+    case SemIR::BuiltinFunctionKind::IntLessEq:
+      result = is_signed ? lhs_val.sle(rhs_val) : lhs_val.ule(rhs_val);
+      break;
+    case SemIR::BuiltinFunctionKind::IntGreater:
+      result = is_signed ? lhs_val.sgt(rhs_val) : lhs_val.sgt(rhs_val);
+      break;
+    case SemIR::BuiltinFunctionKind::IntGreaterEq:
+      result = is_signed ? lhs_val.sge(rhs_val) : lhs_val.sge(rhs_val);
+      break;
     default:
       CARBON_FATAL() << "Unexpected operation kind.";
   }
 
-  return MakeConstantResult(
-      context, SemIR::BoolLiteral{bool_type_id, SemIR::BoolValue::From(result)},
-      Phase::Template);
+  return MakeBoolResult(context, bool_type_id, result);
 }
 
 static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
@@ -424,20 +498,25 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
       CARBON_FATAL() << "Not a builtin function.";
 
     // Unary integer -> integer operations.
-    case SemIR::BuiltinFunctionKind::IntNegate: {
-      // TODO: Complement.
+    case SemIR::BuiltinFunctionKind::IntNegate:
+    case SemIR::BuiltinFunctionKind::IntComplement: {
       if (phase != Phase::Template) {
         break;
       }
       return PerformBuiltinUnaryIntOp(context, loc, builtin_kind, arg_ids[0]);
     }
 
-    // Homogeneous binary integer -> integer operations.
+    // Binary integer -> integer operations.
     case SemIR::BuiltinFunctionKind::IntAdd:
     case SemIR::BuiltinFunctionKind::IntSub:
     case SemIR::BuiltinFunctionKind::IntMul:
     case SemIR::BuiltinFunctionKind::IntDiv:
-    case SemIR::BuiltinFunctionKind::IntMod: {
+    case SemIR::BuiltinFunctionKind::IntMod:
+    case SemIR::BuiltinFunctionKind::IntAnd:
+    case SemIR::BuiltinFunctionKind::IntOr:
+    case SemIR::BuiltinFunctionKind::IntXor:
+    case SemIR::BuiltinFunctionKind::IntLeftShift:
+    case SemIR::BuiltinFunctionKind::IntRightShift: {
       // TODO: Bitwise operators.
       if (phase != Phase::Template) {
         break;
@@ -448,7 +527,11 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
 
     // Integer comparisons.
     case SemIR::BuiltinFunctionKind::IntEq:
-    case SemIR::BuiltinFunctionKind::IntNeq: {
+    case SemIR::BuiltinFunctionKind::IntNeq:
+    case SemIR::BuiltinFunctionKind::IntLess:
+    case SemIR::BuiltinFunctionKind::IntLessEq:
+    case SemIR::BuiltinFunctionKind::IntGreater:
+    case SemIR::BuiltinFunctionKind::IntGreaterEq: {
       // TODO: Relational comparisons.
       if (phase != Phase::Template) {
         break;
@@ -696,8 +779,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       if (phase == Phase::Template) {
         auto value =
             context.insts().GetAs<SemIR::BoolLiteral>(const_id.inst_id());
-        value.value = SemIR::BoolValue::From(!value.value.ToBool());
-        return MakeConstantResult(context, value, Phase::Template);
+        return MakeBoolResult(context, value.type_id, !value.value.ToBool());
       }
       if (phase == Phase::UnknownDueToError) {
         return SemIR::ConstantId::Error;

+ 0 - 0
toolchain/check/testdata/builtins/int_add.carbon → toolchain/check/testdata/builtins/int/add.carbon


+ 75 - 0
toolchain/check/testdata/builtins/int/and.carbon

@@ -0,0 +1,75 @@
+// 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_and.carbon
+
+fn And(a: i32, b: i32) -> i32 = "int.and";
+
+var arr: [i32; And(12, 10)];
+let arr_p: [i32; 8]* = &arr;
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return And(a, b);
+}
+
+// CHECK:STDOUT: --- int_and.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 12 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 10 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 8 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 8] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .And = %And
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %And: <function> = fn_decl @And [template] {
+// CHECK:STDOUT:     %a.loc2_8.1: i32 = param a
+// CHECK:STDOUT:     @And.%a: i32 = bind_name a, %a.loc2_8.1
+// CHECK:STDOUT:     %b.loc2_16.1: i32 = param b
+// CHECK:STDOUT:     @And.%b: i32 = bind_name b, %b.loc2_16.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %And.ref: <function> = name_ref And, %And [template = %And]
+// CHECK:STDOUT:   %.loc4_20: i32 = int_literal 12 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_24: i32 = int_literal 10 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_19: init i32 = call %And.ref(%.loc4_20, %.loc4_24) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_27: type = array_type %.loc4_19, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 8] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 8] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 8 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_19: type = array_type %.loc5_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_20: type = ptr_type [i32; 8] [template = constants.%.5]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 8] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_24: [i32; 8]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 8]* = bind_name arr_p, %.loc5_24
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @And(%a: i32, %b: i32) -> i32 = "int.and";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %And.ref: <function> = name_ref And, file.%And [template = file.%And]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc8_13.1: init i32 = call %And.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc8_19: i32 = value_of_initializer %.loc8_13.1
+// CHECK:STDOUT:   %.loc8_13.2: i32 = converted %.loc8_13.1, %.loc8_19
+// CHECK:STDOUT:   return %.loc8_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 86 - 0
toolchain/check/testdata/builtins/int/complement.carbon

@@ -0,0 +1,86 @@
+// 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_complement.carbon
+
+fn Complement(a: i32) -> i32 = "int.complement";
+fn And(a: i32, b: i32) -> i32 = "int.and";
+
+var arr: [i32; And(Complement(0x123456), 0xFFFFFF)];
+let arr_p: [i32; 0xEDCBA9]* = &arr;
+
+fn RuntimeCall(a: i32) -> i32 {
+  return Complement(a);
+}
+
+// CHECK:STDOUT: --- int_complement.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1193046 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal -1193047 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 16777215 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 15584169 [template]
+// CHECK:STDOUT:   %.5: type = array_type %.4, i32 [template]
+// CHECK:STDOUT:   %.6: type = ptr_type [i32; 15584169] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Complement = %Complement
+// CHECK:STDOUT:     .And = %And
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Complement: <function> = fn_decl @Complement [template] {
+// CHECK:STDOUT:     %a.loc2_15.1: i32 = param a
+// CHECK:STDOUT:     @Complement.%a: i32 = bind_name a, %a.loc2_15.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %And: <function> = fn_decl @And [template] {
+// CHECK:STDOUT:     %a.loc3_8.1: i32 = param a
+// CHECK:STDOUT:     @And.%a: i32 = bind_name a, %a.loc3_8.1
+// CHECK:STDOUT:     %b.loc3_16.1: i32 = param b
+// CHECK:STDOUT:     @And.%b: i32 = bind_name b, %b.loc3_16.1
+// CHECK:STDOUT:     %return.var.loc3: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %And.ref: <function> = name_ref And, %And [template = %And]
+// CHECK:STDOUT:   %Complement.ref: <function> = name_ref Complement, %Complement [template = %Complement]
+// CHECK:STDOUT:   %.loc5_31: i32 = int_literal 1193046 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc5_30.1: init i32 = call %Complement.ref(%.loc5_31) [template = constants.%.2]
+// CHECK:STDOUT:   %.loc5_42: i32 = int_literal 16777215 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_19.1: i32 = value_of_initializer %.loc5_30.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc5_30.2: i32 = converted %.loc5_30.1, %.loc5_19.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc5_19.2: init i32 = call %And.ref(%.loc5_30.2, %.loc5_42) [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_51: type = array_type %.loc5_19.2, i32 [template = constants.%.5]
+// CHECK:STDOUT:   %arr.var: ref [i32; 15584169] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 15584169] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc6_18: i32 = int_literal 15584169 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc6_26: type = array_type %.loc6_18, i32 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc6_27: type = ptr_type [i32; 15584169] [template = constants.%.6]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 15584169] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc6_31: [i32; 15584169]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 15584169]* = bind_name arr_p, %.loc6_31
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc8_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc8_16.1
+// CHECK:STDOUT:     %return.var.loc8: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Complement(%a: i32) -> i32 = "int.complement";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @And(%a: i32, %b: i32) -> i32 = "int.and";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Complement.ref: <function> = name_ref Complement, file.%Complement [template = file.%Complement]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %.loc9_20.1: init i32 = call %Complement.ref(%a.ref)
+// CHECK:STDOUT:   %.loc9_23: i32 = value_of_initializer %.loc9_20.1
+// CHECK:STDOUT:   %.loc9_20.2: i32 = converted %.loc9_20.1, %.loc9_23
+// CHECK:STDOUT:   return %.loc9_20.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 0 - 0
toolchain/check/testdata/builtins/int_div.carbon → toolchain/check/testdata/builtins/int/div.carbon


+ 162 - 0
toolchain/check/testdata/builtins/int/eq.carbon

@@ -0,0 +1,162 @@
+// 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_eq.carbon
+
+fn Eq(a: i32, b: i32) -> bool = "int.eq";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  true_ as (if Eq(1, 1) then True else False);
+  false_ as (if Eq(1, 2) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return Eq(a, b);
+}
+
+// --- fail_bad_decl.carbon
+
+package FailBadDecl api;
+
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+3]]:1: ERROR: Invalid signature for builtin function "int.eq".
+// CHECK:STDERR: fn WrongResult(a: i32, b: i32) -> i32 = "int.eq";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn WrongResult(a: i32, b: i32) -> i32 = "int.eq";
+
+// CHECK:STDOUT: --- int_eq.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Eq = %Eq
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Eq: <function> = fn_decl @Eq [template] {
+// CHECK:STDOUT:     %a.loc2_7.1: i32 = param a
+// CHECK:STDOUT:     @Eq.%a: i32 = bind_name a, %a.loc2_7.1
+// CHECK:STDOUT:     %b.loc2_15.1: i32 = param b
+// CHECK:STDOUT:     @Eq.%b: i32 = bind_name b, %b.loc2_15.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc7_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc7_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc7_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc7_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc12_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc12_16.1
+// CHECK:STDOUT:     %b.loc12_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc12_24.1
+// CHECK:STDOUT:     %return.var.loc12: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Eq(%a: i32, %b: i32) -> bool = "int.eq";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %true_.ref: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Eq.ref.loc8: <function> = name_ref Eq, file.%Eq [template = file.%Eq]
+// CHECK:STDOUT:   %.loc8_19: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc8_22: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc8_18.1: init bool = call %Eq.ref.loc8(%.loc8_19, %.loc8_22) [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_13.1: bool = value_of_initializer %.loc8_18.1 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_18.2: bool = converted %.loc8_18.1, %.loc8_13.1 [template = constants.%.5]
+// CHECK:STDOUT:   if %.loc8_18.2 br !if.expr.then.loc8 else br !if.expr.else.loc8
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc8:
+// CHECK:STDOUT:   %True.ref.loc8: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc8(%True.ref.loc8)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc8:
+// CHECK:STDOUT:   %False.ref.loc8: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc8(%False.ref.loc8)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc8:
+// CHECK:STDOUT:   %.loc8_13.2: type = block_arg !if.expr.result.loc8 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Eq.ref.loc9: <function> = name_ref Eq, file.%Eq [template = file.%Eq]
+// CHECK:STDOUT:   %.loc9_20: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_23: i32 = int_literal 2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_19.1: init bool = call %Eq.ref.loc9(%.loc9_20, %.loc9_23) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %.loc9_19.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_19.2: bool = converted %.loc9_19.1, %.loc9_14.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc9_19.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_14.2: type = block_arg !if.expr.result.loc9 [template = constants.%False]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Eq.ref: <function> = name_ref Eq, file.%Eq [template = file.%Eq]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc13_12.1: init bool = call %Eq.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc13_18: bool = value_of_initializer %.loc13_12.1
+// CHECK:STDOUT:   %.loc13_12.2: bool = converted %.loc13_12.1, %.loc13_18
+// CHECK:STDOUT:   return %.loc13_12.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .WrongResult = %WrongResult
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %WrongResult: <function> = fn_decl @WrongResult [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @WrongResult.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @WrongResult.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @WrongResult(%a: i32, %b: i32) -> i32;
+// CHECK:STDOUT:

+ 215 - 0
toolchain/check/testdata/builtins/int/greater.carbon

@@ -0,0 +1,215 @@
+// 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_greater.carbon
+
+fn Greater(a: i32, b: i32) -> bool = "int.greater";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  false_ as (if Greater(1, 2) then True else False);
+  false_ as (if Greater(1, 1) then True else False);
+  true_ as (if Greater(1, 0) then True else False);
+  false_ as (if Greater(Negate(1), 0) then True else False);
+  true_ as (if Greater(0, Negate(1)) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return Greater(a, b);
+}
+
+// CHECK:STDOUT: --- int_greater.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.8: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Greater = %Greater
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Greater: <function> = fn_decl @Greater [template] {
+// CHECK:STDOUT:     %a.loc2_12.1: i32 = param a
+// CHECK:STDOUT:     @Greater.%a: i32 = bind_name a, %a.loc2_12.1
+// CHECK:STDOUT:     %b.loc2_20.1: i32 = param b
+// CHECK:STDOUT:     @Greater.%b: i32 = bind_name b, %b.loc2_20.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc3_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc3_11.1
+// CHECK:STDOUT:     %return.var.loc3: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc8_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc8_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc8_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc8_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var.loc16: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Greater(%a: i32, %b: i32) -> bool = "int.greater";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %false_.ref.loc9: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Greater.ref.loc9: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %.loc9_25: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_28: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_24.1: init bool = call %Greater.ref.loc9(%.loc9_25, %.loc9_28) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %.loc9_24.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_24.2: bool = converted %.loc9_24.1, %.loc9_14.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc9_24.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_14.2: type = block_arg !if.expr.result.loc9 [template = constants.%False]
+// CHECK:STDOUT:   %false_.ref.loc10: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Greater.ref.loc10: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %.loc10_25: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_28: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_24.1: init bool = call %Greater.ref.loc10(%.loc10_25, %.loc10_28) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc10_14.1: bool = value_of_initializer %.loc10_24.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc10_24.2: bool = converted %.loc10_24.1, %.loc10_14.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc10_24.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc10:
+// CHECK:STDOUT:   %True.ref.loc10: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%True.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc10:
+// CHECK:STDOUT:   %False.ref.loc10: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%False.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc10:
+// CHECK:STDOUT:   %.loc10_14.2: type = block_arg !if.expr.result.loc10 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc11: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Greater.ref.loc11: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %.loc11_24: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_27: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_23.1: init bool = call %Greater.ref.loc11(%.loc11_24, %.loc11_27) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %.loc11_23.1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_23.2: bool = converted %.loc11_23.1, %.loc11_13.1 [template = constants.%.8]
+// CHECK:STDOUT:   if %.loc11_23.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc11:
+// CHECK:STDOUT:   %True.ref.loc11: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%True.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc11:
+// CHECK:STDOUT:   %False.ref.loc11: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%False.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc11:
+// CHECK:STDOUT:   %.loc11_13.2: type = block_arg !if.expr.result.loc11 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc12: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Greater.ref.loc12: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %Negate.ref.loc12: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc12_32: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc12_31.1: init i32 = call %Negate.ref.loc12(%.loc12_32) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_36: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_24.1: i32 = value_of_initializer %.loc12_31.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_31.2: i32 = converted %.loc12_31.1, %.loc12_24.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_24.2: init bool = call %Greater.ref.loc12(%.loc12_31.2, %.loc12_36) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_14.1: bool = value_of_initializer %.loc12_24.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_24.3: bool = converted %.loc12_24.2, %.loc12_14.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc12_24.3 br !if.expr.then.loc12 else br !if.expr.else.loc12
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc12:
+// CHECK:STDOUT:   %True.ref.loc12: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%True.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc12:
+// CHECK:STDOUT:   %False.ref.loc12: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%False.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc12:
+// CHECK:STDOUT:   %.loc12_14.2: type = block_arg !if.expr.result.loc12 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc13: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Greater.ref.loc13: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %.loc13_24: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %Negate.ref.loc13: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc13_34: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_33.1: init i32 = call %Negate.ref.loc13(%.loc13_34) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.1: i32 = value_of_initializer %.loc13_33.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_33.2: i32 = converted %.loc13_33.1, %.loc13_23.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.2: init bool = call %Greater.ref.loc13(%.loc13_24, %.loc13_33.2) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %.loc13_23.2 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_23.3: bool = converted %.loc13_23.2, %.loc13_13.1 [template = constants.%.8]
+// CHECK:STDOUT:   if %.loc13_23.3 br !if.expr.then.loc13 else br !if.expr.else.loc13
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc13:
+// CHECK:STDOUT:   %True.ref.loc13: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%True.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc13:
+// CHECK:STDOUT:   %False.ref.loc13: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%False.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc13:
+// CHECK:STDOUT:   %.loc13_13.2: type = block_arg !if.expr.result.loc13 [template = constants.%True]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Greater.ref: <function> = name_ref Greater, file.%Greater [template = file.%Greater]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc17_17.1: init bool = call %Greater.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc17_23: bool = value_of_initializer %.loc17_17.1
+// CHECK:STDOUT:   %.loc17_17.2: bool = converted %.loc17_17.1, %.loc17_23
+// CHECK:STDOUT:   return %.loc17_17.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 215 - 0
toolchain/check/testdata/builtins/int/greater_eq.carbon

@@ -0,0 +1,215 @@
+// 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_greater_eq.carbon
+
+fn GreaterEq(a: i32, b: i32) -> bool = "int.greater_eq";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  false_ as (if GreaterEq(1, 2) then True else False);
+  true_ as (if GreaterEq(1, 1) then True else False);
+  true_ as (if GreaterEq(1, 0) then True else False);
+  false_ as (if GreaterEq(Negate(1), 0) then True else False);
+  true_ as (if GreaterEq(0, Negate(1)) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return GreaterEq(a, b);
+}
+
+// CHECK:STDOUT: --- int_greater_eq.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .GreaterEq = %GreaterEq
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %GreaterEq: <function> = fn_decl @GreaterEq [template] {
+// CHECK:STDOUT:     %a.loc2_14.1: i32 = param a
+// CHECK:STDOUT:     @GreaterEq.%a: i32 = bind_name a, %a.loc2_14.1
+// CHECK:STDOUT:     %b.loc2_22.1: i32 = param b
+// CHECK:STDOUT:     @GreaterEq.%b: i32 = bind_name b, %b.loc2_22.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc3_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc3_11.1
+// CHECK:STDOUT:     %return.var.loc3: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc8_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc8_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc8_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc8_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var.loc16: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @GreaterEq(%a: i32, %b: i32) -> bool = "int.greater_eq";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %false_.ref.loc9: False = name_ref false_, %false_
+// CHECK:STDOUT:   %GreaterEq.ref.loc9: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %.loc9_27: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_30: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_26.1: init bool = call %GreaterEq.ref.loc9(%.loc9_27, %.loc9_30) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_14.1: bool = value_of_initializer %.loc9_26.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_26.2: bool = converted %.loc9_26.1, %.loc9_14.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc9_26.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_14.2: type = block_arg !if.expr.result.loc9 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc10: True = name_ref true_, %true_
+// CHECK:STDOUT:   %GreaterEq.ref.loc10: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %.loc10_26: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_29: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_25.1: init bool = call %GreaterEq.ref.loc10(%.loc10_26, %.loc10_29) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_13.1: bool = value_of_initializer %.loc10_25.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_25.2: bool = converted %.loc10_25.1, %.loc10_13.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc10_25.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc10:
+// CHECK:STDOUT:   %True.ref.loc10: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%True.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc10:
+// CHECK:STDOUT:   %False.ref.loc10: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%False.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc10:
+// CHECK:STDOUT:   %.loc10_13.2: type = block_arg !if.expr.result.loc10 [template = constants.%True]
+// CHECK:STDOUT:   %true_.ref.loc11: True = name_ref true_, %true_
+// CHECK:STDOUT:   %GreaterEq.ref.loc11: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %.loc11_26: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_29: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_25.1: init bool = call %GreaterEq.ref.loc11(%.loc11_26, %.loc11_29) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_13.1: bool = value_of_initializer %.loc11_25.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_25.2: bool = converted %.loc11_25.1, %.loc11_13.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc11_25.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc11:
+// CHECK:STDOUT:   %True.ref.loc11: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%True.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc11:
+// CHECK:STDOUT:   %False.ref.loc11: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%False.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc11:
+// CHECK:STDOUT:   %.loc11_13.2: type = block_arg !if.expr.result.loc11 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc12: False = name_ref false_, %false_
+// CHECK:STDOUT:   %GreaterEq.ref.loc12: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %Negate.ref.loc12: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc12_34: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc12_33.1: init i32 = call %Negate.ref.loc12(%.loc12_34) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_38: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc12_26.1: i32 = value_of_initializer %.loc12_33.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_33.2: i32 = converted %.loc12_33.1, %.loc12_26.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_26.2: init bool = call %GreaterEq.ref.loc12(%.loc12_33.2, %.loc12_38) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_14.1: bool = value_of_initializer %.loc12_26.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_26.3: bool = converted %.loc12_26.2, %.loc12_14.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc12_26.3 br !if.expr.then.loc12 else br !if.expr.else.loc12
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc12:
+// CHECK:STDOUT:   %True.ref.loc12: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%True.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc12:
+// CHECK:STDOUT:   %False.ref.loc12: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%False.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc12:
+// CHECK:STDOUT:   %.loc12_14.2: type = block_arg !if.expr.result.loc12 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc13: True = name_ref true_, %true_
+// CHECK:STDOUT:   %GreaterEq.ref.loc13: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %.loc13_26: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %Negate.ref.loc13: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc13_36: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_35.1: init i32 = call %Negate.ref.loc13(%.loc13_36) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_25.1: i32 = value_of_initializer %.loc13_35.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_35.2: i32 = converted %.loc13_35.1, %.loc13_25.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_25.2: init bool = call %GreaterEq.ref.loc13(%.loc13_26, %.loc13_35.2) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_13.1: bool = value_of_initializer %.loc13_25.2 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_25.3: bool = converted %.loc13_25.2, %.loc13_13.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc13_25.3 br !if.expr.then.loc13 else br !if.expr.else.loc13
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc13:
+// CHECK:STDOUT:   %True.ref.loc13: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%True.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc13:
+// CHECK:STDOUT:   %False.ref.loc13: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%False.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc13:
+// CHECK:STDOUT:   %.loc13_13.2: type = block_arg !if.expr.result.loc13 [template = constants.%True]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %GreaterEq.ref: <function> = name_ref GreaterEq, file.%GreaterEq [template = file.%GreaterEq]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc17_19.1: init bool = call %GreaterEq.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc17_25: bool = value_of_initializer %.loc17_19.1
+// CHECK:STDOUT:   %.loc17_19.2: bool = converted %.loc17_19.1, %.loc17_25
+// CHECK:STDOUT:   return %.loc17_19.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 215 - 0
toolchain/check/testdata/builtins/int/left_shift.carbon

@@ -0,0 +1,215 @@
+// 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_left_shift.carbon
+
+fn LeftShift(a: i32, b: i32) -> i32 = "int.left_shift";
+
+var arr: [i32; LeftShift(5, 2)];
+let arr_p: [i32; 20]* = &arr;
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return LeftShift(a, b);
+}
+
+// TODO: Test mixed types for LHS and RHS.
+
+// --- fail_bad_shift.carbon
+
+package BadShift api;
+
+fn LeftShift(a: i32, b: i32) -> i32 = "int.left_shift";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+// Shift greater than size is disallowed.
+let size_1: i32 = LeftShift(1, 31);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:19: ERROR: Shift distance not in range [0, 32) in 1 << 32.
+// CHECK:STDERR: let size_2: i32 = LeftShift(1, 32);
+// CHECK:STDERR:                   ^~~~~~~~~~
+// CHECK:STDERR:
+let size_2: i32 = LeftShift(1, 32);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:19: ERROR: Shift distance not in range [0, 32) in 1 << 33.
+// CHECK:STDERR: let size_3: i32 = LeftShift(1, 33);
+// CHECK:STDERR:                   ^~~~~~~~~~
+// CHECK:STDERR:
+let size_3: i32 = LeftShift(1, 33);
+
+// Overflow is allowed if the shift distance is in bounds.
+let overflow_1: i32 = LeftShift(1000, 31);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:23: ERROR: Shift distance not in range [0, 32) in 1000 << 32.
+// CHECK:STDERR: let overflow_2: i32 = LeftShift(1000, 32);
+// CHECK:STDERR:                       ^~~~~~~~~~
+// CHECK:STDERR:
+let overflow_2: i32 = LeftShift(1000, 32);
+
+// Oversize shifts aren't allowed even if there's no overflow.
+let no_overflow_1: i32 = LeftShift(0, 31);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:26: ERROR: Shift distance not in range [0, 32) in 0 << 32.
+// CHECK:STDERR: let no_overflow_2: i32 = LeftShift(0, 32);
+// CHECK:STDERR:                          ^~~~~~~~~~
+// CHECK:STDERR:
+let no_overflow_2: i32 = LeftShift(0, 32);
+
+// Negative shifts aren't allowed either.
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+3]]:21: ERROR: Shift distance not in range [0, 32) in 1 << -1.
+// CHECK:STDERR: let negative: i32 = LeftShift(1, Negate(1));
+// CHECK:STDERR:                     ^~~~~~~~~~
+let negative: i32 = LeftShift(1, Negate(1));
+
+// CHECK:STDOUT: --- int_left_shift.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 5 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 20 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 20] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .LeftShift = %LeftShift
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %LeftShift: <function> = fn_decl @LeftShift [template] {
+// CHECK:STDOUT:     %a.loc2_14.1: i32 = param a
+// CHECK:STDOUT:     @LeftShift.%a: i32 = bind_name a, %a.loc2_14.1
+// CHECK:STDOUT:     %b.loc2_22.1: i32 = param b
+// CHECK:STDOUT:     @LeftShift.%b: i32 = bind_name b, %b.loc2_22.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %LeftShift.ref: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc4_26: i32 = int_literal 5 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_29: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_25: init i32 = call %LeftShift.ref(%.loc4_26, %.loc4_29) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_31: type = array_type %.loc4_25, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 20] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 20] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 20 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_20: type = array_type %.loc5_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_21: type = ptr_type [i32; 20] [template = constants.%.5]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 20] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_25: [i32; 20]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 20]* = bind_name arr_p, %.loc5_25
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @LeftShift(%a: i32, %b: i32) -> i32 = "int.left_shift";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %LeftShift.ref: <function> = name_ref LeftShift, file.%LeftShift [template = file.%LeftShift]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc8_19.1: init i32 = call %LeftShift.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc8_25: i32 = value_of_initializer %.loc8_19.1
+// CHECK:STDOUT:   %.loc8_19.2: i32 = converted %.loc8_19.1, %.loc8_25
+// CHECK:STDOUT:   return %.loc8_19.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_shift.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 31 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal -2147483648 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 32 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 33 [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal 1000 [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .LeftShift = %LeftShift
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %LeftShift: <function> = fn_decl @LeftShift [template] {
+// CHECK:STDOUT:     %a.loc4_14.1: i32 = param a
+// CHECK:STDOUT:     @LeftShift.%a: i32 = bind_name a, %a.loc4_14.1
+// CHECK:STDOUT:     %b.loc4_22.1: i32 = param b
+// CHECK:STDOUT:     @LeftShift.%b: i32 = bind_name b, %b.loc4_22.1
+// CHECK:STDOUT:     %return.var.loc4: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc5_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc5_11.1
+// CHECK:STDOUT:     %return.var.loc5: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %LeftShift.ref.loc8: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc8_29: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc8_32: i32 = int_literal 31 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc8_28.1: init i32 = call %LeftShift.ref.loc8(%.loc8_29, %.loc8_32) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc8_35: i32 = value_of_initializer %.loc8_28.1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc8_28.2: i32 = converted %.loc8_28.1, %.loc8_35 [template = constants.%.3]
+// CHECK:STDOUT:   %size_1: i32 = bind_name size_1, %.loc8_28.2
+// CHECK:STDOUT:   %LeftShift.ref.loc13: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc13_29: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc13_32: i32 = int_literal 32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_28.1: init i32 = call %LeftShift.ref.loc13(%.loc13_29, %.loc13_32) [template = <error>]
+// CHECK:STDOUT:   %.loc13_35: i32 = value_of_initializer %.loc13_28.1 [template = <error>]
+// CHECK:STDOUT:   %.loc13_28.2: i32 = converted %.loc13_28.1, %.loc13_35 [template = <error>]
+// CHECK:STDOUT:   %size_2: i32 = bind_name size_2, %.loc13_28.2
+// CHECK:STDOUT:   %LeftShift.ref.loc18: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc18_29: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc18_32: i32 = int_literal 33 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc18_28.1: init i32 = call %LeftShift.ref.loc18(%.loc18_29, %.loc18_32) [template = <error>]
+// CHECK:STDOUT:   %.loc18_35: i32 = value_of_initializer %.loc18_28.1 [template = <error>]
+// CHECK:STDOUT:   %.loc18_28.2: i32 = converted %.loc18_28.1, %.loc18_35 [template = <error>]
+// CHECK:STDOUT:   %size_3: i32 = bind_name size_3, %.loc18_28.2
+// CHECK:STDOUT:   %LeftShift.ref.loc21: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc21_33: i32 = int_literal 1000 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc21_39: i32 = int_literal 31 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc21_32.1: init i32 = call %LeftShift.ref.loc21(%.loc21_33, %.loc21_39) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc21_42: i32 = value_of_initializer %.loc21_32.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc21_32.2: i32 = converted %.loc21_32.1, %.loc21_42 [template = constants.%.7]
+// CHECK:STDOUT:   %overflow_1: i32 = bind_name overflow_1, %.loc21_32.2
+// CHECK:STDOUT:   %LeftShift.ref.loc26: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc26_33: i32 = int_literal 1000 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc26_39: i32 = int_literal 32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc26_32.1: init i32 = call %LeftShift.ref.loc26(%.loc26_33, %.loc26_39) [template = <error>]
+// CHECK:STDOUT:   %.loc26_42: i32 = value_of_initializer %.loc26_32.1 [template = <error>]
+// CHECK:STDOUT:   %.loc26_32.2: i32 = converted %.loc26_32.1, %.loc26_42 [template = <error>]
+// CHECK:STDOUT:   %overflow_2: i32 = bind_name overflow_2, %.loc26_32.2
+// CHECK:STDOUT:   %LeftShift.ref.loc29: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc29_36: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc29_39: i32 = int_literal 31 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc29_35.1: init i32 = call %LeftShift.ref.loc29(%.loc29_36, %.loc29_39) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc29_42: i32 = value_of_initializer %.loc29_35.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc29_35.2: i32 = converted %.loc29_35.1, %.loc29_42 [template = constants.%.7]
+// CHECK:STDOUT:   %no_overflow_1: i32 = bind_name no_overflow_1, %.loc29_35.2
+// CHECK:STDOUT:   %LeftShift.ref.loc34: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc34_36: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc34_39: i32 = int_literal 32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc34_35.1: init i32 = call %LeftShift.ref.loc34(%.loc34_36, %.loc34_39) [template = <error>]
+// CHECK:STDOUT:   %.loc34_42: i32 = value_of_initializer %.loc34_35.1 [template = <error>]
+// CHECK:STDOUT:   %.loc34_35.2: i32 = converted %.loc34_35.1, %.loc34_42 [template = <error>]
+// CHECK:STDOUT:   %no_overflow_2: i32 = bind_name no_overflow_2, %.loc34_35.2
+// CHECK:STDOUT:   %LeftShift.ref.loc40: <function> = name_ref LeftShift, %LeftShift [template = %LeftShift]
+// CHECK:STDOUT:   %.loc40_31: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %Negate.ref: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc40_41: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc40_40.1: init i32 = call %Negate.ref(%.loc40_41) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc40_30.1: i32 = value_of_initializer %.loc40_40.1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc40_40.2: i32 = converted %.loc40_40.1, %.loc40_30.1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc40_30.2: init i32 = call %LeftShift.ref.loc40(%.loc40_31, %.loc40_40.2) [template = <error>]
+// CHECK:STDOUT:   %.loc40_44: i32 = value_of_initializer %.loc40_30.2 [template = <error>]
+// CHECK:STDOUT:   %.loc40_30.3: i32 = converted %.loc40_30.2, %.loc40_44 [template = <error>]
+// CHECK:STDOUT:   %negative: i32 = bind_name negative, %.loc40_30.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @LeftShift(%a: i32, %b: i32) -> i32 = "int.left_shift";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:

+ 215 - 0
toolchain/check/testdata/builtins/int/less.carbon

@@ -0,0 +1,215 @@
+// 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_less.carbon
+
+fn Less(a: i32, b: i32) -> bool = "int.less";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  true_ as (if Less(1, 2) then True else False);
+  false_ as (if Less(1, 1) then True else False);
+  false_ as (if Less(1, 0) then True else False);
+  true_ as (if Less(Negate(1), 0) then True else False);
+  false_ as (if Less(0, Negate(1)) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return Less(a, b);
+}
+
+// CHECK:STDOUT: --- int_less.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Less = %Less
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Less: <function> = fn_decl @Less [template] {
+// CHECK:STDOUT:     %a.loc2_9.1: i32 = param a
+// CHECK:STDOUT:     @Less.%a: i32 = bind_name a, %a.loc2_9.1
+// CHECK:STDOUT:     %b.loc2_17.1: i32 = param b
+// CHECK:STDOUT:     @Less.%b: i32 = bind_name b, %b.loc2_17.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc3_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc3_11.1
+// CHECK:STDOUT:     %return.var.loc3: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc8_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc8_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc8_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc8_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var.loc16: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Less(%a: i32, %b: i32) -> bool = "int.less";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %true_.ref.loc9: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Less.ref.loc9: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %.loc9_21: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_24: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_20.1: init bool = call %Less.ref.loc9(%.loc9_21, %.loc9_24) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_13.1: bool = value_of_initializer %.loc9_20.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_20.2: bool = converted %.loc9_20.1, %.loc9_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc9_20.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_13.2: type = block_arg !if.expr.result.loc9 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc10: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Less.ref.loc10: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %.loc10_22: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_25: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_21.1: init bool = call %Less.ref.loc10(%.loc10_22, %.loc10_25) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_14.1: bool = value_of_initializer %.loc10_21.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc10_21.2: bool = converted %.loc10_21.1, %.loc10_14.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc10_21.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc10:
+// CHECK:STDOUT:   %True.ref.loc10: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%True.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc10:
+// CHECK:STDOUT:   %False.ref.loc10: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%False.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc10:
+// CHECK:STDOUT:   %.loc10_14.2: type = block_arg !if.expr.result.loc10 [template = constants.%False]
+// CHECK:STDOUT:   %false_.ref.loc11: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Less.ref.loc11: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %.loc11_22: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_25: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_21.1: init bool = call %Less.ref.loc11(%.loc11_22, %.loc11_25) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %.loc11_21.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_21.2: bool = converted %.loc11_21.1, %.loc11_14.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc11_21.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc11:
+// CHECK:STDOUT:   %True.ref.loc11: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%True.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc11:
+// CHECK:STDOUT:   %False.ref.loc11: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%False.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc11:
+// CHECK:STDOUT:   %.loc11_14.2: type = block_arg !if.expr.result.loc11 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc12: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Less.ref.loc12: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %Negate.ref.loc12: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc12_28: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc12_27.1: init i32 = call %Negate.ref.loc12(%.loc12_28) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_32: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc12_20.1: i32 = value_of_initializer %.loc12_27.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_27.2: i32 = converted %.loc12_27.1, %.loc12_20.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_20.2: init bool = call %Less.ref.loc12(%.loc12_27.2, %.loc12_32) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_13.1: bool = value_of_initializer %.loc12_20.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_20.3: bool = converted %.loc12_20.2, %.loc12_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc12_20.3 br !if.expr.then.loc12 else br !if.expr.else.loc12
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc12:
+// CHECK:STDOUT:   %True.ref.loc12: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%True.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc12:
+// CHECK:STDOUT:   %False.ref.loc12: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%False.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc12:
+// CHECK:STDOUT:   %.loc12_13.2: type = block_arg !if.expr.result.loc12 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc13: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Less.ref.loc13: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %.loc13_22: i32 = int_literal 0 [template = constants.%.8]
+// CHECK:STDOUT:   %Negate.ref.loc13: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc13_32: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_31.1: init i32 = call %Negate.ref.loc13(%.loc13_32) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_21.1: i32 = value_of_initializer %.loc13_31.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_31.2: i32 = converted %.loc13_31.1, %.loc13_21.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_21.2: init bool = call %Less.ref.loc13(%.loc13_22, %.loc13_31.2) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %.loc13_21.2 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc13_21.3: bool = converted %.loc13_21.2, %.loc13_14.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc13_21.3 br !if.expr.then.loc13 else br !if.expr.else.loc13
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc13:
+// CHECK:STDOUT:   %True.ref.loc13: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%True.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc13:
+// CHECK:STDOUT:   %False.ref.loc13: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%False.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc13:
+// CHECK:STDOUT:   %.loc13_14.2: type = block_arg !if.expr.result.loc13 [template = constants.%False]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Less.ref: <function> = name_ref Less, file.%Less [template = file.%Less]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc17_14.1: init bool = call %Less.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc17_20: bool = value_of_initializer %.loc17_14.1
+// CHECK:STDOUT:   %.loc17_14.2: bool = converted %.loc17_14.1, %.loc17_20
+// CHECK:STDOUT:   return %.loc17_14.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 215 - 0
toolchain/check/testdata/builtins/int/less_eq.carbon

@@ -0,0 +1,215 @@
+// 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_less_eq.carbon
+
+fn LessEq(a: i32, b: i32) -> bool = "int.less_eq";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  true_ as (if LessEq(1, 2) then True else False);
+  true_ as (if LessEq(1, 1) then True else False);
+  false_ as (if LessEq(1, 0) then True else False);
+  true_ as (if LessEq(Negate(1), 0) then True else False);
+  false_ as (if LessEq(0, Negate(1)) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return LessEq(a, b);
+}
+
+// CHECK:STDOUT: --- int_less_eq.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.8: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .LessEq = %LessEq
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %LessEq: <function> = fn_decl @LessEq [template] {
+// CHECK:STDOUT:     %a.loc2_11.1: i32 = param a
+// CHECK:STDOUT:     @LessEq.%a: i32 = bind_name a, %a.loc2_11.1
+// CHECK:STDOUT:     %b.loc2_19.1: i32 = param b
+// CHECK:STDOUT:     @LessEq.%b: i32 = bind_name b, %b.loc2_19.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc3_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc3_11.1
+// CHECK:STDOUT:     %return.var.loc3: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc8_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc8_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc8_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc8_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc16_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc16_16.1
+// CHECK:STDOUT:     %b.loc16_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc16_24.1
+// CHECK:STDOUT:     %return.var.loc16: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @LessEq(%a: i32, %b: i32) -> bool = "int.less_eq";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %true_.ref.loc9: True = name_ref true_, %true_
+// CHECK:STDOUT:   %LessEq.ref.loc9: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %.loc9_23: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_26: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_22.1: init bool = call %LessEq.ref.loc9(%.loc9_23, %.loc9_26) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_13.1: bool = value_of_initializer %.loc9_22.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_22.2: bool = converted %.loc9_22.1, %.loc9_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc9_22.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_13.2: type = block_arg !if.expr.result.loc9 [template = constants.%True]
+// CHECK:STDOUT:   %true_.ref.loc10: True = name_ref true_, %true_
+// CHECK:STDOUT:   %LessEq.ref.loc10: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %.loc10_23: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_26: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc10_22.1: init bool = call %LessEq.ref.loc10(%.loc10_23, %.loc10_26) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc10_13.1: bool = value_of_initializer %.loc10_22.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc10_22.2: bool = converted %.loc10_22.1, %.loc10_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc10_22.2 br !if.expr.then.loc10 else br !if.expr.else.loc10
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc10:
+// CHECK:STDOUT:   %True.ref.loc10: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%True.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc10:
+// CHECK:STDOUT:   %False.ref.loc10: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc10(%False.ref.loc10)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc10:
+// CHECK:STDOUT:   %.loc10_13.2: type = block_arg !if.expr.result.loc10 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc11: False = name_ref false_, %false_
+// CHECK:STDOUT:   %LessEq.ref.loc11: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %.loc11_24: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc11_27: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc11_23.1: init bool = call %LessEq.ref.loc11(%.loc11_24, %.loc11_27) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_14.1: bool = value_of_initializer %.loc11_23.1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_23.2: bool = converted %.loc11_23.1, %.loc11_14.1 [template = constants.%.8]
+// CHECK:STDOUT:   if %.loc11_23.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc11:
+// CHECK:STDOUT:   %True.ref.loc11: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%True.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc11:
+// CHECK:STDOUT:   %False.ref.loc11: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%False.ref.loc11)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc11:
+// CHECK:STDOUT:   %.loc11_14.2: type = block_arg !if.expr.result.loc11 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref.loc12: True = name_ref true_, %true_
+// CHECK:STDOUT:   %LessEq.ref.loc12: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %Negate.ref.loc12: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc12_30: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc12_29.1: init i32 = call %Negate.ref.loc12(%.loc12_30) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_34: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc12_22.1: i32 = value_of_initializer %.loc12_29.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_29.2: i32 = converted %.loc12_29.1, %.loc12_22.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc12_22.2: init bool = call %LessEq.ref.loc12(%.loc12_29.2, %.loc12_34) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_13.1: bool = value_of_initializer %.loc12_22.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc12_22.3: bool = converted %.loc12_22.2, %.loc12_13.1 [template = constants.%.6]
+// CHECK:STDOUT:   if %.loc12_22.3 br !if.expr.then.loc12 else br !if.expr.else.loc12
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc12:
+// CHECK:STDOUT:   %True.ref.loc12: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%True.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc12:
+// CHECK:STDOUT:   %False.ref.loc12: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc12(%False.ref.loc12)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc12:
+// CHECK:STDOUT:   %.loc12_13.2: type = block_arg !if.expr.result.loc12 [template = constants.%True]
+// CHECK:STDOUT:   %false_.ref.loc13: False = name_ref false_, %false_
+// CHECK:STDOUT:   %LessEq.ref.loc13: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %.loc13_24: i32 = int_literal 0 [template = constants.%.7]
+// CHECK:STDOUT:   %Negate.ref.loc13: <function> = name_ref Negate, file.%Negate [template = file.%Negate]
+// CHECK:STDOUT:   %.loc13_34: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_33.1: init i32 = call %Negate.ref.loc13(%.loc13_34) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.1: i32 = value_of_initializer %.loc13_33.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_33.2: i32 = converted %.loc13_33.1, %.loc13_23.1 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc13_23.2: init bool = call %LessEq.ref.loc13(%.loc13_24, %.loc13_33.2) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_14.1: bool = value_of_initializer %.loc13_23.2 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc13_23.3: bool = converted %.loc13_23.2, %.loc13_14.1 [template = constants.%.8]
+// CHECK:STDOUT:   if %.loc13_23.3 br !if.expr.then.loc13 else br !if.expr.else.loc13
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc13:
+// CHECK:STDOUT:   %True.ref.loc13: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%True.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc13:
+// CHECK:STDOUT:   %False.ref.loc13: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc13(%False.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc13:
+// CHECK:STDOUT:   %.loc13_14.2: type = block_arg !if.expr.result.loc13 [template = constants.%False]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %LessEq.ref: <function> = name_ref LessEq, file.%LessEq [template = file.%LessEq]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc17_16.1: init bool = call %LessEq.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc17_22: bool = value_of_initializer %.loc17_16.1
+// CHECK:STDOUT:   %.loc17_16.2: bool = converted %.loc17_16.1, %.loc17_22
+// CHECK:STDOUT:   return %.loc17_16.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 0 - 0
toolchain/check/testdata/builtins/int_mod.carbon → toolchain/check/testdata/builtins/int/mod.carbon


+ 0 - 0
toolchain/check/testdata/builtins/int_mul.carbon → toolchain/check/testdata/builtins/int/mul.carbon


+ 21 - 19
toolchain/check/testdata/builtins/int_negate.carbon → toolchain/check/testdata/builtins/int/negate.carbon

@@ -8,8 +8,8 @@
 
 fn Negate(a: i32) -> i32 = "int.negate";
 
-var arr: [i32; Negate(Negate(1))];
-let arr_p: [i32; 1]* = &arr;
+var arr: [i32; Negate(Negate(123))];
+let arr_p: [i32; 123]* = &arr;
 
 let n: i32 = Negate(1);
 
@@ -115,10 +115,12 @@ let b: i32 = Negate(Sub(Negate(0x7FFFFFFF), 1));
 // CHECK:STDOUT: --- int_negate.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
-// CHECK:STDOUT:   %.2: i32 = int_literal -1 [template]
+// CHECK:STDOUT:   %.1: i32 = int_literal 123 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal -123 [template]
 // CHECK:STDOUT:   %.3: type = array_type %.1, i32 [template]
-// CHECK:STDOUT:   %.4: type = ptr_type [i32; 1] [template]
+// CHECK:STDOUT:   %.4: type = ptr_type [i32; 123] [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal -1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -134,25 +136,25 @@ let b: i32 = Negate(Sub(Negate(0x7FFFFFFF), 1));
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Negate.ref.loc4_16: <function> = name_ref Negate, %Negate [template = %Negate]
 // CHECK:STDOUT:   %Negate.ref.loc4_23: <function> = name_ref Negate, %Negate [template = %Negate]
-// CHECK:STDOUT:   %.loc4_30: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_30: i32 = int_literal 123 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc4_29.1: init i32 = call %Negate.ref.loc4_23(%.loc4_30) [template = constants.%.2]
 // CHECK:STDOUT:   %.loc4_22.1: i32 = value_of_initializer %.loc4_29.1 [template = constants.%.2]
 // CHECK:STDOUT:   %.loc4_29.2: i32 = converted %.loc4_29.1, %.loc4_22.1 [template = constants.%.2]
 // CHECK:STDOUT:   %.loc4_22.2: init i32 = call %Negate.ref.loc4_16(%.loc4_29.2) [template = constants.%.1]
-// CHECK:STDOUT:   %.loc4_33: type = array_type %.loc4_22.2, i32 [template = constants.%.3]
-// CHECK:STDOUT:   %arr.var: ref [i32; 1] = var arr
-// CHECK:STDOUT:   %arr: ref [i32; 1] = bind_name arr, %arr.var
-// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc5_19: type = array_type %.loc5_18, i32 [template = constants.%.3]
-// CHECK:STDOUT:   %.loc5_20: type = ptr_type [i32; 1] [template = constants.%.4]
-// CHECK:STDOUT:   %arr.ref: ref [i32; 1] = name_ref arr, %arr
-// CHECK:STDOUT:   %.loc5_24: [i32; 1]* = addr_of %arr.ref
-// CHECK:STDOUT:   %arr_p: [i32; 1]* = bind_name arr_p, %.loc5_24
+// CHECK:STDOUT:   %.loc4_35: type = array_type %.loc4_22.2, i32 [template = constants.%.3]
+// CHECK:STDOUT:   %arr.var: ref [i32; 123] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 123] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 123 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc5_21: type = array_type %.loc5_18, i32 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_22: type = ptr_type [i32; 123] [template = constants.%.4]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 123] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_26: [i32; 123]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 123]* = bind_name arr_p, %.loc5_26
 // CHECK:STDOUT:   %Negate.ref.loc7: <function> = name_ref Negate, %Negate [template = %Negate]
-// CHECK:STDOUT:   %.loc7_21: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc7_20.1: init i32 = call %Negate.ref.loc7(%.loc7_21) [template = constants.%.2]
-// CHECK:STDOUT:   %.loc7_23: i32 = value_of_initializer %.loc7_20.1 [template = constants.%.2]
-// CHECK:STDOUT:   %.loc7_20.2: i32 = converted %.loc7_20.1, %.loc7_23 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc7_21: i32 = int_literal 1 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc7_20.1: init i32 = call %Negate.ref.loc7(%.loc7_21) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc7_23: i32 = value_of_initializer %.loc7_20.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc7_20.2: i32 = converted %.loc7_20.1, %.loc7_23 [template = constants.%.6]
 // CHECK:STDOUT:   %n: i32 = bind_name n, %.loc7_20.2
 // CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
 // CHECK:STDOUT:     %a.loc9_16.1: i32 = param a

+ 136 - 0
toolchain/check/testdata/builtins/int/neq.carbon

@@ -0,0 +1,136 @@
+// 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_neq.carbon
+
+fn Neq(a: i32, b: i32) -> bool = "int.neq";
+
+class True {}
+class False {}
+
+fn F(true_: True, false_: False) {
+  false_ as (if Neq(1, 1) then True else False);
+  true_ as (if Neq(1, 2) then True else False);
+}
+
+fn RuntimeCall(a: i32, b: i32) -> bool {
+  return Neq(a, b);
+}
+
+// CHECK:STDOUT: --- int_neq.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %True: type = class_type @True [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %False: type = class_type @False [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.7: bool = bool_literal true [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Neq = %Neq
+// CHECK:STDOUT:     .True = %True.decl
+// CHECK:STDOUT:     .False = %False.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Neq: <function> = fn_decl @Neq [template] {
+// CHECK:STDOUT:     %a.loc2_8.1: i32 = param a
+// CHECK:STDOUT:     @Neq.%a: i32 = bind_name a, %a.loc2_8.1
+// CHECK:STDOUT:     %b.loc2_16.1: i32 = param b
+// CHECK:STDOUT:     @Neq.%b: i32 = bind_name b, %b.loc2_16.1
+// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %True.decl: type = class_decl @True [template = constants.%True] {}
+// CHECK:STDOUT:   %False.decl: type = class_decl @False [template = constants.%False] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %True.ref: type = name_ref True, %True.decl [template = constants.%True]
+// CHECK:STDOUT:     %true_.loc7_6.1: True = param true_
+// CHECK:STDOUT:     @F.%true_: True = bind_name true_, %true_.loc7_6.1
+// CHECK:STDOUT:     %False.ref: type = name_ref False, %False.decl [template = constants.%False]
+// CHECK:STDOUT:     %false_.loc7_19.1: False = param false_
+// CHECK:STDOUT:     @F.%false_: False = bind_name false_, %false_.loc7_19.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc12_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc12_16.1
+// CHECK:STDOUT:     %b.loc12_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc12_24.1
+// CHECK:STDOUT:     %return.var.loc12: ref bool = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @True {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%True
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @False {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%False
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Neq(%a: i32, %b: i32) -> bool = "int.neq";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%true_: True, %false_: False) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %false_.ref: False = name_ref false_, %false_
+// CHECK:STDOUT:   %Neq.ref.loc8: <function> = name_ref Neq, file.%Neq [template = file.%Neq]
+// CHECK:STDOUT:   %.loc8_21: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc8_24: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc8_20.1: init bool = call %Neq.ref.loc8(%.loc8_21, %.loc8_24) [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_14.1: bool = value_of_initializer %.loc8_20.1 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc8_20.2: bool = converted %.loc8_20.1, %.loc8_14.1 [template = constants.%.5]
+// CHECK:STDOUT:   if %.loc8_20.2 br !if.expr.then.loc8 else br !if.expr.else.loc8
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc8:
+// CHECK:STDOUT:   %True.ref.loc8: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc8(%True.ref.loc8)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc8:
+// CHECK:STDOUT:   %False.ref.loc8: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc8(%False.ref.loc8)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc8:
+// CHECK:STDOUT:   %.loc8_14.2: type = block_arg !if.expr.result.loc8 [template = constants.%False]
+// CHECK:STDOUT:   %true_.ref: True = name_ref true_, %true_
+// CHECK:STDOUT:   %Neq.ref.loc9: <function> = name_ref Neq, file.%Neq [template = file.%Neq]
+// CHECK:STDOUT:   %.loc9_20: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_23: i32 = int_literal 2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_19.1: init bool = call %Neq.ref.loc9(%.loc9_20, %.loc9_23) [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_13.1: bool = value_of_initializer %.loc9_19.1 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc9_19.2: bool = converted %.loc9_19.1, %.loc9_13.1 [template = constants.%.7]
+// CHECK:STDOUT:   if %.loc9_19.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %True.ref.loc9: type = name_ref True, file.%True.decl [template = constants.%True]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%True.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %False.ref.loc9: type = name_ref False, file.%False.decl [template = constants.%False]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%False.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_13.2: type = block_arg !if.expr.result.loc9 [template = constants.%True]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Neq.ref: <function> = name_ref Neq, file.%Neq [template = file.%Neq]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc13_13.1: init bool = call %Neq.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc13_19: bool = value_of_initializer %.loc13_13.1
+// CHECK:STDOUT:   %.loc13_13.2: bool = converted %.loc13_13.1, %.loc13_19
+// CHECK:STDOUT:   return %.loc13_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 75 - 0
toolchain/check/testdata/builtins/int/or.carbon

@@ -0,0 +1,75 @@
+// 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_or.carbon
+
+fn Or(a: i32, b: i32) -> i32 = "int.or";
+
+var arr: [i32; Or(12, 10)];
+let arr_p: [i32; 14]* = &arr;
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return Or(a, b);
+}
+
+// CHECK:STDOUT: --- int_or.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 12 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 10 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 14 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 14] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Or = %Or
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Or: <function> = fn_decl @Or [template] {
+// CHECK:STDOUT:     %a.loc2_7.1: i32 = param a
+// CHECK:STDOUT:     @Or.%a: i32 = bind_name a, %a.loc2_7.1
+// CHECK:STDOUT:     %b.loc2_15.1: i32 = param b
+// CHECK:STDOUT:     @Or.%b: i32 = bind_name b, %b.loc2_15.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Or.ref: <function> = name_ref Or, %Or [template = %Or]
+// CHECK:STDOUT:   %.loc4_19: i32 = int_literal 12 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_23: i32 = int_literal 10 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_18: init i32 = call %Or.ref(%.loc4_19, %.loc4_23) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_26: type = array_type %.loc4_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 14] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 14] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 14 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_20: type = array_type %.loc5_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_21: type = ptr_type [i32; 14] [template = constants.%.5]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 14] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_25: [i32; 14]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 14]* = bind_name arr_p, %.loc5_25
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Or(%a: i32, %b: i32) -> i32 = "int.or";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Or.ref: <function> = name_ref Or, file.%Or [template = file.%Or]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc8_12.1: init i32 = call %Or.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc8_18: i32 = value_of_initializer %.loc8_12.1
+// CHECK:STDOUT:   %.loc8_12.2: i32 = converted %.loc8_12.1, %.loc8_18
+// CHECK:STDOUT:   return %.loc8_12.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 269 - 0
toolchain/check/testdata/builtins/int/right_shift.carbon

@@ -0,0 +1,269 @@
+// 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_right_shift.carbon
+
+fn RightShift(a: i32, b: i32) -> i32 = "int.right_shift";
+
+var arr: [i32; RightShift(22, 2)];
+let arr_p: [i32; 5]* = &arr;
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return RightShift(a, b);
+}
+
+// TODO: Test mixed types for LHS and RHS.
+
+// --- arith_shift.carbon
+
+// TODO: Also test unsigned / logical right shift.
+
+package ArithShift api;
+
+fn RightShift(a: i32, b: i32) -> i32 = "int.right_shift";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+// -1 >> 1 is -1.
+var arr1: [i32; Negate(RightShift(Negate(1), 1))];
+let arr1_p: [i32; 1]* = &arr1;
+
+// -10 >> 2 is -3.
+var arr2: [i32; Negate(RightShift(Negate(10), 2))];
+let arr2_p: [i32; 3]* = &arr2;
+
+// --- fail_bad_shift.carbon
+
+package BadShift api;
+
+fn RightShift(a: i32, b: i32) -> i32 = "int.right_shift";
+fn Negate(a: i32) -> i32 = "int.negate";
+
+// Shift greater than size is disallowed.
+let size_1: i32 = RightShift(1, 31);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:19: ERROR: Shift distance not in range [0, 32) in 1 >> 32.
+// CHECK:STDERR: let size_2: i32 = RightShift(1, 32);
+// CHECK:STDERR:                   ^~~~~~~~~~~
+// CHECK:STDERR:
+let size_2: i32 = RightShift(1, 32);
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+4]]:19: ERROR: Shift distance not in range [0, 32) in 1 >> 33.
+// CHECK:STDERR: let size_3: i32 = RightShift(1, 33);
+// CHECK:STDERR:                   ^~~~~~~~~~~
+// CHECK:STDERR:
+let size_3: i32 = RightShift(1, 33);
+
+// Negative shifts aren't allowed either.
+// CHECK:STDERR: fail_bad_shift.carbon:[[@LINE+3]]:21: ERROR: Shift distance not in range [0, 32) in 1 >> -1.
+// CHECK:STDERR: let negative: i32 = RightShift(1, Negate(1));
+// CHECK:STDERR:                     ^~~~~~~~~~~
+let negative: i32 = RightShift(1, Negate(1));
+
+// CHECK:STDOUT: --- int_right_shift.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 22 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 5 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 5] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .RightShift = %RightShift
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RightShift: <function> = fn_decl @RightShift [template] {
+// CHECK:STDOUT:     %a.loc2_15.1: i32 = param a
+// CHECK:STDOUT:     @RightShift.%a: i32 = bind_name a, %a.loc2_15.1
+// CHECK:STDOUT:     %b.loc2_23.1: i32 = param b
+// CHECK:STDOUT:     @RightShift.%b: i32 = bind_name b, %b.loc2_23.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RightShift.ref: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %.loc4_27: i32 = int_literal 22 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_31: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_26: init i32 = call %RightShift.ref(%.loc4_27, %.loc4_31) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_33: type = array_type %.loc4_26, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 5] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 5] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 5 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_19: type = array_type %.loc5_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_20: type = ptr_type [i32; 5] [template = constants.%.5]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 5] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_24: [i32; 5]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 5]* = bind_name arr_p, %.loc5_24
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RightShift(%a: i32, %b: i32) -> i32 = "int.right_shift";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %RightShift.ref: <function> = name_ref RightShift, file.%RightShift [template = file.%RightShift]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc8_20.1: init i32 = call %RightShift.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc8_26: i32 = value_of_initializer %.loc8_20.1
+// CHECK:STDOUT:   %.loc8_20.2: i32 = converted %.loc8_20.1, %.loc8_26
+// CHECK:STDOUT:   return %.loc8_20.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- arith_shift.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal -1 [template]
+// CHECK:STDOUT:   %.3: type = array_type %.1, i32 [template]
+// CHECK:STDOUT:   %.4: type = ptr_type [i32; 1] [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 10 [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal -10 [template]
+// CHECK:STDOUT:   %.7: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal -3 [template]
+// CHECK:STDOUT:   %.9: i32 = int_literal 3 [template]
+// CHECK:STDOUT:   %.10: type = array_type %.9, i32 [template]
+// CHECK:STDOUT:   %.11: type = ptr_type [i32; 3] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .RightShift = %RightShift
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .arr1 = %arr1
+// CHECK:STDOUT:     .arr2 = %arr2
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RightShift: <function> = fn_decl @RightShift [template] {
+// CHECK:STDOUT:     %a.loc6_15.1: i32 = param a
+// CHECK:STDOUT:     @RightShift.%a: i32 = bind_name a, %a.loc6_15.1
+// CHECK:STDOUT:     %b.loc6_23.1: i32 = param b
+// CHECK:STDOUT:     @RightShift.%b: i32 = bind_name b, %b.loc6_23.1
+// CHECK:STDOUT:     %return.var.loc6: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc7_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc7_11.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate.ref.loc10_17: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %RightShift.ref.loc10: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %Negate.ref.loc10_35: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc10_42: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_41.1: init i32 = call %Negate.ref.loc10_35(%.loc10_42) [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_46: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_34.1: i32 = value_of_initializer %.loc10_41.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_41.2: i32 = converted %.loc10_41.1, %.loc10_34.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_34.2: init i32 = call %RightShift.ref.loc10(%.loc10_41.2, %.loc10_46) [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_23.1: i32 = value_of_initializer %.loc10_34.2 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_34.3: i32 = converted %.loc10_34.2, %.loc10_23.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc10_23.2: init i32 = call %Negate.ref.loc10_17(%.loc10_34.3) [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_49: type = array_type %.loc10_23.2, i32 [template = constants.%.3]
+// CHECK:STDOUT:   %arr1.var: ref [i32; 1] = var arr1
+// CHECK:STDOUT:   %arr1: ref [i32; 1] = bind_name arr1, %arr1.var
+// CHECK:STDOUT:   %.loc11_19: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc11_20: type = array_type %.loc11_19, i32 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc11_21: type = ptr_type [i32; 1] [template = constants.%.4]
+// CHECK:STDOUT:   %arr1.ref: ref [i32; 1] = name_ref arr1, %arr1
+// CHECK:STDOUT:   %.loc11_25: [i32; 1]* = addr_of %arr1.ref
+// CHECK:STDOUT:   %arr1_p: [i32; 1]* = bind_name arr1_p, %.loc11_25
+// CHECK:STDOUT:   %Negate.ref.loc14_17: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %RightShift.ref.loc14: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %Negate.ref.loc14_35: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc14_42: i32 = int_literal 10 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc14_41.1: init i32 = call %Negate.ref.loc14_35(%.loc14_42) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc14_47: i32 = int_literal 2 [template = constants.%.7]
+// CHECK:STDOUT:   %.loc14_34.1: i32 = value_of_initializer %.loc14_41.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc14_41.2: i32 = converted %.loc14_41.1, %.loc14_34.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc14_34.2: init i32 = call %RightShift.ref.loc14(%.loc14_41.2, %.loc14_47) [template = constants.%.8]
+// CHECK:STDOUT:   %.loc14_23.1: i32 = value_of_initializer %.loc14_34.2 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc14_34.3: i32 = converted %.loc14_34.2, %.loc14_23.1 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc14_23.2: init i32 = call %Negate.ref.loc14_17(%.loc14_34.3) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc14_50: type = array_type %.loc14_23.2, i32 [template = constants.%.10]
+// CHECK:STDOUT:   %arr2.var: ref [i32; 3] = var arr2
+// CHECK:STDOUT:   %arr2: ref [i32; 3] = bind_name arr2, %arr2.var
+// CHECK:STDOUT:   %.loc15_19: i32 = int_literal 3 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc15_20: type = array_type %.loc15_19, i32 [template = constants.%.10]
+// CHECK:STDOUT:   %.loc15_21: type = ptr_type [i32; 3] [template = constants.%.11]
+// CHECK:STDOUT:   %arr2.ref: ref [i32; 3] = name_ref arr2, %arr2
+// CHECK:STDOUT:   %.loc15_25: [i32; 3]* = addr_of %arr2.ref
+// CHECK:STDOUT:   %arr2_p: [i32; 3]* = bind_name arr2_p, %.loc15_25
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RightShift(%a: i32, %b: i32) -> i32 = "int.right_shift";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_shift.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 31 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 32 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 33 [template]
+// CHECK:STDOUT:   %.6: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .RightShift = %RightShift
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RightShift: <function> = fn_decl @RightShift [template] {
+// CHECK:STDOUT:     %a.loc4_15.1: i32 = param a
+// CHECK:STDOUT:     @RightShift.%a: i32 = bind_name a, %a.loc4_15.1
+// CHECK:STDOUT:     %b.loc4_23.1: i32 = param b
+// CHECK:STDOUT:     @RightShift.%b: i32 = bind_name b, %b.loc4_23.1
+// CHECK:STDOUT:     %return.var.loc4: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %a.loc5_11.1: i32 = param a
+// CHECK:STDOUT:     @Negate.%a: i32 = bind_name a, %a.loc5_11.1
+// CHECK:STDOUT:     %return.var.loc5: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %RightShift.ref.loc8: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %.loc8_30: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc8_33: i32 = int_literal 31 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc8_29.1: init i32 = call %RightShift.ref.loc8(%.loc8_30, %.loc8_33) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc8_36: i32 = value_of_initializer %.loc8_29.1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc8_29.2: i32 = converted %.loc8_29.1, %.loc8_36 [template = constants.%.3]
+// CHECK:STDOUT:   %size_1: i32 = bind_name size_1, %.loc8_29.2
+// CHECK:STDOUT:   %RightShift.ref.loc13: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %.loc13_30: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc13_33: i32 = int_literal 32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_29.1: init i32 = call %RightShift.ref.loc13(%.loc13_30, %.loc13_33) [template = <error>]
+// CHECK:STDOUT:   %.loc13_36: i32 = value_of_initializer %.loc13_29.1 [template = <error>]
+// CHECK:STDOUT:   %.loc13_29.2: i32 = converted %.loc13_29.1, %.loc13_36 [template = <error>]
+// CHECK:STDOUT:   %size_2: i32 = bind_name size_2, %.loc13_29.2
+// CHECK:STDOUT:   %RightShift.ref.loc18: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %.loc18_30: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc18_33: i32 = int_literal 33 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc18_29.1: init i32 = call %RightShift.ref.loc18(%.loc18_30, %.loc18_33) [template = <error>]
+// CHECK:STDOUT:   %.loc18_36: i32 = value_of_initializer %.loc18_29.1 [template = <error>]
+// CHECK:STDOUT:   %.loc18_29.2: i32 = converted %.loc18_29.1, %.loc18_36 [template = <error>]
+// CHECK:STDOUT:   %size_3: i32 = bind_name size_3, %.loc18_29.2
+// CHECK:STDOUT:   %RightShift.ref.loc24: <function> = name_ref RightShift, %RightShift [template = %RightShift]
+// CHECK:STDOUT:   %.loc24_32: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %Negate.ref: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc24_42: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc24_41.1: init i32 = call %Negate.ref(%.loc24_42) [template = constants.%.6]
+// CHECK:STDOUT:   %.loc24_31.1: i32 = value_of_initializer %.loc24_41.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc24_41.2: i32 = converted %.loc24_41.1, %.loc24_31.1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc24_31.2: init i32 = call %RightShift.ref.loc24(%.loc24_32, %.loc24_41.2) [template = <error>]
+// CHECK:STDOUT:   %.loc24_45: i32 = value_of_initializer %.loc24_31.2 [template = <error>]
+// CHECK:STDOUT:   %.loc24_31.3: i32 = converted %.loc24_31.2, %.loc24_45 [template = <error>]
+// CHECK:STDOUT:   %negative: i32 = bind_name negative, %.loc24_31.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RightShift(%a: i32, %b: i32) -> i32 = "int.right_shift";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%a: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:

+ 0 - 0
toolchain/check/testdata/builtins/int_sub.carbon → toolchain/check/testdata/builtins/int/sub.carbon


+ 75 - 0
toolchain/check/testdata/builtins/int/xor.carbon

@@ -0,0 +1,75 @@
+// 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_xor.carbon
+
+fn Xor(a: i32, b: i32) -> i32 = "int.xor";
+
+var arr: [i32; Xor(12, 10)];
+let arr_p: [i32; 6]* = &arr;
+
+fn RuntimeCall(a: i32, b: i32) -> i32 {
+  return Xor(a, b);
+}
+
+// CHECK:STDOUT: --- int_xor.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 12 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 10 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 6 [template]
+// CHECK:STDOUT:   %.4: type = array_type %.3, i32 [template]
+// CHECK:STDOUT:   %.5: type = ptr_type [i32; 6] [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Xor = %Xor
+// CHECK:STDOUT:     .arr = %arr
+// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Xor: <function> = fn_decl @Xor [template] {
+// CHECK:STDOUT:     %a.loc2_8.1: i32 = param a
+// CHECK:STDOUT:     @Xor.%a: i32 = bind_name a, %a.loc2_8.1
+// CHECK:STDOUT:     %b.loc2_16.1: i32 = param b
+// CHECK:STDOUT:     @Xor.%b: i32 = bind_name b, %b.loc2_16.1
+// CHECK:STDOUT:     %return.var.loc2: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Xor.ref: <function> = name_ref Xor, %Xor [template = %Xor]
+// CHECK:STDOUT:   %.loc4_20: i32 = int_literal 12 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc4_24: i32 = int_literal 10 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc4_19: init i32 = call %Xor.ref(%.loc4_20, %.loc4_24) [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_27: type = array_type %.loc4_19, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %arr.var: ref [i32; 6] = var arr
+// CHECK:STDOUT:   %arr: ref [i32; 6] = bind_name arr, %arr.var
+// CHECK:STDOUT:   %.loc5_18: i32 = int_literal 6 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc5_19: type = array_type %.loc5_18, i32 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5_20: type = ptr_type [i32; 6] [template = constants.%.5]
+// CHECK:STDOUT:   %arr.ref: ref [i32; 6] = name_ref arr, %arr
+// CHECK:STDOUT:   %.loc5_24: [i32; 6]* = addr_of %arr.ref
+// CHECK:STDOUT:   %arr_p: [i32; 6]* = bind_name arr_p, %.loc5_24
+// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
+// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
+// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc7_16.1
+// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
+// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc7_24.1
+// CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Xor(%a: i32, %b: i32) -> i32 = "int.xor";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Xor.ref: <function> = name_ref Xor, file.%Xor [template = file.%Xor]
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc8_13.1: init i32 = call %Xor.ref(%a.ref, %b.ref)
+// CHECK:STDOUT:   %.loc8_19: i32 = value_of_initializer %.loc8_13.1
+// CHECK:STDOUT:   %.loc8_13.2: i32 = converted %.loc8_13.1, %.loc8_19
+// CHECK:STDOUT:   return %.loc8_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 0 - 101
toolchain/check/testdata/builtins/int_eq.carbon

@@ -1,101 +0,0 @@
-// 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_eq.carbon
-
-fn Eq(a: i32, b: i32) -> bool = "int.eq";
-
-// TODO: Use a test that will fail to compile if we get the wrong value.
-let b_true: bool = Eq(1, 1);
-let b_false: bool = Eq(1, 2);
-
-fn RuntimeCall(a: i32, b: i32) -> bool {
-  return Eq(a, b);
-}
-
-// --- fail_bad_decl.carbon
-
-package FailBadDecl api;
-
-// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+3]]:1: ERROR: Invalid signature for builtin function "int.eq".
-// CHECK:STDERR: fn WrongResult(a: i32, b: i32) -> i32 = "int.eq";
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-fn WrongResult(a: i32, b: i32) -> i32 = "int.eq";
-
-// CHECK:STDOUT: --- int_eq.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
-// CHECK:STDOUT:   %.2: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.3: i32 = int_literal 2 [template]
-// CHECK:STDOUT:   %.4: bool = bool_literal false [template]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {
-// CHECK:STDOUT:     .Eq = %Eq
-// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Eq: <function> = fn_decl @Eq [template] {
-// CHECK:STDOUT:     %a.loc2_7.1: i32 = param a
-// CHECK:STDOUT:     @Eq.%a: i32 = bind_name a, %a.loc2_7.1
-// CHECK:STDOUT:     %b.loc2_15.1: i32 = param b
-// CHECK:STDOUT:     @Eq.%b: i32 = bind_name b, %b.loc2_15.1
-// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Eq.ref.loc5: <function> = name_ref Eq, %Eq [template = %Eq]
-// CHECK:STDOUT:   %.loc5_23: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc5_26: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc5_22.1: init bool = call %Eq.ref.loc5(%.loc5_23, %.loc5_26) [template = constants.%.2]
-// CHECK:STDOUT:   %.loc5_28: bool = value_of_initializer %.loc5_22.1 [template = constants.%.2]
-// CHECK:STDOUT:   %.loc5_22.2: bool = converted %.loc5_22.1, %.loc5_28 [template = constants.%.2]
-// CHECK:STDOUT:   %b_true: bool = bind_name b_true, %.loc5_22.2
-// CHECK:STDOUT:   %Eq.ref.loc6: <function> = name_ref Eq, %Eq [template = %Eq]
-// CHECK:STDOUT:   %.loc6_24: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc6_27: i32 = int_literal 2 [template = constants.%.3]
-// CHECK:STDOUT:   %.loc6_23.1: init bool = call %Eq.ref.loc6(%.loc6_24, %.loc6_27) [template = constants.%.4]
-// CHECK:STDOUT:   %.loc6_29: bool = value_of_initializer %.loc6_23.1 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc6_23.2: bool = converted %.loc6_23.1, %.loc6_29 [template = constants.%.4]
-// CHECK:STDOUT:   %b_false: bool = bind_name b_false, %.loc6_23.2
-// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
-// CHECK:STDOUT:     %a.loc8_16.1: i32 = param a
-// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc8_16.1
-// CHECK:STDOUT:     %b.loc8_24.1: i32 = param b
-// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc8_24.1
-// CHECK:STDOUT:     %return.var.loc8: ref bool = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @Eq(%a: i32, %b: i32) -> bool = "int.eq";
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Eq.ref: <function> = name_ref Eq, file.%Eq [template = file.%Eq]
-// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
-// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
-// CHECK:STDOUT:   %.loc9_12.1: init bool = call %Eq.ref(%a.ref, %b.ref)
-// CHECK:STDOUT:   %.loc9_18: bool = value_of_initializer %.loc9_12.1
-// CHECK:STDOUT:   %.loc9_12.2: bool = converted %.loc9_12.1, %.loc9_18
-// CHECK:STDOUT:   return %.loc9_12.2
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_bad_decl.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {
-// CHECK:STDOUT:     .WrongResult = %WrongResult
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %WrongResult: <function> = fn_decl @WrongResult [template] {
-// CHECK:STDOUT:     %a.loc7_16.1: i32 = param a
-// CHECK:STDOUT:     @WrongResult.%a: i32 = bind_name a, %a.loc7_16.1
-// CHECK:STDOUT:     %b.loc7_24.1: i32 = param b
-// CHECK:STDOUT:     @WrongResult.%b: i32 = bind_name b, %b.loc7_24.1
-// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @WrongResult(%a: i32, %b: i32) -> i32;
-// CHECK:STDOUT:

+ 0 - 75
toolchain/check/testdata/builtins/int_neq.carbon

@@ -1,75 +0,0 @@
-// 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_neq.carbon
-
-fn Neq(a: i32, b: i32) -> bool = "int.neq";
-
-// TODO: Use a test that will fail to compile if we get the wrong value.
-let b_true: bool = Neq(1, 2);
-let b_false: bool = Neq(1, 1);
-
-fn RuntimeCall(a: i32, b: i32) -> bool {
-  return Neq(a, b);
-}
-
-// CHECK:STDOUT: --- int_neq.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: bool = bool_literal true [template]
-// CHECK:STDOUT:   %.4: bool = bool_literal false [template]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {
-// CHECK:STDOUT:     .Neq = %Neq
-// CHECK:STDOUT:     .RuntimeCall = %RuntimeCall
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Neq: <function> = fn_decl @Neq [template] {
-// CHECK:STDOUT:     %a.loc2_8.1: i32 = param a
-// CHECK:STDOUT:     @Neq.%a: i32 = bind_name a, %a.loc2_8.1
-// CHECK:STDOUT:     %b.loc2_16.1: i32 = param b
-// CHECK:STDOUT:     @Neq.%b: i32 = bind_name b, %b.loc2_16.1
-// CHECK:STDOUT:     %return.var.loc2: ref bool = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Neq.ref.loc5: <function> = name_ref Neq, %Neq [template = %Neq]
-// CHECK:STDOUT:   %.loc5_24: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc5_27: i32 = int_literal 2 [template = constants.%.2]
-// CHECK:STDOUT:   %.loc5_23.1: init bool = call %Neq.ref.loc5(%.loc5_24, %.loc5_27) [template = constants.%.3]
-// CHECK:STDOUT:   %.loc5_29: bool = value_of_initializer %.loc5_23.1 [template = constants.%.3]
-// CHECK:STDOUT:   %.loc5_23.2: bool = converted %.loc5_23.1, %.loc5_29 [template = constants.%.3]
-// CHECK:STDOUT:   %b_true: bool = bind_name b_true, %.loc5_23.2
-// CHECK:STDOUT:   %Neq.ref.loc6: <function> = name_ref Neq, %Neq [template = %Neq]
-// CHECK:STDOUT:   %.loc6_25: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc6_28: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc6_24.1: init bool = call %Neq.ref.loc6(%.loc6_25, %.loc6_28) [template = constants.%.4]
-// CHECK:STDOUT:   %.loc6_30: bool = value_of_initializer %.loc6_24.1 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc6_24.2: bool = converted %.loc6_24.1, %.loc6_30 [template = constants.%.4]
-// CHECK:STDOUT:   %b_false: bool = bind_name b_false, %.loc6_24.2
-// CHECK:STDOUT:   %RuntimeCall: <function> = fn_decl @RuntimeCall [template] {
-// CHECK:STDOUT:     %a.loc8_16.1: i32 = param a
-// CHECK:STDOUT:     @RuntimeCall.%a: i32 = bind_name a, %a.loc8_16.1
-// CHECK:STDOUT:     %b.loc8_24.1: i32 = param b
-// CHECK:STDOUT:     @RuntimeCall.%b: i32 = bind_name b, %b.loc8_24.1
-// CHECK:STDOUT:     %return.var.loc8: ref bool = var <return slot>
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @Neq(%a: i32, %b: i32) -> bool = "int.neq";
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @RuntimeCall(%a: i32, %b: i32) -> bool {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Neq.ref: <function> = name_ref Neq, file.%Neq [template = file.%Neq]
-// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
-// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
-// CHECK:STDOUT:   %.loc9_13.1: init bool = call %Neq.ref(%a.ref, %b.ref)
-// CHECK:STDOUT:   %.loc9_19: bool = value_of_initializer %.loc9_13.1
-// CHECK:STDOUT:   %.loc9_13.2: bool = converted %.loc9_13.1, %.loc9_19
-// CHECK:STDOUT:   return %.loc9_13.2
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -246,6 +246,7 @@ CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CompileTimeDivisionByZero)
 CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerOverflow)
 CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerNegateOverflow)
+CARBON_DIAGNOSTIC_KIND(CompileTimeShiftOutOfRange)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType)
 CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer)

+ 81 - 7
toolchain/lower/handle.cpp

@@ -12,7 +12,6 @@
 #include "llvm/Support/Casting.h"
 #include "toolchain/lower/function_context.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
-#include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -168,6 +167,36 @@ auto HandleBuiltin(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
   CARBON_FATAL() << "TODO: Add support: " << inst;
 }
 
+// Get the predicate to use for an `icmp` instruction generated for the
+// specified builtin.
+static auto GetBuiltinICmpPredicate(SemIR::BuiltinFunctionKind builtin_kind,
+                                    bool is_signed)
+    -> llvm::CmpInst::Predicate {
+  switch (builtin_kind) {
+    case SemIR::BuiltinFunctionKind::IntEq:
+      return llvm::CmpInst::ICMP_EQ;
+    case SemIR::BuiltinFunctionKind::IntNeq:
+      return llvm::CmpInst::ICMP_NE;
+    case SemIR::BuiltinFunctionKind::IntLess:
+      return is_signed ? llvm::CmpInst::ICMP_SLT : llvm::CmpInst::ICMP_ULT;
+    case SemIR::BuiltinFunctionKind::IntLessEq:
+      return is_signed ? llvm::CmpInst::ICMP_SLE : llvm::CmpInst::ICMP_ULE;
+    case SemIR::BuiltinFunctionKind::IntGreater:
+      return is_signed ? llvm::CmpInst::ICMP_SGT : llvm::CmpInst::ICMP_UGT;
+    case SemIR::BuiltinFunctionKind::IntGreaterEq:
+      return is_signed ? llvm::CmpInst::ICMP_SGE : llvm::CmpInst::ICMP_UGE;
+    default:
+      CARBON_FATAL() << "Unexpected builtin kind " << builtin_kind;
+  }
+}
+
+// Returns whether the specified instruction has a signed integer type.
+static auto IsSignedInt(FunctionContext& context, SemIR::InstId int_id)
+    -> bool {
+  return context.sem_ir().types().IsSignedInt(
+      context.sem_ir().insts().Get(int_id).type_id());
+}
+
 // Handles a call to a builtin function.
 static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
                               SemIR::BuiltinFunctionKind builtin_kind,
@@ -191,6 +220,15 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
                            /*HasNSW=*/SignedOverflowIsUB));
       return;
     }
+    case SemIR::BuiltinFunctionKind::IntComplement: {
+      // Lower `^x` as `-1 ^ x`.
+      auto* operand = context.GetValue(arg_ids[0]);
+      context.SetLocal(inst_id,
+                       context.builder().CreateXor(
+                           llvm::ConstantInt::getSigned(operand->getType(), -1),
+                           operand, "cmpl"));
+      return;
+    }
     case SemIR::BuiltinFunctionKind::IntAdd: {
       context.SetLocal(inst_id, context.builder().CreateAdd(
                                     context.GetValue(arg_ids[0]),
@@ -227,16 +265,52 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
                                     context.GetValue(arg_ids[1]), "rem"));
       return;
     }
-    case SemIR::BuiltinFunctionKind::IntEq: {
-      context.SetLocal(inst_id, context.builder().CreateICmpEQ(
+    case SemIR::BuiltinFunctionKind::IntAnd: {
+      context.SetLocal(inst_id, context.builder().CreateAnd(
                                     context.GetValue(arg_ids[0]),
-                                    context.GetValue(arg_ids[1]), "eq"));
+                                    context.GetValue(arg_ids[1]), "and"));
       return;
     }
-    case SemIR::BuiltinFunctionKind::IntNeq: {
-      context.SetLocal(inst_id, context.builder().CreateICmpNE(
+    case SemIR::BuiltinFunctionKind::IntOr: {
+      context.SetLocal(inst_id, context.builder().CreateOr(
                                     context.GetValue(arg_ids[0]),
-                                    context.GetValue(arg_ids[1]), "neq"));
+                                    context.GetValue(arg_ids[1]), "or"));
+      return;
+    }
+    case SemIR::BuiltinFunctionKind::IntXor: {
+      context.SetLocal(inst_id, context.builder().CreateXor(
+                                    context.GetValue(arg_ids[0]),
+                                    context.GetValue(arg_ids[1]), "xor"));
+      return;
+    }
+    case SemIR::BuiltinFunctionKind::IntLeftShift: {
+      context.SetLocal(inst_id, context.builder().CreateShl(
+                                    context.GetValue(arg_ids[0]),
+                                    context.GetValue(arg_ids[1]), "shl"));
+      return;
+    }
+    case SemIR::BuiltinFunctionKind::IntRightShift: {
+      context.SetLocal(inst_id, IsSignedInt(context, inst_id)
+                                    ? context.builder().CreateAShr(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "shr")
+                                    : context.builder().CreateLShr(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "shr"));
+      return;
+    }
+    case SemIR::BuiltinFunctionKind::IntEq:
+    case SemIR::BuiltinFunctionKind::IntNeq:
+    case SemIR::BuiltinFunctionKind::IntLess:
+    case SemIR::BuiltinFunctionKind::IntLessEq:
+    case SemIR::BuiltinFunctionKind::IntGreater:
+    case SemIR::BuiltinFunctionKind::IntGreaterEq: {
+      context.SetLocal(inst_id,
+                       context.builder().CreateICmp(
+                           GetBuiltinICmpPredicate(
+                               builtin_kind, IsSignedInt(context, arg_ids[0])),
+                           context.GetValue(arg_ids[0]),
+                           context.GetValue(arg_ids[1]), "cmp"));
       return;
     }
   }

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

@@ -4,13 +4,149 @@
 //
 // AUTOUPDATE
 
+fn Negate(a: i32) -> i32 = "int.negate";
+fn TestNegate(a: i32) -> i32 { return Negate(a); }
+
 fn Add(a: i32, b: i32) -> i32 = "int.add";
 fn TestAdd(a: i32, b: i32) -> i32 { return Add(a, b); }
 
+fn Sub(a: i32, b: i32) -> i32 = "int.sub";
+fn TestSub(a: i32, b: i32) -> i32 { return Sub(a, b); }
+
+fn Mul(a: i32, b: i32) -> i32 = "int.mul";
+fn TestMul(a: i32, b: i32) -> i32 { return Mul(a, b); }
+
+fn Div(a: i32, b: i32) -> i32 = "int.div";
+fn TestDiv(a: i32, b: i32) -> i32 { return Div(a, b); }
+
+fn Mod(a: i32, b: i32) -> i32 = "int.mod";
+fn TestMod(a: i32, b: i32) -> i32 { return Mod(a, b); }
+
+fn Complement(a: i32) -> i32 = "int.complement";
+fn TestComplement(a: i32) -> i32 { return Complement(a); }
+
+fn And(a: i32, b: i32) -> i32 = "int.and";
+fn TestAnd(a: i32, b: i32) -> i32 { return And(a, b); }
+
+fn Or(a: i32, b: i32) -> i32 = "int.or";
+fn TestOr(a: i32, b: i32) -> i32 { return Or(a, b); }
+
+fn Xor(a: i32, b: i32) -> i32 = "int.xor";
+fn TestXor(a: i32, b: i32) -> i32 { return Xor(a, b); }
+
+fn LeftShift(a: i32, b: i32) -> i32 = "int.left_shift";
+fn TestLeftShift(a: i32, b: i32) -> i32 { return LeftShift(a, b); }
+
+fn RightShift(a: i32, b: i32) -> i32 = "int.right_shift";
+fn TestRightShift(a: i32, b: i32) -> i32 { return RightShift(a, b); }
+
+fn Eq(a: i32, b: i32) -> bool = "int.eq";
+fn TestEq(a: i32, b: i32) -> bool { return Eq(a, b); }
+
+fn Neq(a: i32, b: i32) -> bool = "int.neq";
+fn TestNeq(a: i32, b: i32) -> bool { return Neq(a, b); }
+
+fn Less(a: i32, b: i32) -> bool = "int.less";
+fn TestLess(a: i32, b: i32) -> bool { return Less(a, b); }
+
+fn LessEq(a: i32, b: i32) -> bool = "int.less_eq";
+fn TestLessEq(a: i32, b: i32) -> bool { return LessEq(a, b); }
+
+fn Greater(a: i32, b: i32) -> bool = "int.greater";
+fn TestGreater(a: i32, b: i32) -> bool { return Greater(a, b); }
+
+fn GreaterEq(a: i32, b: i32) -> bool = "int.greater_eq";
+fn TestGreaterEq(a: i32, b: i32) -> bool { return GreaterEq(a, b); }
+
 // CHECK:STDOUT: ; ModuleID = 'int.carbon'
 // CHECK:STDOUT: source_filename = "int.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestNegate(i32 %a) {
+// CHECK:STDOUT:   %neg = sub i32 0, %a
+// CHECK:STDOUT:   ret i32 %neg
+// CHECK:STDOUT: }
+// 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: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestSub(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %sub = sub i32 %a, %b
+// CHECK:STDOUT:   ret i32 %sub
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestMul(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %mul = mul i32 %a, %b
+// CHECK:STDOUT:   ret i32 %mul
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestDiv(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %div = sdiv i32 %a, %b
+// CHECK:STDOUT:   ret i32 %div
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestMod(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %rem = srem i32 %a, %b
+// CHECK:STDOUT:   ret i32 %rem
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestComplement(i32 %a) {
+// CHECK:STDOUT:   %cmpl = xor i32 -1, %a
+// CHECK:STDOUT:   ret i32 %cmpl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestAnd(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %and = and i32 %a, %b
+// CHECK:STDOUT:   ret i32 %and
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestOr(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %or = or i32 %a, %b
+// CHECK:STDOUT:   ret i32 %or
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestXor(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %xor = xor i32 %a, %b
+// CHECK:STDOUT:   ret i32 %xor
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestLeftShift(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %shl = shl i32 %a, %b
+// CHECK:STDOUT:   ret i32 %shl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @TestRightShift(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %shr = ashr i32 %a, %b
+// CHECK:STDOUT:   ret i32 %shr
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestEq(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp eq i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestNeq(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp ne i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestLess(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp slt i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestLessEq(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp sle i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestGreater(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp sgt i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestGreaterEq(i32 %a, i32 %b) {
+// CHECK:STDOUT:   %cmp = icmp sge i32 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }

+ 46 - 2
toolchain/sem_ir/builtin_function_kind.cpp

@@ -24,14 +24,14 @@ struct BuiltinInfo {
 };
 
 // The maximum number of type parameters any builtin needs.
-constexpr int MaxTypeParams = 1;
+constexpr int MaxTypeParams = 2;
 
 // State used when validating a builtin signature that persists between
 // individual checks.
 struct ValidateState {
   // The type values of type parameters in the builtin signature. Invalid if
   // either no value has been deduced yet or the parameter is not used.
-  TypeId type_params[MaxTypeParams] = {TypeId::Invalid};
+  TypeId type_params[MaxTypeParams] = {TypeId::Invalid, TypeId::Invalid};
 };
 
 // Constraint that a type is generic type parameter `I` of the builtin,
@@ -125,6 +125,10 @@ namespace BuiltinFunctionInfo {
 // generic type parameter that is constrained to be an integer type.
 using IntT = TypeParam<0, AnyInt>;
 
+// Convenience name used in the builtin type signatures below for a second
+// generic type parameter that is constrained to be an integer type.
+using IntU = TypeParam<1, AnyInt>;
+
 // Not a builtin function.
 constexpr BuiltinInfo None = {"", nullptr};
 
@@ -152,6 +156,30 @@ constexpr BuiltinInfo IntDiv = {"int.div",
 constexpr BuiltinInfo IntMod = {"int.mod",
                                 ValidateSignature<auto(IntT, IntT)->IntT>};
 
+// "int.complement": integer bitwise complement.
+constexpr BuiltinInfo IntComplement = {"int.complement",
+                                       ValidateSignature<auto(IntT)->IntT>};
+
+// "int.and": integer bitwise and.
+constexpr BuiltinInfo IntAnd = {"int.and",
+                                ValidateSignature<auto(IntT, IntT)->IntT>};
+
+// "int.or": integer bitwise or.
+constexpr BuiltinInfo IntOr = {"int.or",
+                               ValidateSignature<auto(IntT, IntT)->IntT>};
+
+// "int.xor": integer bitwise xor.
+constexpr BuiltinInfo IntXor = {"int.xor",
+                                ValidateSignature<auto(IntT, IntT)->IntT>};
+
+// "int.left_shift": integer left shift.
+constexpr BuiltinInfo IntLeftShift = {
+    "int.left_shift", ValidateSignature<auto(IntT, IntU)->IntT>};
+
+// "int.left_shift": integer right shift.
+constexpr BuiltinInfo IntRightShift = {
+    "int.right_shift", ValidateSignature<auto(IntT, IntU)->IntT>};
+
 // "int.eq": integer equality comparison.
 constexpr BuiltinInfo IntEq = {"int.eq",
                                ValidateSignature<auto(IntT, IntT)->Bool>};
@@ -160,6 +188,22 @@ constexpr BuiltinInfo IntEq = {"int.eq",
 constexpr BuiltinInfo IntNeq = {"int.neq",
                                 ValidateSignature<auto(IntT, IntT)->Bool>};
 
+// "int.less": integer less than comparison.
+constexpr BuiltinInfo IntLess = {"int.less",
+                                 ValidateSignature<auto(IntT, IntT)->Bool>};
+
+// "int.less_eq": integer less than or equal comparison.
+constexpr BuiltinInfo IntLessEq = {"int.less_eq",
+                                   ValidateSignature<auto(IntT, IntT)->Bool>};
+
+// "int.greater": integer greater than comparison.
+constexpr BuiltinInfo IntGreater = {"int.greater",
+                                    ValidateSignature<auto(IntT, IntT)->Bool>};
+
+// "int.greater_eq": integer greater than or equal comparison.
+constexpr BuiltinInfo IntGreaterEq = {
+    "int.greater_eq", ValidateSignature<auto(IntT, IntT)->Bool>};
+
 }  // namespace BuiltinFunctionInfo
 
 CARBON_DEFINE_ENUM_CLASS_NAMES(BuiltinFunctionKind) = {

+ 16 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -17,13 +17,29 @@
 #endif
 
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
+
+// Integer arithmetic.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntNegate)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntAdd)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntSub)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMul)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntDiv)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMod)
+
+// Integer bitwise.
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntComplement)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntAnd)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntOr)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntXor)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntLeftShift)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntRightShift)
+
+// Integer comparison.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntEq)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntNeq)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntLess)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntLessEq)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntGreater)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntGreaterEq)
 
 #undef CARBON_SEM_IR_BUILTIN_FUNCTION_KIND