Add tracking of function parameters (#2552)

For parameters (and in the future, arguments too; generally comma-separated lists) track two node blocks:

1. param_ir: The complete IR.
2. param_refs: Nodes within the IR that are the "root" parameter.

param_refs should allow quick counting of the # of parameters, and more efficient comparison of call args with function parameters. param_ir should be necessary to generate the actual signature.

In order to construct this, this refactors the node_block_stack into its own class, which is reused in params_stack. These carry references to the underlying SmallVector for lazy modification in order to avoid a dependency cycle with SemanticsIR (also see notes on empty node blocks below).

When finalized, the block pair is pushed onto finished_params_stack. That's because node_stack only has space for one thing, and this is two things -- so I'm essentially choosing a trade-off of adding another stack in order to avoid consuming more space in the expectation that most parse nodes have 0 or 1 things to return, and 2 will be very rare.

As factored, this currently consolidates most empty node blocks into a single canonical empty node block. This is because I think empty blocks, i.e. `()`, will be very common. In order to achieve this, SemanticsNodeBlockStack does lazy creation.

An alternative approach would have been to use 1 node block per parameter. We decided against this in order to reduce the number of vectors being created.
This commit is contained in:
Jon Ross-Perkins
2023-01-26 14:30:38 -08:00
committed by GitHub
parent 6fde5cfab0
commit 6feed2ae33
37 changed files with 749 additions and 150 deletions
@@ -4,58 +4,43 @@
#include "toolchain/semantics/semantics_parse_tree_handler.h"
#include <functional>
#include <utility>
#include "common/vlog.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "toolchain/lexer/token_kind.h"
#include "toolchain/lexer/tokenized_buffer.h"
#include "toolchain/parser/parse_node_kind.h"
#include "toolchain/semantics/semantics_builtin_kind.h"
#include "toolchain/semantics/semantics_ir.h"
#include "toolchain/semantics/semantics_node.h"
namespace Carbon {
class SemanticsParseTreeHandler::PrettyStackTraceNodeStack
: public llvm::PrettyStackTraceEntry {
class PrettyStackTraceFunction : public llvm::PrettyStackTraceEntry {
public:
explicit PrettyStackTraceNodeStack(const SemanticsParseTreeHandler* handler)
: handler_(handler) {}
~PrettyStackTraceNodeStack() override = default;
explicit PrettyStackTraceFunction(std::function<void(llvm::raw_ostream&)> fn)
: fn_(std::move(fn)) {}
~PrettyStackTraceFunction() override = default;
auto print(llvm::raw_ostream& output) const -> void override {
handler_->node_stack_.PrintForStackDump(output);
}
auto print(llvm::raw_ostream& output) const -> void override { fn_(output); }
private:
const SemanticsParseTreeHandler* handler_;
};
class SemanticsParseTreeHandler::PrettyStackTraceNodeBlockStack
: public llvm::PrettyStackTraceEntry {
public:
explicit PrettyStackTraceNodeBlockStack(
const SemanticsParseTreeHandler* handler)
: handler_(handler) {}
~PrettyStackTraceNodeBlockStack() override = default;
auto print(llvm::raw_ostream& output) const -> void override {
output << "node_block_stack_:\n";
for (int i = 0; i < static_cast<int>(handler_->node_block_stack_.size());
++i) {
const auto& entry = handler_->node_block_stack_[i];
output << "\t" << i << ".\t" << entry << "\n";
}
}
private:
const SemanticsParseTreeHandler* handler_;
const std::function<void(llvm::raw_ostream&)> fn_;
};
auto SemanticsParseTreeHandler::Build() -> void {
PrettyStackTraceNodeStack pretty_node_stack(this);
PrettyStackTraceNodeBlockStack pretty_node_block_stack(this);
PrettyStackTraceFunction pretty_node_stack([&](llvm::raw_ostream& output) {
node_stack_.PrintForStackDump(output);
});
PrettyStackTraceFunction pretty_node_block_stack(
[&](llvm::raw_ostream& output) {
node_block_stack_.PrintForStackDump(output);
});
// Add a block for the ParseTree.
node_block_stack_.push_back(semantics_->AddNodeBlock());
node_block_stack_.Push();
PushScope();
for (auto parse_node : parse_tree_->postorder()) {
@@ -69,8 +54,7 @@ auto SemanticsParseTreeHandler::Build() -> void {
}
}
node_block_stack_.pop_back();
CARBON_CHECK(node_block_stack_.empty()) << node_block_stack_.size();
node_block_stack_.Pop();
PopScope();
CARBON_CHECK(name_lookup_.empty()) << name_lookup_.size();
@@ -78,8 +62,9 @@ auto SemanticsParseTreeHandler::Build() -> void {
}
auto SemanticsParseTreeHandler::AddNode(SemanticsNode node) -> SemanticsNodeId {
CARBON_VLOG() << "AddNode " << current_block_id() << ": " << node << "\n";
return semantics_->AddNode(current_block_id(), node);
auto block = node_block_stack_.PeekForAdd();
CARBON_VLOG() << "AddNode " << block << ": " << node << "\n";
return semantics_->AddNode(block, node);
}
auto SemanticsParseTreeHandler::AddNodeAndPush(ParseTree::Node parse_node,
@@ -244,7 +229,7 @@ auto SemanticsParseTreeHandler::HandleExpressionStatement(
// Pop the expression without investigating its contents.
// TODO: This will probably eventually need to do some "do not discard"
// analysis.
node_stack_.PopAndIgnore();
node_stack_.PopAndDiscardId();
node_stack_.Push(parse_node);
}
@@ -288,23 +273,35 @@ auto SemanticsParseTreeHandler::HandleFunctionDefinition(
node_stack_.PopAndIgnore();
PopScope();
node_block_stack_.pop_back();
node_block_stack_.Pop();
node_stack_.Push(parse_node);
}
auto SemanticsParseTreeHandler::HandleFunctionDefinitionStart(
ParseTree::Node parse_node) -> void {
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterList);
node_stack_.PopForSoloParseNode(ParseNodeKind::ParameterList);
auto [param_ir_id, param_refs_id] = finished_params_stack_.pop_back_val();
auto name_node = node_stack_.PopForSoloParseNode(ParseNodeKind::DeclaredName);
auto fn_node =
node_stack_.PopForSoloParseNode(ParseNodeKind::FunctionIntroducer);
auto decl_id = AddNode(SemanticsNode::MakeFunctionDeclaration(fn_node));
SemanticsCallable callable;
callable.param_ir_id = param_ir_id;
callable.param_refs_id = param_refs_id;
auto callable_id = semantics_->AddCallable(callable);
auto decl_id =
AddNode(SemanticsNode::MakeFunctionDeclaration(fn_node, callable_id));
// TODO: Propagate the type of the function.
BindName(name_node, SemanticsNodeId::MakeInvalid(), decl_id);
auto block_id = semantics_->AddNodeBlock();
AddNode(SemanticsNode::MakeFunctionDefinition(parse_node, decl_id, block_id));
node_block_stack_.push_back(block_id);
// TODO: Consider approaches that allow lazy creation of the definition block.
auto outer_block = node_block_stack_.PeekForAdd();
auto def_block = node_block_stack_.PushWithUnconditionalAlloc();
auto node =
SemanticsNode::MakeFunctionDefinition(parse_node, decl_id, def_block);
CARBON_VLOG() << "AddNode " << outer_block << ": " << node << "\n";
semantics_->AddNode(outer_block, node);
PushScope();
node_stack_.Push(parse_node);
}
@@ -451,23 +448,76 @@ auto SemanticsParseTreeHandler::HandlePackageLibrary(
CARBON_FATAL() << "TODO";
}
auto SemanticsParseTreeHandler::SaveParam() -> bool {
// Copy the last node added to the IR block into the params block.
auto ir_id = node_block_stack_.Peek();
if (!ir_id.is_valid()) {
return false;
}
auto& ir = semantics_->GetNodeBlock(ir_id);
CARBON_CHECK(!ir.empty())
<< "Should only have a valid ID if a node was added";
auto& param = ir.back();
auto& params = semantics_->GetNodeBlock(params_stack_.PeekForAdd());
if (!params.empty() && param == params.back()) {
// The param was already added after a comma.
return false;
}
params.push_back(ir.back());
return true;
}
auto SemanticsParseTreeHandler::HandleParameterList(ParseTree::Node parse_node)
-> void {
// TODO: This should transform into a usable parameter list. For now
// it's unused and only stored so that node counts match.
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
node_stack_.Push(parse_node);
// If there's a node in the IR block that has yet to be added to the params
// block, add it now.
SaveParam();
while (true) {
switch (auto parse_kind =
parse_tree_->node_kind(node_stack_.PeekParseNode())) {
case ParseNodeKind::ParameterListStart:
node_stack_.PopAndDiscardSoloParseNode(
ParseNodeKind::ParameterListStart);
finished_params_stack_.push_back(
{node_block_stack_.Pop(), params_stack_.Pop()});
node_stack_.Push(parse_node);
return;
case ParseNodeKind::ParameterListComma:
node_stack_.PopAndDiscardSoloParseNode(
ParseNodeKind::ParameterListComma);
break;
case ParseNodeKind::PatternBinding:
node_stack_.PopAndDiscardId(ParseNodeKind::PatternBinding);
break;
default:
// This should only occur for invalid parse trees.
CARBON_FATAL() << "TODO: " << parse_kind;
}
}
llvm_unreachable("loop always exits");
}
auto SemanticsParseTreeHandler::HandleParameterListComma(
ParseTree::Node /*parse_node*/) -> void {
CARBON_FATAL() << "TODO";
ParseTree::Node parse_node) -> void {
node_stack_.Push(parse_node);
// Copy the last node added to the IR block into the params block.
bool had_param_before_comma = SaveParam();
CARBON_CHECK(had_param_before_comma)
<< "TODO: Should have a param before comma, will need error recovery";
}
auto SemanticsParseTreeHandler::HandleParameterListStart(
ParseTree::Node parse_node) -> void {
// TODO: See HandleParameterList.
node_stack_.Push(parse_node);
params_stack_.Push();
node_block_stack_.Push();
}
auto SemanticsParseTreeHandler::HandleParenExpression(