mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Add a no-op builtin function which shouldn't generate code. (#5306)
This is part of a broader plan to have noop destructor functions for trivial destruction. Note this emits a SemIR call (`%no_op: init %empty_tuple.type = call %NoOp.ref() [concrete = constants.%empty_tuple]`), but not LLVM IR. My thought was this was probably okay, since even though it'll be a little spammy with destructor calls, the flipside is there'll probably already be a fair amount for the name reference, and this at least shows when the call is injected (and discarded). --------- Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
co-authored by
Chandler Carruth
parent
da83b65aa2
commit
b49e89e97e
@@ -1379,6 +1379,16 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
|
||||
case SemIR::BuiltinFunctionKind::None:
|
||||
CARBON_FATAL("Not a builtin function.");
|
||||
|
||||
case SemIR::BuiltinFunctionKind::NoOp: {
|
||||
// Return an empty tuple value.
|
||||
auto type_id = GetTupleType(eval_context.context(), {});
|
||||
return MakeConstantResult(
|
||||
eval_context.context(),
|
||||
SemIR::TupleValue{.type_id = type_id,
|
||||
.elements_id = SemIR::InstBlockId::Empty},
|
||||
phase);
|
||||
}
|
||||
|
||||
case SemIR::BuiltinFunctionKind::PrintChar:
|
||||
case SemIR::BuiltinFunctionKind::PrintInt:
|
||||
case SemIR::BuiltinFunctionKind::ReadChar: {
|
||||
|
||||
@@ -73,7 +73,7 @@ auto CheckFunctionTypeMatches(Context& context,
|
||||
}
|
||||
|
||||
auto CheckFunctionReturnType(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::Function& function,
|
||||
const SemIR::Function& function,
|
||||
SemIR::SpecificId specific_id)
|
||||
-> SemIR::ReturnTypeInfo {
|
||||
auto return_info = SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(),
|
||||
|
||||
@@ -54,7 +54,7 @@ inline auto CheckFunctionTypeMatches(Context& context,
|
||||
// necessary, and returns information about how the function returns its return
|
||||
// value.
|
||||
auto CheckFunctionReturnType(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::Function& function,
|
||||
const SemIR::Function& function,
|
||||
SemIR::SpecificId specific_id)
|
||||
-> SemIR::ReturnTypeInfo;
|
||||
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// 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/no_prelude/no_op.carbon
|
||||
// TIP: To dump output, run:
|
||||
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/no_prelude/no_op.carbon
|
||||
|
||||
// --- no_op.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn NoOp() = "no_op";
|
||||
|
||||
fn F() {
|
||||
NoOp();
|
||||
}
|
||||
|
||||
// --- explicit_return.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn NoOp() -> () = "no_op";
|
||||
|
||||
fn F() {
|
||||
NoOp();
|
||||
}
|
||||
|
||||
// --- assign.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
fn NoOp() = "no_op";
|
||||
|
||||
fn F() {
|
||||
var x: () = NoOp();
|
||||
}
|
||||
|
||||
// --- fail_signature.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
// CHECK:STDERR: fail_signature.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "no_op" [InvalidBuiltinSignature]
|
||||
// CHECK:STDERR: fn NoOp() -> {} = "no_op";
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR:
|
||||
fn NoOp() -> {} = "no_op";
|
||||
|
||||
// CHECK:STDOUT: --- no_op.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %NoOp.type: type = fn_type @NoOp [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %NoOp: %NoOp.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: file {
|
||||
// CHECK:STDOUT: package: <namespace> = namespace [concrete] {
|
||||
// CHECK:STDOUT: .NoOp = %NoOp.decl
|
||||
// CHECK:STDOUT: .F = %F.decl
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %NoOp.decl: %NoOp.type = fn_decl @NoOp [concrete = constants.%NoOp] {} {}
|
||||
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @NoOp() = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @F() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %NoOp.ref: %NoOp.type = name_ref NoOp, file.%NoOp.decl [concrete = constants.%NoOp]
|
||||
// CHECK:STDOUT: %no_op: init %empty_tuple.type = call %NoOp.ref() [concrete = constants.%empty_tuple]
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- explicit_return.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %NoOp.type: type = fn_type @NoOp [concrete]
|
||||
// CHECK:STDOUT: %NoOp: %NoOp.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: file {
|
||||
// CHECK:STDOUT: package: <namespace> = namespace [concrete] {
|
||||
// CHECK:STDOUT: .NoOp = %NoOp.decl
|
||||
// CHECK:STDOUT: .F = %F.decl
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %NoOp.decl: %NoOp.type = fn_decl @NoOp [concrete = constants.%NoOp] {
|
||||
// CHECK:STDOUT: %return.patt: %empty_tuple.type = return_slot_pattern
|
||||
// CHECK:STDOUT: %return.param_patt: %empty_tuple.type = out_param_pattern %return.patt, call_param0
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %.loc4_15.1: %empty_tuple.type = tuple_literal ()
|
||||
// CHECK:STDOUT: %.loc4_15.2: type = converted %.loc4_15.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
|
||||
// CHECK:STDOUT: %return.param: ref %empty_tuple.type = out_param call_param0
|
||||
// CHECK:STDOUT: %return: ref %empty_tuple.type = return_slot %return.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @NoOp() -> %empty_tuple.type = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @F() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %NoOp.ref: %NoOp.type = name_ref NoOp, file.%NoOp.decl [concrete = constants.%NoOp]
|
||||
// CHECK:STDOUT: %no_op: init %empty_tuple.type = call %NoOp.ref() [concrete = constants.%empty_tuple]
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- assign.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %NoOp.type: type = fn_type @NoOp [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %NoOp: %NoOp.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: file {
|
||||
// CHECK:STDOUT: package: <namespace> = namespace [concrete] {
|
||||
// CHECK:STDOUT: .NoOp = %NoOp.decl
|
||||
// CHECK:STDOUT: .F = %F.decl
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %NoOp.decl: %NoOp.type = fn_decl @NoOp [concrete = constants.%NoOp] {} {}
|
||||
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @NoOp() = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @F() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %x.patt: %empty_tuple.type = binding_pattern x
|
||||
// CHECK:STDOUT: %.loc7_3: %empty_tuple.type = var_pattern %x.patt
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %x.var: ref %empty_tuple.type = var x
|
||||
// CHECK:STDOUT: %NoOp.ref: %NoOp.type = name_ref NoOp, file.%NoOp.decl [concrete = constants.%NoOp]
|
||||
// CHECK:STDOUT: %no_op: init %empty_tuple.type = call %NoOp.ref() [concrete = constants.%empty_tuple]
|
||||
// CHECK:STDOUT: assign %x.var, %no_op
|
||||
// CHECK:STDOUT: %.loc7_11.1: type = splice_block %.loc7_11.3 [concrete = constants.%empty_tuple.type] {
|
||||
// CHECK:STDOUT: %.loc7_11.2: %empty_tuple.type = tuple_literal ()
|
||||
// CHECK:STDOUT: %.loc7_11.3: type = converted %.loc7_11.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %x: ref %empty_tuple.type = bind_name x, %x.var
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- fail_signature.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %NoOp.type: type = fn_type @NoOp [concrete]
|
||||
// CHECK:STDOUT: %NoOp: %NoOp.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: file {
|
||||
// CHECK:STDOUT: package: <namespace> = namespace [concrete] {
|
||||
// CHECK:STDOUT: .NoOp = %NoOp.decl
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %NoOp.decl: %NoOp.type = fn_decl @NoOp [concrete = constants.%NoOp] {
|
||||
// CHECK:STDOUT: %return.patt: %empty_struct_type = return_slot_pattern
|
||||
// CHECK:STDOUT: %return.param_patt: %empty_struct_type = out_param_pattern %return.patt, call_param0
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %.loc8_15.1: %empty_struct_type = struct_literal ()
|
||||
// CHECK:STDOUT: %.loc8_15.2: type = converted %.loc8_15.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
|
||||
// CHECK:STDOUT: %return.param: ref %empty_struct_type = out_param call_param0
|
||||
// CHECK:STDOUT: %return: ref %empty_struct_type = return_slot %return.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @NoOp() -> %empty_struct_type;
|
||||
// CHECK:STDOUT:
|
||||
@@ -160,6 +160,9 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
|
||||
case SemIR::BuiltinFunctionKind::None:
|
||||
CARBON_FATAL("No callee in function call.");
|
||||
|
||||
case SemIR::BuiltinFunctionKind::NoOp:
|
||||
CARBON_FATAL("NoOp is a constant expression and won't reach this.");
|
||||
|
||||
case SemIR::BuiltinFunctionKind::PrintChar: {
|
||||
auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
|
||||
llvm::Value* arg_value = context.builder().CreateSExtOrTrunc(
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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/no_prelude/no_op.carbon
|
||||
// TIP: To dump output, run:
|
||||
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/builtins/no_prelude/no_op.carbon
|
||||
|
||||
fn NoOp() = "no_op";
|
||||
|
||||
fn F() {
|
||||
NoOp();
|
||||
}
|
||||
|
||||
fn G() -> () {
|
||||
var a: () = NoOp();
|
||||
return a;
|
||||
}
|
||||
|
||||
// CHECK:STDOUT: ; ModuleID = 'no_op.carbon'
|
||||
// CHECK:STDOUT: source_filename = "no_op.carbon"
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: define void @_CF.Main() !dbg !4 {
|
||||
// CHECK:STDOUT: entry:
|
||||
// CHECK:STDOUT: ret void, !dbg !7
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: define void @_CG.Main() !dbg !8 {
|
||||
// CHECK:STDOUT: entry:
|
||||
// CHECK:STDOUT: %a.var = alloca {}, align 8, !dbg !9
|
||||
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 0, ptr %a.var), !dbg !9
|
||||
// CHECK:STDOUT: ret void, !dbg !10
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
|
||||
// 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: "no_op.carbon", directory: "")
|
||||
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
|
||||
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
|
||||
// CHECK:STDOUT: !6 = !{}
|
||||
// CHECK:STDOUT: !7 = !DILocation(line: 13, column: 1, scope: !4)
|
||||
// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
|
||||
// CHECK:STDOUT: !9 = !DILocation(line: 18, column: 3, scope: !8)
|
||||
// CHECK:STDOUT: !10 = !DILocation(line: 19, column: 3, scope: !8)
|
||||
@@ -218,6 +218,8 @@ using FloatT = TypeParam<0, AnyFloat>;
|
||||
// Not a builtin function.
|
||||
constexpr BuiltinInfo None = {"", nullptr};
|
||||
|
||||
constexpr BuiltinInfo NoOp = {"no_op", ValidateSignature<auto()->NoReturn>};
|
||||
|
||||
// Prints a single character.
|
||||
constexpr BuiltinInfo PrintChar = {
|
||||
"print.char", ValidateSignature<auto(AnySizedInt)->AnySizedInt>};
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
|
||||
|
||||
// A no-op function definition; calls should be elided when lowering.
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(NoOp)
|
||||
|
||||
// Temporary builtins for primitive IO.
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintChar)
|
||||
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PrintInt)
|
||||
|
||||
Reference in New Issue
Block a user