mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 10:18:29 +00:00
A function with a value return form, such as `->? form(val C)`, is lowered to return the value representation of `C`: a pointer for types with a pointer value representation. However, lowering of `ReturnExpr` re-derived the return convention independently of the function's signature, and assumed that any returned value expression was a `returned var`, taking the initializing-return path. For types with an in-place initializing representation, that tried to initialize a nonexistent return slot and crashed. (This only appeared to work for types like `i32`, whose by-copy initializing representation happens to match the value representation.) Fix this by recording how the function returns its result -- `void`, by copy, by reference, in place via an `sret` parameter, or as the entry point's implicit `i32` -- as a `ReturnKind` in `FunctionTypeInfo` and `FunctionInfo` when the signature is lowered, and having the `Return` and `ReturnExpr` handlers switch on it. This keeps the signature and the `return` lowering in agreement by construction, and replaces the `IsEntryPoint` check in `Return` lowering.
433 lines
17 KiB
C++
433 lines
17 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/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 {
|
|
context.SetLocal(inst_id, context.GetValue(inst.value_id));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
auto inner_inst_id = inst.value_id;
|
|
|
|
// `GetValue` will fail on package-scope value bindings because they aren't
|
|
// constants, and they aren't global variables, so as a workaround we
|
|
// peek through bindings here to directly access the bound value.
|
|
// TODO: Find a way of dealing with this that still works if the bound
|
|
// value isn't a global variable or constant either.
|
|
if (auto bind_name =
|
|
context.sem_ir().insts().TryGetAs<SemIR::AnyBinding>(inner_inst_id)) {
|
|
inner_inst_id = bind_name->value_id;
|
|
}
|
|
|
|
context.SetLocal(inst_id, context.GetValue(inner_inst_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
|