Files
sdk/runtime/vm/code_generator.cc
T
dgrove@google.com 4c0f559d23 Initial checkin.
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15 260f80e4-7a28-3924-810f-c04153c831b5
2011-10-05 05:20:07 +00:00

997 lines
40 KiB
C++

// Copyright (c) 2011, 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/code_generator.h"
#include "vm/code_index_table.h"
#include "vm/code_patcher.h"
#include "vm/compiler.h"
#include "vm/dart_entry.h"
#include "vm/exceptions.h"
#include "vm/ic_stubs.h"
#include "vm/object_store.h"
#include "vm/resolver.h"
#include "vm/runtime_entry.h"
#include "vm/stack_frame.h"
#include "vm/verifier.h"
namespace dart {
DEFINE_FLAG(bool, inline_cache, true, "enable inline caches");
DEFINE_FLAG(bool, trace_deopt, false, "Trace deoptimization");
DEFINE_FLAG(bool, trace_ic, false, "trace IC handling");
DEFINE_FLAG(bool, trace_patching, false, "Trace patching of code.");
DEFINE_FLAG(bool, trace_runtime_calls, false, "Trace runtime calls.");
const Array& CodeGenerator::ArgumentsDescriptor(int num_arguments,
ArgumentListNode* arguments) {
const Array& names = arguments == NULL ? Array::Handle() : arguments->names();
const intptr_t num_named_args = names.IsNull() ? 0 : names.Length();
const intptr_t num_pos_args = num_arguments - num_named_args;
// The code generator may prepend an extra positional argument to the list of
// actual parameters. This can be the receiver of an instance call, the
// allocated but uninitialized receiver of a constructor call, or the type
// argument vector of a factory call.
// The value 'num_arguments' is the actual total number of arguments,
// including the prepended positional argument, whereas
// 'arguments->length()' is the parsed number of arguments not including the
// prepended argument.
ASSERT((arguments == NULL) ||
(arguments->length() == num_arguments) ||
(arguments->length() == (num_arguments - 1)));
// Build the argument descriptor array, which consists of the total number of
// arguments, the number of positional arguments, alphabetically sorted
// pairs of name/position, and a terminating null.
const int descriptor_len = 3 + (2 * num_named_args);
Array& descriptor = Array::ZoneHandle(Array::New(descriptor_len));
// Set total number of passed arguments.
descriptor.SetAt(0, Smi::Handle(Smi::New(num_arguments)));
// Set number of positional arguments.
descriptor.SetAt(1, Smi::Handle(Smi::New(num_pos_args)));
// Set alphabetically sorted pairs of name/position for named arguments.
String& name = String::Handle();
Smi& pos = Smi::Handle();
for (int i = 0; i < num_named_args; i++) {
name ^= names.At(i);
pos = Smi::New(num_pos_args + i);
int j = i;
// Shift already inserted pairs with "larger" names.
String& name_j = String::Handle();
Smi& pos_j = Smi::Handle();
while (--j >= 0) {
name_j ^= descriptor.At(2 + (2 * j));
const intptr_t result = name.CompareTo(name_j);
ASSERT(result != 0); // Duplicate argument names checked in parser.
if (result > 0) break;
pos_j ^= descriptor.At(3 + (2 * j));
descriptor.SetAt(2 + (2 * (j + 1)), name_j);
descriptor.SetAt(3 + (2 * (j + 1)), pos_j);
}
// Insert pair in descriptor array.
descriptor.SetAt(2 + (2 * (j + 1)), name);
descriptor.SetAt(3 + (2 * (j + 1)), pos);
}
// Set terminating null.
descriptor.SetAt(descriptor_len - 1, Object::Handle());
// Share the immutable descriptor when possible by canonicalizing it.
descriptor.MakeImmutable();
descriptor ^= descriptor.Canonicalize();
return descriptor;
}
DEFINE_RUNTIME_ENTRY(TraceFunctionEntry, 1) {
ASSERT(arguments.Count() == kTraceFunctionEntryRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
const String& function_name = String::Handle(function.name());
const String& class_name =
String::Handle(Class::Handle(function.owner()).Name());
OS::Print("> Entering '%s.%s'\n",
class_name.ToCString(), function_name.ToCString());
}
DEFINE_RUNTIME_ENTRY(TraceFunctionExit, 1) {
ASSERT(arguments.Count() == kTraceFunctionExitRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
const String& function_name = String::Handle(function.name());
const String& class_name =
String::Handle(Class::Handle(function.owner()).Name());
OS::Print("< Exiting '%s.%s'\n",
class_name.ToCString(), function_name.ToCString());
}
// Allocation of a fixed length array of given element type.
// Arg0: array length.
// Arg1: array element type.
// Arg2: type arguments of the instantiator.
// Return value: newly allocated array of length arg0.
DEFINE_RUNTIME_ENTRY(AllocateArray, 3) {
ASSERT(arguments.Count() == kAllocateArrayRuntimeEntry.argument_count());
const Smi& length = Smi::CheckedHandle(arguments.At(0));
const Array& array = Array::Handle(Array::New(length.Value()));
arguments.SetReturn(array);
TypeArguments& element_type = TypeArguments::CheckedHandle(arguments.At(1));
if (element_type.IsNull()) {
// No instantiator required for a raw type.
ASSERT(TypeArguments::CheckedHandle(arguments.At(2)).IsNull());
return;
}
// An Array takes only one type argument.
ASSERT(element_type.Length() == 1);
const TypeArguments& instantiator =
TypeArguments::CheckedHandle(arguments.At(2));
if (instantiator.IsNull()) {
// Either the type element is instantiated (use it), or the instantiator is
// of a raw type and we cannot instantiate the element type (leave as null).
if (element_type.IsInstantiated()) {
array.SetTypeArguments(element_type);
}
return;
}
ASSERT(!element_type.IsInstantiated());
// If possible, use the instantiator as the type argument vector.
if (element_type.IsUninstantiatedIdentity() && (instantiator.Length() == 1)) {
// No need to check that the instantiator is a TypeArray, since the virtual
// call to Length() handles other cases that are harder to inline.
element_type = instantiator.raw();
} else {
element_type = TypeArguments::NewInstantiatedTypeArguments(element_type,
instantiator);
}
array.SetTypeArguments(element_type);
}
// Allocate a new object.
// Arg0: class of the object that needs to be allocated.
// Arg1: type arguments of the object that needs to be allocated.
// Arg2: type arguments of the instantiator.
// Return value: newly allocated object.
DEFINE_RUNTIME_ENTRY(AllocateObject, 3) {
ASSERT(arguments.Count() == kAllocateObjectRuntimeEntry.argument_count());
const Class& cls = Class::CheckedHandle(arguments.At(0));
const Instance& instance = Instance::Handle(Instance::New(cls));
arguments.SetReturn(instance);
if (!cls.IsParameterized()) {
// No type arguments required for a non-parameterized type.
ASSERT(Instance::CheckedHandle(arguments.At(1)).IsNull());
return;
}
TypeArguments& type_arguments = TypeArguments::CheckedHandle(arguments.At(1));
if (type_arguments.IsNull()) {
// No instantiator is required for a raw type.
ASSERT(Instance::CheckedHandle(arguments.At(2)).IsNull());
return;
}
ASSERT(type_arguments.Length() == cls.NumTypeArguments());
const TypeArguments& instantiator =
TypeArguments::CheckedHandle(arguments.At(2));
if (instantiator.IsNull()) {
// Either the type argument vector is instantiated (use it), or the
// instantiator is of a raw type and we cannot instantiate the type argument
// vector (leave it as null).
if (type_arguments.IsInstantiated()) {
instance.SetTypeArguments(type_arguments);
}
return;
}
ASSERT(!type_arguments.IsInstantiated());
// If possible, use the instantiator as the type argument vector.
if (instantiator.IsTypeArray()) {
// Code inlined in the caller should have optimized the case where the
// instantiator is a TypeArray and can be used as type argument vector.
ASSERT(!type_arguments.IsUninstantiatedIdentity() ||
(instantiator.Length() != type_arguments.Length()));
type_arguments = TypeArguments::NewInstantiatedTypeArguments(type_arguments,
instantiator);
} else {
if (type_arguments.IsUninstantiatedIdentity() &&
(instantiator.Length() == type_arguments.Length())) {
type_arguments = instantiator.raw();
} else {
type_arguments =
TypeArguments::NewInstantiatedTypeArguments(type_arguments,
instantiator);
}
}
instance.SetTypeArguments(type_arguments);
}
// Instantiate type arguments.
// Arg0: uninstantiated type arguments.
// Arg1: instantiator type arguments.
// Return value: instantiated type arguments.
DEFINE_RUNTIME_ENTRY(InstantiateTypeArguments, 2) {
ASSERT(arguments.Count() ==
kInstantiateTypeArgumentsRuntimeEntry.argument_count());
TypeArguments& type_arguments = TypeArguments::CheckedHandle(arguments.At(0));
const TypeArguments& instantiator =
TypeArguments::CheckedHandle(arguments.At(1));
ASSERT(!type_arguments.IsNull() &&
!type_arguments.IsInstantiated() &&
!instantiator.IsNull());
// Code inlined in the caller should have optimized the case where the
// instantiator can be used as type argument vector.
ASSERT(!type_arguments.IsUninstantiatedIdentity() ||
!instantiator.IsTypeArray() ||
(instantiator.Length() != type_arguments.Length()));
type_arguments = TypeArguments::NewInstantiatedTypeArguments(type_arguments,
instantiator);
arguments.SetReturn(type_arguments);
}
// Allocate a new closure.
// Arg0: local function.
// TODO(regis): Arg1: type arguments of the closure.
// TODO(regis): Arg2: type arguments of the instantiator.
// Return value: newly allocated closure.
DEFINE_RUNTIME_ENTRY(AllocateClosure, 1) {
ASSERT(arguments.Count() == kAllocateClosureRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
// TODO(regis): Process type arguments unless the closure is static.
// The current context was saved in the Isolate structure when entering the
// runtime.
const Context& context = Context::Handle(Isolate::Current()->top_context());
ASSERT(!context.IsNull());
arguments.SetReturn(Closure::Handle(Closure::New(function, context)));
}
// Allocate a new static implicit closure.
// Arg0: local function.
// Return value: newly allocated closure.
DEFINE_RUNTIME_ENTRY(AllocateStaticImplicitClosure, 1) {
ASSERT(arguments.Count() ==
kAllocateStaticImplicitClosureRuntimeEntry.argument_count());
ObjectStore* object_store = Isolate::Current()->object_store();
ASSERT(object_store != NULL);
const Function& function = Function::CheckedHandle(arguments.At(0));
ASSERT(function.is_static()); // Closure functions are always static for now.
const Context& context = Context::Handle(object_store->empty_context());
arguments.SetReturn(Closure::Handle(Closure::New(function, context)));
}
// Allocate a new implicit closure.
// Arg0: local function.
// Arg1: receiver object.
// TODO(regis): Arg2: type arguments of the closure.
// TODO(regis): Arg3: type arguments of the instantiator.
// Return value: newly allocated closure.
DEFINE_RUNTIME_ENTRY(AllocateImplicitClosure, 2) {
ASSERT(arguments.Count() ==
kAllocateImplicitClosureRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
ASSERT(function.is_static()); // Closure functions are always static for now.
const Instance& receiver = Instance::CheckedHandle(arguments.At(1));
Context& context = Context::Handle();
context = Context::New(1);
context.SetAt(0, receiver);
arguments.SetReturn(Closure::Handle(Closure::New(function, context)));
// TODO(regis): Set type arguments.
}
// Allocate a new context large enough to hold the given number of variables.
// Arg0: number of variables.
// Return value: newly allocated context.
DEFINE_RUNTIME_ENTRY(AllocateContext, 1) {
CHECK_STACK_ALIGNMENT;
ASSERT(arguments.Count() == kAllocateContextRuntimeEntry.argument_count());
const Smi& num_variables = Smi::CheckedHandle(arguments.At(0));
arguments.SetReturn(Context::Handle(Context::New(num_variables.Value())));
}
// Check that the given instance is an instance of the given type.
// Tested instance may not be null, because the null test is inlined.
// Arg0: instance being checked.
// Arg1: type.
// Arg2: type arguments of the instantiator of the type.
// Return value: true or false.
DEFINE_RUNTIME_ENTRY(Instanceof, 3) {
ASSERT(arguments.Count() == kInstanceofRuntimeEntry.argument_count());
const Instance& instance = Instance::CheckedHandle(arguments.At(0));
const Type& type = Type::CheckedHandle(arguments.At(1));
const TypeArguments& type_instantiator =
TypeArguments::CheckedHandle(arguments.At(2));
ASSERT(type.IsFinalized());
ASSERT(!instance.IsNull());
const Bool& result = Bool::Handle(
instance.IsInstanceOf(type, type_instantiator) ?
Bool::True() : Bool::False());
arguments.SetReturn(result);
}
DEFINE_RUNTIME_ENTRY(Throw, 1) {
ASSERT(arguments.Count() == kThrowRuntimeEntry.argument_count());
const Instance& exception = Instance::CheckedHandle(arguments.At(0));
Exceptions::Throw(exception);
}
DEFINE_RUNTIME_ENTRY(ReThrow, 2) {
ASSERT(arguments.Count() == kReThrowRuntimeEntry.argument_count());
const Instance& exception = Instance::CheckedHandle(arguments.At(0));
const Instance& stacktrace = Instance::CheckedHandle(arguments.At(1));
Exceptions::ReThrow(exception, stacktrace);
}
DEFINE_RUNTIME_ENTRY(PatchStaticCall, 0) {
// This function is called after successful resolving and compilation of
// the target method.
ASSERT(arguments.Count() == kPatchStaticCallRuntimeEntry.argument_count());
DartFrameIterator iterator;
DartFrame* caller_frame = iterator.NextFrame();
ASSERT(caller_frame != NULL);
uword target = 0;
Function& target_function = Function::Handle();
CodePatcher::GetStaticCallAt(caller_frame->pc(), &target_function, &target);
ASSERT(target_function.HasCode());
uword new_target = Code::Handle(target_function.code()).EntryPoint();
// Verify that we are not patching repeatedly.
ASSERT(target != new_target);
CodePatcher::PatchStaticCallAt(caller_frame->pc(), new_target);
if (FLAG_trace_patching) {
OS::Print("PatchStaticCall: patching from 0x%x to '%s' 0x%x\n",
caller_frame->pc(),
target_function.ToFullyQualifiedCString(),
new_target);
}
}
// Resolves and compiles the target function of an instance call, updates
// function cache of the receiver's class and returns the compiled code or null.
// Only the number of named arguments is checked, but not the actual names.
static RawCode* ResolveCompileInstanceCallTarget(const Instance& receiver) {
DartFrameIterator iterator;
DartFrame* caller_frame = iterator.NextFrame();
ASSERT(caller_frame != NULL);
int num_arguments = -1;
int num_named_arguments = -1;
uword target = 0;
String& function_name = String::Handle();
CodePatcher::GetInstanceCallAt(caller_frame->pc(),
&function_name,
&num_arguments,
&num_named_arguments,
&target);
ASSERT(function_name.IsSymbol());
Class& receiver_class = Class::Handle();
if (receiver.IsNull()) {
// TODO(srdjan): Clarify behavior of null objects.
receiver_class = Isolate::Current()->object_store()->object_class();
} else {
receiver_class = receiver.clazz();
}
FunctionsCache functions_cache(receiver_class);
Code& code = Code::Handle();
code = functions_cache.LookupCode(function_name,
num_arguments,
num_named_arguments);
if (!code.IsNull()) {
// Function's code found in the cache.
return code.raw();
}
Function& function = Function::Handle();
function = Resolver::ResolveDynamic(receiver,
function_name,
num_arguments,
num_named_arguments);
if (function.IsNull()) {
return Code::null();
} else {
if (!function.HasCode()) {
Compiler::CompileFunction(function);
}
functions_cache.AddCompiledFunction(function,
num_arguments,
num_named_arguments);
return function.code();
}
}
// Result of an invoke may be an unhandled exception, in which case we
// rethrow it.
static void CheckResultException(const Instance& result) {
if (result.IsUnhandledException()) {
const UnhandledException& unhandled = UnhandledException::Handle(
reinterpret_cast<RawUnhandledException*>(result.raw()));
const Instance& excp = Instance::Handle(unhandled.exception());
const Instance& stack = Instance::Handle(unhandled.stacktrace());
Exceptions::ReThrow(excp, stack);
}
}
// Resolves an instance function and compiles it if necessary.
// Arg0: receiver object.
// Returns: RawCode object or NULL (method not found or not compileable).
// This is called by the megamorphic stub when instance call does not need to be
// patched.
DEFINE_RUNTIME_ENTRY(ResolveCompileInstanceFunction, 1) {
ASSERT(arguments.Count() ==
kResolveCompileInstanceFunctionRuntimeEntry.argument_count());
const Instance& receiver = Instance::CheckedHandle(arguments.At(0));
const Code& code = Code::Handle(ResolveCompileInstanceCallTarget(receiver));
arguments.SetReturn(Code::Handle(code.raw()));
}
// Resolve instance call and patch it to jump to IC stub or megamorphic stub.
// After patching the caller's instance call instruction, that call will
// be reexecuted and ran through the created IC stub. The null receivers
// have special handling, i.e., they lead to megamorphic lookup that implements
// the appropriate null behavior.
// Arg0: receiver object.
DEFINE_RUNTIME_ENTRY(ResolvePatchInstanceCall, 1) {
ASSERT(arguments.Count() ==
kResolvePatchInstanceCallRuntimeEntry.argument_count());
const Instance& receiver = Instance::CheckedHandle(arguments.At(0));
const Code& code = Code::Handle(ResolveCompileInstanceCallTarget(receiver));
DartFrameIterator iterator;
DartFrame* caller_frame = iterator.NextFrame();
String& function_name = String::Handle();
if ((!receiver.IsNull() && code.IsNull()) || !FLAG_inline_cache) {
// Let megamorphic lookup handle noSuchMethod.
CodePatcher::PatchInstanceCallAt(
caller_frame->pc(), StubCode::MegamorphicLookupEntryPoint());
if (FLAG_trace_ic) {
OS::Print("IC: cannot find function at 0x%x -> megamorphic lookup.\n",
caller_frame->pc());
}
if (FLAG_trace_patching) {
OS::Print("ResolvePatchInstanceCall: patching 0x%x to megamorphic\n",
caller_frame->pc());
}
} else {
int num_arguments = -1;
int num_named_arguments = -1;
uword caller_target = 0;
CodePatcher::GetInstanceCallAt(caller_frame->pc(),
&function_name,
&num_arguments,
&num_named_arguments,
&caller_target);
// If caller_target is not in CallInstanceFunction stub (resolve call)
// then it must be pointing to an IC stub.
const Class& receiver_class = Class::ZoneHandle(receiver.clazz());
const bool ic_miss =
!StubCode::InCallInstanceFunctionStubCode(caller_target);
GrowableArray<const Class*> classes;
GrowableArray<const Function*> targets;
if (ic_miss) {
bool is_ic =
ICStubs::RecognizeICStub(caller_target, &classes, &targets);
ASSERT(is_ic);
ASSERT(classes.length() == targets.length());
// The returned classes array can be empty if the first patch occured
// with a null class. 'receiver_class' should not exists.
ASSERT(ICStubs::IndexOfClass(classes, receiver_class) < 0);
ASSERT(!code.IsNull());
ASSERT(!receiver_class.IsNullClass());
const Function& function = Function::ZoneHandle(code.function());
targets.Add(&function);
classes.Add(&receiver_class);
} else {
// First patch of instance call.
// Do not add classes for null receiver. For first IC patch it means that
// the IC will always miss and jump to megamorphic lookup (null handling).
if (!receiver_class.IsNullClass()) {
ASSERT(!code.IsNull());
const Function& function = Function::ZoneHandle(code.function());
targets.Add(&function);
classes.Add(&receiver_class);
}
}
const Code& ic_code = Code::Handle(ICStubs::GetICStub(classes, targets));
if (FLAG_trace_ic) {
CodeIndexTable* ci_table = Isolate::Current()->code_index_table();
ASSERT(ci_table != NULL);
const Function& caller =
Function::Handle(ci_table->LookupFunction(caller_frame->pc()));
const char* patch_kind = ic_miss ? "miss" : "patch";
OS::Print("IC %s at 0x%x '%s' (receiver:'%s' function:'%s')",
patch_kind,
caller_frame->pc(),
String::Handle(caller.name()).ToCString(),
receiver.ToCString(),
function_name.ToCString());
OS::Print(" patched to 0x%x\n", ic_code.EntryPoint());
if (ic_miss) {
for (int i = 0; i < classes.length(); i++) {
OS::Print(" IC Miss on %s\n", classes[i]->ToCString());
}
}
}
CodePatcher::PatchInstanceCallAt(caller_frame->pc(), ic_code.EntryPoint());
if (FLAG_trace_patching) {
OS::Print("ResolvePatchInstanceCall: patching 0x%x to ic 0x%x\n",
caller_frame->pc(), ic_code.EntryPoint());
}
}
}
static RawFunction* LookupDynamicFunction(Class* cls, const String& name) {
Function& function = Function::Handle();
while (!cls->IsNull()) {
// Check if function exists.
function = cls->LookupDynamicFunction(name);
if (!function.IsNull()) {
break;
}
*cls = cls->SuperClass();
}
return function.raw();
}
// Resolve an implicit closure by checking if an instance function
// of the same name exists and creating a closure object of the function.
// Arg0: receiver object.
// Arg1: original function name.
// Returns: Closure object or NULL (instance function not found).
// This is called by the megamorphic stub when it is unable to resolve an
// instance method. This is done just before the call to noSuchMethod.
DEFINE_RUNTIME_ENTRY(ResolveImplicitClosureFunction, 2) {
ASSERT(arguments.Count() ==
kResolveImplicitClosureFunctionRuntimeEntry.argument_count());
const Instance& receiver = Instance::CheckedHandle(arguments.At(0));
const String& original_func_name = String::CheckedHandle(arguments.At(1));
if (receiver.IsNull()) {
// No implicit closure functions on null.
GrowableArray<const Object*> args;
Exceptions::ThrowByType(Exceptions::kNullPointer, args);
}
const String& getter_prefix = String::Handle(String::New("get:"));
Closure& closure = Closure::Handle();
if (!original_func_name.StartsWith(getter_prefix)) {
// This is not a getter so can't be the case where we are trying to
// create an implicit closure of an instance function.
arguments.SetReturn(closure);
return;
}
Class& receiver_class = Class::Handle();
receiver_class ^= receiver.clazz();
ASSERT(!receiver_class.IsNull());
String& func_name = String::Handle();
func_name = String::SubString(original_func_name, getter_prefix.Length());
func_name = String::NewSymbol(func_name);
const Function& function =
Function::Handle(LookupDynamicFunction(&receiver_class, func_name));
if (function.IsNull()) {
// There is no function of the same name so can't be the case where
// we are trying to create an implicit closure of an instance function.
arguments.SetReturn(closure);
return;
}
Function& implicit_closure_function =
Function::Handle(function.ImplicitClosureFunction());
// Create a closure object for the implicit closure function.
const Context& context = Context::Handle(Context::New(1));
context.SetAt(0, receiver);
closure = Closure::New(implicit_closure_function, context);
arguments.SetReturn(closure);
}
// Resolve an implicit closure by invoking getter and checking if the return
// value from getter is a closure.
// Arg0: receiver object.
// Arg1: original function name.
// Returns: Closure object or NULL (closure not found).
// This is called by the megamorphic stub when it is unable to resolve an
// instance method. This is done just before the call to noSuchMethod.
DEFINE_RUNTIME_ENTRY(ResolveImplicitClosureThroughGetter, 2) {
ASSERT(arguments.Count() ==
kResolveImplicitClosureThroughGetterRuntimeEntry.argument_count());
const Instance& receiver = Instance::CheckedHandle(arguments.At(0));
const String& original_function_name = String::CheckedHandle(arguments.At(1));
const int kNumArguments = 1;
const int kNumNamedArguments = 0;
const String& getter_function_name =
String::Handle(Field::GetterName(original_function_name));
Function& function = Function::ZoneHandle(
Resolver::ResolveDynamic(receiver,
getter_function_name,
kNumArguments,
kNumNamedArguments));
Code& code = Code::Handle();
if (function.IsNull()) {
arguments.SetReturn(code);
return; // No getter function found so can't be an implicit closure.
}
GrowableArray<const Object*> invoke_arguments(0);
const Instance& result =
Instance::Handle(
DartEntry::InvokeDynamic(receiver, function, invoke_arguments));
if (result.IsUnhandledException()) {
arguments.SetReturn(code);
return; // Error accessing getter, treat as no such method.
}
if (!result.IsSmi()) {
const Class& cls = Class::Handle(result.clazz());
ASSERT(!cls.IsNull());
function = cls.signature_function();
if (!function.IsNull()) {
arguments.SetReturn(result);
return; // Return closure object.
}
}
Exceptions::ThrowByType(Exceptions::kObjectNotClosure, invoke_arguments);
}
// Invoke Implicit Closure function.
// Arg0: closure object.
// Arg1: arguments descriptor (originally passed as dart instance invocation).
// Arg2: arguments array (originally passed to dart instance invocation).
DEFINE_RUNTIME_ENTRY(InvokeImplicitClosureFunction, 3) {
ASSERT(arguments.Count() ==
kInvokeImplicitClosureFunctionRuntimeEntry.argument_count());
const Closure& closure = Closure::CheckedHandle(arguments.At(0));
const Array& arg_descriptor = Array::CheckedHandle(arguments.At(1));
const Array& func_arguments = Array::CheckedHandle(arguments.At(2));
const Function& function = Function::Handle(closure.function());
ASSERT(!function.IsNull());
if (!function.HasCode()) {
Compiler::CompileFunction(function);
}
const Context& context = Context::Handle(closure.context());
const Code& code = Code::Handle(function.code());
ASSERT(!code.IsNull());
const Instructions& instrs = Instructions::Handle(code.instructions());
ASSERT(!instrs.IsNull());
// Adjust arguments descriptor array to account for removal of the receiver
// parameter. Since the arguments descriptor array is canonicalized, create a
// new one instead of patching the original one.
const intptr_t len = arg_descriptor.Length();
const intptr_t num_named_args = (len - 3) / 2;
const Array& adjusted_arg_descriptor = Array::Handle(Array::New(len));
Smi& smi = Smi::Handle();
smi ^= arg_descriptor.At(0); // Get argument length.
smi = Smi::New(smi.Value() - 1); // Adjust argument length.
ASSERT(smi.Value() == func_arguments.Length());
adjusted_arg_descriptor.SetAt(0, smi);
smi ^= arg_descriptor.At(1); // Get number of positional parameters.
smi = Smi::New(smi.Value() - 1); // Adjust number of positional params.
adjusted_arg_descriptor.SetAt(1, smi);
// Adjust name/position pairs for each named argument.
String& named_arg_name = String::Handle();
Smi& named_arg_pos = Smi::Handle();
for (intptr_t i = 0; i < num_named_args; i++) {
const int index = 2 + (2 * i);
named_arg_name ^= arg_descriptor.At(index);
ASSERT(named_arg_name.IsSymbol());
adjusted_arg_descriptor.SetAt(index, named_arg_name);
named_arg_pos ^= arg_descriptor.At(index + 1);
named_arg_pos = Smi::New(named_arg_pos.Value() - 1);
adjusted_arg_descriptor.SetAt(index + 1, named_arg_pos);
}
adjusted_arg_descriptor.SetAt(len - 1, Object::Handle(Object::null()));
// It is too late to share the descriptor by canonicalizing it. However, it is
// important that the argument names are canonicalized (i.e. are symbols).
// Receiver parameter has already been skipped by caller.
GrowableArray<const Object*> invoke_arguments(0);
for (intptr_t i = 0; i < func_arguments.Length(); i++) {
const Object& value = Object::Handle(func_arguments.At(i));
invoke_arguments.Add(&value);
}
// Now Call the invoke stub which will invoke the closure.
DartEntry::invokestub entrypoint = reinterpret_cast<DartEntry::invokestub>(
StubCode::InvokeDartCodeEntryPoint());
ASSERT(context.isolate() == Isolate::Current());
const Instance& result = Instance::Handle(
entrypoint(instrs.EntryPoint(),
adjusted_arg_descriptor,
invoke_arguments.data(),
context));
CheckResultException(result);
arguments.SetReturn(result);
}
// Invoke appropriate noSuchMethod function.
// Arg0: receiver.
// Arg1: original function name.
// Arg2: original arguments descriptor array.
// Arg3: original arguments array.
DEFINE_RUNTIME_ENTRY(InvokeNoSuchMethodFunction, 4) {
ASSERT(arguments.Count() ==
kInvokeNoSuchMethodFunctionRuntimeEntry.argument_count());
const Instance& receiver = Instance::CheckedHandle(arguments.At(0));
const String& original_function_name = String::CheckedHandle(arguments.At(1));
ASSERT(!Array::CheckedHandle(arguments.At(2)).IsNull());
const Array& orig_arguments = Array::CheckedHandle(arguments.At(3));
// TODO(regis): The signature of the "noSuchMethod" method has to change from
// noSuchMethod(String name, Array arguments) to something like
// noSuchMethod(InvocationMirror call).
const int kNumArguments = 3;
const int kNumNamedArguments = 0;
const String& function_name =
String::Handle(String::NewSymbol("noSuchMethod"));
const Function& function = Function::ZoneHandle(
Resolver::ResolveDynamic(receiver,
function_name,
kNumArguments,
kNumNamedArguments));
ASSERT(!function.IsNull());
GrowableArray<const Object*> invoke_arguments(2);
invoke_arguments.Add(&original_function_name);
invoke_arguments.Add(&orig_arguments);
const Instance& result =
Instance::Handle(
DartEntry::InvokeDynamic(receiver, function, invoke_arguments));
CheckResultException(result);
arguments.SetReturn(result);
}
// Report that an object is not a closure.
// Arg0: non-closure object.
// Arg1: arguments array.
DEFINE_RUNTIME_ENTRY(ReportObjectNotClosure, 2) {
ASSERT(arguments.Count() ==
kReportObjectNotClosureRuntimeEntry.argument_count());
const Instance& bad_closure = Instance::CheckedHandle(arguments.At(0));
// const Array& arguments = Array::CheckedHandle(arguments.At(1));
OS::PrintErr("object '%s' is not a closure\n", bad_closure.ToCString());
GrowableArray<const Object*> args;
Exceptions::ThrowByType(Exceptions::kObjectNotClosure, args);
}
DEFINE_RUNTIME_ENTRY(ClosureArgumentMismatch, 0) {
ASSERT(arguments.Count() ==
kClosureArgumentMismatchRuntimeEntry.argument_count());
GrowableArray<const Object*> args;
Exceptions::ThrowByType(Exceptions::kClosureArgumentMismatch, args);
}
DEFINE_RUNTIME_ENTRY(StackOverflow, 0) {
ASSERT(arguments.Count() ==
kStackOverflowRuntimeEntry.argument_count());
uword old_stack_limit = Isolate::Current()->stack_limit();
Isolate::Current()->AdjustStackLimitForException();
// Recursive stack overflow check.
ASSERT(old_stack_limit != Isolate::Current()->stack_limit());
GrowableArray<const Object*> args;
Exceptions::ThrowByType(Exceptions::kStackOverflow, args);
Isolate::Current()->ResetStackLimitAfterException();
}
static void DisableOldCode(const Function& function,
const Code& old_code,
const Code& new_code) {
const Array& class_ic_stubs = Array::Handle(old_code.class_ic_stubs());
if (function.IsClosureFunction()) {
// Nothing to do, code may not have inline caches.
ASSERT(class_ic_stubs.Length() == 0);
return;
}
if (function.is_static() || function.IsConstructor()) {
ASSERT(class_ic_stubs.Length() == 0);
return;
}
Code& ic_stub = Code::Handle();
for (int i = 0; i < class_ic_stubs.Length(); i += 2) {
// i: array of classes, i + 1: ic stub code.
ic_stub ^= class_ic_stubs.At(i + 1);
ICStubs::PatchTargets(ic_stub.EntryPoint(),
old_code.EntryPoint(),
new_code.EntryPoint());
}
new_code.set_class_ic_stubs(class_ic_stubs);
old_code.set_class_ic_stubs(Array::Handle(Array::Empty()));
}
// Only unoptimized code has invocation counter threshold checking.
// Once the invocation counter threshold is reached any entry into the
// unoptimized code is redirected to this function.
DEFINE_RUNTIME_ENTRY(OptimizeInvokedFunction, 1) {
ASSERT(arguments.Count() ==
kOptimizeInvokedFunctionRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
ASSERT(function.is_optimizable());
ASSERT(!Code::Handle(function.code()).is_optimized());
const Code& unoptimized_code = Code::Handle(function.code());
// Compilation patches the entry of unoptimized code.
Compiler::CompileOptimizedFunction(function);
const Code& optimized_code = Code::Handle(function.code());
ASSERT(!optimized_code.IsNull());
ASSERT(!unoptimized_code.IsNull());
DisableOldCode(function, unoptimized_code, optimized_code);
}
// The caller must be a static call in a Dart frame, or an entry frame.
// Patch static call to point to 'new_entry_point'.
DEFINE_RUNTIME_ENTRY(FixCallersTarget, 1) {
ASSERT(arguments.Count() == kFixCallersTargetRuntimeEntry.argument_count());
const Function& function = Function::CheckedHandle(arguments.At(0));
ASSERT(!function.IsNull());
ASSERT(function.HasCode());
StackFrameIterator iterator(StackFrameIterator::kDontValidateFrames);
StackFrame* frame = iterator.NextFrame();
while (frame != NULL && !frame->IsDartFrame() && !frame->IsEntryFrame()) {
frame = iterator.NextFrame();
}
ASSERT(frame != NULL);
if (frame->IsDartFrame()) {
uword target = 0;
Function& target_function = Function::Handle();
CodePatcher::GetStaticCallAt(frame->pc(), &target_function, &target);
const uword new_entry_point = Code::Handle(function.code()).EntryPoint();
ASSERT(target != new_entry_point); // Why patch otherwise.
ASSERT(target_function.HasCode());
CodePatcher::PatchStaticCallAt(frame->pc(), new_entry_point);
if (FLAG_trace_patching) {
OS::Print("FixCallersTarget: patching from 0x%x to '%s' 0x%x\n",
frame->pc(),
target_function.ToFullyQualifiedCString(),
new_entry_point);
}
}
}
// The top Dart frame belongs to the optimized method that needs to be
// deoptimized. The pc of the Dart frame points to the deoptimization point.
// Find the node id of the deoptimization point and find the continuation
// pc in the unoptimized code.
// Since both unoptimized and optimized code have the same layout, we need only
// to patch the pc of the Dart frame and to disable/enable appropriate code.
DEFINE_RUNTIME_ENTRY(Deoptimize, 0) {
ASSERT(arguments.Count() == kDeoptimizeRuntimeEntry.argument_count());
DartFrameIterator iterator;
DartFrame* caller_frame = iterator.NextFrame();
ASSERT(caller_frame != NULL);
CodeIndexTable* ci_table = Isolate::Current()->code_index_table();
const Code& optimized_code =
Code::Handle(ci_table->LookupCode(caller_frame->pc()));
const Function& function = Function::Handle(optimized_code.function());
ASSERT(!function.IsNull());
const Code& unoptimized_code = Code::Handle(function.unoptimized_code());
ASSERT(!optimized_code.IsNull() && optimized_code.is_optimized());
ASSERT(!unoptimized_code.IsNull() && !unoptimized_code.is_optimized());
const PcDescriptors& descriptors =
PcDescriptors::Handle(optimized_code.pc_descriptors());
ASSERT(!descriptors.IsNull());
// Locate node id at deoptimization point inside optimized code.
intptr_t deopt_node_id = AstNode::kInvalidId;
for (int i = 0; i < descriptors.Length(); i++) {
if (static_cast<uword>(descriptors.PC(i)) == caller_frame->pc()) {
deopt_node_id = descriptors.NodeId(i);
break;
}
}
ASSERT(deopt_node_id != AstNode::kInvalidId);
uword continue_at_pc =
unoptimized_code.GetDeoptPcAtNodeId(deopt_node_id);
ASSERT(continue_at_pc != 0);
if (FLAG_trace_deopt) {
OS::Print("Deoptimizing at pc 0x%x id %d '%s' -> continue at 0x%x \n",
caller_frame->pc(), deopt_node_id, function.ToFullyQualifiedCString(),
continue_at_pc);
}
caller_frame->set_pc(continue_at_pc);
// Clear invocation counter so that the function gets optimized after
// types/classes have been collected.
function.set_invocation_counter(0);
function.set_deoptimization_counter(function.deoptimization_counter() + 1);
// Get unoptimized code. Compilation restores (reenables) the entry of
// unoptimized code.
Compiler::CompileFunction(function);
DisableOldCode(function, optimized_code, unoptimized_code);
if (FLAG_trace_deopt) {
OS::Print("After patching ->0x%x:\n", continue_at_pc);
}
}
// We are entering function name for a valid argument count.
void FunctionsCache::EnterFunctionAt(int i,
const Array& cache,
const Function& function,
int num_arguments,
int num_named_arguments) {
ASSERT((i % kNumEntries) == 0);
ASSERT(function.AreValidArgumentCounts(num_arguments, num_named_arguments));
cache.SetAt(i + FunctionsCache::kFunctionName,
String::Handle(function.name()));
cache.SetAt(i + FunctionsCache::kArgCount,
Smi::Handle(Smi::New(num_arguments)));
cache.SetAt(i + FunctionsCache::kNamedArgCount,
Smi::Handle(Smi::New(num_named_arguments)));
cache.SetAt(i + FunctionsCache::kFunction, function);
}
void FunctionsCache::AddCompiledFunction(const Function& function,
int num_arguments,
int num_named_arguments) {
ASSERT(function.HasCode());
Array& cache = Array::Handle(class_.functions_cache());
// Search for first free slot. Last entry is always NULL object.
for (intptr_t i = 0; i < (cache.Length() - kNumEntries); i += kNumEntries) {
if (Object::Handle(cache.At(i)).IsNull()) {
EnterFunctionAt(i,
cache,
function,
num_arguments,
num_named_arguments);
return;
}
}
intptr_t ix = cache.Length() - kNumEntries;
// Grow by 8 entries.
cache = Array::Grow(cache, cache.Length() + (8 * kNumEntries));
class_.set_functions_cache(cache);
EnterFunctionAt(ix,
cache,
function,
num_arguments,
num_named_arguments);
}
// TODO(regis): The actual names of named arguments must match as well.
RawCode* FunctionsCache::LookupCode(const String& function_name,
int num_arguments,
int num_named_arguments) {
const Array& cache = Array::Handle(class_.functions_cache());
String& test_name = String::Handle();
for (intptr_t i = 0; i < cache.Length(); i += kNumEntries) {
test_name ^= cache.At(i + FunctionsCache::kFunctionName);
if (test_name.IsNull()) {
// Found NULL, no more entries to check, abort lookup.
return Code::null();
}
if (function_name.Equals(test_name)) {
Smi& smi = Smi::Handle();
smi ^= cache.At(i + FunctionsCache::kArgCount);
if (num_arguments == smi.Value()) {
smi ^= cache.At(i + FunctionsCache::kNamedArgCount);
if (num_named_arguments == smi.Value()) {
Function& result = Function::Handle();
result ^= cache.At(i + FunctionsCache::kFunction);
ASSERT(!result.IsNull());
ASSERT(result.HasCode());
return result.code();
}
}
}
}
// The cache is null terminated, therefore the loop above should never
// terminate by itself.
UNREACHABLE();
return Code::null();
}
} // namespace dart