[vm] Decouple flow graph building of implicit closure functions from kernel reading
Change-Id: I6bf5ad6ed76c363904ae1765d8303eff4a799363 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/95706 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
f44fecbe39
commit
f2a8e71ff9
@@ -422,531 +422,30 @@ Fragment StreamingFlowGraphBuilder::BuildInitializers(
|
||||
return instructions;
|
||||
}
|
||||
|
||||
// If no type arguments are passed to a generic function, we need to fill the
|
||||
// type arguments in with the default types stored on the TypeParameter nodes
|
||||
// in Kernel.
|
||||
Fragment StreamingFlowGraphBuilder::BuildDefaultTypeHandling(
|
||||
const Function& function,
|
||||
intptr_t type_parameters_offset) {
|
||||
if (function.IsGeneric()) {
|
||||
AlternativeReadingScope alt(&reader_);
|
||||
SetOffset(type_parameters_offset);
|
||||
intptr_t num_type_params = ReadListLength();
|
||||
ASSERT(num_type_params == function.NumTypeParameters());
|
||||
TypeArguments& default_types =
|
||||
TypeArguments::ZoneHandle(TypeArguments::New(num_type_params));
|
||||
for (intptr_t i = 0; i < num_type_params; ++i) {
|
||||
TypeParameterHelper helper(this);
|
||||
helper.ReadUntilExcludingAndSetJustRead(
|
||||
TypeParameterHelper::kDefaultType);
|
||||
if (ReadTag() == kSomething) {
|
||||
default_types.SetTypeAt(i, T.BuildType());
|
||||
} else {
|
||||
default_types.SetTypeAt(i, Object::dynamic_type());
|
||||
}
|
||||
helper.Finish();
|
||||
}
|
||||
default_types = default_types.Canonicalize();
|
||||
|
||||
if (!default_types.IsNull()) {
|
||||
Fragment then;
|
||||
Fragment otherwise;
|
||||
|
||||
otherwise += TranslateInstantiatedTypeArguments(default_types);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function()->function_type_arguments());
|
||||
otherwise += Drop();
|
||||
return B->TestAnyTypeArgs(then, otherwise);
|
||||
}
|
||||
}
|
||||
return Fragment();
|
||||
}
|
||||
|
||||
void StreamingFlowGraphBuilder::RecordUncheckedEntryPoint(
|
||||
FunctionEntryInstr* extra_entry) {
|
||||
// Closures always check all arguments on their checked entry-point, most
|
||||
// call-sites are unchecked, and they're inlined less often, so it's very
|
||||
// beneficial to build multiple entry-points for them. Regular methods however
|
||||
// have fewer checks to begin with since they have dynamic invocation
|
||||
// forwarders, so in AOT we implement a more conservative time-space tradeoff
|
||||
// by only building the unchecked entry-point when inlining. We should
|
||||
// reconsider this heuristic if we identify non-inlined type-checks in
|
||||
// hotspots of new benchmarks.
|
||||
if (!B->IsInlining() && (parsed_function()->function().IsClosureFunction() ||
|
||||
!FLAG_precompiled_mode)) {
|
||||
B->graph_entry_->set_unchecked_entry(extra_entry);
|
||||
} else if (B->InliningUncheckedEntry()) {
|
||||
B->graph_entry_->set_normal_entry(extra_entry);
|
||||
}
|
||||
}
|
||||
|
||||
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
|
||||
void StreamingFlowGraphBuilder::ReadDefaultFunctionTypeArguments(
|
||||
const Function& function) {
|
||||
const Function& parent = Function::ZoneHandle(Z, function.parent_function());
|
||||
const String& func_name = String::ZoneHandle(Z, parent.name());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
Function& target = Function::ZoneHandle(Z, owner.LookupFunction(func_name));
|
||||
|
||||
if (!target.IsNull() && (target.raw() != parent.raw())) {
|
||||
DEBUG_ASSERT(Isolate::Current()->HasAttemptedReload());
|
||||
if ((target.is_static() != parent.is_static()) ||
|
||||
(target.kind() != parent.kind())) {
|
||||
target = Function::null();
|
||||
}
|
||||
if (!function.IsGeneric()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.IsNull() ||
|
||||
(parent.num_fixed_parameters() != target.num_fixed_parameters())) {
|
||||
return BuildGraphOfNoSuchMethodForwarder(function, true,
|
||||
parent.is_static());
|
||||
}
|
||||
|
||||
// The prologue builder needs the default parameter values.
|
||||
SetupDefaultParameterValues();
|
||||
|
||||
flow_graph_builder_->graph_entry_ =
|
||||
new (Z) GraphEntryInstr(*parsed_function(), Compiler::kNoOSRDeoptId);
|
||||
|
||||
auto normal_entry = flow_graph_builder_->BuildFunctionEntry(
|
||||
flow_graph_builder_->graph_entry_);
|
||||
flow_graph_builder_->graph_entry_->set_normal_entry(normal_entry);
|
||||
|
||||
PrologueInfo prologue_info(-1, -1);
|
||||
BlockEntryInstr* instruction_cursor =
|
||||
flow_graph_builder_->BuildPrologue(normal_entry, &prologue_info);
|
||||
|
||||
const Fragment prologue =
|
||||
flow_graph_builder_->CheckStackOverflowInPrologue(function.token_pos());
|
||||
|
||||
AlternativeReadingScope alt(&reader_);
|
||||
FunctionNodeHelper function_node_helper(this);
|
||||
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters);
|
||||
|
||||
const Fragment default_type_handling =
|
||||
BuildDefaultTypeHandling(function, ReaderOffset());
|
||||
|
||||
const ProcedureAttributesMetadata parent_attrs =
|
||||
procedure_attributes_metadata_helper_.GetProcedureAttributes(
|
||||
parent.kernel_offset());
|
||||
|
||||
// We're going to throw away the explicit checks because the target will
|
||||
// always check them.
|
||||
Fragment implicit_checks;
|
||||
if (function.NeedsArgumentTypeChecks(I)) {
|
||||
Fragment explicit_checks_unused;
|
||||
if (target.is_static()) {
|
||||
// Tearoffs of static methods needs to perform arguments checks since
|
||||
// static methods they forward to don't do it themselves.
|
||||
AlternativeReadingScope _(&reader_);
|
||||
B->BuildArgumentTypeChecks(
|
||||
TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
||||
&explicit_checks_unused, &implicit_checks, nullptr);
|
||||
intptr_t num_type_params = ReadListLength();
|
||||
ASSERT(num_type_params == function.NumTypeParameters());
|
||||
TypeArguments& default_types =
|
||||
TypeArguments::Handle(Z, TypeArguments::New(num_type_params));
|
||||
for (intptr_t i = 0; i < num_type_params; ++i) {
|
||||
TypeParameterHelper helper(this);
|
||||
helper.ReadUntilExcludingAndSetJustRead(TypeParameterHelper::kDefaultType);
|
||||
if (ReadTag() == kSomething) {
|
||||
default_types.SetTypeAt(i, T.BuildType());
|
||||
} else {
|
||||
// Check if parent function was annotated with no-dynamic-invocations.
|
||||
if (MethodCanSkipTypeChecksForNonCovariantArguments(parent,
|
||||
parent_attrs)) {
|
||||
// If it was then we might need to build some checks in the
|
||||
// tear-off.
|
||||
AlternativeReadingScope _(&reader_);
|
||||
B->BuildArgumentTypeChecks(
|
||||
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds,
|
||||
&explicit_checks_unused, &implicit_checks, nullptr);
|
||||
}
|
||||
default_types.SetTypeAt(i, Object::dynamic_type());
|
||||
}
|
||||
helper.Finish();
|
||||
}
|
||||
|
||||
Fragment body;
|
||||
|
||||
function_node_helper.ReadUntilExcluding(
|
||||
FunctionNodeHelper::kPositionalParameters);
|
||||
|
||||
intptr_t type_args_len = 0;
|
||||
if (function.IsGeneric()) {
|
||||
type_args_len = function.NumTypeParameters();
|
||||
ASSERT(parsed_function()->function_type_arguments() != NULL);
|
||||
body += LoadLocal(parsed_function()->function_type_arguments());
|
||||
body += PushArgument();
|
||||
}
|
||||
|
||||
// Load all the arguments.
|
||||
if (!target.is_static()) {
|
||||
// The context has a fixed shape: a single variable which is the
|
||||
// closed-over receiver.
|
||||
body +=
|
||||
LoadLocal(parsed_function()->node_sequence()->scope()->VariableAt(0));
|
||||
body += LoadNativeField(Slot::Closure_context());
|
||||
body += LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
|
||||
body += PushArgument();
|
||||
}
|
||||
|
||||
// Positional.
|
||||
intptr_t positional_argument_count = ReadListLength();
|
||||
for (intptr_t i = 0; i < positional_argument_count; ++i) {
|
||||
body += LoadLocal(LookupVariable(
|
||||
ReaderOffset() + data_program_offset_)); // ith variable offset.
|
||||
body += PushArgument();
|
||||
SkipVariableDeclaration(); // read ith variable.
|
||||
}
|
||||
|
||||
// Named.
|
||||
intptr_t named_argument_count = ReadListLength();
|
||||
Array& argument_names = Array::ZoneHandle(Z);
|
||||
if (named_argument_count > 0) {
|
||||
argument_names = Array::New(named_argument_count, H.allocation_space());
|
||||
for (intptr_t i = 0; i < named_argument_count; ++i) {
|
||||
// ith variable offset.
|
||||
body += LoadLocal(LookupVariable(ReaderOffset() + data_program_offset_));
|
||||
body += PushArgument();
|
||||
|
||||
// read ith variable.
|
||||
VariableDeclarationHelper helper(this);
|
||||
helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd);
|
||||
|
||||
argument_names.SetAt(i, H.DartSymbolObfuscate(helper.name_index_));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward them to the parent.
|
||||
intptr_t argument_count = positional_argument_count + named_argument_count;
|
||||
if (!parent.is_static()) {
|
||||
++argument_count;
|
||||
}
|
||||
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
||||
argument_names, ICData::kNoRebind,
|
||||
/* result_type = */ NULL, type_args_len);
|
||||
|
||||
// Return the result.
|
||||
body += Return(function_node_helper.end_position_);
|
||||
|
||||
// Setup multiple entrypoints if useful.
|
||||
FunctionEntryInstr* extra_entry = nullptr;
|
||||
if (function.MayHaveUncheckedEntryPoint(I)) {
|
||||
// The prologue for a closure will always have context handling (e.g.
|
||||
// setting up the 'this_variable'), but we don't need it on the unchecked
|
||||
// entry because the only time we reference this is for loading the
|
||||
// receiver, which we fetch directly from the context.
|
||||
if (PrologueBuilder::PrologueSkippableOnUncheckedEntry(function)) {
|
||||
// Use separate entry points since we can skip almost everything on the
|
||||
// static entry.
|
||||
extra_entry = BuildSeparateUncheckedEntryPoint(
|
||||
/*normal_entry=*/instruction_cursor,
|
||||
/*normal_prologue=*/prologue + default_type_handling +
|
||||
implicit_checks,
|
||||
/*extra_prologue=*/
|
||||
B->CheckStackOverflowInPrologue(function.token_pos()),
|
||||
/*shared_prologue=*/Fragment(),
|
||||
/*body=*/body);
|
||||
} else {
|
||||
Fragment shared_prologue(normal_entry, instruction_cursor);
|
||||
shared_prologue += prologue;
|
||||
extra_entry = BuildSharedUncheckedEntryPoint(
|
||||
/*shared_prologue_linked_in=*/shared_prologue,
|
||||
/*skippable_checks=*/default_type_handling + implicit_checks,
|
||||
/*redefinitions_if_skipped=*/Fragment(),
|
||||
/*body=*/body);
|
||||
}
|
||||
RecordUncheckedEntryPoint(extra_entry);
|
||||
} else {
|
||||
Fragment function(instruction_cursor);
|
||||
function += prologue;
|
||||
function += default_type_handling;
|
||||
function += implicit_checks;
|
||||
function += body;
|
||||
}
|
||||
|
||||
return new (Z)
|
||||
FlowGraph(*parsed_function(), flow_graph_builder_->graph_entry_,
|
||||
flow_graph_builder_->last_used_block_id_, prologue_info);
|
||||
}
|
||||
|
||||
// If throw_no_such_method_error is set to true (defaults to false), an
|
||||
// instance of NoSuchMethodError is thrown. Otherwise, the instance
|
||||
// noSuchMethod is called.
|
||||
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
||||
const Function& function,
|
||||
bool is_implicit_closure_function,
|
||||
bool throw_no_such_method_error) {
|
||||
// The prologue builder needs the default parameter values.
|
||||
SetupDefaultParameterValues();
|
||||
|
||||
B->graph_entry_ =
|
||||
new (Z) GraphEntryInstr(*parsed_function(), Compiler::kNoOSRDeoptId);
|
||||
|
||||
auto normal_entry = B->BuildFunctionEntry(B->graph_entry_);
|
||||
B->graph_entry_->set_normal_entry(normal_entry);
|
||||
|
||||
PrologueInfo prologue_info(-1, -1);
|
||||
BlockEntryInstr* instruction_cursor =
|
||||
B->BuildPrologue(normal_entry, &prologue_info);
|
||||
|
||||
Fragment body(instruction_cursor);
|
||||
body += B->CheckStackOverflowInPrologue(function.token_pos());
|
||||
|
||||
// If we are inside the tearoff wrapper function (implicit closure), we need
|
||||
// to extract the receiver from the context. We just replace it directly on
|
||||
// the stack to simplify the rest of the code.
|
||||
if (is_implicit_closure_function && !function.is_static()) {
|
||||
if (parsed_function()->has_arg_desc_var()) {
|
||||
body += B->LoadArgDescriptor();
|
||||
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
||||
body += LoadLocal(parsed_function()->current_context_var());
|
||||
body += B->LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
|
||||
body += B->StoreFpRelativeSlot(
|
||||
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
||||
} else {
|
||||
body += LoadLocal(parsed_function()->current_context_var());
|
||||
body += B->LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
|
||||
body += B->StoreFpRelativeSlot(
|
||||
kWordSize * (compiler::target::frame_layout.param_end_from_fp +
|
||||
function.NumParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
FunctionNodeHelper function_node_helper(this);
|
||||
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters);
|
||||
|
||||
if (function.NeedsArgumentTypeChecks(I)) {
|
||||
AlternativeReadingScope _(&reader_);
|
||||
B->BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
||||
&body, &body, nullptr);
|
||||
}
|
||||
|
||||
function_node_helper.ReadUntilExcluding(
|
||||
FunctionNodeHelper::kPositionalParameters);
|
||||
|
||||
body += MakeTemp();
|
||||
LocalVariable* result = MakeTemporary();
|
||||
|
||||
// Do "++argument_count" if any type arguments were passed.
|
||||
LocalVariable* argument_count_var = parsed_function()->expression_temp_var();
|
||||
body += IntConstant(0);
|
||||
body += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
body += Drop();
|
||||
if (function.IsGeneric()) {
|
||||
Fragment then;
|
||||
Fragment otherwise;
|
||||
otherwise += IntConstant(1);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
otherwise += Drop();
|
||||
body += flow_graph_builder_->TestAnyTypeArgs(then, otherwise);
|
||||
}
|
||||
|
||||
if (function.HasOptionalParameters()) {
|
||||
body += B->LoadArgDescriptor();
|
||||
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
||||
} else {
|
||||
body += IntConstant(function.NumParameters());
|
||||
}
|
||||
body += LoadLocal(argument_count_var);
|
||||
body += B->SmiBinaryOp(Token::kADD, /* truncate= */ true);
|
||||
LocalVariable* argument_count = MakeTemporary();
|
||||
|
||||
// We are generating code like the following:
|
||||
//
|
||||
// var arguments = new Array<dynamic>(argument_count);
|
||||
//
|
||||
// int i = 0;
|
||||
// if (any type arguments are passed) {
|
||||
// arguments[0] = function_type_arguments;
|
||||
// ++i;
|
||||
// }
|
||||
//
|
||||
// for (; i < argument_count; ++i) {
|
||||
// arguments[i] = LoadFpRelativeSlot(
|
||||
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
||||
// }
|
||||
body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
|
||||
body += LoadLocal(argument_count);
|
||||
body += CreateArray();
|
||||
LocalVariable* arguments = MakeTemporary();
|
||||
|
||||
{
|
||||
// int i = 0
|
||||
LocalVariable* index = parsed_function()->expression_temp_var();
|
||||
body += IntConstant(0);
|
||||
body += StoreLocal(TokenPosition::kNoSource, index);
|
||||
body += Drop();
|
||||
|
||||
// if (any type arguments are passed) {
|
||||
// arguments[0] = function_type_arguments;
|
||||
// i = 1;
|
||||
// }
|
||||
if (function.IsGeneric()) {
|
||||
Fragment store;
|
||||
store += LoadLocal(arguments);
|
||||
store += IntConstant(0);
|
||||
store += LoadFunctionTypeArguments();
|
||||
store += StoreIndexed(kArrayCid);
|
||||
store += IntConstant(1);
|
||||
store += StoreLocal(TokenPosition::kNoSource, index);
|
||||
store += Drop();
|
||||
body += B->TestAnyTypeArgs(store, Fragment());
|
||||
}
|
||||
|
||||
TargetEntryInstr* body_entry;
|
||||
TargetEntryInstr* loop_exit;
|
||||
|
||||
Fragment condition;
|
||||
// i < argument_count
|
||||
condition += LoadLocal(index);
|
||||
condition += LoadLocal(argument_count);
|
||||
condition += B->SmiRelationalOp(Token::kLT);
|
||||
condition += BranchIfTrue(&body_entry, &loop_exit, /*negate=*/false);
|
||||
|
||||
Fragment loop_body(body_entry);
|
||||
|
||||
// arguments[i] = LoadFpRelativeSlot(
|
||||
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
||||
loop_body += LoadLocal(arguments);
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += LoadLocal(argument_count);
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += B->SmiBinaryOp(Token::kSUB, /*truncate=*/true);
|
||||
loop_body += B->LoadFpRelativeSlot(
|
||||
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
||||
loop_body += StoreIndexed(kArrayCid);
|
||||
|
||||
// ++i
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += IntConstant(1);
|
||||
loop_body += B->SmiBinaryOp(Token::kADD, /*truncate=*/true);
|
||||
loop_body += StoreLocal(TokenPosition::kNoSource, index);
|
||||
loop_body += Drop();
|
||||
|
||||
JoinEntryInstr* join = BuildJoinEntry();
|
||||
loop_body += Goto(join);
|
||||
|
||||
Fragment loop(join);
|
||||
loop += condition;
|
||||
|
||||
Instruction* entry =
|
||||
new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId());
|
||||
body += Fragment(entry, loop_exit);
|
||||
}
|
||||
|
||||
// Load receiver.
|
||||
if (is_implicit_closure_function) {
|
||||
if (throw_no_such_method_error) {
|
||||
const Function& parent =
|
||||
Function::ZoneHandle(Z, function.parent_function());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
AbstractType& type = AbstractType::ZoneHandle(Z);
|
||||
type ^= Type::New(owner, TypeArguments::Handle(Z), owner.token_pos(),
|
||||
Heap::kOld);
|
||||
type ^= ClassFinalizer::FinalizeType(owner, type);
|
||||
body += Constant(type);
|
||||
} else {
|
||||
body += LoadLocal(parsed_function()->current_context_var());
|
||||
body += B->LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
|
||||
}
|
||||
} else {
|
||||
LocalScope* scope = parsed_function()->node_sequence()->scope();
|
||||
body += LoadLocal(scope->VariableAt(0));
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
body += Constant(String::ZoneHandle(Z, function.name()));
|
||||
body += PushArgument();
|
||||
|
||||
if (!parsed_function()->has_arg_desc_var()) {
|
||||
// If there is no variable for the arguments descriptor (this function's
|
||||
// signature doesn't require it), then we need to create one.
|
||||
Array& args_desc = Array::ZoneHandle(
|
||||
Z, ArgumentsDescriptor::New(0, function.NumParameters()));
|
||||
body += Constant(args_desc);
|
||||
} else {
|
||||
body += B->LoadArgDescriptor();
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
body += LoadLocal(arguments);
|
||||
body += PushArgument();
|
||||
|
||||
if (throw_no_such_method_error) {
|
||||
const Function& parent =
|
||||
Function::ZoneHandle(Z, function.parent_function());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
InvocationMirror::Level im_level = owner.IsTopLevel()
|
||||
? InvocationMirror::kTopLevel
|
||||
: InvocationMirror::kStatic;
|
||||
InvocationMirror::Kind im_kind;
|
||||
if (function.IsImplicitGetterFunction() || function.IsGetterFunction()) {
|
||||
im_kind = InvocationMirror::kGetter;
|
||||
} else if (function.IsImplicitSetterFunction() ||
|
||||
function.IsSetterFunction()) {
|
||||
im_kind = InvocationMirror::kSetter;
|
||||
} else {
|
||||
im_kind = InvocationMirror::kMethod;
|
||||
}
|
||||
body += IntConstant(InvocationMirror::EncodeType(im_level, im_kind));
|
||||
} else {
|
||||
body += NullConstant();
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
// Push the number of delayed type arguments.
|
||||
if (function.IsClosureFunction()) {
|
||||
LocalVariable* closure =
|
||||
parsed_function()->node_sequence()->scope()->VariableAt(0);
|
||||
Fragment then;
|
||||
then += IntConstant(function.NumTypeParameters());
|
||||
then += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
then += Drop();
|
||||
Fragment otherwise;
|
||||
otherwise += IntConstant(0);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
otherwise += Drop();
|
||||
body += B->TestDelayedTypeArgs(closure, then, otherwise);
|
||||
body += LoadLocal(argument_count_var);
|
||||
} else {
|
||||
body += IntConstant(0);
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
const Class& mirror_class =
|
||||
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
|
||||
ASSERT(!mirror_class.IsNull());
|
||||
const Function& allocation_function = Function::ZoneHandle(
|
||||
Z, mirror_class.LookupStaticFunction(Library::PrivateCoreLibName(
|
||||
Symbols::AllocateInvocationMirrorForClosure())));
|
||||
ASSERT(!allocation_function.IsNull());
|
||||
body += StaticCall(TokenPosition::kMinSource, allocation_function,
|
||||
/* argument_count = */ 5, ICData::kStatic);
|
||||
body += PushArgument(); // For the call to noSuchMethod.
|
||||
|
||||
if (throw_no_such_method_error) {
|
||||
const Class& klass = Class::ZoneHandle(
|
||||
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
|
||||
ASSERT(!klass.IsNull());
|
||||
const Function& throw_function = Function::ZoneHandle(
|
||||
Z,
|
||||
klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewInvocation()));
|
||||
ASSERT(!throw_function.IsNull());
|
||||
body += StaticCall(TokenPosition::kNoSource, throw_function, 2,
|
||||
ICData::kStatic);
|
||||
} else {
|
||||
body += InstanceCall(TokenPosition::kNoSource, Symbols::NoSuchMethod(),
|
||||
Token::kILLEGAL, 2, 1);
|
||||
}
|
||||
body += StoreLocal(TokenPosition::kNoSource, result);
|
||||
body += Drop();
|
||||
|
||||
body += Drop(); // arguments
|
||||
body += Drop(); // argument count
|
||||
|
||||
AbstractType& return_type = AbstractType::Handle(function.result_type());
|
||||
if (!return_type.IsDynamicType() && !return_type.IsVoidType() &&
|
||||
!return_type.IsObjectType()) {
|
||||
body += flow_graph_builder_->AssertAssignable(
|
||||
TokenPosition::kNoSource, return_type, Symbols::Empty());
|
||||
}
|
||||
body += Return(TokenPosition::kNoSource);
|
||||
|
||||
return new (Z) FlowGraph(*parsed_function(), B->graph_entry_,
|
||||
B->last_used_block_id_, prologue_info);
|
||||
default_types = default_types.Canonicalize();
|
||||
parsed_function()->SetDefaultFunctionTypeArguments(default_types);
|
||||
}
|
||||
|
||||
Fragment StreamingFlowGraphBuilder::PushAllArguments(PushedArguments* pushed) {
|
||||
@@ -1011,6 +510,8 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder() {
|
||||
|
||||
// The prologue builder needs the default parameter values.
|
||||
SetupDefaultParameterValues();
|
||||
// BuildDefaultTypeHandling needs default function type arguments.
|
||||
ReadDefaultFunctionTypeArguments(dart_function);
|
||||
|
||||
B->graph_entry_ = new (Z) GraphEntryInstr(*parsed_function(), B->osr_id_);
|
||||
|
||||
@@ -1050,7 +551,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder() {
|
||||
|
||||
// Even if the caller did not pass argument vector we would still
|
||||
// call the target with instantiate-to-bounds type arguments.
|
||||
body += BuildDefaultTypeHandling(dart_function, type_parameters_offset);
|
||||
body += B->BuildDefaultTypeHandling(dart_function);
|
||||
|
||||
String& name = String::Handle(Z, dart_function.name());
|
||||
name = Function::DemangleDynamicInvocationForwarderName(name);
|
||||
@@ -1164,10 +665,8 @@ Fragment StreamingFlowGraphBuilder::SetAsyncStackTrace(
|
||||
}
|
||||
|
||||
Fragment StreamingFlowGraphBuilder::TypeArgumentsHandling(
|
||||
const Function& dart_function,
|
||||
intptr_t type_parameters_offset) {
|
||||
Fragment prologue =
|
||||
BuildDefaultTypeHandling(dart_function, type_parameters_offset);
|
||||
const Function& dart_function) {
|
||||
Fragment prologue = B->BuildDefaultTypeHandling(dart_function);
|
||||
|
||||
if (dart_function.IsClosureFunction() &&
|
||||
dart_function.NumParentTypeParameters() > 0) {
|
||||
@@ -1448,134 +947,7 @@ Fragment StreamingFlowGraphBuilder::BuildFirstTimePrologue(
|
||||
return F;
|
||||
}
|
||||
|
||||
// Pop the index of the current entry-point off the stack. If there is any
|
||||
// entrypoint-tracing hook registered in a pragma for the function, it is called
|
||||
// with the name of the current function and the current entry-point index.
|
||||
Fragment StreamingFlowGraphBuilder::BuildEntryPointsIntrospection() {
|
||||
if (!FLAG_enable_testing_pragmas) return Drop();
|
||||
|
||||
auto& function = Function::Handle(Z, parsed_function()->function().raw());
|
||||
|
||||
if (function.IsImplicitClosureFunction()) {
|
||||
const auto& parent = Function::Handle(Z, function.parent_function());
|
||||
const auto& func_name = String::Handle(Z, parent.name());
|
||||
const auto& owner = Class::Handle(Z, parent.Owner());
|
||||
function = owner.LookupFunction(func_name);
|
||||
}
|
||||
|
||||
auto& tmp = Object::Handle(Z);
|
||||
tmp = function.Owner();
|
||||
tmp = Class::Cast(tmp).library();
|
||||
auto& library = Library::Cast(tmp);
|
||||
|
||||
Object& options = Object::Handle(Z);
|
||||
if (!library.FindPragma(H.thread(), function, Symbols::vm_trace_entrypoints(),
|
||||
&options) ||
|
||||
options.IsNull() || !options.IsClosure()) {
|
||||
return Drop();
|
||||
}
|
||||
auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw());
|
||||
LocalVariable* entry_point_num = MakeTemporary();
|
||||
|
||||
auto& function_name = String::ZoneHandle(
|
||||
Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld));
|
||||
if (parsed_function()->function().IsImplicitClosureFunction()) {
|
||||
function_name = String::Concat(
|
||||
function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)),
|
||||
Heap::kOld);
|
||||
}
|
||||
|
||||
Fragment call_hook;
|
||||
call_hook += Constant(closure);
|
||||
call_hook += PushArgument();
|
||||
call_hook += Constant(function_name);
|
||||
call_hook += PushArgument();
|
||||
call_hook += LoadLocal(entry_point_num);
|
||||
call_hook += PushArgument();
|
||||
call_hook += Constant(Function::ZoneHandle(Z, closure.function()));
|
||||
call_hook += B->ClosureCall(TokenPosition::kNoSource,
|
||||
/*type_args_len=*/0, /*argument_count=*/3,
|
||||
/*argument_names=*/Array::ZoneHandle(Z));
|
||||
call_hook += Drop(); // result of closure call
|
||||
call_hook += Drop(); // entrypoint number
|
||||
return call_hook;
|
||||
}
|
||||
|
||||
FunctionEntryInstr* StreamingFlowGraphBuilder::BuildSharedUncheckedEntryPoint(
|
||||
Fragment shared_prologue_linked_in,
|
||||
Fragment skippable_checks,
|
||||
Fragment redefinitions_if_skipped,
|
||||
Fragment body) {
|
||||
ASSERT(shared_prologue_linked_in.entry == B->graph_entry_->normal_entry());
|
||||
ASSERT(parsed_function()->has_entry_points_temp_var());
|
||||
Instruction* prologue_start = shared_prologue_linked_in.entry->next();
|
||||
|
||||
auto* join_entry = B->BuildJoinEntry();
|
||||
|
||||
Fragment normal_entry(shared_prologue_linked_in.entry);
|
||||
normal_entry += IntConstant(UncheckedEntryPointStyle::kNone);
|
||||
normal_entry += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function()->entry_points_temp_var());
|
||||
normal_entry += Drop();
|
||||
normal_entry += Goto(join_entry);
|
||||
|
||||
auto* extra_target_entry = B->BuildFunctionEntry(B->graph_entry_);
|
||||
Fragment extra_entry(extra_target_entry);
|
||||
extra_entry += IntConstant(UncheckedEntryPointStyle::kSharedWithVariable);
|
||||
extra_entry += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function()->entry_points_temp_var());
|
||||
extra_entry += Drop();
|
||||
extra_entry += Goto(join_entry);
|
||||
|
||||
join_entry->LinkTo(prologue_start);
|
||||
|
||||
TargetEntryInstr *do_checks, *skip_checks;
|
||||
shared_prologue_linked_in +=
|
||||
LoadLocal(parsed_function()->entry_points_temp_var());
|
||||
shared_prologue_linked_in += BuildEntryPointsIntrospection();
|
||||
shared_prologue_linked_in +=
|
||||
LoadLocal(parsed_function()->entry_points_temp_var());
|
||||
shared_prologue_linked_in +=
|
||||
IntConstant(UncheckedEntryPointStyle::kSharedWithVariable);
|
||||
shared_prologue_linked_in +=
|
||||
BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false);
|
||||
|
||||
JoinEntryInstr* rest_entry = B->BuildJoinEntry();
|
||||
|
||||
Fragment(do_checks) + skippable_checks + Goto(rest_entry);
|
||||
Fragment(skip_checks) + redefinitions_if_skipped + Goto(rest_entry);
|
||||
Fragment(rest_entry) + body;
|
||||
|
||||
return extra_target_entry;
|
||||
}
|
||||
|
||||
FunctionEntryInstr* StreamingFlowGraphBuilder::BuildSeparateUncheckedEntryPoint(
|
||||
BlockEntryInstr* normal_entry,
|
||||
Fragment normal_prologue,
|
||||
Fragment extra_prologue,
|
||||
Fragment shared_prologue,
|
||||
Fragment body) {
|
||||
auto* join_entry = BuildJoinEntry();
|
||||
auto* extra_entry = B->BuildFunctionEntry(B->graph_entry_);
|
||||
|
||||
Fragment normal(normal_entry);
|
||||
normal += IntConstant(UncheckedEntryPointStyle::kNone);
|
||||
normal += BuildEntryPointsIntrospection();
|
||||
normal += normal_prologue;
|
||||
normal += Goto(join_entry);
|
||||
|
||||
Fragment extra(extra_entry);
|
||||
extra += IntConstant(UncheckedEntryPointStyle::kSeparate);
|
||||
extra += BuildEntryPointsIntrospection();
|
||||
extra += extra_prologue;
|
||||
extra += Goto(join_entry);
|
||||
|
||||
Fragment(join_entry) + shared_prologue + body;
|
||||
return extra_entry;
|
||||
}
|
||||
|
||||
StreamingFlowGraphBuilder::UncheckedEntryPointStyle
|
||||
StreamingFlowGraphBuilder::ChooseEntryPointStyle(
|
||||
UncheckedEntryPointStyle StreamingFlowGraphBuilder::ChooseEntryPointStyle(
|
||||
const Function& dart_function,
|
||||
const Fragment& implicit_type_checks,
|
||||
const Fragment& first_time_prologue,
|
||||
@@ -1611,8 +983,13 @@ StreamingFlowGraphBuilder::ChooseEntryPointStyle(
|
||||
|
||||
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
bool is_constructor) {
|
||||
const Function& dart_function = parsed_function()->function();
|
||||
|
||||
// The prologue builder needs the default parameter values.
|
||||
SetupDefaultParameterValues();
|
||||
// TypeArgumentsHandling / BuildDefaultTypeHandling needs
|
||||
// default function type arguments.
|
||||
ReadDefaultFunctionTypeArguments(dart_function);
|
||||
|
||||
intptr_t type_parameters_offset = 0;
|
||||
LocalVariable* first_parameter = nullptr;
|
||||
@@ -1633,8 +1010,6 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
token_position = function_node_helper.position_;
|
||||
}
|
||||
|
||||
const Function& dart_function = parsed_function()->function();
|
||||
|
||||
auto graph_entry = flow_graph_builder_->graph_entry_ =
|
||||
new (Z) GraphEntryInstr(*parsed_function(), flow_graph_builder_->osr_id_);
|
||||
|
||||
@@ -1658,8 +1033,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
// TODO(#34162): We can remove the default type handling (and
|
||||
// shorten the prologue type handling sequence) for non-dynamic invocations of
|
||||
// regular methods.
|
||||
const Fragment type_args_handling =
|
||||
TypeArgumentsHandling(dart_function, type_parameters_offset);
|
||||
const Fragment type_args_handling = TypeArgumentsHandling(dart_function);
|
||||
|
||||
Fragment explicit_type_checks;
|
||||
Fragment implicit_type_checks;
|
||||
@@ -1693,7 +1067,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
const Fragment prologue_copy = BuildEveryTimePrologue(
|
||||
dart_function, token_position, type_parameters_offset);
|
||||
|
||||
extra_entry = BuildSeparateUncheckedEntryPoint(
|
||||
extra_entry = B->BuildSeparateUncheckedEntryPoint(
|
||||
normal_entry,
|
||||
/*normal_prologue=*/every_time_prologue + implicit_type_checks,
|
||||
/*extra_prologue=*/prologue_copy,
|
||||
@@ -1707,7 +1081,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
prologue += first_time_prologue;
|
||||
prologue += type_args_handling;
|
||||
prologue += explicit_type_checks;
|
||||
extra_entry = BuildSharedUncheckedEntryPoint(
|
||||
extra_entry = B->BuildSharedUncheckedEntryPoint(
|
||||
/*shared_prologue_linked_in=*/prologue,
|
||||
/*skippable_checks=*/implicit_type_checks,
|
||||
/*redefinitions_if_skipped=*/implicit_redefinitions,
|
||||
@@ -1716,7 +1090,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
||||
}
|
||||
}
|
||||
if (extra_entry != nullptr) {
|
||||
RecordUncheckedEntryPoint(extra_entry);
|
||||
B->RecordUncheckedEntryPoint(extra_entry);
|
||||
}
|
||||
} else {
|
||||
// If the function's body contains any yield points, build switch statement
|
||||
@@ -1824,15 +1198,8 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph() {
|
||||
|
||||
switch (function.kind()) {
|
||||
case RawFunction::kRegularFunction:
|
||||
case RawFunction::kImplicitClosureFunction:
|
||||
case RawFunction::kGetterFunction:
|
||||
case RawFunction::kSetterFunction: {
|
||||
ReadUntilFunctionNode();
|
||||
if (function.IsImplicitClosureFunction()) {
|
||||
return BuildGraphOfImplicitClosureFunction(function);
|
||||
}
|
||||
FALL_THROUGH;
|
||||
}
|
||||
case RawFunction::kSetterFunction:
|
||||
case RawFunction::kClosureFunction: {
|
||||
ReadUntilFunctionNode();
|
||||
return BuildGraphOfFunction(false);
|
||||
@@ -1861,6 +1228,11 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph() {
|
||||
return flow_graph_builder_->BuildGraphOfNoSuchMethodDispatcher(function);
|
||||
case RawFunction::kInvokeFieldDispatcher:
|
||||
return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function);
|
||||
case RawFunction::kImplicitClosureFunction:
|
||||
ReadUntilFunctionNode();
|
||||
SetupDefaultParameterValues();
|
||||
ReadDefaultFunctionTypeArguments(function);
|
||||
return flow_graph_builder_->BuildGraphOfImplicitClosureFunction(function);
|
||||
case RawFunction::kSignatureFunction:
|
||||
case RawFunction::kIrregexpFunction:
|
||||
case RawFunction::kFfiTrampoline:
|
||||
|
||||
@@ -59,48 +59,15 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
|
||||
FlowGraph* BuildGraphOfFieldInitializer();
|
||||
FlowGraph* BuildGraphOfFieldAccessor(LocalVariable* setter_value);
|
||||
void SetupDefaultParameterValues();
|
||||
void ReadDefaultFunctionTypeArguments(const Function& function);
|
||||
Fragment BuildFieldInitializer(NameIndex canonical_name);
|
||||
Fragment BuildInitializers(const Class& parent_class);
|
||||
FlowGraph* BuildGraphOfImplicitClosureFunction(const Function& function);
|
||||
FlowGraph* BuildGraphOfFunction(bool constructor);
|
||||
FlowGraph* BuildGraphOfDynamicInvocationForwarder();
|
||||
FlowGraph* BuildGraphOfNoSuchMethodForwarder(
|
||||
const Function& function,
|
||||
bool is_implicit_closure_function,
|
||||
bool throw_no_such_method_error = false);
|
||||
|
||||
Fragment BuildExpression(TokenPosition* position = NULL);
|
||||
Fragment BuildStatement();
|
||||
|
||||
// Indicates which form of the unchecked entrypoint we are compiling.
|
||||
//
|
||||
// kNone:
|
||||
//
|
||||
// There is no unchecked entrypoint: the unchecked entry is set to NULL in
|
||||
// the 'GraphEntryInstr'.
|
||||
//
|
||||
// kSeparate:
|
||||
//
|
||||
// The normal and unchecked entrypoint each point to their own versions of
|
||||
// the prologue, containing exactly those checks which need to be performed
|
||||
// on either side. Both sides jump directly to the body after performing
|
||||
// their prologue.
|
||||
//
|
||||
// kSharedWithVariable:
|
||||
//
|
||||
// A temporary variable is allocated and initialized to 0 on normal entry
|
||||
// and 2 on unchecked entry. Code which should be ommitted on the unchecked
|
||||
// entrypoint is made conditional on this variable being equal to 0.
|
||||
//
|
||||
struct UncheckedEntryPointStyle_ {
|
||||
enum Style {
|
||||
kNone = 0,
|
||||
kSeparate = 1,
|
||||
kSharedWithVariable = 2,
|
||||
};
|
||||
};
|
||||
typedef UncheckedEntryPointStyle_::Style UncheckedEntryPointStyle;
|
||||
|
||||
// Kernel offset:
|
||||
// start of function expression -> end of function body statement
|
||||
Fragment BuildFunctionBody(const Function& dart_function,
|
||||
@@ -121,27 +88,13 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
|
||||
Fragment SetupCapturedParameters(const Function& dart_function);
|
||||
Fragment ShortcutForUserDefinedEquals(const Function& dart_function,
|
||||
LocalVariable* first_parameter);
|
||||
Fragment TypeArgumentsHandling(const Function& dart_function,
|
||||
intptr_t type_parameters_offset);
|
||||
Fragment TypeArgumentsHandling(const Function& dart_function);
|
||||
void CheckArgumentTypesAsNecessary(const Function& dart_function,
|
||||
intptr_t type_parameters_offset,
|
||||
Fragment* explicit_checks,
|
||||
Fragment* implicit_checks,
|
||||
Fragment* implicit_redefinitions);
|
||||
Fragment CompleteBodyWithYieldContinuations(Fragment body);
|
||||
FunctionEntryInstr* BuildSeparateUncheckedEntryPoint(
|
||||
BlockEntryInstr* normal_entry,
|
||||
Fragment normal_prologue,
|
||||
Fragment extra_prologue,
|
||||
Fragment shared_prologue,
|
||||
Fragment body);
|
||||
FunctionEntryInstr* BuildSharedUncheckedEntryPoint(
|
||||
Fragment prologue_from_normal_entry,
|
||||
Fragment skippable_checks,
|
||||
Fragment redefinitions_if_skipped,
|
||||
Fragment body);
|
||||
|
||||
Fragment BuildEntryPointsIntrospection();
|
||||
|
||||
static UncheckedEntryPointStyle ChooseEntryPointStyle(
|
||||
const Function& dart_function,
|
||||
@@ -150,8 +103,6 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
|
||||
const Fragment& every_time_prologue,
|
||||
const Fragment& type_args_handling);
|
||||
|
||||
void RecordUncheckedEntryPoint(FunctionEntryInstr* extra_entry);
|
||||
|
||||
void loop_depth_inc();
|
||||
void loop_depth_dec();
|
||||
intptr_t for_in_depth();
|
||||
@@ -242,10 +193,6 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
|
||||
bool use_unchecked_entry = false,
|
||||
const CallSiteAttributesMetadata* call_site_attrs = nullptr);
|
||||
|
||||
// Does not move the cursor.
|
||||
Fragment BuildDefaultTypeHandling(const Function& function,
|
||||
intptr_t type_parameters_offset);
|
||||
|
||||
struct PushedArguments {
|
||||
intptr_t type_args_len;
|
||||
intptr_t argument_count;
|
||||
|
||||
@@ -1389,7 +1389,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
|
||||
ASSERT(!owner.IsNull());
|
||||
const String& field_name = String::Handle(Z, function.name());
|
||||
const String& getter_name = String::ZoneHandle(
|
||||
Z, Symbols::New(H.thread(),
|
||||
Z, Symbols::New(thread_,
|
||||
String::Handle(Z, Field::GetterSymbol(field_name))));
|
||||
|
||||
// Determine if this is `class Closure { get call => this; }`
|
||||
@@ -1488,6 +1488,609 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
|
||||
prologue_info);
|
||||
}
|
||||
|
||||
FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
||||
const Function& function,
|
||||
bool is_implicit_closure_function,
|
||||
bool throw_no_such_method_error) {
|
||||
graph_entry_ =
|
||||
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
|
||||
|
||||
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
||||
graph_entry_->set_normal_entry(normal_entry);
|
||||
|
||||
PrologueInfo prologue_info(-1, -1);
|
||||
BlockEntryInstr* instruction_cursor =
|
||||
BuildPrologue(normal_entry, &prologue_info);
|
||||
|
||||
Fragment body(instruction_cursor);
|
||||
body += CheckStackOverflowInPrologue(function.token_pos());
|
||||
|
||||
// If we are inside the tearoff wrapper function (implicit closure), we need
|
||||
// to extract the receiver from the context. We just replace it directly on
|
||||
// the stack to simplify the rest of the code.
|
||||
if (is_implicit_closure_function && !function.is_static()) {
|
||||
if (parsed_function_->has_arg_desc_var()) {
|
||||
body += LoadArgDescriptor();
|
||||
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
||||
body += LoadLocal(parsed_function_->current_context_var());
|
||||
body += LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
|
||||
body += StoreFpRelativeSlot(
|
||||
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
||||
} else {
|
||||
body += LoadLocal(parsed_function_->current_context_var());
|
||||
body += LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
|
||||
body += StoreFpRelativeSlot(
|
||||
kWordSize * (compiler::target::frame_layout.param_end_from_fp +
|
||||
function.NumParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
if (function.NeedsArgumentTypeChecks(I)) {
|
||||
BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
||||
&body, &body, nullptr);
|
||||
}
|
||||
|
||||
body += MakeTemp();
|
||||
LocalVariable* result = MakeTemporary();
|
||||
|
||||
// Do "++argument_count" if any type arguments were passed.
|
||||
LocalVariable* argument_count_var = parsed_function_->expression_temp_var();
|
||||
body += IntConstant(0);
|
||||
body += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
body += Drop();
|
||||
if (function.IsGeneric()) {
|
||||
Fragment then;
|
||||
Fragment otherwise;
|
||||
otherwise += IntConstant(1);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
otherwise += Drop();
|
||||
body += TestAnyTypeArgs(then, otherwise);
|
||||
}
|
||||
|
||||
if (function.HasOptionalParameters()) {
|
||||
body += LoadArgDescriptor();
|
||||
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
||||
} else {
|
||||
body += IntConstant(function.NumParameters());
|
||||
}
|
||||
body += LoadLocal(argument_count_var);
|
||||
body += SmiBinaryOp(Token::kADD, /* truncate= */ true);
|
||||
LocalVariable* argument_count = MakeTemporary();
|
||||
|
||||
// We are generating code like the following:
|
||||
//
|
||||
// var arguments = new Array<dynamic>(argument_count);
|
||||
//
|
||||
// int i = 0;
|
||||
// if (any type arguments are passed) {
|
||||
// arguments[0] = function_type_arguments;
|
||||
// ++i;
|
||||
// }
|
||||
//
|
||||
// for (; i < argument_count; ++i) {
|
||||
// arguments[i] = LoadFpRelativeSlot(
|
||||
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
||||
// }
|
||||
body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
|
||||
body += LoadLocal(argument_count);
|
||||
body += CreateArray();
|
||||
LocalVariable* arguments = MakeTemporary();
|
||||
|
||||
{
|
||||
// int i = 0
|
||||
LocalVariable* index = parsed_function_->expression_temp_var();
|
||||
body += IntConstant(0);
|
||||
body += StoreLocal(TokenPosition::kNoSource, index);
|
||||
body += Drop();
|
||||
|
||||
// if (any type arguments are passed) {
|
||||
// arguments[0] = function_type_arguments;
|
||||
// i = 1;
|
||||
// }
|
||||
if (function.IsGeneric()) {
|
||||
Fragment store;
|
||||
store += LoadLocal(arguments);
|
||||
store += IntConstant(0);
|
||||
store += LoadFunctionTypeArguments();
|
||||
store += StoreIndexed(kArrayCid);
|
||||
store += IntConstant(1);
|
||||
store += StoreLocal(TokenPosition::kNoSource, index);
|
||||
store += Drop();
|
||||
body += TestAnyTypeArgs(store, Fragment());
|
||||
}
|
||||
|
||||
TargetEntryInstr* body_entry;
|
||||
TargetEntryInstr* loop_exit;
|
||||
|
||||
Fragment condition;
|
||||
// i < argument_count
|
||||
condition += LoadLocal(index);
|
||||
condition += LoadLocal(argument_count);
|
||||
condition += SmiRelationalOp(Token::kLT);
|
||||
condition += BranchIfTrue(&body_entry, &loop_exit, /*negate=*/false);
|
||||
|
||||
Fragment loop_body(body_entry);
|
||||
|
||||
// arguments[i] = LoadFpRelativeSlot(
|
||||
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
||||
loop_body += LoadLocal(arguments);
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += LoadLocal(argument_count);
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += SmiBinaryOp(Token::kSUB, /*truncate=*/true);
|
||||
loop_body += LoadFpRelativeSlot(
|
||||
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
||||
loop_body += StoreIndexed(kArrayCid);
|
||||
|
||||
// ++i
|
||||
loop_body += LoadLocal(index);
|
||||
loop_body += IntConstant(1);
|
||||
loop_body += SmiBinaryOp(Token::kADD, /*truncate=*/true);
|
||||
loop_body += StoreLocal(TokenPosition::kNoSource, index);
|
||||
loop_body += Drop();
|
||||
|
||||
JoinEntryInstr* join = BuildJoinEntry();
|
||||
loop_body += Goto(join);
|
||||
|
||||
Fragment loop(join);
|
||||
loop += condition;
|
||||
|
||||
Instruction* entry =
|
||||
new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId());
|
||||
body += Fragment(entry, loop_exit);
|
||||
}
|
||||
|
||||
// Load receiver.
|
||||
if (is_implicit_closure_function) {
|
||||
if (throw_no_such_method_error) {
|
||||
const Function& parent =
|
||||
Function::ZoneHandle(Z, function.parent_function());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
AbstractType& type = AbstractType::ZoneHandle(Z);
|
||||
type ^= Type::New(owner, TypeArguments::Handle(Z), owner.token_pos(),
|
||||
Heap::kOld);
|
||||
type ^= ClassFinalizer::FinalizeType(owner, type);
|
||||
body += Constant(type);
|
||||
} else {
|
||||
body += LoadLocal(parsed_function_->current_context_var());
|
||||
body += LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
|
||||
}
|
||||
} else {
|
||||
LocalScope* scope = parsed_function_->node_sequence()->scope();
|
||||
body += LoadLocal(scope->VariableAt(0));
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
body += Constant(String::ZoneHandle(Z, function.name()));
|
||||
body += PushArgument();
|
||||
|
||||
if (!parsed_function_->has_arg_desc_var()) {
|
||||
// If there is no variable for the arguments descriptor (this function's
|
||||
// signature doesn't require it), then we need to create one.
|
||||
Array& args_desc = Array::ZoneHandle(
|
||||
Z, ArgumentsDescriptor::New(0, function.NumParameters()));
|
||||
body += Constant(args_desc);
|
||||
} else {
|
||||
body += LoadArgDescriptor();
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
body += LoadLocal(arguments);
|
||||
body += PushArgument();
|
||||
|
||||
if (throw_no_such_method_error) {
|
||||
const Function& parent =
|
||||
Function::ZoneHandle(Z, function.parent_function());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
InvocationMirror::Level im_level = owner.IsTopLevel()
|
||||
? InvocationMirror::kTopLevel
|
||||
: InvocationMirror::kStatic;
|
||||
InvocationMirror::Kind im_kind;
|
||||
if (function.IsImplicitGetterFunction() || function.IsGetterFunction()) {
|
||||
im_kind = InvocationMirror::kGetter;
|
||||
} else if (function.IsImplicitSetterFunction() ||
|
||||
function.IsSetterFunction()) {
|
||||
im_kind = InvocationMirror::kSetter;
|
||||
} else {
|
||||
im_kind = InvocationMirror::kMethod;
|
||||
}
|
||||
body += IntConstant(InvocationMirror::EncodeType(im_level, im_kind));
|
||||
} else {
|
||||
body += NullConstant();
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
// Push the number of delayed type arguments.
|
||||
if (function.IsClosureFunction()) {
|
||||
LocalVariable* closure =
|
||||
parsed_function_->node_sequence()->scope()->VariableAt(0);
|
||||
Fragment then;
|
||||
then += IntConstant(function.NumTypeParameters());
|
||||
then += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
then += Drop();
|
||||
Fragment otherwise;
|
||||
otherwise += IntConstant(0);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
||||
otherwise += Drop();
|
||||
body += TestDelayedTypeArgs(closure, then, otherwise);
|
||||
body += LoadLocal(argument_count_var);
|
||||
} else {
|
||||
body += IntConstant(0);
|
||||
}
|
||||
body += PushArgument();
|
||||
|
||||
const Class& mirror_class =
|
||||
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
|
||||
ASSERT(!mirror_class.IsNull());
|
||||
const Function& allocation_function = Function::ZoneHandle(
|
||||
Z, mirror_class.LookupStaticFunction(Library::PrivateCoreLibName(
|
||||
Symbols::AllocateInvocationMirrorForClosure())));
|
||||
ASSERT(!allocation_function.IsNull());
|
||||
body += StaticCall(TokenPosition::kMinSource, allocation_function,
|
||||
/* argument_count = */ 5, ICData::kStatic);
|
||||
body += PushArgument(); // For the call to noSuchMethod.
|
||||
|
||||
if (throw_no_such_method_error) {
|
||||
const Class& klass = Class::ZoneHandle(
|
||||
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
|
||||
ASSERT(!klass.IsNull());
|
||||
const Function& throw_function = Function::ZoneHandle(
|
||||
Z,
|
||||
klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewInvocation()));
|
||||
ASSERT(!throw_function.IsNull());
|
||||
body += StaticCall(TokenPosition::kNoSource, throw_function, 2,
|
||||
ICData::kStatic);
|
||||
} else {
|
||||
body += InstanceCall(
|
||||
TokenPosition::kNoSource, Symbols::NoSuchMethod(), Token::kILLEGAL,
|
||||
/*type_args_len=*/0, /*argument_count=*/2, Array::null_array(),
|
||||
/*checked_argument_count=*/1, Function::null_function());
|
||||
}
|
||||
body += StoreLocal(TokenPosition::kNoSource, result);
|
||||
body += Drop();
|
||||
|
||||
body += Drop(); // arguments
|
||||
body += Drop(); // argument count
|
||||
|
||||
AbstractType& return_type = AbstractType::Handle(function.result_type());
|
||||
if (!return_type.IsDynamicType() && !return_type.IsVoidType() &&
|
||||
!return_type.IsObjectType()) {
|
||||
body += AssertAssignable(TokenPosition::kNoSource, return_type,
|
||||
Symbols::Empty());
|
||||
}
|
||||
body += Return(TokenPosition::kNoSource);
|
||||
|
||||
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
||||
prologue_info);
|
||||
}
|
||||
|
||||
Fragment FlowGraphBuilder::BuildDefaultTypeHandling(const Function& function) {
|
||||
if (function.IsGeneric()) {
|
||||
const TypeArguments& default_types =
|
||||
parsed_function_->DefaultFunctionTypeArguments();
|
||||
|
||||
if (!default_types.IsNull()) {
|
||||
Fragment then;
|
||||
Fragment otherwise;
|
||||
|
||||
otherwise += TranslateInstantiatedTypeArguments(default_types);
|
||||
otherwise += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function_->function_type_arguments());
|
||||
otherwise += Drop();
|
||||
return TestAnyTypeArgs(then, otherwise);
|
||||
}
|
||||
}
|
||||
return Fragment();
|
||||
}
|
||||
|
||||
// Pop the index of the current entry-point off the stack. If there is any
|
||||
// entrypoint-tracing hook registered in a pragma for the function, it is called
|
||||
// with the name of the current function and the current entry-point index.
|
||||
Fragment FlowGraphBuilder::BuildEntryPointsIntrospection() {
|
||||
if (!FLAG_enable_testing_pragmas) return Drop();
|
||||
|
||||
auto& function = Function::Handle(Z, parsed_function_->function().raw());
|
||||
|
||||
if (function.IsImplicitClosureFunction()) {
|
||||
const auto& parent = Function::Handle(Z, function.parent_function());
|
||||
const auto& func_name = String::Handle(Z, parent.name());
|
||||
const auto& owner = Class::Handle(Z, parent.Owner());
|
||||
function = owner.LookupFunction(func_name);
|
||||
}
|
||||
|
||||
auto& tmp = Object::Handle(Z);
|
||||
tmp = function.Owner();
|
||||
tmp = Class::Cast(tmp).library();
|
||||
auto& library = Library::Cast(tmp);
|
||||
|
||||
Object& options = Object::Handle(Z);
|
||||
if (!library.FindPragma(thread_, function, Symbols::vm_trace_entrypoints(),
|
||||
&options) ||
|
||||
options.IsNull() || !options.IsClosure()) {
|
||||
return Drop();
|
||||
}
|
||||
auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw());
|
||||
LocalVariable* entry_point_num = MakeTemporary();
|
||||
|
||||
auto& function_name = String::ZoneHandle(
|
||||
Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld));
|
||||
if (parsed_function_->function().IsImplicitClosureFunction()) {
|
||||
function_name = String::Concat(
|
||||
function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)),
|
||||
Heap::kOld);
|
||||
}
|
||||
|
||||
Fragment call_hook;
|
||||
call_hook += Constant(closure);
|
||||
call_hook += PushArgument();
|
||||
call_hook += Constant(function_name);
|
||||
call_hook += PushArgument();
|
||||
call_hook += LoadLocal(entry_point_num);
|
||||
call_hook += PushArgument();
|
||||
call_hook += Constant(Function::ZoneHandle(Z, closure.function()));
|
||||
call_hook += ClosureCall(TokenPosition::kNoSource,
|
||||
/*type_args_len=*/0, /*argument_count=*/3,
|
||||
/*argument_names=*/Array::ZoneHandle(Z));
|
||||
call_hook += Drop(); // result of closure call
|
||||
call_hook += Drop(); // entrypoint number
|
||||
return call_hook;
|
||||
}
|
||||
|
||||
FunctionEntryInstr* FlowGraphBuilder::BuildSharedUncheckedEntryPoint(
|
||||
Fragment shared_prologue_linked_in,
|
||||
Fragment skippable_checks,
|
||||
Fragment redefinitions_if_skipped,
|
||||
Fragment body) {
|
||||
ASSERT(shared_prologue_linked_in.entry == graph_entry_->normal_entry());
|
||||
ASSERT(parsed_function_->has_entry_points_temp_var());
|
||||
Instruction* prologue_start = shared_prologue_linked_in.entry->next();
|
||||
|
||||
auto* join_entry = BuildJoinEntry();
|
||||
|
||||
Fragment normal_entry(shared_prologue_linked_in.entry);
|
||||
normal_entry +=
|
||||
IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone));
|
||||
normal_entry += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function_->entry_points_temp_var());
|
||||
normal_entry += Drop();
|
||||
normal_entry += Goto(join_entry);
|
||||
|
||||
auto* extra_target_entry = BuildFunctionEntry(graph_entry_);
|
||||
Fragment extra_entry(extra_target_entry);
|
||||
extra_entry += IntConstant(
|
||||
static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable));
|
||||
extra_entry += StoreLocal(TokenPosition::kNoSource,
|
||||
parsed_function_->entry_points_temp_var());
|
||||
extra_entry += Drop();
|
||||
extra_entry += Goto(join_entry);
|
||||
|
||||
join_entry->LinkTo(prologue_start);
|
||||
|
||||
TargetEntryInstr *do_checks, *skip_checks;
|
||||
shared_prologue_linked_in +=
|
||||
LoadLocal(parsed_function_->entry_points_temp_var());
|
||||
shared_prologue_linked_in += BuildEntryPointsIntrospection();
|
||||
shared_prologue_linked_in +=
|
||||
LoadLocal(parsed_function_->entry_points_temp_var());
|
||||
shared_prologue_linked_in += IntConstant(
|
||||
static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable));
|
||||
shared_prologue_linked_in +=
|
||||
BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false);
|
||||
|
||||
JoinEntryInstr* rest_entry = BuildJoinEntry();
|
||||
|
||||
Fragment(do_checks) + skippable_checks + Goto(rest_entry);
|
||||
Fragment(skip_checks) + redefinitions_if_skipped + Goto(rest_entry);
|
||||
Fragment(rest_entry) + body;
|
||||
|
||||
return extra_target_entry;
|
||||
}
|
||||
|
||||
FunctionEntryInstr* FlowGraphBuilder::BuildSeparateUncheckedEntryPoint(
|
||||
BlockEntryInstr* normal_entry,
|
||||
Fragment normal_prologue,
|
||||
Fragment extra_prologue,
|
||||
Fragment shared_prologue,
|
||||
Fragment body) {
|
||||
auto* join_entry = BuildJoinEntry();
|
||||
auto* extra_entry = BuildFunctionEntry(graph_entry_);
|
||||
|
||||
Fragment normal(normal_entry);
|
||||
normal += IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone));
|
||||
normal += BuildEntryPointsIntrospection();
|
||||
normal += normal_prologue;
|
||||
normal += Goto(join_entry);
|
||||
|
||||
Fragment extra(extra_entry);
|
||||
extra +=
|
||||
IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kSeparate));
|
||||
extra += BuildEntryPointsIntrospection();
|
||||
extra += extra_prologue;
|
||||
extra += Goto(join_entry);
|
||||
|
||||
Fragment(join_entry) + shared_prologue + body;
|
||||
return extra_entry;
|
||||
}
|
||||
|
||||
void FlowGraphBuilder::RecordUncheckedEntryPoint(
|
||||
FunctionEntryInstr* extra_entry) {
|
||||
// Closures always check all arguments on their checked entry-point, most
|
||||
// call-sites are unchecked, and they're inlined less often, so it's very
|
||||
// beneficial to build multiple entry-points for them. Regular methods however
|
||||
// have fewer checks to begin with since they have dynamic invocation
|
||||
// forwarders, so in AOT we implement a more conservative time-space tradeoff
|
||||
// by only building the unchecked entry-point when inlining. We should
|
||||
// reconsider this heuristic if we identify non-inlined type-checks in
|
||||
// hotspots of new benchmarks.
|
||||
if (!IsInlining() && (parsed_function_->function().IsClosureFunction() ||
|
||||
!FLAG_precompiled_mode)) {
|
||||
graph_entry_->set_unchecked_entry(extra_entry);
|
||||
} else if (InliningUncheckedEntry()) {
|
||||
graph_entry_->set_normal_entry(extra_entry);
|
||||
}
|
||||
}
|
||||
|
||||
FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
|
||||
const Function& function) {
|
||||
const Function& parent = Function::ZoneHandle(Z, function.parent_function());
|
||||
const String& func_name = String::ZoneHandle(Z, parent.name());
|
||||
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
||||
Function& target = Function::ZoneHandle(Z, owner.LookupFunction(func_name));
|
||||
|
||||
if (!target.IsNull() && (target.raw() != parent.raw())) {
|
||||
DEBUG_ASSERT(Isolate::Current()->HasAttemptedReload());
|
||||
if ((target.is_static() != parent.is_static()) ||
|
||||
(target.kind() != parent.kind())) {
|
||||
target = Function::null();
|
||||
}
|
||||
}
|
||||
|
||||
if (target.IsNull() ||
|
||||
(parent.num_fixed_parameters() != target.num_fixed_parameters())) {
|
||||
return BuildGraphOfNoSuchMethodForwarder(function, true,
|
||||
parent.is_static());
|
||||
}
|
||||
|
||||
graph_entry_ =
|
||||
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
|
||||
|
||||
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
||||
graph_entry_->set_normal_entry(normal_entry);
|
||||
|
||||
PrologueInfo prologue_info(-1, -1);
|
||||
BlockEntryInstr* instruction_cursor =
|
||||
BuildPrologue(normal_entry, &prologue_info);
|
||||
|
||||
const Fragment prologue = CheckStackOverflowInPrologue(function.token_pos());
|
||||
|
||||
const Fragment default_type_handling = BuildDefaultTypeHandling(function);
|
||||
|
||||
// We're going to throw away the explicit checks because the target will
|
||||
// always check them.
|
||||
Fragment implicit_checks;
|
||||
if (function.NeedsArgumentTypeChecks(I)) {
|
||||
Fragment explicit_checks_unused;
|
||||
if (target.is_static()) {
|
||||
// Tearoffs of static methods needs to perform arguments checks since
|
||||
// static methods they forward to don't do it themselves.
|
||||
BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
||||
&explicit_checks_unused, &implicit_checks,
|
||||
nullptr);
|
||||
} else {
|
||||
if (MethodCanSkipTypeChecksForNonCovariantArguments(
|
||||
parent, ProcedureAttributesMetadata())) {
|
||||
// Generate checks that are skipped inside a body of a function.
|
||||
BuildArgumentTypeChecks(
|
||||
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds,
|
||||
&explicit_checks_unused, &implicit_checks, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Fragment body;
|
||||
|
||||
intptr_t type_args_len = 0;
|
||||
if (function.IsGeneric()) {
|
||||
type_args_len = function.NumTypeParameters();
|
||||
ASSERT(parsed_function_->function_type_arguments() != NULL);
|
||||
body += LoadLocal(parsed_function_->function_type_arguments());
|
||||
body += PushArgument();
|
||||
}
|
||||
|
||||
// Load all the arguments.
|
||||
auto scope = parsed_function_->node_sequence()->scope();
|
||||
|
||||
if (!target.is_static()) {
|
||||
// The context has a fixed shape: a single variable which is the
|
||||
// closed-over receiver.
|
||||
body += LoadLocal(scope->VariableAt(0));
|
||||
body += LoadNativeField(Slot::Closure_context());
|
||||
body += LoadNativeField(
|
||||
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
|
||||
body += PushArgument();
|
||||
}
|
||||
|
||||
// Positional.
|
||||
intptr_t arg_index = function.NumImplicitParameters();
|
||||
const intptr_t named_argument_count = function.NumOptionalNamedParameters();
|
||||
const intptr_t positional_argument_count = function.NumParameters() -
|
||||
function.NumImplicitParameters() -
|
||||
named_argument_count;
|
||||
for (intptr_t i = 0; i < positional_argument_count; ++i, ++arg_index) {
|
||||
body += LoadLocal(scope->VariableAt(arg_index)); // ith variable offset.
|
||||
body += PushArgument();
|
||||
}
|
||||
|
||||
// Named.
|
||||
Array& argument_names = Array::ZoneHandle(Z);
|
||||
if (named_argument_count > 0) {
|
||||
argument_names = Array::New(named_argument_count, Heap::kOld);
|
||||
String& name = String::Handle(Z);
|
||||
for (intptr_t i = 0; i < named_argument_count; ++i, ++arg_index) {
|
||||
body += LoadLocal(scope->VariableAt(arg_index));
|
||||
body += PushArgument();
|
||||
|
||||
name = function.ParameterNameAt(arg_index);
|
||||
argument_names.SetAt(i, name);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward them to the parent.
|
||||
intptr_t argument_count = positional_argument_count + named_argument_count;
|
||||
if (!parent.is_static()) {
|
||||
++argument_count;
|
||||
}
|
||||
ASSERT(argument_count == target.NumParameters());
|
||||
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
||||
argument_names, ICData::kNoRebind,
|
||||
/* result_type = */ NULL, type_args_len);
|
||||
|
||||
// Return the result.
|
||||
body += Return(function.end_token_pos());
|
||||
|
||||
// Setup multiple entrypoints if useful.
|
||||
FunctionEntryInstr* extra_entry = nullptr;
|
||||
if (function.MayHaveUncheckedEntryPoint(I)) {
|
||||
// The prologue for a closure will always have context handling (e.g.
|
||||
// setting up the 'this_variable'), but we don't need it on the unchecked
|
||||
// entry because the only time we reference this is for loading the
|
||||
// receiver, which we fetch directly from the context.
|
||||
if (PrologueBuilder::PrologueSkippableOnUncheckedEntry(function)) {
|
||||
// Use separate entry points since we can skip almost everything on the
|
||||
// static entry.
|
||||
extra_entry = BuildSeparateUncheckedEntryPoint(
|
||||
/*normal_entry=*/instruction_cursor,
|
||||
/*normal_prologue=*/prologue + default_type_handling +
|
||||
implicit_checks,
|
||||
/*extra_prologue=*/
|
||||
CheckStackOverflowInPrologue(function.token_pos()),
|
||||
/*shared_prologue=*/Fragment(),
|
||||
/*body=*/body);
|
||||
} else {
|
||||
Fragment shared_prologue(normal_entry, instruction_cursor);
|
||||
shared_prologue += prologue;
|
||||
extra_entry = BuildSharedUncheckedEntryPoint(
|
||||
/*shared_prologue_linked_in=*/shared_prologue,
|
||||
/*skippable_checks=*/default_type_handling + implicit_checks,
|
||||
/*redefinitions_if_skipped=*/Fragment(),
|
||||
/*body=*/body);
|
||||
}
|
||||
RecordUncheckedEntryPoint(extra_entry);
|
||||
} else {
|
||||
Fragment function(instruction_cursor);
|
||||
function += prologue;
|
||||
function += default_type_handling;
|
||||
function += implicit_checks;
|
||||
function += body;
|
||||
}
|
||||
|
||||
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
||||
prologue_info);
|
||||
}
|
||||
|
||||
void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) {
|
||||
try_catch_block_ = try_catch_block;
|
||||
SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex
|
||||
|
||||
@@ -47,6 +47,32 @@ enum class TypeChecksToBuild {
|
||||
kCheckCovariantTypeParameterBounds,
|
||||
};
|
||||
|
||||
// Indicates which form of the unchecked entrypoint we are compiling.
|
||||
//
|
||||
// kNone:
|
||||
//
|
||||
// There is no unchecked entrypoint: the unchecked entry is set to NULL in
|
||||
// the 'GraphEntryInstr'.
|
||||
//
|
||||
// kSeparate:
|
||||
//
|
||||
// The normal and unchecked entrypoint each point to their own versions of
|
||||
// the prologue, containing exactly those checks which need to be performed
|
||||
// on either side. Both sides jump directly to the body after performing
|
||||
// their prologue.
|
||||
//
|
||||
// kSharedWithVariable:
|
||||
//
|
||||
// A temporary variable is allocated and initialized to 0 on normal entry
|
||||
// and 2 on unchecked entry. Code which should be ommitted on the unchecked
|
||||
// entrypoint is made conditional on this variable being equal to 0.
|
||||
//
|
||||
enum class UncheckedEntryPointStyle {
|
||||
kNone = 0,
|
||||
kSeparate = 1,
|
||||
kSharedWithVariable = 2,
|
||||
};
|
||||
|
||||
class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
||||
public:
|
||||
FlowGraphBuilder(ParsedFunction* parsed_function,
|
||||
@@ -186,6 +212,62 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
||||
Fragment* implicit_checks,
|
||||
Fragment* implicit_redefinitions);
|
||||
|
||||
// Builds flow graph for noSuchMethod forwarder.
|
||||
//
|
||||
// If throw_no_such_method_error is set to true, an
|
||||
// instance of NoSuchMethodError is thrown. Otherwise, the instance
|
||||
// noSuchMethod is called.
|
||||
//
|
||||
// ParsedFunction should have the following information:
|
||||
// - default_parameter_values()
|
||||
// - is_forwarding_stub()
|
||||
// - forwarding_stub_super_target()
|
||||
//
|
||||
// Scope should be populated with parameter variables including
|
||||
// - needs_type_check()
|
||||
// - is_explicit_covariant_parameter()
|
||||
//
|
||||
FlowGraph* BuildGraphOfNoSuchMethodForwarder(
|
||||
const Function& function,
|
||||
bool is_implicit_closure_function,
|
||||
bool throw_no_such_method_error);
|
||||
|
||||
// If no type arguments are passed to a generic function, we need to fill the
|
||||
// type arguments in with the default types stored on the TypeParameter nodes
|
||||
// in Kernel.
|
||||
//
|
||||
// ParsedFunction should have the following information:
|
||||
// - DefaultFunctionTypeArguments()
|
||||
// - function_type_arguments()
|
||||
Fragment BuildDefaultTypeHandling(const Function& function);
|
||||
|
||||
Fragment BuildEntryPointsIntrospection();
|
||||
FunctionEntryInstr* BuildSharedUncheckedEntryPoint(
|
||||
Fragment prologue_from_normal_entry,
|
||||
Fragment skippable_checks,
|
||||
Fragment redefinitions_if_skipped,
|
||||
Fragment body);
|
||||
FunctionEntryInstr* BuildSeparateUncheckedEntryPoint(
|
||||
BlockEntryInstr* normal_entry,
|
||||
Fragment normal_prologue,
|
||||
Fragment extra_prologue,
|
||||
Fragment shared_prologue,
|
||||
Fragment body);
|
||||
void RecordUncheckedEntryPoint(FunctionEntryInstr* extra_entry);
|
||||
|
||||
// Builds flow graph for implicit closure function (tear-off).
|
||||
//
|
||||
// ParsedFunction should have the following information:
|
||||
// - default_parameter_values()
|
||||
// - is_forwarding_stub()
|
||||
// - forwarding_stub_super_target()
|
||||
//
|
||||
// Scope should be populated with parameter variables including
|
||||
// - needs_type_check()
|
||||
// - is_explicit_covariant_parameter()
|
||||
//
|
||||
FlowGraph* BuildGraphOfImplicitClosureFunction(const Function& function);
|
||||
|
||||
TranslationHelper translation_helper_;
|
||||
Thread* thread_;
|
||||
Zone* zone_;
|
||||
|
||||
@@ -65,7 +65,8 @@ ParsedFunction::ParsedFunction(Thread* thread, const Function& function)
|
||||
first_parameter_index_(),
|
||||
num_stack_locals_(0),
|
||||
have_seen_await_expr_(false),
|
||||
kernel_scopes_(NULL) {
|
||||
kernel_scopes_(NULL),
|
||||
default_function_type_arguments_(TypeArguments::ZoneHandle(zone())) {
|
||||
ASSERT(function.IsZoneHandle());
|
||||
// Every function has a local variable for the current context.
|
||||
LocalVariable* temp = new (zone())
|
||||
|
||||
@@ -198,6 +198,14 @@ class ParsedFunction : public ZoneAllocated {
|
||||
return raw_parameters_->At(i);
|
||||
}
|
||||
|
||||
void SetDefaultFunctionTypeArguments(const TypeArguments& value) {
|
||||
default_function_type_arguments_ = value.raw();
|
||||
}
|
||||
|
||||
const TypeArguments& DefaultFunctionTypeArguments() const {
|
||||
return default_function_type_arguments_;
|
||||
}
|
||||
|
||||
private:
|
||||
Thread* thread_;
|
||||
const Function& function_;
|
||||
@@ -225,6 +233,8 @@ class ParsedFunction : public ZoneAllocated {
|
||||
const Function* forwarding_stub_super_target_ = nullptr;
|
||||
kernel::ScopeBuildingResult* kernel_scopes_;
|
||||
|
||||
TypeArguments& default_function_type_arguments_;
|
||||
|
||||
friend class Parser;
|
||||
DISALLOW_COPY_AND_ASSIGN(ParsedFunction);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user