Explorar el Código

Lower global variables (#4228)

R B hace 1 año
padre
commit
61e87c3a88

+ 2 - 2
toolchain/check/global_init.cpp

@@ -34,7 +34,7 @@ auto GlobalInit::Finalize() -> void {
   context_->inst_block_stack().Pop();
 
   auto name_id = context_->sem_ir().identifiers().Add("__global_init");
-  context_->sem_ir().functions().Add(
+  context_->sem_ir().set_global_ctor_id(context_->sem_ir().functions().Add(
       {{.name_id = SemIR::NameId::ForIdentifier(name_id),
         .parent_scope_id = SemIR::NameScopeId::Package,
         .generic_id = SemIR::GenericId::Invalid,
@@ -47,7 +47,7 @@ auto GlobalInit::Finalize() -> void {
         .non_owning_decl_id = SemIR::InstId::Invalid,
         .first_owning_decl_id = SemIR::InstId::Invalid},
        {.return_storage_id = SemIR::InstId::Invalid,
-        .body_block_ids = {SemIR::InstBlockId::GlobalInit}}});
+        .body_block_ids = {SemIR::InstBlockId::GlobalInit}}}));
 }
 
 }  // namespace Carbon::Check

+ 1 - 0
toolchain/lower/BUILD

@@ -53,5 +53,6 @@ cc_library(
         "//toolchain/sem_ir:inst_namer",
         "@llvm-project//llvm:Core",
         "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:TransformUtils",
     ],
 )

+ 30 - 3
toolchain/lower/file_context.cpp

@@ -7,6 +7,7 @@
 #include "common/vlog.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/Sequence.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/lower/constant.h"
 #include "toolchain/lower/function_context.h"
@@ -50,7 +51,15 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
     functions_[i] = BuildFunctionDecl(SemIR::FunctionId(i));
   }
 
-  // TODO: Lower global variable declarations.
+  // Lower global variable declarations.
+  for (auto inst_id :
+       sem_ir().inst_blocks().Get(sem_ir().top_inst_block_id())) {
+    // Only `VarStorage` indicates a global variable declaration in the
+    // top instruction block.
+    if (auto var = sem_ir().insts().TryGetAs<SemIR::VarStorage>(inst_id)) {
+      global_variables_.Insert(inst_id, BuildGlobalVariableDecl(*var));
+    }
+  }
 
   // Lower constants.
   constants_.resize(sem_ir_->insts().size());
@@ -60,8 +69,13 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   for (auto i : llvm::seq(sem_ir_->functions().size())) {
     BuildFunctionDefinition(SemIR::FunctionId(i));
   }
-
-  // TODO: Lower global variable initializers.
+  // Append `__global_init` to `llvm::global_ctors` to initialize global
+  // variables.
+  if (sem_ir().global_ctor_id().is_valid()) {
+    llvm::appendToGlobalCtors(llvm_module(),
+                              GetFunction(sem_ir().global_ctor_id()),
+                              /*Priority=*/0);
+  }
 
   return std::move(llvm_module_);
 }
@@ -463,4 +477,17 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
   }
 }
 
+auto FileContext::BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
+    -> llvm::GlobalVariable* {
+  // TODO: Mangle name.
+  auto mangled_name =
+      *sem_ir().names().GetAsStringIfIdentifier(var_storage.name_id);
+  auto* type =
+      var_storage.type_id.is_valid() ? GetType(var_storage.type_id) : nullptr;
+  return new llvm::GlobalVariable(llvm_module(), type,
+                                  /*isConstant=*/false,
+                                  llvm::GlobalVariable::InternalLinkage,
+                                  /*Initializer=*/nullptr, mangled_name);
+}
+
 }  // namespace Carbon::Lower

+ 11 - 0
toolchain/lower/file_context.h

@@ -59,6 +59,9 @@ class FileContext {
   auto llvm_module() -> llvm::Module& { return *llvm_module_; }
   auto sem_ir() -> const SemIR::File& { return *sem_ir_; }
   auto inst_namer() -> const SemIR::InstNamer* { return inst_namer_; }
+  auto global_variables() -> const Map<SemIR::InstId, llvm::GlobalVariable*>& {
+    return global_variables_;
+  }
 
  private:
   // Builds the declaration for the given function, which should then be cached
@@ -73,6 +76,11 @@ class FileContext {
   // the caller.
   auto BuildType(SemIR::InstId inst_id) -> llvm::Type*;
 
+  // Builds the global for the given instruction, which should then be cached by
+  // the caller.
+  auto BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
+      -> llvm::GlobalVariable*;
+
   // State for building the LLVM IR.
   llvm::LLVMContext* llvm_context_;
   std::unique_ptr<llvm::Module> llvm_module_;
@@ -101,6 +109,9 @@ class FileContext {
   // Maps constants to their lowered values.
   // We resize this directly to the (often large) correct size.
   llvm::SmallVector<llvm::Constant*, 0> constants_;
+
+  // Maps global variables to their lowered variant.
+  Map<SemIR::InstId, llvm::GlobalVariable*> global_variables_;
 };
 
 }  // namespace Carbon::Lower

+ 4 - 0
toolchain/lower/function_context.h

@@ -52,6 +52,10 @@ class FunctionContext {
     if (auto result = locals_.Lookup(inst_id)) {
       return result.value();
     }
+
+    if (auto result = file_context_->global_variables().Lookup(inst_id)) {
+      return result.value();
+    }
     return file_context_->GetGlobal(inst_id);
   }
 

+ 30 - 0
toolchain/lower/testdata/global/class_obj.carbon

@@ -0,0 +1,30 @@
+// 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/global/class_obj.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/global/class_obj.carbon
+class A {}
+
+var a: A = {};
+
+// CHECK:STDOUT: ; ModuleID = 'class_obj.carbon'
+// CHECK:STDOUT: source_filename = "class_obj.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @a = internal global {}
+// CHECK:STDOUT: @struct.loc12_14 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @__global_init, ptr null }]
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @__global_init() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 @a, ptr align 1 @struct.loc12_14, i64 0, i1 false)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 44 - 0
toolchain/lower/testdata/global/class_with_fun.carbon

@@ -0,0 +1,44 @@
+// 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/global/class_with_fun.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/global/class_with_fun.carbon
+class A {}
+
+fn ret_a() -> A {
+  return {};
+}
+
+var a: A = {};
+
+// CHECK:STDOUT: ; ModuleID = 'class_with_fun.carbon'
+// CHECK:STDOUT: source_filename = "class_with_fun.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @a = internal global {}
+// CHECK:STDOUT: @struct.loc13_12 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @struct.loc16_14 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @__global_init, ptr null }]
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @ret_a(ptr sret({}) %return) {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %return, ptr align 1 @struct.loc13_12, i64 0, i1 false)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @__global_init() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 @a, ptr align 1 @struct.loc16_14, i64 0, i1 false)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

+ 15 - 0
toolchain/lower/testdata/global/decl.carbon

@@ -0,0 +1,15 @@
+// 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/global/decl.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/global/decl.carbon
+var a: i32;
+
+// CHECK:STDOUT: ; ModuleID = 'decl.carbon'
+// CHECK:STDOUT: source_filename = "decl.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @a = internal global i32

+ 25 - 0
toolchain/lower/testdata/global/simple_init.carbon

@@ -0,0 +1,25 @@
+// 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/global/simple_init.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/global/simple_init.carbon
+var a: i32 = 0;
+
+// CHECK:STDOUT: ; ModuleID = 'simple_init.carbon'
+// CHECK:STDOUT: source_filename = "simple_init.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @a = internal global i32
+// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @__global_init, ptr null }]
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @__global_init() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   store i32 0, ptr @a, align 4
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 0, { 1, 0 }

+ 36 - 0
toolchain/lower/testdata/global/simple_with_fun.carbon

@@ -0,0 +1,36 @@
+// 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/global/simple_with_fun.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/global/simple_with_fun.carbon
+
+fn test_a() -> i32 {
+  return 0;
+}
+
+var a: i32 = test_a();
+
+// CHECK:STDOUT: ; ModuleID = 'simple_with_fun.carbon'
+// CHECK:STDOUT: source_filename = "simple_with_fun.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @a = internal global i32
+// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @__global_init, ptr null }]
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @test_a() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @__global_init() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %test_a.call = call i32 @test_a()
+// CHECK:STDOUT:   store i32 %test_a.call, ptr @a, align 4
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder i32 0, { 1, 0 }

+ 7 - 0
toolchain/sem_ir/file.h

@@ -163,6 +163,10 @@ class File : public Printable<File> {
   auto set_top_inst_block_id(InstBlockId block_id) -> void {
     top_inst_block_id_ = block_id;
   }
+  auto global_ctor_id() const -> FunctionId { return global_ctor_id_; }
+  auto set_global_ctor_id(FunctionId function_id) -> void {
+    global_ctor_id_ = function_id;
+  }
 
   // Returns true if there were errors creating the semantics IR.
   auto has_errors() const -> bool { return has_errors_; }
@@ -242,6 +246,9 @@ class File : public Printable<File> {
   // The top instruction block ID.
   InstBlockId top_inst_block_id_ = InstBlockId::Invalid;
 
+  // The global constructor function id.
+  FunctionId global_ctor_id_ = FunctionId::Invalid;
+
   // Storage for instructions that represent computed global constants, such as
   // types.
   ConstantStore constants_;