From 8bb80d82710a3e09a4511b10dc4eaf34141a442d Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Tue, 25 Jun 2024 14:32:07 -0700 Subject: [PATCH] Add a basic Core.Print function for ints. (#4078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ... ``` --- core/prelude.carbon | 4 ++ examples/sieve.carbon | 1 + toolchain/check/eval.cpp | 6 ++ .../check/testdata/builtins/print.carbon | 71 +++++++++++++++++++ toolchain/codegen/codegen.cpp | 3 +- toolchain/lower/handle.cpp | 17 +++++ .../lower/testdata/builtins/print.carbon | 26 +++++++ toolchain/sem_ir/builtin_function_kind.cpp | 16 +++++ toolchain/sem_ir/builtin_function_kind.def | 1 + 9 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 toolchain/check/testdata/builtins/print.carbon create mode 100644 toolchain/lower/testdata/builtins/print.carbon diff --git a/core/prelude.carbon b/core/prelude.carbon index a6a9e2a9d7a20..8e3969c3e0122 100644 --- a/core/prelude.carbon +++ b/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"; diff --git a/examples/sieve.carbon b/examples/sieve.carbon index 1d2c6eeda179d..4a12f95165b58 100644 --- a/examples/sieve.carbon +++ b/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; diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 6146653bdb7d8..2cd46932b2323 100644 --- a/toolchain/check/eval.cpp +++ b/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); } diff --git a/toolchain/check/testdata/builtins/print.carbon b/toolchain/check/testdata/builtins/print.carbon new file mode 100644 index 0000000000000..56dde46dd7ce2 --- /dev/null +++ b/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 [template] { +// CHECK:STDOUT: .Core = %Core +// CHECK:STDOUT: .Print = %Print.decl +// CHECK:STDOUT: .Main = %Main.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core: = 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: = 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: diff --git a/toolchain/codegen/codegen.cpp b/toolchain/codegen/codegen.cpp index 5bfd8e01e3f49..9d63b30d38a7b 100644 --- a/toolchain/codegen/codegen.cpp +++ b/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 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; } diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 3fbcee885a107..aa847e82e28da 100644 --- a/toolchain/lower/handle.cpp +++ b/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(char_type, 1), /*isVarArg=*/true); + auto callee = + context.llvm_module().getOrInsertFunction("printf", printf_type); + + llvm::SmallVector 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: diff --git a/toolchain/lower/testdata/builtins/print.carbon b/toolchain/lower/testdata/builtins/print.carbon new file mode 100644 index 0000000000000..d22897904bb0d --- /dev/null +++ b/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, ...) diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index cc42031962261..7846cc56fa433 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/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(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; @@ -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", + ValidateSignatureNoReturn>}; + // 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", diff --git a/toolchain/sem_ir/builtin_function_kind.def b/toolchain/sem_ir/builtin_function_kind.def index 7e04f2c72cdc5..071e1ffd41802 100644 --- a/toolchain/sem_ir/builtin_function_kind.def +++ b/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)