Parcourir la source

Add solutions for advent of code 2024 day 1 to `examples/`. (#4673)

In order to support these examples, this adds two new builtins to the
toolchain: `print.char` and `read.char`, which map to the libc functions
`putchar` and `getchar`.
Richard Smith il y a 1 an
Parent
commit
3645143e27

+ 2 - 1
core/BUILD

@@ -5,9 +5,10 @@
 load("//bazel/manifest:defs.bzl", "manifest")
 
 # Raw prelude files.
+# TODO: This includes all of Core, not just the prelude.
 filegroup(
     name = "prelude_files",
-    srcs = ["prelude.carbon"] + glob(["prelude/**/*.carbon"]),
+    srcs = glob(["**/*.carbon"]),
     visibility = ["//visibility:public"],
 )
 

+ 19 - 0
core/io.carbon

@@ -0,0 +1,19 @@
+// 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
+
+package Core library "io";
+
+import library "prelude";
+
+// TODO: Support printing other types.
+// TODO: Consider rewriting using native support once library support exists.
+fn Print(x: i32) = "print.int";
+fn PrintChar(x: i32) -> i32 = "print.char";
+fn ReadChar() -> i32 = "read.char";
+
+// TODO: Change this to a global constant once they are fully supported.
+// TODO: Use simply -1 once we support negate on an IntLiteral.
+fn EOF() -> i32 { return -(1 as i32); }

+ 0 - 4
core/prelude.carbon

@@ -8,7 +8,3 @@ package Core library "prelude";
 
 export import library "prelude/operators";
 export import library "prelude/types";
-
-// TODO: Support printing other types.
-// TODO: Consider rewriting using native support once library support exists.
-fn Print(x: i32) = "print.int";

+ 26 - 0
examples/advent2024/BUILD

@@ -0,0 +1,26 @@
+# 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
+
+load("//bazel/carbon_rules:defs.bzl", "carbon_binary")
+
+utils = [
+    "io_utils.carbon",
+    "sort.carbon",
+]
+
+carbon_binary(
+    name = "day1_part1",
+    srcs = [
+        "day1_common.carbon",
+        "day1_part1.carbon",
+    ] + utils,
+)
+
+carbon_binary(
+    name = "day1_part2",
+    srcs = [
+        "day1_common.carbon",
+        "day1_part2.carbon",
+    ] + utils,
+)

+ 17 - 0
examples/advent2024/README.md

@@ -0,0 +1,17 @@
+# Advent of Code 2024
+
+<!--
+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
+-->
+
+This directory contains sample solutions written in Carbon for
+[Advent of Code 2024](https://adventofcode.com/2024/).
+
+The Carbon toolchain is in a very early state, so these samples frequently need
+to work around missing functionality and are not reflective of expected Carbon
+style and idioms. Instead, the purpose of these examples are to test the current
+state of the toolchain against larger code examples than those that are present
+in the toolchain's own tests, to find bugs in the toolchain, and to drive
+feature development in the toolchain by presenting somewhat realistic testcases.

+ 21 - 0
examples/advent2024/day1_common.carbon

@@ -0,0 +1,21 @@
+// 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
+
+library "day1_common";
+
+import library "io_utils";
+
+// Read a sequence of lines each containing a pair of numbers into two arrays.
+// Returns the number of lines read.
+fn ReadInputs(ap: [i32; 1000]*, bp: [i32; 1000]*) -> i32 {
+  var n: i32 = 0;
+  var a: i32;
+  var b: i32;
+  while (n < 1000 and ReadInt(&a) and SkipSpaces() and ReadInt(&b) and SkipNewline()) {
+    (*ap)[n] = a;
+    (*bp)[n] = b;
+    ++n;
+  }
+  return n;
+}

+ 26 - 0
examples/advent2024/day1_part1.carbon

@@ -0,0 +1,26 @@
+// 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
+
+import Core library "io";
+
+import library "day1_common";
+import library "sort";
+
+fn Abs(n: i32) -> i32 { return if n < 0 then -n else n; }
+
+fn Run() {
+  var a: [i32; 1000];
+  var b: [i32; 1000];
+  var n: i32 = ReadInputs(&a, &b);
+  Quicksort(&a, 0, n);
+  Quicksort(&b, 0, n);
+
+  var i: i32 = 0;
+  var difference: i32 = 0;
+  while (i < n) {
+    difference += Abs(a[i] - b[i]);
+    i += 1;
+  }
+  Core.Print(difference);
+}

+ 31 - 0
examples/advent2024/day1_part2.carbon

@@ -0,0 +1,31 @@
+// 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
+
+import Core library "io";
+
+import library "day1_common";
+import library "sort";
+
+fn Run() {
+  var a: [i32; 1000];
+  var b: [i32; 1000];
+  var n: i32 = ReadInputs(&a, &b);
+  Quicksort(&a, 0, n);
+  Quicksort(&b, 0, n);
+
+  var i: i32 = 0;
+  var j: i32 = 0;
+  var similarity: i32 = 0;
+  while (i < n and j < n) {
+    if (a[i] < b[j]) {
+      ++i;
+    } else {
+      if (a[i] == b[j]) {
+        similarity += b[j];
+      }
+      ++j;
+    }
+  }
+  Core.Print(similarity);
+}

+ 73 - 0
examples/advent2024/io_utils.carbon

@@ -0,0 +1,73 @@
+// 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
+
+library "io_utils";
+
+import Core library "io";
+
+// TODO: Use Core.EOF() rather than 0 as a sentinel here.
+// At the moment, 0 is the only value we can initialize a global to,
+// so we store the value plus 1 here.
+var push_back: i32 = 0;
+
+fn ReadChar() -> i32 {
+  var next: i32 = push_back - 1;
+  push_back = 0;
+  // TODO: assert(push_back == Core.EOF());
+  if (next == Core.EOF()) {
+    next = Core.ReadChar();
+  }
+  return next;
+}
+
+fn UnreadChar(c: i32) {
+  // TODO: assert(push_back == Core.EOF());
+  push_back = c + 1;
+}
+
+fn ReadInt(p: i32*) -> bool {
+  var read_any_digits: bool = false;
+  *p = 0;
+
+  while (true) {
+    var c: i32 = ReadChar();
+    if (c < 0x30 or c > 0x39) {
+      UnreadChar(c);
+      break;
+    }
+    // TODO: Check for overflow.
+    *p *= 10;
+    *p += c - 0x30;
+    read_any_digits = true;
+  }
+  return read_any_digits;
+}
+
+fn SkipSpaces() -> bool {
+  var skipped_any_spaces: bool = false;
+  while (true) {
+    var c: i32 = ReadChar();
+    if (c != 0x20) {
+      UnreadChar(c);
+      break;
+    }
+    skipped_any_spaces = true;
+  }
+  return skipped_any_spaces;
+}
+
+fn SkipNewline() -> bool {
+  var c: i32 = ReadChar();
+  // Optional carriage return.
+  if (c == 0x0D) {
+    c = ReadChar();
+  }
+  // Newline.
+  if (c == 0x0A) {
+    return true;
+  }
+  // TODO: Unread the CR?
+  UnreadChar(c);
+  return false;
+}

+ 43 - 0
examples/advent2024/sort.carbon

@@ -0,0 +1,43 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+library "sort";
+
+// TODO: Generalize this for other container types once we implement lowering
+// for generic functions.
+
+fn Swap(p: [i32; 1000]*, from: i32, to: i32) {
+  var tmp: i32 = (*p)[from];
+  (*p)[from] = (*p)[to];
+  (*p)[to] = tmp;
+}
+
+fn Partition(p: [i32; 1000]*, from_in: i32, to_in: i32) -> i32 {
+  var pivot_index: i32 = from_in;
+  var pivot: i32 = (*p)[pivot_index];
+  var from: i32 = from_in + 1;
+  var to: i32 = to_in;
+  while (from < to) {
+    if ((*p)[from] <= pivot) {
+      ++from;
+    } else if ((*p)[to - 1] > pivot) {
+      --to;
+    } else {
+      // Element at `from` is > pivot, and
+      // element at `to` is <= pivot.
+      Swap(p, from, to - 1);
+      ++from;
+      --to;
+    }
+  }
+  Swap(p, pivot_index, from - 1);
+  return from - 1;
+}
+
+fn Quicksort(p: [i32; 1000]*, from: i32, to: i32) {
+  if (from + 1 >= to) { return; }
+  var pivot: i32 = Partition(p, from, to);
+  Quicksort(p, from, pivot);
+  Quicksort(p, pivot + 1, to);
+}

+ 2 - 0
examples/sieve.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+import Core library "io";
+
 // Compute and return the number of primes less than 1000.
 
 class Sieve {

+ 5 - 2
toolchain/check/eval.cpp

@@ -1107,8 +1107,11 @@ static auto MakeConstantForBuiltinCall(Context& context, SemIRLoc loc,
     case SemIR::BuiltinFunctionKind::None:
       CARBON_FATAL("Not a builtin function.");
 
-    case SemIR::BuiltinFunctionKind::PrintInt: {
-      // Providing a constant result would allow eliding the function call.
+    case SemIR::BuiltinFunctionKind::PrintChar:
+    case SemIR::BuiltinFunctionKind::PrintInt:
+    case SemIR::BuiltinFunctionKind::ReadChar: {
+      // These are runtime-only builtins.
+      // TODO: Consider tracking this on the `BuiltinFunctionKind`.
       return SemIR::ConstantId::NotConstant;
     }
 

+ 107 - 0
toolchain/check/testdata/builtins/print/char.carbon

@@ -0,0 +1,107 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/print/char.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/print/char.carbon
+
+import Core library "io";
+
+fn PrintChar(a: i32) -> i32 = "print.char";
+
+fn Main() {
+  PrintChar(1);
+  Core.PrintChar(2);
+}
+
+// CHECK:STDOUT: --- char.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [template]
+// CHECK:STDOUT:   %PrintChar.type.1: type = fn_type @PrintChar.1 [template]
+// CHECK:STDOUT:   %PrintChar.1: %PrintChar.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %Main.type: type = fn_type @Main [template]
+// CHECK:STDOUT:   %Main: %Main.type = struct_value () [template]
+// CHECK:STDOUT:   %int_1.1: Core.IntLiteral = int_value 1 [template]
+// CHECK:STDOUT:   %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
+// CHECK:STDOUT:   %Convert.type.10: type = fn_type @Convert.2, @impl.1(%int_32) [template]
+// CHECK:STDOUT:   %Convert.10: %Convert.type.10 = struct_value () [template]
+// CHECK:STDOUT:   %interface.5: <witness> = interface_witness (%Convert.10) [template]
+// CHECK:STDOUT:   %Convert.bound.1: <bound method> = bound_method %int_1.1, %Convert.10 [template]
+// CHECK:STDOUT:   %Convert.specific_fn.1: <specific function> = specific_function %Convert.bound.1, @Convert.2(%int_32) [template]
+// CHECK:STDOUT:   %int_1.2: %i32 = int_value 1 [template]
+// CHECK:STDOUT:   %PrintChar.type.2: type = fn_type @PrintChar.2 [template]
+// CHECK:STDOUT:   %PrintChar.2: %PrintChar.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %int_2.1: Core.IntLiteral = int_value 2 [template]
+// CHECK:STDOUT:   %Convert.bound.2: <bound method> = bound_method %int_2.1, %Convert.10 [template]
+// CHECK:STDOUT:   %Convert.specific_fn.2: <specific function> = specific_function %Convert.bound.2, @Convert.2(%int_32) [template]
+// CHECK:STDOUT:   %int_2.2: %i32 = int_value 2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int = %import_ref.1
+// CHECK:STDOUT:     .ImplicitAs = %import_ref.5
+// CHECK:STDOUT:     .PrintChar = %import_ref.193
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//io
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.193: %PrintChar.type.2 = import_ref Core//io, PrintChar, loaded [template = constants.%PrintChar.2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .PrintChar = %PrintChar.decl
+// CHECK:STDOUT:     .Main = %Main.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %PrintChar.decl: %PrintChar.type.1 = fn_decl @PrintChar.1 [template = constants.%PrintChar.1] {
+// CHECK:STDOUT:     %a.patt: %i32 = binding_pattern a
+// CHECK:STDOUT:     %a.param_patt: %i32 = value_param_pattern %a.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: %i32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %i32 = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %int_32.loc13_17: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc13_17: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
+// CHECK:STDOUT:     %int_32.loc13_25: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc13_25: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
+// CHECK:STDOUT:     %a.param: %i32 = value_param runtime_param0
+// CHECK:STDOUT:     %a: %i32 = bind_name a, %a.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param runtime_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Main.decl: %Main.type = fn_decl @Main [template = constants.%Main] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @PrintChar.1(%a.param_patt: %i32) -> %i32 = "print.char";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Main() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %PrintChar.ref.loc16: %PrintChar.type.1 = name_ref PrintChar, file.%PrintChar.decl [template = constants.%PrintChar.1]
+// CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.1]
+// CHECK:STDOUT:   %impl.elem0.loc16: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
+// CHECK:STDOUT:   %Convert.bound.loc16: <bound method> = bound_method %int_1, %impl.elem0.loc16 [template = constants.%Convert.bound.1]
+// CHECK:STDOUT:   %Convert.specific_fn.loc16: <specific function> = specific_function %Convert.bound.loc16, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.1]
+// CHECK:STDOUT:   %int.convert_checked.loc16: init %i32 = call %Convert.specific_fn.loc16(%int_1) [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc16_13.1: %i32 = value_of_initializer %int.convert_checked.loc16 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc16_13.2: %i32 = converted %int_1, %.loc16_13.1 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %print.char.loc16: init %i32 = call %PrintChar.ref.loc16(%.loc16_13.2)
+// CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [template = imports.%Core]
+// CHECK:STDOUT:   %PrintChar.ref.loc17: %PrintChar.type.2 = name_ref PrintChar, imports.%import_ref.193 [template = constants.%PrintChar.2]
+// CHECK:STDOUT:   %int_2: Core.IntLiteral = int_value 2 [template = constants.%int_2.1]
+// CHECK:STDOUT:   %impl.elem0.loc17: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
+// CHECK:STDOUT:   %Convert.bound.loc17: <bound method> = bound_method %int_2, %impl.elem0.loc17 [template = constants.%Convert.bound.2]
+// CHECK:STDOUT:   %Convert.specific_fn.loc17: <specific function> = specific_function %Convert.bound.loc17, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2]
+// CHECK:STDOUT:   %int.convert_checked.loc17: init %i32 = call %Convert.specific_fn.loc17(%int_2) [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc17_18.1: %i32 = value_of_initializer %int.convert_checked.loc17 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc17_18.2: %i32 = converted %int_2, %.loc17_18.1 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %print.char.loc17: init %i32 = call %PrintChar.ref.loc17(%.loc17_18.2)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 23 - 20
toolchain/check/testdata/builtins/print.carbon → toolchain/check/testdata/builtins/print/int.carbon

@@ -4,9 +4,11 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/print.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/print/int.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/print.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/print/int.carbon
+
+import Core library "io";
 
 fn Print(a: i32) = "print.int";
 
@@ -16,7 +18,7 @@ fn Main() {
   Core.Print(2);
 }
 
-// CHECK:STDOUT: --- print.carbon
+// CHECK:STDOUT: --- int.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template]
@@ -48,9 +50,10 @@ fn Main() {
 // CHECK:STDOUT:     .ImplicitAs = %import_ref.5
 // CHECK:STDOUT:     .Print = %import_ref.193
 // CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//io
 // CHECK:STDOUT:     import Core//prelude/...
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %import_ref.193: %Print.type.2 = import_ref Core//prelude, Print, loaded [template = constants.%Print.2]
+// CHECK:STDOUT:   %import_ref.193: %Print.type.2 = import_ref Core//io, Print, loaded [template = constants.%Print.2]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -76,25 +79,25 @@ fn Main() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Main() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Print.ref.loc14: %Print.type.1 = name_ref Print, file.%Print.decl [template = constants.%Print.1]
+// CHECK:STDOUT:   %Print.ref.loc16: %Print.type.1 = name_ref Print, file.%Print.decl [template = constants.%Print.1]
 // CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.1]
-// CHECK:STDOUT:   %impl.elem0.loc14: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
-// CHECK:STDOUT:   %Convert.bound.loc14: <bound method> = bound_method %int_1, %impl.elem0.loc14 [template = constants.%Convert.bound.1]
-// CHECK:STDOUT:   %Convert.specific_fn.loc14: <specific function> = specific_function %Convert.bound.loc14, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.1]
-// CHECK:STDOUT:   %int.convert_checked.loc14: init %i32 = call %Convert.specific_fn.loc14(%int_1) [template = constants.%int_1.2]
-// CHECK:STDOUT:   %.loc14_9.1: %i32 = value_of_initializer %int.convert_checked.loc14 [template = constants.%int_1.2]
-// CHECK:STDOUT:   %.loc14_9.2: %i32 = converted %int_1, %.loc14_9.1 [template = constants.%int_1.2]
-// CHECK:STDOUT:   %print.int.loc14: init %empty_tuple.type = call %Print.ref.loc14(%.loc14_9.2)
+// CHECK:STDOUT:   %impl.elem0.loc16: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
+// CHECK:STDOUT:   %Convert.bound.loc16: <bound method> = bound_method %int_1, %impl.elem0.loc16 [template = constants.%Convert.bound.1]
+// CHECK:STDOUT:   %Convert.specific_fn.loc16: <specific function> = specific_function %Convert.bound.loc16, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.1]
+// CHECK:STDOUT:   %int.convert_checked.loc16: init %i32 = call %Convert.specific_fn.loc16(%int_1) [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc16_9.1: %i32 = value_of_initializer %int.convert_checked.loc16 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc16_9.2: %i32 = converted %int_1, %.loc16_9.1 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %print.int.loc16: init %empty_tuple.type = call %Print.ref.loc16(%.loc16_9.2)
 // CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [template = imports.%Core]
-// CHECK:STDOUT:   %Print.ref.loc16: %Print.type.2 = name_ref Print, imports.%import_ref.193 [template = constants.%Print.2]
+// CHECK:STDOUT:   %Print.ref.loc18: %Print.type.2 = name_ref Print, imports.%import_ref.193 [template = constants.%Print.2]
 // CHECK:STDOUT:   %int_2: Core.IntLiteral = int_value 2 [template = constants.%int_2.1]
-// CHECK:STDOUT:   %impl.elem0.loc16: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
-// CHECK:STDOUT:   %Convert.bound.loc16: <bound method> = bound_method %int_2, %impl.elem0.loc16 [template = constants.%Convert.bound.2]
-// CHECK:STDOUT:   %Convert.specific_fn.loc16: <specific function> = specific_function %Convert.bound.loc16, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2]
-// CHECK:STDOUT:   %int.convert_checked.loc16: init %i32 = call %Convert.specific_fn.loc16(%int_2) [template = constants.%int_2.2]
-// CHECK:STDOUT:   %.loc16_14.1: %i32 = value_of_initializer %int.convert_checked.loc16 [template = constants.%int_2.2]
-// CHECK:STDOUT:   %.loc16_14.2: %i32 = converted %int_2, %.loc16_14.1 [template = constants.%int_2.2]
-// CHECK:STDOUT:   %print.int.loc16: init %empty_tuple.type = call %Print.ref.loc16(%.loc16_14.2)
+// CHECK:STDOUT:   %impl.elem0.loc18: %Convert.type.2 = interface_witness_access constants.%interface.5, element0 [template = constants.%Convert.10]
+// CHECK:STDOUT:   %Convert.bound.loc18: <bound method> = bound_method %int_2, %impl.elem0.loc18 [template = constants.%Convert.bound.2]
+// CHECK:STDOUT:   %Convert.specific_fn.loc18: <specific function> = specific_function %Convert.bound.loc18, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2]
+// CHECK:STDOUT:   %int.convert_checked.loc18: init %i32 = call %Convert.specific_fn.loc18(%int_2) [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc18_14.1: %i32 = value_of_initializer %int.convert_checked.loc18 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc18_14.2: %i32 = converted %int_2, %.loc18_14.1 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %print.int.loc18: init %empty_tuple.type = call %Print.ref.loc18(%.loc18_14.2)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 84 - 0
toolchain/check/testdata/builtins/read/int.carbon

@@ -0,0 +1,84 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/read/int.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/read/int.carbon
+
+import Core library "io";
+
+fn ReadChar() -> i32 = "read.char";
+
+fn Main() {
+  let n: i32 = ReadChar();
+  let m: i32 = Core.ReadChar();
+}
+
+// CHECK:STDOUT: --- int.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [template]
+// CHECK:STDOUT:   %ReadChar.type.1: type = fn_type @ReadChar.1 [template]
+// CHECK:STDOUT:   %ReadChar.1: %ReadChar.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %Main.type: type = fn_type @Main [template]
+// CHECK:STDOUT:   %Main: %Main.type = struct_value () [template]
+// CHECK:STDOUT:   %ReadChar.type.2: type = fn_type @ReadChar.2 [template]
+// CHECK:STDOUT:   %ReadChar.2: %ReadChar.type.2 = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int = %import_ref.1
+// CHECK:STDOUT:     .ReadChar = %import_ref.5
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//io
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.5: %ReadChar.type.2 = import_ref Core//io, ReadChar, loaded [template = constants.%ReadChar.2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .ReadChar = %ReadChar.decl
+// CHECK:STDOUT:     .Main = %Main.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %ReadChar.decl: %ReadChar.type.1 = fn_decl @ReadChar.1 [template = constants.%ReadChar.1] {
+// CHECK:STDOUT:     %return.patt: %i32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %i32 = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:     %i32: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param runtime_param0
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Main.decl: %Main.type = fn_decl @Main [template = constants.%Main] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ReadChar.1() -> %i32 = "read.char";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Main() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %int_32.loc16: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:   %i32.loc16: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
+// CHECK:STDOUT:   %ReadChar.ref.loc16: %ReadChar.type.1 = name_ref ReadChar, file.%ReadChar.decl [template = constants.%ReadChar.1]
+// CHECK:STDOUT:   %read.char.loc16: init %i32 = call %ReadChar.ref.loc16()
+// CHECK:STDOUT:   %.loc16_26.1: %i32 = value_of_initializer %read.char.loc16
+// CHECK:STDOUT:   %.loc16_26.2: %i32 = converted %read.char.loc16, %.loc16_26.1
+// CHECK:STDOUT:   %n: %i32 = bind_name n, %.loc16_26.2
+// CHECK:STDOUT:   %int_32.loc17: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:   %i32.loc17: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
+// CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [template = imports.%Core]
+// CHECK:STDOUT:   %ReadChar.ref.loc17: %ReadChar.type.2 = name_ref ReadChar, imports.%import_ref.5 [template = constants.%ReadChar.2]
+// CHECK:STDOUT:   %read.char.loc17: init %i32 = call %ReadChar.ref.loc17()
+// CHECK:STDOUT:   %.loc17_31.1: %i32 = value_of_initializer %read.char.loc17
+// CHECK:STDOUT:   %.loc17_31.2: %i32 = converted %read.char.loc17, %.loc17_31.1
+// CHECK:STDOUT:   %m: %i32 = bind_name m, %.loc17_31.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 39 - 11
toolchain/lower/handle_call.cpp

@@ -76,20 +76,48 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::None:
       CARBON_FATAL("No callee in function call.");
 
+    case SemIR::BuiltinFunctionKind::PrintChar: {
+      auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
+      llvm::Value* arg_value = context.builder().CreateSExtOrTrunc(
+          context.GetValue(arg_ids[0]), i32_type);
+      auto putchar = context.llvm_module().getOrInsertFunction(
+          "putchar", i32_type, i32_type);
+      auto* result = context.builder().CreateCall(putchar, {arg_value});
+      context.SetLocal(
+          inst_id,
+          context.builder().CreateSExtOrTrunc(
+              result, context.GetType(
+                          context.sem_ir().insts().Get(inst_id).type_id())));
+      return;
+    }
+
     case SemIR::BuiltinFunctionKind::PrintInt: {
-      llvm::Type* char_type[] = {llvm::PointerType::get(
-          llvm::Type::getInt8Ty(context.llvm_context()), 0)};
-      auto* printf_type = llvm::FunctionType::get(
-          llvm::IntegerType::getInt32Ty(context.llvm_context()),
-          llvm::ArrayRef<llvm::Type*>(char_type, 1), /*isVarArg=*/true);
-      auto callee =
+      auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
+      auto* ptr_type = llvm::PointerType::get(context.llvm_context(), 0);
+      auto* printf_type = llvm::FunctionType::get(i32_type, {ptr_type},
+                                                  /*isVarArg=*/true);
+      llvm::FunctionCallee printf =
           context.llvm_module().getOrInsertFunction("printf", printf_type);
 
-      llvm::SmallVector<llvm::Value*, 1> args = {
-          context.builder().CreateGlobalString("%d\n", "printf.int.format")};
-      args.push_back(context.GetValue(arg_ids[0]));
-      context.SetLocal(inst_id,
-                       context.builder().CreateCall(callee, args, "printf"));
+      llvm::Value* format_string =
+          context.builder().CreateGlobalString("%d\n", "printf.int.format");
+      llvm::Value* arg_value = context.builder().CreateSExtOrTrunc(
+          context.GetValue(arg_ids[0]), i32_type);
+      context.SetLocal(inst_id, context.builder().CreateCall(
+                                    printf, {format_string, arg_value}));
+      return;
+    }
+
+    case SemIR::BuiltinFunctionKind::ReadChar: {
+      auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
+      auto getchar =
+          context.llvm_module().getOrInsertFunction("getchar", i32_type);
+      auto* result = context.builder().CreateCall(getchar, {});
+      context.SetLocal(
+          inst_id,
+          context.builder().CreateSExtOrTrunc(
+              result, context.GetType(
+                          context.sem_ir().insts().Get(inst_id).type_id())));
       return;
     }
 

+ 0 - 39
toolchain/lower/testdata/builtins/print.carbon

@@ -1,39 +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
-// TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/builtins/print.carbon
-// TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/builtins/print.carbon
-
-fn Main() {
-  Core.Print(1);
-}
-
-// CHECK:STDOUT: ; ModuleID = 'print.carbon'
-// CHECK:STDOUT: source_filename = "print.carbon"
-// CHECK:STDOUT:
-// CHECK:STDOUT: @printf.int.format = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
-// CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CMain.Main() !dbg !4 {
-// CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %print.int.printf = call i32 (ptr, ...) @printf(ptr @printf.int.format, i32 1), !dbg !7
-// CHECK:STDOUT:   ret void, !dbg !8
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: declare i32 @printf(ptr, ...)
-// CHECK:STDOUT:
-// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
-// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
-// CHECK:STDOUT:
-// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
-// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
-// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
-// CHECK:STDOUT: !3 = !DIFile(filename: "print.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
-// CHECK:STDOUT: !6 = !{}
-// CHECK:STDOUT: !7 = !DILocation(line: 12, column: 3, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 11, column: 1, scope: !4)

+ 86 - 0
toolchain/lower/testdata/builtins/print_read.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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/builtins/print_read.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/builtins/print_read.carbon
+
+import Core library "io";
+
+fn PrintChar(c: i8) -> i32 = "print.char";
+fn ReadChar() -> i32 = "read.char";
+
+fn Main() {
+  Core.Print(1);
+
+  let EOF: i32 = -(1 as i32);
+  while (ReadChar() != EOF) {
+    // "Hi"
+    if (PrintChar(0x48) != EOF) {
+      PrintChar(0x69);
+    }
+  }
+}
+
+// CHECK:STDOUT: ; ModuleID = 'print_read.carbon'
+// CHECK:STDOUT: source_filename = "print_read.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @printf.int.format = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMain.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %print.int = call i32 (ptr, ...) @printf(ptr @printf.int.format, i32 1), !dbg !7
+// CHECK:STDOUT:   br label %while.cond, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: while.cond:                                       ; preds = %if.else, %entry
+// CHECK:STDOUT:   %read.char = call i32 @getchar(), !dbg !9
+// CHECK:STDOUT:   %int.neq.loc20 = icmp ne i32 %read.char, -1, !dbg !9
+// CHECK:STDOUT:   br i1 %int.neq.loc20, label %while.body, label %while.done, !dbg !8
+// CHECK:STDOUT:
+// CHECK:STDOUT: while.body:                                       ; preds = %while.cond
+// CHECK:STDOUT:   %print.char.loc22 = call i32 @putchar(i32 72), !dbg !10
+// CHECK:STDOUT:   %int.neq.loc22 = icmp ne i32 %print.char.loc22, -1, !dbg !10
+// CHECK:STDOUT:   br i1 %int.neq.loc22, label %if.then, label %if.else, !dbg !11
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.then:                                          ; preds = %while.body
+// CHECK:STDOUT:   %print.char.loc23 = call i32 @putchar(i32 105), !dbg !12
+// CHECK:STDOUT:   br label %if.else, !dbg !13
+// CHECK:STDOUT:
+// CHECK:STDOUT: if.else:                                          ; preds = %if.then, %while.body
+// CHECK:STDOUT:   br label %while.cond, !dbg !14
+// CHECK:STDOUT:
+// CHECK:STDOUT: while.done:                                       ; preds = %while.cond
+// CHECK:STDOUT:   ret void, !dbg !15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @printf(ptr, ...)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @getchar()
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @putchar(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @putchar, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "print_read.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Main", linkageName: "_CMain.Main", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 17, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 20, column: 9, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 20, column: 10, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 9, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 22, column: 8, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 23, column: 7, scope: !4)
+// CHECK:STDOUT: !13 = !DILocation(line: 22, column: 5, scope: !4)
+// CHECK:STDOUT: !14 = !DILocation(line: 20, column: 3, scope: !4)
+// CHECK:STDOUT: !15 = !DILocation(line: 16, column: 1, scope: !4)

+ 11 - 2
toolchain/sem_ir/builtin_function_kind.cpp

@@ -63,7 +63,8 @@ struct BuiltinType {
   }
 };
 
-// Constraint that the function has no return.
+// Constraint that a type is `()`, used as the return type of builtin functions
+// with no return value.
 struct NoReturn {
   static auto Check(const File& sem_ir, ValidateState& /*state*/,
                     TypeId type_id) -> bool {
@@ -194,10 +195,18 @@ using FloatT = TypeParam<0, AnyFloat>;
 // Not a builtin function.
 constexpr BuiltinInfo None = {"", nullptr};
 
-// Prints an argument.
+// Prints a single character.
+constexpr BuiltinInfo PrintChar = {"print.char",
+                                   ValidateSignature<auto(AnyInt)->AnyInt>};
+
+// Prints an integer.
 constexpr BuiltinInfo PrintInt = {"print.int",
                                   ValidateSignature<auto(AnyInt)->NoReturn>};
 
+// Reads a single character from stdin.
+constexpr BuiltinInfo ReadChar = {"read.char",
+                                  ValidateSignature<auto()->AnyInt>};
+
 // Returns the `Core.IntLiteral` type.
 constexpr BuiltinInfo IntLiteralMakeType = {"int_literal.make_type",
                                             ValidateSignature<auto()->Type>};

+ 4 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -17,7 +17,11 @@
 #endif
 
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
+
+// Temporary builtins for primitive IO.
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintChar)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintInt)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(ReadChar)
 
 // Type factories.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntLiteralMakeType)