Files
carbon-lang/toolchain/semantics/semantics_node_stack.h
T
Richard SmithandChandler Carruth 1013d1773c Model function calls as initializing expressions (#3089)
Start treating function calls as initializing expressions instead of as
value expressions.

This required adding support for expression categories. Value bindings
and temporary materialization conversions are created where necessary to
transition between expression categories. For a function call with a
return slot, we speculatively create a materialized temporary before the
call and either commit to it or replace it with something else later,
once we see how the function call expression is actually used.

This change follows the direction suggested in #3133 for initializing
expressions: depending on the return type of a function, the return
value will either be initialized in-place or returned directly. This is
visible in the semantics IR, which is a little unfortunate but is
probably necessary as this is part of the semantics of the program.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2023-08-24 19:35:07 +00:00

360 lines
13 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
#ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_STACK_H_
#define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_STACK_H_
#include <type_traits>
#include "common/vlog.h"
#include "llvm/ADT/SmallVector.h"
#include "toolchain/parser/parse_node_kind.h"
#include "toolchain/parser/parse_tree.h"
#include "toolchain/semantics/semantics_node.h"
namespace Carbon::Check {
// Wraps the stack of nodes for Context.
//
// All pushes and pops will be vlogged.
//
// Pop APIs will run basic verification:
//
// - If receiving a pop_parse_kind, verify that the parse_node being popped is
// of pop_parse_kind.
// - Validates presence of node_id based on whether it's a solo
// parse_node.
//
// These should be assumed API constraints unless otherwise mentioned on a
// method. The main exception is PopAndIgnore, which doesn't do verification.
class NodeStack {
public:
explicit NodeStack(const ParseTree& parse_tree,
llvm::raw_ostream* vlog_stream)
: parse_tree_(&parse_tree), vlog_stream_(vlog_stream) {}
// Pushes a solo parse tree node onto the stack. Used when there is no
// IR generated by the node.
auto Push(ParseTree::Node parse_node) -> void {
CARBON_CHECK(ParseNodeKindToIdKind(parse_tree_->node_kind(parse_node)) ==
IdKind::SoloParseNode)
<< "Parse kind expects an Id: " << parse_tree_->node_kind(parse_node);
CARBON_VLOG() << "Node Push " << stack_.size() << ": "
<< parse_tree_->node_kind(parse_node) << " -> <none>\n";
CARBON_CHECK(stack_.size() < (1 << 20))
<< "Excessive stack size: likely infinite loop";
stack_.push_back(Entry(parse_node, SemIR::NodeId::Invalid));
}
// Pushes a parse tree node onto the stack with an ID.
template <typename IdT>
auto Push(ParseTree::Node parse_node, IdT id) -> void {
CARBON_CHECK(ParseNodeKindToIdKind(parse_tree_->node_kind(parse_node)) ==
IdTypeToIdKind<IdT>())
<< "Parse kind expected a different IdT: "
<< parse_tree_->node_kind(parse_node) << " -> " << id << "\n";
CARBON_CHECK(id.is_valid()) << "Push called with invalid id: "
<< parse_tree_->node_kind(parse_node);
CARBON_VLOG() << "Node Push " << stack_.size() << ": "
<< parse_tree_->node_kind(parse_node) << " -> " << id << "\n";
CARBON_CHECK(stack_.size() < (1 << 20))
<< "Excessive stack size: likely infinite loop";
stack_.push_back(Entry(parse_node, id));
}
// Pops the top of the stack without any verification.
auto PopAndIgnore() -> void { PopEntry<SemIR::NodeId>(); }
// Pops the top of the stack and returns the parse_node.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto PopForSoloParseNode() -> ParseTree::Node {
Entry back = PopEntry<SemIR::NodeId>();
RequireIdKind(ParseNodeKind::Create(RequiredParseKind),
IdKind::SoloParseNode);
RequireParseKind<RequiredParseKind>(back.parse_node);
return back.parse_node;
}
// Pops the top of the stack.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto PopAndDiscardSoloParseNode() -> void {
PopForSoloParseNode<RequiredParseKind>();
}
// Pops an expression from the top of the stack and returns the parse_node and
// the ID.
auto PopExpressionWithParseNode()
-> std::pair<ParseTree::Node, SemIR::NodeId> {
return PopWithParseNode<SemIR::NodeId>();
}
// Pops the top of the stack and returns the parse_node and the ID.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto PopWithParseNode() -> auto {
constexpr IdKind RequiredIdKind =
ParseNodeKindToIdKind(ParseNodeKind::Create(RequiredParseKind));
if constexpr (RequiredIdKind == IdKind::NodeId) {
auto back = PopWithParseNode<SemIR::NodeId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::NodeBlockId) {
auto back = PopWithParseNode<SemIR::NodeBlockId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::FunctionId) {
auto back = PopWithParseNode<SemIR::FunctionId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::StringId) {
auto back = PopWithParseNode<SemIR::StringId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::TypeId) {
auto back = PopWithParseNode<SemIR::TypeId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
CARBON_FATAL() << "Unpoppable IdKind for parse kind: "
<< ParseNodeKind::Create(RequiredParseKind)
<< "; see value in ParseNodeKindToIdKind";
}
// Pops an expression from the top of the stack and returns the ID.
// Expressions map multiple ParseNodeKinds to SemIR::NodeId always.
auto PopExpression() -> SemIR::NodeId {
return PopExpressionWithParseNode().second;
}
// Pops the top of the stack and returns the ID.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto Pop() -> auto {
return PopWithParseNode<RequiredParseKind>().second;
}
// Peeks at the parse_node of the top of the stack.
auto PeekParseNode() -> ParseTree::Node { return stack_.back().parse_node; }
// Peeks at the ID of the top of the stack.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto Peek() -> auto {
Entry back = stack_.back();
RequireParseKind<RequiredParseKind>(back.parse_node);
constexpr IdKind RequiredIdKind =
ParseNodeKindToIdKind(ParseNodeKind::Create(RequiredParseKind));
if constexpr (RequiredIdKind == IdKind::NodeId) {
return back.id<SemIR::NodeId>();
}
if constexpr (RequiredIdKind == IdKind::NodeBlockId) {
return back.id<SemIR::NodeBlockId>();
}
if constexpr (RequiredIdKind == IdKind::FunctionId) {
return back.id<SemIR::FunctionId>();
}
if constexpr (RequiredIdKind == IdKind::StringId) {
return back.id<SemIR::StringId>();
}
if constexpr (RequiredIdKind == IdKind::TypeId) {
return back.id<SemIR::TypeId>();
}
CARBON_FATAL() << "Unpeekable IdKind for parse kind: "
<< ParseNodeKind::Create(RequiredParseKind)
<< "; see value in ParseNodeKindToIdKind";
}
// Prints the stack for a stack dump.
auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
auto empty() const -> bool { return stack_.empty(); }
auto size() const -> size_t { return stack_.size(); }
private:
// Possible associated ID types.
enum class IdKind {
NodeId,
NodeBlockId,
FunctionId,
StringId,
TypeId,
// No associated ID type.
SoloParseNode,
// Not expected in the node stack.
Unused,
};
// An entry in stack_.
struct Entry {
explicit Entry(ParseTree::Node parse_node, SemIR::NodeId node_id)
: parse_node(parse_node), node_id(node_id) {}
explicit Entry(ParseTree::Node parse_node, SemIR::NodeBlockId node_block_id)
: parse_node(parse_node), node_block_id(node_block_id) {}
explicit Entry(ParseTree::Node parse_node, SemIR::FunctionId function_id)
: parse_node(parse_node), function_id(function_id) {}
explicit Entry(ParseTree::Node parse_node, SemIR::StringId name_id)
: parse_node(parse_node), name_id(name_id) {}
explicit Entry(ParseTree::Node parse_node, SemIR::TypeId type_id)
: parse_node(parse_node), type_id(type_id) {}
// Returns the appropriate ID basaed on type.
template <typename T>
auto id() -> T& {
if constexpr (std::is_same<T, SemIR::NodeId>()) {
return node_id;
}
if constexpr (std::is_same<T, SemIR::NodeBlockId>()) {
return node_block_id;
}
if constexpr (std::is_same<T, SemIR::FunctionId>()) {
return function_id;
}
if constexpr (std::is_same<T, SemIR::StringId>()) {
return name_id;
}
if constexpr (std::is_same<T, SemIR::TypeId>()) {
return type_id;
}
}
// The node associated with the stack entry.
ParseTree::Node parse_node;
// The entries will evaluate as invalid if and only if they're a solo
// parse_node. Invalid is used instead of optional to save space.
//
// A discriminator isn't needed because the caller can determine which field
// is used based on the ParseNodeKind.
union {
SemIR::NodeId node_id;
SemIR::NodeBlockId node_block_id;
SemIR::FunctionId function_id;
SemIR::StringId name_id;
SemIR::TypeId type_id;
};
};
static_assert(sizeof(Entry) == 8, "Unexpected Entry size");
// Translate a parse node kind to the enum ID kind it should always provide.
static constexpr auto ParseNodeKindToIdKind(ParseNodeKind kind) -> IdKind {
switch (kind) {
case ParseNodeKind::ArrayExpression:
case ParseNodeKind::CallExpression:
case ParseNodeKind::CallExpressionStart:
case ParseNodeKind::IfExpressionElse:
case ParseNodeKind::IndexExpression:
case ParseNodeKind::InfixOperator:
case ParseNodeKind::Literal:
case ParseNodeKind::MemberAccessExpression:
case ParseNodeKind::NameExpression:
case ParseNodeKind::ParenExpression:
case ParseNodeKind::PatternBinding:
case ParseNodeKind::PostfixOperator:
case ParseNodeKind::PrefixOperator:
case ParseNodeKind::ReturnType:
case ParseNodeKind::ShortCircuitOperand:
case ParseNodeKind::StructFieldValue:
case ParseNodeKind::StructLiteral:
case ParseNodeKind::StructFieldType:
case ParseNodeKind::StructTypeLiteral:
case ParseNodeKind::TupleLiteral:
return IdKind::NodeId;
case ParseNodeKind::IfExpressionThen:
case ParseNodeKind::IfStatementElse:
case ParseNodeKind::ParameterList:
return IdKind::NodeBlockId;
case ParseNodeKind::FunctionDefinitionStart:
return IdKind::FunctionId;
case ParseNodeKind::Name:
return IdKind::StringId;
case ParseNodeKind::ArrayExpressionSemi:
case ParseNodeKind::CodeBlockStart:
case ParseNodeKind::FunctionIntroducer:
case ParseNodeKind::IfCondition:
case ParseNodeKind::IfExpressionIf:
case ParseNodeKind::ParameterListStart:
case ParseNodeKind::ParenExpressionOrTupleLiteralStart:
case ParseNodeKind::QualifiedDeclaration:
case ParseNodeKind::ReturnStatementStart:
case ParseNodeKind::StructLiteralOrStructTypeLiteralStart:
case ParseNodeKind::VariableInitializer:
case ParseNodeKind::VariableIntroducer:
return IdKind::SoloParseNode;
default:
return IdKind::Unused;
}
}
// Translates an ID type to the enum ID kind for comparison with
// ParseNodeKindToIdKind.
template <typename IdT>
static constexpr auto IdTypeToIdKind() -> IdKind {
if constexpr (std::is_same_v<IdT, SemIR::NodeId>) {
return IdKind::NodeId;
}
if constexpr (std::is_same_v<IdT, SemIR::NodeBlockId>) {
return IdKind::NodeBlockId;
}
if constexpr (std::is_same_v<IdT, SemIR::FunctionId>) {
return IdKind::FunctionId;
}
if constexpr (std::is_same_v<IdT, SemIR::StringId>) {
return IdKind::StringId;
}
if constexpr (std::is_same_v<IdT, SemIR::TypeId>) {
return IdKind::TypeId;
}
}
// Pops an entry.
template <typename IdT>
auto PopEntry() -> Entry {
Entry back = stack_.pop_back_val();
CARBON_VLOG() << "Node Pop " << stack_.size() << ": "
<< parse_tree_->node_kind(back.parse_node) << " -> "
<< back.id<IdT>() << "\n";
return back;
}
// Pops the top of the stack and returns the parse_node and the ID.
template <typename IdT>
auto PopWithParseNode() -> std::pair<ParseTree::Node, IdT> {
Entry back = PopEntry<IdT>();
RequireIdKind(parse_tree_->node_kind(back.parse_node),
IdTypeToIdKind<IdT>());
return {back.parse_node, back.id<IdT>()};
}
// Require a ParseNodeKind be mapped to a particular IdKind.
auto RequireIdKind(ParseNodeKind parse_kind, IdKind id_kind) -> void {
CARBON_CHECK(ParseNodeKindToIdKind(parse_kind) == id_kind)
<< "Unexpected IdKind mapping for " << parse_kind;
}
// Require an entry to have the given ParseNodeKind.
template <ParseNodeKind::RawEnumType RequiredParseKind>
auto RequireParseKind(ParseTree::Node parse_node) -> void {
auto actual_kind = parse_tree_->node_kind(parse_node);
CARBON_CHECK(RequiredParseKind == actual_kind)
<< "Expected " << ParseNodeKind::Create(RequiredParseKind) << ", found "
<< actual_kind;
}
// The file's parse tree.
const ParseTree* parse_tree_;
// Whether to print verbose output.
llvm::raw_ostream* vlog_stream_;
// The actual stack.
// PushEntry and PopEntry control modification in order to centralize
// vlogging.
llvm::SmallVector<Entry> stack_;
};
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_NODE_STACK_H_