7e1d1963e5
TEST=ci Change-Id: Ic3162ef1558fb803ab68749f1f1a4d820645ec7e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194017 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Liam Appelbe <liama@google.com>
4515 lines
175 KiB
C++
4515 lines
175 KiB
C++
// Copyright (c) 2016, 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/kernel_to_il.h"
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/globals.h"
|
|
#include "vm/class_id.h"
|
|
#include "vm/compiler/aot/precompiler.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/compiler/backend/il_printer.h"
|
|
#include "vm/compiler/backend/locations.h"
|
|
#include "vm/compiler/backend/range_analysis.h"
|
|
#include "vm/compiler/ffi/abi.h"
|
|
#include "vm/compiler/ffi/marshaller.h"
|
|
#include "vm/compiler/ffi/native_calling_convention.h"
|
|
#include "vm/compiler/ffi/native_type.h"
|
|
#include "vm/compiler/ffi/recognized_method.h"
|
|
#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
|
|
#include "vm/compiler/frontend/kernel_translation_helper.h"
|
|
#include "vm/compiler/frontend/prologue_builder.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/kernel_isolate.h"
|
|
#include "vm/kernel_loader.h"
|
|
#include "vm/log.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/native_entry.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/report.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/scopes.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/symbols.h"
|
|
|
|
namespace dart {
|
|
namespace kernel {
|
|
|
|
#define Z (zone_)
|
|
#define H (translation_helper_)
|
|
#define T (type_translator_)
|
|
#define I Isolate::Current()
|
|
#define IG IsolateGroup::Current()
|
|
|
|
FlowGraphBuilder::FlowGraphBuilder(
|
|
ParsedFunction* parsed_function,
|
|
ZoneGrowableArray<const ICData*>* ic_data_array,
|
|
ZoneGrowableArray<intptr_t>* context_level_array,
|
|
InlineExitCollector* exit_collector,
|
|
bool optimizing,
|
|
intptr_t osr_id,
|
|
intptr_t first_block_id,
|
|
bool inlining_unchecked_entry)
|
|
: BaseFlowGraphBuilder(parsed_function,
|
|
first_block_id - 1,
|
|
osr_id,
|
|
context_level_array,
|
|
exit_collector,
|
|
inlining_unchecked_entry),
|
|
translation_helper_(Thread::Current()),
|
|
thread_(translation_helper_.thread()),
|
|
zone_(translation_helper_.zone()),
|
|
parsed_function_(parsed_function),
|
|
optimizing_(optimizing),
|
|
ic_data_array_(*ic_data_array),
|
|
next_function_id_(0),
|
|
loop_depth_(0),
|
|
try_depth_(0),
|
|
catch_depth_(0),
|
|
for_in_depth_(0),
|
|
block_expression_depth_(0),
|
|
graph_entry_(NULL),
|
|
scopes_(NULL),
|
|
breakable_block_(NULL),
|
|
switch_block_(NULL),
|
|
try_catch_block_(NULL),
|
|
try_finally_block_(NULL),
|
|
catch_block_(NULL),
|
|
prepend_type_arguments_(Function::ZoneHandle(zone_)),
|
|
throw_new_null_assertion_(Function::ZoneHandle(zone_)) {
|
|
const Script& script =
|
|
Script::Handle(Z, parsed_function->function().script());
|
|
H.InitFromScript(script);
|
|
}
|
|
|
|
FlowGraphBuilder::~FlowGraphBuilder() {}
|
|
|
|
Fragment FlowGraphBuilder::EnterScope(
|
|
intptr_t kernel_offset,
|
|
const LocalScope** context_scope /* = nullptr */) {
|
|
Fragment instructions;
|
|
const LocalScope* scope = scopes_->scopes.Lookup(kernel_offset);
|
|
if (scope->num_context_variables() > 0) {
|
|
instructions += PushContext(scope);
|
|
instructions += Drop();
|
|
}
|
|
if (context_scope != nullptr) {
|
|
*context_scope = scope;
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ExitScope(intptr_t kernel_offset) {
|
|
Fragment instructions;
|
|
const intptr_t context_size =
|
|
scopes_->scopes.Lookup(kernel_offset)->num_context_variables();
|
|
if (context_size > 0) {
|
|
instructions += PopContext();
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::AdjustContextTo(int depth) {
|
|
ASSERT(depth <= context_depth_ && depth >= 0);
|
|
Fragment instructions;
|
|
if (depth < context_depth_) {
|
|
instructions += LoadContextAt(depth);
|
|
instructions += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->current_context_var());
|
|
instructions += Drop();
|
|
context_depth_ = depth;
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::PushContext(const LocalScope* scope) {
|
|
ASSERT(scope->num_context_variables() > 0);
|
|
Fragment instructions = AllocateContext(scope->context_slots());
|
|
LocalVariable* context = MakeTemporary();
|
|
instructions += LoadLocal(context);
|
|
instructions += LoadLocal(parsed_function_->current_context_var());
|
|
instructions +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Context_parent(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
instructions += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->current_context_var());
|
|
++context_depth_;
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::PopContext() {
|
|
return AdjustContextTo(context_depth_ - 1);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::LoadInstantiatorTypeArguments() {
|
|
// TODO(27590): We could use `active_class_->IsGeneric()`.
|
|
Fragment instructions;
|
|
if (scopes_ != nullptr && scopes_->type_arguments_variable != nullptr) {
|
|
#ifdef DEBUG
|
|
Function& function =
|
|
Function::Handle(Z, parsed_function_->function().ptr());
|
|
while (function.IsClosureFunction()) {
|
|
function = function.parent_function();
|
|
}
|
|
ASSERT(function.IsFactory());
|
|
#endif
|
|
instructions += LoadLocal(scopes_->type_arguments_variable);
|
|
} else if (parsed_function_->has_receiver_var() &&
|
|
active_class_.ClassNumTypeArguments() > 0) {
|
|
ASSERT(!parsed_function_->function().IsFactory());
|
|
instructions += LoadLocal(parsed_function_->receiver_var());
|
|
instructions += LoadNativeField(
|
|
Slot::GetTypeArgumentsSlotFor(thread_, *active_class_.klass));
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
// This function is responsible for pushing a type arguments vector which
|
|
// contains all type arguments of enclosing functions prepended to the type
|
|
// arguments of the current function.
|
|
Fragment FlowGraphBuilder::LoadFunctionTypeArguments() {
|
|
Fragment instructions;
|
|
|
|
const Function& function = parsed_function_->function();
|
|
|
|
if (function.IsGeneric() || function.HasGenericParent()) {
|
|
ASSERT(parsed_function_->function_type_arguments() != NULL);
|
|
instructions += LoadLocal(parsed_function_->function_type_arguments());
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::TranslateInstantiatedTypeArguments(
|
|
const TypeArguments& type_arguments) {
|
|
Fragment instructions;
|
|
|
|
if (type_arguments.IsNull() || type_arguments.IsInstantiated()) {
|
|
// There are no type references to type parameters so we can just take it.
|
|
instructions += Constant(type_arguments);
|
|
} else {
|
|
// The [type_arguments] vector contains a type reference to a type
|
|
// parameter we need to resolve it.
|
|
if (type_arguments.CanShareInstantiatorTypeArguments(
|
|
*active_class_.klass)) {
|
|
// If the instantiator type arguments are just passed on, we don't need to
|
|
// resolve the type parameters.
|
|
//
|
|
// This is for example the case here:
|
|
// class Foo<T> {
|
|
// newList() => new List<T>();
|
|
// }
|
|
// We just use the type argument vector from the [Foo] object and pass it
|
|
// directly to the `new List<T>()` factory constructor.
|
|
instructions += LoadInstantiatorTypeArguments();
|
|
} else if (type_arguments.CanShareFunctionTypeArguments(
|
|
parsed_function_->function())) {
|
|
instructions += LoadFunctionTypeArguments();
|
|
} else {
|
|
// Otherwise we need to resolve [TypeParameterType]s in the type
|
|
// expression based on the current instantiator type argument vector.
|
|
if (!type_arguments.IsInstantiated(kCurrentClass)) {
|
|
instructions += LoadInstantiatorTypeArguments();
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
if (!type_arguments.IsInstantiated(kFunctions)) {
|
|
instructions += LoadFunctionTypeArguments();
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
instructions += InstantiateTypeArguments(type_arguments);
|
|
}
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CatchBlockEntry(const Array& handler_types,
|
|
intptr_t handler_index,
|
|
bool needs_stacktrace,
|
|
bool is_synthesized) {
|
|
LocalVariable* exception_var = CurrentException();
|
|
LocalVariable* stacktrace_var = CurrentStackTrace();
|
|
LocalVariable* raw_exception_var = CurrentRawException();
|
|
LocalVariable* raw_stacktrace_var = CurrentRawStackTrace();
|
|
|
|
CatchBlockEntryInstr* entry = new (Z) CatchBlockEntryInstr(
|
|
is_synthesized, // whether catch block was synthesized by FE compiler
|
|
AllocateBlockId(), CurrentTryIndex(), graph_entry_, handler_types,
|
|
handler_index, needs_stacktrace, GetNextDeoptId(), exception_var,
|
|
stacktrace_var, raw_exception_var, raw_stacktrace_var);
|
|
graph_entry_->AddCatchEntry(entry);
|
|
|
|
Fragment instructions(entry);
|
|
|
|
// Auxiliary variables introduced by the try catch can be captured if we are
|
|
// inside a function with yield/resume points. In this case we first need
|
|
// to restore the context to match the context at entry into the closure.
|
|
const bool should_restore_closure_context =
|
|
CurrentException()->is_captured() || CurrentCatchContext()->is_captured();
|
|
LocalVariable* context_variable = parsed_function_->current_context_var();
|
|
if (should_restore_closure_context) {
|
|
ASSERT(parsed_function_->function().IsClosureFunction());
|
|
|
|
LocalVariable* closure_parameter = parsed_function_->ParameterVariable(0);
|
|
ASSERT(!closure_parameter->is_captured());
|
|
instructions += LoadLocal(closure_parameter);
|
|
instructions += LoadNativeField(Slot::Closure_context());
|
|
instructions += StoreLocal(TokenPosition::kNoSource, context_variable);
|
|
instructions += Drop();
|
|
}
|
|
|
|
if (exception_var->is_captured()) {
|
|
instructions += LoadLocal(context_variable);
|
|
instructions += LoadLocal(raw_exception_var);
|
|
instructions += StoreInstanceField(
|
|
TokenPosition::kNoSource,
|
|
Slot::GetContextVariableSlotFor(thread_, *exception_var));
|
|
}
|
|
if (stacktrace_var->is_captured()) {
|
|
instructions += LoadLocal(context_variable);
|
|
instructions += LoadLocal(raw_stacktrace_var);
|
|
instructions += StoreInstanceField(
|
|
TokenPosition::kNoSource,
|
|
Slot::GetContextVariableSlotFor(thread_, *stacktrace_var));
|
|
}
|
|
|
|
// :saved_try_context_var can be captured in the context of
|
|
// of the closure, in this case CatchBlockEntryInstr restores
|
|
// :current_context_var to point to closure context in the
|
|
// same way as normal function prologue does.
|
|
// Update current context depth to reflect that.
|
|
const intptr_t saved_context_depth = context_depth_;
|
|
ASSERT(!CurrentCatchContext()->is_captured() ||
|
|
CurrentCatchContext()->owner()->context_level() == 0);
|
|
context_depth_ = 0;
|
|
instructions += LoadLocal(CurrentCatchContext());
|
|
instructions += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->current_context_var());
|
|
instructions += Drop();
|
|
context_depth_ = saved_context_depth;
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::TryCatch(int try_handler_index) {
|
|
// The body of the try needs to have it's own block in order to get a new try
|
|
// index.
|
|
//
|
|
// => We therefore create a block for the body (fresh try index) and another
|
|
// join block (with current try index).
|
|
Fragment body;
|
|
JoinEntryInstr* entry = new (Z)
|
|
JoinEntryInstr(AllocateBlockId(), try_handler_index, GetNextDeoptId());
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += StoreLocal(TokenPosition::kNoSource, CurrentCatchContext());
|
|
body += Drop();
|
|
body += Goto(entry);
|
|
return Fragment(body.entry, entry);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CheckStackOverflowInPrologue(
|
|
TokenPosition position) {
|
|
ASSERT(loop_depth_ == 0);
|
|
return BaseFlowGraphBuilder::CheckStackOverflowInPrologue(position);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CloneContext(
|
|
const ZoneGrowableArray<const Slot*>& context_slots) {
|
|
LocalVariable* context_variable = parsed_function_->current_context_var();
|
|
|
|
Fragment instructions = LoadLocal(context_variable);
|
|
|
|
CloneContextInstr* clone_instruction = new (Z) CloneContextInstr(
|
|
InstructionSource(), Pop(), context_slots, GetNextDeoptId());
|
|
instructions <<= clone_instruction;
|
|
Push(clone_instruction);
|
|
|
|
instructions += StoreLocal(TokenPosition::kNoSource, context_variable);
|
|
instructions += Drop();
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::InstanceCall(
|
|
TokenPosition position,
|
|
const String& name,
|
|
Token::Kind kind,
|
|
intptr_t type_args_len,
|
|
intptr_t argument_count,
|
|
const Array& argument_names,
|
|
intptr_t checked_argument_count,
|
|
const Function& interface_target,
|
|
const Function& tearoff_interface_target,
|
|
const InferredTypeMetadata* result_type,
|
|
bool use_unchecked_entry,
|
|
const CallSiteAttributesMetadata* call_site_attrs,
|
|
bool receiver_is_not_smi) {
|
|
const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
|
|
InputsArray* arguments = GetArguments(total_count);
|
|
InstanceCallInstr* call = new (Z) InstanceCallInstr(
|
|
InstructionSource(position), name, kind, arguments, type_args_len,
|
|
argument_names, checked_argument_count, ic_data_array_, GetNextDeoptId(),
|
|
interface_target, tearoff_interface_target);
|
|
if ((result_type != NULL) && !result_type->IsTrivial()) {
|
|
call->SetResultType(Z, result_type->ToCompileType(Z));
|
|
}
|
|
if (use_unchecked_entry) {
|
|
call->set_entry_kind(Code::EntryKind::kUnchecked);
|
|
}
|
|
if (call_site_attrs != nullptr && call_site_attrs->receiver_type != nullptr &&
|
|
call_site_attrs->receiver_type->IsInstantiated()) {
|
|
call->set_receivers_static_type(call_site_attrs->receiver_type);
|
|
} else if (!interface_target.IsNull()) {
|
|
const Class& owner = Class::Handle(Z, interface_target.Owner());
|
|
const AbstractType& type =
|
|
AbstractType::ZoneHandle(Z, owner.DeclarationType());
|
|
call->set_receivers_static_type(&type);
|
|
}
|
|
call->set_receiver_is_not_smi(receiver_is_not_smi);
|
|
Push(call);
|
|
if (result_type != nullptr && result_type->IsConstant()) {
|
|
Fragment instructions(call);
|
|
instructions += Drop();
|
|
instructions += Constant(result_type->constant_value);
|
|
return instructions;
|
|
}
|
|
return Fragment(call);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiCall(
|
|
const compiler::ffi::CallMarshaller& marshaller) {
|
|
Fragment body;
|
|
|
|
FfiCallInstr* const call =
|
|
new (Z) FfiCallInstr(Z, GetNextDeoptId(), marshaller);
|
|
|
|
for (intptr_t i = call->InputCount() - 1; i >= 0; --i) {
|
|
call->SetInputAt(i, Pop());
|
|
}
|
|
|
|
Push(call);
|
|
body <<= call;
|
|
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ThrowException(TokenPosition position) {
|
|
Fragment instructions;
|
|
Value* exception = Pop();
|
|
instructions += Fragment(new (Z) ThrowInstr(InstructionSource(position),
|
|
GetNextDeoptId(), exception))
|
|
.closed();
|
|
// Use its side effect of leaving a constant on the stack (does not change
|
|
// the graph).
|
|
NullConstant();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::RethrowException(TokenPosition position,
|
|
int catch_try_index) {
|
|
Fragment instructions;
|
|
Value* stacktrace = Pop();
|
|
Value* exception = Pop();
|
|
instructions += Fragment(new (Z) ReThrowInstr(
|
|
InstructionSource(position), catch_try_index,
|
|
GetNextDeoptId(), exception, stacktrace))
|
|
.closed();
|
|
// Use its side effect of leaving a constant on the stack (does not change
|
|
// the graph).
|
|
NullConstant();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::LoadLocal(LocalVariable* variable) {
|
|
// Captured 'this' is immutable, so within the outer method we don't need to
|
|
// load it from the context.
|
|
const ParsedFunction* pf = parsed_function_;
|
|
if (pf->function().HasThisParameter() && pf->has_receiver_var() &&
|
|
variable == pf->receiver_var()) {
|
|
ASSERT(variable == pf->ParameterVariable(0));
|
|
variable = pf->RawParameterVariable(0);
|
|
}
|
|
if (variable->is_captured()) {
|
|
Fragment instructions;
|
|
instructions += LoadContextAt(variable->owner()->context_level());
|
|
instructions +=
|
|
LoadNativeField(Slot::GetContextVariableSlotFor(thread_, *variable));
|
|
return instructions;
|
|
} else {
|
|
return BaseFlowGraphBuilder::LoadLocal(variable);
|
|
}
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ThrowLateInitializationError(
|
|
TokenPosition position,
|
|
const char* throw_method_name,
|
|
const String& name) {
|
|
const Class& klass =
|
|
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::LateError()));
|
|
ASSERT(!klass.IsNull());
|
|
|
|
const auto& error = klass.EnsureIsFinalized(thread_);
|
|
ASSERT(error == Error::null());
|
|
const Function& throw_new =
|
|
Function::ZoneHandle(Z, klass.LookupStaticFunctionAllowPrivate(
|
|
H.DartSymbolObfuscate(throw_method_name)));
|
|
ASSERT(!throw_new.IsNull());
|
|
|
|
Fragment instructions;
|
|
|
|
// Call LateError._throwFoo.
|
|
instructions += Constant(name);
|
|
instructions += StaticCall(position, throw_new,
|
|
/* argument_count = */ 1, ICData::kStatic);
|
|
instructions += Drop();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::StoreLateField(const Field& field,
|
|
LocalVariable* instance,
|
|
LocalVariable* setter_value) {
|
|
Fragment instructions;
|
|
TargetEntryInstr *is_uninitialized, *is_initialized;
|
|
const TokenPosition position = field.token_pos();
|
|
const bool is_static = field.is_static();
|
|
const bool is_final = field.is_final();
|
|
|
|
if (is_final) {
|
|
// Check whether the field has been initialized already.
|
|
if (is_static) {
|
|
instructions += LoadStaticField(field, /*calls_initializer=*/false);
|
|
} else {
|
|
instructions += LoadLocal(instance);
|
|
instructions += LoadField(field, /*calls_initializer=*/false);
|
|
}
|
|
instructions += Constant(Object::sentinel());
|
|
instructions += BranchIfStrictEqual(&is_uninitialized, &is_initialized);
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
|
|
{
|
|
// If the field isn't initialized, do nothing.
|
|
Fragment initialize(is_uninitialized);
|
|
initialize += Goto(join);
|
|
}
|
|
|
|
{
|
|
// If the field is already initialized, throw a LateInitializationError.
|
|
Fragment already_initialized(is_initialized);
|
|
already_initialized += ThrowLateInitializationError(
|
|
position, "_throwFieldAlreadyInitialized",
|
|
String::ZoneHandle(Z, field.name()));
|
|
already_initialized += Goto(join);
|
|
}
|
|
|
|
instructions = Fragment(instructions.entry, join);
|
|
}
|
|
|
|
if (!is_static) {
|
|
instructions += LoadLocal(instance);
|
|
}
|
|
instructions += LoadLocal(setter_value);
|
|
if (is_static) {
|
|
instructions += StoreStaticField(position, field);
|
|
} else {
|
|
instructions += StoreInstanceFieldGuarded(field);
|
|
}
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::NativeCall(const String* name,
|
|
const Function* function) {
|
|
InlineBailout("kernel::FlowGraphBuilder::NativeCall");
|
|
const intptr_t num_args =
|
|
function->NumParameters() + (function->IsGeneric() ? 1 : 0);
|
|
InputsArray* arguments = GetArguments(num_args);
|
|
NativeCallInstr* call = new (Z)
|
|
NativeCallInstr(name, function, FLAG_link_natives_lazily,
|
|
InstructionSource(function->end_token_pos()), arguments);
|
|
Push(call);
|
|
return Fragment(call);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::Return(TokenPosition position,
|
|
bool omit_result_type_check,
|
|
intptr_t yield_index) {
|
|
Fragment instructions;
|
|
const Function& function = parsed_function_->function();
|
|
|
|
// Emit a type check of the return type in checked mode for all functions
|
|
// and in strong mode for native functions.
|
|
if (!omit_result_type_check && function.is_native()) {
|
|
const AbstractType& return_type =
|
|
AbstractType::Handle(Z, function.result_type());
|
|
instructions += CheckAssignable(return_type, Symbols::FunctionResult());
|
|
}
|
|
|
|
if (NeedsDebugStepCheck(function, position)) {
|
|
instructions += DebugStepCheck(position);
|
|
}
|
|
|
|
instructions += BaseFlowGraphBuilder::Return(position, yield_index);
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::StaticCall(TokenPosition position,
|
|
const Function& target,
|
|
intptr_t argument_count,
|
|
ICData::RebindRule rebind_rule) {
|
|
return StaticCall(position, target, argument_count, Array::null_array(),
|
|
rebind_rule);
|
|
}
|
|
|
|
void FlowGraphBuilder::SetResultTypeForStaticCall(
|
|
StaticCallInstr* call,
|
|
const Function& target,
|
|
intptr_t argument_count,
|
|
const InferredTypeMetadata* result_type) {
|
|
if (call->InitResultType(Z)) {
|
|
ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) ||
|
|
(result_type->cid == call->result_cid()));
|
|
return;
|
|
}
|
|
if ((result_type != NULL) && !result_type->IsTrivial()) {
|
|
call->SetResultType(Z, result_type->ToCompileType(Z));
|
|
}
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::StaticCall(TokenPosition position,
|
|
const Function& target,
|
|
intptr_t argument_count,
|
|
const Array& argument_names,
|
|
ICData::RebindRule rebind_rule,
|
|
const InferredTypeMetadata* result_type,
|
|
intptr_t type_args_count,
|
|
bool use_unchecked_entry) {
|
|
const intptr_t total_count = argument_count + (type_args_count > 0 ? 1 : 0);
|
|
InputsArray* arguments = GetArguments(total_count);
|
|
StaticCallInstr* call = new (Z) StaticCallInstr(
|
|
InstructionSource(position), target, type_args_count, argument_names,
|
|
arguments, ic_data_array_, GetNextDeoptId(), rebind_rule);
|
|
SetResultTypeForStaticCall(call, target, argument_count, result_type);
|
|
if (use_unchecked_entry) {
|
|
call->set_entry_kind(Code::EntryKind::kUnchecked);
|
|
}
|
|
Push(call);
|
|
if (result_type != nullptr && result_type->IsConstant()) {
|
|
Fragment instructions(call);
|
|
instructions += Drop();
|
|
instructions += Constant(result_type->constant_value);
|
|
return instructions;
|
|
}
|
|
return Fragment(call);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::StringInterpolateSingle(TokenPosition position) {
|
|
const int kTypeArgsLen = 0;
|
|
const int kNumberOfArguments = 1;
|
|
const Array& kNoArgumentNames = Object::null_array();
|
|
const Class& cls =
|
|
Class::Handle(Library::LookupCoreClass(Symbols::StringBase()));
|
|
ASSERT(!cls.IsNull());
|
|
const Function& function = Function::ZoneHandle(
|
|
Z, Resolver::ResolveStatic(
|
|
cls, Library::PrivateCoreLibName(Symbols::InterpolateSingle()),
|
|
kTypeArgsLen, kNumberOfArguments, kNoArgumentNames));
|
|
Fragment instructions;
|
|
instructions +=
|
|
StaticCall(position, function, /* argument_count = */ 1, ICData::kStatic);
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ThrowTypeError() {
|
|
const Class& klass =
|
|
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::TypeError()));
|
|
ASSERT(!klass.IsNull());
|
|
GrowableHandlePtrArray<const String> pieces(Z, 3);
|
|
pieces.Add(Symbols::TypeError());
|
|
pieces.Add(Symbols::Dot());
|
|
pieces.Add(H.DartSymbolObfuscate("_create"));
|
|
|
|
const Function& constructor = Function::ZoneHandle(
|
|
Z, klass.LookupConstructorAllowPrivate(
|
|
String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces))));
|
|
ASSERT(!constructor.IsNull());
|
|
|
|
const String& url = H.DartString(
|
|
parsed_function_->function().ToLibNamePrefixedQualifiedCString(),
|
|
Heap::kOld);
|
|
|
|
Fragment instructions;
|
|
|
|
// Create instance of _FallThroughError
|
|
instructions += AllocateObject(TokenPosition::kNoSource, klass, 0);
|
|
LocalVariable* instance = MakeTemporary();
|
|
|
|
// Call _TypeError._create constructor.
|
|
instructions += LoadLocal(instance); // this
|
|
instructions += Constant(url); // url
|
|
instructions += NullConstant(); // line
|
|
instructions += IntConstant(0); // column
|
|
instructions += Constant(H.DartSymbolPlain("Malformed type.")); // message
|
|
|
|
instructions += StaticCall(TokenPosition::kNoSource, constructor,
|
|
/* argument_count = */ 5, ICData::kStatic);
|
|
instructions += Drop();
|
|
|
|
// Throw the exception
|
|
instructions += ThrowException(TokenPosition::kNoSource);
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ThrowNoSuchMethodError(const Function& target) {
|
|
const Class& klass = Class::ZoneHandle(
|
|
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
|
|
ASSERT(!klass.IsNull());
|
|
const auto& error = klass.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
const Function& throw_function = Function::ZoneHandle(
|
|
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew()));
|
|
ASSERT(!throw_function.IsNull());
|
|
|
|
Fragment instructions;
|
|
|
|
const Class& owner = Class::Handle(Z, target.Owner());
|
|
AbstractType& receiver = AbstractType::ZoneHandle();
|
|
InvocationMirror::Kind kind = InvocationMirror::Kind::kMethod;
|
|
if (target.IsImplicitGetterFunction() || target.IsGetterFunction()) {
|
|
kind = InvocationMirror::kGetter;
|
|
} else if (target.IsImplicitSetterFunction() || target.IsSetterFunction()) {
|
|
kind = InvocationMirror::kSetter;
|
|
}
|
|
InvocationMirror::Level level;
|
|
if (owner.IsTopLevel()) {
|
|
level = InvocationMirror::Level::kTopLevel;
|
|
} else {
|
|
receiver = owner.RareType();
|
|
if (target.kind() == UntaggedFunction::kConstructor) {
|
|
level = InvocationMirror::Level::kConstructor;
|
|
} else {
|
|
level = InvocationMirror::Level::kStatic;
|
|
}
|
|
}
|
|
|
|
// Call NoSuchMethodError._throwNew static function.
|
|
instructions += Constant(receiver); // receiver
|
|
instructions += Constant(String::ZoneHandle(Z, target.name())); // memberName
|
|
instructions += IntConstant(InvocationMirror::EncodeType(level, kind));
|
|
instructions += IntConstant(0); // type arguments length
|
|
instructions += NullConstant(); // type arguments
|
|
instructions += NullConstant(); // arguments
|
|
instructions += NullConstant(); // argumentNames
|
|
|
|
instructions += StaticCall(TokenPosition::kNoSource, throw_function,
|
|
/* argument_count = */ 7, ICData::kStatic);
|
|
|
|
// Properly close graph with a ThrowInstr, although it is not executed.
|
|
instructions += ThrowException(TokenPosition::kNoSource);
|
|
instructions += Drop();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
LocalVariable* FlowGraphBuilder::LookupVariable(intptr_t kernel_offset) {
|
|
LocalVariable* local = scopes_->locals.Lookup(kernel_offset);
|
|
ASSERT(local != NULL);
|
|
return local;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraph() {
|
|
const Function& function = parsed_function_->function();
|
|
|
|
#ifdef DEBUG
|
|
// If we attached the native name to the function after it's creation (namely
|
|
// after reading the constant table from the kernel blob), we must have done
|
|
// so before building flow graph for the functions (since FGB depends needs
|
|
// the native name to be there).
|
|
const Script& script = Script::Handle(Z, function.script());
|
|
const KernelProgramInfo& info =
|
|
KernelProgramInfo::Handle(script.kernel_program_info());
|
|
ASSERT(info.IsNull() ||
|
|
info.potential_natives() == GrowableObjectArray::null());
|
|
|
|
// Check that all functions that are explicitly marked as recognized with the
|
|
// vm:recognized annotation are in fact recognized. The check can't be done on
|
|
// function creation, since the recognized status isn't set until later.
|
|
if ((function.IsRecognized() !=
|
|
MethodRecognizer::IsMarkedAsRecognized(function)) &&
|
|
!function.IsDynamicInvocationForwarder()) {
|
|
if (function.IsRecognized()) {
|
|
FATAL1(
|
|
"Recognized method %s is not marked with the vm:recognized pragma.",
|
|
function.ToQualifiedCString());
|
|
} else {
|
|
FATAL1(
|
|
"Non-recognized method %s is marked with the vm:recognized pragma.",
|
|
function.ToQualifiedCString());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
auto& kernel_data = ExternalTypedData::Handle(Z, function.KernelData());
|
|
intptr_t kernel_data_program_offset = function.KernelDataProgramOffset();
|
|
|
|
StreamingFlowGraphBuilder streaming_flow_graph_builder(
|
|
this, kernel_data, kernel_data_program_offset);
|
|
return streaming_flow_graph_builder.BuildGraph();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
|
|
LocalVariable* first_parameter) {
|
|
ASSERT(function.is_native());
|
|
ASSERT(!IsRecognizedMethodForFlowGraph(function));
|
|
|
|
Fragment body;
|
|
String& name = String::ZoneHandle(Z, function.native_name());
|
|
if (function.IsGeneric()) {
|
|
body += LoadLocal(parsed_function_->RawTypeArgumentsVariable());
|
|
}
|
|
for (intptr_t i = 0; i < function.NumParameters(); ++i) {
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(i));
|
|
}
|
|
body += NativeCall(&name, &function);
|
|
// We typecheck results of native calls for type safety.
|
|
body +=
|
|
Return(TokenPosition::kNoSource, /* omit_result_type_check = */ false);
|
|
return body;
|
|
}
|
|
|
|
bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
|
|
const Function& function) {
|
|
const MethodRecognizer::Kind kind = function.recognized_kind();
|
|
|
|
switch (kind) {
|
|
case MethodRecognizer::kTypedData_ByteDataView_factory:
|
|
case MethodRecognizer::kTypedData_Int8ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Uint8ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Int16ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Uint16ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Int32ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Uint32ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Int64ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Uint64ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Float32ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Float64ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
|
|
case MethodRecognizer::kTypedData_Int8Array_factory:
|
|
case MethodRecognizer::kTypedData_Uint8Array_factory:
|
|
case MethodRecognizer::kTypedData_Uint8ClampedArray_factory:
|
|
case MethodRecognizer::kTypedData_Int16Array_factory:
|
|
case MethodRecognizer::kTypedData_Uint16Array_factory:
|
|
case MethodRecognizer::kTypedData_Int32Array_factory:
|
|
case MethodRecognizer::kTypedData_Uint32Array_factory:
|
|
case MethodRecognizer::kTypedData_Int64Array_factory:
|
|
case MethodRecognizer::kTypedData_Uint64Array_factory:
|
|
case MethodRecognizer::kTypedData_Float32Array_factory:
|
|
case MethodRecognizer::kTypedData_Float64Array_factory:
|
|
case MethodRecognizer::kTypedData_Float32x4Array_factory:
|
|
case MethodRecognizer::kTypedData_Int32x4Array_factory:
|
|
case MethodRecognizer::kTypedData_Float64x2Array_factory:
|
|
case MethodRecognizer::kFfiLoadInt8:
|
|
case MethodRecognizer::kFfiLoadInt16:
|
|
case MethodRecognizer::kFfiLoadInt32:
|
|
case MethodRecognizer::kFfiLoadInt64:
|
|
case MethodRecognizer::kFfiLoadUint8:
|
|
case MethodRecognizer::kFfiLoadUint16:
|
|
case MethodRecognizer::kFfiLoadUint32:
|
|
case MethodRecognizer::kFfiLoadUint64:
|
|
case MethodRecognizer::kFfiLoadIntPtr:
|
|
case MethodRecognizer::kFfiLoadFloat:
|
|
case MethodRecognizer::kFfiLoadFloatUnaligned:
|
|
case MethodRecognizer::kFfiLoadDouble:
|
|
case MethodRecognizer::kFfiLoadDoubleUnaligned:
|
|
case MethodRecognizer::kFfiLoadPointer:
|
|
case MethodRecognizer::kFfiStoreInt8:
|
|
case MethodRecognizer::kFfiStoreInt16:
|
|
case MethodRecognizer::kFfiStoreInt32:
|
|
case MethodRecognizer::kFfiStoreInt64:
|
|
case MethodRecognizer::kFfiStoreUint8:
|
|
case MethodRecognizer::kFfiStoreUint16:
|
|
case MethodRecognizer::kFfiStoreUint32:
|
|
case MethodRecognizer::kFfiStoreUint64:
|
|
case MethodRecognizer::kFfiStoreIntPtr:
|
|
case MethodRecognizer::kFfiStoreFloat:
|
|
case MethodRecognizer::kFfiStoreFloatUnaligned:
|
|
case MethodRecognizer::kFfiStoreDouble:
|
|
case MethodRecognizer::kFfiStoreDoubleUnaligned:
|
|
case MethodRecognizer::kFfiStorePointer:
|
|
case MethodRecognizer::kFfiFromAddress:
|
|
case MethodRecognizer::kFfiGetAddress:
|
|
case MethodRecognizer::kObjectEquals:
|
|
case MethodRecognizer::kStringBaseLength:
|
|
case MethodRecognizer::kStringBaseIsEmpty:
|
|
case MethodRecognizer::kGrowableArrayLength:
|
|
case MethodRecognizer::kObjectArrayLength:
|
|
case MethodRecognizer::kImmutableArrayLength:
|
|
case MethodRecognizer::kTypedListLength:
|
|
case MethodRecognizer::kTypedListViewLength:
|
|
case MethodRecognizer::kByteDataViewLength:
|
|
case MethodRecognizer::kByteDataViewOffsetInBytes:
|
|
case MethodRecognizer::kTypedDataViewOffsetInBytes:
|
|
case MethodRecognizer::kByteDataViewTypedData:
|
|
case MethodRecognizer::kTypedDataViewTypedData:
|
|
case MethodRecognizer::kClassIDgetID:
|
|
case MethodRecognizer::kGrowableArrayCapacity:
|
|
case MethodRecognizer::kListFactory:
|
|
case MethodRecognizer::kObjectArrayAllocate:
|
|
case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString:
|
|
case MethodRecognizer::kLinkedHashMap_getIndex:
|
|
case MethodRecognizer::kLinkedHashMap_setIndex:
|
|
case MethodRecognizer::kLinkedHashMap_getData:
|
|
case MethodRecognizer::kLinkedHashMap_setData:
|
|
case MethodRecognizer::kLinkedHashMap_getHashMask:
|
|
case MethodRecognizer::kLinkedHashMap_setHashMask:
|
|
case MethodRecognizer::kLinkedHashMap_getUsedData:
|
|
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
|
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
|
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
|
case MethodRecognizer::kWeakProperty_getKey:
|
|
case MethodRecognizer::kWeakProperty_setKey:
|
|
case MethodRecognizer::kWeakProperty_getValue:
|
|
case MethodRecognizer::kWeakProperty_setValue:
|
|
case MethodRecognizer::kFfiAbi:
|
|
case MethodRecognizer::kReachabilityFence:
|
|
case MethodRecognizer::kUtf8DecoderScan:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
|
|
const Function& function) {
|
|
ASSERT(IsRecognizedMethodForFlowGraph(function));
|
|
|
|
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());
|
|
|
|
const MethodRecognizer::Kind kind = function.recognized_kind();
|
|
switch (kind) {
|
|
case MethodRecognizer::kTypedData_ByteDataView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function, kByteDataViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int8ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt8ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataUint8ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint8ClampedArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int16ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt16ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint16ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint16ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat32x4ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataInt32x4ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat64x2ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int8Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataInt8ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataUint8ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8ClampedArray_factory:
|
|
body += BuildTypedDataFactoryConstructor(function,
|
|
kTypedDataUint8ClampedArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int16Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataInt16ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint16Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataUint16ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataInt32ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint32Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataUint32ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int64Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataInt64ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint64Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataUint64ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataFloat32ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataFloat64ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32x4Array_factory:
|
|
body += BuildTypedDataFactoryConstructor(function,
|
|
kTypedDataFloat32x4ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32x4Array_factory:
|
|
body +=
|
|
BuildTypedDataFactoryConstructor(function, kTypedDataInt32x4ArrayCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64x2Array_factory:
|
|
body += BuildTypedDataFactoryConstructor(function,
|
|
kTypedDataFloat64x2ArrayCid);
|
|
break;
|
|
|
|
case MethodRecognizer::kObjectEquals:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StrictCompare(Token::kEQ_STRICT);
|
|
break;
|
|
case MethodRecognizer::kStringBaseLength:
|
|
case MethodRecognizer::kStringBaseIsEmpty:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::String_length());
|
|
if (kind == MethodRecognizer::kStringBaseIsEmpty) {
|
|
body += IntConstant(0);
|
|
body += StrictCompare(Token::kEQ_STRICT);
|
|
}
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayLength:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::GrowableObjectArray_length());
|
|
break;
|
|
case MethodRecognizer::kObjectArrayLength:
|
|
case MethodRecognizer::kImmutableArrayLength:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::Array_length());
|
|
break;
|
|
case MethodRecognizer::kTypedListLength:
|
|
case MethodRecognizer::kTypedListViewLength:
|
|
case MethodRecognizer::kByteDataViewLength:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::TypedDataBase_length());
|
|
break;
|
|
case MethodRecognizer::kByteDataViewOffsetInBytes:
|
|
case MethodRecognizer::kTypedDataViewOffsetInBytes:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::TypedDataView_offset_in_bytes());
|
|
break;
|
|
case MethodRecognizer::kByteDataViewTypedData:
|
|
case MethodRecognizer::kTypedDataViewTypedData:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::TypedDataView_data());
|
|
break;
|
|
case MethodRecognizer::kClassIDgetID:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadClassId();
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayCapacity:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::GrowableObjectArray_data());
|
|
body += LoadNativeField(Slot::Array_length());
|
|
break;
|
|
case MethodRecognizer::kListFactory: {
|
|
ASSERT(function.IsFactory() && (function.NumParameters() == 2) &&
|
|
function.HasOptionalParameters());
|
|
// factory List<E>([int length]) {
|
|
// return (:arg_desc.positional_count == 2) ? new _List<E>(length)
|
|
// : new _GrowableList<E>(0);
|
|
// }
|
|
const Library& core_lib = Library::Handle(Z, Library::CoreLibrary());
|
|
|
|
TargetEntryInstr *allocate_non_growable, *allocate_growable;
|
|
|
|
body += LoadArgDescriptor();
|
|
body += LoadNativeField(Slot::ArgumentsDescriptor_positional_count());
|
|
body += IntConstant(2);
|
|
body += BranchIfStrictEqual(&allocate_non_growable, &allocate_growable);
|
|
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
|
|
{
|
|
const Class& cls = Class::Handle(
|
|
Z, core_lib.LookupClass(
|
|
Library::PrivateCoreLibName(Symbols::_List())));
|
|
ASSERT(!cls.IsNull());
|
|
const Function& func = Function::ZoneHandle(
|
|
Z, cls.LookupFactoryAllowPrivate(Symbols::_ListFactory()));
|
|
ASSERT(!func.IsNull());
|
|
|
|
Fragment allocate(allocate_non_growable);
|
|
allocate += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
allocate += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
allocate +=
|
|
StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic);
|
|
allocate += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->expression_temp_var());
|
|
allocate += Drop();
|
|
allocate += Goto(join);
|
|
}
|
|
|
|
{
|
|
const Class& cls = Class::Handle(
|
|
Z, core_lib.LookupClass(
|
|
Library::PrivateCoreLibName(Symbols::_GrowableList())));
|
|
ASSERT(!cls.IsNull());
|
|
const Function& func = Function::ZoneHandle(
|
|
Z, cls.LookupFactoryAllowPrivate(Symbols::_GrowableListFactory()));
|
|
ASSERT(!func.IsNull());
|
|
|
|
Fragment allocate(allocate_growable);
|
|
allocate += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
allocate += IntConstant(0);
|
|
allocate +=
|
|
StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic);
|
|
allocate += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->expression_temp_var());
|
|
allocate += Drop();
|
|
allocate += Goto(join);
|
|
}
|
|
|
|
body = Fragment(body.entry, join);
|
|
body += LoadLocal(parsed_function_->expression_temp_var());
|
|
break;
|
|
}
|
|
case MethodRecognizer::kObjectArrayAllocate:
|
|
ASSERT(function.IsFactory() && (function.NumParameters() == 2));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += CreateArray();
|
|
break;
|
|
case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString:
|
|
ASSERT(function.NumParameters() == 5);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(2));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(3));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(4));
|
|
body += MemoryCopy(kTypedDataUint8ArrayCid, kOneByteStringCid);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getIndex:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::LinkedHashMap_index());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setIndex:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_index());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getData:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::LinkedHashMap_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setData:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_data());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getHashMask:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::LinkedHashMap_hash_mask());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setHashMask:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::LinkedHashMap_hash_mask(),
|
|
StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getUsedData:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::LinkedHashMap_used_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::LinkedHashMap_used_data(),
|
|
StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::LinkedHashMap_deleted_keys());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::LinkedHashMap_deleted_keys(),
|
|
StoreInstanceFieldInstr::Kind::kOther, kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kWeakProperty_getKey:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::WeakProperty_key());
|
|
break;
|
|
case MethodRecognizer::kWeakProperty_setKey:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::WeakProperty_key());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kWeakProperty_getValue:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(Slot::WeakProperty_value());
|
|
break;
|
|
case MethodRecognizer::kWeakProperty_setValue:
|
|
ASSERT(function.NumParameters() == 2);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1));
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::WeakProperty_value());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kUtf8DecoderScan:
|
|
ASSERT(function.NumParameters() == 5);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // decoder
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(1)); // bytes
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(2)); // start
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
body += UnboxTruncate(kUnboxedIntPtr);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(3)); // end
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
body += UnboxTruncate(kUnboxedIntPtr);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(4)); // table
|
|
body += Utf8Scan();
|
|
body += Box(kUnboxedIntPtr);
|
|
break;
|
|
case MethodRecognizer::kReachabilityFence:
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += ReachabilityFence();
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kFfiAbi:
|
|
ASSERT(function.NumParameters() == 0);
|
|
body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
|
|
break;
|
|
case MethodRecognizer::kFfiLoadInt8:
|
|
case MethodRecognizer::kFfiLoadInt16:
|
|
case MethodRecognizer::kFfiLoadInt32:
|
|
case MethodRecognizer::kFfiLoadInt64:
|
|
case MethodRecognizer::kFfiLoadUint8:
|
|
case MethodRecognizer::kFfiLoadUint16:
|
|
case MethodRecognizer::kFfiLoadUint32:
|
|
case MethodRecognizer::kFfiLoadUint64:
|
|
case MethodRecognizer::kFfiLoadIntPtr:
|
|
case MethodRecognizer::kFfiLoadFloat:
|
|
case MethodRecognizer::kFfiLoadFloatUnaligned:
|
|
case MethodRecognizer::kFfiLoadDouble:
|
|
case MethodRecognizer::kFfiLoadDoubleUnaligned:
|
|
case MethodRecognizer::kFfiLoadPointer: {
|
|
const classid_t ffi_type_arg_cid =
|
|
compiler::ffi::RecognizedMethodTypeArgCid(kind);
|
|
const AlignmentType alignment =
|
|
compiler::ffi::RecognizedMethodAlignment(kind);
|
|
const classid_t typed_data_cid =
|
|
compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid);
|
|
|
|
ASSERT(function.NumParameters() == 2);
|
|
LocalVariable* arg_pointer = parsed_function_->RawParameterVariable(0);
|
|
LocalVariable* arg_offset = parsed_function_->RawParameterVariable(1);
|
|
|
|
body += LoadLocal(arg_offset);
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
LocalVariable* arg_offset_not_null = MakeTemporary();
|
|
|
|
body += LoadLocal(arg_pointer);
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
// No GC from here til LoadIndexed.
|
|
body += LoadUntagged(compiler::target::PointerBase::data_field_offset());
|
|
body += LoadLocal(arg_offset_not_null);
|
|
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
body += LoadIndexed(typed_data_cid, /*index_scale=*/1,
|
|
/*index_unboxed=*/true, alignment);
|
|
if (kind == MethodRecognizer::kFfiLoadFloat ||
|
|
kind == MethodRecognizer::kFfiLoadFloatUnaligned) {
|
|
body += FloatToDouble();
|
|
}
|
|
// Avoid any unnecessary (and potentially deoptimizing) int
|
|
// conversions by using the representation returned from LoadIndexed.
|
|
body +=
|
|
Box(LoadIndexedInstr::RepresentationOfArrayElement(typed_data_cid));
|
|
if (kind == MethodRecognizer::kFfiLoadPointer) {
|
|
const auto class_table = thread_->isolate_group()->class_table();
|
|
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
|
const auto& pointer_class =
|
|
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
|
|
|
// We find the reified type to use for the pointer allocation.
|
|
//
|
|
// Call sites to this recognized method are guaranteed to pass a
|
|
// Pointer<Pointer<X>> as RawParameterVariable(0). This function
|
|
// will return a Pointer<X> object - for which we inspect the
|
|
// reified type on the argument.
|
|
//
|
|
// The following is safe to do, as (1) we are guaranteed to have a
|
|
// Pointer<Pointer<X>> as argument, and (2) the bound on the pointer
|
|
// type parameter guarantees X is an interface type.
|
|
ASSERT(function.NumTypeParameters() == 1);
|
|
LocalVariable* address = MakeTemporary();
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
|
body += LoadNativeField(
|
|
Slot::GetTypeArgumentsSlotFor(thread_, pointer_class));
|
|
body += LoadNativeField(Slot::GetTypeArgumentsIndexSlot(
|
|
thread_, Pointer::kNativeTypeArgPos));
|
|
body += LoadNativeField(Slot::Type_arguments());
|
|
body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1);
|
|
LocalVariable* pointer = MakeTemporary();
|
|
body += LoadLocal(pointer);
|
|
body += LoadLocal(address);
|
|
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
|
|
body += StoreUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += DropTempsPreserveTop(1); // Drop [address] keep [pointer].
|
|
}
|
|
body += DropTempsPreserveTop(1); // Drop [arg_offset].
|
|
} break;
|
|
case MethodRecognizer::kFfiStoreInt8:
|
|
case MethodRecognizer::kFfiStoreInt16:
|
|
case MethodRecognizer::kFfiStoreInt32:
|
|
case MethodRecognizer::kFfiStoreInt64:
|
|
case MethodRecognizer::kFfiStoreUint8:
|
|
case MethodRecognizer::kFfiStoreUint16:
|
|
case MethodRecognizer::kFfiStoreUint32:
|
|
case MethodRecognizer::kFfiStoreUint64:
|
|
case MethodRecognizer::kFfiStoreIntPtr:
|
|
case MethodRecognizer::kFfiStoreFloat:
|
|
case MethodRecognizer::kFfiStoreFloatUnaligned:
|
|
case MethodRecognizer::kFfiStoreDouble:
|
|
case MethodRecognizer::kFfiStoreDoubleUnaligned:
|
|
case MethodRecognizer::kFfiStorePointer: {
|
|
const classid_t ffi_type_arg_cid =
|
|
compiler::ffi::RecognizedMethodTypeArgCid(kind);
|
|
const AlignmentType alignment =
|
|
compiler::ffi::RecognizedMethodAlignment(kind);
|
|
const classid_t typed_data_cid =
|
|
compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid);
|
|
|
|
LocalVariable* arg_pointer = parsed_function_->RawParameterVariable(0);
|
|
LocalVariable* arg_offset = parsed_function_->RawParameterVariable(1);
|
|
LocalVariable* arg_value = parsed_function_->RawParameterVariable(2);
|
|
|
|
if (kind == MethodRecognizer::kFfiStorePointer) {
|
|
// Do type check before anything untagged is on the stack.
|
|
const auto class_table = thread_->isolate_group()->class_table();
|
|
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
|
const auto& pointer_class =
|
|
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
|
const auto& pointer_type_args =
|
|
TypeArguments::Handle(pointer_class.type_parameters());
|
|
const auto& pointer_type_arg =
|
|
AbstractType::ZoneHandle(pointer_type_args.TypeAt(0));
|
|
|
|
// But we type check it as a method on a generic class at runtime.
|
|
body += LoadLocal(arg_value); // value.
|
|
body += Constant(pointer_type_arg); // dst_type.
|
|
// We pass the Pointer type argument as instantiator_type_args.
|
|
//
|
|
// Call sites to this recognized method are guaranteed to pass a
|
|
// Pointer<Pointer<X>> as RawParameterVariable(0). This function
|
|
// will takes a Pointer<X> object - for which we inspect the
|
|
// reified type on the argument.
|
|
//
|
|
// The following is safe to do, as (1) we are guaranteed to have a
|
|
// Pointer<Pointer<X>> as argument, and (2) the bound on the pointer
|
|
// type parameter guarantees X is an interface type.
|
|
body += LoadLocal(arg_pointer);
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
body += LoadNativeField(
|
|
Slot::GetTypeArgumentsSlotFor(thread_, pointer_class));
|
|
body += NullConstant(); // function_type_args.
|
|
body += AssertAssignable(TokenPosition::kNoSource, Symbols::Empty());
|
|
body += Drop();
|
|
}
|
|
|
|
ASSERT(function.NumParameters() == 3);
|
|
body += LoadLocal(arg_offset);
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
LocalVariable* arg_offset_not_null = MakeTemporary();
|
|
body += LoadLocal(arg_value);
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
LocalVariable* arg_value_not_null = MakeTemporary();
|
|
|
|
body += LoadLocal(arg_pointer); // Pointer.
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
// No GC from here til StoreIndexed.
|
|
body += LoadUntagged(compiler::target::PointerBase::data_field_offset());
|
|
body += LoadLocal(arg_offset_not_null);
|
|
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
body += LoadLocal(arg_value_not_null);
|
|
if (kind == MethodRecognizer::kFfiStorePointer) {
|
|
// This can only be Pointer, so it is always safe to LoadUntagged.
|
|
body += LoadUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
|
|
} else {
|
|
// Avoid any unnecessary (and potentially deoptimizing) int
|
|
// conversions by using the representation consumed by StoreIndexed.
|
|
body += UnboxTruncate(
|
|
StoreIndexedInstr::RepresentationOfArrayElement(typed_data_cid));
|
|
if (kind == MethodRecognizer::kFfiStoreFloat ||
|
|
kind == MethodRecognizer::kFfiStoreFloatUnaligned) {
|
|
body += DoubleToFloat();
|
|
}
|
|
}
|
|
body += StoreIndexedTypedData(typed_data_cid, /*index_scale=*/1,
|
|
/*index_unboxed=*/true, alignment);
|
|
body += Drop(); // Drop [arg_value].
|
|
body += Drop(); // Drop [arg_offset].
|
|
body += NullConstant();
|
|
} break;
|
|
case MethodRecognizer::kFfiFromAddress: {
|
|
const auto class_table = thread_->isolate_group()->class_table();
|
|
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
|
const auto& pointer_class =
|
|
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
|
|
|
ASSERT(function.NumTypeParameters() == 1);
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawTypeArgumentsVariable());
|
|
body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1);
|
|
body += LoadLocal(MakeTemporary()); // Duplicate Pointer.
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Address.
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
|
|
body += StoreUntagged(compiler::target::Pointer::data_field_offset());
|
|
} break;
|
|
case MethodRecognizer::kFfiGetAddress: {
|
|
ASSERT(function.NumParameters() == 1);
|
|
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Pointer.
|
|
body += CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, function.name()));
|
|
// This can only be Pointer, so it is always safe to LoadUntagged.
|
|
body += LoadUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
|
|
body += Box(kUnboxedFfiIntPtr);
|
|
} break;
|
|
default: {
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource, /* omit_result_type_check = */ true);
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildTypedDataViewFactoryConstructor(
|
|
const Function& function,
|
|
classid_t cid) {
|
|
auto token_pos = function.token_pos();
|
|
auto class_table = Thread::Current()->isolate_group()->class_table();
|
|
|
|
ASSERT(class_table->HasValidClassAt(cid));
|
|
const auto& view_class = Class::ZoneHandle(H.zone(), class_table->At(cid));
|
|
|
|
ASSERT(function.IsFactory() && (function.NumParameters() == 4));
|
|
LocalVariable* typed_data = parsed_function_->RawParameterVariable(1);
|
|
LocalVariable* offset_in_bytes = parsed_function_->RawParameterVariable(2);
|
|
LocalVariable* length = parsed_function_->RawParameterVariable(3);
|
|
|
|
Fragment body;
|
|
|
|
body += AllocateObject(token_pos, view_class, /*arg_count=*/0);
|
|
LocalVariable* view_object = MakeTemporary();
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(typed_data);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataView_data(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(offset_in_bytes);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataView_offset_in_bytes(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing,
|
|
kNoStoreBarrier);
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(length);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataBase_length(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing,
|
|
kNoStoreBarrier);
|
|
|
|
// Update the inner pointer.
|
|
//
|
|
// WARNING: Notice that we assume here no GC happens between those 4
|
|
// instructions!
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(typed_data);
|
|
body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset());
|
|
body += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
|
|
body += LoadLocal(offset_in_bytes);
|
|
body += UnboxSmiToIntptr();
|
|
body += AddIntptrIntegers();
|
|
body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
|
|
body += StoreUntagged(compiler::target::TypedDataBase::data_field_offset());
|
|
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildTypedDataFactoryConstructor(
|
|
const Function& function,
|
|
classid_t cid) {
|
|
const auto token_pos = function.token_pos();
|
|
ASSERT(
|
|
Thread::Current()->isolate_group()->class_table()->HasValidClassAt(cid));
|
|
|
|
ASSERT(function.IsFactory() && (function.NumParameters() == 2));
|
|
LocalVariable* length = parsed_function_->RawParameterVariable(1);
|
|
|
|
Fragment instructions;
|
|
instructions += LoadLocal(length);
|
|
// AllocateTypedData instruction checks that length is valid (a non-negative
|
|
// Smi below maximum allowed length).
|
|
instructions += AllocateTypedData(token_pos, cid);
|
|
return instructions;
|
|
}
|
|
|
|
static const LocalScope* MakeImplicitClosureScope(Zone* Z, const Class& klass) {
|
|
ASSERT(!klass.IsNull());
|
|
// Note that if klass is _Closure, DeclarationType will be _Closure,
|
|
// and not the signature type.
|
|
Type& klass_type = Type::ZoneHandle(Z, klass.DeclarationType());
|
|
|
|
LocalVariable* receiver_variable = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
Symbols::This(), klass_type, /*param_type=*/nullptr);
|
|
|
|
receiver_variable->set_is_captured();
|
|
// receiver_variable->set_is_final();
|
|
LocalScope* scope = new (Z) LocalScope(NULL, 0, 0);
|
|
scope->set_context_level(0);
|
|
scope->AddVariable(receiver_variable);
|
|
scope->AddContextVariable(receiver_variable);
|
|
return scope;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildImplicitClosureCreation(
|
|
const Function& target) {
|
|
Fragment fragment;
|
|
fragment += AllocateClosure(TokenPosition::kNoSource, target);
|
|
LocalVariable* closure = MakeTemporary();
|
|
|
|
// The function signature can have uninstantiated class type parameters.
|
|
if (!target.HasInstantiatedSignature(kCurrentClass)) {
|
|
fragment += LoadLocal(closure);
|
|
fragment += LoadInstantiatorTypeArguments();
|
|
fragment += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::Closure_instantiator_type_arguments(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
}
|
|
|
|
// The function cannot be local and have parent generic functions.
|
|
ASSERT(!target.HasGenericParent());
|
|
|
|
// Allocate a context that closes over `this`.
|
|
// Note: this must be kept in sync with ScopeBuilder::BuildScopes.
|
|
const LocalScope* implicit_closure_scope =
|
|
MakeImplicitClosureScope(Z, Class::Handle(Z, target.Owner()));
|
|
fragment += AllocateContext(implicit_closure_scope->context_slots());
|
|
LocalVariable* context = MakeTemporary();
|
|
|
|
// Store the function and the context in the closure.
|
|
fragment += LoadLocal(closure);
|
|
fragment += Constant(target);
|
|
fragment +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_function(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
|
|
fragment += LoadLocal(closure);
|
|
fragment += LoadLocal(context);
|
|
fragment +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_context(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
|
|
if (target.IsGeneric()) {
|
|
// Only generic functions need to have properly initialized
|
|
// delayed_type_arguments.
|
|
fragment += LoadLocal(closure);
|
|
fragment += Constant(Object::empty_type_arguments());
|
|
fragment += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::Closure_delayed_type_arguments(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
}
|
|
|
|
// The context is on top of the operand stack. Store `this`. The context
|
|
// doesn't need a parent pointer because it doesn't close over anything
|
|
// else.
|
|
fragment += LoadLocal(parsed_function_->receiver_var());
|
|
fragment += StoreInstanceField(
|
|
TokenPosition::kNoSource,
|
|
Slot::GetContextVariableSlotFor(
|
|
thread_, *implicit_closure_scope->context_variables()[0]),
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
|
|
return fragment;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CheckVariableTypeInCheckedMode(
|
|
const AbstractType& dst_type,
|
|
const String& name_symbol) {
|
|
return Fragment();
|
|
}
|
|
|
|
bool FlowGraphBuilder::NeedsDebugStepCheck(const Function& function,
|
|
TokenPosition position) {
|
|
return position.IsDebugPause() && !function.is_native() &&
|
|
function.is_debuggable();
|
|
}
|
|
|
|
bool FlowGraphBuilder::NeedsDebugStepCheck(Value* value,
|
|
TokenPosition position) {
|
|
if (!position.IsDebugPause()) {
|
|
return false;
|
|
}
|
|
Definition* definition = value->definition();
|
|
if (definition->IsConstant() || definition->IsLoadStaticField()) {
|
|
return true;
|
|
}
|
|
if (definition->IsAllocateObject()) {
|
|
return !definition->AsAllocateObject()->closure_function().IsNull();
|
|
}
|
|
return definition->IsLoadLocal();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::EvaluateAssertion() {
|
|
const Class& klass =
|
|
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError()));
|
|
ASSERT(!klass.IsNull());
|
|
const auto& error = klass.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
const Function& target = Function::ZoneHandle(
|
|
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::EvaluateAssertion()));
|
|
ASSERT(!target.IsNull());
|
|
return StaticCall(TokenPosition::kNoSource, target, /* argument_count = */ 1,
|
|
ICData::kStatic);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CheckBoolean(TokenPosition position) {
|
|
Fragment instructions;
|
|
LocalVariable* top_of_stack = MakeTemporary();
|
|
instructions += LoadLocal(top_of_stack);
|
|
instructions += AssertBool(position);
|
|
instructions += Drop();
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CheckAssignable(const AbstractType& dst_type,
|
|
const String& dst_name,
|
|
AssertAssignableInstr::Kind kind) {
|
|
Fragment instructions;
|
|
if (!dst_type.IsTopTypeForSubtyping()) {
|
|
LocalVariable* top_of_stack = MakeTemporary();
|
|
instructions += LoadLocal(top_of_stack);
|
|
instructions += AssertAssignableLoadTypeArguments(TokenPosition::kNoSource,
|
|
dst_type, dst_name, kind);
|
|
instructions += Drop();
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::AssertAssignableLoadTypeArguments(
|
|
TokenPosition position,
|
|
const AbstractType& dst_type,
|
|
const String& dst_name,
|
|
AssertAssignableInstr::Kind kind) {
|
|
Fragment instructions;
|
|
|
|
instructions += Constant(AbstractType::ZoneHandle(dst_type.ptr()));
|
|
|
|
if (!dst_type.IsInstantiated(kCurrentClass)) {
|
|
instructions += LoadInstantiatorTypeArguments();
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
|
|
if (!dst_type.IsInstantiated(kFunctions)) {
|
|
instructions += LoadFunctionTypeArguments();
|
|
} else {
|
|
instructions += NullConstant();
|
|
}
|
|
|
|
instructions += AssertAssignable(position, dst_name, kind);
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position,
|
|
const AbstractType& sub_type_value,
|
|
const AbstractType& super_type_value,
|
|
const String& dst_name_value) {
|
|
Fragment instructions;
|
|
instructions += LoadInstantiatorTypeArguments();
|
|
instructions += LoadFunctionTypeArguments();
|
|
instructions += Constant(AbstractType::ZoneHandle(Z, sub_type_value.ptr()));
|
|
instructions += Constant(AbstractType::ZoneHandle(Z, super_type_value.ptr()));
|
|
instructions += Constant(String::ZoneHandle(Z, dst_name_value.ptr()));
|
|
instructions += AssertSubtype(position);
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position) {
|
|
Fragment instructions;
|
|
|
|
Value* dst_name = Pop();
|
|
Value* super_type = Pop();
|
|
Value* sub_type = Pop();
|
|
Value* function_type_args = Pop();
|
|
Value* instantiator_type_args = Pop();
|
|
|
|
AssertSubtypeInstr* instr = new (Z) AssertSubtypeInstr(
|
|
InstructionSource(position), instantiator_type_args, function_type_args,
|
|
sub_type, super_type, dst_name, GetNextDeoptId());
|
|
instructions += Fragment(instr);
|
|
|
|
return instructions;
|
|
}
|
|
|
|
void FlowGraphBuilder::BuildTypeArgumentTypeChecks(TypeChecksToBuild mode,
|
|
Fragment* implicit_checks) {
|
|
const Function& dart_function = parsed_function_->function();
|
|
|
|
const Function* forwarding_target = nullptr;
|
|
if (parsed_function_->is_forwarding_stub()) {
|
|
forwarding_target = parsed_function_->forwarding_stub_super_target();
|
|
ASSERT(!forwarding_target->IsNull());
|
|
}
|
|
|
|
TypeArguments& type_parameters = TypeArguments::Handle(Z);
|
|
if (dart_function.IsFactory()) {
|
|
type_parameters = Class::Handle(Z, dart_function.Owner()).type_parameters();
|
|
} else {
|
|
type_parameters = dart_function.type_parameters();
|
|
}
|
|
intptr_t num_type_params = type_parameters.Length();
|
|
if (forwarding_target != nullptr) {
|
|
type_parameters = forwarding_target->type_parameters();
|
|
ASSERT(type_parameters.Length() == num_type_params);
|
|
}
|
|
|
|
TypeParameter& type_param = TypeParameter::Handle(Z);
|
|
String& name = String::Handle(Z);
|
|
AbstractType& bound = AbstractType::Handle(Z);
|
|
Fragment check_bounds;
|
|
for (intptr_t i = 0; i < num_type_params; ++i) {
|
|
type_param ^= type_parameters.TypeAt(i);
|
|
|
|
bound = type_param.bound();
|
|
if (bound.IsTopTypeForSubtyping()) {
|
|
continue;
|
|
}
|
|
|
|
switch (mode) {
|
|
case TypeChecksToBuild::kCheckAllTypeParameterBounds:
|
|
break;
|
|
case TypeChecksToBuild::kCheckCovariantTypeParameterBounds:
|
|
if (!type_param.IsGenericCovariantImpl()) {
|
|
continue;
|
|
}
|
|
break;
|
|
case TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds:
|
|
if (type_param.IsGenericCovariantImpl()) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
name = type_param.name();
|
|
|
|
ASSERT(type_param.IsFinalized());
|
|
check_bounds +=
|
|
AssertSubtype(TokenPosition::kNoSource, type_param, bound, name);
|
|
}
|
|
|
|
// Type arguments passed through partial instantiation are guaranteed to be
|
|
// bounds-checked at the point of partial instantiation, so we don't need to
|
|
// check them again at the call-site.
|
|
if (dart_function.IsClosureFunction() && !check_bounds.is_empty() &&
|
|
FLAG_eliminate_type_checks) {
|
|
LocalVariable* closure = parsed_function_->ParameterVariable(0);
|
|
*implicit_checks += TestDelayedTypeArgs(closure, /*present=*/{},
|
|
/*absent=*/check_bounds);
|
|
} else {
|
|
*implicit_checks += check_bounds;
|
|
}
|
|
}
|
|
|
|
void FlowGraphBuilder::BuildArgumentTypeChecks(
|
|
Fragment* explicit_checks,
|
|
Fragment* implicit_checks,
|
|
Fragment* implicit_redefinitions) {
|
|
const Function& dart_function = parsed_function_->function();
|
|
|
|
const Function* forwarding_target = nullptr;
|
|
if (parsed_function_->is_forwarding_stub()) {
|
|
forwarding_target = parsed_function_->forwarding_stub_super_target();
|
|
ASSERT(!forwarding_target->IsNull());
|
|
}
|
|
|
|
const intptr_t num_params = dart_function.NumParameters();
|
|
for (intptr_t i = dart_function.NumImplicitParameters(); i < num_params;
|
|
++i) {
|
|
LocalVariable* param = parsed_function_->ParameterVariable(i);
|
|
const String& name = param->name();
|
|
if (!param->needs_type_check()) {
|
|
continue;
|
|
}
|
|
if (param->is_captured()) {
|
|
param = parsed_function_->RawParameterVariable(i);
|
|
}
|
|
|
|
const AbstractType* target_type = ¶m->type();
|
|
if (forwarding_target != NULL) {
|
|
// We add 1 to the parameter index to account for the receiver.
|
|
target_type =
|
|
&AbstractType::ZoneHandle(Z, forwarding_target->ParameterTypeAt(i));
|
|
}
|
|
|
|
if (target_type->IsTopTypeForSubtyping()) continue;
|
|
|
|
const bool is_covariant = param->is_explicit_covariant_parameter();
|
|
Fragment* checks = is_covariant ? explicit_checks : implicit_checks;
|
|
|
|
*checks += LoadLocal(param);
|
|
*checks += AssertAssignableLoadTypeArguments(
|
|
TokenPosition::kNoSource, *target_type, name,
|
|
AssertAssignableInstr::kParameterCheck);
|
|
*checks += StoreLocal(param);
|
|
*checks += Drop();
|
|
|
|
if (!is_covariant && implicit_redefinitions != nullptr && optimizing_) {
|
|
// We generate slightly different code in optimized vs. un-optimized code,
|
|
// which is ok since we don't allocate any deopt ids.
|
|
AssertNoDeoptIdsAllocatedScope no_deopt_allocation(thread_);
|
|
|
|
*implicit_redefinitions += LoadLocal(param);
|
|
*implicit_redefinitions += RedefinitionWithType(*target_type);
|
|
*implicit_redefinitions += StoreLocal(TokenPosition::kNoSource, param);
|
|
*implicit_redefinitions += Drop();
|
|
}
|
|
}
|
|
}
|
|
|
|
BlockEntryInstr* FlowGraphBuilder::BuildPrologue(BlockEntryInstr* normal_entry,
|
|
PrologueInfo* prologue_info) {
|
|
const bool compiling_for_osr = IsCompiledForOsr();
|
|
|
|
kernel::PrologueBuilder prologue_builder(
|
|
parsed_function_, last_used_block_id_, compiling_for_osr, IsInlining());
|
|
BlockEntryInstr* instruction_cursor =
|
|
prologue_builder.BuildPrologue(normal_entry, prologue_info);
|
|
|
|
last_used_block_id_ = prologue_builder.last_used_block_id();
|
|
|
|
return instruction_cursor;
|
|
}
|
|
|
|
ArrayPtr FlowGraphBuilder::GetOptionalParameterNames(const Function& function) {
|
|
if (!function.HasOptionalNamedParameters()) {
|
|
return Array::null();
|
|
}
|
|
|
|
const intptr_t num_fixed_params = function.num_fixed_parameters();
|
|
const intptr_t num_opt_params = function.NumOptionalNamedParameters();
|
|
const auto& names = Array::Handle(Z, Array::New(num_opt_params, Heap::kOld));
|
|
auto& name = String::Handle(Z);
|
|
for (intptr_t i = 0; i < num_opt_params; ++i) {
|
|
name = function.ParameterNameAt(num_fixed_params + i);
|
|
names.SetAt(i, name);
|
|
}
|
|
return names.ptr();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::PushExplicitParameters(
|
|
const Function& function,
|
|
const Function& target /* = Function::null_function()*/) {
|
|
Fragment instructions;
|
|
for (intptr_t i = function.NumImplicitParameters(),
|
|
n = function.NumParameters();
|
|
i < n; ++i) {
|
|
Fragment push_param = LoadLocal(parsed_function_->ParameterVariable(i));
|
|
if (!target.IsNull() && target.is_unboxed_parameter_at(i)) {
|
|
Representation to;
|
|
if (target.is_unboxed_integer_parameter_at(i)) {
|
|
to = kUnboxedInt64;
|
|
} else {
|
|
ASSERT(target.is_unboxed_double_parameter_at(i));
|
|
to = kUnboxedDouble;
|
|
}
|
|
const auto unbox = UnboxInstr::Create(to, Pop(), DeoptId::kNone,
|
|
Instruction::kNotSpeculative);
|
|
Push(unbox);
|
|
push_param += Fragment(unbox);
|
|
}
|
|
instructions += push_param;
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfMethodExtractor(
|
|
const Function& method) {
|
|
// A method extractor is the implicit getter for a method.
|
|
const Function& function =
|
|
Function::ZoneHandle(Z, method.extracted_method_closure());
|
|
|
|
graph_entry_ =
|
|
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
|
|
|
|
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
|
graph_entry_->set_normal_entry(normal_entry);
|
|
|
|
Fragment body(normal_entry);
|
|
body += CheckStackOverflowInPrologue(method.token_pos());
|
|
body += BuildImplicitClosureCreation(function);
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
// There is no prologue code for a method extractor.
|
|
PrologueInfo prologue_info(-1, -1);
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodDispatcher(
|
|
const Function& function) {
|
|
// This function is specialized for a receiver class, a method name, and
|
|
// the arguments descriptor at a call site.
|
|
const ArgumentsDescriptor descriptor(saved_args_desc_array());
|
|
|
|
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());
|
|
|
|
// The receiver is the first argument to noSuchMethod, and it is the first
|
|
// argument passed to the dispatcher function.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
|
|
// The second argument to noSuchMethod is an invocation mirror. Push the
|
|
// arguments for allocating the invocation mirror. First, the name.
|
|
body += Constant(String::ZoneHandle(Z, function.name()));
|
|
|
|
// Second, the arguments descriptor.
|
|
body += Constant(saved_args_desc_array());
|
|
|
|
// Third, an array containing the original arguments. Create it and fill
|
|
// it in.
|
|
const intptr_t receiver_index = descriptor.TypeArgsLen() > 0 ? 1 : 0;
|
|
body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
|
|
body += IntConstant(receiver_index + descriptor.Size());
|
|
body += CreateArray();
|
|
LocalVariable* array = MakeTemporary();
|
|
if (receiver_index > 0) {
|
|
LocalVariable* type_args = parsed_function_->function_type_arguments();
|
|
ASSERT(type_args != NULL);
|
|
body += LoadLocal(array);
|
|
body += IntConstant(0);
|
|
body += LoadLocal(type_args);
|
|
body += StoreIndexed(kArrayCid);
|
|
}
|
|
for (intptr_t i = 0; i < descriptor.PositionalCount(); ++i) {
|
|
body += LoadLocal(array);
|
|
body += IntConstant(receiver_index + i);
|
|
body += LoadLocal(parsed_function_->ParameterVariable(i));
|
|
body += StoreIndexed(kArrayCid);
|
|
}
|
|
String& name = String::Handle(Z);
|
|
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
|
|
const intptr_t parameter_index = descriptor.PositionAt(i);
|
|
name = descriptor.NameAt(i);
|
|
name = Symbols::New(H.thread(), name);
|
|
body += LoadLocal(array);
|
|
body += IntConstant(receiver_index + parameter_index);
|
|
body += LoadLocal(parsed_function_->ParameterVariable(parameter_index));
|
|
body += StoreIndexed(kArrayCid);
|
|
}
|
|
|
|
// Fourth, false indicating this is not a super NoSuchMethod.
|
|
body += Constant(Bool::False());
|
|
|
|
const Class& mirror_class =
|
|
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
|
|
ASSERT(!mirror_class.IsNull());
|
|
const auto& error = mirror_class.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
const Function& allocation_function = Function::ZoneHandle(
|
|
Z, mirror_class.LookupStaticFunction(
|
|
Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror())));
|
|
ASSERT(!allocation_function.IsNull());
|
|
body += StaticCall(TokenPosition::kMinSource, allocation_function,
|
|
/* argument_count = */ 4, ICData::kStatic);
|
|
|
|
const int kTypeArgsLen = 0;
|
|
ArgumentsDescriptor two_arguments(
|
|
Array::Handle(Z, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, 2)));
|
|
Function& no_such_method =
|
|
Function::ZoneHandle(Z, Resolver::ResolveDynamicForReceiverClass(
|
|
Class::Handle(Z, function.Owner()),
|
|
Symbols::NoSuchMethod(), two_arguments));
|
|
if (no_such_method.IsNull()) {
|
|
// If noSuchMethod is not found on the receiver class, call
|
|
// Object.noSuchMethod.
|
|
no_such_method = Resolver::ResolveDynamicForReceiverClass(
|
|
Class::Handle(Z, IG->object_store()->object_class()),
|
|
Symbols::NoSuchMethod(), two_arguments);
|
|
}
|
|
body += StaticCall(TokenPosition::kMinSource, no_such_method,
|
|
/* argument_count = */ 2, ICData::kNSMDispatch);
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
// Information used by the various dynamic closure call fragment builders.
|
|
struct FlowGraphBuilder::ClosureCallInfo {
|
|
ClosureCallInfo(LocalVariable* closure,
|
|
JoinEntryInstr* throw_no_such_method,
|
|
const Array& arguments_descriptor_array,
|
|
ParsedFunction::DynamicClosureCallVars* const vars)
|
|
: closure(ASSERT_NOTNULL(closure)),
|
|
throw_no_such_method(ASSERT_NOTNULL(throw_no_such_method)),
|
|
descriptor(arguments_descriptor_array),
|
|
vars(ASSERT_NOTNULL(vars)) {}
|
|
|
|
LocalVariable* const closure;
|
|
JoinEntryInstr* const throw_no_such_method;
|
|
const ArgumentsDescriptor descriptor;
|
|
ParsedFunction::DynamicClosureCallVars* const vars;
|
|
|
|
// Set up by BuildDynamicCallChecks() when needed. These values are
|
|
// read-only, so they don't need real local variables and are created
|
|
// using MakeTemporary().
|
|
LocalVariable* signature = nullptr;
|
|
LocalVariable* num_fixed_params = nullptr;
|
|
LocalVariable* num_opt_params = nullptr;
|
|
LocalVariable* num_max_params = nullptr;
|
|
LocalVariable* has_named_params = nullptr;
|
|
LocalVariable* parameter_names = nullptr;
|
|
LocalVariable* parameter_types = nullptr;
|
|
LocalVariable* type_parameters = nullptr;
|
|
LocalVariable* instantiator_type_args = nullptr;
|
|
LocalVariable* parent_function_type_args = nullptr;
|
|
};
|
|
|
|
Fragment FlowGraphBuilder::TestClosureFunctionGeneric(
|
|
const ClosureCallInfo& info,
|
|
Fragment generic,
|
|
Fragment not_generic) {
|
|
JoinEntryInstr* after_branch = BuildJoinEntry();
|
|
|
|
Fragment check;
|
|
check += LoadLocal(info.type_parameters);
|
|
TargetEntryInstr *is_not_generic, *is_generic;
|
|
check += BranchIfNull(&is_not_generic, &is_generic);
|
|
|
|
generic.Prepend(is_generic);
|
|
generic += Goto(after_branch);
|
|
|
|
not_generic.Prepend(is_not_generic);
|
|
not_generic += Goto(after_branch);
|
|
|
|
return Fragment(check.entry, after_branch);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::TestClosureFunctionNamedParameterRequired(
|
|
const ClosureCallInfo& info,
|
|
Fragment set,
|
|
Fragment not_set) {
|
|
// Required named arguments only exist if null_safety is enabled.
|
|
if (!IG->use_strict_null_safety_checks()) return not_set;
|
|
|
|
Fragment check_required;
|
|
// First, we convert the index to be in terms of the number of optional
|
|
// parameters, not total parameters (to calculate the flag index and shift).
|
|
check_required += LoadLocal(info.vars->current_param_index);
|
|
check_required += LoadLocal(info.num_fixed_params);
|
|
check_required += SmiBinaryOp(Token::kSUB, /*is_truncating=*/true);
|
|
LocalVariable* opt_index = MakeTemporary("opt_index"); // Read-only.
|
|
|
|
// Next, we calculate the index to dereference in the parameter names array.
|
|
check_required += LoadLocal(opt_index);
|
|
check_required +=
|
|
IntConstant(compiler::target::kNumParameterFlagsPerElementLog2);
|
|
check_required += SmiBinaryOp(Token::kSHR);
|
|
check_required += LoadLocal(info.num_max_params);
|
|
check_required += SmiBinaryOp(Token::kADD);
|
|
LocalVariable* flags_index = MakeTemporary("flags_index"); // Read-only.
|
|
|
|
// Two read-only stack values (opt_index, flag_index) that must be dropped
|
|
// after we rejoin at after_check.
|
|
JoinEntryInstr* after_check = BuildJoinEntry();
|
|
|
|
// Now we check to see if the flags index is within the bounds of the
|
|
// parameters names array. If not, it cannot be required.
|
|
check_required += LoadLocal(flags_index);
|
|
check_required += LoadLocal(info.parameter_names);
|
|
check_required += LoadNativeField(Slot::Array_length());
|
|
check_required += SmiRelationalOp(Token::kLT);
|
|
TargetEntryInstr *valid_index, *invalid_index;
|
|
check_required += BranchIfTrue(&valid_index, &invalid_index);
|
|
|
|
JoinEntryInstr* join_not_set = BuildJoinEntry();
|
|
|
|
Fragment(invalid_index) + Goto(join_not_set);
|
|
|
|
// Otherwise, we need to retrieve the value. We're guaranteed the Smis in
|
|
// the flag slots are non-null, so after loading we can immediate check
|
|
// the required flag bit for the given named parameter.
|
|
check_required.current = valid_index;
|
|
check_required += LoadLocal(info.parameter_names);
|
|
check_required += LoadLocal(flags_index);
|
|
check_required += LoadIndexed(kArrayCid);
|
|
check_required += LoadLocal(opt_index);
|
|
check_required +=
|
|
IntConstant(compiler::target::kNumParameterFlagsPerElement - 1);
|
|
check_required += SmiBinaryOp(Token::kBIT_AND);
|
|
// If the below changes, we'll need to multiply by the number of parameter
|
|
// flags before shifting.
|
|
static_assert(compiler::target::kNumParameterFlags == 1,
|
|
"IL builder assumes only one flag bit per parameter");
|
|
check_required += SmiBinaryOp(Token::kSHR);
|
|
check_required +=
|
|
IntConstant(1 << compiler::target::kRequiredNamedParameterFlag);
|
|
check_required += SmiBinaryOp(Token::kBIT_AND);
|
|
check_required += IntConstant(0);
|
|
TargetEntryInstr *is_not_set, *is_set;
|
|
check_required += BranchIfEqual(&is_not_set, &is_set);
|
|
|
|
Fragment(is_not_set) + Goto(join_not_set);
|
|
|
|
set.Prepend(is_set);
|
|
set += Goto(after_check);
|
|
|
|
not_set.Prepend(join_not_set);
|
|
not_set += Goto(after_check);
|
|
|
|
// After rejoining, drop the introduced temporaries.
|
|
check_required.current = after_check;
|
|
check_required += DropTemporary(&flags_index);
|
|
check_required += DropTemporary(&opt_index);
|
|
return check_required;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallDefaultTypeHandling(
|
|
const ClosureCallInfo& info) {
|
|
if (info.descriptor.TypeArgsLen() > 0) {
|
|
ASSERT(parsed_function_->function_type_arguments() != nullptr);
|
|
// A TAV was provided, so we don't need default type argument handling
|
|
// and can just take the arguments we were given.
|
|
Fragment store_provided;
|
|
store_provided += LoadLocal(parsed_function_->function_type_arguments());
|
|
store_provided += StoreLocal(info.vars->function_type_args);
|
|
store_provided += Drop();
|
|
return store_provided;
|
|
}
|
|
|
|
// Load the defaults, instantiating or replacing them with the other type
|
|
// arguments as appropriate.
|
|
Fragment store_default;
|
|
store_default += LoadLocal(info.closure);
|
|
store_default += LoadNativeField(Slot::Closure_function());
|
|
store_default += LoadNativeField(Slot::Function_data());
|
|
LocalVariable* closure_data = MakeTemporary("closure_data");
|
|
|
|
store_default += LoadLocal(closure_data);
|
|
const auto& slot = Slot::ClosureData_default_type_arguments_kind();
|
|
store_default += LoadNativeField(slot);
|
|
store_default += Box(slot.representation());
|
|
LocalVariable* default_tav_kind = MakeTemporary("default_tav_kind");
|
|
|
|
// Two locals to drop after join, closure_data and default_tav_kind.
|
|
JoinEntryInstr* done = BuildJoinEntry();
|
|
|
|
store_default += LoadLocal(default_tav_kind);
|
|
TargetEntryInstr *is_instantiated, *is_not_instantiated;
|
|
store_default += IntConstant(static_cast<intptr_t>(
|
|
ClosureData::DefaultTypeArgumentsKind::kIsInstantiated));
|
|
store_default += BranchIfEqual(&is_instantiated, &is_not_instantiated);
|
|
store_default.current = is_not_instantiated; // Check next case.
|
|
store_default += LoadLocal(default_tav_kind);
|
|
TargetEntryInstr *needs_instantiation, *can_share;
|
|
store_default += IntConstant(static_cast<intptr_t>(
|
|
ClosureData::DefaultTypeArgumentsKind::kNeedsInstantiation));
|
|
store_default += BranchIfEqual(&needs_instantiation, &can_share);
|
|
store_default.current = can_share; // Check next case.
|
|
store_default += LoadLocal(default_tav_kind);
|
|
TargetEntryInstr *can_share_instantiator, *can_share_function;
|
|
store_default += IntConstant(static_cast<intptr_t>(
|
|
ClosureData::DefaultTypeArgumentsKind::kSharesInstantiatorTypeArguments));
|
|
store_default += BranchIfEqual(&can_share_instantiator, &can_share_function);
|
|
|
|
Fragment instantiated(is_instantiated);
|
|
instantiated += LoadLocal(closure_data);
|
|
instantiated += LoadNativeField(Slot::ClosureData_default_type_arguments());
|
|
instantiated += StoreLocal(info.vars->function_type_args);
|
|
instantiated += Drop();
|
|
instantiated += Goto(done);
|
|
|
|
Fragment do_instantiation(needs_instantiation);
|
|
// Load the instantiator type arguments.
|
|
do_instantiation += LoadLocal(info.instantiator_type_args);
|
|
// Load the parent function type arguments. (No local function type arguments
|
|
// can be used within the defaults).
|
|
do_instantiation += LoadLocal(info.parent_function_type_args);
|
|
// Load the default type arguments to instantiate.
|
|
do_instantiation += LoadLocal(closure_data);
|
|
do_instantiation +=
|
|
LoadNativeField(Slot::ClosureData_default_type_arguments());
|
|
do_instantiation += InstantiateDynamicTypeArguments();
|
|
do_instantiation += StoreLocal(info.vars->function_type_args);
|
|
do_instantiation += Drop();
|
|
do_instantiation += Goto(done);
|
|
|
|
Fragment share_instantiator(can_share_instantiator);
|
|
share_instantiator += LoadLocal(info.instantiator_type_args);
|
|
share_instantiator += StoreLocal(info.vars->function_type_args);
|
|
share_instantiator += Drop();
|
|
share_instantiator += Goto(done);
|
|
|
|
Fragment share_function(can_share_function);
|
|
// Since the defaults won't have local type parameters, these must all be
|
|
// from the parent function type arguments, so we can just use it.
|
|
share_function += LoadLocal(info.parent_function_type_args);
|
|
share_function += StoreLocal(info.vars->function_type_args);
|
|
share_function += Drop();
|
|
share_function += Goto(done);
|
|
|
|
store_default.current = done; // Return here after branching.
|
|
store_default += DropTemporary(&default_tav_kind);
|
|
store_default += DropTemporary(&closure_data);
|
|
|
|
Fragment store_delayed;
|
|
store_delayed += LoadLocal(info.closure);
|
|
store_delayed += LoadNativeField(Slot::Closure_delayed_type_arguments());
|
|
store_delayed += StoreLocal(info.vars->function_type_args);
|
|
store_delayed += Drop();
|
|
|
|
// Use the delayed type args if present, else the default ones.
|
|
return TestDelayedTypeArgs(info.closure, store_delayed, store_default);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallNamedArgumentsCheck(
|
|
const ClosureCallInfo& info) {
|
|
// When no named arguments are provided, we just need to check for possible
|
|
// required named arguments.
|
|
if (info.descriptor.NamedCount() == 0) {
|
|
// No work to do if there are no possible required named parameters.
|
|
if (!IG->use_strict_null_safety_checks()) {
|
|
return Fragment();
|
|
}
|
|
// If the below changes, we can no longer assume that flag slots existing
|
|
// means there are required parameters.
|
|
static_assert(compiler::target::kNumParameterFlags == 1,
|
|
"IL builder assumes only one flag bit per parameter");
|
|
// No named args were provided, so check for any required named params.
|
|
// Here, we assume that the only parameter flag saved is the required bit
|
|
// for named parameters. If this changes, we'll need to check each flag
|
|
// entry appropriately for any set required bits.
|
|
Fragment has_any;
|
|
has_any += LoadLocal(info.num_max_params);
|
|
has_any += LoadLocal(info.parameter_names);
|
|
has_any += LoadNativeField(Slot::Array_length());
|
|
TargetEntryInstr *no_required, *has_required;
|
|
has_any += BranchIfEqual(&no_required, &has_required);
|
|
|
|
Fragment(has_required) + Goto(info.throw_no_such_method);
|
|
|
|
return Fragment(has_any.entry, no_required);
|
|
}
|
|
|
|
// Otherwise, we need to loop through the parameter names to check the names
|
|
// of named arguments for validity (and possibly missing required ones).
|
|
Fragment check_names;
|
|
check_names += LoadLocal(info.vars->current_param_index);
|
|
LocalVariable* old_index = MakeTemporary("old_index"); // Read-only.
|
|
check_names += LoadLocal(info.vars->current_num_processed);
|
|
LocalVariable* old_processed = MakeTemporary("old_processed"); // Read-only.
|
|
|
|
// Two local stack values (old_index, old_processed) to drop after rejoining
|
|
// at done.
|
|
JoinEntryInstr* loop = BuildJoinEntry();
|
|
JoinEntryInstr* done = BuildJoinEntry();
|
|
|
|
check_names += IntConstant(0);
|
|
check_names += StoreLocal(info.vars->current_num_processed);
|
|
check_names += Drop();
|
|
check_names += LoadLocal(info.num_fixed_params);
|
|
check_names += StoreLocal(info.vars->current_param_index);
|
|
check_names += Drop();
|
|
check_names += Goto(loop);
|
|
|
|
Fragment loop_check(loop);
|
|
loop_check += LoadLocal(info.vars->current_param_index);
|
|
loop_check += LoadLocal(info.num_max_params);
|
|
loop_check += SmiRelationalOp(Token::kLT);
|
|
TargetEntryInstr *no_more, *more;
|
|
loop_check += BranchIfTrue(&more, &no_more);
|
|
|
|
Fragment(no_more) + Goto(done);
|
|
|
|
Fragment loop_body(more);
|
|
// First load the name we need to check against.
|
|
loop_body += LoadLocal(info.parameter_names);
|
|
loop_body += LoadLocal(info.vars->current_param_index);
|
|
loop_body += LoadIndexed(kArrayCid);
|
|
LocalVariable* param_name = MakeTemporary("param_name"); // Read only.
|
|
|
|
// One additional local value on the stack within the loop body (param_name)
|
|
// that should be dropped after rejoining at loop_incr.
|
|
JoinEntryInstr* loop_incr = BuildJoinEntry();
|
|
|
|
// Now iterate over the ArgumentsDescriptor names and check for a match.
|
|
for (intptr_t i = 0; i < info.descriptor.NamedCount(); i++) {
|
|
const auto& name = String::ZoneHandle(Z, info.descriptor.NameAt(i));
|
|
loop_body += Constant(name);
|
|
loop_body += LoadLocal(param_name);
|
|
TargetEntryInstr *match, *mismatch;
|
|
loop_body += BranchIfEqual(&match, &mismatch);
|
|
loop_body.current = mismatch;
|
|
|
|
// We have a match, so go to the next name after storing the corresponding
|
|
// parameter index on the stack and incrementing the number of matched
|
|
// arguments. (No need to check the required bit for provided parameters.)
|
|
Fragment matched(match);
|
|
matched += LoadLocal(info.vars->current_param_index);
|
|
matched += StoreLocal(info.vars->named_argument_parameter_indices.At(i));
|
|
matched += Drop();
|
|
matched += LoadLocal(info.vars->current_num_processed);
|
|
matched += IntConstant(1);
|
|
matched += SmiBinaryOp(Token::kADD, /*is_truncating=*/true);
|
|
matched += StoreLocal(info.vars->current_num_processed);
|
|
matched += Drop();
|
|
matched += Goto(loop_incr);
|
|
}
|
|
|
|
// None of the names in the arguments descriptor matched, so check if this
|
|
// is a required parameter.
|
|
loop_body += TestClosureFunctionNamedParameterRequired(
|
|
info,
|
|
/*set=*/Goto(info.throw_no_such_method),
|
|
/*not_set=*/{});
|
|
|
|
loop_body += Goto(loop_incr);
|
|
|
|
Fragment incr_index(loop_incr);
|
|
incr_index += DropTemporary(¶m_name);
|
|
incr_index += LoadLocal(info.vars->current_param_index);
|
|
incr_index += IntConstant(1);
|
|
incr_index += SmiBinaryOp(Token::kADD, /*is_truncating=*/true);
|
|
incr_index += StoreLocal(info.vars->current_param_index);
|
|
incr_index += Drop();
|
|
incr_index += Goto(loop);
|
|
|
|
Fragment check_processed(done);
|
|
check_processed += LoadLocal(info.vars->current_num_processed);
|
|
check_processed += IntConstant(info.descriptor.NamedCount());
|
|
TargetEntryInstr *all_processed, *bad_name;
|
|
check_processed += BranchIfEqual(&all_processed, &bad_name);
|
|
|
|
// Didn't find a matching parameter name for at least one argument name.
|
|
Fragment(bad_name) + Goto(info.throw_no_such_method);
|
|
|
|
// Drop the temporaries at the end of the fragment.
|
|
check_names.current = all_processed;
|
|
check_names += LoadLocal(old_processed);
|
|
check_names += StoreLocal(info.vars->current_num_processed);
|
|
check_names += Drop();
|
|
check_names += DropTemporary(&old_processed);
|
|
check_names += LoadLocal(old_index);
|
|
check_names += StoreLocal(info.vars->current_param_index);
|
|
check_names += Drop();
|
|
check_names += DropTemporary(&old_index);
|
|
return check_names;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallArgumentsValidCheck(
|
|
const ClosureCallInfo& info) {
|
|
Fragment check_entry;
|
|
// We only need to check the length of any explicitly provided type arguments.
|
|
if (info.descriptor.TypeArgsLen() > 0) {
|
|
Fragment check_type_args_length;
|
|
check_type_args_length += LoadLocal(info.type_parameters);
|
|
TargetEntryInstr *null, *not_null;
|
|
check_type_args_length += BranchIfNull(&null, ¬_null);
|
|
check_type_args_length.current = not_null; // Continue in non-error case.
|
|
check_type_args_length += LoadLocal(info.type_parameters);
|
|
check_type_args_length += LoadNativeField(Slot::TypeArguments_length());
|
|
check_type_args_length += IntConstant(info.descriptor.TypeArgsLen());
|
|
TargetEntryInstr *equal, *not_equal;
|
|
check_type_args_length += BranchIfEqual(&equal, ¬_equal);
|
|
check_type_args_length.current = equal; // Continue in non-error case.
|
|
|
|
// The function is not generic.
|
|
Fragment(null) + Goto(info.throw_no_such_method);
|
|
|
|
// An incorrect number of type arguments were passed.
|
|
Fragment(not_equal) + Goto(info.throw_no_such_method);
|
|
|
|
// Type arguments should not be provided if there are delayed type
|
|
// arguments, as then the closure itself is not generic.
|
|
check_entry += TestDelayedTypeArgs(
|
|
info.closure, /*present=*/Goto(info.throw_no_such_method),
|
|
/*absent=*/check_type_args_length);
|
|
}
|
|
|
|
check_entry += LoadLocal(info.has_named_params);
|
|
TargetEntryInstr *has_named, *has_positional;
|
|
check_entry += BranchIfTrue(&has_named, &has_positional);
|
|
JoinEntryInstr* join_after_optional = BuildJoinEntry();
|
|
check_entry.current = join_after_optional;
|
|
|
|
if (info.descriptor.NamedCount() > 0) {
|
|
// No reason to continue checking, as this function doesn't take named args.
|
|
Fragment(has_positional) + Goto(info.throw_no_such_method);
|
|
} else {
|
|
Fragment check_pos(has_positional);
|
|
check_pos += LoadLocal(info.num_fixed_params);
|
|
check_pos += IntConstant(info.descriptor.PositionalCount());
|
|
check_pos += SmiRelationalOp(Token::kLTE);
|
|
TargetEntryInstr *enough, *too_few;
|
|
check_pos += BranchIfTrue(&enough, &too_few);
|
|
check_pos.current = enough;
|
|
|
|
Fragment(too_few) + Goto(info.throw_no_such_method);
|
|
|
|
check_pos += IntConstant(info.descriptor.PositionalCount());
|
|
check_pos += LoadLocal(info.num_max_params);
|
|
check_pos += SmiRelationalOp(Token::kLTE);
|
|
TargetEntryInstr *valid, *too_many;
|
|
check_pos += BranchIfTrue(&valid, &too_many);
|
|
check_pos.current = valid;
|
|
|
|
Fragment(too_many) + Goto(info.throw_no_such_method);
|
|
|
|
check_pos += Goto(join_after_optional);
|
|
}
|
|
|
|
Fragment check_named(has_named);
|
|
|
|
TargetEntryInstr *same, *different;
|
|
check_named += LoadLocal(info.num_fixed_params);
|
|
check_named += IntConstant(info.descriptor.PositionalCount());
|
|
check_named += BranchIfEqual(&same, &different);
|
|
check_named.current = same;
|
|
|
|
Fragment(different) + Goto(info.throw_no_such_method);
|
|
|
|
if (info.descriptor.NamedCount() > 0) {
|
|
check_named += IntConstant(info.descriptor.NamedCount());
|
|
check_named += LoadLocal(info.num_opt_params);
|
|
check_named += SmiRelationalOp(Token::kLTE);
|
|
TargetEntryInstr *valid, *too_many;
|
|
check_named += BranchIfTrue(&valid, &too_many);
|
|
check_named.current = valid;
|
|
|
|
Fragment(too_many) + Goto(info.throw_no_such_method);
|
|
}
|
|
|
|
// Check the names for optional arguments. If applicable, also check that all
|
|
// required named parameters are provided.
|
|
check_named += BuildClosureCallNamedArgumentsCheck(info);
|
|
check_named += Goto(join_after_optional);
|
|
|
|
check_entry.current = join_after_optional;
|
|
return check_entry;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallTypeArgumentsTypeCheck(
|
|
const ClosureCallInfo& info) {
|
|
JoinEntryInstr* done = BuildJoinEntry();
|
|
JoinEntryInstr* loop = BuildJoinEntry();
|
|
|
|
// We assume that the value stored in :t_type_parameters is not null (i.e.,
|
|
// the function stored in :t_function is generic).
|
|
Fragment loop_init;
|
|
// Loop over the type parameters array.
|
|
loop_init += IntConstant(0);
|
|
loop_init += StoreLocal(info.vars->current_param_index);
|
|
loop_init += Drop();
|
|
loop_init += Goto(loop);
|
|
|
|
Fragment loop_check(loop);
|
|
loop_check += LoadLocal(info.vars->current_param_index);
|
|
loop_check += LoadLocal(info.type_parameters);
|
|
loop_check += LoadNativeField(Slot::TypeArguments_length());
|
|
loop_check += SmiRelationalOp(Token::kLT);
|
|
TargetEntryInstr *more, *no_more;
|
|
loop_check += BranchIfTrue(&more, &no_more);
|
|
|
|
Fragment(no_more) + Goto(done);
|
|
|
|
Fragment loop_body(more);
|
|
loop_body += LoadLocal(info.type_parameters);
|
|
loop_body += LoadLocal(info.vars->current_param_index);
|
|
loop_body += LoadIndexed(
|
|
kTypeArgumentsCid, /*index_scale*/ compiler::target::kCompressedWordSize);
|
|
LocalVariable* current_param = MakeTemporary("current_param"); // Read-only.
|
|
|
|
// One read-only local variable on stack (param) to drop after joining.
|
|
JoinEntryInstr* next = BuildJoinEntry();
|
|
|
|
loop_body += LoadLocal(current_param);
|
|
loop_body += LoadNativeField(Slot::TypeParameter_flags());
|
|
loop_body += Box(kUnboxedUint8);
|
|
loop_body += IntConstant(
|
|
UntaggedTypeParameter::GenericCovariantImplBit::mask_in_place());
|
|
loop_body += SmiBinaryOp(Token::kBIT_AND);
|
|
loop_body += IntConstant(0);
|
|
TargetEntryInstr *is_noncovariant, *is_covariant;
|
|
loop_body += BranchIfEqual(&is_noncovariant, &is_covariant);
|
|
|
|
Fragment(is_covariant) + Goto(next); // Continue if covariant.
|
|
|
|
loop_body.current = is_noncovariant; // Type check if non-covariant.
|
|
loop_body += LoadLocal(info.instantiator_type_args);
|
|
loop_body += LoadLocal(info.vars->function_type_args);
|
|
// Load parameter.
|
|
loop_body += LoadLocal(current_param);
|
|
// Load bounds from parameter.
|
|
loop_body += LoadLocal(current_param);
|
|
loop_body += LoadNativeField(Slot::TypeParameter_bound());
|
|
// Load name from parameter.
|
|
loop_body += LoadLocal(current_param);
|
|
loop_body += LoadNativeField(Slot::TypeParameter_name());
|
|
// Assert that the type the parameter is instantiated as is consistent with
|
|
// the bounds of the parameter.
|
|
loop_body += AssertSubtype(TokenPosition::kNoSource);
|
|
loop_body += Goto(next);
|
|
|
|
Fragment loop_incr(next);
|
|
loop_incr += DropTemporary(¤t_param);
|
|
loop_incr += LoadLocal(info.vars->current_param_index);
|
|
loop_incr += IntConstant(1);
|
|
loop_incr += SmiBinaryOp(Token::kADD, /*is_truncating=*/true);
|
|
loop_incr += StoreLocal(info.vars->current_param_index);
|
|
loop_incr += Drop();
|
|
loop_incr += Goto(loop);
|
|
|
|
return Fragment(loop_init.entry, done);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallArgumentTypeCheck(
|
|
const ClosureCallInfo& info,
|
|
LocalVariable* param_index,
|
|
intptr_t arg_index,
|
|
const String& arg_name) {
|
|
Fragment instructions;
|
|
|
|
// Load value.
|
|
instructions += LoadLocal(parsed_function_->ParameterVariable(arg_index));
|
|
// Load destination type.
|
|
instructions += LoadLocal(info.parameter_types);
|
|
instructions += LoadLocal(param_index);
|
|
instructions += LoadIndexed(kArrayCid);
|
|
// Load instantiator type arguments.
|
|
instructions += LoadLocal(info.instantiator_type_args);
|
|
// Load the full set of function type arguments.
|
|
instructions += LoadLocal(info.vars->function_type_args);
|
|
// Check that the value has the right type.
|
|
instructions += AssertAssignable(TokenPosition::kNoSource, arg_name,
|
|
AssertAssignableInstr::kParameterCheck);
|
|
// Make sure to store the result to keep data dependencies accurate.
|
|
instructions += StoreLocal(parsed_function_->ParameterVariable(arg_index));
|
|
instructions += Drop();
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildClosureCallArgumentTypeChecks(
|
|
const ClosureCallInfo& info) {
|
|
Fragment instructions;
|
|
|
|
// Only check explicit arguments (i.e., skip the receiver), as the receiver
|
|
// is always assignable to its type (stored as dynamic).
|
|
for (intptr_t i = 1; i < info.descriptor.PositionalCount(); i++) {
|
|
instructions += IntConstant(i);
|
|
LocalVariable* param_index = MakeTemporary("param_index");
|
|
// We don't have a compile-time name, so this symbol signals the runtime
|
|
// that it should recreate the type check using info from the stack.
|
|
instructions += BuildClosureCallArgumentTypeCheck(
|
|
info, param_index, i, Symbols::dynamic_assert_assignable_stc_check());
|
|
instructions += DropTemporary(¶m_index);
|
|
}
|
|
|
|
for (intptr_t i = 0; i < info.descriptor.NamedCount(); i++) {
|
|
const intptr_t arg_index = info.descriptor.PositionAt(i);
|
|
const auto& arg_name = String::ZoneHandle(Z, info.descriptor.NameAt(i));
|
|
auto const param_index = info.vars->named_argument_parameter_indices.At(i);
|
|
instructions += BuildClosureCallArgumentTypeCheck(info, param_index,
|
|
arg_index, arg_name);
|
|
}
|
|
|
|
return instructions;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildDynamicClosureCallChecks(
|
|
LocalVariable* closure) {
|
|
ClosureCallInfo info(closure, BuildThrowNoSuchMethod(),
|
|
saved_args_desc_array(),
|
|
parsed_function_->dynamic_closure_call_vars());
|
|
|
|
Fragment body;
|
|
body += LoadLocal(info.closure);
|
|
body += LoadNativeField(Slot::Closure_function());
|
|
body += LoadNativeField(Slot::Function_signature());
|
|
info.signature = MakeTemporary("signature");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += BuildExtractUnboxedSlotBitFieldIntoSmi<
|
|
FunctionType::PackedNumFixedParameters>(
|
|
Slot::FunctionType_packed_fields());
|
|
info.num_fixed_params = MakeTemporary("num_fixed_params");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += BuildExtractUnboxedSlotBitFieldIntoSmi<
|
|
FunctionType::PackedNumOptionalParameters>(
|
|
Slot::FunctionType_packed_fields());
|
|
info.num_opt_params = MakeTemporary("num_opt_params");
|
|
|
|
body += LoadLocal(info.num_fixed_params);
|
|
body += LoadLocal(info.num_opt_params);
|
|
body += SmiBinaryOp(Token::kADD);
|
|
info.num_max_params = MakeTemporary("num_max_params");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += BuildExtractUnboxedSlotBitFieldIntoSmi<
|
|
FunctionType::PackedHasNamedOptionalParameters>(
|
|
Slot::FunctionType_packed_fields());
|
|
|
|
body += IntConstant(0);
|
|
body += StrictCompare(Token::kNE_STRICT);
|
|
info.has_named_params = MakeTemporary("has_named_params");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += LoadNativeField(Slot::FunctionType_parameter_names());
|
|
info.parameter_names = MakeTemporary("parameter_names");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += LoadNativeField(Slot::FunctionType_parameter_types());
|
|
info.parameter_types = MakeTemporary("parameter_types");
|
|
|
|
body += LoadLocal(info.signature);
|
|
body += LoadNativeField(Slot::FunctionType_type_parameters());
|
|
info.type_parameters = MakeTemporary("type_parameters");
|
|
|
|
body += LoadLocal(info.closure);
|
|
body += LoadNativeField(Slot::Closure_instantiator_type_arguments());
|
|
info.instantiator_type_args = MakeTemporary("instantiator_type_args");
|
|
|
|
body += LoadLocal(info.closure);
|
|
body += LoadNativeField(Slot::Closure_function_type_arguments());
|
|
info.parent_function_type_args = MakeTemporary("parent_function_type_args");
|
|
|
|
// At this point, all the read-only temporaries stored in the ClosureCallInfo
|
|
// should be either loaded or still nullptr, if not needed for this function.
|
|
// Now we check that the arguments to the closure call have the right shape.
|
|
body += BuildClosureCallArgumentsValidCheck(info);
|
|
|
|
// If the closure function is not generic, there are no local function type
|
|
// args. Thus, use whatever was stored for the parent function type arguments,
|
|
// which has already been checked against any parent type parameter bounds.
|
|
Fragment not_generic;
|
|
not_generic += LoadLocal(info.parent_function_type_args);
|
|
not_generic += StoreLocal(info.vars->function_type_args);
|
|
not_generic += Drop();
|
|
|
|
// If the closure function is generic, then we first need to calculate the
|
|
// full set of function type arguments, then check the local function type
|
|
// arguments against the closure function's type parameter bounds.
|
|
Fragment generic;
|
|
// Calculate the local function type arguments and store them in
|
|
// info.vars->function_type_args.
|
|
generic += BuildClosureCallDefaultTypeHandling(info);
|
|
// Load the local function type args.
|
|
generic += LoadLocal(info.vars->function_type_args);
|
|
// Load the parent function type args.
|
|
generic += LoadLocal(info.parent_function_type_args);
|
|
// Load the number of parent type parameters.
|
|
generic += LoadLocal(info.signature);
|
|
generic += BuildExtractUnboxedSlotBitFieldIntoSmi<
|
|
UntaggedFunctionType::PackedNumParentTypeArguments>(
|
|
Slot::FunctionType_packed_fields());
|
|
// Load the number of total type parameters.
|
|
LocalVariable* num_parents = MakeTemporary();
|
|
generic += LoadLocal(info.type_parameters);
|
|
generic += LoadNativeField(Slot::TypeArguments_length());
|
|
generic += LoadLocal(num_parents);
|
|
generic += SmiBinaryOp(Token::kADD, /*is_truncating=*/true);
|
|
|
|
// Call the static function for prepending type arguments.
|
|
generic += StaticCall(TokenPosition::kNoSource,
|
|
PrependTypeArgumentsFunction(), 4, ICData::kStatic);
|
|
generic += StoreLocal(info.vars->function_type_args);
|
|
generic += Drop();
|
|
|
|
// Now that we have the full set of function type arguments, check them
|
|
// against the type parameter bounds. However, if the local function type
|
|
// arguments are delayed type arguments, they have already been checked by
|
|
// the type system and need not be checked again at the call site.
|
|
auto const check_bounds = BuildClosureCallTypeArgumentsTypeCheck(info);
|
|
if (FLAG_eliminate_type_checks) {
|
|
generic += TestDelayedTypeArgs(info.closure, /*present=*/{},
|
|
/*absent=*/check_bounds);
|
|
} else {
|
|
generic += check_bounds;
|
|
}
|
|
|
|
// Call the appropriate fragment for setting up the function type arguments
|
|
// and performing any needed type argument checking.
|
|
body += TestClosureFunctionGeneric(info, generic, not_generic);
|
|
|
|
// Check that the values provided as arguments are assignable to the types
|
|
// of the corresponding closure function parameters.
|
|
body += BuildClosureCallArgumentTypeChecks(info);
|
|
|
|
// Drop all the read-only temporaries at the end of the fragment.
|
|
body += DropTemporary(&info.parent_function_type_args);
|
|
body += DropTemporary(&info.instantiator_type_args);
|
|
body += DropTemporary(&info.type_parameters);
|
|
body += DropTemporary(&info.parameter_types);
|
|
body += DropTemporary(&info.parameter_names);
|
|
body += DropTemporary(&info.has_named_params);
|
|
body += DropTemporary(&info.num_max_params);
|
|
body += DropTemporary(&info.num_opt_params);
|
|
body += DropTemporary(&info.num_fixed_params);
|
|
body += DropTemporary(&info.signature);
|
|
|
|
return body;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
|
|
const Function& function) {
|
|
const ArgumentsDescriptor descriptor(saved_args_desc_array());
|
|
// Find the name of the field we should dispatch to.
|
|
const Class& owner = Class::Handle(Z, function.Owner());
|
|
ASSERT(!owner.IsNull());
|
|
auto& field_name = String::Handle(Z, function.name());
|
|
// If the field name has a dyn: tag, then remove it. We don't add dynamic
|
|
// invocation forwarders for field getters used for invoking, we just use
|
|
// the tag in the name of the invoke field dispatcher to detect dynamic calls.
|
|
const bool is_dynamic_call =
|
|
Function::IsDynamicInvocationForwarderName(field_name);
|
|
if (is_dynamic_call) {
|
|
field_name = Function::DemangleDynamicInvocationForwarderName(field_name);
|
|
}
|
|
const String& getter_name = String::ZoneHandle(
|
|
Z, Symbols::New(thread_,
|
|
String::Handle(Z, Field::GetterSymbol(field_name))));
|
|
|
|
// Determine if this is `class Closure { get call => this; }`
|
|
const Class& closure_class =
|
|
Class::Handle(Z, IG->object_store()->closure_class());
|
|
const bool is_closure_call = (owner.ptr() == closure_class.ptr()) &&
|
|
field_name.Equals(Symbols::Call());
|
|
|
|
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());
|
|
|
|
// Build any dynamic closure call checks before pushing arguments to the
|
|
// final call on the stack to make debugging easier.
|
|
LocalVariable* closure = nullptr;
|
|
if (is_closure_call) {
|
|
closure = parsed_function_->ParameterVariable(0);
|
|
if (is_dynamic_call) {
|
|
// The whole reason for making this invoke field dispatcher is that
|
|
// this closure call needs checking, so we shouldn't inline a call to an
|
|
// unchecked entry that can't tail call NSM.
|
|
InlineBailout(
|
|
"kernel::FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher");
|
|
|
|
body += BuildDynamicClosureCallChecks(closure);
|
|
}
|
|
}
|
|
|
|
if (descriptor.TypeArgsLen() > 0) {
|
|
LocalVariable* type_args = parsed_function_->function_type_arguments();
|
|
ASSERT(type_args != nullptr);
|
|
body += LoadLocal(type_args);
|
|
}
|
|
|
|
if (is_closure_call) {
|
|
// The closure itself is the first argument.
|
|
body += LoadLocal(closure);
|
|
} else {
|
|
// Invoke the getter to get the field value.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
const intptr_t kTypeArgsLen = 0;
|
|
const intptr_t kNumArgsChecked = 1;
|
|
body += InstanceCall(TokenPosition::kMinSource, getter_name, Token::kGET,
|
|
kTypeArgsLen, 1, Array::null_array(), kNumArgsChecked);
|
|
}
|
|
|
|
// Push all arguments onto the stack.
|
|
for (intptr_t pos = 1; pos < descriptor.Count(); pos++) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(pos));
|
|
}
|
|
|
|
// Construct argument names array if necessary.
|
|
const Array* argument_names = &Object::null_array();
|
|
if (descriptor.NamedCount() > 0) {
|
|
const auto& array_handle =
|
|
Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kNew));
|
|
String& string_handle = String::Handle(Z);
|
|
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
|
|
const intptr_t named_arg_index =
|
|
descriptor.PositionAt(i) - descriptor.PositionalCount();
|
|
string_handle = descriptor.NameAt(i);
|
|
array_handle.SetAt(named_arg_index, string_handle);
|
|
}
|
|
argument_names = &array_handle;
|
|
}
|
|
|
|
if (is_closure_call) {
|
|
// Lookup the function in the closure.
|
|
body += LoadLocal(closure);
|
|
body += LoadNativeField(Slot::Closure_function());
|
|
|
|
body += ClosureCall(TokenPosition::kNoSource, descriptor.TypeArgsLen(),
|
|
descriptor.Count(), *argument_names);
|
|
} else {
|
|
const intptr_t kNumArgsChecked = 1;
|
|
body +=
|
|
InstanceCall(TokenPosition::kMinSource,
|
|
is_dynamic_call ? Symbols::DynamicCall() : Symbols::Call(),
|
|
Token::kILLEGAL, descriptor.TypeArgsLen(),
|
|
descriptor.Count(), *argument_names, kNumArgsChecked);
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
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_size());
|
|
} else {
|
|
ASSERT(function.NumOptionalParameters() == 0);
|
|
body += IntConstant(function.NumParameters());
|
|
}
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
body += StoreFpRelativeSlot(
|
|
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
|
}
|
|
|
|
if (function.NeedsTypeArgumentTypeChecks()) {
|
|
BuildTypeArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
|
&body);
|
|
}
|
|
|
|
if (function.NeedsArgumentTypeChecks()) {
|
|
BuildArgumentTypeChecks(&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_size());
|
|
} 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(compiler::target::kWordSize *
|
|
compiler::target::frame_layout.param_end_from_fp,
|
|
CompileType::Dynamic());
|
|
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));
|
|
type = ClassFinalizer::FinalizeType(type);
|
|
body += Constant(type);
|
|
} else {
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
}
|
|
} else {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
}
|
|
|
|
body += Constant(String::ZoneHandle(Z, function.name()));
|
|
|
|
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::NewBoxed(0, function.NumParameters()));
|
|
body += Constant(args_desc);
|
|
} else {
|
|
body += LoadArgDescriptor();
|
|
}
|
|
|
|
body += LoadLocal(arguments);
|
|
|
|
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();
|
|
}
|
|
|
|
// Push the number of delayed type arguments.
|
|
if (function.IsClosureFunction()) {
|
|
LocalVariable* closure = parsed_function_->ParameterVariable(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);
|
|
}
|
|
|
|
const Class& mirror_class =
|
|
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
|
|
ASSERT(!mirror_class.IsNull());
|
|
const auto& error = mirror_class.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
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);
|
|
|
|
if (throw_no_such_method_error) {
|
|
const Class& klass = Class::ZoneHandle(
|
|
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
|
|
ASSERT(!klass.IsNull());
|
|
const auto& error = klass.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
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);
|
|
}
|
|
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.IsTopTypeForSubtyping()) {
|
|
body += AssertAssignableLoadTypeArguments(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()) {
|
|
auto& default_types =
|
|
TypeArguments::ZoneHandle(Z, function.InstantiateToBounds(thread_));
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
|
|
if (prologue_start != nullptr) {
|
|
join_entry->LinkTo(prologue_start);
|
|
} else {
|
|
// Prologue is empty.
|
|
shared_prologue_linked_in.current = join_entry;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
|
|
const Function& function) {
|
|
const Function& parent = Function::ZoneHandle(Z, function.parent_function());
|
|
Function& target = Function::ZoneHandle(Z, function.ImplicitClosureTarget(Z));
|
|
|
|
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);
|
|
|
|
Fragment closure(instruction_cursor);
|
|
closure += CheckStackOverflowInPrologue(function.token_pos());
|
|
closure += BuildDefaultTypeHandling(function);
|
|
|
|
// For implicit closure functions, any non-covariant checks are either
|
|
// performed by the type system or a dynamic invocation layer (dynamic closure
|
|
// call dispatcher, mirror, etc.). Static targets never have covariant
|
|
// arguments, and for non-static targets, they already perform the covariant
|
|
// checks internally. Thus, no checks are needed and we just need to invoke
|
|
// the target with the right receiver (unless static).
|
|
//
|
|
// TODO(dartbug.com/44195): Consider replacing the argument pushes + static
|
|
// call with stack manipulation and a tail call instead.
|
|
|
|
intptr_t type_args_len = 0;
|
|
if (function.IsGeneric()) {
|
|
type_args_len = function.NumTypeParameters();
|
|
ASSERT(parsed_function_->function_type_arguments() != NULL);
|
|
closure += LoadLocal(parsed_function_->function_type_arguments());
|
|
}
|
|
|
|
// Push receiver.
|
|
if (!target.is_static()) {
|
|
// The context has a fixed shape: a single variable which is the
|
|
// closed-over receiver.
|
|
closure += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
closure += LoadNativeField(Slot::Closure_context());
|
|
closure += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
}
|
|
|
|
closure += PushExplicitParameters(function);
|
|
|
|
// Forward parameters to the target.
|
|
intptr_t argument_count = function.NumParameters() -
|
|
function.NumImplicitParameters() +
|
|
(target.is_static() ? 0 : 1);
|
|
ASSERT(argument_count == target.NumParameters());
|
|
|
|
Array& argument_names =
|
|
Array::ZoneHandle(Z, GetOptionalParameterNames(function));
|
|
|
|
closure += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
|
argument_names, ICData::kNoRebind,
|
|
/* result_type = */ NULL, type_args_len);
|
|
|
|
// Return the result.
|
|
closure += Return(function.end_token_pos());
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFieldAccessor(
|
|
const Function& function) {
|
|
ASSERT(function.IsImplicitGetterOrSetter() ||
|
|
function.IsDynamicInvocationForwarder());
|
|
|
|
// Instead of building a dynamic invocation forwarder that checks argument
|
|
// type and then invokes original setter we simply generate the type check
|
|
// and inlined field store. Scope builder takes care of setting correct
|
|
// type check mode in this case.
|
|
const auto& target = Function::Handle(
|
|
Z, function.IsDynamicInvocationForwarder() ? function.ForwardingTarget()
|
|
: function.ptr());
|
|
ASSERT(target.IsImplicitGetterOrSetter());
|
|
|
|
const bool is_method = !function.IsStaticFunction();
|
|
const bool is_setter = target.IsImplicitSetterFunction();
|
|
const bool is_getter = target.IsImplicitGetterFunction() ||
|
|
target.IsImplicitStaticGetterFunction();
|
|
ASSERT(is_setter || is_getter);
|
|
|
|
const auto& field = Field::ZoneHandle(Z, target.accessor_field());
|
|
|
|
graph_entry_ =
|
|
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
|
|
|
|
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
|
graph_entry_->set_normal_entry(normal_entry);
|
|
|
|
Fragment body(normal_entry);
|
|
if (is_setter) {
|
|
auto const setter_value =
|
|
parsed_function_->ParameterVariable(is_method ? 1 : 0);
|
|
if (is_method) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
}
|
|
body += LoadLocal(setter_value);
|
|
|
|
// The dyn:* forwarder has to check the parameters that the
|
|
// actual target will not check.
|
|
// Though here we manually inline the target, so the dyn:* forwarder has to
|
|
// check all parameters.
|
|
const bool needs_type_check = function.IsDynamicInvocationForwarder() ||
|
|
setter_value->needs_type_check();
|
|
if (needs_type_check) {
|
|
body += CheckAssignable(setter_value->type(), setter_value->name(),
|
|
AssertAssignableInstr::kParameterCheck);
|
|
}
|
|
body += BuildNullAssertions();
|
|
if (field.is_late()) {
|
|
if (is_method) {
|
|
body += Drop();
|
|
}
|
|
body += Drop();
|
|
body += StoreLateField(
|
|
field, is_method ? parsed_function_->ParameterVariable(0) : nullptr,
|
|
setter_value);
|
|
} else {
|
|
if (is_method) {
|
|
body += StoreInstanceFieldGuarded(
|
|
field, StoreInstanceFieldInstr::Kind::kOther);
|
|
} else {
|
|
body += StoreStaticField(TokenPosition::kNoSource, field);
|
|
}
|
|
}
|
|
body += NullConstant();
|
|
} else if (is_getter && is_method) {
|
|
ASSERT(!field.needs_load_guard()
|
|
NOT_IN_PRODUCT(|| IG->HasAttemptedReload()));
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
body += LoadField(
|
|
field, /*calls_initializer=*/field.NeedsInitializationCheckOnLoad());
|
|
if (field.needs_load_guard()) {
|
|
#if defined(PRODUCT)
|
|
UNREACHABLE();
|
|
#else
|
|
body += CheckAssignable(AbstractType::Handle(Z, field.type()),
|
|
Symbols::FunctionResult());
|
|
#endif
|
|
}
|
|
} else if (field.is_const()) {
|
|
const auto& value = Object::Handle(Z, field.StaticConstFieldValue());
|
|
if (value.IsError()) {
|
|
Report::LongJump(Error::Cast(value));
|
|
}
|
|
body += Constant(Instance::ZoneHandle(Z, Instance::RawCast(value.ptr())));
|
|
} else {
|
|
// Static fields
|
|
// - with trivial initializer
|
|
// - without initializer if they are not late
|
|
// are initialized eagerly and do not have implicit getters.
|
|
// Static fields with non-trivial initializer need getter to perform
|
|
// lazy initialization. Late fields without initializer need getter
|
|
// to make sure they are already initialized.
|
|
ASSERT(field.has_nontrivial_initializer() ||
|
|
(field.is_late() && !field.has_initializer()));
|
|
body += LoadStaticField(field, /*calls_initializer=*/true);
|
|
if (field.needs_load_guard()) {
|
|
#if defined(PRODUCT)
|
|
UNREACHABLE();
|
|
#else
|
|
ASSERT(IsolateGroup::Current()->HasAttemptedReload());
|
|
body += CheckAssignable(AbstractType::Handle(Z, field.type()),
|
|
Symbols::FunctionResult());
|
|
#endif
|
|
}
|
|
}
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
PrologueInfo prologue_info(-1, -1);
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder(
|
|
const Function& function) {
|
|
auto& name = String::Handle(Z, function.name());
|
|
name = Function::DemangleDynamicInvocationForwarderName(name);
|
|
const auto& target = Function::ZoneHandle(Z, function.ForwardingTarget());
|
|
ASSERT(!target.IsNull());
|
|
|
|
if (target.IsImplicitSetterFunction() || target.IsImplicitGetterFunction()) {
|
|
return BuildGraphOfFieldAccessor(function);
|
|
}
|
|
if (target.IsMethodExtractor()) {
|
|
return BuildGraphOfMethodExtractor(target);
|
|
}
|
|
|
|
graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, osr_id_);
|
|
|
|
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
|
graph_entry_->set_normal_entry(normal_entry);
|
|
|
|
PrologueInfo prologue_info(-1, -1);
|
|
auto instruction_cursor = BuildPrologue(normal_entry, &prologue_info);
|
|
|
|
Fragment body;
|
|
if (!function.is_native()) {
|
|
body += CheckStackOverflowInPrologue(function.token_pos());
|
|
}
|
|
|
|
ASSERT(parsed_function_->scope()->num_context_variables() == 0);
|
|
|
|
// Should never build a dynamic invocation forwarder for equality
|
|
// operator.
|
|
ASSERT(function.name() != Symbols::EqualOperator().ptr());
|
|
|
|
// Even if the caller did not pass argument vector we would still
|
|
// call the target with instantiate-to-bounds type arguments.
|
|
body += BuildDefaultTypeHandling(function);
|
|
|
|
// Build argument type checks that complement those that are emitted in the
|
|
// target.
|
|
BuildTypeArgumentTypeChecks(
|
|
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds, &body);
|
|
BuildArgumentTypeChecks(&body, &body, nullptr);
|
|
|
|
// Push all arguments and invoke the original method.
|
|
|
|
intptr_t type_args_len = 0;
|
|
if (function.IsGeneric()) {
|
|
type_args_len = function.NumTypeParameters();
|
|
ASSERT(parsed_function_->function_type_arguments() != nullptr);
|
|
body += LoadLocal(parsed_function_->function_type_arguments());
|
|
}
|
|
|
|
// Push receiver.
|
|
ASSERT(function.NumImplicitParameters() == 1);
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += PushExplicitParameters(function, target);
|
|
|
|
const intptr_t argument_count = function.NumParameters();
|
|
const auto& argument_names =
|
|
Array::ZoneHandle(Z, GetOptionalParameterNames(function));
|
|
|
|
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
|
argument_names, ICData::kNoRebind, nullptr, type_args_len);
|
|
|
|
if (target.has_unboxed_integer_return()) {
|
|
body += Box(kUnboxedInt64);
|
|
} else if (target.has_unboxed_double_return()) {
|
|
body += Box(kUnboxedDouble);
|
|
}
|
|
|
|
// Later optimization passes assume that result of a x.[]=(...) call is not
|
|
// used. We must guarantee this invariant because violation will lead to an
|
|
// illegal IL once we replace x.[]=(...) with a sequence that does not
|
|
// actually produce any value. See http://dartbug.com/29135 for more details.
|
|
if (name.ptr() == Symbols::AssignIndexToken().ptr()) {
|
|
body += Drop();
|
|
body += NullConstant();
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
instruction_cursor->LinkTo(body.entry);
|
|
|
|
// When compiling for OSR, use a depth first search to find the OSR
|
|
// entry and make graph entry jump to it instead of normal entry.
|
|
// Catch entries are always considered reachable, even if they
|
|
// become unreachable after OSR.
|
|
if (IsCompiledForOsr()) {
|
|
graph_entry_->RelinkToOsrEntry(Z, last_used_block_id_ + 1);
|
|
}
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
void FlowGraphBuilder::SetConstantRangeOfCurrentDefinition(
|
|
const Fragment& fragment,
|
|
int64_t min,
|
|
int64_t max) {
|
|
ASSERT(fragment.current->IsDefinition());
|
|
Range range(RangeBoundary::FromConstant(min),
|
|
RangeBoundary::FromConstant(max));
|
|
fragment.current->AsDefinition()->set_range(range);
|
|
}
|
|
|
|
static classid_t TypedDataCidUnboxed(Representation unboxed_representation) {
|
|
switch (unboxed_representation) {
|
|
case kUnboxedFloat:
|
|
// Note kTypedDataFloat32ArrayCid loads kUnboxedDouble.
|
|
UNREACHABLE();
|
|
return kTypedDataFloat32ArrayCid;
|
|
case kUnboxedInt32:
|
|
return kTypedDataInt32ArrayCid;
|
|
case kUnboxedUint32:
|
|
return kTypedDataUint32ArrayCid;
|
|
case kUnboxedInt64:
|
|
return kTypedDataInt64ArrayCid;
|
|
case kUnboxedDouble:
|
|
return kTypedDataFloat64ArrayCid;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::StoreIndexedTypedDataUnboxed(
|
|
Representation unboxed_representation,
|
|
intptr_t index_scale,
|
|
bool index_unboxed) {
|
|
ASSERT(unboxed_representation == kUnboxedInt32 ||
|
|
unboxed_representation == kUnboxedUint32 ||
|
|
unboxed_representation == kUnboxedInt64 ||
|
|
unboxed_representation == kUnboxedFloat ||
|
|
unboxed_representation == kUnboxedDouble);
|
|
Fragment fragment;
|
|
if (unboxed_representation == kUnboxedFloat) {
|
|
fragment += BitCast(kUnboxedFloat, kUnboxedInt32);
|
|
unboxed_representation = kUnboxedInt32;
|
|
}
|
|
fragment += StoreIndexedTypedData(TypedDataCidUnboxed(unboxed_representation),
|
|
index_scale, index_unboxed);
|
|
return fragment;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::LoadIndexedTypedDataUnboxed(
|
|
Representation unboxed_representation,
|
|
intptr_t index_scale,
|
|
bool index_unboxed) {
|
|
ASSERT(unboxed_representation == kUnboxedInt32 ||
|
|
unboxed_representation == kUnboxedUint32 ||
|
|
unboxed_representation == kUnboxedInt64 ||
|
|
unboxed_representation == kUnboxedFloat ||
|
|
unboxed_representation == kUnboxedDouble);
|
|
Representation representation_for_load = unboxed_representation;
|
|
if (unboxed_representation == kUnboxedFloat) {
|
|
representation_for_load = kUnboxedInt32;
|
|
}
|
|
Fragment fragment;
|
|
fragment += LoadIndexed(TypedDataCidUnboxed(representation_for_load),
|
|
index_scale, index_unboxed);
|
|
if (unboxed_representation == kUnboxedFloat) {
|
|
fragment += BitCast(kUnboxedInt32, kUnboxedFloat);
|
|
}
|
|
return fragment;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::EnterHandleScope() {
|
|
auto* instr = new (Z)
|
|
EnterHandleScopeInstr(EnterHandleScopeInstr::Kind::kEnterHandleScope);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::GetTopHandleScope() {
|
|
auto* instr = new (Z)
|
|
EnterHandleScopeInstr(EnterHandleScopeInstr::Kind::kGetTopHandleScope);
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::ExitHandleScope() {
|
|
auto* instr = new (Z) ExitHandleScopeInstr();
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::AllocateHandle(LocalVariable* api_local_scope) {
|
|
Fragment code;
|
|
if (api_local_scope != nullptr) {
|
|
// Use the reference the scope we created in the trampoline.
|
|
code += LoadLocal(api_local_scope);
|
|
} else {
|
|
// Or get a reference to the top handle scope.
|
|
code += GetTopHandleScope();
|
|
}
|
|
Value* api_local_scope_value = Pop();
|
|
auto* instr = new (Z) AllocateHandleInstr(api_local_scope_value);
|
|
Push(instr);
|
|
code <<= instr;
|
|
return code;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::RawStoreField(int32_t offset) {
|
|
Fragment code;
|
|
Value* value = Pop();
|
|
Value* base = Pop();
|
|
auto* instr = new (Z) RawStoreFieldInstr(base, value, offset);
|
|
code <<= instr;
|
|
return code;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::WrapHandle(LocalVariable* api_local_scope) {
|
|
Fragment code;
|
|
LocalVariable* object = MakeTemporary();
|
|
code += AllocateHandle(api_local_scope);
|
|
|
|
code += LoadLocal(MakeTemporary()); // Duplicate handle pointer.
|
|
code += ConvertUnboxedToUntagged(kUnboxedIntPtr);
|
|
code += LoadLocal(object);
|
|
code += RawStoreField(compiler::target::LocalHandle::ptr_offset());
|
|
|
|
code += DropTempsPreserveTop(1); // Drop object below handle.
|
|
return code;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::UnwrapHandle() {
|
|
Fragment code;
|
|
code += ConvertUnboxedToUntagged(kUnboxedIntPtr);
|
|
code += IntConstant(compiler::target::LocalHandle::ptr_offset());
|
|
code += UnboxTruncate(kUnboxedIntPtr);
|
|
code += LoadIndexed(kArrayCid, /*index_scale=*/1, /*index_unboxed=*/true);
|
|
return code;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::UnhandledException() {
|
|
const auto class_table = thread_->isolate_group()->class_table();
|
|
ASSERT(class_table->HasValidClassAt(kUnhandledExceptionCid));
|
|
const auto& klass =
|
|
Class::ZoneHandle(H.zone(), class_table->At(kUnhandledExceptionCid));
|
|
ASSERT(!klass.IsNull());
|
|
Fragment body;
|
|
body += AllocateObject(TokenPosition::kNoSource, klass, 0);
|
|
LocalVariable* error_instance = MakeTemporary();
|
|
|
|
body += LoadLocal(error_instance);
|
|
body += LoadLocal(CurrentException());
|
|
body += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::UnhandledException_exception(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier);
|
|
|
|
body += LoadLocal(error_instance);
|
|
body += LoadLocal(CurrentStackTrace());
|
|
body += StoreInstanceField(
|
|
TokenPosition::kNoSource, Slot::UnhandledException_stacktrace(),
|
|
StoreInstanceFieldInstr::Kind::kInitializing, kNoStoreBarrier);
|
|
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::UnboxTruncate(Representation to) {
|
|
auto* unbox = UnboxInstr::Create(to, Pop(), DeoptId::kNone,
|
|
Instruction::kNotSpeculative);
|
|
Push(unbox);
|
|
return Fragment(unbox);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::NativeReturn(
|
|
const compiler::ffi::CallbackMarshaller& marshaller) {
|
|
auto* instr = new (Z)
|
|
NativeReturnInstr(InstructionSource(), Pop(), marshaller, DeoptId::kNone);
|
|
return Fragment(instr).closed();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) {
|
|
LocalVariable* address = MakeTemporary();
|
|
LocalVariable* result = parsed_function_->expression_temp_var();
|
|
|
|
Class& result_class = Class::ZoneHandle(Z, result_type.type_class());
|
|
// This class might only be instantiated as a return type of ffi calls.
|
|
result_class.EnsureIsFinalized(thread_);
|
|
|
|
TypeArguments& args = TypeArguments::ZoneHandle(Z, result_type.arguments());
|
|
|
|
// A kernel transform for FFI in the front-end ensures that type parameters
|
|
// do not appear in the type arguments to a any Pointer classes in an FFI
|
|
// signature.
|
|
ASSERT(args.IsNull() || args.IsInstantiated());
|
|
args = args.Canonicalize(thread_, nullptr);
|
|
|
|
Fragment code;
|
|
code += Constant(args);
|
|
code += AllocateObject(TokenPosition::kNoSource, result_class, 1);
|
|
LocalVariable* pointer = MakeTemporary();
|
|
code += LoadLocal(pointer);
|
|
code += LoadLocal(address);
|
|
code += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
code += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
|
|
code += StoreUntagged(compiler::target::Pointer::data_field_offset());
|
|
code += StoreLocal(TokenPosition::kNoSource, result);
|
|
code += Drop(); // StoreLocal^
|
|
code += Drop(); // address
|
|
code += LoadLocal(result);
|
|
|
|
return code;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BitCast(Representation from, Representation to) {
|
|
BitCastInstr* instr = new (Z) BitCastInstr(from, to, Pop());
|
|
Push(instr);
|
|
return Fragment(instr);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::WrapTypedDataBaseInCompound(
|
|
const AbstractType& compound_type) {
|
|
const auto& compound_sub_class =
|
|
Class::ZoneHandle(Z, compound_type.type_class());
|
|
compound_sub_class.EnsureIsFinalized(thread_);
|
|
const auto& lib_ffi = Library::Handle(Z, Library::FfiLibrary());
|
|
const auto& compound_class =
|
|
Class::Handle(Z, lib_ffi.LookupClassAllowPrivate(Symbols::Compound()));
|
|
const auto& compound_typed_data_base =
|
|
Field::ZoneHandle(Z, compound_class.LookupInstanceFieldAllowPrivate(
|
|
Symbols::_typedDataBase()));
|
|
ASSERT(!compound_typed_data_base.IsNull());
|
|
|
|
Fragment body;
|
|
LocalVariable* typed_data = MakeTemporary("typed_data_base");
|
|
body += AllocateObject(TokenPosition::kNoSource, compound_sub_class, 0);
|
|
body += LoadLocal(MakeTemporary("compound")); // Duplicate Struct or Union.
|
|
body += LoadLocal(typed_data);
|
|
body += StoreInstanceField(compound_typed_data_base,
|
|
StoreInstanceFieldInstr::Kind::kInitializing);
|
|
body += DropTempsPreserveTop(1); // Drop TypedData.
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::LoadTypedDataBaseFromCompound() {
|
|
const auto& lib_ffi = Library::Handle(Z, Library::FfiLibrary());
|
|
const auto& compound_class =
|
|
Class::Handle(Z, lib_ffi.LookupClassAllowPrivate(Symbols::Compound()));
|
|
const auto& compound_typed_data_base =
|
|
Field::ZoneHandle(Z, compound_class.LookupInstanceFieldAllowPrivate(
|
|
Symbols::_typedDataBase()));
|
|
ASSERT(!compound_typed_data_base.IsNull());
|
|
|
|
Fragment body;
|
|
body += LoadField(compound_typed_data_base, /*calls_initializer=*/false);
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CopyFromCompoundToStack(
|
|
LocalVariable* variable,
|
|
const GrowableArray<Representation>& representations) {
|
|
Fragment body;
|
|
const intptr_t num_defs = representations.length();
|
|
int offset_in_bytes = 0;
|
|
for (intptr_t i = 0; i < num_defs; i++) {
|
|
body += LoadLocal(variable);
|
|
body += LoadTypedDataBaseFromCompound();
|
|
body += LoadUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += IntConstant(offset_in_bytes);
|
|
const Representation representation = representations[i];
|
|
offset_in_bytes += RepresentationUtils::ValueSize(representation);
|
|
body += LoadIndexedTypedDataUnboxed(representation, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
}
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::PopFromStackToTypedDataBase(
|
|
ZoneGrowableArray<LocalVariable*>* definitions,
|
|
const GrowableArray<Representation>& representations) {
|
|
Fragment body;
|
|
const intptr_t num_defs = representations.length();
|
|
ASSERT(definitions->length() == num_defs);
|
|
|
|
LocalVariable* uint8_list = MakeTemporary("uint8_list");
|
|
int offset_in_bytes = 0;
|
|
for (intptr_t i = 0; i < num_defs; i++) {
|
|
const Representation representation = representations[i];
|
|
body += LoadLocal(uint8_list);
|
|
body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset());
|
|
body += IntConstant(offset_in_bytes);
|
|
body += LoadLocal(definitions->At(i));
|
|
body += StoreIndexedTypedDataUnboxed(representation, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
offset_in_bytes += RepresentationUtils::ValueSize(representation);
|
|
}
|
|
body += DropTempsPreserveTop(num_defs); // Drop chunck defs keep TypedData.
|
|
return body;
|
|
}
|
|
|
|
static intptr_t chunk_size(intptr_t bytes_left) {
|
|
ASSERT(bytes_left >= 1);
|
|
if (bytes_left >= 8 && compiler::target::kWordSize == 8) {
|
|
return 8;
|
|
}
|
|
if (bytes_left >= 4) {
|
|
return 4;
|
|
}
|
|
if (bytes_left >= 2) {
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static classid_t typed_data_cid(intptr_t chunk_size) {
|
|
switch (chunk_size) {
|
|
case 8:
|
|
return kTypedDataInt64ArrayCid;
|
|
case 4:
|
|
return kTypedDataInt32ArrayCid;
|
|
case 2:
|
|
return kTypedDataInt16ArrayCid;
|
|
case 1:
|
|
return kTypedDataInt8ArrayCid;
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CopyFromTypedDataBaseToUnboxedAddress(
|
|
intptr_t length_in_bytes) {
|
|
Fragment body;
|
|
Value* unboxed_address_value = Pop();
|
|
LocalVariable* typed_data_base = MakeTemporary("typed_data_base");
|
|
Push(unboxed_address_value->definition());
|
|
LocalVariable* unboxed_address = MakeTemporary("unboxed_address");
|
|
|
|
intptr_t offset_in_bytes = 0;
|
|
while (offset_in_bytes < length_in_bytes) {
|
|
const intptr_t bytes_left = length_in_bytes - offset_in_bytes;
|
|
const intptr_t chunk_sizee = chunk_size(bytes_left);
|
|
const classid_t typed_data_cidd = typed_data_cid(chunk_sizee);
|
|
|
|
body += LoadLocal(typed_data_base);
|
|
body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset());
|
|
body += IntConstant(offset_in_bytes);
|
|
body += LoadIndexed(typed_data_cidd, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
LocalVariable* chunk_value = MakeTemporary("chunk_value");
|
|
|
|
body += LoadLocal(unboxed_address);
|
|
body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
|
|
body += IntConstant(offset_in_bytes);
|
|
body += LoadLocal(chunk_value);
|
|
body += StoreIndexedTypedData(typed_data_cidd, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
body += DropTemporary(&chunk_value);
|
|
|
|
offset_in_bytes += chunk_sizee;
|
|
}
|
|
ASSERT(offset_in_bytes == length_in_bytes);
|
|
|
|
body += DropTemporary(&unboxed_address);
|
|
body += DropTemporary(&typed_data_base);
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CopyFromUnboxedAddressToTypedDataBase(
|
|
intptr_t length_in_bytes) {
|
|
Fragment body;
|
|
Value* typed_data_base_value = Pop();
|
|
LocalVariable* unboxed_address = MakeTemporary("unboxed_address");
|
|
Push(typed_data_base_value->definition());
|
|
LocalVariable* typed_data_base = MakeTemporary("typed_data_base");
|
|
|
|
intptr_t offset_in_bytes = 0;
|
|
while (offset_in_bytes < length_in_bytes) {
|
|
const intptr_t bytes_left = length_in_bytes - offset_in_bytes;
|
|
const intptr_t chunk_sizee = chunk_size(bytes_left);
|
|
const classid_t typed_data_cidd = typed_data_cid(chunk_sizee);
|
|
|
|
body += LoadLocal(unboxed_address);
|
|
body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
|
|
body += IntConstant(offset_in_bytes);
|
|
body += LoadIndexed(typed_data_cidd, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
LocalVariable* chunk_value = MakeTemporary("chunk_value");
|
|
|
|
body += LoadLocal(typed_data_base);
|
|
body += LoadUntagged(compiler::target::TypedDataBase::data_field_offset());
|
|
body += IntConstant(offset_in_bytes);
|
|
body += LoadLocal(chunk_value);
|
|
body += StoreIndexedTypedData(typed_data_cidd, /*index_scale=*/1,
|
|
/*index_unboxed=*/false);
|
|
body += DropTemporary(&chunk_value);
|
|
|
|
offset_in_bytes += chunk_sizee;
|
|
}
|
|
ASSERT(offset_in_bytes == length_in_bytes);
|
|
|
|
body += DropTemporary(&typed_data_base);
|
|
body += DropTemporary(&unboxed_address);
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiCallConvertCompoundArgumentToNative(
|
|
LocalVariable* variable,
|
|
const compiler::ffi::BaseMarshaller& marshaller,
|
|
intptr_t arg_index) {
|
|
Fragment body;
|
|
const auto& native_loc = marshaller.Location(arg_index);
|
|
if (native_loc.IsStack() || native_loc.IsMultiple()) {
|
|
// Break struct in pieces to separate IL definitions to pass those
|
|
// separate definitions into the FFI call.
|
|
GrowableArray<Representation> representations;
|
|
marshaller.RepsInFfiCall(arg_index, &representations);
|
|
body += CopyFromCompoundToStack(variable, representations);
|
|
} else {
|
|
ASSERT(native_loc.IsPointerToMemory());
|
|
// Only load the typed data, do copying in the FFI call machine code.
|
|
body += LoadLocal(variable); // User-defined struct.
|
|
body += LoadTypedDataBaseFromCompound();
|
|
}
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiCallConvertCompoundReturnToDart(
|
|
const compiler::ffi::BaseMarshaller& marshaller,
|
|
intptr_t arg_index) {
|
|
Fragment body;
|
|
// The typed data is allocated before the FFI call, and is populated in
|
|
// machine code. So, here, it only has to be wrapped in the struct class.
|
|
const auto& compound_type =
|
|
AbstractType::Handle(Z, marshaller.CType(arg_index));
|
|
body += WrapTypedDataBaseInCompound(compound_type);
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiCallbackConvertCompoundArgumentToDart(
|
|
const compiler::ffi::BaseMarshaller& marshaller,
|
|
intptr_t arg_index,
|
|
ZoneGrowableArray<LocalVariable*>* definitions) {
|
|
const intptr_t length_in_bytes =
|
|
marshaller.Location(arg_index).payload_type().SizeInBytes();
|
|
|
|
Fragment body;
|
|
if ((marshaller.Location(arg_index).IsMultiple() ||
|
|
marshaller.Location(arg_index).IsStack())) {
|
|
// Allocate and populate a TypedData from the individual NativeParameters.
|
|
body += IntConstant(length_in_bytes);
|
|
body +=
|
|
AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid);
|
|
GrowableArray<Representation> representations;
|
|
marshaller.RepsInFfiCall(arg_index, &representations);
|
|
body += PopFromStackToTypedDataBase(definitions, representations);
|
|
} else {
|
|
ASSERT(marshaller.Location(arg_index).IsPointerToMemory());
|
|
// Allocate a TypedData and copy contents pointed to by an address into it.
|
|
LocalVariable* address_of_compound = MakeTemporary("address_of_compound");
|
|
body += IntConstant(length_in_bytes);
|
|
body +=
|
|
AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid);
|
|
LocalVariable* typed_data_base = MakeTemporary("typed_data_base");
|
|
body += LoadLocal(address_of_compound);
|
|
body += LoadLocal(typed_data_base);
|
|
body += CopyFromUnboxedAddressToTypedDataBase(length_in_bytes);
|
|
body += DropTempsPreserveTop(1); // address_of_compound.
|
|
}
|
|
// Wrap typed data in compound class.
|
|
const auto& compound_type =
|
|
AbstractType::Handle(Z, marshaller.CType(arg_index));
|
|
body += WrapTypedDataBaseInCompound(compound_type);
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiCallbackConvertCompoundReturnToNative(
|
|
const compiler::ffi::CallbackMarshaller& marshaller,
|
|
intptr_t arg_index) {
|
|
Fragment body;
|
|
const auto& native_loc = marshaller.Location(arg_index);
|
|
if (native_loc.IsMultiple()) {
|
|
// We pass in typed data to native return instruction, and do the copying
|
|
// in machine code.
|
|
body += LoadTypedDataBaseFromCompound();
|
|
} else {
|
|
ASSERT(native_loc.IsPointerToMemory());
|
|
// We copy the data into the right location in IL.
|
|
const intptr_t length_in_bytes =
|
|
marshaller.Location(arg_index).payload_type().SizeInBytes();
|
|
|
|
body += LoadTypedDataBaseFromCompound();
|
|
LocalVariable* typed_data_base = MakeTemporary("typed_data_base");
|
|
|
|
auto* pointer_to_return =
|
|
new (Z) NativeParameterInstr(marshaller, compiler::ffi::kResultIndex);
|
|
Push(pointer_to_return); // Address where return value should be stored.
|
|
body <<= pointer_to_return;
|
|
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
LocalVariable* unboxed_address = MakeTemporary("unboxed_address");
|
|
|
|
body += LoadLocal(typed_data_base);
|
|
body += LoadLocal(unboxed_address);
|
|
body += CopyFromTypedDataBaseToUnboxedAddress(length_in_bytes);
|
|
body += DropTempsPreserveTop(1); // Keep address, drop typed_data_base.
|
|
}
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiConvertPrimitiveToDart(
|
|
const compiler::ffi::BaseMarshaller& marshaller,
|
|
intptr_t arg_index) {
|
|
ASSERT(!marshaller.IsCompound(arg_index));
|
|
|
|
Fragment body;
|
|
if (marshaller.IsPointer(arg_index)) {
|
|
body += Box(kUnboxedFfiIntPtr);
|
|
body += FfiPointerFromAddress(
|
|
Type::CheckedHandle(Z, marshaller.CType(arg_index)));
|
|
} else if (marshaller.IsHandle(arg_index)) {
|
|
body += UnwrapHandle();
|
|
} else if (marshaller.IsVoid(arg_index)) {
|
|
body += Drop();
|
|
body += NullConstant();
|
|
} else {
|
|
if (marshaller.RequiresBitCast(arg_index)) {
|
|
body += BitCast(
|
|
marshaller.RepInFfiCall(marshaller.FirstDefinitionIndex(arg_index)),
|
|
marshaller.RepInDart(arg_index));
|
|
}
|
|
|
|
body += Box(marshaller.RepInDart(arg_index));
|
|
}
|
|
return body;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiConvertPrimitiveToNative(
|
|
const compiler::ffi::BaseMarshaller& marshaller,
|
|
intptr_t arg_index,
|
|
LocalVariable* api_local_scope) {
|
|
ASSERT(!marshaller.IsCompound(arg_index));
|
|
|
|
Fragment body;
|
|
if (marshaller.IsPointer(arg_index)) {
|
|
// This can only be Pointer, so it is always safe to LoadUntagged.
|
|
body += LoadUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
|
|
} else if (marshaller.IsHandle(arg_index)) {
|
|
body += WrapHandle(api_local_scope);
|
|
} else {
|
|
body += UnboxTruncate(marshaller.RepInDart(arg_index));
|
|
}
|
|
|
|
if (marshaller.RequiresBitCast(arg_index)) {
|
|
body += BitCast(
|
|
marshaller.RepInDart(arg_index),
|
|
marshaller.RepInFfiCall(marshaller.FirstDefinitionIndex(arg_index)));
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
|
|
const Function& function) {
|
|
if (function.FfiCallbackTarget() != Function::null()) {
|
|
return BuildGraphOfFfiCallback(function);
|
|
} else {
|
|
return BuildGraphOfFfiNative(function);
|
|
}
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) {
|
|
const intptr_t kClosureParameterOffset = 0;
|
|
const intptr_t kFirstArgumentParameterOffset = kClosureParameterOffset + 1;
|
|
|
|
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 function_body(instruction_cursor);
|
|
function_body += CheckStackOverflowInPrologue(function.token_pos());
|
|
|
|
const auto& marshaller = *new (Z) compiler::ffi::CallMarshaller(Z, function);
|
|
|
|
const bool signature_contains_handles = marshaller.ContainsHandles();
|
|
|
|
// FFI trampolines are accessed via closures, so non-covariant argument types
|
|
// and type arguments are either statically checked by the type system or
|
|
// dynamically checked via dynamic closure call dispatchers.
|
|
|
|
// Null check arguments before we go into the try catch, so that we don't
|
|
// catch our own null errors.
|
|
const intptr_t num_args = marshaller.num_args();
|
|
for (intptr_t i = 0; i < num_args; i++) {
|
|
if (marshaller.IsHandle(i)) {
|
|
continue;
|
|
}
|
|
function_body += LoadLocal(
|
|
parsed_function_->ParameterVariable(kFirstArgumentParameterOffset + i));
|
|
// Check for 'null'.
|
|
// TODO(36780): Mention the param name instead of function reciever.
|
|
function_body +=
|
|
CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, marshaller.function_name()));
|
|
function_body += StoreLocal(
|
|
TokenPosition::kNoSource,
|
|
parsed_function_->ParameterVariable(kFirstArgumentParameterOffset + i));
|
|
function_body += Drop();
|
|
}
|
|
|
|
Fragment body;
|
|
intptr_t try_handler_index = -1;
|
|
LocalVariable* api_local_scope = nullptr;
|
|
if (signature_contains_handles) {
|
|
// Wrap in Try catch to transition from Native to Generated on a throw from
|
|
// the dart_api.
|
|
try_handler_index = AllocateTryIndex();
|
|
body += TryCatch(try_handler_index);
|
|
++try_depth_;
|
|
|
|
body += EnterHandleScope();
|
|
api_local_scope = MakeTemporary("api_local_scope");
|
|
}
|
|
|
|
// Allocate typed data before FfiCall and pass it in to ffi call if needed.
|
|
LocalVariable* typed_data = nullptr;
|
|
if (marshaller.PassTypedData()) {
|
|
body += IntConstant(marshaller.TypedDataSizeInBytes());
|
|
body +=
|
|
AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid);
|
|
typed_data = MakeTemporary();
|
|
}
|
|
|
|
// Unbox and push the arguments.
|
|
for (intptr_t i = 0; i < marshaller.num_args(); i++) {
|
|
if (marshaller.IsCompound(i)) {
|
|
body += FfiCallConvertCompoundArgumentToNative(
|
|
parsed_function_->ParameterVariable(kFirstArgumentParameterOffset +
|
|
i),
|
|
marshaller, i);
|
|
} else {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(
|
|
kFirstArgumentParameterOffset + i));
|
|
body += FfiConvertPrimitiveToNative(marshaller, i, api_local_scope);
|
|
}
|
|
}
|
|
|
|
// Push the function pointer, which is stored (as Pointer object) in the
|
|
// first slot of the context.
|
|
body +=
|
|
LoadLocal(parsed_function_->ParameterVariable(kClosureParameterOffset));
|
|
body += LoadNativeField(Slot::Closure_context());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *MakeImplicitClosureScope(
|
|
Z, Class::Handle(IG->object_store()->ffi_pointer_class()))
|
|
->context_variables()[0]));
|
|
|
|
// This can only be Pointer, so it is always safe to LoadUntagged.
|
|
body += LoadUntagged(compiler::target::Pointer::data_field_offset());
|
|
body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
|
|
|
|
if (marshaller.PassTypedData()) {
|
|
body += LoadLocal(typed_data);
|
|
}
|
|
|
|
body += FfiCall(marshaller);
|
|
|
|
for (intptr_t i = 0; i < marshaller.num_args(); i++) {
|
|
if (marshaller.IsPointer(i)) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(
|
|
kFirstArgumentParameterOffset + i));
|
|
body += ReachabilityFence();
|
|
}
|
|
}
|
|
|
|
const intptr_t num_defs = marshaller.NumReturnDefinitions();
|
|
ASSERT(num_defs >= 1);
|
|
auto defs = new (Z) ZoneGrowableArray<LocalVariable*>(Z, num_defs);
|
|
LocalVariable* def = MakeTemporary();
|
|
defs->Add(def);
|
|
|
|
if (marshaller.PassTypedData()) {
|
|
// Drop call result, typed data with contents is already on the stack.
|
|
body += Drop();
|
|
}
|
|
|
|
if (marshaller.IsCompound(compiler::ffi::kResultIndex)) {
|
|
body += FfiCallConvertCompoundReturnToDart(marshaller,
|
|
compiler::ffi::kResultIndex);
|
|
} else {
|
|
body += FfiConvertPrimitiveToDart(marshaller, compiler::ffi::kResultIndex);
|
|
}
|
|
|
|
if (signature_contains_handles) {
|
|
body += DropTempsPreserveTop(1); // Drop api_local_scope.
|
|
body += ExitHandleScope();
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
if (signature_contains_handles) {
|
|
--try_depth_;
|
|
}
|
|
|
|
function_body += body;
|
|
|
|
if (signature_contains_handles) {
|
|
++catch_depth_;
|
|
Fragment catch_body =
|
|
CatchBlockEntry(Array::empty_array(), try_handler_index,
|
|
/*needs_stacktrace=*/true, /*is_synthesized=*/true);
|
|
|
|
// TODO(41984): If we want to pass in the handle scope, move it out
|
|
// of the try catch.
|
|
catch_body += ExitHandleScope();
|
|
|
|
catch_body += LoadLocal(CurrentException());
|
|
catch_body += LoadLocal(CurrentStackTrace());
|
|
catch_body += RethrowException(TokenPosition::kNoSource, try_handler_index);
|
|
--catch_depth_;
|
|
}
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) {
|
|
const auto& marshaller =
|
|
*new (Z) compiler::ffi::CallbackMarshaller(Z, function);
|
|
|
|
graph_entry_ =
|
|
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
|
|
|
|
auto* const native_entry = new (Z) NativeEntryInstr(
|
|
marshaller, graph_entry_, AllocateBlockId(), CurrentTryIndex(),
|
|
GetNextDeoptId(), function.FfiCallbackId());
|
|
|
|
graph_entry_->set_normal_entry(native_entry);
|
|
|
|
Fragment function_body(native_entry);
|
|
function_body += CheckStackOverflowInPrologue(function.token_pos());
|
|
|
|
// Wrap the entire method in a big try/catch. This is important to ensure that
|
|
// the VM does not crash if the callback throws an exception.
|
|
const intptr_t try_handler_index = AllocateTryIndex();
|
|
Fragment body = TryCatch(try_handler_index);
|
|
++try_depth_;
|
|
|
|
// Box and push the arguments.
|
|
for (intptr_t i = 0; i < marshaller.num_args(); i++) {
|
|
const intptr_t num_defs = marshaller.NumDefinitions(i);
|
|
auto defs = new (Z) ZoneGrowableArray<LocalVariable*>(Z, num_defs);
|
|
|
|
for (intptr_t j = 0; j < num_defs; j++) {
|
|
const intptr_t def_index = marshaller.DefinitionIndex(j, i);
|
|
auto* parameter = new (Z) NativeParameterInstr(marshaller, def_index);
|
|
Push(parameter);
|
|
body <<= parameter;
|
|
LocalVariable* def = MakeTemporary();
|
|
defs->Add(def);
|
|
}
|
|
|
|
if (marshaller.IsCompound(i)) {
|
|
body += FfiCallbackConvertCompoundArgumentToDart(marshaller, i, defs);
|
|
} else {
|
|
body += FfiConvertPrimitiveToDart(marshaller, i);
|
|
}
|
|
}
|
|
|
|
// Call the target.
|
|
//
|
|
// TODO(36748): Determine the hot-reload semantics of callbacks and update the
|
|
// rebind-rule accordingly.
|
|
body += StaticCall(TokenPosition::kNoSource,
|
|
Function::ZoneHandle(Z, function.FfiCallbackTarget()),
|
|
marshaller.num_args(), Array::empty_array(),
|
|
ICData::kNoRebind);
|
|
if (marshaller.IsVoid(compiler::ffi::kResultIndex)) {
|
|
body += Drop();
|
|
body += IntConstant(0);
|
|
} else if (!marshaller.IsHandle(compiler::ffi::kResultIndex)) {
|
|
body +=
|
|
CheckNullOptimized(TokenPosition::kNoSource,
|
|
String::ZoneHandle(Z, marshaller.function_name()));
|
|
}
|
|
|
|
if (marshaller.IsCompound(compiler::ffi::kResultIndex)) {
|
|
body += FfiCallbackConvertCompoundReturnToNative(
|
|
marshaller, compiler::ffi::kResultIndex);
|
|
} else {
|
|
body += FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex,
|
|
/*api_local_scope=*/nullptr);
|
|
}
|
|
|
|
body += NativeReturn(marshaller);
|
|
|
|
--try_depth_;
|
|
function_body += body;
|
|
|
|
++catch_depth_;
|
|
Fragment catch_body = CatchBlockEntry(Array::empty_array(), try_handler_index,
|
|
/*needs_stacktrace=*/false,
|
|
/*is_synthesized=*/true);
|
|
|
|
// Return the "exceptional return" value given in 'fromFunction'.
|
|
//
|
|
// For pointer and void return types, the exceptional return is always null --
|
|
// return 0 instead.
|
|
if (marshaller.IsPointer(compiler::ffi::kResultIndex) ||
|
|
marshaller.IsVoid(compiler::ffi::kResultIndex)) {
|
|
ASSERT(function.FfiCallbackExceptionalReturn() == Object::null());
|
|
catch_body += IntConstant(0);
|
|
catch_body += UnboxTruncate(kUnboxedFfiIntPtr);
|
|
} else if (marshaller.IsHandle(compiler::ffi::kResultIndex)) {
|
|
catch_body += UnhandledException();
|
|
catch_body +=
|
|
FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex,
|
|
/*api_local_scope=*/nullptr);
|
|
|
|
} else if (marshaller.IsCompound(compiler::ffi::kResultIndex)) {
|
|
ASSERT(function.FfiCallbackExceptionalReturn() == Object::null());
|
|
// Manufacture empty result.
|
|
const intptr_t size =
|
|
Utils::RoundUp(marshaller.Location(compiler::ffi::kResultIndex)
|
|
.payload_type()
|
|
.SizeInBytes(),
|
|
compiler::target::kWordSize);
|
|
catch_body += IntConstant(size);
|
|
catch_body +=
|
|
AllocateTypedData(TokenPosition::kNoSource, kTypedDataUint8ArrayCid);
|
|
catch_body += WrapTypedDataBaseInCompound(
|
|
AbstractType::Handle(Z, marshaller.CType(compiler::ffi::kResultIndex)));
|
|
catch_body += FfiCallbackConvertCompoundReturnToNative(
|
|
marshaller, compiler::ffi::kResultIndex);
|
|
|
|
} else {
|
|
catch_body += Constant(
|
|
Instance::ZoneHandle(Z, function.FfiCallbackExceptionalReturn()));
|
|
catch_body +=
|
|
FfiConvertPrimitiveToNative(marshaller, compiler::ffi::kResultIndex,
|
|
/*api_local_scope=*/nullptr);
|
|
}
|
|
|
|
catch_body += NativeReturn(marshaller);
|
|
--catch_depth_;
|
|
|
|
PrologueInfo prologue_info(-1, -1);
|
|
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
|
|
: try_catch_block->try_index());
|
|
}
|
|
|
|
bool FlowGraphBuilder::NeedsNullAssertion(const AbstractType& type) {
|
|
if (!type.IsNonNullable()) {
|
|
return false;
|
|
}
|
|
if (type.IsTypeParameter()) {
|
|
return NeedsNullAssertion(
|
|
AbstractType::Handle(Z, TypeParameter::Cast(type).bound()));
|
|
}
|
|
if (type.IsFutureOrType()) {
|
|
return NeedsNullAssertion(AbstractType::Handle(Z, type.UnwrapFutureOr()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::NullAssertion(LocalVariable* variable) {
|
|
Fragment code;
|
|
if (!NeedsNullAssertion(variable->type())) {
|
|
return code;
|
|
}
|
|
|
|
TargetEntryInstr* then;
|
|
TargetEntryInstr* otherwise;
|
|
|
|
code += LoadLocal(variable);
|
|
code += NullConstant();
|
|
code += BranchIfEqual(&then, &otherwise);
|
|
|
|
const Script& script =
|
|
Script::Handle(Z, parsed_function_->function().script());
|
|
intptr_t line = -1;
|
|
intptr_t column = -1;
|
|
script.GetTokenLocation(variable->token_pos(), &line, &column);
|
|
|
|
// Build equivalent of `throw _AssertionError._throwNewNullAssertion(name)`
|
|
// expression. We build throw (even through _throwNewNullAssertion already
|
|
// throws) because call is not a valid last instruction for the block.
|
|
// Blocks can only terminate with explicit control flow instructions
|
|
// (Branch, Goto, Return or Throw).
|
|
Fragment null_code(then);
|
|
null_code += Constant(variable->name());
|
|
null_code += IntConstant(line);
|
|
null_code += IntConstant(column);
|
|
null_code += StaticCall(variable->token_pos(),
|
|
ThrowNewNullAssertionFunction(), 3, ICData::kStatic);
|
|
null_code += ThrowException(TokenPosition::kNoSource);
|
|
null_code += Drop();
|
|
|
|
return Fragment(code.entry, otherwise);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildNullAssertions() {
|
|
Fragment code;
|
|
if (IG->null_safety() || !IG->asserts() || !FLAG_null_assertions) {
|
|
return code;
|
|
}
|
|
|
|
const Function& dart_function = parsed_function_->function();
|
|
for (intptr_t i = dart_function.NumImplicitParameters(),
|
|
n = dart_function.NumParameters();
|
|
i < n; ++i) {
|
|
LocalVariable* variable = parsed_function_->ParameterVariable(i);
|
|
code += NullAssertion(variable);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
const Function& FlowGraphBuilder::ThrowNewNullAssertionFunction() {
|
|
if (throw_new_null_assertion_.IsNull()) {
|
|
const Class& klass = Class::ZoneHandle(
|
|
Z, Library::LookupCoreClass(Symbols::AssertionError()));
|
|
ASSERT(!klass.IsNull());
|
|
const auto& error = klass.EnsureIsFinalized(H.thread());
|
|
ASSERT(error == Error::null());
|
|
throw_new_null_assertion_ = klass.LookupStaticFunctionAllowPrivate(
|
|
Symbols::ThrowNewNullAssertion());
|
|
ASSERT(!throw_new_null_assertion_.IsNull());
|
|
}
|
|
return throw_new_null_assertion_;
|
|
}
|
|
|
|
const Function& FlowGraphBuilder::PrependTypeArgumentsFunction() {
|
|
if (prepend_type_arguments_.IsNull()) {
|
|
const auto& dart_internal = Library::Handle(Z, Library::InternalLibrary());
|
|
prepend_type_arguments_ = dart_internal.LookupFunctionAllowPrivate(
|
|
Symbols::PrependTypeArguments());
|
|
ASSERT(!prepend_type_arguments_.IsNull());
|
|
}
|
|
return prepend_type_arguments_;
|
|
}
|
|
|
|
} // namespace kernel
|
|
|
|
} // namespace dart
|