Parcourir la source

Start supporting struct type literals in lowering. (#2822)

This currently handles most of struct types and member access, but not values. The value issue is, I think, an underlying semantic IR approach (changed in #2824). I'd still like to get this in in order to ensure I'm creating IR at least reasonably well, but I want to be clear this is expected to be incomplete and is split out mainly to try to keep PRs in reasonable units.
Jon Ross-Perkins il y a 3 ans
Parent
commit
e42b48ecc5

+ 47 - 6
toolchain/lowering/lowering_context.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/lowering/lowering_context.h"
 
 #include "toolchain/semantics/semantics_ir.h"
+#include "toolchain/semantics/semantics_node_kind.h"
 
 namespace Carbon {
 
@@ -47,19 +48,59 @@ auto LoweringContext::LowerBlock(SemanticsNodeBlockId block_id) -> void {
   }
 }
 
-auto LoweringContext::LowerNodeToType(SemanticsNodeId node_id) -> llvm::Type* {
-  CARBON_CHECK(node_id.is_valid());
+auto LoweringContext::BuildLoweredNodeAsType(SemanticsNodeId node_id)
+    -> llvm::Type* {
   switch (node_id.index) {
+    case SemanticsBuiltinKind::EmptyStructType.AsInt():
     case SemanticsBuiltinKind::EmptyTuple.AsInt():
-      // TODO: Should probably switch this to an actual empty tuple in the
-      // future, but it's implemented as void for now.
-      return builder_.getVoidTy();
+    case SemanticsBuiltinKind::EmptyTupleType.AsInt():
+      // Represent empty types as empty structs.
+      // TODO: Investigate special-casing handling of these so that they can be
+      // collectively replaced with LLVM's void, particularly around function
+      // returns. LLVM doesn't allow declaring variables with a void type, so
+      // that may require significant special casing.
+      // TODO: Work to remove EmptyTuple here.
+      return llvm::StructType::create(*llvm_context_,
+                                      llvm::ArrayRef<llvm::Type*>());
+    case SemanticsBuiltinKind::FloatingPointType.AsInt():
+      // TODO: Handle different sizes.
+      return builder_.getDoubleTy();
     case SemanticsBuiltinKind::IntegerType.AsInt():
       // TODO: Handle different sizes.
       return builder_.getInt32Ty();
-    default:
+  }
+
+  auto node = semantics_ir_->GetNode(node_id);
+  switch (node.kind()) {
+    case SemanticsNodeKind::StructType: {
+      auto refs = semantics_ir_->GetNodeBlock(node.GetAsStructType().second);
+      llvm::SmallVector<llvm::Type*> subtypes;
+      subtypes.reserve(refs.size());
+      for (auto ref_id : refs) {
+        auto type_id = semantics_ir_->GetNode(ref_id).type_id();
+        // TODO: Handle recursive types. The restriction for builtins prevents
+        // recursion while still letting them cache.
+        CARBON_CHECK(type_id.index < SemanticsBuiltinKind::ValidCount)
+            << type_id;
+        subtypes.push_back(GetLoweredNodeAsType(type_id));
+      }
+      return llvm::StructType::create(*llvm_context_, subtypes);
+    }
+    default: {
       CARBON_FATAL() << "Cannot use node as type: " << node_id;
+    }
   }
 }
 
+auto LoweringContext::GetLoweredNodeAsType(SemanticsNodeId node_id)
+    -> llvm::Type* {
+  if (lowered_nodes_[node_id.index]) {
+    return lowered_nodes_[node_id.index].get<llvm::Type*>();
+  }
+
+  auto* type = BuildLoweredNodeAsType(node_id);
+  lowered_nodes_[node_id.index] = type;
+  return type;
+}
+
 }  // namespace Carbon

+ 25 - 7
toolchain/lowering/lowering_context.h

@@ -24,8 +24,25 @@ class LoweringContext {
   // the main execution loop.
   auto Run() -> std::unique_ptr<llvm::Module>;
 
-  // Returns a type for the given node.
-  auto LowerNodeToType(SemanticsNodeId node_id) -> llvm::Type*;
+  auto HasLoweredNode(SemanticsNodeId node_id) -> bool {
+    return !lowered_nodes_[node_id.index].isNull();
+  }
+
+  // Returns a type for the given node. May construct and cache the type
+  // if it hasn't yet been built.
+  auto GetLoweredNodeAsType(SemanticsNodeId node_id) -> llvm::Type*;
+
+  // Returns a value for the given node.
+  auto GetLoweredNodeAsValue(SemanticsNodeId node_id) -> llvm::Value* {
+    CARBON_CHECK(lowered_nodes_[node_id.index].is<llvm::Value*>())
+        << node_id << ": isNull == " << lowered_nodes_[node_id.index].isNull();
+    return lowered_nodes_[node_id.index].get<llvm::Value*>();
+  }
+  // Sets the value for the given node.
+  auto SetLoweredNodeAsValue(SemanticsNodeId node_id, llvm::Value* value) {
+    CARBON_CHECK(lowered_nodes_[node_id.index].isNull()) << node_id;
+    lowered_nodes_[node_id.index] = value;
+  }
 
   auto llvm_context() -> llvm::LLVMContext& { return *llvm_context_; }
   auto llvm_module() -> llvm::Module& { return *llvm_module_; }
@@ -35,14 +52,15 @@ class LoweringContext {
       std::pair<llvm::BasicBlock*, SemanticsNodeBlockId>>& {
     return todo_blocks_;
   }
-  auto lowered_nodes() -> llvm::SmallVector<llvm::Value*>& {
-    return lowered_nodes_;
-  }
 
  private:
   // Runs lowering for a block.
   auto LowerBlock(SemanticsNodeBlockId block_id) -> void;
 
+  // Builds the type for the given node, which should then be cached by the
+  // caller.
+  auto BuildLoweredNodeAsType(SemanticsNodeId node_id) -> llvm::Type*;
+
   // State for building the LLVM IR.
   llvm::LLVMContext* llvm_context_;
   std::unique_ptr<llvm::Module> llvm_module_;
@@ -58,12 +76,12 @@ class LoweringContext {
   // Maps nodes in SemanticsIR to a lowered value. This will have one entry per
   // node, and will be non-null when lowered. It's expected to be sparse during
   // execution because while expressions will have entries, statements won't.
-  // TODO: This will probably become a PointerUnion of Value and Type.
   // TODO: Long-term, we should examine the practical trade-offs of making this
   // a map; a map may end up lower memory consumption, but a vector offers cache
   // efficiency and better performance. As a consequence, the right choice is
   // unclear.
-  llvm::SmallVector<llvm::Value*> lowered_nodes_;
+  llvm::SmallVector<llvm::PointerUnion<llvm::Type*, llvm::Value*>>
+      lowered_nodes_;
 };
 
 // Declare handlers for each SemanticsIR node.

+ 55 - 25
toolchain/lowering/lowering_handle.cpp

@@ -18,10 +18,17 @@ auto LoweringHandleCrossReference(LoweringContext& /*context*/,
   CARBON_FATAL() << "TODO: Add support: " << node;
 }
 
-auto LoweringHandleAssign(LoweringContext& /*context*/,
-                          SemanticsNodeId /*node_id*/, SemanticsNode node)
-    -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+auto LoweringHandleAssign(LoweringContext& context, SemanticsNodeId /*node_id*/,
+                          SemanticsNode node) -> void {
+  auto [storage_id, value_id] = node.GetAsAssign();
+  if (value_id == SemanticsNodeId::BuiltinEmptyStruct ||
+      value_id == SemanticsNodeId::BuiltinEmptyTuple) {
+    // Elide the 0-length store; these have no value assigned and it should have
+    // no effect.
+    return;
+  }
+  context.builder().CreateStore(context.GetLoweredNodeAsValue(value_id),
+                                context.GetLoweredNodeAsValue(storage_id));
 }
 
 auto LoweringHandleBinaryOperatorAdd(LoweringContext& /*context*/,
@@ -31,9 +38,9 @@ auto LoweringHandleBinaryOperatorAdd(LoweringContext& /*context*/,
 }
 
 auto LoweringHandleBindName(LoweringContext& /*context*/,
-                            SemanticsNodeId /*node_id*/, SemanticsNode node)
+                            SemanticsNodeId /*node_id*/, SemanticsNode /*node*/)
     -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  // Probably need to do something here, but not necessary for now.
 }
 
 auto LoweringHandleBuiltin(LoweringContext& /*context*/,
@@ -65,11 +72,11 @@ auto LoweringHandleFunctionDeclaration(LoweringContext& context,
   llvm::SmallVector<llvm::Type*> args;
   args.resize_for_overwrite(param_refs.size());
   for (int i = 0; i < static_cast<int>(param_refs.size()); ++i) {
-    args[i] = context.LowerNodeToType(
+    args[i] = context.GetLoweredNodeAsType(
         context.semantics_ir().GetNode(param_refs[i]).type_id());
   }
 
-  llvm::Type* return_type = context.LowerNodeToType(
+  llvm::Type* return_type = context.GetLoweredNodeAsType(
       callable.return_type_id.is_valid() ? callable.return_type_id
                                          : SemanticsNodeId::BuiltinEmptyTuple);
   llvm::FunctionType* function_type =
@@ -108,9 +115,8 @@ auto LoweringHandleIntegerLiteral(LoweringContext& context,
     -> void {
   SemanticsIntegerLiteralId int_id = node.GetAsIntegerLiteral();
   llvm::APInt i = context.semantics_ir().GetIntegerLiteral(int_id);
-  llvm::Value* v = llvm::ConstantInt::get(context.builder().getInt32Ty(),
-                                          i.getLimitedValue());
-  context.lowered_nodes()[node_id.index] = v;
+  llvm::Value* v = context.builder().getInt32(i.getLimitedValue());
+  context.SetLoweredNodeAsValue(node_id, v);
 }
 
 auto LoweringHandleRealLiteral(LoweringContext& /*context*/,
@@ -128,7 +134,7 @@ auto LoweringHandleReturnExpression(LoweringContext& context,
                                     SemanticsNodeId /*node_id*/,
                                     SemanticsNode node) -> void {
   SemanticsNodeId expr_id = node.GetAsReturnExpression();
-  context.builder().CreateRet(context.lowered_nodes()[expr_id.index]);
+  context.builder().CreateRet(context.GetLoweredNodeAsValue(expr_id));
 }
 
 auto LoweringHandleStringLiteral(LoweringContext& /*context*/,
@@ -137,16 +143,22 @@ auto LoweringHandleStringLiteral(LoweringContext& /*context*/,
   CARBON_FATAL() << "TODO: Add support: " << node;
 }
 
-auto LoweringHandleStructMemberAccess(LoweringContext& /*context*/,
-                                      SemanticsNodeId /*node_id*/,
+auto LoweringHandleStructMemberAccess(LoweringContext& context,
+                                      SemanticsNodeId node_id,
                                       SemanticsNode node) -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  auto [struct_id, member_index] = node.GetAsStructMemberAccess();
+  auto* struct_type = context.GetLoweredNodeAsType(
+      context.semantics_ir().GetNode(struct_id).type_id());
+  auto* gep = context.builder().CreateStructGEP(
+      struct_type, context.GetLoweredNodeAsValue(struct_id),
+      member_index.index);
+  context.SetLoweredNodeAsValue(node_id, gep);
 }
 
 auto LoweringHandleStructType(LoweringContext& /*context*/,
-                              SemanticsNodeId /*node_id*/, SemanticsNode node)
-    -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+                              SemanticsNodeId /*node_id*/,
+                              SemanticsNode /*node*/) -> void {
+  // No action to take.
 }
 
 auto LoweringHandleStructTypeField(LoweringContext& /*context*/,
@@ -155,10 +167,22 @@ auto LoweringHandleStructTypeField(LoweringContext& /*context*/,
   CARBON_FATAL() << "TODO: Add support: " << node;
 }
 
-auto LoweringHandleStructValue(LoweringContext& /*context*/,
-                               SemanticsNodeId /*node_id*/, SemanticsNode node)
+auto LoweringHandleStructValue(LoweringContext& context,
+                               SemanticsNodeId node_id, SemanticsNode node)
     -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+  auto* type = context.GetLoweredNodeAsType(node.type_id());
+  auto* alloca = context.builder().CreateAlloca(type);
+  context.SetLoweredNodeAsValue(node_id, alloca);
+
+  // TODO: Figure out changes to flow so that values are calculated for store.
+  // Right now, the struct value IR is unevaluated.
+  // auto refs =
+  //     context.semantics_ir().GetNodeBlock(node.GetAsStructValue().second);
+  // for (int i = 0; i < static_cast<int>(refs.size()); ++i) {
+  //   auto* gep = context.builder().CreateStructGEP(type, alloca, i);
+  //   context.builder().CreateStore(context.GetLoweredNodeAsValue(refs[i]),
+  //                                 gep);
+  // }
 }
 
 auto LoweringHandleStubReference(LoweringContext& /*context*/,
@@ -167,10 +191,16 @@ auto LoweringHandleStubReference(LoweringContext& /*context*/,
   CARBON_FATAL() << "TODO: Add support: " << node;
 }
 
-auto LoweringHandleVarStorage(LoweringContext& /*context*/,
-                              SemanticsNodeId /*node_id*/, SemanticsNode node)
-    -> void {
-  CARBON_FATAL() << "TODO: Add support: " << node;
+auto LoweringHandleVarStorage(LoweringContext& context, SemanticsNodeId node_id,
+                              SemanticsNode node) -> void {
+  // TODO: This doesn't handle globals. Also, LLVM requires globals to have a
+  // name. Do we want to generate a name, which would need to be consistent
+  // across translation units, or use the given name, which requires either
+  // looking ahead for BindName or restructuring semantics, either of which
+  // affects the destructuring due to the difference in storage?
+  auto* alloca = context.builder().CreateAlloca(
+      context.GetLoweredNodeAsType(node.type_id()));
+  context.SetLoweredNodeAsValue(node_id, alloca);
 }
 
 }  // namespace Carbon

+ 3 - 1
toolchain/lowering/testdata/function/definition/params_one.carbon

@@ -6,7 +6,9 @@
 // CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/function/definition/params_one.carbon'
 // CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/function/definition/params_one.carbon"
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @Foo(i32 %a) {
+// CHECK:STDOUT: %0 = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %0 @Foo(i32 %a) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 

+ 3 - 1
toolchain/lowering/testdata/function/definition/params_two.carbon

@@ -6,7 +6,9 @@
 // CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/function/definition/params_two.carbon'
 // CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/function/definition/params_two.carbon"
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @Foo(i32 %a, i32 %b) {
+// CHECK:STDOUT: %0 = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %0 @Foo(i32 %a, i32 %b) {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 

+ 3 - 1
toolchain/lowering/testdata/function/definition/params_zero.carbon

@@ -6,7 +6,9 @@
 // CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/function/definition/params_zero.carbon'
 // CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/function/definition/params_zero.carbon"
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @Foo() {
+// CHECK:STDOUT: %0 = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %0 @Foo() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: }
 

+ 3 - 1
toolchain/lowering/testdata/return/no_value.carbon

@@ -6,7 +6,9 @@
 // CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/return/no_value.carbon'
 // CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/return/no_value.carbon"
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @Main() {
+// CHECK:STDOUT: %0 = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %0 @Main() {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }

+ 23 - 0
toolchain/lowering/testdata/struct/empty.carbon

@@ -0,0 +1,23 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/struct/empty.carbon'
+// CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/struct/empty.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %0 = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Run() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = alloca %0, align 8
+// CHECK:STDOUT:   %1 = alloca %0, align 8
+// CHECK:STDOUT:   store ptr %0, ptr %1, align 8
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+
+fn Run() -> i32 {
+  var x: {} = {};
+  var y: {} = x;
+  return 0;
+}

+ 30 - 0
toolchain/lowering/testdata/struct/member_access.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
+// CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/struct/member_access.carbon'
+// CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/struct/member_access.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %0 = type { double, i32 }
+// CHECK:STDOUT: %1 = type { double, i32 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Run() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = alloca %0, align 8
+// CHECK:STDOUT:   %1 = alloca %1, align 8
+// CHECK:STDOUT:   store ptr %1, ptr %0, align 8
+// CHECK:STDOUT:   %2 = alloca i32, align 4
+// CHECK:STDOUT:   %3 = getelementptr inbounds %0, ptr %0, i32 0, i32 1
+// CHECK:STDOUT:   store ptr %3, ptr %2, align 8
+// CHECK:STDOUT:   %4 = alloca i32, align 4
+// CHECK:STDOUT:   store ptr %2, ptr %4, align 8
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+
+fn Run() -> i32 {
+  var x: {.a: f64, .b: i32} = {.a = 0.0, .b = 1};
+  var y: i32 = x.b;
+  var z: i32 = y;
+  return 0;
+}

+ 27 - 0
toolchain/lowering/testdata/struct/one_entry.carbon

@@ -0,0 +1,27 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/struct/one_entry.carbon'
+// CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/struct/one_entry.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %0 = type { i32 }
+// CHECK:STDOUT: %1 = type { i32 }
+// CHECK:STDOUT: %2 = type { i32 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Run() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = alloca %0, align 8
+// CHECK:STDOUT:   %1 = alloca %1, align 8
+// CHECK:STDOUT:   store ptr %1, ptr %0, align 8
+// CHECK:STDOUT:   %2 = alloca %2, align 8
+// CHECK:STDOUT:   store ptr %0, ptr %2, align 8
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+
+fn Run() -> i32 {
+  var x: {.a: i32} = {.a = 4};
+  var y: {.a: i32} = x;
+  return 0;
+}

+ 27 - 0
toolchain/lowering/testdata/struct/two_entries.carbon

@@ -0,0 +1,27 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/struct/two_entries.carbon'
+// CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/struct/two_entries.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %0 = type { i32, i32 }
+// CHECK:STDOUT: %1 = type { i32, i32 }
+// CHECK:STDOUT: %2 = type { i32, i32 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Run() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = alloca %0, align 8
+// CHECK:STDOUT:   %1 = alloca %1, align 8
+// CHECK:STDOUT:   store ptr %1, ptr %0, align 8
+// CHECK:STDOUT:   %2 = alloca %2, align 8
+// CHECK:STDOUT:   store ptr %0, ptr %2, align 8
+// CHECK:STDOUT:   ret i32 0
+// CHECK:STDOUT: }
+
+fn Run() -> i32 {
+  var x: {.a: i32, .b: i32} = {.a = 1, .b = 2};
+  var y: {.a: i32, .b: i32} = x;
+  return 0;
+}

+ 19 - 0
toolchain/lowering/testdata/var/local.carbon

@@ -0,0 +1,19 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'toolchain/lowering/testdata/var/local.carbon'
+// CHECK:STDOUT: source_filename = "toolchain/lowering/testdata/var/local.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Run() {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %0 = alloca i32, align 4
+// CHECK:STDOUT:   store i32 1, ptr %0, align 4
+// CHECK:STDOUT:   ret ptr %0
+// CHECK:STDOUT: }
+
+fn Run() -> i32 {
+  var x: i32 = 1;
+  return x;
+}