507 lines
13 KiB
C++
507 lines
13 KiB
C++
// escape.h -- Go escape analysis (based on Go compiler algorithm).
|
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#ifndef GO_ESCAPE_H
|
|
#define GO_ESCAPE_H
|
|
|
|
#include "gogo.h"
|
|
|
|
class Named_object;
|
|
class Expression;
|
|
class Statement;
|
|
class Escape_context;
|
|
|
|
// There can be loops in the escape graph that lead to arbitrary recursions.
|
|
// See comment in gc/esc.go.
|
|
static const int MIN_LEVEL = -2;
|
|
|
|
// Level models the escapement of a Node using two integers that are computed
|
|
// by backwards-analyzing the flow of a function from its sink and increasing or
|
|
// decreasing based on dereferences and addressing, respectively.
|
|
// One integer, known as the level's VALUE (think absolute value), is just the
|
|
// sum of indirections (via referencing or dereferencing) applied to the Node.
|
|
// The second, known as the level's SUFFIX_VALUE, is the amount of indirections
|
|
// applied after some data has been copied from the node. When accessing a
|
|
// field F of an object O and then applying indirections, for example, the field
|
|
// access O.F is assumed to copy that data from O before applying indirections.
|
|
// With this, even if O.F escapes, it might mean that the content of O escape,
|
|
// but not the object O itself.
|
|
|
|
class Level
|
|
{
|
|
public:
|
|
Level()
|
|
: value_(0), suffix_value_(0)
|
|
{ }
|
|
|
|
Level(int value, int suffix)
|
|
: value_(value), suffix_value_(suffix)
|
|
{ }
|
|
|
|
// Return this level's value.
|
|
int
|
|
value() const
|
|
{ return this->value_; }
|
|
|
|
// Return this level's suffix value.
|
|
int
|
|
suffix_value() const
|
|
{ return this->suffix_value_; }
|
|
|
|
// Increase the level because a node is dereferenced.
|
|
Level
|
|
increase() const
|
|
{
|
|
if (this->value_ <= MIN_LEVEL)
|
|
return Level(MIN_LEVEL, 0);
|
|
|
|
return Level(this->value_ + 1, this->suffix_value_ + 1);
|
|
}
|
|
|
|
// Decrease the level because a node is referenced.
|
|
Level
|
|
decrease() const
|
|
{
|
|
if (this->value_ <= MIN_LEVEL)
|
|
return Level(MIN_LEVEL, 0);
|
|
|
|
return Level(this->value_ - 1, this->suffix_value_ - 1);
|
|
}
|
|
|
|
// Model a node being copied.
|
|
Level
|
|
copy() const
|
|
{
|
|
return Level(this->value_, std::max(this->suffix_value_, 0));
|
|
}
|
|
|
|
// Return a level with the minimum values of this level and l.
|
|
Level
|
|
min(const Level& l) const
|
|
{
|
|
return Level(std::min(this->value_, l.value()),
|
|
std::min(this->suffix_value_, l.suffix_value()));
|
|
}
|
|
|
|
// Compare two levels for equality.
|
|
bool
|
|
operator==(const Level& l) const
|
|
{
|
|
return (this->value_ == l.value()
|
|
&& this->suffix_value_ == l.suffix_value());
|
|
}
|
|
|
|
// Create a level from an integer value.
|
|
static Level
|
|
From(int i)
|
|
{
|
|
if (i <= MIN_LEVEL)
|
|
return Level(MIN_LEVEL, 0);
|
|
return Level(i, 0);
|
|
}
|
|
|
|
private:
|
|
// The sum of all references (-1) and indirects (+1) applied to a Node.
|
|
int value_;
|
|
// The sum of all references (-1) abd indirects (+1) applied to a copied Node.
|
|
int suffix_value_;
|
|
};
|
|
|
|
// A node in the escape graph. This node is an alias to a particular node
|
|
// in the Go parse tree. Specifically, it can represent an expression node,
|
|
// a statement node, or a named object node (a variable or function).
|
|
|
|
class Node
|
|
{
|
|
public:
|
|
// This classification represents type of nodes in the Go parse tree that are
|
|
// interesting during the analysis.
|
|
enum Node_classification
|
|
{
|
|
NODE_OBJECT,
|
|
NODE_EXPRESSION,
|
|
NODE_STATEMENT,
|
|
// A "fake" node that models the indirection of its child node.
|
|
// This node does not correspond to an AST node.
|
|
NODE_INDIRECT
|
|
};
|
|
|
|
// The state necessary to keep track of how a node escapes.
|
|
struct Escape_state
|
|
{
|
|
// The current function.
|
|
Named_object* fn;
|
|
// A list of source nodes that flow into this node.
|
|
std::set<Node*> flows;
|
|
// If the node is a function call, the list of nodes returned.
|
|
std::vector<Node*> retvals;
|
|
// The node's loop depth.
|
|
int loop_depth;
|
|
// There is an extra loop depth in the flood phase used to account for
|
|
// variables referenced across closures. This is the maximum value of the
|
|
// extra loop depth seen during the flood that touches this node.
|
|
int max_extra_loop_depth;
|
|
// The node's level.
|
|
Level level;
|
|
// An ID given to a node when it is encountered as a flow from the current
|
|
// dst node. This is used to avoid infinite recursion of cyclic nodes.
|
|
int flood_id;
|
|
|
|
Escape_state()
|
|
: fn(NULL), loop_depth(0), max_extra_loop_depth(0), flood_id(0)
|
|
{ }
|
|
};
|
|
|
|
// Note: values in this enum appear in export data, and therefore MUST NOT
|
|
// change.
|
|
enum Escapement_encoding
|
|
{
|
|
ESCAPE_UNKNOWN,
|
|
// Does not escape to heap, result, or parameters.
|
|
ESCAPE_NONE,
|
|
// Is returned or reachable from a return statement.
|
|
ESCAPE_RETURN,
|
|
// Reachable from the heap.
|
|
ESCAPE_HEAP,
|
|
// By construction will not escape.
|
|
ESCAPE_NEVER
|
|
};
|
|
|
|
// Multiple constructors for each classification.
|
|
Node(Named_object* no)
|
|
: classification_(NODE_OBJECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
|
|
child_(NULL)
|
|
{ this->u_.object_val = no; }
|
|
|
|
Node(Expression* e)
|
|
: classification_(NODE_EXPRESSION), state_(NULL), encoding_(ESCAPE_UNKNOWN),
|
|
child_(NULL)
|
|
{ this->u_.expression_val = e; }
|
|
|
|
Node(Statement* s)
|
|
: classification_(NODE_STATEMENT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
|
|
child_(NULL)
|
|
{ this->u_.statement_val = s; }
|
|
|
|
Node(Node *n)
|
|
: classification_(NODE_INDIRECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
|
|
child_(n)
|
|
{}
|
|
|
|
~Node();
|
|
|
|
// Return this node's type.
|
|
Type*
|
|
type() const;
|
|
|
|
// Return this node's location.
|
|
Location
|
|
location() const;
|
|
|
|
// Return the location where the node's underlying object is defined.
|
|
Location
|
|
definition_location() const;
|
|
|
|
// Return this node's AST formatted string.
|
|
std::string
|
|
ast_format(Gogo*) const;
|
|
|
|
// Return this node's detailed format string.
|
|
std::string
|
|
details();
|
|
|
|
std::string
|
|
op_format() const;
|
|
|
|
// Return this node's escape state.
|
|
Escape_state*
|
|
state(Escape_context* context, Named_object* fn);
|
|
|
|
// Return this node's escape encoding.
|
|
int
|
|
encoding();
|
|
|
|
// Set the node's escape encoding.
|
|
void
|
|
set_encoding(int enc);
|
|
|
|
bool
|
|
is_big(Escape_context*) const;
|
|
|
|
bool
|
|
is_sink() const;
|
|
|
|
// Methods to return the underlying value in the Node union.
|
|
Named_object*
|
|
object() const
|
|
{
|
|
return (this->classification_ == NODE_OBJECT
|
|
? this->u_.object_val
|
|
: NULL);
|
|
}
|
|
|
|
Expression*
|
|
expr() const
|
|
{
|
|
return (this->classification_ == NODE_EXPRESSION
|
|
? this->u_.expression_val
|
|
: NULL);
|
|
}
|
|
|
|
Statement*
|
|
statement() const
|
|
{
|
|
return (this->classification_ == NODE_STATEMENT
|
|
? this->u_.statement_val
|
|
: NULL);
|
|
}
|
|
|
|
bool
|
|
is_indirect() const
|
|
{ return this->classification_ == NODE_INDIRECT; }
|
|
|
|
// Return its child node.
|
|
// Child node is used only in indirect node, and in expression node
|
|
// representing slicing an array.
|
|
Node*
|
|
child() const
|
|
{ return this->child_; }
|
|
|
|
// Set the child node.
|
|
void
|
|
set_child(Node* n)
|
|
{ this->child_ = n; }
|
|
|
|
// Static creation methods for each value supported in the union.
|
|
static Node*
|
|
make_node(Named_object*);
|
|
|
|
static Node*
|
|
make_node(Expression*);
|
|
|
|
static Node*
|
|
make_node(Statement*);
|
|
|
|
static Node*
|
|
make_indirect_node(Node*);
|
|
|
|
// Return the maximum of an existing escape encoding E and a new
|
|
// escape type.
|
|
static int
|
|
max_encoding(int e, int etype);
|
|
|
|
// Return a modified encoding for an input parameter that flows into an
|
|
// output parameter.
|
|
static int
|
|
note_inout_flows(int e, int index, Level level);
|
|
|
|
// Reclaim nodes.
|
|
static void
|
|
reclaim_nodes();
|
|
|
|
private:
|
|
// The classification of this Node.
|
|
Node_classification classification_;
|
|
// The value union.
|
|
union
|
|
{
|
|
// If NODE_OBJECT.
|
|
Named_object* object_val;
|
|
// If NODE_EXPRESSION.
|
|
Expression* expression_val;
|
|
// If NODE_STATEMENT.
|
|
Statement* statement_val;
|
|
} u_;
|
|
// The node's escape state.
|
|
Escape_state* state_;
|
|
// The node's escape encoding.
|
|
// The encoding:
|
|
// | Return Encoding: (width - ESCAPE_RETURN_BITS) |
|
|
// | Content Escapes bit: 1 |
|
|
// | Escapement_encoding: ESCAPE_BITS |
|
|
int encoding_;
|
|
|
|
// Child node, used only in indirect node, and expression node representing
|
|
// slicing an array.
|
|
Node* child_;
|
|
|
|
// Cache all the Nodes created via Node::make_node to make the API simpler.
|
|
static Unordered_map(Named_object*, Node*) objects;
|
|
static Unordered_map(Expression*, Node*) expressions;
|
|
static Unordered_map(Statement*, Node*) statements;
|
|
|
|
// Collection of all NODE_INDIRECT Nodes, used for reclaiming memory. This
|
|
// is not a cache -- each make_indirect_node will make a fresh Node.
|
|
static std::vector<Node*> indirects;
|
|
};
|
|
|
|
// The amount of bits used for the escapement encoding.
|
|
static const int ESCAPE_BITS = 3;
|
|
|
|
// Mask used to extract encoding.
|
|
static const int ESCAPE_MASK = (1 << ESCAPE_BITS) - 1;
|
|
|
|
// Value obtained by indirect of parameter escapes to heap.
|
|
static const int ESCAPE_CONTENT_ESCAPES = 1 << ESCAPE_BITS;
|
|
|
|
// The amount of bits used in encoding of return values.
|
|
static const int ESCAPE_RETURN_BITS = ESCAPE_BITS + 1;
|
|
|
|
// For each output, the number of bits for a tag.
|
|
static const int ESCAPE_BITS_PER_OUTPUT_IN_TAG = 3;
|
|
|
|
// The bit max to extract a single tag.
|
|
static const int ESCAPE_BITS_MASK_FOR_TAG = (1 << ESCAPE_BITS_PER_OUTPUT_IN_TAG) - 1;
|
|
|
|
// The largest level that can be stored in a tag.
|
|
static const int ESCAPE_MAX_ENCODED_LEVEL = ESCAPE_BITS_MASK_FOR_TAG - 1;
|
|
|
|
// A helper for converting escape notes from encoded integers to a
|
|
// textual format and vice-versa.
|
|
|
|
class Escape_note
|
|
{
|
|
public:
|
|
// Return the string representation of an escapement encoding.
|
|
static std::string
|
|
make_tag(int encoding);
|
|
|
|
// Return the escapement encoding for a string tag.
|
|
static int
|
|
parse_tag(std::string* tag);
|
|
};
|
|
|
|
// The escape context for a set of functions being analyzed.
|
|
|
|
class Escape_context
|
|
{
|
|
public:
|
|
Escape_context(Gogo* gogo, bool recursive);
|
|
|
|
// Return the Go IR.
|
|
Gogo*
|
|
gogo() const
|
|
{ return this->gogo_; }
|
|
|
|
// Return the current function being analyzed.
|
|
Named_object*
|
|
current_function() const
|
|
{ return this->current_function_; }
|
|
|
|
// Change the function being analyzed.
|
|
void
|
|
set_current_function(Named_object* fn)
|
|
{ this->current_function_ = fn; }
|
|
|
|
// Return the name of the current function.
|
|
std::string
|
|
current_function_name() const;
|
|
|
|
// Return true if this is the context for a mutually recursive set of functions.
|
|
bool
|
|
recursive() const
|
|
{ return this->recursive_; }
|
|
|
|
// Return the special sink node for this context.
|
|
Node*
|
|
sink()
|
|
{ return this->sink_; }
|
|
|
|
// Return the current loop depth.
|
|
int
|
|
loop_depth() const
|
|
{ return this->loop_depth_; }
|
|
|
|
// Increase the loop depth.
|
|
void
|
|
increase_loop_depth()
|
|
{ this->loop_depth_++; }
|
|
|
|
// Decrease the loop depth.
|
|
void
|
|
decrease_loop_depth()
|
|
{ this->loop_depth_--; }
|
|
|
|
void
|
|
set_loop_depth(int depth)
|
|
{ this->loop_depth_ = depth; }
|
|
|
|
// Return the destination nodes encountered in this context.
|
|
const std::set<Node*>&
|
|
dsts() const
|
|
{ return this->dsts_; }
|
|
|
|
// Add a destination node.
|
|
void
|
|
add_dst(Node* dst)
|
|
{ this->dsts_.insert(dst); }
|
|
|
|
// Return the nodes initially marked as non-escaping before flooding.
|
|
const std::vector<Node*>&
|
|
non_escaping_nodes() const
|
|
{ return this->noesc_; }
|
|
|
|
// Initialize the dummy return values for this Node N using the results
|
|
// in FNTYPE.
|
|
void
|
|
init_retvals(Node* n, Function_type* fntype);
|
|
|
|
// Return the indirection of Node N.
|
|
Node*
|
|
add_dereference(Node* n);
|
|
|
|
// Keep track of possibly non-escaping node N.
|
|
void
|
|
track(Node* n);
|
|
|
|
int
|
|
flood_id() const
|
|
{ return this->flood_id_; }
|
|
|
|
void
|
|
increase_flood_id()
|
|
{ this->flood_id_++; }
|
|
|
|
int
|
|
pdepth() const
|
|
{ return this->pdepth_; }
|
|
|
|
void
|
|
increase_pdepth()
|
|
{ this->pdepth_++; }
|
|
|
|
void
|
|
decrease_pdepth()
|
|
{ this->pdepth_--; }
|
|
|
|
private:
|
|
// The Go IR.
|
|
Gogo* gogo_;
|
|
// The current function being analyzed.
|
|
Named_object* current_function_;
|
|
// Return whether this is the context for a recursive function or a group of mutually
|
|
// recursive functions.
|
|
bool recursive_;
|
|
// The sink for this escape context. Nodes whose reference objects created
|
|
// outside the current function are assigned to the sink as well as nodes that
|
|
// the analysis loses track of.
|
|
Node* sink_;
|
|
// Used to detect nested loop scopes.
|
|
int loop_depth_;
|
|
// All the destination nodes considered in this set of analyzed functions.
|
|
std::set<Node*> dsts_;
|
|
// All the nodes that were noted as possibly not escaping in this context.
|
|
std::vector<Node*> noesc_;
|
|
// An ID given to each dst and the flows discovered through DFS of that dst.
|
|
// This is used to avoid infinite recursion from nodes that point to each
|
|
// other within the flooding phase.
|
|
int flood_id_;
|
|
// The current level of recursion within a flooded section; used to debug.
|
|
int pdepth_;
|
|
};
|
|
|
|
#endif // !defined(GO_ESCAPE_H)
|