6378a26212
This CL fixes two bugs related to scoping: 1. Empty variable name lookup in outer functions failing. 2. Let expressions not pushing and popping contexts. Additionally, this CL introduces a `--print-scoping` debug flag to ease debugging scoping issues in the future. Variable lookup is now only using kernel offset, unless the offset is `kNoKernelOffset`. In that case, the names are used. All synthetic variables in the VM now use `kNoKernelOffset` as their offset. The let expressions not pushing contexts is fixed by pushing context. The context is only pushed if the scope captures variables. So this should not lead to performance regressions. (Any lacking pushing of scopes should have lead to crashes.) TEST=tests/ffi/regress_56412_2_test.dart TEST=tests/ffi/regress_56412_test.dart Closes: https://github.com/dart-lang/sdk/issues/56412 Change-Id: I85fc4a161b833df80ce8f2911d618aa2ac9797eb Cq-Include-Trybots: dart/try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-arm64-try,vm-fuchsia-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/380983 Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
477 lines
18 KiB
C++
477 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;
|
|
}
|
|
|
|
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;
|
|
|
|
void PrintTo(BaseTextBuffer* f,
|
|
const char* label = "variable",
|
|
int depth = 0,
|
|
const LocalScope* scope = nullptr) const;
|
|
const char* ToCString() 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 IsLateBit =
|
|
BitField<uint32_t, bool, IsCapturedParameterBit ::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;
|
|
|
|
// 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);
|
|
|
|
void PrintTo(BaseTextBuffer* f, int depth = 0) const;
|
|
const char* ToCString() const;
|
|
|
|
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_
|