mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
This works to leverage the capabilities of the hashtable as much as possible, for example using the key context in the value stores. However, there may still be opportunities to refactor more deeply and use the functionality even better. Hopefully this is at least a reasonable start and gets us a clean baseline. On an Arm M1, this is a 15% improvement on my large lexing stress test, but ends up a wash on my x86-64 server. This is a smaller benefit than I expected, and it's because we're using a set-of-IDs and looking up values with a key context for things like identifiers. This pattern has a surprising tradeoff. The new hashtable uses significantly less memory, a 10% peak RSS reduction just from the hashtable change. But indirecting through the vector of values makes growing the hashtable dramatically less cache-friendly: it causes growth to randomly access every key when rehashing. On x86, everything gained by the faster hashtable is lost in even slower growth. And even on Arm, this eats into the benefits. But I have a plan to tweak how identifiers specifically work to avoid most of the growth, and so I suspect this is the right tradeoff on the whole. It gives us significant working set size reduction and we can likely avoid the regressed operation (growth with rehash) in most cases by clever reserving and if necessary by adding a hash caching layer to the table infrastructure. --------- Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
170 lines
6.0 KiB
C++
170 lines
6.0 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/function_context.h"
|
|
|
|
#include "common/vlog.h"
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/sem_ir/file.h"
|
|
|
|
namespace Carbon::Lower {
|
|
|
|
FunctionContext::FunctionContext(FileContext& file_context,
|
|
llvm::Function* function,
|
|
llvm::raw_ostream* vlog_stream)
|
|
: file_context_(&file_context),
|
|
function_(function),
|
|
builder_(file_context.llvm_context(), llvm::ConstantFolder(),
|
|
Inserter(file_context.inst_namer())),
|
|
vlog_stream_(vlog_stream) {}
|
|
|
|
auto FunctionContext::GetBlock(SemIR::InstBlockId block_id)
|
|
-> llvm::BasicBlock* {
|
|
auto result = blocks_.Insert(block_id, [&] {
|
|
llvm::StringRef label_name;
|
|
if (const auto* inst_namer = file_context_->inst_namer()) {
|
|
label_name = inst_namer->GetUnscopedLabelFor(block_id);
|
|
}
|
|
return llvm::BasicBlock::Create(llvm_context(), label_name, function_);
|
|
});
|
|
return result.value();
|
|
}
|
|
|
|
auto FunctionContext::TryToReuseBlock(SemIR::InstBlockId block_id,
|
|
llvm::BasicBlock* block) -> bool {
|
|
if (!blocks_.Insert(block_id, block).is_inserted()) {
|
|
return false;
|
|
}
|
|
if (block == synthetic_block_) {
|
|
synthetic_block_ = nullptr;
|
|
}
|
|
if (const auto* inst_namer = file_context_->inst_namer()) {
|
|
block->setName(inst_namer->GetUnscopedLabelFor(block_id));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto FunctionContext::LowerBlock(SemIR::InstBlockId block_id) -> void {
|
|
for (auto inst_id : sem_ir().inst_blocks().Get(block_id)) {
|
|
LowerInst(inst_id);
|
|
}
|
|
}
|
|
|
|
auto FunctionContext::LowerInst(SemIR::InstId inst_id) -> void {
|
|
// Skip over constants. `FileContext::GetGlobal` lowers them as needed.
|
|
if (sem_ir().constant_values().Get(inst_id).is_constant()) {
|
|
return;
|
|
}
|
|
|
|
auto inst = sem_ir().insts().Get(inst_id);
|
|
CARBON_VLOG() << "Lowering " << inst_id << ": " << inst << "\n";
|
|
builder_.getInserter().SetCurrentInstId(inst_id);
|
|
CARBON_KIND_SWITCH(inst) {
|
|
#define CARBON_SEM_IR_INST_KIND_CONSTANT_ALWAYS(Name)
|
|
#define CARBON_SEM_IR_INST_KIND(Name) \
|
|
case CARBON_KIND(SemIR::Name typed_inst): \
|
|
Handle##Name(*this, inst_id, typed_inst); \
|
|
break;
|
|
#include "toolchain/sem_ir/inst_kind.def"
|
|
|
|
default:
|
|
CARBON_FATAL() << "Missing constant value for constant instruction "
|
|
<< inst;
|
|
}
|
|
builder_.getInserter().SetCurrentInstId(SemIR::InstId::Invalid);
|
|
}
|
|
|
|
auto FunctionContext::GetBlockArg(SemIR::InstBlockId block_id,
|
|
SemIR::TypeId type_id) -> llvm::PHINode* {
|
|
llvm::BasicBlock* block = GetBlock(block_id);
|
|
|
|
// Find the existing phi, if any.
|
|
auto phis = block->phis();
|
|
if (!phis.empty()) {
|
|
CARBON_CHECK(std::next(phis.begin()) == phis.end())
|
|
<< "Expected at most one phi, found "
|
|
<< std::distance(phis.begin(), phis.end());
|
|
return &*phis.begin();
|
|
}
|
|
|
|
// The number of predecessor slots to reserve.
|
|
static constexpr unsigned NumReservedPredecessors = 2;
|
|
auto* phi = llvm::PHINode::Create(GetType(type_id), NumReservedPredecessors);
|
|
phi->insertInto(block, block->begin());
|
|
return phi;
|
|
}
|
|
|
|
auto FunctionContext::MakeSyntheticBlock() -> llvm::BasicBlock* {
|
|
synthetic_block_ = llvm::BasicBlock::Create(llvm_context(), "", function_);
|
|
return synthetic_block_;
|
|
}
|
|
|
|
auto FunctionContext::FinishInit(SemIR::TypeId type_id, SemIR::InstId dest_id,
|
|
SemIR::InstId source_id) -> void {
|
|
switch (SemIR::GetInitRepr(sem_ir(), type_id).kind) {
|
|
case SemIR::InitRepr::None:
|
|
break;
|
|
case SemIR::InitRepr::InPlace:
|
|
if (sem_ir().constant_values().Get(source_id).is_constant()) {
|
|
// When initializing from a constant, emission of the source doesn't
|
|
// initialize the destination. Copy the constant value instead.
|
|
CopyValue(type_id, source_id, dest_id);
|
|
}
|
|
break;
|
|
case SemIR::InitRepr::ByCopy:
|
|
CopyValue(type_id, source_id, dest_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto FunctionContext::CopyValue(SemIR::TypeId type_id, SemIR::InstId source_id,
|
|
SemIR::InstId dest_id) -> void {
|
|
switch (auto rep = SemIR::GetValueRepr(sem_ir(), type_id); rep.kind) {
|
|
case SemIR::ValueRepr::Unknown:
|
|
CARBON_FATAL() << "Attempt to copy incomplete type";
|
|
case SemIR::ValueRepr::None:
|
|
break;
|
|
case SemIR::ValueRepr::Copy:
|
|
builder().CreateStore(GetValue(source_id), GetValue(dest_id));
|
|
break;
|
|
case SemIR::ValueRepr::Pointer:
|
|
CopyObject(type_id, source_id, dest_id);
|
|
break;
|
|
case SemIR::ValueRepr::Custom:
|
|
CARBON_FATAL() << "TODO: Add support for CopyValue with custom value rep";
|
|
}
|
|
}
|
|
|
|
auto FunctionContext::CopyObject(SemIR::TypeId type_id, SemIR::InstId source_id,
|
|
SemIR::InstId dest_id) -> void {
|
|
const auto& layout = llvm_module().getDataLayout();
|
|
auto* type = GetType(type_id);
|
|
// TODO: Compute known alignment of the source and destination, which may
|
|
// be greater than the alignment computed by LLVM.
|
|
auto align = layout.getABITypeAlign(type);
|
|
|
|
// TODO: Attach !tbaa.struct metadata indicating which portions of the
|
|
// type we actually need to copy and which are padding.
|
|
builder().CreateMemCpy(GetValue(dest_id), align, GetValue(source_id), align,
|
|
layout.getTypeAllocSize(type));
|
|
}
|
|
|
|
auto FunctionContext::Inserter::InsertHelper(
|
|
llvm::Instruction* inst, const llvm::Twine& name,
|
|
llvm::BasicBlock::iterator insert_pt) const -> void {
|
|
llvm::StringRef base_name;
|
|
llvm::StringRef separator;
|
|
if (inst_namer_ && !inst->getType()->isVoidTy()) {
|
|
base_name = inst_namer_->GetUnscopedNameFor(inst_id_);
|
|
}
|
|
if (!base_name.empty() && !name.isTriviallyEmpty()) {
|
|
separator = ".";
|
|
}
|
|
|
|
IRBuilderDefaultInserter::InsertHelper(inst, base_name + separator + name,
|
|
insert_pt);
|
|
}
|
|
|
|
} // namespace Carbon::Lower
|