dart-sdk/runtime/vm/scopes.h
Ivan Inozemtsev e005326c86 Mark operator== const to avoid ambiguity in C++20.
C++20 will automatically generate an operator== with reversed operand order, which is ambiguous with the written operator== when one argument is marked const and the other isn't.

TEST=ci
Change-Id: Ie4f7b293c1e68462f08335f4d4e9a8146dcc0e15
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/329105
Commit-Queue: Ivan Inozemtsev <iinozemtsev@google.com>
Auto-Submit: Ivan Inozemtsev <iinozemtsev@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2023-10-04 13:18:55 +00:00

481 lines
18 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_SCOPES_H_
#define RUNTIME_VM_SCOPES_H_
#include <limits>
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/allocation.h"
#include "vm/growable_array.h"
#include "vm/object.h"
#include "vm/raw_object.h"
#include "vm/symbols.h"
#include "vm/token.h"
namespace dart {
class CompileType;
class LocalScope;
class Slot;
// Indices of [LocalVariable]s are abstract and have little todo with the
// actual frame layout!
//
// There are generally 4 different kinds of [LocalVariable]s:
//
// a) [LocalVariable]s referring to a parameter: The indices for those
// variables are assigned by the flow graph builder. Parameter n gets
// assigned the index (function.num_parameters - n - 1). I.e. the last
// parameter has index 1.
//
// b) [LocalVariable]s referring to actual variables in the body of a
// function (either from Dart code or specially injected ones. The
// indices of those variables are assigned by the scope builder
// from 0, -1, ... -(M-1) for M local variables.
//
// -> These variables participate in full SSA renaming and can therefore
// be used with [StoreLocalInstr]s (in addition to [LoadLocal]s).
//
// c) [LocalVariable]s referring to values on the expression stack. Those are
// assigned by the flow graph builder. The indices of those variables are
// assigned by the flow graph builder (it simulates the expression stack
// height), they go from -NumVariables - ExpressionHeight.
//
// -> These variables participate only partially in SSA renaming and can
// therefore only be used with [LoadLocalInstr]s and with
// [StoreLocalInstr]s **where no phis are necessary**.
//
// b) [LocalVariable]s referring to captured variables. Those are never
// loaded/stored directly. Their only purpose is to tell the flow graph
// builder how many parent links to follow and into which context index to
// store. The indices of those variables are assigned by the scope
// builder and they refer to indices into context objects.
class VariableIndex {
public:
static constexpr int kInvalidIndex = std::numeric_limits<int>::min();
explicit VariableIndex(int value = kInvalidIndex) : value_(value) {}
bool operator==(const VariableIndex& other) const {
return value_ == other.value_;
}
bool IsValid() const { return value_ != kInvalidIndex; }
int value() const { return value_; }
private:
int value_;
};
class LocalVariable : public ZoneAllocated {
public:
static constexpr intptr_t kNoKernelOffset = -1;
LocalVariable(TokenPosition declaration_pos,
TokenPosition token_pos,
const String& name,
const AbstractType& static_type,
intptr_t kernel_offset = kNoKernelOffset);
LocalVariable(TokenPosition declaration_pos,
TokenPosition token_pos,
const String& name,
const AbstractType& static_type,
intptr_t kernel_offset,
CompileType* inferred_type,
CompileType* inferred_arg_type = nullptr,
const Object* inferred_arg_value = nullptr)
: declaration_pos_(declaration_pos),
token_pos_(token_pos),
name_(name),
kernel_offset_(kernel_offset),
annotations_offset_(kNoKernelOffset),
owner_(nullptr),
static_type_(static_type),
inferred_type_(inferred_type),
inferred_arg_type_(inferred_arg_type),
inferred_arg_value_(inferred_arg_value),
covariance_mode_(kNotCovariant),
late_init_offset_(0),
type_check_mode_(kDoTypeCheck),
index_(),
is_awaiter_link_(IsAwaiterLink::kNotLink) {
DEBUG_ASSERT(static_type.IsNotTemporaryScopedHandle());
ASSERT(static_type.IsFinalized());
ASSERT(inferred_type != nullptr);
ASSERT(name.IsSymbol());
if (IsFilteredIdentifier(name)) {
set_invisible(true);
}
}
TokenPosition token_pos() const { return token_pos_; }
TokenPosition declaration_token_pos() const { return declaration_pos_; }
const String& name() const { return name_; }
intptr_t kernel_offset() const { return kernel_offset_; }
intptr_t annotations_offset() const { return annotations_offset_; }
LocalScope* owner() const { return owner_; }
void set_owner(LocalScope* owner) {
ASSERT(owner_ == nullptr);
owner_ = owner;
}
void set_annotations_offset(intptr_t offset) {
annotations_offset_ = offset;
is_awaiter_link_ = (offset == kNoKernelOffset) ? IsAwaiterLink::kNotLink
: IsAwaiterLink::kUnknown;
}
const AbstractType& static_type() const { return static_type_; }
CompileType* inferred_type() const { return inferred_type_; }
CompileType* inferred_arg_type() const { return inferred_arg_type_; }
const Object* inferred_arg_value() const { return inferred_arg_value_; }
bool is_final() const { return IsFinalBit::decode(bitfield_); }
void set_is_final() { bitfield_ = IsFinalBit::update(true, bitfield_); }
bool is_captured() const { return IsCapturedBit::decode(bitfield_); }
void set_is_captured() { bitfield_ = IsCapturedBit::update(true, bitfield_); }
bool ComputeIfIsAwaiterLink(const Library& library);
void set_is_awaiter_link(bool value) {
is_awaiter_link_ = value ? IsAwaiterLink::kLink : IsAwaiterLink::kNotLink;
}
// Variables marked as forced to stack are skipped and not captured by
// CaptureLocalVariables - which iterates scope chain between two scopes
// and indiscriminately marks all variables as captured.
// TODO(27590) remove the hardcoded list of names from CaptureLocalVariables
bool is_forced_stack() const { return IsForcedStackBit::decode(bitfield_); }
void set_is_forced_stack() {
bitfield_ = IsForcedStackBit::update(true, bitfield_);
}
bool is_late() const { return IsLateBit::decode(bitfield_); }
void set_is_late() { bitfield_ = IsLateBit::update(true, bitfield_); }
intptr_t late_init_offset() const { return late_init_offset_; }
void set_late_init_offset(intptr_t late_init_offset) {
late_init_offset_ = late_init_offset;
}
bool is_explicit_covariant_parameter() const {
return covariance_mode_ == kExplicit;
}
void set_is_explicit_covariant_parameter() { covariance_mode_ = kExplicit; }
bool needs_covariant_check_in_method() const {
return covariance_mode_ != kNotCovariant;
}
void set_needs_covariant_check_in_method() {
if (covariance_mode_ == kNotCovariant) {
covariance_mode_ = kImplicit;
}
}
enum TypeCheckMode {
kDoTypeCheck,
kSkipTypeCheck,
kTypeCheckedByCaller,
};
// Returns true if this local variable represents a parameter that needs type
// check when we enter the function.
bool needs_type_check() const { return (type_check_mode_ == kDoTypeCheck); }
// Returns true if this local variable represents a parameter which type is
// guaranteed by the caller.
bool was_type_checked_by_caller() const {
return type_check_mode_ == kTypeCheckedByCaller;
}
TypeCheckMode type_check_mode() const { return type_check_mode_; }
void set_type_check_mode(TypeCheckMode mode) { type_check_mode_ = mode; }
bool HasIndex() const { return index_.IsValid(); }
VariableIndex index() const {
ASSERT(HasIndex());
return index_;
}
// Assign an index to a local.
void set_index(VariableIndex index) {
ASSERT(index.IsValid());
index_ = index;
}
// Invisible variables are not included into LocalVarDescriptors
// and not displayed in the debugger.
bool is_invisible() const { return IsInvisibleBit::decode(bitfield_); }
void set_invisible(bool value) {
bitfield_ = IsInvisibleBit::update(value, bitfield_);
}
bool is_captured_parameter() const {
return IsCapturedParameterBit::decode(bitfield_);
}
void set_is_captured_parameter(bool value) {
bitfield_ = IsCapturedParameterBit::update(value, bitfield_);
}
bool Equals(const LocalVariable& other) const;
private:
// If true, this variable is readonly.
using IsFinalBit = BitField<uint32_t, bool, 0, 1>;
// If true, this variable lives in the context, otherwise
// in the stack frame.
using IsCapturedBit = BitField<uint32_t, bool, IsFinalBit::kNextBit, 1>;
using IsInvisibleBit = BitField<uint32_t, bool, IsCapturedBit::kNextBit, 1>;
using IsCapturedParameterBit =
BitField<uint32_t, bool, IsInvisibleBit::kNextBit, 1>;
using IsForcedStackBit =
BitField<uint32_t, bool, IsCapturedParameterBit::kNextBit, 1>;
using IsLateBit = BitField<uint32_t, bool, IsForcedStackBit ::kNextBit, 1>;
enum CovarianceMode {
kNotCovariant,
kImplicit,
kExplicit,
};
static constexpr int kUninitializedIndex = INT_MIN;
static bool IsFilteredIdentifier(const String& name);
const TokenPosition declaration_pos_;
const TokenPosition token_pos_;
const String& name_;
const intptr_t kernel_offset_;
intptr_t annotations_offset_;
LocalScope* owner_; // Local scope declaring this variable.
const AbstractType& static_type_; // Declaration type of local variable.
// Inferred variable type.
CompileType* const inferred_type_;
// nullptr or inferred type of incoming argument.
CompileType* const inferred_arg_type_;
// nullptr or inferred value of incoming argument.
const Object* const inferred_arg_value_;
uint32_t bitfield_ = 0;
CovarianceMode covariance_mode_;
intptr_t late_init_offset_;
TypeCheckMode type_check_mode_;
VariableIndex index_;
enum class IsAwaiterLink {
kUnknown,
kNotLink,
kLink,
};
IsAwaiterLink is_awaiter_link_;
friend class LocalScope;
DISALLOW_COPY_AND_ASSIGN(LocalVariable);
};
// Accumulates local variable descriptors while building
// LocalVarDescriptors object.
class LocalVarDescriptorsBuilder : public ValueObject {
public:
struct VarDesc {
const String* name;
UntaggedLocalVarDescriptors::VarInfo info;
};
LocalVarDescriptorsBuilder() : vars_(8) {}
// Add variable descriptor.
void Add(const VarDesc& var_desc) { vars_.Add(var_desc); }
// Add all variable descriptors from given [LocalVarDescriptors] object.
void AddAll(Zone* zone, const LocalVarDescriptors& var_descs);
// Record deopt-id -> context-level mappings, using ranges of deopt-ids with
// the same context-level. [context_level_array] contains (deopt_id,
// context_level) tuples.
void AddDeoptIdToContextLevelMappings(
ZoneGrowableArray<intptr_t>* context_level_array);
// Finish building LocalVarDescriptor object.
LocalVarDescriptorsPtr Done();
private:
GrowableArray<VarDesc> vars_;
};
class LocalScope : public ZoneAllocated {
public:
LocalScope(LocalScope* parent, int function_level, int loop_level);
LocalScope* parent() const { return parent_; }
LocalScope* child() const { return child_; }
LocalScope* sibling() const { return sibling_; }
int function_level() const { return function_level_; }
int loop_level() const { return loop_level_; }
// Check if this scope is nested within the passed in scope.
bool IsNestedWithin(LocalScope* scope) const;
// The context level is only set in a scope that is either the owner scope of
// a captured variable or that is the owner scope of a context.
bool HasContextLevel() const {
return context_level_ != kUninitializedContextLevel;
}
int context_level() const {
ASSERT(HasContextLevel());
return context_level_;
}
void set_context_level(int context_level) {
ASSERT(!HasContextLevel());
ASSERT(context_level != kUninitializedContextLevel);
context_level_ = context_level;
}
TokenPosition begin_token_pos() const { return begin_token_pos_; }
void set_begin_token_pos(TokenPosition value) { begin_token_pos_ = value; }
TokenPosition end_token_pos() const { return end_token_pos_; }
void set_end_token_pos(TokenPosition value) { end_token_pos_ = value; }
// Return the list of variables allocated in the context and belonging to this
// scope and to its children at the same loop level.
const GrowableArray<LocalVariable*>& context_variables() const {
return context_variables_;
}
const ZoneGrowableArray<const Slot*>& context_slots() const {
return *context_slots_;
}
// The number of variables allocated in the context and belonging to this
// scope and to its children at the same loop level.
int num_context_variables() const { return context_variables().length(); }
// Add a variable to the scope. Returns false if a variable with the
// same name and kernel offset is already present.
bool AddVariable(LocalVariable* variable);
// Add a variable to the scope as a context allocated variable and assigns
// it an index within the context. Does not check if the scope already
// contains this variable or a variable with the same name.
void AddContextVariable(LocalVariable* var);
// Insert a formal parameter variable to the scope at the given position,
// possibly in front of aliases already added with AddVariable.
// Returns false if a variable with the same name is already present.
bool InsertParameterAt(intptr_t pos, LocalVariable* parameter);
// Lookup a variable in this scope only.
LocalVariable* LocalLookupVariable(const String& name,
intptr_t kernel_offset) const;
// Lookup a variable in this scope and its parents. If the variable
// is found in a parent scope and 'test_only' is not true, we insert
// aliases of the variable in the current and intermediate scopes up to
// the declaration scope in order to detect "used before declared" errors.
// We mark a variable as 'captured' when applicable.
LocalVariable* LookupVariable(const String& name,
intptr_t kernel_offset,
bool test_only);
// Lookup a variable in this scope and its parents by name.
LocalVariable* LookupVariableByName(const String& name);
// Mark this variable as captured by this scope.
void CaptureVariable(LocalVariable* variable);
// Accessing the variables in the scope.
intptr_t num_variables() const { return variables_.length(); }
LocalVariable* VariableAt(intptr_t index) const {
ASSERT((index >= 0) && (index < variables_.length()));
return variables_[index];
}
// Count the captured variables belonging to outer scopes and referenced in
// this local scope.
int NumCapturedVariables() const;
// Allocate both captured and non-captured variables declared in this scope
// and in its children scopes of the same function level. Allocating means
// assigning a frame slot index or a context slot index.
// Parameters to be allocated in the frame must all appear in the top scope
// and not in its children (we do not yet handle register parameters).
// Locals must be listed after parameters in top scope and in its children.
// Two locals in different sibling scopes may share the same frame slot.
//
// Return the index of the next available frame slot.
VariableIndex AllocateVariables(const Function& function,
VariableIndex first_parameter_index,
int num_parameters,
VariableIndex first_local_index,
LocalScope* context_owner,
bool* found_captured_variables);
// Creates variable info for the scope and all its nested scopes.
// Must be called after AllocateVariables() has been called.
LocalVarDescriptorsPtr GetVarDescriptors(
const Function& func,
ZoneGrowableArray<intptr_t>* context_level_array);
// Create a ContextScope object describing all captured variables referenced
// from this scope and belonging to outer scopes.
ContextScopePtr PreserveOuterScope(const Function& function,
intptr_t current_context_level) const;
// Mark all local variables that are accessible from this scope up to
// top_scope (included) as captured unless they are marked as forced to stack.
void CaptureLocalVariables(LocalScope* top_scope);
// Creates a LocalScope representing the outer scope of a local function to be
// compiled. This outer scope contains the variables captured by the function
// as specified by the given ContextScope, which was created during the
// compilation of the enclosing function.
static LocalScope* RestoreOuterScope(const ContextScope& context_scope);
// Create a ContextScope object which will capture "this" for an implicit
// closure object.
static ContextScopePtr CreateImplicitClosureScope(const Function& func);
private:
// Allocate the variable in the current context, possibly updating the current
// context owner scope, if the variable is the first one to be allocated at
// this loop level.
// The variable may belong to this scope or to any of its children, but at the
// same loop level.
void AllocateContextVariable(LocalVariable* variable,
LocalScope** context_owner);
void CollectLocalVariables(LocalVarDescriptorsBuilder* vars,
int16_t* scope_id);
static constexpr int kUninitializedContextLevel = INT_MIN;
LocalScope* parent_;
LocalScope* child_;
LocalScope* sibling_;
int function_level_; // Reflects the nesting level of local functions.
int loop_level_; // Reflects the loop nesting level.
int context_level_; // Reflects the level of the runtime context.
TokenPosition begin_token_pos_; // Token index of beginning of scope.
TokenPosition end_token_pos_; // Token index of end of scope.
GrowableArray<LocalVariable*> variables_;
// List of variables allocated into the context which is owned by this scope,
// and their corresponding Slots.
GrowableArray<LocalVariable*> context_variables_;
ZoneGrowableArray<const Slot*>* context_slots_;
DISALLOW_COPY_AND_ASSIGN(LocalScope);
};
} // namespace dart
#endif // RUNTIME_VM_SCOPES_H_