Преглед изворни кода

Add a basic Core.Print function for ints. (#4078)

We'd been discussing that explorer remains necessary for print, and I
was wondering if this kind of approach would be okay (we _probably_ want
this to work, based on #2110, albeit with more overloads -- but I don't
think there's a good way to support overloads at the moment).

```
╚╡../bazel-bin/examples/sieve
2
3
5
7
11
13
17
19
23
29
31
37
41
43
...
```
Jon Ross-Perkins пре 1 година
родитељ
комит
8bb80d8271

+ 4 - 0
core/prelude.carbon

@@ -8,3 +8,7 @@ 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";

+ 1 - 0
examples/sieve.carbon

@@ -116,6 +116,7 @@ fn Run() -> i32 {
   while (n < 1000) {
     if (s.is_prime[n]) {
       ++number_of_primes;
+      Core.Print(n);
       s.MarkMultiplesNotPrime(n);
     }
     ++n;

+ 6 - 0
toolchain/check/eval.cpp

@@ -10,6 +10,7 @@
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst_kind.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -709,6 +710,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.
+      return SemIR::ConstantId::NotConstant;
+    }
+
     case SemIR::BuiltinFunctionKind::IntMakeType32: {
       return context.constant_values().Get(SemIR::InstId::BuiltinIntType);
     }

+ 71 - 0
toolchain/check/testdata/builtins/print.carbon

@@ -0,0 +1,71 @@
+// 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.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/print.carbon
+
+fn Print(a: i32) = "print.int";
+
+fn Main() {
+  Print(1);
+
+  Core.Print(2);
+}
+
+// CHECK:STDOUT: --- print.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %Print.type.1: type = fn_type @Print.1 [template]
+// CHECK:STDOUT:   %Print.1: %Print.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %Main.type: type = fn_type @Main [template]
+// CHECK:STDOUT:   %Main: %Main.type = struct_value () [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %Print.type.2: type = fn_type @Print.2 [template]
+// CHECK:STDOUT:   %Print.2: %Print.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %.3: i32 = int_literal 2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Print = %Print.decl
+// CHECK:STDOUT:     .Main = %Main.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %import_ref.1: %Int32.type = import_ref ir3, inst+3, loaded [template = constants.%Int32]
+// CHECK:STDOUT:   %Print.decl: %Print.type.1 = fn_decl @Print.1 [template = constants.%Print.1] {
+// CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc11_13.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc11_13.2: type = converted %int.make_type_32, %.loc11_13.1 [template = i32]
+// CHECK:STDOUT:     %a.loc11_10.1: i32 = param a
+// CHECK:STDOUT:     @Print.1.%a: i32 = bind_name a, %a.loc11_10.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Main.decl: %Main.type = fn_decl @Main [template = constants.%Main] {}
+// CHECK:STDOUT:   %import_ref.2: %Print.type.2 = import_ref ir1, inst+42, loaded [template = constants.%Print.2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Print.1(%a: i32) = "print.int";
+// 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:   %.loc14: i32 = int_literal 1 [template = constants.%.2]
+// CHECK:STDOUT:   %print.int.loc14: init %.1 = call %Print.ref.loc14(%.loc14)
+// CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, file.%Core [template = file.%Core]
+// CHECK:STDOUT:   %Print.ref.loc16: %Print.type.2 = name_ref Print, file.%import_ref.2 [template = constants.%Print.2]
+// CHECK:STDOUT:   %.loc16: i32 = int_literal 2 [template = constants.%.3]
+// CHECK:STDOUT:   %print.int.loc16: init %.1 = call %Print.ref.loc16(%.loc16)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Print.2(%x: i32) = "print.int";
+// CHECK:STDOUT:

+ 1 - 2
toolchain/codegen/codegen.cpp

@@ -29,10 +29,9 @@ auto CodeGen::Make(llvm::Module& module, llvm::StringRef target_triple,
   constexpr llvm::StringLiteral Features = "";
 
   llvm::TargetOptions target_opts;
-  std::optional<llvm::Reloc::Model> reloc_model;
   CodeGen codegen(module, errors);
   codegen.target_machine_.reset(target->createTargetMachine(
-      target_triple, CPU, Features, target_opts, reloc_model));
+      target_triple, CPU, Features, target_opts, llvm::Reloc::PIC_));
   return codegen;
 }
 

+ 17 - 0
toolchain/lower/handle.cpp

@@ -235,6 +235,23 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::None:
       CARBON_FATAL() << "No callee in function call.";
 
+    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 =
+          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"));
+      return;
+    }
+
     case SemIR::BuiltinFunctionKind::BoolMakeType:
     case SemIR::BuiltinFunctionKind::FloatMakeType:
     case SemIR::BuiltinFunctionKind::IntMakeType32:

+ 26 - 0
toolchain/lower/testdata/builtins/print.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
+//
+// 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 @Main() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %print.int.printf = call i32 (ptr, ...) @printf(ptr @printf.int.format, i32 1)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare i32 @printf(ptr, ...)

+ 16 - 0
toolchain/sem_ir/builtin_function_kind.cpp

@@ -60,6 +60,18 @@ struct BuiltinType {
   }
 };
 
+// Constraint that the function has no return.
+struct NoReturn {
+  static auto Check(const File& sem_ir, ValidateState& /*state*/,
+                    TypeId type_id) -> bool {
+    auto tuple = sem_ir.types().TryGetAs<SemIR::TupleType>(type_id);
+    if (!tuple) {
+      return false;
+    }
+    return sem_ir.type_blocks().Get(tuple->elements_id).empty();
+  }
+};
+
 // Constraint that a type is `bool`.
 using Bool = BuiltinType<InstId::BuiltinBoolType>;
 
@@ -157,6 +169,10 @@ using FloatT = TypeParam<0, AnyFloat>;
 // Not a builtin function.
 constexpr BuiltinInfo None = {"", nullptr};
 
+// Prints an argument.
+constexpr BuiltinInfo PrintInt = {"print.int",
+                                  ValidateSignature<auto(AnyInt)->NoReturn>};
+
 // Returns the `i32` type. Doesn't take a bit size because we need an integer
 // type as a basis for that.
 constexpr BuiltinInfo IntMakeType32 = {"int.make_type_32",

+ 1 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -17,6 +17,7 @@
 #endif
 
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintInt)
 
 // Type factories.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeType32)