Selaa lähdekoodia

C++ interop: Add support for importing globals (#6005)

This supports importing globals in the global scope and within
namespaces, and support for class static data members.

C++ Interop Demo:

```c++
// my_global.h

extern int my_global;
void inc_my_global();
```

```c++
// my_global.cpp

#include "my_global.h"

int my_global = 5;

void inc_my_global() {
  ++my_global;
}
```

```carbon
// main.carbon

library "Main";

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

fn Run() -> i32 {
  Core.Print(Cpp.my_global);
  Cpp.inc_my_global();
  Core.Print(Cpp.my_global);
  return 0;
}
```

```shell
$ clang -c my_global.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link my_global.o main.o --output=demo
$ ./demo
5
6
```

Part of #6006.
Boaz Brickner 7 kuukautta sitten
vanhempi
sitoutus
b88b53e7e3

+ 57 - 0
toolchain/check/import_cpp.cpp

@@ -28,6 +28,7 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/class.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/cpp_custom_type_mapping.h"
 #include "toolchain/check/cpp_thunk.h"
@@ -1844,6 +1845,59 @@ static auto AddDependentUnimportedDecls(const Context& context,
   }
 }
 
+static auto ImportVarDecl(Context& context, SemIR::LocId loc_id,
+                          clang::VarDecl* var_decl) -> SemIR::InstId {
+  if (SemIR::InstId existing_inst_id = LookupClangDeclInstId(context, var_decl);
+      existing_inst_id.has_value()) {
+    return existing_inst_id;
+  }
+
+  // Extract type and name.
+  clang::QualType var_type = var_decl->getType();
+  SemIR::TypeId var_type_id = MapType(context, loc_id, var_type).type_id;
+  if (!var_type_id.has_value()) {
+    context.TODO(loc_id, llvm::formatv("Unsupported: var type: {0}",
+                                       var_type.getAsString()));
+    return SemIR::ErrorInst::InstId;
+  }
+  SemIR::NameId var_name_id = AddIdentifierName(context, var_decl->getName());
+
+  SemIR::VarStorage var_storage{.type_id = var_type_id,
+                                .pattern_id = SemIR::InstId::None};
+  // We can't use the convenience for `AddPlaceholderInstInNoBlock()` with typed
+  // nodes because it doesn't support insts with cleanup.
+  SemIR::InstId var_storage_inst_id =
+      AddPlaceholderInstInNoBlock(context, {loc_id, var_storage});
+
+  auto clang_decl_id = context.sem_ir().clang_decls().Add(
+      {.decl = var_decl, .inst_id = var_storage_inst_id});
+
+  // Entity name referring to a Clang decl for mangling.
+  SemIR::EntityNameId entity_name_id =
+      context.entity_names().AddSymbolicBindingName(
+          var_name_id, GetParentNameScopeId(context, var_decl),
+          SemIR::CompileTimeBindIndex::None, false, clang_decl_id);
+
+  // Create `BindingPattern` and `VarPattern` in a `NameBindingDecl`.
+  context.pattern_block_stack().Push();
+  SemIR::TypeId pattern_type_id = GetPatternType(context, var_type_id);
+  SemIR::InstId binding_pattern_inst_id = AddPatternInst<SemIR::BindingPattern>(
+      context, loc_id,
+      {.type_id = pattern_type_id, .entity_name_id = entity_name_id});
+  var_storage.pattern_id = AddPatternInst<SemIR::VarPattern>(
+      context, Parse::VariablePatternId::None,
+      {.type_id = pattern_type_id, .subpattern_id = binding_pattern_inst_id});
+  context.imports().push_back(AddInstInNoBlock<SemIR::NameBindingDecl>(
+      context, loc_id,
+      {.pattern_block_id = context.pattern_block_stack().Pop()}));
+
+  // Finalize the `VarStorage` instruction.
+  ReplaceInstBeforeConstantUse(context, var_storage_inst_id, var_storage);
+  context.imports().push_back(var_storage_inst_id);
+
+  return var_storage_inst_id;
+}
+
 // Imports a declaration from Clang to Carbon. Returns the instruction for the
 // new Carbon declaration, which will be an ErrorInst on failure. Assumes all
 // dependencies have already been imported.
@@ -1883,6 +1937,9 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
   if (auto* enum_const_decl = dyn_cast<clang::EnumConstantDecl>(clang_decl)) {
     return ImportEnumConstantDecl(context, enum_const_decl);
   }
+  if (auto* var_decl = dyn_cast<clang::VarDecl>(clang_decl)) {
+    return ImportVarDecl(context, loc_id, var_decl);
+  }
 
   context.TODO(AddImportIRInst(context.sem_ir(), clang_decl->getLocation()),
                llvm::formatv("Unsupported: Declaration type {0}",

+ 1 - 1
toolchain/check/testdata/basics/raw_sem_ir/multifile.carbon

@@ -97,7 +97,7 @@ fn B() {
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name1: inst16, name0: inst17}}
 // CHECK:STDOUT:     name_scope1:     {inst: inst16, parent_scope: name_scope0, has_error: false, extended_scopes: [], names: {name1: inst22}}
 // CHECK:STDOUT:   entity_names:
-// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope1, index: -1, is_template: 0}
+// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope1, index: -1, is_template: 0, clang_decl_id: clang_decl_id<none>}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, call_params_id: inst_block_empty, body: [inst_block5]}
 // CHECK:STDOUT:     function1:       {name: name1, parent_scope: name_scope1}

+ 1 - 1
toolchain/check/testdata/basics/raw_sem_ir/multifile_with_textual_ir.carbon

@@ -116,7 +116,7 @@ fn B() {
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name1: inst16, name0: inst17}}
 // CHECK:STDOUT:     name_scope1:     {inst: inst16, parent_scope: name_scope0, has_error: false, extended_scopes: [], names: {name1: inst22}}
 // CHECK:STDOUT:   entity_names:
-// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope1, index: -1, is_template: 0}
+// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope1, index: -1, is_template: 0, clang_decl_id: clang_decl_id<none>}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, call_params_id: inst_block_empty, body: [inst_block5]}
 // CHECK:STDOUT:     function1:       {name: name1, parent_scope: name_scope1}

+ 3 - 3
toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon

@@ -27,9 +27,9 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst45}}
 // CHECK:STDOUT:   entity_names:
-// CHECK:STDOUT:     entity_name0:    {name: name(PeriodSelf), parent_scope: name_scope<none>, index: -1, is_template: 0}
-// CHECK:STDOUT:     entity_name1:    {name: name1, parent_scope: name_scope<none>, index: 0, is_template: 0}
-// CHECK:STDOUT:     entity_name2:    {name: name2, parent_scope: name_scope<none>, index: -1, is_template: 0}
+// CHECK:STDOUT:     entity_name0:    {name: name(PeriodSelf), parent_scope: name_scope<none>, index: -1, is_template: 0, clang_decl_id: clang_decl_id<none>}
+// CHECK:STDOUT:     entity_name1:    {name: name1, parent_scope: name_scope<none>, index: 0, is_template: 0, clang_decl_id: clang_decl_id<none>}
+// CHECK:STDOUT:     entity_name2:    {name: name2, parent_scope: name_scope<none>, index: -1, is_template: 0, clang_decl_id: clang_decl_id<none>}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, call_params_id: inst_block13, return_slot_pattern: inst41, body: [inst_block20]}
 // CHECK:STDOUT:   classes:         {}

+ 1 - 1
toolchain/check/testdata/basics/raw_sem_ir/one_file_with_textual_ir.carbon

@@ -27,7 +27,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst37}}
 // CHECK:STDOUT:   entity_names:
-// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope<none>, index: -1, is_template: 0}
+// CHECK:STDOUT:     entity_name0:    {name: name1, parent_scope: name_scope<none>, index: -1, is_template: 0, clang_decl_id: clang_decl_id<none>}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, call_params_id: inst_block9, return_slot_pattern: inst32, body: [inst_block12]}
 // CHECK:STDOUT:   classes:         {}

+ 14 - 20
toolchain/check/testdata/interop/cpp/class/class.carbon

@@ -114,26 +114,18 @@ fn MyF() {
 
 class Bar {
  public:
-  static Bar* foo;
+  static Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_static_data_member.carbon
+// --- import_static_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./static_data_member.h:4:15: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-// CHECK:STDERR:   static Bar* foo;
-// CHECK:STDERR:               ^
 import Cpp library "static_data_member.h";
 
 fn MyF() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:23: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let bar: Cpp.Bar* = Cpp.Bar.foo();
-  // CHECK:STDERR:                       ^~~~~~~~~~~
-  // CHECK:STDERR:
-  let bar: Cpp.Bar* = Cpp.Bar.foo();
+  let bar: Cpp.Bar* = Cpp.Bar.foo;
   //@dump-sem-ir-end
 }
 
@@ -367,7 +359,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_static_data_member.carbon
+// CHECK:STDOUT: --- import_static_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
@@ -381,6 +373,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
+// CHECK:STDOUT:   %foo.var: ref %ptr.f68 = var %foo.var_patt
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MyF() {
@@ -388,15 +381,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp.ref.loc16_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %Bar.ref.loc16_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc16: type = splice_block %ptr [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc16_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc16_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc16_15 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %Cpp.ref.loc8_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Bar.ref.loc8_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:   %foo.ref: ref %ptr.f68 = name_ref foo, imports.%foo.var
+// CHECK:STDOUT:   %.loc8_19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc8_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30: %ptr.f68 = bind_value %foo.ref
+// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, %.loc8_30
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 13 - 19
toolchain/check/testdata/interop/cpp/class/struct.carbon

@@ -115,23 +115,15 @@ struct Bar {
   static Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_static_data_member.carbon
+// --- import_static_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./static_data_member.h:3:24: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-// CHECK:STDERR:   static Bar* _Nonnull foo;
-// CHECK:STDERR:                        ^
 import Cpp library "static_data_member.h";
 
 fn MyF() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:23: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let bar: Cpp.Bar* = Cpp.Bar.foo();
-  // CHECK:STDERR:                       ^~~~~~~~~~~
-  // CHECK:STDERR:
-  let bar: Cpp.Bar* = Cpp.Bar.foo();
+  let bar: Cpp.Bar* = Cpp.Bar.foo;
   //@dump-sem-ir-end
 }
 
@@ -355,7 +347,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_static_data_member.carbon
+// CHECK:STDOUT: --- import_static_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
@@ -369,6 +361,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
+// CHECK:STDOUT:   %foo.var: ref %ptr.f68 = var %foo.var_patt
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MyF() {
@@ -376,15 +369,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp.ref.loc16_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %Bar.ref.loc16_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc16: type = splice_block %ptr [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc16_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc16_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc16_15 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %Cpp.ref.loc8_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Bar.ref.loc8_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:   %foo.ref: ref %ptr.f68 = name_ref foo, imports.%foo.var
+// CHECK:STDOUT:   %.loc8_19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc8_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30: %ptr.f68 = bind_value %foo.ref
+// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, %.loc8_30
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 13 - 19
toolchain/check/testdata/interop/cpp/class/union.carbon

@@ -117,23 +117,15 @@ union Bar {
   static Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_static_data_member.carbon
+// --- import_static_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./static_data_member.h:4:24: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-// CHECK:STDERR:   static Bar* _Nonnull foo;
-// CHECK:STDERR:                        ^
 import Cpp library "static_data_member.h";
 
 fn MyF() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_static_data_member.carbon:[[@LINE+4]]:23: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let bar: Cpp.Bar* = Cpp.Bar.foo();
-  // CHECK:STDERR:                       ^~~~~~~~~~~
-  // CHECK:STDERR:
-  let bar: Cpp.Bar* = Cpp.Bar.foo();
+  let bar: Cpp.Bar* = Cpp.Bar.foo;
   //@dump-sem-ir-end
 }
 
@@ -319,7 +311,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_static_data_member.carbon
+// CHECK:STDOUT: --- import_static_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
@@ -333,6 +325,7 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
+// CHECK:STDOUT:   %foo.var: ref %ptr.f68 = var %foo.var_patt
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MyF() {
@@ -340,15 +333,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp.ref.loc16_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %Bar.ref.loc16_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc16: type = splice_block %ptr [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc16_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc16_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc16_15 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %Cpp.ref.loc8_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Bar.ref.loc8_26: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:   %foo.ref: ref %ptr.f68 = name_ref foo, imports.%foo.var
+// CHECK:STDOUT:   %.loc8_19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc8_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30: %ptr.f68 = bind_value %foo.ref
+// CHECK:STDOUT:   %bar: %ptr.f68 = bind_name bar, %.loc8_30
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 266 - 0
toolchain/check/testdata/interop/cpp/globals.carbon

@@ -0,0 +1,266 @@
+// 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
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon
+// EXTRA-ARGS: --dump-sem-ir-ranges=if-present
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/globals.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/globals.carbon
+
+// ============================================================================
+// Global scope
+// ============================================================================
+
+// --- global_scope.h
+
+class C {};
+C global;
+
+// --- import_global_scope.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "global_scope.h";
+
+fn MyF() {
+  let local: Cpp.C = Cpp.global;
+}
+
+// ============================================================================
+// Namespace
+// ============================================================================
+
+// --- namespace.h
+
+namespace N {
+class C {};
+C global;
+}  // namespace N
+
+// --- import_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace.h";
+
+fn MyF() {
+  let local: Cpp.N.C = Cpp.N.global;
+}
+
+// ============================================================================
+// Unsupported global type
+// ============================================================================
+
+// --- unsupported_type.h
+
+using UnusupportedType = _BitInt(23);
+UnusupportedType global;
+
+// --- fail_import_unsupported_type.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_import_unsupported_type.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./unsupported_type.h:2:7: error: semantics TODO: `Unsupported: Type declaration: UnusupportedType` [SemanticsTodo]
+// CHECK:STDERR: using UnusupportedType = _BitInt(23);
+// CHECK:STDERR:       ^
+import Cpp library "unsupported_type.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_unsupported_type.carbon:[[@LINE+11]]:14: note: in `Cpp` name lookup for `UnusupportedType` [InCppNameLookup]
+  // CHECK:STDERR:   let local: Cpp.UnusupportedType = Cpp.global;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_import_unsupported_type.carbon:[[@LINE+7]]:37: error: semantics TODO: `Unsupported: var type: UnusupportedType` [SemanticsTodo]
+  // CHECK:STDERR:   let local: Cpp.UnusupportedType = Cpp.global;
+  // CHECK:STDERR:                                     ^~~~~~~~~~
+  // CHECK:STDERR: fail_import_unsupported_type.carbon:[[@LINE+4]]:37: note: in `Cpp` name lookup for `global` [InCppNameLookup]
+  // CHECK:STDERR:   let local: Cpp.UnusupportedType = Cpp.global;
+  // CHECK:STDERR:                                     ^~~~~~~~~~
+  // CHECK:STDERR:
+  let local: Cpp.UnusupportedType = Cpp.global;
+}
+
+// CHECK:STDOUT: --- import_global_scope.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %C [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:     .C = %C.decl
+// CHECK:STDOUT:     .global = %global.var
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %global.var: ref %C = var %global.var_patt [concrete]
+// 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:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "global_scope.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %local.patt: %pattern_type = binding_pattern local [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc7_22: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %global.ref: ref %C = name_ref global, imports.%global.var [concrete = imports.%global.var]
+// CHECK:STDOUT:   %.loc7_17: type = splice_block %C.ref [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc7_14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc7_25: %C = bind_value %global.ref
+// CHECK:STDOUT:   %local: %C = bind_name local, %.loc7_25
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %C [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:     .N = %N
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .global = %global.var
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %global.var: ref %C = var %global.var_patt [concrete]
+// 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:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %local.patt: %pattern_type = binding_pattern local [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc7_24: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %N.ref.loc7_27: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
+// CHECK:STDOUT:   %global.ref: ref %C = name_ref global, imports.%global.var [concrete = imports.%global.var]
+// CHECK:STDOUT:   %.loc7_19: type = splice_block %C.ref [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc7_14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %N.ref.loc7_17: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
+// CHECK:STDOUT:     %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc7_29: %C = bind_value %global.ref
+// CHECK:STDOUT:   %local: %C = bind_name local, %.loc7_29
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_unsupported_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.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:     .UnusupportedType = <error>
+// CHECK:STDOUT:     .global = <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:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "unsupported_type.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %local.patt: <error> = binding_pattern local [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc22_37: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %global.ref: <error> = name_ref global, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.1: <error> = splice_block <error> [concrete = <error>] {
+// CHECK:STDOUT:     %Cpp.ref.loc22_14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %UnusupportedType.ref: <error> = name_ref UnusupportedType, <error> [concrete = <error>]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %local: <error> = bind_name local, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 7 - 2
toolchain/lower/mangler.cpp

@@ -151,7 +151,7 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
     return "main";
   }
   if (function.clang_decl_id.has_value()) {
-    return MangleCppClang(dyn_cast<clang::NamedDecl>(
+    return MangleCppClang(cast<clang::NamedDecl>(
         sem_ir().clang_decls().Get(function.clang_decl_id).decl));
   }
   RawStringOstream os;
@@ -205,10 +205,15 @@ auto Mangler::MangleGlobalVariable(SemIR::InstId pattern_id) -> std::string {
     return std::string();
   }
 
+  auto var_name = sem_ir().entity_names().Get(var_name_id);
+  if (var_name.clang_decl_id.has_value()) {
+    return MangleCppClang(cast<clang::NamedDecl>(
+        sem_ir().clang_decls().Get(var_name.clang_decl_id).decl));
+  }
+
   RawStringOstream os;
   os << "_C";
 
-  auto var_name = sem_ir().entity_names().Get(var_name_id);
   MangleNameId(os, var_name.name_id);
   // TODO: If the variable is private, also include the library name as part of
   // the mangling.

+ 118 - 1
toolchain/lower/testdata/interop/cpp/extern_c.carbon

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -28,6 +28,44 @@ fn MyF() {
   Cpp.foo();
 }
 
+// ============================================================================
+// extern "C" variable
+// ============================================================================
+
+// --- extern_c_variable.h
+
+extern "C" int foo;
+
+// --- import_extern_c_variable.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "extern_c_variable.h";
+
+fn MyF() -> i32 {
+  return Cpp.foo;
+}
+
+// ============================================================================
+// extern "C" function with C++ special name
+// ============================================================================
+
+// --- extern_c_with_special_name.h
+
+struct X {};
+
+extern "C" X operator+(X, X);
+
+// --- import_extern_c_with_special_name.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "extern_c_with_special_name.h";
+
+fn MyF(a: Cpp.X, b: Cpp.X) -> Cpp.X {
+  return a + b;
+}
+
 // ============================================================================
 // extern "C" function with asm label
 // ============================================================================
@@ -74,6 +112,85 @@ fn MyF() {
 // CHECK:STDOUT: !9 = !{}
 // CHECK:STDOUT: !10 = !DILocation(line: 7, column: 3, scope: !7)
 // CHECK:STDOUT: !11 = !DILocation(line: 6, column: 1, scope: !7)
+// CHECK:STDOUT: ; ModuleID = 'import_extern_c_variable.carbon'
+// CHECK:STDOUT: source_filename = "import_extern_c_variable.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @foo = external global i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CMyF.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc7 = load i32, ptr @foo, align 4, !dbg !10
+// CHECK:STDOUT:   ret i32 %.loc7, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_extern_c_variable.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 10, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !7)
+// CHECK:STDOUT: ; ModuleID = 'import_extern_c_with_special_name.carbon'
+// CHECK:STDOUT: source_filename = "import_extern_c_with_special_name.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %struct.X = type { i8 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main(ptr sret({}) %return, ptr %a, ptr %b) !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Zpl1XS_.carbon_thunk(ptr %a, ptr %b, ptr %return), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Zpl1XS_(ptr sret({}), ptr, ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress
+// CHECK:STDOUT: define dso_local void @_Zpl1XS_.carbon_thunk(ptr %0, ptr %1, ptr %return) #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca ptr, align 8
+// CHECK:STDOUT:   %.addr1 = alloca ptr, align 8
+// CHECK:STDOUT:   %return.addr = alloca ptr, align 8
+// CHECK:STDOUT:   %agg.tmp = alloca %struct.X, align 1
+// CHECK:STDOUT:   %agg.tmp2 = alloca %struct.X, align 1
+// CHECK:STDOUT:   %undef.agg.tmp = alloca %struct.X, align 1
+// CHECK:STDOUT:   store ptr %0, ptr %.addr, align 8
+// CHECK:STDOUT:   store ptr %1, ptr %.addr1, align 8
+// CHECK:STDOUT:   store ptr %return, ptr %return.addr, align 8
+// CHECK:STDOUT:   %2 = load ptr, ptr %return.addr, align 8
+// CHECK:STDOUT:   %3 = load ptr, ptr %.addr, align 8
+// CHECK:STDOUT:   %4 = load ptr, ptr %.addr1, align 8
+// CHECK:STDOUT:   call void @_Zpl1XS_()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_extern_c_with_special_name.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 10, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !7)
 // CHECK:STDOUT: ; ModuleID = 'import_extern_c_with_asm_label.carbon'
 // CHECK:STDOUT: source_filename = "import_extern_c_with_asm_label.carbon"
 // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"

+ 0 - 61
toolchain/lower/testdata/interop/cpp/fail_extern_c.carbon

@@ -1,61 +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
-//
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
-//
-// AUTOUPDATE
-// TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/fail_extern_c.carbon
-// TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/fail_extern_c.carbon
-
-// These tests were factored out of `extern_c.carbon` because we do not generate
-// LLVM IR if any test contains errors. They should be moved back once they can
-// successfully compile.
-
-// ============================================================================
-// extern "C" variable
-// ============================================================================
-
-// --- extern_c_variable.h
-
-extern "C" int foo;
-
-// --- fail_todo_import_extern_c_variable.carbon
-
-library "[[@TEST_NAME]]";
-
-// CHECK:STDERR: fail_todo_import_extern_c_variable.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./extern_c_variable.h:2:16: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-// CHECK:STDERR: extern "C" int foo;
-// CHECK:STDERR:                ^
-import Cpp library "extern_c_variable.h";
-
-fn MyF() -> i32 {
-  // CHECK:STDERR: fail_todo_import_extern_c_variable.carbon:[[@LINE+4]]:10: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   return Cpp.foo;
-  // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR:
-  return Cpp.foo;
-}
-
-// ============================================================================
-// extern "C" function with C++ special name
-// ============================================================================
-
-// --- extern_c_with_special_name.h
-
-struct X {};
-
-extern "C" X operator+(X, X);
-
-// --- import_extern_c_with_special_name.carbon
-
-library "[[@TEST_NAME]]";
-
-import Cpp library "extern_c_with_special_name.h";
-
-fn MyF(a: Cpp.X, b: Cpp.X) -> Cpp.X {
-  return a + b;
-}

+ 57 - 0
toolchain/lower/testdata/interop/cpp/globals.carbon

@@ -0,0 +1,57 @@
+// 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
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/globals.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/globals.carbon
+
+// ============================================================================
+// Global
+// ============================================================================
+
+// --- global.h
+
+class C {};
+C global;
+
+// --- import_global.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "global.h";
+
+fn MyF() {
+  let local: Cpp.C = Cpp.global;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'import_global.carbon'
+// CHECK:STDOUT: source_filename = "import_global.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @global = external global {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_global.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 1, scope: !7)

+ 10 - 3
toolchain/sem_ir/entity_name.h

@@ -8,6 +8,7 @@
 #include "common/hashing.h"
 #include "common/set.h"
 #include "toolchain/base/value_store.h"
+#include "toolchain/sem_ir/clang_decl.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::SemIR {
@@ -16,7 +17,7 @@ struct EntityName : public Printable<EntityName> {
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "{name: " << name_id << ", parent_scope: " << parent_scope_id
         << ", index: " << bind_index_value << ", is_template: " << is_template
-        << "}";
+        << ", clang_decl_id: " << clang_decl_id << "}";
   }
 
   friend auto CarbonHashtableEq(const EntityName& lhs, const EntityName& rhs)
@@ -55,6 +56,10 @@ struct EntityName : public Printable<EntityName> {
   int32_t bind_index_value : 31 = CompileTimeBindIndex::None.index;
   // Whether this binding is a template parameter.
   bool is_template : 1 = false;
+  // For imported C++ global variable names, the Clang decl to use for mangling.
+  // TODO: Move the mapping between global variables and the clang decl to avoid
+  // paying extra memory when the names are not imported from C++.
+  ClangDeclId clang_decl_id = ClangDeclId::None;
 };
 
 // Value store for EntityName. In addition to the regular ValueStore
@@ -63,12 +68,14 @@ struct EntityNameStore : public ValueStore<EntityNameId, EntityName> {
  public:
   // Adds an entity name for a symbolic binding.
   auto AddSymbolicBindingName(NameId name_id, NameScopeId parent_scope_id,
-                              CompileTimeBindIndex bind_index, bool is_template)
+                              CompileTimeBindIndex bind_index, bool is_template,
+                              ClangDeclId clang_decl_id = ClangDeclId::None)
       -> EntityNameId {
     return Add({.name_id = name_id,
                 .parent_scope_id = parent_scope_id,
                 .bind_index_value = bind_index.index,
-                .is_template = is_template});
+                .is_template = is_template,
+                .clang_decl_id = clang_decl_id});
   }
 
   // Convert an `EntityName` to a canonical ID. All calls to this with