Files
carbon-lang/toolchain/lower/file_context.cpp
T
Richard SmithandChandler Carruth e4caf7d604 Compute and cache the value representation of a type when it becomes complete. (#3271)
Using the computed value representation, fix lowering of struct and
tuple values to use the value representation rather than the object
representation. Fixes an issue found in the review of #3257.

This currently causes us to compute value representations of all types
as they are created, which generates substantially more SemIR to
represent types. We can get some of that back by deferring computation
of the value representation until the type is required to be complete,
but some of the additional cost here will persist with this approach.

I also considered making the computation of the value representation
type be something that lives entirely within the lowering phase, but I
think that's not the right approach in the longer term, because the
value representation will be semantically visible and relevant once we
start allowing it to be customized.

We should consider moving the nodes that exist to compute canonical
non-local types, including value representations, out into a separate
global block. That will clean up the SemIR representation substantially,
and make the SemIR produced for a function not depend on which types we
happen to have encountered beforehand. But that's not being done in this
PR.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2023-10-13 22:27:02 +00:00

268 lines
10 KiB
C++

// 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 "toolchain/lower/file_context.h"
#include "common/vlog.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "toolchain/lower/function_context.h"
#include "toolchain/sem_ir/entry_point.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/node.h"
#include "toolchain/sem_ir/node_kind.h"
namespace Carbon::Lower {
FileContext::FileContext(llvm::LLVMContext& llvm_context,
llvm::StringRef module_name,
const SemIR::File& semantics_ir,
llvm::raw_ostream* vlog_stream)
: llvm_context_(&llvm_context),
llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
semantics_ir_(&semantics_ir),
vlog_stream_(vlog_stream) {
CARBON_CHECK(!semantics_ir.has_errors())
<< "Generating LLVM IR from invalid SemIR::File is unsupported.";
}
// TODO: Move this to lower.cpp.
auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
CARBON_CHECK(llvm_module_) << "Run can only be called once.";
// Lower types.
auto types = semantics_ir_->types();
types_.resize_for_overwrite(types.size());
for (auto [i, type] : llvm::enumerate(types)) {
types_[i] = BuildType(type.node_id);
}
// Lower function declarations.
functions_.resize_for_overwrite(semantics_ir_->functions_size());
for (auto i : llvm::seq(semantics_ir_->functions_size())) {
functions_[i] = BuildFunctionDeclaration(SemIR::FunctionId(i));
}
// TODO: Lower global variable declarations.
// Lower function definitions.
for (auto i : llvm::seq(semantics_ir_->functions_size())) {
BuildFunctionDefinition(SemIR::FunctionId(i));
}
// TODO: Lower global variable initializers.
return std::move(llvm_module_);
}
auto FileContext::BuildFunctionDeclaration(SemIR::FunctionId function_id)
-> llvm::Function* {
const auto& function = semantics_ir().GetFunction(function_id);
const bool has_return_slot = function.return_slot_id.is_valid();
auto param_refs = semantics_ir().GetNodeBlock(function.param_refs_id);
SemIR::InitializingRepresentation return_rep =
function.return_type_id.is_valid()
? SemIR::GetInitializingRepresentation(semantics_ir(),
function.return_type_id)
: SemIR::InitializingRepresentation{
.kind = SemIR::InitializingRepresentation::None};
CARBON_CHECK(return_rep.has_return_slot() == has_return_slot);
llvm::SmallVector<llvm::Type*> param_types;
// TODO: Consider either storing `param_node_ids` somewhere so that we can
// reuse it from `BuildFunctionDefinition` and when building calls, or factor
// out a mechanism to compute the mapping between parameters and arguments on
// demand.
llvm::SmallVector<SemIR::NodeId> param_node_ids;
param_types.reserve(has_return_slot + param_refs.size());
param_node_ids.reserve(has_return_slot + param_refs.size());
if (has_return_slot) {
param_types.push_back(GetType(function.return_type_id)->getPointerTo());
param_node_ids.push_back(function.return_slot_id);
}
for (auto param_ref_id : param_refs) {
auto param_type_id = semantics_ir().GetNode(param_ref_id).type_id();
switch (auto value_rep =
SemIR::GetValueRepresentation(semantics_ir(), param_type_id);
value_rep.kind) {
case SemIR::ValueRepresentation::Unknown:
CARBON_FATAL()
<< "Incomplete parameter type lowering function declaration";
case SemIR::ValueRepresentation::None:
break;
case SemIR::ValueRepresentation::Copy:
case SemIR::ValueRepresentation::Custom:
case SemIR::ValueRepresentation::Pointer:
param_types.push_back(GetType(value_rep.type_id));
param_node_ids.push_back(param_ref_id);
break;
}
}
// If the initializing representation doesn't produce a value, set the return
// type to void.
llvm::Type* return_type =
return_rep.kind == SemIR::InitializingRepresentation::ByCopy
? GetType(function.return_type_id)
: llvm::Type::getVoidTy(llvm_context());
std::string mangled_name;
if (SemIR::IsEntryPoint(semantics_ir(), function_id)) {
// TODO: Add an implicit `return 0` if `Run` doesn't return `i32`.
mangled_name = "main";
} else {
// TODO: Decide on a name mangling scheme.
mangled_name = semantics_ir().GetString(function.name_id);
}
llvm::FunctionType* function_type =
llvm::FunctionType::get(return_type, param_types, /*isVarArg=*/false);
auto* llvm_function =
llvm::Function::Create(function_type, llvm::Function::ExternalLinkage,
mangled_name, llvm_module());
// Set up parameters and the return slot.
for (auto [node_id, arg] :
llvm::zip_equal(param_node_ids, llvm_function->args())) {
if (node_id == function.return_slot_id) {
arg.setName("return");
arg.addAttr(llvm::Attribute::getWithStructRetType(
llvm_context(), GetType(function.return_type_id)));
} else {
arg.setName(semantics_ir().GetString(
semantics_ir().GetNodeAs<SemIR::Parameter>(node_id).name_id));
}
}
return llvm_function;
}
auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id)
-> void {
const auto& function = semantics_ir().GetFunction(function_id);
const auto& body_block_ids = function.body_block_ids;
if (body_block_ids.empty()) {
// Function is probably defined in another file; not an error.
return;
}
llvm::Function* llvm_function = GetFunction(function_id);
FunctionContext function_lowering(*this, llvm_function, vlog_stream_);
const bool has_return_slot = function.return_slot_id.is_valid();
// Add parameters to locals.
// TODO: This duplicates the mapping between semantics nodes and LLVM
// function parameters that was already computed in BuildFunctionDeclaration.
// We should only do that once.
auto param_refs = semantics_ir().GetNodeBlock(function.param_refs_id);
int param_index = 0;
if (has_return_slot) {
function_lowering.SetLocal(function.return_slot_id,
llvm_function->getArg(param_index));
++param_index;
}
for (auto param_ref_id : param_refs) {
auto param_type_id = semantics_ir().GetNode(param_ref_id).type_id();
if (SemIR::GetValueRepresentation(semantics_ir(), param_type_id).kind ==
SemIR::ValueRepresentation::None) {
function_lowering.SetLocal(
param_ref_id, llvm::PoisonValue::get(GetType(param_type_id)));
} else {
function_lowering.SetLocal(param_ref_id,
llvm_function->getArg(param_index));
++param_index;
}
}
// Lower all blocks.
for (auto block_id : body_block_ids) {
CARBON_VLOG() << "Lowering " << block_id << "\n";
auto* llvm_block = function_lowering.GetBlock(block_id);
// Keep the LLVM blocks in lexical order.
llvm_block->moveBefore(llvm_function->end());
function_lowering.builder().SetInsertPoint(llvm_block);
function_lowering.LowerBlock(block_id);
}
// LLVM requires that the entry block has no predecessors.
auto* entry_block = &llvm_function->getEntryBlock();
if (entry_block->hasNPredecessorsOrMore(1)) {
auto* new_entry_block = llvm::BasicBlock::Create(
llvm_context(), "entry", llvm_function, entry_block);
llvm::BranchInst::Create(entry_block, new_entry_block);
}
}
auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* {
switch (node_id.index) {
case SemIR::BuiltinKind::FloatingPointType.AsInt():
// TODO: Handle different sizes.
return llvm::Type::getDoubleTy(*llvm_context_);
case SemIR::BuiltinKind::IntegerType.AsInt():
// TODO: Handle different sizes.
return llvm::Type::getInt32Ty(*llvm_context_);
case SemIR::BuiltinKind::BoolType.AsInt():
// TODO: We may want to have different representations for `bool` storage
// (`i8`) versus for `bool` values (`i1`).
return llvm::Type::getInt1Ty(*llvm_context_);
case SemIR::BuiltinKind::FunctionType.AsInt():
case SemIR::BuiltinKind::NamespaceType.AsInt():
// Return an empty struct as a placeholder.
return llvm::StructType::get(*llvm_context_);
default:
// Handled below.
break;
}
auto node = semantics_ir_->GetNode(node_id);
switch (node.kind()) {
case SemIR::ArrayType::Kind: {
auto array_type = node.As<SemIR::ArrayType>();
return llvm::ArrayType::get(
GetType(array_type.element_type_id),
semantics_ir_->GetArrayBoundValue(array_type.bound_id));
}
case SemIR::ConstType::Kind:
return GetType(node.As<SemIR::ConstType>().inner_id);
case SemIR::PointerType::Kind:
return llvm::PointerType::get(*llvm_context_, /*AddressSpace=*/0);
case SemIR::StructType::Kind: {
auto fields =
semantics_ir_->GetNodeBlock(node.As<SemIR::StructType>().fields_id);
llvm::SmallVector<llvm::Type*> subtypes;
subtypes.reserve(fields.size());
for (auto field_id : fields) {
auto field = semantics_ir_->GetNodeAs<SemIR::StructTypeField>(field_id);
// TODO: Handle recursive types. The restriction for builtins prevents
// recursion while still letting them cache.
CARBON_CHECK(field.type_id.index < SemIR::BuiltinKind::ValidCount)
<< field.type_id;
subtypes.push_back(GetType(field.type_id));
}
return llvm::StructType::get(*llvm_context_, subtypes);
}
case SemIR::TupleType::Kind: {
// TODO: Investigate special-casing handling of empty tuples 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.
auto elements =
semantics_ir_->GetTypeBlock(node.As<SemIR::TupleType>().elements_id);
llvm::SmallVector<llvm::Type*> subtypes;
subtypes.reserve(elements.size());
for (auto element_id : elements) {
subtypes.push_back(GetType(element_id));
}
return llvm::StructType::get(*llvm_context_, subtypes);
}
default: {
CARBON_FATAL() << "Cannot use node as type: " << node_id;
}
}
}
} // namespace Carbon::Lower