Files
sdk/runtime/vm/compiler/frontend/bytecode_scope_builder.cc
T
Tess Strickland 787446213c [vm/compiler] Move argument shape (not type) checks out of closures.
This CL performs the following checks in the invoke field dispatcher for
dynamic closure calls when lazy dispatchers are enabled:

* The provided function type arguments vector (if any) has the correct
  length.

* No function type arguments should be provided if the closure has
  delayed type arguments.

* All required arguments (positional in all modes, named in appropriate
  null safety modes) have been provided by the caller.

* If there are optional positional arguments, an appropriate number
  has been provided.

* If there are optional named arguments, their names are valid.

Since the runtime already handles checking the argument shapes when lazy
dispatchers are disabled, these checks are now completely removed from
closure bodies in all cases. Thus, the only remaining checks in closure
bodies are the type checks performed by AssertSubtype and
AssertAssignable when lazy dispatchers are enabled.

Changes in the Flutter Gallery:

* ARM7, release: -3.61% instructions, -2.19% total
* ARM7, sizeopt: -3.62% instructions, -2.55% total
* ARM8, release: -3.66% instructions, -1.98% total
* ARM8, sizeopt: -3.65% instructions, -2.37% total

Most of these changes are already exercised by existing tests such as
(but not limited to):

* corelib{,_2}/dynamic_nosuchmethod_test
* language{,_2}/call/call_test
* language{,_2}/closure/tearoff_dynamic_test
* language{,_2}/generic/function_bounds_test
* language{,_2}/parameter/named_with_conversions_test
* language{,_2}/vm/no_such_args_error_message_vm_test

I've added one test to specifically check the interaction between
dynamic calls and required named parameters. There is some coverage in
other NNBD tests, but those are not directly focused on testing this
specifically.

Other changes:

* Adds initial cached ranges for certain BinarySmiOp and ShiftIntegerOp
  instructions when the RHS is a constant, to avoid false negatives for
  deoptimization and throw checks prior to range analysis.

* Adds new slots for various Function fields.

* Adds the ability to define unboxed native slots, which are always
  unboxed after retrieval even in unoptimized code. In the first
  iteration, the backend only handles loads from Uint32 unboxed native
  slots. Part of https://github.com/dart-lang/sdk/issues/42793.

* Removed the special handling for loading from non-nullable int fields
  in AOT compilation. Instead, their treatment is unified with the
  treatment of the new unboxed native fields, since the source field is
  always unboxed and the result of the load is also always unboxed, as
  code involving them is always optimized.

Bug: https://github.com/dart-lang/sdk/issues/40813
Change-Id: Ia02aa3e872c1fefd906fd67b55021ea1797556e4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155604
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
2020-08-17 08:58:08 +00:00

195 lines
7.2 KiB
C++

// Copyright (c) 2018, 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.
#include "vm/compiler/frontend/bytecode_scope_builder.h"
#include "vm/compiler/frontend/bytecode_reader.h"
namespace dart {
namespace kernel {
#define Z (zone_)
BytecodeScopeBuilder::BytecodeScopeBuilder(ParsedFunction* parsed_function)
: parsed_function_(parsed_function),
zone_(parsed_function->zone()),
scope_(nullptr) {}
void BytecodeScopeBuilder::BuildScopes() {
if (parsed_function_->scope() != nullptr) {
return; // Scopes are already built.
}
const Function& function = parsed_function_->function();
LocalScope* enclosing_scope = nullptr;
if (function.IsImplicitClosureFunction() && !function.is_static()) {
// Create artificial enclosing scope for the tear-off that contains
// captured receiver value. This ensure that AssertAssignable will correctly
// load instantiator type arguments if they are needed.
LocalVariable* receiver_variable =
MakeReceiverVariable(/* is_parameter = */ false);
receiver_variable->set_is_captured();
enclosing_scope = new (Z) LocalScope(NULL, 0, 0);
enclosing_scope->set_context_level(0);
enclosing_scope->AddVariable(receiver_variable);
enclosing_scope->AddContextVariable(receiver_variable);
}
scope_ = new (Z) LocalScope(enclosing_scope, 0, 0);
scope_->set_begin_token_pos(function.token_pos());
scope_->set_end_token_pos(function.end_token_pos());
// Add function type arguments variable before current context variable.
if ((function.IsGeneric() || function.HasGenericParent())) {
LocalVariable* type_args_var = MakeVariable(
Symbols::FunctionTypeArgumentsVar(), AbstractType::dynamic_type());
scope_->AddVariable(type_args_var);
parsed_function_->set_function_type_arguments(type_args_var);
}
bool needs_expr_temp = false;
if (parsed_function_->has_arg_desc_var()) {
needs_expr_temp = true;
scope_->AddVariable(parsed_function_->arg_desc_var());
}
LocalVariable* context_var = parsed_function_->current_context_var();
context_var->set_is_forced_stack();
scope_->AddVariable(context_var);
parsed_function_->set_scope(scope_);
switch (function.kind()) {
case FunctionLayout::kImplicitClosureFunction: {
ASSERT(function.NumImplicitParameters() == 1);
const auto& parent = Function::Handle(Z, function.parent_function());
const auto& target =
Function::Handle(Z, function.ImplicitClosureTarget(Z));
// For BuildGraphOfNoSuchMethodForwarder, since closures no longer
// require arg_desc_var in all cases.
if (target.IsNull() ||
(parent.num_fixed_parameters() != target.num_fixed_parameters())) {
needs_expr_temp = true;
}
LocalVariable* closure_parameter = MakeVariable(
Symbols::ClosureParameter(), AbstractType::dynamic_type());
closure_parameter->set_is_forced_stack();
scope_->InsertParameterAt(0, closure_parameter);
// Type check all parameters by default.
// This may be overridden with parameter flags in
// BytecodeReaderHelper::ParseForwarderFunction.
AddParameters(function, LocalVariable::kDoTypeCheck);
break;
}
case FunctionLayout::kImplicitGetter:
case FunctionLayout::kImplicitSetter: {
const bool is_setter = function.IsImplicitSetterFunction();
const bool is_method = !function.IsStaticFunction();
const Field& field = Field::Handle(Z, function.accessor_field());
intptr_t pos = 0;
if (is_method) {
MakeReceiverVariable(/* is_parameter = */ true);
++pos;
}
if (is_setter) {
LocalVariable* setter_value = MakeVariable(
Symbols::Value(),
AbstractType::ZoneHandle(Z, function.ParameterTypeAt(pos)));
scope_->InsertParameterAt(pos++, setter_value);
if (is_method) {
if (field.is_covariant()) {
setter_value->set_is_explicit_covariant_parameter();
} else {
const bool needs_type_check =
field.is_generic_covariant_impl() &&
kernel::ProcedureAttributesOf(field, Z).has_non_this_uses;
if (!needs_type_check) {
setter_value->set_type_check_mode(
LocalVariable::kTypeCheckedByCaller);
}
}
}
}
break;
}
case FunctionLayout::kImplicitStaticGetter: {
ASSERT(!IsStaticFieldGetterGeneratedAsInitializer(function, Z));
break;
}
case FunctionLayout::kDynamicInvocationForwarder: {
// Create [this] variable.
MakeReceiverVariable(/* is_parameter = */ true);
// Type check all parameters by default.
// This may be overridden with parameter flags in
// BytecodeReaderHelper::ParseForwarderFunction.
AddParameters(function, LocalVariable::kDoTypeCheck);
break;
}
case FunctionLayout::kMethodExtractor: {
// Add a receiver parameter. Though it is captured, we emit code to
// explicitly copy it to a fixed offset in a freshly-allocated context
// instead of using the generic code for regular functions.
// Therefore, it isn't necessary to mark it as captured here.
MakeReceiverVariable(/* is_parameter = */ true);
break;
}
default:
UNREACHABLE();
}
if (needs_expr_temp) {
scope_->AddVariable(parsed_function_->EnsureExpressionTemp());
}
if (parsed_function_->function().MayHaveUncheckedEntryPoint()) {
scope_->AddVariable(parsed_function_->EnsureEntryPointsTemp());
}
parsed_function_->AllocateVariables();
}
// TODO(alexmarkov): pass bitvectors of parameter covariance to set type
// check mode before AllocateVariables.
void BytecodeScopeBuilder::AddParameters(const Function& function,
LocalVariable::TypeCheckMode mode) {
for (intptr_t i = function.NumImplicitParameters(),
n = function.NumParameters();
i < n; ++i) {
// LocalVariable caches handles, so new handles are created for each
// parameter.
String& name = String::ZoneHandle(Z, function.ParameterNameAt(i));
AbstractType& type =
AbstractType::ZoneHandle(Z, function.ParameterTypeAt(i));
LocalVariable* variable = MakeVariable(name, type);
variable->set_type_check_mode(mode);
scope_->InsertParameterAt(i, variable);
}
}
LocalVariable* BytecodeScopeBuilder::MakeVariable(const String& name,
const AbstractType& type) {
return new (Z) LocalVariable(TokenPosition::kNoSource,
TokenPosition::kNoSource, name, type, nullptr);
}
LocalVariable* BytecodeScopeBuilder::MakeReceiverVariable(bool is_parameter) {
const auto& cls = Class::Handle(Z, parsed_function_->function().Owner());
const auto& type = Type::ZoneHandle(Z, cls.DeclarationType());
LocalVariable* receiver_variable = MakeVariable(Symbols::This(), type);
parsed_function_->set_receiver_var(receiver_variable);
if (is_parameter) {
scope_->InsertParameterAt(0, receiver_variable);
}
return receiver_variable;
}
} // namespace kernel
} // namespace dart