Files
carbon-lang/toolchain/lower/handle.cpp
T
Richard Smith d50576ca16 Handle references to global lets and enclosing locals. (#8061)
When building an expression referencing the result of name lookup, if
the lookup result is non-constant then check we can access it from the
current scope.

* If it is a global, then use a new `GlobalRef` instruction to get the
value of that global. In lowering, create global variables to hold the
values of global `let` bindings so that `GlobalRef` can access those
values.
* If it is a local, walk the lexical scopes between it and the point of
use and ensure it is usable through from all of them. Error if there's
an intervening declarative scope such as a class, add a TODO to handle
captures for intervening functions, and allow `typeof(...)` to name
things it can't read the value of since it's unevaluated.

Global `let`s (which includes `let`s at class scope) now also have their
initializers collected into the global inits block. If the initializer
is non-constant, the wrapper binding also lives in the global inits
block; otherwise it continues to live in the file block and the variable
emitted for it is a global constant.

Assisted-by: Gemini and Claude via Antigravity
2026-11-06 02:53:03 +00:00

513 lines
20 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 "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Casting.h"
#include "toolchain/lower/function_context.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/expr_info.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/import_ir.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Lower {
// Returns whether this instruction names a namespace.
static auto IsNamespace(FunctionContext& context, SemIR::InstId inst_id)
-> bool {
// Note, we don't use context.GetTypeOfInst here. An instruction can't change
// from being a non-namespace in a generic to being a namespace in a specific,
// because namespace names are not first-class.
auto type_inst_id = context.sem_ir().types().GetTypeInstId(
context.sem_ir().insts().Get(inst_id).type_id());
return type_inst_id == SemIR::NamespaceType::TypeInstId;
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::AddrOf inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.lvalue_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::ArrayIndex inst) -> void {
auto* array_value = context.GetValue(inst.array_id);
auto* llvm_type = context.GetTypeOfInst(inst.array_id);
// The index in an `ArrayIndex` can be of any integer type, including
// IntLiteral. If it is an IntLiteral, its value representation is empty, so
// create a ConstantInt from its SemIR value directly.
llvm::Value* index;
auto index_type = context.GetTypeIdOfInst(inst.index_id);
if (index_type.file->types().GetTypeInstId(index_type.type_id) ==
SemIR::IntLiteralType::TypeInstId) {
auto value = context.sem_ir().insts().GetAs<SemIR::IntValue>(
context.sem_ir().constant_values().GetConstantInstId(inst.index_id));
const auto& apint_value = context.sem_ir().ints().Get(value.int_id);
context.AddIntToCurrentFingerprint(apint_value.getSExtValue());
index = llvm::ConstantInt::get(context.llvm_context(), apint_value);
} else {
context.AddIntToCurrentFingerprint(-1);
index = context.GetValue(inst.index_id);
}
llvm::Value* indexes[2] = {
llvm::ConstantInt::get(llvm::Type::getInt32Ty(context.llvm_context()), 0),
index};
context.SetLocal(inst_id,
context.builder().CreateInBoundsGEP(llvm_type, array_value,
indexes, "array.index"));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::ArrayInit inst) -> void {
// The result of initialization is the return slot of the initializer.
context.SetLocal(inst_id, context.GetValue(inst.dest_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::AsCompatible inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.source_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::Assign inst) -> void {
if (SemIR::GetExprCategory(context.sem_ir(), inst.rhs_id) !=
SemIR::ExprCategory::InPlaceInitializing) {
context.InitializeStorage(context.GetTypeIdOfInst(inst.lhs_id), inst.lhs_id,
inst.rhs_id);
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::AliasBinding inst) -> void {
if (IsNamespace(context, inst_id)) {
return;
}
context.SetLocal(inst_id, context.GetValue(inst.value_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::CppAddrOfFunction inst) -> void {
context.SetLocal(
inst_id, context.specific_file_context().GetFunction(inst.function_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::ExportDecl inst) -> void {
if (IsNamespace(context, inst_id)) {
return;
}
context.SetLocal(inst_id, context.GetValue(inst.value_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::SymbolicBinding inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.value_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::WrapperBinding inst) -> void {
auto* value = context.GetValue(inst.value_id);
context.SetLocal(inst_id, value);
const auto& entity_name =
context.sem_ir().entity_names().Get(inst.entity_name_id);
if (!entity_name.parent_scope_id.has_value() ||
entity_name.name_id == SemIR::NameId::Underscore ||
context.sem_ir().constant_values().Get(inst_id).is_constant()) {
return;
}
auto& file_context = context.GetFileContext(&context.sem_ir());
auto* global_var = file_context.GetOrCreateGlobalBinding(inst_id);
if (!global_var) {
return;
}
global_var->setInitializer(
llvm::Constant::getNullValue(global_var->getValueType()));
if (SemIR::IsRefCategory(SemIR::GetExprCategory(context.sem_ir(), inst_id))) {
auto* store = context.builder().CreateStore(value, global_var);
store->setAlignment(
context.llvm_module().getDataLayout().getPointerABIAlignment(0));
return;
}
auto value_rep = SemIR::ValueRepr::ForType(context.sem_ir(), inst.type_id);
switch (value_rep.kind) {
case SemIR::ValueRepr::Copy:
context.StoreObject(
{.file = &context.sem_ir(), .type_id = value_rep.type_id}, value,
global_var);
break;
case SemIR::ValueRepr::Pointer: {
auto pointee_type_id = context.sem_ir().GetPointeeType(value_rep.type_id);
context.CopyObject(
{.file = &context.sem_ir(), .type_id = pointee_type_id}, value,
global_var);
break;
}
default:
CARBON_FATAL("Unexpected value repr for global binding: {0}", inst);
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::GlobalRef inst) -> void {
auto [canonical_ir, canonical_inst_id] =
SemIR::GetCanonicalFileAndInstId(&context.sem_ir(), inst.inst_id);
auto& target_file_context = context.GetFileContext(canonical_ir);
auto* global_var =
target_file_context.GetOrCreateGlobalBinding(canonical_inst_id);
context.AddGlobalToCurrentFingerprint(global_var);
if (SemIR::IsRefCategory(
SemIR::GetExprCategory(*canonical_ir, canonical_inst_id))) {
auto* ptr_type = llvm::PointerType::get(context.llvm_context(),
/*AddressSpace=*/0);
auto* load = context.builder().CreateLoad(ptr_type, global_var);
load->setAlignment(
context.llvm_module().getDataLayout().getPointerABIAlignment(0));
context.SetLocal(inst_id, load);
return;
}
auto canonical_binding =
canonical_ir->insts().GetAs<SemIR::WrapperBinding>(canonical_inst_id);
auto value_rep =
SemIR::ValueRepr::ForType(*canonical_ir, canonical_binding.type_id);
switch (value_rep.kind) {
case SemIR::ValueRepr::Unknown:
case SemIR::ValueRepr::Dependent:
CARBON_FATAL("Unexpected incomplete or dependent global binding: {0}",
canonical_binding);
case SemIR::ValueRepr::None:
context.SetLocal(
inst_id,
llvm::PoisonValue::get(context.GetType(
{.file = canonical_ir, .type_id = canonical_binding.type_id})));
break;
case SemIR::ValueRepr::Copy:
context.SetLocal(inst_id,
context.LoadObject(
{.file = canonical_ir, .type_id = value_rep.type_id},
global_var));
break;
case SemIR::ValueRepr::Pointer:
context.SetLocal(inst_id, global_var);
break;
case SemIR::ValueRepr::Custom:
CARBON_FATAL("TODO: Support global binding with custom value repr");
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::BlockArg inst) -> void {
context.SetLocal(
inst_id,
context.GetBlockArg(inst.block_id, context.GetTypeIdOfInst(inst_id)));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::BoundMethod inst) -> void {
// Propagate just the function; the object is separately provided to the
// enclosing call as an implicit argument.
context.SetLocal(inst_id, context.GetValue(inst.function_decl_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::Branch inst) -> void {
// Opportunistically avoid creating a BasicBlock that contains just a branch.
// TODO: Don't do this if it would remove a loop preheader block.
llvm::BasicBlock* block = context.builder().GetInsertBlock();
if (block->empty() && context.TryToReuseBlock(inst.target_id, block)) {
// Reuse this block as the branch target.
} else {
context.builder().CreateBr(context.GetBlock(inst.target_id));
}
context.builder().ClearInsertionPoint();
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::BranchIf inst) -> void {
llvm::Value* cond = context.GetValue(inst.cond_id);
llvm::BasicBlock* then_block = context.GetBlock(inst.target_id);
llvm::BasicBlock* else_block = context.MakeSyntheticBlock();
context.builder().CreateCondBr(cond, then_block, else_block);
context.builder().SetInsertPoint(else_block);
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::BranchWithArg inst) -> void {
llvm::Value* arg = context.GetValue(inst.arg_id);
auto arg_type = context.GetTypeIdOfInst(inst.arg_id);
// Opportunistically avoid creating a BasicBlock that contains just a branch.
// We only do this for a block that we know will only have a single
// predecessor, so that we can correctly populate the predecessors of the
// PHINode.
llvm::BasicBlock* block = context.builder().GetInsertBlock();
llvm::BasicBlock* phi_predecessor = block;
if (block->empty() && context.IsCurrentSyntheticBlock(block) &&
context.TryToReuseBlock(inst.target_id, block)) {
// Reuse this block as the branch target.
phi_predecessor = block->getSinglePredecessor();
CARBON_CHECK(phi_predecessor,
"Synthetic block did not have a single predecessor");
} else {
context.builder().CreateBr(context.GetBlock(inst.target_id));
}
context.GetBlockArg(inst.target_id, arg_type)
->addIncoming(arg, phi_predecessor);
context.builder().ClearInsertionPoint();
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::Converted inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.result_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::Deref inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.pointer_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::FacetValue /*inst*/) -> void {
context.SetLocal(inst_id, context.GetTypeAsValue());
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::InPlaceInit inst) -> void {
context.InitializeStorage(context.GetTypeIdOfInst(inst.dest_id), inst.dest_id,
inst.src_id);
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::NameBindingDecl /*inst*/) -> void {
// A NameBindingDecl is lowered by pattern matching.
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::NameRef inst) -> void {
if (IsNamespace(context, inst_id)) {
return;
}
context.SetLocal(inst_id, context.GetValue(inst.value_id));
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::OutParam /*inst*/) -> void {
// Parameters are lowered by `BuildFunctionDefinition`.
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::RefParam /*inst*/) -> void {
// Parameters are lowered by `BuildFunctionDefinition`.
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::ValueParam /*inst*/) -> void {
// Parameters are lowered by `BuildFunctionDefinition`.
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::RefTagExpr inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.expr_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::ReturnSlot inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.storage_id));
}
// Returns how the function being lowered returns its result, as determined
// when its signature was lowered.
static auto GetReturnKind(FunctionContext& context) -> ReturnKind {
const auto& function_info = context.specific_file_context().GetFunctionInfo(
context.specific_sem_ir_function_id(), context.specific_id());
CARBON_CHECK(function_info, "Lowering body of undeclared function");
return function_info->return_kind;
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::Return /*inst*/) -> void {
// The 'Run()' entry point does not need to specify a return type, but
// the C runtime and system ABI expects the entry point to return an `int`.
// In this situation we modify the lowered IR to return `0`
// with the expected LLVM type that corresponds to the `int` type.
if (GetReturnKind(context) == ReturnKind::EntryPointInt32) {
context.builder().CreateRet(context.builder().getInt32(0));
return;
}
context.builder().CreateRetVoid();
}
auto HandleInst(FunctionContext& context, SemIR::InstId /*inst_id*/,
SemIR::ReturnExpr inst) -> void {
auto expr_cat =
SemIR::GetExprCategory(context.sem_ir(), inst.expr_id,
&context.specific_sem_ir(), context.specific_id());
context.AddEnumToCurrentFingerprint(expr_cat);
auto return_kind = GetReturnKind(context);
context.AddEnumToCurrentFingerprint(return_kind);
switch (return_kind) {
case ReturnKind::None:
// Nothing to return.
context.builder().CreateRetVoid();
return;
case ReturnKind::ByReference:
// Return a pointer: either the address of a reference, or the value
// representation of a value of a type with a pointer (or pointer-like
// custom) value representation.
context.builder().CreateRet(context.GetValue(inst.expr_id));
return;
case ReturnKind::ByCopy: {
auto* value = context.GetValue(inst.expr_id);
if (expr_cat == SemIR::ExprCategory::InPlaceInitializing) {
value = context.builder().CreateLoad(
context.GetType(context.GetTypeIdOfInst(inst.expr_id)), value);
}
context.builder().CreateRet(value);
return;
}
case ReturnKind::InPlace: {
auto result_type = context.GetTypeIdOfInst(inst.expr_id);
CARBON_CHECK(context.GetValueRepr(result_type).repr.kind ==
SemIR::ValueRepr::Pointer,
"TODO: Add support for ReturnExpr with custom value repr");
context.InitializeStorage(result_type, inst.dest_id, inst.expr_id);
context.builder().CreateRetVoid();
return;
}
case ReturnKind::EntryPointInt32:
CARBON_FATAL(
"`return` with expression in entry point without a return "
"type");
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::RefReturn /*inst*/) -> void {
// A `RefReturn` is a placeholder that represents the absence of a storage
// location, so it should never actually be used, but in some cases it will
// be propagated, so we poison it.
context.SetLocal(inst_id,
llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::SpecificFunction /*inst*/) -> void {
// Nothing to do. This value should never be consumed.
}
auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
SemIR::SpecificImplFunction /*inst*/) -> void {
// Nothing to do. This value should never be consumed.
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::SpecificInst inst) -> void {
// If it's valid to refer to inst.inst_id at runtime and it's not constant, it
// must be within this function, and hence in the same specific that we're
// lowering.
CARBON_CHECK(&context.sem_ir() == &context.specific_sem_ir() &&
context.specific_id() == inst.specific_id,
"Runtime specific_inst refers to a different specific");
// When lowering templates, the specific may contain instructions that
// neither emit a value during lowering nor have a constant value, so set
// require_value=false.
if (auto* value = context.GetValue(inst.inst_id, /*require_value=*/false)) {
context.SetLocal(inst_id, value);
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::SpliceBlock inst) -> void {
context.LowerBlockContents(inst.block_id);
context.SetLocal(inst_id, context.GetValue(inst.result_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::SpliceInst inst) -> void {
auto [inst_ir, inst_value_id] = GetConstantValueInSpecific(
context.specific_sem_ir(), context.specific_id(), context.sem_ir(),
inst.inst_id);
auto inst_value =
inst_ir->constant_values().GetInstAs<SemIR::InstValue>(inst_value_id);
if (inst_ir == &context.sem_ir()) {
// Easy case: same file. Just emit the spliced instruction.
context.AddInstToCurrentFingerprint(inst_value.inst_id);
context.LowerInst(inst_value.inst_id);
context.SetLocal(inst_id, context.GetValue(inst_value.inst_id));
} else {
// TODO: We don't yet have support for cross-file templates at all. Lowering
// the result here would mostly just require that we switch the `sem_ir` on
// the `FunctionContext` to instead refer to the `specific_sem_ir`, but we
// would also need to handle references from the instantiated instructions
// back to the original ones, and we don't even know how to represent those
// references yet.
CARBON_FATAL("Cross-file template lowering not implemented yet");
}
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::TypeLiteral inst) -> void {
context.SetLocal(inst_id, context.GetValue(inst.value_id));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::UnaryOperatorNot inst) -> void {
context.SetLocal(
inst_id, context.builder().CreateNot(context.GetValue(inst.operand_id)));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::UpdateInit inst) -> void {
// Ensure that our subordinate initializations have been performed. They may
// have been skipped if they were constant.
context.InitializeStorage(inst.base_init_id);
context.InitializeStorage(inst.update_init_id);
// TODO: Add a helper to poison a value slot.
context.SetLocal(inst_id,
llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::ValueReturn /*inst*/) -> void {
// A `ValueReturn` is a placeholder that represents the absence of a storage
// location, so it should never actually be used, but in some cases it will
// be propagated, so we poison it.
context.SetLocal(inst_id,
llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
}
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
SemIR::VarStorage /* inst */) -> void {
context.SetLocal(inst_id,
context.CreateAlloca(context.GetTypeIdOfInst(inst_id)));
}
} // namespace Carbon::Lower