Просмотр исходного кода

Add support for int return type in Carbon/C++ interop (#5114)

Adding support for importing C++ functions with `int` return type in
Carbon.

Part of #5063

Here is a demo of the functionality:

```c++
// hello_int.h;

auto foo_int() -> int;
```

```c++
// hello_int.cpp

#include "hello_int.h"

auto foo_int() -> int {
    return 1;
}
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_int.h";
import Core library "io";

fn Carbon_foo(b: i32) {
  Core.Print(b);
}

fn Run() -> i32 {
  Carbon_foo(Cpp.foo_int());
  return 0;
}
```

```
$ clang -c hello_int.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$  bazel-bin/toolchain/carbon link hello_int.o main.o --output=demo
$ ./demo
1
```
Ivana Ivanovska 1 год назад
Родитель
Сommit
ce2ff0a35d

+ 3 - 0
toolchain/check/context.h

@@ -240,6 +240,9 @@ class Context {
   auto import_ir_insts() -> ValueStore<SemIR::ImportIRInstId>& {
     return sem_ir().import_ir_insts();
   }
+  auto ast_context() -> clang::ASTContext& {
+    return sem_ir().cpp_ast()->getASTContext();
+  }
   auto names() -> SemIR::NameStoreWrapper { return sem_ir().names(); }
   auto name_scopes() -> SemIR::NameScopeStore& {
     return sem_ir().name_scopes();

+ 51 - 3
toolchain/check/import_cpp.cpp

@@ -19,6 +19,7 @@
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/import.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/literal.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
@@ -196,6 +197,52 @@ static auto ClangLookup(Context& context, SemIR::LocId loc_id,
   return lookup;
 }
 
+// Returns the return type of the given function declaration.
+// Currently only void and 32-bit int are supported.
+// TODO: Support more return types.
+static auto GetReturnType(Context& context, SemIRLoc loc_id,
+                          const clang::FunctionDecl* clang_decl)
+    -> SemIR::InstId {
+  clang::QualType ret_type = clang_decl->getReturnType().getCanonicalType();
+  if (ret_type->isVoidType()) {
+    return SemIR::InstId::None;
+  }
+  if (const auto* builtin_type = dyn_cast<clang::BuiltinType>(ret_type);
+      builtin_type && builtin_type->getKind() == clang::BuiltinType::Int) {
+    constexpr int SupportedIntWidth = 32;
+    uint64_t int_size = context.ast_context().getTypeSize(ret_type);
+    if (int_size != SupportedIntWidth) {
+      // TODO: Add tests for this case.
+      context.TODO(loc_id,
+                   llvm::formatv("Unsupported: return type: {0}, size: {1}",
+                                 ret_type.getAsString(), int_size));
+      return SemIR::ErrorInst::SingletonInstId;
+    }
+    IntId size_id = context.ints().Add(int_size);
+    // TODO: Fill in a location for the type once available.
+    SemIR::TypeId type_id = MakeIntType(context, Parse::NodeId::None,
+                                        SemIR::IntKind::Signed, size_id);
+    // TODO: Fill in a location for the type once available.
+    SemIR::InstId type_inst_id = MakeIntTypeLiteral(
+        context, Parse::NodeId::None, SemIR::IntKind::Signed, size_id);
+
+    SemIR::InstId return_slot_pattern_id = AddInstInNoBlock(
+        // TODO: Fill in a location for the return type once available.
+        context, SemIR::LocIdAndInst::NoLoc(SemIR::ReturnSlotPattern(
+                     {.type_id = type_id, .type_inst_id = type_inst_id})));
+    SemIR::InstId param_pattern_id = AddInstInNoBlock(
+        // TODO: Fill in a location for the return type once available.
+        context, SemIR::LocIdAndInst::NoLoc(SemIR::OutParamPattern(
+                     {.type_id = type_id,
+                      .subpattern_id = return_slot_pattern_id,
+                      .index = SemIR::CallParamIndex::None})));
+    return param_pattern_id;
+  }
+  context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",
+                                     ret_type.getAsString()));
+  return SemIR::ErrorInst::SingletonInstId;
+}
+
 // Imports a function declaration from Clang to Carbon. If successful, returns
 // the new Carbon function declaration `InstId`.
 static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
@@ -219,8 +266,9 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
     context.TODO(loc_id, "Unsupported: Function with parameters");
     return SemIR::ErrorInst::SingletonInstId;
   }
-  if (!clang_decl->getReturnType()->isVoidType()) {
-    context.TODO(loc_id, "Unsupported: Function with non-void return type");
+
+  auto return_slot_pattern_id = GetReturnType(context, loc_id, clang_decl);
+  if (SemIR::ErrorInst::SingletonInstId == return_slot_pattern_id) {
     return SemIR::ErrorInst::SingletonInstId;
   }
 
@@ -244,7 +292,7 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
        .first_owning_decl_id = decl_id,
        .definition_id = SemIR::InstId::None},
       {.call_params_id = SemIR::InstBlockId::Empty,
-       .return_slot_pattern_id = SemIR::InstId::None,
+       .return_slot_pattern_id = return_slot_pattern_id,
        .virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
        .self_param_id = SemIR::InstId::None,
        .cpp_decl = clang_decl}};

+ 157 - 0
toolchain/check/testdata/interop/cpp/function_decl.carbon

@@ -0,0 +1,157 @@
+// 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/interop/cpp/function_decl.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function_decl.carbon
+
+// --- int_return_function_decl.h
+
+auto foo_int() -> int;
+
+// --- import_int_return_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "int_return_function_decl.h";
+
+fn Carbon_foo(val: i32);
+
+fn F() {
+  Carbon_foo(Cpp.foo_int());
+}
+
+// --- float_return_function_decl.h
+
+auto foo_float() -> float;
+
+// --- fail_import_float_return_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "float_return_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_float_return_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: float` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo_float();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_float_return_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo_float` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo_float();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo_float();
+}
+
+// CHECK:STDOUT: --- import_int_return_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %Carbon_foo.type: type = fn_type @Carbon_foo [concrete]
+// CHECK:STDOUT:   %Carbon_foo: %Carbon_foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo_int.type: type = fn_type @foo_int [concrete]
+// CHECK:STDOUT:   %foo_int: %foo_int.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     .Int = %Core.Int
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo_int = @F.%foo_int.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .Carbon_foo = %Carbon_foo.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "int_return_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Carbon_foo.decl: %Carbon_foo.type = fn_decl @Carbon_foo [concrete = constants.%Carbon_foo] {
+// CHECK:STDOUT:     %val.patt: %i32 = binding_pattern val
+// CHECK:STDOUT:     %val.param_patt: %i32 = value_param_pattern %val.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %val.param: %i32 = value_param call_param0
+// CHECK:STDOUT:     %.loc6: type = splice_block %i32 [concrete = constants.%i32] {
+// CHECK:STDOUT:       %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:       %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %val: %i32 = bind_name val, %val.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Carbon_foo(%val.param_patt: %i32);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Carbon_foo.ref: %Carbon_foo.type = name_ref Carbon_foo, file.%Carbon_foo.decl [concrete = constants.%Carbon_foo]
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %int_32.1: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.1: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %int_32.2: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.2: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %foo_int.decl: %foo_int.type = fn_decl @foo_int [concrete = constants.%foo_int] {} {}
+// CHECK:STDOUT:   %foo_int.ref: %foo_int.type = name_ref foo_int, %foo_int.decl [concrete = constants.%foo_int]
+// CHECK:STDOUT:   %foo_int.call: init %i32 = call %foo_int.ref()
+// CHECK:STDOUT:   %.loc9_26.1: %i32 = value_of_initializer %foo_int.call
+// CHECK:STDOUT:   %.loc9_26.2: %i32 = converted %foo_int.call, %.loc9_26.1
+// CHECK:STDOUT:   %Carbon_foo.call: init %empty_tuple.type = call %Carbon_foo.ref(%.loc9_26.2)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo_int[]() -> %i32;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_float_return_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo_float = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "float_return_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo_float.ref: <error> = name_ref foo_float, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 2
toolchain/check/testdata/interop/cpp/no_prelude/function_decl.carbon

@@ -153,8 +153,9 @@ fn F() {
 }
 
 // --- non_void_return_function_decl.h
+struct F;
 
-int foo();
+F foo();
 
 // --- fail_import_non_void_return_function_decl.carbon
 
@@ -163,7 +164,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "non_void_return_function_decl.h";
 
 fn F() {
-  // CHECK:STDERR: fail_import_non_void_return_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Function with non-void return type` [SemanticsTodo]
+  // CHECK:STDERR: fail_import_non_void_return_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: struct F` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~
   // CHECK:STDERR: fail_import_non_void_return_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]