diff --git a/toolchain/lowering/lowering_context.cpp b/toolchain/lowering/lowering_context.cpp index 9cf3764225f3..f0c92eb037aa 100644 --- a/toolchain/lowering/lowering_context.cpp +++ b/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()); + 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 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(); + } + + auto* type = BuildLoweredNodeAsType(node_id); + lowered_nodes_[node_id.index] = type; + return type; +} + } // namespace Carbon diff --git a/toolchain/lowering/lowering_context.h b/toolchain/lowering/lowering_context.h index 3589b25b48c8..26b69c075a26 100644 --- a/toolchain/lowering/lowering_context.h +++ b/toolchain/lowering/lowering_context.h @@ -24,8 +24,25 @@ class LoweringContext { // the main execution loop. auto Run() -> std::unique_ptr; - // 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()) + << node_id << ": isNull == " << lowered_nodes_[node_id.index].isNull(); + return lowered_nodes_[node_id.index].get(); + } + // 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>& { return todo_blocks_; } - auto lowered_nodes() -> llvm::SmallVector& { - 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_; @@ -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 lowered_nodes_; + llvm::SmallVector> + lowered_nodes_; }; // Declare handlers for each SemanticsIR node. diff --git a/toolchain/lowering/lowering_handle.cpp b/toolchain/lowering/lowering_handle.cpp index ff1626e0e9a6..bdc06ffe0e87 100644 --- a/toolchain/lowering/lowering_handle.cpp +++ b/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 args; args.resize_for_overwrite(param_refs.size()); for (int i = 0; i < static_cast(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(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 diff --git a/toolchain/lowering/testdata/function/definition/params_one.carbon b/toolchain/lowering/testdata/function/definition/params_one.carbon index 74848b730295..a3ed4233f9e0 100644 --- a/toolchain/lowering/testdata/function/definition/params_one.carbon +++ b/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: } diff --git a/toolchain/lowering/testdata/function/definition/params_two.carbon b/toolchain/lowering/testdata/function/definition/params_two.carbon index 9b137b9c8dcf..f284c112e7da 100644 --- a/toolchain/lowering/testdata/function/definition/params_two.carbon +++ b/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: } diff --git a/toolchain/lowering/testdata/function/definition/params_zero.carbon b/toolchain/lowering/testdata/function/definition/params_zero.carbon index 938e08f2e037..dbc40f376e2a 100644 --- a/toolchain/lowering/testdata/function/definition/params_zero.carbon +++ b/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: } diff --git a/toolchain/lowering/testdata/return/no_value.carbon b/toolchain/lowering/testdata/return/no_value.carbon index 8070351b945f..e5be729f8565 100644 --- a/toolchain/lowering/testdata/return/no_value.carbon +++ b/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: } diff --git a/toolchain/lowering/testdata/struct/empty.carbon b/toolchain/lowering/testdata/struct/empty.carbon new file mode 100644 index 000000000000..303234ecd4f9 --- /dev/null +++ b/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; +} diff --git a/toolchain/lowering/testdata/struct/member_access.carbon b/toolchain/lowering/testdata/struct/member_access.carbon new file mode 100644 index 000000000000..855e6199f4a6 --- /dev/null +++ b/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; +} diff --git a/toolchain/lowering/testdata/struct/one_entry.carbon b/toolchain/lowering/testdata/struct/one_entry.carbon new file mode 100644 index 000000000000..85e57b14a46a --- /dev/null +++ b/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; +} diff --git a/toolchain/lowering/testdata/struct/two_entries.carbon b/toolchain/lowering/testdata/struct/two_entries.carbon new file mode 100644 index 000000000000..45b1f2664a33 --- /dev/null +++ b/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; +} diff --git a/toolchain/lowering/testdata/var/local.carbon b/toolchain/lowering/testdata/var/local.carbon new file mode 100644 index 000000000000..f9a4767da0f7 --- /dev/null +++ b/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; +}