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.
This commit is contained in:
Jon Ross-Perkins
2023-05-18 08:01:55 -07:00
committed by GitHub
parent 735502273b
commit e42b48ecc5
12 changed files with 265 additions and 42 deletions
+47 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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: }
@@ -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: }
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}