Przeglądaj źródła

Add support for `iN` and `uN` for all suitable `N`. (#3868)

`i32` is retained as a special case for now, for bootstrapping purposes,
and maps to `BuiltinIntType`, which is distinct from `Core.Int(32)`.
This will be removed later once we support `Core.BigInt`.

For now this provides both the `iN` types and also the builtins to
support `Core.Int(N)`. The intent is that we'll change the `iN` support
to rewrite to calls here when we do that for the other type literals and
type keywords.

No conversions between integer types are supported yet, and all literals
are of type `i32`, so we can't actually form values of any of these new
types.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Richard Smith 2 lat temu
rodzic
commit
bb117aea3a

+ 1 - 0
toolchain/check/context.cpp

@@ -1049,6 +1049,7 @@ class TypeCompleter {
 
       case SemIR::AssociatedEntityType::Kind:
       case SemIR::BindSymbolicName::Kind:
+      case SemIR::IntType::Kind:
       case SemIR::PointerType::Kind:
       case SemIR::UnboundElementType::Kind:
         return MakeCopyValueRepr(type_id);

+ 68 - 0
toolchain/check/eval.cpp

@@ -302,6 +302,56 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
   return context.constant_values().Get(elements[index_val.getZExtValue()]);
 }
 
+// Enforces that an integer type has a valid bit width.
+auto ValidateIntType(Context& context, SemIRLoc loc, SemIR::IntType result)
+    -> bool {
+  auto bit_width =
+      context.insts().TryGetAs<SemIR::IntLiteral>(result.bit_width_id);
+  if (!bit_width) {
+    // Symbolic bit width.
+    return true;
+  }
+  const auto& bit_width_val = context.ints().Get(bit_width->int_id);
+  if (bit_width_val.isZero() ||
+      (context.types().IsSignedInt(bit_width->type_id) &&
+       bit_width_val.isNegative())) {
+    CARBON_DIAGNOSTIC(IntWidthNotPositive, Error,
+                      "Integer type width of {0} is not positive.", TypedInt);
+    context.emitter().Emit(loc, IntWidthNotPositive,
+                           TypedInt{bit_width->type_id, bit_width_val});
+    return false;
+  }
+  // TODO: Pick a maximum size and document it in the design. For now
+  // we use 2^^23, because that's the largest size that LLVM supports.
+  constexpr int MaxIntWidth = 1 << 23;
+  if (bit_width_val.ugt(MaxIntWidth)) {
+    CARBON_DIAGNOSTIC(IntWidthTooLarge, Error,
+                      "Integer type width of {0} is greater than the "
+                      "maximum supported width of {1}.",
+                      TypedInt, int);
+    context.emitter().Emit(loc, IntWidthTooLarge,
+                           TypedInt{bit_width->type_id, bit_width_val},
+                           MaxIntWidth);
+    return false;
+  }
+  return true;
+}
+
+// Forms a constant int type as an evaluation result. Requires that width_id is
+// constant.
+auto MakeIntTypeResult(Context& context, SemIRLoc loc, SemIR::IntKind int_kind,
+                       SemIR::InstId width_id, Phase phase)
+    -> SemIR::ConstantId {
+  auto result = SemIR::IntType{
+      .type_id = context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
+      .int_kind = int_kind,
+      .bit_width_id = width_id};
+  if (!ValidateIntType(context, loc, result)) {
+    return SemIR::ConstantId::Error;
+  }
+  return MakeConstantResult(context, result, phase);
+}
+
 // Enforces that the bit width is 64 for a float.
 static auto ValidateFloatBitWidth(Context& context, SemIRLoc loc,
                                   SemIR::InstId inst_id) -> bool {
@@ -514,6 +564,16 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
       return context.constant_values().Get(SemIR::InstId::BuiltinIntType);
     }
 
+    case SemIR::BuiltinFunctionKind::IntMakeTypeSigned: {
+      return MakeIntTypeResult(context, loc, SemIR::IntKind::Signed, arg_ids[0],
+                               phase);
+    }
+
+    case SemIR::BuiltinFunctionKind::IntMakeTypeUnsigned: {
+      return MakeIntTypeResult(context, loc, SemIR::IntKind::Unsigned,
+                               arg_ids[0], phase);
+    }
+
     case SemIR::BuiltinFunctionKind::FloatMakeType: {
       // TODO: Support a symbolic constant width.
       if (phase != Phase::Template) {
@@ -663,6 +723,14 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::InterfaceWitness::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::InterfaceWitness::elements_id);
+    case SemIR::IntType::Kind:
+      return RebuildAndValidateIfFieldsAreConstant(
+          context, inst,
+          [&](SemIR::IntType result) {
+            return ValidateIntType(
+                context, inst.As<SemIR::IntType>().bit_width_id, result);
+          },
+          &SemIR::IntType::bit_width_id);
     case SemIR::PointerType::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::PointerType::pointee_id);

+ 57 - 23
toolchain/check/handle_literal.cpp

@@ -4,6 +4,7 @@
 
 #include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
@@ -25,27 +26,34 @@ auto HandleBoolLiteralTrue(Context& context, Parse::BoolLiteralTrueId node_id)
   return true;
 }
 
-auto HandleIntLiteral(Context& context, Parse::IntLiteralId node_id) -> bool {
-  // Convert the literal to i32.
-  // TODO: Form an integer literal value and a corresponding type here instead.
-  auto literal_int_id =
-      context.tokens().GetIntLiteral(context.parse_tree().node_token(node_id));
-  auto literal_val = context.ints().Get(literal_int_id);
-  if (literal_val.getActiveBits() > 31) {
+// Forms an IntLiteral instruction with type `i32` for a given literal integer
+// value, which is assumed to be unsigned.
+static auto MakeI32Literal(Context& context, Parse::NodeId node_id,
+                           IntId int_id) -> SemIR::InstId {
+  auto val = context.ints().Get(int_id);
+  if (val.getActiveBits() > 31) {
     CARBON_DIAGNOSTIC(IntLiteralTooLargeForI32, Error,
                       "Integer literal with value {0} does not fit in i32.",
                       llvm::APSInt);
     context.emitter().Emit(node_id, IntLiteralTooLargeForI32,
-                           llvm::APSInt(literal_val, /*isUnsigned=*/true));
-    context.node_stack().Push(node_id, SemIR::InstId::BuiltinError);
-    return true;
+                           llvm::APSInt(val, /*isUnsigned=*/true));
+    return SemIR::InstId::BuiltinError;
   }
   // Literals are always represented as unsigned, so zero-extend if needed.
-  auto i32_val = literal_val.zextOrTrunc(32);
-  context.AddInstAndPush(
+  auto i32_val = val.zextOrTrunc(32);
+  return context.AddInst(
       {node_id,
        SemIR::IntLiteral{context.GetBuiltinType(SemIR::BuiltinKind::IntType),
                          context.ints().Add(i32_val)}});
+}
+
+auto HandleIntLiteral(Context& context, Parse::IntLiteralId node_id) -> bool {
+  // Convert the literal to i32.
+  // TODO: Form an integer literal value and a corresponding type here instead.
+  auto int_literal_id = MakeI32Literal(
+      context, node_id,
+      context.tokens().GetIntLiteral(context.parse_tree().node_token(node_id)));
+  context.node_stack().Push(node_id, int_literal_id);
   return true;
 }
 
@@ -78,25 +86,51 @@ auto HandleBoolTypeLiteral(Context& context, Parse::BoolTypeLiteralId node_id)
   return true;
 }
 
+// Shared implementation for handling `iN` and `uN` literals.
+static auto HandleIntOrUnsignedIntTypeLiteral(Context& context,
+                                              Parse::NodeId node_id,
+                                              SemIR::IntKind int_kind,
+                                              IntId size_id) -> bool {
+  if (!(context.ints().Get(size_id) & 3).isZero()) {
+    CARBON_DIAGNOSTIC(IntWidthNotMultipleOf8, Error,
+                      "Bit width of integer type literal must be a multiple of "
+                      "8. Use `Core.{0}({1})` instead.",
+                      std::string, llvm::APSInt);
+    context.emitter().Emit(
+        node_id, IntWidthNotMultipleOf8, int_kind.is_signed() ? "Int" : "UInt",
+        llvm::APSInt(context.ints().Get(size_id), /*isUnsigned=*/true));
+  }
+  // TODO: Migrate to a call to `Core.Int` or `Core.UInt`.
+  auto width_id = MakeI32Literal(context, node_id, size_id);
+  context.AddInstAndPush(
+      {node_id, SemIR::IntType{.type_id = context.GetBuiltinType(
+                                   SemIR::BuiltinKind::TypeType),
+                               .int_kind = int_kind,
+                               .bit_width_id = width_id}});
+  return true;
+}
+
 auto HandleIntTypeLiteral(Context& context, Parse::IntTypeLiteralId node_id)
     -> bool {
-  auto text =
-      context.tokens().GetTokenText(context.parse_tree().node_token(node_id));
-  if (text != "i32") {
-    return context.TODO(node_id, "Currently only i32 is allowed");
+  auto tok_id = context.parse_tree().node_token(node_id);
+  auto size_id = context.tokens().GetTypeLiteralSize(tok_id);
+  // Special case: `i32` has a custom builtin for now.
+  // TODO: Remove this special case.
+  if (context.ints().Get(size_id) == 32) {
+    context.node_stack().Push(node_id, SemIR::InstId::BuiltinIntType);
+    return true;
   }
-  // TODO: Migrate once functions can be in prelude.carbon.
-  // auto fn_inst_id = context.LookupNameInCore(node_id, "Int32");
-  // auto type_inst_id = PerformCall(context, node_id, fn_inst_id, {});
-  // context.node_stack().Push(node_id, type_inst_id);
-  context.node_stack().Push(node_id, SemIR::InstId::BuiltinIntType);
-  return true;
+  return HandleIntOrUnsignedIntTypeLiteral(context, node_id,
+                                           SemIR::IntKind::Signed, size_id);
 }
 
 auto HandleUnsignedIntTypeLiteral(Context& context,
                                   Parse::UnsignedIntTypeLiteralId node_id)
     -> bool {
-  return context.TODO(node_id, "Need to support unsigned type literals");
+  auto tok_id = context.parse_tree().node_token(node_id);
+  auto size_id = context.tokens().GetTypeLiteralSize(tok_id);
+  return HandleIntOrUnsignedIntTypeLiteral(context, node_id,
+                                           SemIR::IntKind::Unsigned, size_id);
 }
 
 auto HandleFloatTypeLiteral(Context& context, Parse::FloatTypeLiteralId node_id)

+ 263 - 0
toolchain/check/testdata/basics/type_literals.carbon

@@ -0,0 +1,263 @@
+// 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
+
+// --- iN.carbon
+library "iN" api;
+
+var test_i8: i8;
+var test_i16: i16;
+var test_i64: i64;
+
+// --- fail_iN_bad_width.carbon
+library "fail_iN_bad_width" api;
+
+// CHECK:STDERR: fail_iN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Name `i0` not found.
+// CHECK:STDERR: var test_i0: i0;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_i0: i0;
+// CHECK:STDERR: fail_iN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Bit width of integer type literal must be a multiple of 8. Use `Core.Int(1)` instead.
+// CHECK:STDERR: var test_i1: i1;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_i1: i1;
+// CHECK:STDERR: fail_iN_bad_width.carbon:[[@LINE+4]]:15: ERROR: Bit width of integer type literal must be a multiple of 8. Use `Core.Int(15)` instead.
+// CHECK:STDERR: var test_i15: i15;
+// CHECK:STDERR:               ^~~
+// CHECK:STDERR:
+var test_i15: i15;
+// CHECK:STDERR: fail_iN_bad_width.carbon:[[@LINE+4]]:23: ERROR: Integer type width of 1000000000 is greater than the maximum supported width of 8388608.
+// CHECK:STDERR: var test_i1000000000: i1000000000;
+// CHECK:STDERR:                       ^~~~~~~~~~~
+// CHECK:STDERR:
+var test_i1000000000: i1000000000;
+// TODO: This diagnostic is not very good.
+// CHECK:STDERR: fail_iN_bad_width.carbon:[[@LINE+4]]:33: ERROR: Integer literal with value 10000000000000000000 does not fit in i32.
+// CHECK:STDERR: var test_i10000000000000000000: i10000000000000000000;
+// CHECK:STDERR:                                 ^~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var test_i10000000000000000000: i10000000000000000000;
+
+// --- uN.carbon
+library "uN" api;
+
+var test_u8: u8;
+var test_u16: u16;
+var test_u64: u64;
+
+// --- fail_uN_bad_width.carbon
+library "fail_uN_bad_width" api;
+
+// CHECK:STDERR: fail_uN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Name `u0` not found.
+// CHECK:STDERR: var test_u0: u0;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_u0: u0;
+// CHECK:STDERR: fail_uN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Bit width of integer type literal must be a multiple of 8. Use `Core.UInt(1)` instead.
+// CHECK:STDERR: var test_u1: u1;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_u1: u1;
+// CHECK:STDERR: fail_uN_bad_width.carbon:[[@LINE+4]]:15: ERROR: Bit width of integer type literal must be a multiple of 8. Use `Core.UInt(15)` instead.
+// CHECK:STDERR: var test_u15: u15;
+// CHECK:STDERR:               ^~~
+// CHECK:STDERR:
+var test_u15: u15;
+// CHECK:STDERR: fail_uN_bad_width.carbon:[[@LINE+4]]:23: ERROR: Integer type width of 1000000000 is greater than the maximum supported width of 8388608.
+// CHECK:STDERR: var test_u1000000000: u1000000000;
+// CHECK:STDERR:                       ^~~~~~~~~~~
+// CHECK:STDERR:
+var test_u1000000000: u1000000000;
+// TODO: This diagnostic is not very good.
+// CHECK:STDERR: fail_uN_bad_width.carbon:[[@LINE+4]]:33: ERROR: Integer literal with value 10000000000000000000 does not fit in i32.
+// CHECK:STDERR: var test_u10000000000000000000: u10000000000000000000;
+// CHECK:STDERR:                                 ^~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var test_u10000000000000000000: u10000000000000000000;
+
+// --- fail_fN_bad_width.carbon
+library "fail_fN_bad_width" api;
+
+// CHECK:STDERR: fail_fN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Name `f0` not found.
+// CHECK:STDERR: var test_f0: f0;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_f0: f0;
+// CHECK:STDERR: fail_fN_bad_width.carbon:[[@LINE+4]]:14: ERROR: Semantics TODO: `Currently only f64 is allowed`.
+// CHECK:STDERR: var test_f1: f1;
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+var test_f1: f1;
+var test_f15: f15;
+var test_f100: f100;
+var test_f1000000000: f1000000000;
+var test_f1000000000000: f1000000000000;
+
+// --- fail_fN_todo_unsupported.carbon
+library "fail_fN_todo_unsupported" api;
+
+// TODO: Some or all of these should eventually work.
+// CHECK:STDERR: fail_fN_todo_unsupported.carbon:[[@LINE+3]]:15: ERROR: Semantics TODO: `Currently only f64 is allowed`.
+// CHECK:STDERR: var test_f16: f16;
+// CHECK:STDERR:               ^~~
+var test_f16: f16;
+var test_f32: f32;
+var test_f128: f128;
+
+// CHECK:STDOUT: --- iN.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 8 [template]
+// CHECK:STDOUT:   %.2: type = int_type signed, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 16 [template]
+// CHECK:STDOUT:   %.4: type = int_type signed, %.3 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 64 [template]
+// CHECK:STDOUT:   %.6: type = int_type signed, %.5 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .test_i8 = %test_i8
+// CHECK:STDOUT:     .test_i16 = %test_i16
+// CHECK:STDOUT:     .test_i64 = %test_i64
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc3_14.1: i32 = int_literal 8 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc3_14.2: type = int_type signed, %.loc3_14.1 [template = constants.%.2]
+// CHECK:STDOUT:   %test_i8.var: ref i8 = var test_i8
+// CHECK:STDOUT:   %test_i8: ref i8 = bind_name test_i8, %test_i8.var
+// CHECK:STDOUT:   %.loc4_15.1: i32 = int_literal 16 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_15.2: type = int_type signed, %.loc4_15.1 [template = constants.%.4]
+// CHECK:STDOUT:   %test_i16.var: ref i16 = var test_i16
+// CHECK:STDOUT:   %test_i16: ref i16 = bind_name test_i16, %test_i16.var
+// CHECK:STDOUT:   %.loc5_15.1: i32 = int_literal 64 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc5_15.2: type = int_type signed, %.loc5_15.1 [template = constants.%.6]
+// CHECK:STDOUT:   %test_i64.var: ref i64 = var test_i64
+// CHECK:STDOUT:   %test_i64: ref i64 = bind_name test_i64, %test_i64.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_iN_bad_width.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: type = int_type signed, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 15 [template]
+// CHECK:STDOUT:   %.4: type = int_type signed, %.3 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 1000000000 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .test_i0 = %test_i0
+// CHECK:STDOUT:     .test_i1 = %test_i1
+// CHECK:STDOUT:     .test_i15 = %test_i15
+// CHECK:STDOUT:     .test_i1000000000 = %test_i1000000000
+// CHECK:STDOUT:     .test_i10000000000000000000 = %test_i10000000000000000000
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %i0.ref: <error> = name_ref i0, <error> [template = <error>]
+// CHECK:STDOUT:   %test_i0.var: ref <error> = var test_i0
+// CHECK:STDOUT:   %test_i0: ref <error> = bind_name test_i0, %test_i0.var
+// CHECK:STDOUT:   %.loc12_14.1: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc12_14.2: type = int_type signed, %.loc12_14.1 [template = constants.%.2]
+// CHECK:STDOUT:   %test_i1.var: ref i1 = var test_i1
+// CHECK:STDOUT:   %test_i1: ref i1 = bind_name test_i1, %test_i1.var
+// CHECK:STDOUT:   %.loc17_15.1: i32 = int_literal 15 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc17_15.2: type = int_type signed, %.loc17_15.1 [template = constants.%.4]
+// CHECK:STDOUT:   %test_i15.var: ref i15 = var test_i15
+// CHECK:STDOUT:   %test_i15: ref i15 = bind_name test_i15, %test_i15.var
+// CHECK:STDOUT:   %.loc22_23.1: i32 = int_literal 1000000000 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc22_23.2: type = int_type signed, %.loc22_23.1 [template = <error>]
+// CHECK:STDOUT:   %test_i1000000000.var: ref <error> = var test_i1000000000
+// CHECK:STDOUT:   %test_i1000000000: ref <error> = bind_name test_i1000000000, %test_i1000000000.var
+// CHECK:STDOUT:   %.loc28: type = int_type signed, <error> [template = <error>]
+// CHECK:STDOUT:   %test_i10000000000000000000.var: ref <error> = var test_i10000000000000000000
+// CHECK:STDOUT:   %test_i10000000000000000000: ref <error> = bind_name test_i10000000000000000000, %test_i10000000000000000000.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- uN.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 8 [template]
+// CHECK:STDOUT:   %.2: type = int_type unsigned, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 16 [template]
+// CHECK:STDOUT:   %.4: type = int_type unsigned, %.3 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 64 [template]
+// CHECK:STDOUT:   %.6: type = int_type unsigned, %.5 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .test_u8 = %test_u8
+// CHECK:STDOUT:     .test_u16 = %test_u16
+// CHECK:STDOUT:     .test_u64 = %test_u64
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc3_14.1: i32 = int_literal 8 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc3_14.2: type = int_type unsigned, %.loc3_14.1 [template = constants.%.2]
+// CHECK:STDOUT:   %test_u8.var: ref u8 = var test_u8
+// CHECK:STDOUT:   %test_u8: ref u8 = bind_name test_u8, %test_u8.var
+// CHECK:STDOUT:   %.loc4_15.1: i32 = int_literal 16 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc4_15.2: type = int_type unsigned, %.loc4_15.1 [template = constants.%.4]
+// CHECK:STDOUT:   %test_u16.var: ref u16 = var test_u16
+// CHECK:STDOUT:   %test_u16: ref u16 = bind_name test_u16, %test_u16.var
+// CHECK:STDOUT:   %.loc5_15.1: i32 = int_literal 64 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc5_15.2: type = int_type unsigned, %.loc5_15.1 [template = constants.%.6]
+// CHECK:STDOUT:   %test_u64.var: ref u64 = var test_u64
+// CHECK:STDOUT:   %test_u64: ref u64 = bind_name test_u64, %test_u64.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_uN_bad_width.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: type = int_type unsigned, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 15 [template]
+// CHECK:STDOUT:   %.4: type = int_type unsigned, %.3 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 1000000000 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .test_u0 = %test_u0
+// CHECK:STDOUT:     .test_u1 = %test_u1
+// CHECK:STDOUT:     .test_u15 = %test_u15
+// CHECK:STDOUT:     .test_u1000000000 = %test_u1000000000
+// CHECK:STDOUT:     .test_u10000000000000000000 = %test_u10000000000000000000
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %u0.ref: <error> = name_ref u0, <error> [template = <error>]
+// CHECK:STDOUT:   %test_u0.var: ref <error> = var test_u0
+// CHECK:STDOUT:   %test_u0: ref <error> = bind_name test_u0, %test_u0.var
+// CHECK:STDOUT:   %.loc12_14.1: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc12_14.2: type = int_type unsigned, %.loc12_14.1 [template = constants.%.2]
+// CHECK:STDOUT:   %test_u1.var: ref u1 = var test_u1
+// CHECK:STDOUT:   %test_u1: ref u1 = bind_name test_u1, %test_u1.var
+// CHECK:STDOUT:   %.loc17_15.1: i32 = int_literal 15 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc17_15.2: type = int_type unsigned, %.loc17_15.1 [template = constants.%.4]
+// CHECK:STDOUT:   %test_u15.var: ref u15 = var test_u15
+// CHECK:STDOUT:   %test_u15: ref u15 = bind_name test_u15, %test_u15.var
+// CHECK:STDOUT:   %.loc22_23.1: i32 = int_literal 1000000000 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc22_23.2: type = int_type unsigned, %.loc22_23.1 [template = <error>]
+// CHECK:STDOUT:   %test_u1000000000.var: ref <error> = var test_u1000000000
+// CHECK:STDOUT:   %test_u1000000000: ref <error> = bind_name test_u1000000000, %test_u1000000000.var
+// CHECK:STDOUT:   %.loc28: type = int_type unsigned, <error> [template = <error>]
+// CHECK:STDOUT:   %test_u10000000000000000000.var: ref <error> = var test_u10000000000000000000
+// CHECK:STDOUT:   %test_u10000000000000000000: ref <error> = bind_name test_u10000000000000000000, %test_u10000000000000000000.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_fN_bad_width.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_fN_todo_unsupported.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:

+ 261 - 0
toolchain/check/testdata/builtins/int/make_type_signed.carbon

@@ -0,0 +1,261 @@
+// 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
+
+// --- types.carbon
+
+library "types" api;
+
+fn Int(n: i32) -> type = "int.make_type_signed";
+
+// --- use_types.carbon
+
+library "uses_types" api;
+
+import library "types";
+
+fn F(n: Int(64)) -> Int(64) {
+  return n;
+}
+
+fn G(n: Int(13)) -> Int(13) {
+  return n;
+}
+
+fn Symbolic(N:! i32, x: Int(N)) -> Int(N) {
+  return x;
+}
+
+// --- fail_zero_size.carbon
+
+library "fail_zero_size" api;
+
+import library "types";
+
+// CHECK:STDERR: fail_zero_size.carbon:[[@LINE+4]]:8: ERROR: Integer type width of 0 is not positive.
+// CHECK:STDERR: var n: Int(0);
+// CHECK:STDERR:        ^~~~
+// CHECK:STDERR:
+var n: Int(0);
+
+// --- fail_negative_size.carbon
+
+library "fail_negative_size" api;
+
+import library "types";
+
+fn Negate(n: i32) -> i32 = "int.negate";
+
+// CHECK:STDERR: fail_negative_size.carbon:[[@LINE+4]]:8: ERROR: Integer type width of -1 is not positive.
+// CHECK:STDERR: var n: Int(Negate(1));
+// CHECK:STDERR:        ^~~~
+// CHECK:STDERR:
+var n: Int(Negate(1));
+
+// --- fail_oversized.carbon
+
+library "fail_oversized" api;
+
+import library "types";
+
+// CHECK:STDERR: fail_oversized.carbon:[[@LINE+3]]:8: ERROR: Integer type width of 1000000000 is greater than the maximum supported width of 8388608.
+// CHECK:STDERR: var m: Int(1000000000);
+// CHECK:STDERR:        ^~~~
+var m: Int(1000000000);
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Int = %Int
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Int: <function> = fn_decl @Int [template] {
+// CHECK:STDOUT:     %n.loc4_8.1: i32 = param n
+// CHECK:STDOUT:     @Int.%n: i32 = bind_name n, %n.loc4_8.1
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%n: i32) -> type = "int.make_type_signed";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 64 [template]
+// CHECK:STDOUT:   %.2: type = int_type signed, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 13 [template]
+// CHECK:STDOUT:   %.4: type = int_type signed, %.3 [template]
+// CHECK:STDOUT:   %.5: type = int_type signed, @Symbolic.%N [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .G = %G
+// CHECK:STDOUT:     .Symbolic = %Symbolic
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_13 [template = imports.%Int]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %Int.ref.loc6_9: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %.loc6_13: i32 = int_literal 64 [template = constants.%.1]
+// CHECK:STDOUT:     %.loc6_12.1: init type = call %Int.ref.loc6_9(%.loc6_13) [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_15: type = value_of_initializer %.loc6_12.1 [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_12.2: type = converted %.loc6_12.1, %.loc6_15 [template = constants.%.2]
+// CHECK:STDOUT:     %n.loc6_6.1: i64 = param n
+// CHECK:STDOUT:     @F.%n: i64 = bind_name n, %n.loc6_6.1
+// CHECK:STDOUT:     %Int.ref.loc6_21: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %.loc6_25: i32 = int_literal 64 [template = constants.%.1]
+// CHECK:STDOUT:     %.loc6_24.1: init type = call %Int.ref.loc6_21(%.loc6_25) [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_27: type = value_of_initializer %.loc6_24.1 [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_24.2: type = converted %.loc6_24.1, %.loc6_27 [template = constants.%.2]
+// CHECK:STDOUT:     %return.var.loc6: ref i64 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %Int.ref.loc10_9: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %.loc10_13: i32 = int_literal 13 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc10_12.1: init type = call %Int.ref.loc10_9(%.loc10_13) [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_15: type = value_of_initializer %.loc10_12.1 [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_12.2: type = converted %.loc10_12.1, %.loc10_15 [template = constants.%.4]
+// CHECK:STDOUT:     %n.loc10_6.1: i13 = param n
+// CHECK:STDOUT:     @G.%n: i13 = bind_name n, %n.loc10_6.1
+// CHECK:STDOUT:     %Int.ref.loc10_21: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %.loc10_25: i32 = int_literal 13 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc10_24.1: init type = call %Int.ref.loc10_21(%.loc10_25) [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_27: type = value_of_initializer %.loc10_24.1 [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_24.2: type = converted %.loc10_24.1, %.loc10_27 [template = constants.%.4]
+// CHECK:STDOUT:     %return.var.loc10: ref i13 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Symbolic: <function> = fn_decl @Symbolic [template] {
+// CHECK:STDOUT:     %N.loc14_13.1: i32 = param N
+// CHECK:STDOUT:     @Symbolic.%N: i32 = bind_symbolic_name N, %N.loc14_13.1 [symbolic]
+// CHECK:STDOUT:     %Int.ref.loc14_25: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %N.ref.loc14_29: i32 = name_ref N, @Symbolic.%N [symbolic = @Symbolic.%N]
+// CHECK:STDOUT:     %.loc14_28.1: init type = call %Int.ref.loc14_25(%N.ref.loc14_29) [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_30: type = value_of_initializer %.loc14_28.1 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_28.2: type = converted %.loc14_28.1, %.loc14_30 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %x.loc14_22.1: Core.Int(N) = param x
+// CHECK:STDOUT:     @Symbolic.%x: Core.Int(N) = bind_name x, %x.loc14_22.1
+// CHECK:STDOUT:     %Int.ref.loc14_36: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:     %N.ref.loc14_40: i32 = name_ref N, @Symbolic.%N [symbolic = @Symbolic.%N]
+// CHECK:STDOUT:     %.loc14_39.1: init type = call %Int.ref.loc14_36(%N.ref.loc14_40) [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_41: type = value_of_initializer %.loc14_39.1 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_39.2: type = converted %.loc14_39.1, %.loc14_41 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %return.var.loc14: ref Core.Int(N) = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%n: i32) -> type = "int.make_type_signed";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%n: i64) -> i64 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i64 = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%n: i13) -> i13 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i13 = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: Core.Int(N)) -> Core.Int(N) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %x.ref: Core.Int(N) = name_ref x, %x
+// CHECK:STDOUT:   return %x.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_zero_size.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .n = %n
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_11 [template = imports.%Int]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Int.ref: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:   %.loc10_12: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_11.1: init type = call %Int.ref(%.loc10_12) [template = <error>]
+// CHECK:STDOUT:   %.loc10_13: type = value_of_initializer %.loc10_11.1 [template = <error>]
+// CHECK:STDOUT:   %.loc10_11.2: type = converted %.loc10_11.1, %.loc10_13 [template = <error>]
+// CHECK:STDOUT:   %n.var: ref <error> = var n
+// CHECK:STDOUT:   %n: ref <error> = bind_name n, %n.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%n: i32) -> type = "int.make_type_signed";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_negative_size.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .n = %n.loc12
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_23 [template = imports.%Int]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %n.loc6_11.1: i32 = param n
+// CHECK:STDOUT:     @Negate.%n: i32 = bind_name n, %n.loc6_11.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Int.ref: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:   %Negate.ref: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc12_19: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc12_18.1: init i32 = call %Negate.ref(%.loc12_19) [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_11.1: i32 = value_of_initializer %.loc12_18.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_18.2: i32 = converted %.loc12_18.1, %.loc12_11.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_11.2: init type = call %Int.ref(%.loc12_18.2) [template = <error>]
+// CHECK:STDOUT:   %.loc12_21: type = value_of_initializer %.loc12_11.2 [template = <error>]
+// CHECK:STDOUT:   %.loc12_11.3: type = converted %.loc12_11.2, %.loc12_21 [template = <error>]
+// CHECK:STDOUT:   %n.var: ref <error> = var n
+// CHECK:STDOUT:   %n.loc12: ref <error> = bind_name n, %n.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%n: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%n: i32) -> type = "int.make_type_signed";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_oversized.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1000000000 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .m = %m
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_11 [template = imports.%Int]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Int.ref: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:   %.loc9_12: i32 = int_literal 1000000000 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc9_11.1: init type = call %Int.ref(%.loc9_12) [template = <error>]
+// CHECK:STDOUT:   %.loc9_22: type = value_of_initializer %.loc9_11.1 [template = <error>]
+// CHECK:STDOUT:   %.loc9_11.2: type = converted %.loc9_11.1, %.loc9_22 [template = <error>]
+// CHECK:STDOUT:   %m.var: ref <error> = var m
+// CHECK:STDOUT:   %m: ref <error> = bind_name m, %m.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%n: i32) -> type = "int.make_type_signed";
+// CHECK:STDOUT:

+ 261 - 0
toolchain/check/testdata/builtins/int/make_type_unsigned.carbon

@@ -0,0 +1,261 @@
+// 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
+
+// --- types.carbon
+
+library "types" api;
+
+fn UInt(n: i32) -> type = "int.make_type_unsigned";
+
+// --- use_types.carbon
+
+library "uses_types" api;
+
+import library "types";
+
+fn F(n: UInt(64)) -> UInt(64) {
+  return n;
+}
+
+fn G(n: UInt(13)) -> UInt(13) {
+  return n;
+}
+
+fn Symbolic(N:! i32, x: UInt(N)) -> UInt(N) {
+  return x;
+}
+
+// --- fail_zero_size.carbon
+
+library "fail_zero_size" api;
+
+import library "types";
+
+// CHECK:STDERR: fail_zero_size.carbon:[[@LINE+4]]:8: ERROR: Integer type width of 0 is not positive.
+// CHECK:STDERR: var n: UInt(0);
+// CHECK:STDERR:        ^~~~~
+// CHECK:STDERR:
+var n: UInt(0);
+
+// --- fail_negative_size.carbon
+
+library "fail_negative_size" api;
+
+import library "types";
+
+fn Negate(n: i32) -> i32 = "int.negate";
+
+// CHECK:STDERR: fail_negative_size.carbon:[[@LINE+4]]:8: ERROR: Integer type width of -1 is not positive.
+// CHECK:STDERR: var n: UInt(Negate(1));
+// CHECK:STDERR:        ^~~~~
+// CHECK:STDERR:
+var n: UInt(Negate(1));
+
+// --- fail_oversized.carbon
+
+library "fail_oversized" api;
+
+import library "types";
+
+// CHECK:STDERR: fail_oversized.carbon:[[@LINE+3]]:8: ERROR: Integer type width of 1000000000 is greater than the maximum supported width of 8388608.
+// CHECK:STDERR: var m: UInt(1000000000);
+// CHECK:STDERR:        ^~~~~
+var m: UInt(1000000000);
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .UInt = %UInt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %UInt: <function> = fn_decl @UInt [template] {
+// CHECK:STDOUT:     %n.loc4_9.1: i32 = param n
+// CHECK:STDOUT:     @UInt.%n: i32 = bind_name n, %n.loc4_9.1
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UInt(%n: i32) -> type = "int.make_type_unsigned";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 64 [template]
+// CHECK:STDOUT:   %.2: type = int_type unsigned, %.1 [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 13 [template]
+// CHECK:STDOUT:   %.4: type = int_type unsigned, %.3 [template]
+// CHECK:STDOUT:   %.5: type = int_type unsigned, @Symbolic.%N [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UInt = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:     .G = %G
+// CHECK:STDOUT:     .Symbolic = %Symbolic
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_13 [template = imports.%UInt]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %UInt.ref.loc6_9: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %.loc6_14: i32 = int_literal 64 [template = constants.%.1]
+// CHECK:STDOUT:     %.loc6_13.1: init type = call %UInt.ref.loc6_9(%.loc6_14) [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_16: type = value_of_initializer %.loc6_13.1 [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_13.2: type = converted %.loc6_13.1, %.loc6_16 [template = constants.%.2]
+// CHECK:STDOUT:     %n.loc6_6.1: u64 = param n
+// CHECK:STDOUT:     @F.%n: u64 = bind_name n, %n.loc6_6.1
+// CHECK:STDOUT:     %UInt.ref.loc6_22: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %.loc6_27: i32 = int_literal 64 [template = constants.%.1]
+// CHECK:STDOUT:     %.loc6_26.1: init type = call %UInt.ref.loc6_22(%.loc6_27) [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_29: type = value_of_initializer %.loc6_26.1 [template = constants.%.2]
+// CHECK:STDOUT:     %.loc6_26.2: type = converted %.loc6_26.1, %.loc6_29 [template = constants.%.2]
+// CHECK:STDOUT:     %return.var.loc6: ref u64 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
+// CHECK:STDOUT:     %UInt.ref.loc10_9: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %.loc10_14: i32 = int_literal 13 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc10_13.1: init type = call %UInt.ref.loc10_9(%.loc10_14) [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_16: type = value_of_initializer %.loc10_13.1 [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_13.2: type = converted %.loc10_13.1, %.loc10_16 [template = constants.%.4]
+// CHECK:STDOUT:     %n.loc10_6.1: u13 = param n
+// CHECK:STDOUT:     @G.%n: u13 = bind_name n, %n.loc10_6.1
+// CHECK:STDOUT:     %UInt.ref.loc10_22: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %.loc10_27: i32 = int_literal 13 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc10_26.1: init type = call %UInt.ref.loc10_22(%.loc10_27) [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_29: type = value_of_initializer %.loc10_26.1 [template = constants.%.4]
+// CHECK:STDOUT:     %.loc10_26.2: type = converted %.loc10_26.1, %.loc10_29 [template = constants.%.4]
+// CHECK:STDOUT:     %return.var.loc10: ref u13 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Symbolic: <function> = fn_decl @Symbolic [template] {
+// CHECK:STDOUT:     %N.loc14_13.1: i32 = param N
+// CHECK:STDOUT:     @Symbolic.%N: i32 = bind_symbolic_name N, %N.loc14_13.1 [symbolic]
+// CHECK:STDOUT:     %UInt.ref.loc14_25: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %N.ref.loc14_30: i32 = name_ref N, @Symbolic.%N [symbolic = @Symbolic.%N]
+// CHECK:STDOUT:     %.loc14_29.1: init type = call %UInt.ref.loc14_25(%N.ref.loc14_30) [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_31: type = value_of_initializer %.loc14_29.1 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_29.2: type = converted %.loc14_29.1, %.loc14_31 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %x.loc14_22.1: Core.UInt(N) = param x
+// CHECK:STDOUT:     @Symbolic.%x: Core.UInt(N) = bind_name x, %x.loc14_22.1
+// CHECK:STDOUT:     %UInt.ref.loc14_37: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:     %N.ref.loc14_42: i32 = name_ref N, @Symbolic.%N [symbolic = @Symbolic.%N]
+// CHECK:STDOUT:     %.loc14_41.1: init type = call %UInt.ref.loc14_37(%N.ref.loc14_42) [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_43: type = value_of_initializer %.loc14_41.1 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %.loc14_41.2: type = converted %.loc14_41.1, %.loc14_43 [symbolic = constants.%.5]
+// CHECK:STDOUT:     %return.var.loc14: ref Core.UInt(N) = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UInt(%n: i32) -> type = "int.make_type_unsigned";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%n: u64) -> u64 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: u64 = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%n: u13) -> u13 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: u13 = name_ref n, %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: Core.UInt(N)) -> Core.UInt(N) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %x.ref: Core.UInt(N) = name_ref x, %x
+// CHECK:STDOUT:   return %x.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_zero_size.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UInt = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .n = %n
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_11 [template = imports.%UInt]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %UInt.ref: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:   %.loc10_13: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_12.1: init type = call %UInt.ref(%.loc10_13) [template = <error>]
+// CHECK:STDOUT:   %.loc10_14: type = value_of_initializer %.loc10_12.1 [template = <error>]
+// CHECK:STDOUT:   %.loc10_12.2: type = converted %.loc10_12.1, %.loc10_14 [template = <error>]
+// CHECK:STDOUT:   %n.var: ref <error> = var n
+// CHECK:STDOUT:   %n: ref <error> = bind_name n, %n.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UInt(%n: i32) -> type = "int.make_type_unsigned";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_negative_size.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal -1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UInt = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Negate = %Negate
+// CHECK:STDOUT:     .n = %n.loc12
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_23 [template = imports.%UInt]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Negate: <function> = fn_decl @Negate [template] {
+// CHECK:STDOUT:     %n.loc6_11.1: i32 = param n
+// CHECK:STDOUT:     @Negate.%n: i32 = bind_name n, %n.loc6_11.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %UInt.ref: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:   %Negate.ref: <function> = name_ref Negate, %Negate [template = %Negate]
+// CHECK:STDOUT:   %.loc12_20: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc12_19.1: init i32 = call %Negate.ref(%.loc12_20) [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_12.1: i32 = value_of_initializer %.loc12_19.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_19.2: i32 = converted %.loc12_19.1, %.loc12_12.1 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc12_12.2: init type = call %UInt.ref(%.loc12_19.2) [template = <error>]
+// CHECK:STDOUT:   %.loc12_22: type = value_of_initializer %.loc12_12.2 [template = <error>]
+// CHECK:STDOUT:   %.loc12_12.3: type = converted %.loc12_12.2, %.loc12_22 [template = <error>]
+// CHECK:STDOUT:   %n.var: ref <error> = var n
+// CHECK:STDOUT:   %n.loc12: ref <error> = bind_name n, %n.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Negate(%n: i32) -> i32 = "int.negate";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UInt(%n: i32) -> type = "int.make_type_unsigned";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_oversized.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1000000000 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .UInt = %import_ref
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .m = %m
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+5, loc_11 [template = imports.%UInt]
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %UInt.ref: <function> = name_ref UInt, %import_ref [template = imports.%UInt]
+// CHECK:STDOUT:   %.loc9_13: i32 = int_literal 1000000000 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc9_12.1: init type = call %UInt.ref(%.loc9_13) [template = <error>]
+// CHECK:STDOUT:   %.loc9_23: type = value_of_initializer %.loc9_12.1 [template = <error>]
+// CHECK:STDOUT:   %.loc9_12.2: type = converted %.loc9_12.1, %.loc9_23 [template = <error>]
+// CHECK:STDOUT:   %m.var: ref <error> = var m
+// CHECK:STDOUT:   %m: ref <error> = bind_name m, %m.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @UInt(%n: i32) -> type = "int.make_type_unsigned";
+// CHECK:STDOUT:

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -270,6 +270,9 @@ CARBON_DIAGNOSTIC_KIND(IncompleteTypeInMemberAccess)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInValueConversion)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInVarDecl)
 CARBON_DIAGNOSTIC_KIND(IntLiteralTooLargeForI32)
+CARBON_DIAGNOSTIC_KIND(IntWidthNotMultipleOf8)
+CARBON_DIAGNOSTIC_KIND(IntWidthNotPositive)
+CARBON_DIAGNOSTIC_KIND(IntWidthTooLarge)
 CARBON_DIAGNOSTIC_KIND(InvalidArrayExpr)
 CARBON_DIAGNOSTIC_KIND(TypeNotIndexable)
 CARBON_DIAGNOSTIC_KIND(SelfOutsideImplicitParamList)

+ 2 - 3
toolchain/lex/tokenized_buffer.cpp

@@ -134,11 +134,10 @@ auto TokenizedBuffer::GetStringLiteralValue(TokenIndex token) const
   return token_info.string_literal_id;
 }
 
-auto TokenizedBuffer::GetTypeLiteralSize(TokenIndex token) const
-    -> const llvm::APInt& {
+auto TokenizedBuffer::GetTypeLiteralSize(TokenIndex token) const -> IntId {
   const auto& token_info = GetTokenInfo(token);
   CARBON_CHECK(token_info.kind.is_sized_type_literal()) << token_info.kind;
-  return value_stores_->ints().Get(token_info.int_id);
+  return token_info.int_id;
 }
 
 auto TokenizedBuffer::GetMatchedClosingToken(TokenIndex opening_token) const

+ 1 - 1
toolchain/lex/tokenized_buffer.h

@@ -166,7 +166,7 @@ class TokenizedBuffer : public Printable<TokenizedBuffer> {
   auto GetStringLiteralValue(TokenIndex token) const -> StringLiteralValueId;
 
   // Returns the size specified in a `*TypeLiteral()` token.
-  auto GetTypeLiteralSize(TokenIndex token) const -> const llvm::APInt&;
+  auto GetTypeLiteralSize(TokenIndex token) const -> IntId;
 
   // Returns the closing token matched with the given opening token.
   //

+ 13 - 16
toolchain/lex/tokenized_buffer_test.cpp

@@ -941,22 +941,19 @@ TEST_F(LexerTest, TypeLiterals) {
                   {.kind = TokenKind::FileEnd, .line = 6, .column = 3},
               }));
 
-  auto token_i1 = buffer.tokens().begin() + 2;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_i1), 1);
-  auto token_i20 = buffer.tokens().begin() + 3;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_i20), 20);
-  auto token_i999999999999 = buffer.tokens().begin() + 4;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_i999999999999), 999999999999ULL);
-  auto token_u1 = buffer.tokens().begin() + 7;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_u1), 1);
-  auto token_u64 = buffer.tokens().begin() + 8;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_u64), 64);
-  auto token_f32 = buffer.tokens().begin() + 10;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_f32), 32);
-  auto token_f80 = buffer.tokens().begin() + 11;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_f80), 80);
-  auto token_f1 = buffer.tokens().begin() + 12;
-  EXPECT_EQ(buffer.GetTypeLiteralSize(*token_f1), 1);
+  auto type_size = [&](int token_index) {
+    auto token = buffer.tokens().begin()[token_index];
+    return value_stores_.ints().Get(buffer.GetTypeLiteralSize(token));
+  };
+
+  EXPECT_EQ(type_size(2), 1);
+  EXPECT_EQ(type_size(3), 20);
+  EXPECT_EQ(type_size(4), 999999999999ULL);
+  EXPECT_EQ(type_size(7), 1);
+  EXPECT_EQ(type_size(8), 64);
+  EXPECT_EQ(type_size(10), 32);
+  EXPECT_EQ(type_size(11), 80);
+  EXPECT_EQ(type_size(12), 1);
 }
 
 TEST_F(LexerTest, TypeLiteralTooManyDigits) {

+ 7 - 0
toolchain/lower/file_context.cpp

@@ -319,6 +319,13 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
       // Return an empty struct as a placeholder.
       // TODO: Should we model an interface as a witness table?
       return llvm::StructType::get(*llvm_context_);
+    case SemIR::IntType::Kind: {
+      auto width = sem_ir_->insts().TryGetAs<SemIR::IntLiteral>(
+          inst.As<SemIR::IntType>().bit_width_id);
+      CARBON_CHECK(width) << "Can't lower int type with symbolic width";
+      return llvm::IntegerType::get(
+          *llvm_context_, sem_ir_->ints().Get(width->int_id).getZExtValue());
+    }
     case SemIR::PointerType::Kind:
       return llvm::PointerType::get(*llvm_context_, /*AddressSpace=*/0);
     case SemIR::StructType::Kind: {

+ 16 - 6
toolchain/lower/handle.cpp

@@ -212,6 +212,8 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::BoolMakeType:
     case SemIR::BuiltinFunctionKind::FloatMakeType:
     case SemIR::BuiltinFunctionKind::IntMakeType32:
+    case SemIR::BuiltinFunctionKind::IntMakeTypeSigned:
+    case SemIR::BuiltinFunctionKind::IntMakeTypeUnsigned:
       context.SetLocal(inst_id, context.GetTypeAsValue());
       return;
 
@@ -260,15 +262,23 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
       return;
     }
     case SemIR::BuiltinFunctionKind::IntDiv: {
-      context.SetLocal(inst_id, context.builder().CreateSDiv(
-                                    context.GetValue(arg_ids[0]),
-                                    context.GetValue(arg_ids[1]), "div"));
+      context.SetLocal(inst_id, IsSignedInt(context, inst_id)
+                                    ? context.builder().CreateSDiv(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "div")
+                                    : context.builder().CreateUDiv(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "div"));
       return;
     }
     case SemIR::BuiltinFunctionKind::IntMod: {
-      context.SetLocal(inst_id, context.builder().CreateSRem(
-                                    context.GetValue(arg_ids[0]),
-                                    context.GetValue(arg_ids[1]), "rem"));
+      context.SetLocal(inst_id, IsSignedInt(context, inst_id)
+                                    ? context.builder().CreateSRem(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "rem")
+                                    : context.builder().CreateURem(
+                                          context.GetValue(arg_ids[0]),
+                                          context.GetValue(arg_ids[1]), "rem"));
       return;
     }
     case SemIR::BuiltinFunctionKind::IntAnd: {

+ 5 - 0
toolchain/lower/handle_type.cpp

@@ -37,6 +37,11 @@ auto HandleInterfaceType(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetTypeAsValue());
 }
 
+auto HandleIntType(FunctionContext& context, SemIR::InstId inst_id,
+                   SemIR::IntType /*inst*/) -> void {
+  context.SetLocal(inst_id, context.GetTypeAsValue());
+}
+
 auto HandlePointerType(FunctionContext& context, SemIR::InstId inst_id,
                        SemIR::PointerType /*inst*/) -> void {
   context.SetLocal(inst_id, context.GetTypeAsValue());

+ 29 - 0
toolchain/lower/testdata/basics/int_types.carbon

@@ -0,0 +1,29 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F_i8(a: i8) -> i8 { return a; }
+fn F_u16(a: u16) -> u16 { return a; }
+fn F_i64(a: i64) -> i64 { return a; }
+fn F_u65536(a: u65536) -> u65536 { return a; }
+
+// CHECK:STDOUT: ; ModuleID = 'int_types.carbon'
+// CHECK:STDOUT: source_filename = "int_types.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i8 @F_i8(i8 %a) {
+// CHECK:STDOUT:   ret i8 %a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i16 @F_u16(i16 %a) {
+// CHECK:STDOUT:   ret i16 %a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @F_i64(i64 %a) {
+// CHECK:STDOUT:   ret i64 %a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i65536 @F_u65536(i65536 %a) {
+// CHECK:STDOUT:   ret i65536 %a
+// CHECK:STDOUT: }

+ 8 - 0
toolchain/lower/testdata/basics/type_values.carbon

@@ -11,6 +11,10 @@ fn I32() -> type {
   return i32;
 }
 
+fn I48() -> type {
+  return i48;
+}
+
 fn F64() -> type {
   return f64;
 }
@@ -24,6 +28,10 @@ fn F64() -> type {
 // CHECK:STDOUT:   ret %type zeroinitializer
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: define %type @I48() {
+// CHECK:STDOUT:   ret %type zeroinitializer
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: define %type @F64() {
 // CHECK:STDOUT:   ret %type zeroinitializer
 // CHECK:STDOUT: }

+ 152 - 0
toolchain/lower/testdata/builtins/uint.carbon

@@ -0,0 +1,152 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn Negate(a: u64) -> u64 = "int.negate";
+fn TestNegate(a: u64) -> u64 { return Negate(a); }
+
+fn Add(a: u64, b: u64) -> u64 = "int.add";
+fn TestAdd(a: u64, b: u64) -> u64 { return Add(a, b); }
+
+fn Sub(a: u64, b: u64) -> u64 = "int.sub";
+fn TestSub(a: u64, b: u64) -> u64 { return Sub(a, b); }
+
+fn Mul(a: u64, b: u64) -> u64 = "int.mul";
+fn TestMul(a: u64, b: u64) -> u64 { return Mul(a, b); }
+
+fn Div(a: u64, b: u64) -> u64 = "int.div";
+fn TestDiv(a: u64, b: u64) -> u64 { return Div(a, b); }
+
+fn Mod(a: u64, b: u64) -> u64 = "int.mod";
+fn TestMod(a: u64, b: u64) -> u64 { return Mod(a, b); }
+
+fn Complement(a: u64) -> u64 = "int.complement";
+fn TestComplement(a: u64) -> u64 { return Complement(a); }
+
+fn And(a: u64, b: u64) -> u64 = "int.and";
+fn TestAnd(a: u64, b: u64) -> u64 { return And(a, b); }
+
+fn Or(a: u64, b: u64) -> u64 = "int.or";
+fn TestOr(a: u64, b: u64) -> u64 { return Or(a, b); }
+
+fn Xor(a: u64, b: u64) -> u64 = "int.xor";
+fn TestXor(a: u64, b: u64) -> u64 { return Xor(a, b); }
+
+fn LeftShift(a: u64, b: u64) -> u64 = "int.left_shift";
+fn TestLeftShift(a: u64, b: u64) -> u64 { return LeftShift(a, b); }
+
+fn RightShift(a: u64, b: u64) -> u64 = "int.right_shift";
+fn TestRightShift(a: u64, b: u64) -> u64 { return RightShift(a, b); }
+
+fn Eq(a: u64, b: u64) -> bool = "int.eq";
+fn TestEq(a: u64, b: u64) -> bool { return Eq(a, b); }
+
+fn Neq(a: u64, b: u64) -> bool = "int.neq";
+fn TestNeq(a: u64, b: u64) -> bool { return Neq(a, b); }
+
+fn Less(a: u64, b: u64) -> bool = "int.less";
+fn TestLess(a: u64, b: u64) -> bool { return Less(a, b); }
+
+fn LessEq(a: u64, b: u64) -> bool = "int.less_eq";
+fn TestLessEq(a: u64, b: u64) -> bool { return LessEq(a, b); }
+
+fn Greater(a: u64, b: u64) -> bool = "int.greater";
+fn TestGreater(a: u64, b: u64) -> bool { return Greater(a, b); }
+
+fn GreaterEq(a: u64, b: u64) -> bool = "int.greater_eq";
+fn TestGreaterEq(a: u64, b: u64) -> bool { return GreaterEq(a, b); }
+
+// CHECK:STDOUT: ; ModuleID = 'uint.carbon'
+// CHECK:STDOUT: source_filename = "uint.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestNegate(i64 %a) {
+// CHECK:STDOUT:   %neg = sub i64 0, %a
+// CHECK:STDOUT:   ret i64 %neg
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestAdd(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %add = add i64 %a, %b
+// CHECK:STDOUT:   ret i64 %add
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestSub(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %sub = sub i64 %a, %b
+// CHECK:STDOUT:   ret i64 %sub
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestMul(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %mul = mul i64 %a, %b
+// CHECK:STDOUT:   ret i64 %mul
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestDiv(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %div = udiv i64 %a, %b
+// CHECK:STDOUT:   ret i64 %div
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestMod(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %rem = urem i64 %a, %b
+// CHECK:STDOUT:   ret i64 %rem
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestComplement(i64 %a) {
+// CHECK:STDOUT:   %cmpl = xor i64 -1, %a
+// CHECK:STDOUT:   ret i64 %cmpl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestAnd(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %and = and i64 %a, %b
+// CHECK:STDOUT:   ret i64 %and
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestOr(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %or = or i64 %a, %b
+// CHECK:STDOUT:   ret i64 %or
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestXor(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %xor = xor i64 %a, %b
+// CHECK:STDOUT:   ret i64 %xor
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestLeftShift(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %shl = shl i64 %a, %b
+// CHECK:STDOUT:   ret i64 %shl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i64 @TestRightShift(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %shr = lshr i64 %a, %b
+// CHECK:STDOUT:   ret i64 %shr
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestEq(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp eq i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestNeq(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp ne i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestLess(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp ult i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestLessEq(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp ule i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestGreater(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp ugt i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @TestGreaterEq(i64 %a, i64 %b) {
+// CHECK:STDOUT:   %cmp = icmp uge i64 %a, %b
+// CHECK:STDOUT:   ret i1 %cmp
+// CHECK:STDOUT: }

+ 20 - 5
toolchain/sem_ir/builtin_function_kind.cpp

@@ -64,10 +64,16 @@ struct BuiltinType {
 using Bool = BuiltinType<InstId::BuiltinBoolType>;
 
 // Constraint that requires the type to be an integer type.
-//
-// TODO: This only matches i32 for now. Support iN for all N, and the
-// Core.BigInt type we use to implement for integer literals.
-using AnyInt = BuiltinType<InstId::BuiltinIntType>;
+struct AnyInt {
+  static auto Check(const File& sem_ir, ValidateState& state, TypeId type_id)
+      -> bool {
+    // TODO: Support Core.BigInt once it exists.
+    if (BuiltinType<InstId::BuiltinIntType>::Check(sem_ir, state, type_id)) {
+      return true;
+    }
+    return sem_ir.types().Is<IntType>(type_id);
+  }
+};
 
 // Constraint that requires the type to be the type type.
 using Type = BuiltinType<InstId::BuiltinTypeType>;
@@ -141,9 +147,18 @@ constexpr BuiltinInfo None = {"", nullptr};
 constexpr BuiltinInfo IntMakeType32 = {"int.make_type_32",
                                        ValidateSignature<auto()->Type>};
 
+// Returns the `iN` type.
+// TODO: Should we use a more specific type as the type of the bit width?
+constexpr BuiltinInfo IntMakeTypeSigned = {
+    "int.make_type_signed", ValidateSignature<auto(AnyInt)->Type>};
+
+// Returns the `uN` type.
+constexpr BuiltinInfo IntMakeTypeUnsigned = {
+    "int.make_type_unsigned", ValidateSignature<auto(AnyInt)->Type>};
+
 // Returns float types, such as `f64`. Currently only supports `f64`.
 constexpr BuiltinInfo FloatMakeType = {"float.make_type",
-                                       ValidateSignature<auto(IntT)->Type>};
+                                       ValidateSignature<auto(AnyInt)->Type>};
 
 // Returns the `bool` type.
 constexpr BuiltinInfo BoolMakeType = {"bool.make_type",

+ 2 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -20,6 +20,8 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
 
 // Type factories.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeType32)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeTypeSigned)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeTypeUnsigned)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatMakeType)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolMakeType)
 

+ 17 - 0
toolchain/sem_ir/file.cpp

@@ -209,6 +209,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case ImportRefLoaded::Kind:
     case ImportRefUsed::Kind:
     case InterfaceType::Kind:
+    case IntType::Kind:
     case NameRef::Kind:
     case StructType::Kind:
     case TupleType::Kind:
@@ -398,6 +399,21 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         out << sem_ir.names().GetFormatted(interface_name_id);
         break;
       }
+      case IntType::Kind: {
+        auto int_type = inst.As<IntType>();
+        if (step.index == 1) {
+          out << ")";
+        } else if (auto width_value = sem_ir.insts().TryGetAs<IntLiteral>(
+                       int_type.bit_width_id)) {
+          out << (int_type.int_kind.is_signed() ? "i" : "u");
+          sem_ir.ints().Get(width_value->int_id).print(out, /*isSigned=*/false);
+        } else {
+          out << (int_type.int_kind.is_signed() ? "Core.Int(" : "Core.UInt(");
+          steps.push_back(step.Next());
+          push_inst_id(int_type.bit_width_id);
+        }
+        break;
+      }
       case NameRef::Kind: {
         out << sem_ir.names().GetFormatted(inst.As<NameRef>().name_id);
         break;
@@ -610,6 +626,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case InterfaceWitness::Kind:
       case InterfaceWitnessAccess::Kind:
       case IntLiteral::Kind:
+      case IntType::Kind:
       case Param::Kind:
       case PointerType::Kind:
       case RealLiteral::Kind:

+ 2 - 0
toolchain/sem_ir/formatter.cpp

@@ -1219,6 +1219,8 @@ class Formatter {
 
   auto FormatArg(InterfaceId id) -> void { FormatInterfaceName(id); }
 
+  auto FormatArg(IntKind k) -> void { k.Print(out_); }
+
   auto FormatArg(ImplId id) -> void { FormatImplName(id); }
 
   auto FormatArg(ImportIRId id) -> void { out_ << id; }

+ 1 - 1
toolchain/sem_ir/id_kind.h

@@ -121,7 +121,7 @@ using IdKind = TypeEnum<
     IntId, RealId, StringLiteralValueId,
     // From sem_ir/id.h.
     InstId, ConstantId, BindNameId, FunctionId, ClassId, InterfaceId, ImplId,
-    ImportIRId, ImportIRInstId, LocId, BoolValue, NameId, NameScopeId,
+    ImportIRId, ImportIRInstId, LocId, BoolValue, IntKind, NameId, NameScopeId,
     InstBlockId, TypeId, TypeBlockId, ElementIndex>;
 
 }  // namespace Carbon::SemIR

+ 27 - 0
toolchain/sem_ir/ids.h

@@ -305,6 +305,33 @@ struct BoolValue : public IdBase, public Printable<BoolValue> {
 constexpr BoolValue BoolValue::False = BoolValue(0);
 constexpr BoolValue BoolValue::True = BoolValue(1);
 
+// An integer kind value -- either "signed" or "unsigned".
+//
+// This might eventually capture any other properties of an integer type that
+// affect its semantics, such as overflow behavior.
+struct IntKind : public IdBase, public Printable<IntKind> {
+  static const IntKind Unsigned;
+  static const IntKind Signed;
+
+  using IdBase::IdBase;
+
+  // Returns whether this type is signed.
+  constexpr auto is_signed() -> bool { return *this == Signed; }
+
+  auto Print(llvm::raw_ostream& out) const -> void {
+    if (*this == Unsigned) {
+      out << "unsigned";
+    } else if (*this == Signed) {
+      out << "signed";
+    } else {
+      CARBON_FATAL() << "Invalid int kind value " << index;
+    }
+  }
+};
+
+constexpr IntKind IntKind::Unsigned = IntKind(0);
+constexpr IntKind IntKind::Signed = IntKind(1);
+
 // The ID of a name. A name is either a string or a special name such as
 // `self`, `Self`, or `base`.
 struct NameId : public IdBase, public Printable<NameId> {

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -59,6 +59,7 @@ CARBON_SEM_IR_INST_KIND(InterfaceType)
 CARBON_SEM_IR_INST_KIND(InterfaceWitness)
 CARBON_SEM_IR_INST_KIND(InterfaceWitnessAccess)
 CARBON_SEM_IR_INST_KIND(IntLiteral)
+CARBON_SEM_IR_INST_KIND(IntType)
 CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)
 CARBON_SEM_IR_INST_KIND(Param)

+ 6 - 1
toolchain/sem_ir/type.h

@@ -85,7 +85,12 @@ class TypeStore : public ValueStore<TypeId> {
 
   // Determines whether the given type is a signed integer type.
   auto IsSignedInt(TypeId int_type_id) const -> bool {
-    return GetInstId(int_type_id) == InstId::BuiltinIntType;
+    auto inst_id = GetInstId(int_type_id);
+    if (inst_id == InstId::BuiltinIntType) {
+      return true;
+    }
+    auto int_type = insts_->TryGetAs<IntType>(inst_id);
+    return int_type && int_type->int_kind.is_signed();
   }
 
  private:

+ 11 - 0
toolchain/sem_ir/typed_insts.h

@@ -567,6 +567,17 @@ struct IntLiteral {
   IntId int_id;
 };
 
+struct IntType {
+  static constexpr auto Kind =
+      InstKind::IntType.Define<Parse::NodeId>("int_type");
+
+  TypeId type_id;
+  IntKind int_kind;
+  // TODO: Consider adding a more compact way of representing either a small
+  // unsigned integer bit width or an inst_id.
+  InstId bit_width_id;
+};
+
 struct NameRef {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =