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

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

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

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

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

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

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

Changes in the Flutter Gallery:

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

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

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

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

Other changes:

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

* Adds new slots for various Function fields.

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

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

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

3980 lines
140 KiB
C++

// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include <setjmp.h> // NOLINT
#include <stdlib.h>
#include "vm/globals.h"
#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/interpreter.h"
#include "vm/compiler/api/type_check_mode.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/assembler/disassembler_kbc.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/ffi/abi.h"
#include "vm/compiler/frontend/bytecode_reader.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/cpu.h"
#include "vm/dart_entry.h"
#include "vm/debugger.h"
#include "vm/lockers.h"
#include "vm/native_arguments.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/os_thread.h"
#include "vm/stack_frame_kbc.h"
#include "vm/symbols.h"
namespace dart {
DEFINE_FLAG(uint64_t,
trace_interpreter_after,
ULLONG_MAX,
"Trace interpreter execution after instruction count reached.");
DEFINE_FLAG(charp,
interpreter_trace_file,
NULL,
"File to write a dynamic instruction trace to.");
DEFINE_FLAG(uint64_t,
interpreter_trace_file_max_bytes,
100 * MB,
"Maximum size in bytes of the interpreter trace file");
// InterpreterSetjmpBuffer are linked together, and the last created one
// is referenced by the Interpreter. When an exception is thrown, the exception
// runtime looks at where to jump and finds the corresponding
// InterpreterSetjmpBuffer based on the stack pointer of the exception handler.
// The runtime then does a Longjmp on that buffer to return to the interpreter.
class InterpreterSetjmpBuffer {
public:
void Longjmp() {
// "This" is now the last setjmp buffer.
interpreter_->set_last_setjmp_buffer(this);
longjmp(buffer_, 1);
}
explicit InterpreterSetjmpBuffer(Interpreter* interpreter) {
interpreter_ = interpreter;
link_ = interpreter->last_setjmp_buffer();
interpreter->set_last_setjmp_buffer(this);
fp_ = interpreter->fp_;
}
~InterpreterSetjmpBuffer() {
ASSERT(interpreter_->last_setjmp_buffer() == this);
interpreter_->set_last_setjmp_buffer(link_);
}
InterpreterSetjmpBuffer* link() const { return link_; }
uword fp() const { return reinterpret_cast<uword>(fp_); }
jmp_buf buffer_;
private:
ObjectPtr* fp_;
Interpreter* interpreter_;
InterpreterSetjmpBuffer* link_;
friend class Interpreter;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(InterpreterSetjmpBuffer);
};
DART_FORCE_INLINE static ObjectPtr* SavedCallerFP(ObjectPtr* FP) {
return reinterpret_cast<ObjectPtr*>(
static_cast<uword>(FP[kKBCSavedCallerFpSlotFromFp]));
}
DART_FORCE_INLINE static ObjectPtr* FrameArguments(ObjectPtr* FP,
intptr_t argc) {
return FP - (kKBCDartFrameFixedSize + argc);
}
#define RAW_CAST(Type, val) (InterpreterHelpers::CastTo##Type(val))
class InterpreterHelpers {
public:
#define DEFINE_CASTS(Type) \
DART_FORCE_INLINE static Type##Ptr CastTo##Type(ObjectPtr obj) { \
ASSERT((k##Type##Cid == kSmiCid) \
? !obj->IsHeapObject() \
: (k##Type##Cid == kIntegerCid) \
? (!obj->IsHeapObject() || obj->IsMint()) \
: obj->Is##Type()); \
return static_cast<Type##Ptr>(obj); \
}
CLASS_LIST(DEFINE_CASTS)
#undef DEFINE_CASTS
DART_FORCE_INLINE static SmiPtr GetClassIdAsSmi(ObjectPtr obj) {
return Smi::New(obj->IsHeapObject() ? obj->GetClassId()
: static_cast<intptr_t>(kSmiCid));
}
DART_FORCE_INLINE static intptr_t GetClassId(ObjectPtr obj) {
return obj->IsHeapObject() ? obj->GetClassId()
: static_cast<intptr_t>(kSmiCid);
}
DART_FORCE_INLINE static TypeArgumentsPtr GetTypeArguments(
Thread* thread,
InstancePtr instance) {
ClassPtr instance_class =
thread->isolate()->class_table()->At(GetClassId(instance));
return instance_class->ptr()->num_type_arguments_ > 0
? reinterpret_cast<TypeArgumentsPtr*>(instance->ptr())
[instance_class->ptr()
->host_type_arguments_field_offset_in_words_]
: TypeArguments::null();
}
// The usage counter is actually a 'hotness' counter.
// For an instance call, both the usage counters of the caller and of the
// calle will get incremented, as well as the ICdata counter at the call site.
DART_FORCE_INLINE static void IncrementUsageCounter(FunctionPtr f) {
f->ptr()->usage_counter_++;
}
DART_FORCE_INLINE static void IncrementICUsageCount(ObjectPtr* entries,
intptr_t offset,
intptr_t args_tested) {
const intptr_t count_offset = ICData::CountIndexFor(args_tested);
const intptr_t raw_smi_old =
static_cast<intptr_t>(entries[offset + count_offset]);
const intptr_t raw_smi_new = raw_smi_old + Smi::RawValue(1);
*reinterpret_cast<intptr_t*>(&entries[offset + count_offset]) = raw_smi_new;
}
DART_FORCE_INLINE static bool CheckIndex(SmiPtr index, SmiPtr length) {
return !index->IsHeapObject() && (static_cast<intptr_t>(index) >= 0) &&
(static_cast<intptr_t>(index) < static_cast<intptr_t>(length));
}
DART_FORCE_INLINE static intptr_t ArgDescTypeArgsLen(ArrayPtr argdesc) {
return Smi::Value(*reinterpret_cast<SmiPtr*>(
reinterpret_cast<uword>(argdesc->ptr()) +
Array::element_offset(ArgumentsDescriptor::kTypeArgsLenIndex)));
}
DART_FORCE_INLINE static intptr_t ArgDescArgCount(ArrayPtr argdesc) {
return Smi::Value(*reinterpret_cast<SmiPtr*>(
reinterpret_cast<uword>(argdesc->ptr()) +
Array::element_offset(ArgumentsDescriptor::kCountIndex)));
}
DART_FORCE_INLINE static intptr_t ArgDescArgSize(ArrayPtr argdesc) {
return Smi::Value(*reinterpret_cast<SmiPtr*>(
reinterpret_cast<uword>(argdesc->ptr()) +
Array::element_offset(ArgumentsDescriptor::kSizeIndex)));
}
DART_FORCE_INLINE static intptr_t ArgDescPosCount(ArrayPtr argdesc) {
return Smi::Value(*reinterpret_cast<SmiPtr*>(
reinterpret_cast<uword>(argdesc->ptr()) +
Array::element_offset(ArgumentsDescriptor::kPositionalCountIndex)));
}
DART_FORCE_INLINE static BytecodePtr FrameBytecode(ObjectPtr* FP) {
ASSERT(GetClassId(FP[kKBCPcMarkerSlotFromFp]) == kBytecodeCid);
return static_cast<BytecodePtr>(FP[kKBCPcMarkerSlotFromFp]);
}
DART_FORCE_INLINE static bool FieldNeedsGuardUpdate(FieldPtr field,
ObjectPtr value) {
// The interpreter should never see a cloned field.
ASSERT(field->ptr()->owner_->GetClassId() != kFieldCid);
const classid_t guarded_cid = field->ptr()->guarded_cid_;
if (guarded_cid == kDynamicCid) {
// Field is not guarded.
return false;
}
ASSERT(Isolate::Current()->use_field_guards());
const classid_t nullability_cid = field->ptr()->is_nullable_;
const classid_t value_cid = InterpreterHelpers::GetClassId(value);
if (nullability_cid == value_cid) {
// Storing null into a nullable field.
return false;
}
if (guarded_cid != value_cid) {
// First assignment (guarded_cid == kIllegalCid) or
// field no longer monomorphic or
// field has become nullable.
return true;
}
intptr_t guarded_list_length =
Smi::Value(field->ptr()->guarded_list_length_);
if (UNLIKELY(guarded_list_length >= Field::kUnknownFixedLength)) {
// Guarding length, check this in the runtime.
return true;
}
if (UNLIKELY(field->ptr()->static_type_exactness_state_ >=
StaticTypeExactnessState::Uninitialized().Encode())) {
// Guarding "exactness", check this in the runtime.
return true;
}
// Everything matches.
return false;
}
DART_FORCE_INLINE static bool IsAllocateFinalized(ClassPtr cls) {
return Class::ClassFinalizedBits::decode(cls->ptr()->state_bits_) ==
ClassLayout::kAllocateFinalized;
}
};
DART_FORCE_INLINE static const KBCInstr* SavedCallerPC(ObjectPtr* FP) {
return reinterpret_cast<const KBCInstr*>(
static_cast<uword>(FP[kKBCSavedCallerPcSlotFromFp]));
}
DART_FORCE_INLINE static FunctionPtr FrameFunction(ObjectPtr* FP) {
FunctionPtr function = static_cast<FunctionPtr>(FP[kKBCFunctionSlotFromFp]);
ASSERT(InterpreterHelpers::GetClassId(function) == kFunctionCid ||
InterpreterHelpers::GetClassId(function) == kNullCid);
return function;
}
DART_FORCE_INLINE static ObjectPtr InitializeHeader(uword addr,
intptr_t class_id,
intptr_t instance_size) {
uint32_t tags = 0;
tags = ObjectLayout::ClassIdTag::update(class_id, tags);
tags = ObjectLayout::SizeTag::update(instance_size, tags);
tags = ObjectLayout::OldBit::update(false, tags);
tags = ObjectLayout::OldAndNotMarkedBit::update(false, tags);
tags = ObjectLayout::OldAndNotRememberedBit::update(false, tags);
tags = ObjectLayout::NewBit::update(true, tags);
// Also writes zero in the hash_ field.
*reinterpret_cast<uword*>(addr + Object::tags_offset()) = tags;
return ObjectLayout::FromAddr(addr);
}
DART_FORCE_INLINE static bool TryAllocate(Thread* thread,
intptr_t class_id,
intptr_t instance_size,
ObjectPtr* result) {
ASSERT(instance_size > 0);
ASSERT(Utils::IsAligned(instance_size, kObjectAlignment));
#ifndef PRODUCT
auto table = thread->isolate_group()->shared_class_table();
if (UNLIKELY(table->TraceAllocationFor(class_id))) {
return false;
}
#endif
const uword top = thread->top();
const intptr_t remaining = thread->end() - top;
if (LIKELY(remaining >= instance_size)) {
thread->set_top(top + instance_size);
*result = InitializeHeader(top, class_id, instance_size);
return true;
}
return false;
}
void LookupCache::Clear() {
for (intptr_t i = 0; i < kNumEntries; i++) {
entries_[i].receiver_cid = kIllegalCid;
}
}
bool LookupCache::Lookup(intptr_t receiver_cid,
StringPtr function_name,
ArrayPtr arguments_descriptor,
FunctionPtr* target) const {
ASSERT(receiver_cid != kIllegalCid); // Sentinel value.
const intptr_t hash = receiver_cid ^ static_cast<intptr_t>(function_name) ^
static_cast<intptr_t>(arguments_descriptor);
const intptr_t probe1 = hash & kTableMask;
if (entries_[probe1].receiver_cid == receiver_cid &&
entries_[probe1].function_name == function_name &&
entries_[probe1].arguments_descriptor == arguments_descriptor) {
*target = entries_[probe1].target;
return true;
}
intptr_t probe2 = (hash >> 3) & kTableMask;
if (entries_[probe2].receiver_cid == receiver_cid &&
entries_[probe2].function_name == function_name &&
entries_[probe2].arguments_descriptor == arguments_descriptor) {
*target = entries_[probe2].target;
return true;
}
return false;
}
void LookupCache::Insert(intptr_t receiver_cid,
StringPtr function_name,
ArrayPtr arguments_descriptor,
FunctionPtr target) {
// Otherwise we have to clear the cache or rehash on scavenges too.
ASSERT(function_name->IsOldObject());
ASSERT(arguments_descriptor->IsOldObject());
ASSERT(target->IsOldObject());
const intptr_t hash = receiver_cid ^ static_cast<intptr_t>(function_name) ^
static_cast<intptr_t>(arguments_descriptor);
const intptr_t probe1 = hash & kTableMask;
if (entries_[probe1].receiver_cid == kIllegalCid) {
entries_[probe1].receiver_cid = receiver_cid;
entries_[probe1].function_name = function_name;
entries_[probe1].arguments_descriptor = arguments_descriptor;
entries_[probe1].target = target;
return;
}
const intptr_t probe2 = (hash >> 3) & kTableMask;
if (entries_[probe2].receiver_cid == kIllegalCid) {
entries_[probe2].receiver_cid = receiver_cid;
entries_[probe2].function_name = function_name;
entries_[probe2].arguments_descriptor = arguments_descriptor;
entries_[probe2].target = target;
return;
}
entries_[probe1].receiver_cid = receiver_cid;
entries_[probe1].function_name = function_name;
entries_[probe1].arguments_descriptor = arguments_descriptor;
entries_[probe1].target = target;
}
Interpreter::Interpreter()
: stack_(NULL),
fp_(NULL),
pp_(nullptr),
argdesc_(nullptr),
lookup_cache_() {
// Setup interpreter support first. Some of this information is needed to
// setup the architecture state.
// We allocate the stack here, the size is computed as the sum of
// the size specified by the user and the buffer space needed for
// handling stack overflow exceptions. To be safe in potential
// stack underflows we also add some underflow buffer space.
stack_ = new uintptr_t[(OSThread::GetSpecifiedStackSize() +
OSThread::kStackSizeBufferMax +
kInterpreterStackUnderflowSize) /
sizeof(uintptr_t)];
// Low address.
stack_base_ =
reinterpret_cast<uword>(stack_) + kInterpreterStackUnderflowSize;
// Limit for StackOverflowError.
overflow_stack_limit_ = stack_base_ + OSThread::GetSpecifiedStackSize();
// High address.
stack_limit_ = overflow_stack_limit_ + OSThread::kStackSizeBufferMax;
last_setjmp_buffer_ = NULL;
DEBUG_ONLY(icount_ = 1); // So that tracing after 0 traces first bytecode.
#if defined(DEBUG)
trace_file_bytes_written_ = 0;
trace_file_ = NULL;
if (FLAG_interpreter_trace_file != NULL) {
Dart_FileOpenCallback file_open = Dart::file_open_callback();
if (file_open != NULL) {
trace_file_ = file_open(FLAG_interpreter_trace_file, /* write */ true);
trace_buffer_ = new KBCInstr[kTraceBufferInstrs];
trace_buffer_idx_ = 0;
}
}
#endif
// Make sure interpreter's unboxing view is consistent with compiler.
supports_unboxed_doubles_ = FlowGraphCompiler::SupportsUnboxedDoubles();
supports_unboxed_simd128_ = FlowGraphCompiler::SupportsUnboxedSimd128();
}
Interpreter::~Interpreter() {
delete[] stack_;
pp_ = NULL;
argdesc_ = NULL;
#if defined(DEBUG)
if (trace_file_ != NULL) {
FlushTraceBuffer();
// Close the file.
Dart_FileCloseCallback file_close = Dart::file_close_callback();
if (file_close != NULL) {
file_close(trace_file_);
trace_file_ = NULL;
delete[] trace_buffer_;
trace_buffer_ = NULL;
}
}
#endif
}
// Get the active Interpreter for the current isolate.
Interpreter* Interpreter::Current() {
Thread* thread = Thread::Current();
Interpreter* interpreter = thread->interpreter();
if (interpreter == nullptr) {
NoSafepointScope no_safepoint;
interpreter = new Interpreter();
thread->set_interpreter(interpreter);
}
return interpreter;
}
#if defined(DEBUG)
// Returns true if tracing of executed instructions is enabled.
// May be called on entry, when icount_ has not been incremented yet.
DART_FORCE_INLINE bool Interpreter::IsTracingExecution() const {
return icount_ > FLAG_trace_interpreter_after;
}
// Prints bytecode instruction at given pc for instruction tracing.
DART_NOINLINE void Interpreter::TraceInstruction(const KBCInstr* pc) const {
THR_Print("%" Pu64 " ", icount_);
if (FLAG_support_disassembler) {
KernelBytecodeDisassembler::Disassemble(
reinterpret_cast<uword>(pc),
reinterpret_cast<uword>(KernelBytecode::Next(pc)));
} else {
THR_Print("Disassembler not supported in this mode.\n");
}
}
DART_FORCE_INLINE bool Interpreter::IsWritingTraceFile() const {
return (trace_file_ != NULL) &&
(trace_file_bytes_written_ < FLAG_interpreter_trace_file_max_bytes);
}
void Interpreter::FlushTraceBuffer() {
Dart_FileWriteCallback file_write = Dart::file_write_callback();
if (file_write == NULL) {
return;
}
if (trace_file_bytes_written_ >= FLAG_interpreter_trace_file_max_bytes) {
return;
}
const intptr_t bytes_to_write = Utils::Minimum(
static_cast<uint64_t>(trace_buffer_idx_ * sizeof(KBCInstr)),
FLAG_interpreter_trace_file_max_bytes - trace_file_bytes_written_);
if (bytes_to_write == 0) {
return;
}
file_write(trace_buffer_, bytes_to_write, trace_file_);
trace_file_bytes_written_ += bytes_to_write;
trace_buffer_idx_ = 0;
}
DART_NOINLINE void Interpreter::WriteInstructionToTrace(const KBCInstr* pc) {
Dart_FileWriteCallback file_write = Dart::file_write_callback();
if (file_write == NULL) {
return;
}
const KBCInstr* next = KernelBytecode::Next(pc);
while ((trace_buffer_idx_ < kTraceBufferInstrs) && (pc != next)) {
trace_buffer_[trace_buffer_idx_++] = *pc;
++pc;
}
if (trace_buffer_idx_ == kTraceBufferInstrs) {
FlushTraceBuffer();
}
}
#endif // defined(DEBUG)
// Calls into the Dart runtime are based on this interface.
typedef void (*InterpreterRuntimeCall)(NativeArguments arguments);
// Calls to leaf Dart runtime functions are based on this interface.
typedef intptr_t (*InterpreterLeafRuntimeCall)(intptr_t r0,
intptr_t r1,
intptr_t r2,
intptr_t r3);
// Calls to leaf float Dart runtime functions are based on this interface.
typedef double (*InterpreterLeafFloatRuntimeCall)(double d0, double d1);
void Interpreter::Exit(Thread* thread,
ObjectPtr* base,
ObjectPtr* frame,
const KBCInstr* pc) {
frame[0] = Function::null();
frame[1] = Bytecode::null();
frame[2] = static_cast<ObjectPtr>(reinterpret_cast<uword>(pc));
frame[3] = static_cast<ObjectPtr>(reinterpret_cast<uword>(base));
ObjectPtr* exit_fp = frame + kKBCDartFrameFixedSize;
thread->set_top_exit_frame_info(reinterpret_cast<uword>(exit_fp));
fp_ = exit_fp;
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("Exiting interpreter 0x%" Px " at fp_ 0x%" Px "\n",
reinterpret_cast<uword>(this), reinterpret_cast<uword>(exit_fp));
}
#endif
}
void Interpreter::Unexit(Thread* thread) {
#if !defined(PRODUCT)
// For the profiler.
ObjectPtr* exit_fp =
reinterpret_cast<ObjectPtr*>(thread->top_exit_frame_info());
ASSERT(exit_fp != 0);
pc_ = SavedCallerPC(exit_fp);
fp_ = SavedCallerFP(exit_fp);
#endif
thread->set_top_exit_frame_info(0);
}
// Calling into runtime may trigger garbage collection and relocate objects,
// so all ObjectPtr pointers become outdated and should not be used across
// runtime calls.
// Note: functions below are marked DART_NOINLINE to recover performance where
// inlining these functions into the interpreter loop seemed to cause some code
// quality issues. Functions with the "returns_twice" attribute, such as setjmp,
// prevent reusing spill slots and large frame sizes.
static DART_NOINLINE bool InvokeRuntime(Thread* thread,
Interpreter* interpreter,
RuntimeFunction drt,
const NativeArguments& args) {
InterpreterSetjmpBuffer buffer(interpreter);
if (!setjmp(buffer.buffer_)) {
thread->set_vm_tag(reinterpret_cast<uword>(drt));
drt(args);
thread->set_vm_tag(VMTag::kDartInterpretedTagId);
interpreter->Unexit(thread);
return true;
} else {
return false;
}
}
static DART_NOINLINE bool InvokeNative(Thread* thread,
Interpreter* interpreter,
NativeFunctionWrapper wrapper,
Dart_NativeFunction function,
Dart_NativeArguments args) {
InterpreterSetjmpBuffer buffer(interpreter);
if (!setjmp(buffer.buffer_)) {
thread->set_vm_tag(reinterpret_cast<uword>(function));
wrapper(args, function);
thread->set_vm_tag(VMTag::kDartInterpretedTagId);
interpreter->Unexit(thread);
return true;
} else {
return false;
}
}
extern "C" {
// Note: The invocation stub follows the C ABI, so we cannot pass C++ struct
// values like ObjectPtr. In some calling conventions (IA32), ObjectPtr is
// passed/returned different from a pointer.
typedef uword /*ObjectPtr*/ (*invokestub)(uword /*CodePtr*/ code,
uword /*ArrayPtr*/ argdesc,
ObjectPtr* arg0,
Thread* thread);
}
DART_NOINLINE bool Interpreter::InvokeCompiled(Thread* thread,
FunctionPtr function,
ObjectPtr* call_base,
ObjectPtr* call_top,
const KBCInstr** pc,
ObjectPtr** FP,
ObjectPtr** SP) {
ASSERT(Function::HasCode(function));
CodePtr code = function->ptr()->code_;
ASSERT(code != StubCode::LazyCompile().raw());
// TODO(regis): Once we share the same stack, try to invoke directly.
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("invoking compiled %s\n", Function::Handle(function).ToCString());
}
#endif
// On success, returns a RawInstance. On failure, a RawError.
invokestub volatile entrypoint = reinterpret_cast<invokestub>(
StubCode::InvokeDartCodeFromBytecode().EntryPoint());
ObjectPtr result;
Exit(thread, *FP, call_top + 1, *pc);
{
InterpreterSetjmpBuffer buffer(this);
if (!setjmp(buffer.buffer_)) {
#if defined(USING_SIMULATOR)
// We need to beware that bouncing between the interpreter and the
// simulator may exhaust the C stack before exhausting either the
// interpreter or simulator stacks.
if (!thread->os_thread()->HasStackHeadroom()) {
thread->SetStackLimit(-1);
}
result = bit_copy<ObjectPtr, int64_t>(Simulator::Current()->Call(
reinterpret_cast<intptr_t>(entrypoint), static_cast<intptr_t>(code),
static_cast<intptr_t>(argdesc_),
reinterpret_cast<intptr_t>(call_base),
reinterpret_cast<intptr_t>(thread)));
#else
result = static_cast<ObjectPtr>(entrypoint(static_cast<uword>(code),
static_cast<uword>(argdesc_),
call_base, thread));
#endif
ASSERT(thread->vm_tag() == VMTag::kDartInterpretedTagId);
ASSERT(thread->execution_state() == Thread::kThreadInGenerated);
Unexit(thread);
} else {
return false;
}
}
// Pop args and push result.
*SP = call_base;
**SP = result;
pp_ = InterpreterHelpers::FrameBytecode(*FP)->ptr()->object_pool_;
// If the result is an error (not a Dart instance), it must either be rethrown
// (in the case of an unhandled exception) or it must be returned to the
// caller of the interpreter to be propagated.
if (result->IsHeapObject()) {
const intptr_t result_cid = result->GetClassId();
if (result_cid == kUnhandledExceptionCid) {
(*SP)[0] = UnhandledException::RawCast(result)->ptr()->exception_;
(*SP)[1] = UnhandledException::RawCast(result)->ptr()->stacktrace_;
(*SP)[2] = 0; // Space for result.
Exit(thread, *FP, *SP + 3, *pc);
NativeArguments args(thread, 2, *SP, *SP + 2);
if (!InvokeRuntime(thread, this, DRT_ReThrow, args)) {
return false;
}
UNREACHABLE();
}
if (IsErrorClassId(result_cid)) {
// Unwind to entry frame.
fp_ = *FP;
pc_ = SavedCallerPC(fp_);
while (!IsEntryFrameMarker(pc_)) {
fp_ = SavedCallerFP(fp_);
pc_ = SavedCallerPC(fp_);
}
// Pop entry frame.
fp_ = SavedCallerFP(fp_);
special_[KernelBytecode::kExceptionSpecialIndex] = result;
return false;
}
}
return true;
}
DART_FORCE_INLINE bool Interpreter::InvokeBytecode(Thread* thread,
FunctionPtr function,
ObjectPtr* call_base,
ObjectPtr* call_top,
const KBCInstr** pc,
ObjectPtr** FP,
ObjectPtr** SP) {
ASSERT(Function::HasBytecode(function));
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("invoking %s\n",
Function::Handle(function).ToFullyQualifiedCString());
}
#endif
ObjectPtr* callee_fp = call_top + kKBCDartFrameFixedSize;
ASSERT(function == FrameFunction(callee_fp));
BytecodePtr bytecode = function->ptr()->bytecode_;
callee_fp[kKBCPcMarkerSlotFromFp] = bytecode;
callee_fp[kKBCSavedCallerPcSlotFromFp] =
static_cast<ObjectPtr>(reinterpret_cast<uword>(*pc));
callee_fp[kKBCSavedCallerFpSlotFromFp] =
static_cast<ObjectPtr>(reinterpret_cast<uword>(*FP));
pp_ = bytecode->ptr()->object_pool_;
*pc = reinterpret_cast<const KBCInstr*>(bytecode->ptr()->instructions_);
NOT_IN_PRODUCT(pc_ = *pc); // For the profiler.
*FP = callee_fp;
NOT_IN_PRODUCT(fp_ = callee_fp); // For the profiler.
*SP = *FP - 1;
return true;
}
DART_FORCE_INLINE bool Interpreter::Invoke(Thread* thread,
ObjectPtr* call_base,
ObjectPtr* call_top,
const KBCInstr** pc,
ObjectPtr** FP,
ObjectPtr** SP) {
ObjectPtr* callee_fp = call_top + kKBCDartFrameFixedSize;
FunctionPtr function = FrameFunction(callee_fp);
for (;;) {
if (Function::HasCode(function)) {
return InvokeCompiled(thread, function, call_base, call_top, pc, FP, SP);
}
if (Function::HasBytecode(function)) {
return InvokeBytecode(thread, function, call_base, call_top, pc, FP, SP);
}
// Compile the function to either generate code or load bytecode.
call_top[1] = 0; // Code result.
call_top[2] = function;
Exit(thread, *FP, call_top + 3, *pc);
NativeArguments native_args(thread, 1, call_top + 2, call_top + 1);
if (!InvokeRuntime(thread, this, DRT_CompileFunction, native_args)) {
return false;
}
// Reload objects after the call which may trigger GC.
function = Function::RawCast(call_top[2]);
ASSERT(Function::HasCode(function) || Function::HasBytecode(function));
}
}
DART_FORCE_INLINE bool Interpreter::InstanceCall(Thread* thread,
StringPtr target_name,
ObjectPtr* call_base,
ObjectPtr* top,
const KBCInstr** pc,
ObjectPtr** FP,
ObjectPtr** SP) {
ObjectPtr null_value = Object::null();
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
intptr_t receiver_cid =
InterpreterHelpers::GetClassId(call_base[receiver_idx]);
FunctionPtr target;
if (UNLIKELY(!lookup_cache_.Lookup(receiver_cid, target_name, argdesc_,
&target))) {
// Table lookup miss.
top[0] = null_value; // Clean up slot as it may be visited by GC.
top[1] = call_base[receiver_idx];
top[2] = target_name;
top[3] = argdesc_;
top[4] = null_value; // Result slot.
Exit(thread, *FP, top + 5, *pc);
NativeArguments native_args(thread, 3, /* argv */ top + 1,
/* result */ top + 4);
if (!InvokeRuntime(thread, this, DRT_InterpretedInstanceCallMissHandler,
native_args)) {
return false;
}
target = static_cast<FunctionPtr>(top[4]);
target_name = static_cast<StringPtr>(top[2]);
argdesc_ = static_cast<ArrayPtr>(top[3]);
}
if (target != Function::null()) {
lookup_cache_.Insert(receiver_cid, target_name, argdesc_, target);
top[0] = target;
return Invoke(thread, call_base, top, pc, FP, SP);
}
// The miss handler should only fail to return a function if lazy dispatchers
// are disabled, in which case we need to call DRT_InvokeNoSuchMethod, which
// walks the receiver appropriately in this case.
ASSERT(!FLAG_lazy_dispatchers);
// The receiver, name, and argument descriptor are already in the appropriate
// places on the stack from the previous call.
ASSERT(top[4] == null_value);
// Allocate array of arguments.
{
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
ASSERT_EQUAL(top - call_base, argc);
top[5] = Smi::New(argc); // length
top[6] = null_value; // type
Exit(thread, *FP, top + 7, *pc);
NativeArguments native_args(thread, 2, /* argv */ top + 5,
/* result */ top + 4);
if (!InvokeRuntime(thread, this, DRT_AllocateArray, native_args)) {
return false;
}
// Copy arguments into the newly allocated array.
ArrayPtr array = Array::RawCast(top[4]);
for (intptr_t i = 0; i < argc; i++) {
array->ptr()->data()[i] = call_base[i];
}
}
{
Exit(thread, *FP, top + 5, *pc);
NativeArguments native_args(thread, 4, /* argv */ top + 1,
/* result */ top);
if (!InvokeRuntime(thread, this, DRT_InvokeNoSuchMethod, native_args)) {
return false;
}
// Pop the call args and push the result.
ObjectPtr result = top[0];
*SP = call_base;
**SP = result;
pp_ = InterpreterHelpers::FrameBytecode(*FP)->ptr()->object_pool_;
}
return true;
}
// Note:
// All macro helpers are intended to be used only inside Interpreter::Call.
// Counts and prints executed bytecode instructions (in DEBUG mode).
#if defined(DEBUG)
#define TRACE_INSTRUCTION \
if (IsTracingExecution()) { \
TraceInstruction(pc); \
} \
if (IsWritingTraceFile()) { \
WriteInstructionToTrace(pc); \
} \
icount_++;
#else
#define TRACE_INSTRUCTION
#endif // defined(DEBUG)
// Decode opcode and A part of the given value and dispatch to the
// corresponding bytecode handler.
#ifdef DART_HAS_COMPUTED_GOTO
#define DISPATCH_OP(val) \
do { \
op = (val); \
TRACE_INSTRUCTION \
goto* dispatch[op]; \
} while (0)
#else
#define DISPATCH_OP(val) \
do { \
op = (val); \
TRACE_INSTRUCTION \
goto SwitchDispatch; \
} while (0)
#endif
// Fetch next operation from PC and dispatch.
#define DISPATCH() DISPATCH_OP(*pc)
// Load target of a jump instruction into PC.
#define LOAD_JUMP_TARGET() pc = rT
#define BYTECODE_ENTRY_LABEL(Name) bc##Name:
#define BYTECODE_WIDE_ENTRY_LABEL(Name) bc##Name##_Wide:
#define BYTECODE_IMPL_LABEL(Name) bc##Name##Impl:
#define GOTO_BYTECODE_IMPL(Name) goto bc##Name##Impl;
// Define entry point that handles bytecode Name with the given operand format.
#define BYTECODE(Name, Operands) BYTECODE_HEADER_##Operands(Name)
// Helpers to decode common instruction formats. Used in conjunction with
// BYTECODE() macro.
#define BYTECODE_HEADER_0(Name) \
BYTECODE_ENTRY_LABEL(Name) \
pc += 1;
#define BYTECODE_HEADER_A(Name) \
uint32_t rA; \
USE(rA); \
BYTECODE_ENTRY_LABEL(Name) \
rA = pc[1]; \
pc += 2;
#define BYTECODE_HEADER_D(Name) \
uint32_t rD; \
USE(rD); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rD = static_cast<uint32_t>(pc[1]) | (static_cast<uint32_t>(pc[2]) << 8) | \
(static_cast<uint32_t>(pc[3]) << 16) | \
(static_cast<uint32_t>(pc[4]) << 24); \
pc += 5; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rD = pc[1]; \
pc += 2; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_X(Name) \
int32_t rX; \
USE(rX); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rX = static_cast<int32_t>(static_cast<uint32_t>(pc[1]) | \
(static_cast<uint32_t>(pc[2]) << 8) | \
(static_cast<uint32_t>(pc[3]) << 16) | \
(static_cast<uint32_t>(pc[4]) << 24)); \
pc += 5; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rX = static_cast<int8_t>(pc[1]); \
pc += 2; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_T(Name) \
const KBCInstr* rT; \
USE(rT); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rT = pc + (static_cast<int32_t>((static_cast<uint32_t>(pc[1]) << 8) | \
(static_cast<uint32_t>(pc[2]) << 16) | \
(static_cast<uint32_t>(pc[3]) << 24)) >> \
8); \
pc += 4; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rT = pc + static_cast<int8_t>(pc[1]); \
pc += 2; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_A_E(Name) \
uint32_t rA, rE; \
USE(rA); \
USE(rE); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rA = pc[1]; \
rE = static_cast<uint32_t>(pc[2]) | (static_cast<uint32_t>(pc[3]) << 8) | \
(static_cast<uint32_t>(pc[4]) << 16) | \
(static_cast<uint32_t>(pc[5]) << 24); \
pc += 6; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rA = pc[1]; \
rE = pc[2]; \
pc += 3; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_A_Y(Name) \
uint32_t rA; \
int32_t rY; \
USE(rA); \
USE(rY); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rA = pc[1]; \
rY = static_cast<int32_t>(static_cast<uint32_t>(pc[2]) | \
(static_cast<uint32_t>(pc[3]) << 8) | \
(static_cast<uint32_t>(pc[4]) << 16) | \
(static_cast<uint32_t>(pc[5]) << 24)); \
pc += 6; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rA = pc[1]; \
rY = static_cast<int8_t>(pc[2]); \
pc += 3; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_D_F(Name) \
uint32_t rD, rF; \
USE(rD); \
USE(rF); \
BYTECODE_WIDE_ENTRY_LABEL(Name) \
rD = static_cast<uint32_t>(pc[1]) | (static_cast<uint32_t>(pc[2]) << 8) | \
(static_cast<uint32_t>(pc[3]) << 16) | \
(static_cast<uint32_t>(pc[4]) << 24); \
rF = pc[5]; \
pc += 6; \
GOTO_BYTECODE_IMPL(Name); \
BYTECODE_ENTRY_LABEL(Name) \
rD = pc[1]; \
rF = pc[2]; \
pc += 3; \
BYTECODE_IMPL_LABEL(Name)
#define BYTECODE_HEADER_A_B_C(Name) \
uint32_t rA, rB, rC; \
USE(rA); \
USE(rB); \
USE(rC); \
BYTECODE_ENTRY_LABEL(Name) \
rA = pc[1]; \
rB = pc[2]; \
rC = pc[3]; \
pc += 4;
#define HANDLE_EXCEPTION \
do { \
goto HandleException; \
} while (0)
#define HANDLE_RETURN \
do { \
pp_ = InterpreterHelpers::FrameBytecode(FP)->ptr()->object_pool_; \
} while (0)
// Runtime call helpers: handle invocation and potential exception after return.
#define INVOKE_RUNTIME(Func, Args) \
if (!InvokeRuntime(thread, this, Func, Args)) { \
HANDLE_EXCEPTION; \
} else { \
HANDLE_RETURN; \
}
#define INVOKE_NATIVE(Wrapper, Func, Args) \
if (!InvokeNative(thread, this, Wrapper, Func, Args)) { \
HANDLE_EXCEPTION; \
} else { \
HANDLE_RETURN; \
}
#define LOAD_CONSTANT(index) (pp_->ptr()->data()[(index)].raw_obj_)
#define UNBOX_INT64(value, obj, selector) \
int64_t value; \
{ \
word raw_value = static_cast<word>(obj); \
if (LIKELY((raw_value & kSmiTagMask) == kSmiTag)) { \
value = raw_value >> kSmiTagShift; \
} else { \
if (UNLIKELY(obj == null_value)) { \
SP[0] = selector.raw(); \
goto ThrowNullError; \
} \
value = Integer::GetInt64Value(RAW_CAST(Integer, obj)); \
} \
}
#define BOX_INT64_RESULT(result) \
if (LIKELY(Smi::IsValid(result))) { \
SP[0] = Smi::New(static_cast<intptr_t>(result)); \
} else if (!AllocateMint(thread, result, pc, FP, SP)) { \
HANDLE_EXCEPTION; \
} \
ASSERT(Integer::GetInt64Value(RAW_CAST(Integer, SP[0])) == result);
#define UNBOX_DOUBLE(value, obj, selector) \
double value; \
{ \
if (UNLIKELY(obj == null_value)) { \
SP[0] = selector.raw(); \
goto ThrowNullError; \
} \
value = Double::RawCast(obj)->ptr()->value_; \
}
#define BOX_DOUBLE_RESULT(result) \
if (!AllocateDouble(thread, result, pc, FP, SP)) { \
HANDLE_EXCEPTION; \
} \
ASSERT(Utils::DoublesBitEqual(Double::RawCast(SP[0])->ptr()->value_, result));
#define BUMP_USAGE_COUNTER_ON_ENTRY(function) \
{ \
int32_t counter = ++(function->ptr()->usage_counter_); \
if (UNLIKELY(FLAG_compilation_counter_threshold >= 0 && \
counter >= FLAG_compilation_counter_threshold && \
!Function::HasCode(function))) { \
SP[1] = 0; /* Unused result. */ \
SP[2] = function; \
Exit(thread, FP, SP + 3, pc); \
INVOKE_RUNTIME(DRT_CompileInterpretedFunction, \
NativeArguments(thread, 1, SP + 2, SP + 1)); \
function = FrameFunction(FP); \
} \
}
#ifdef PRODUCT
#define DEBUG_CHECK
#else
// The DEBUG_CHECK macro must only be called from bytecodes listed in
// KernelBytecode::IsDebugCheckedOpcode.
#define DEBUG_CHECK \
if (is_debugging()) { \
/* Check for debug breakpoint or if single stepping. */ \
if (thread->isolate()->debugger()->HasBytecodeBreakpointAt(pc)) { \
SP[1] = null_value; \
Exit(thread, FP, SP + 2, pc); \
INVOKE_RUNTIME(DRT_BreakpointRuntimeHandler, \
NativeArguments(thread, 0, nullptr, SP + 1)) \
} \
/* The debugger expects to see the same pc again when single-stepping */ \
if (thread->isolate()->single_step()) { \
Exit(thread, FP, SP + 1, pc); \
INVOKE_RUNTIME(DRT_SingleStepHandler, \
NativeArguments(thread, 0, nullptr, nullptr)); \
} \
}
#endif // PRODUCT
bool Interpreter::CopyParameters(Thread* thread,
const KBCInstr** pc,
ObjectPtr** FP,
ObjectPtr** SP,
const intptr_t num_fixed_params,
const intptr_t num_opt_pos_params,
const intptr_t num_opt_named_params) {
const intptr_t min_num_pos_args = num_fixed_params;
const intptr_t max_num_pos_args = num_fixed_params + num_opt_pos_params;
// Decode arguments descriptor.
const intptr_t arg_count = InterpreterHelpers::ArgDescArgCount(argdesc_);
const intptr_t pos_count = InterpreterHelpers::ArgDescPosCount(argdesc_);
const intptr_t named_count = (arg_count - pos_count);
// Check that got the right number of positional parameters.
if ((min_num_pos_args > pos_count) || (pos_count > max_num_pos_args)) {
return false;
}
// Copy all passed position arguments.
ObjectPtr* first_arg = FrameArguments(*FP, arg_count);
memmove(*FP, first_arg, pos_count * kWordSize);
if (num_opt_named_params != 0) {
// This is a function with named parameters.
// Walk the list of named parameters and their
// default values encoded as pairs of LoadConstant instructions that
// follows the entry point and find matching values via arguments
// descriptor.
ObjectPtr* argdesc_data = argdesc_->ptr()->data();
intptr_t i = 0; // argument position
intptr_t j = 0; // parameter position
while ((j < num_opt_named_params) && (i < named_count)) {
// Fetch formal parameter information: name, default value, target slot.
const KBCInstr* load_name = *pc;
const KBCInstr* load_value = KernelBytecode::Next(load_name);
*pc = KernelBytecode::Next(load_value);
ASSERT(KernelBytecode::IsLoadConstantOpcode(load_name));
ASSERT(KernelBytecode::IsLoadConstantOpcode(load_value));
const uint8_t reg = KernelBytecode::DecodeA(load_name);
ASSERT(reg == KernelBytecode::DecodeA(load_value));
StringPtr name = static_cast<StringPtr>(
LOAD_CONSTANT(KernelBytecode::DecodeE(load_name)));
if (name == argdesc_data[ArgumentsDescriptor::name_index(i)]) {
// Parameter was passed. Fetch passed value.
const intptr_t arg_index = Smi::Value(static_cast<SmiPtr>(
argdesc_data[ArgumentsDescriptor::position_index(i)]));
(*FP)[reg] = first_arg[arg_index];
++i; // Consume passed argument.
} else {
// Parameter was not passed. Fetch default value.
(*FP)[reg] = LOAD_CONSTANT(KernelBytecode::DecodeE(load_value));
}
++j; // Next formal parameter.
}
// If we have unprocessed formal parameters then initialize them all
// using default values.
while (j < num_opt_named_params) {
const KBCInstr* load_name = *pc;
const KBCInstr* load_value = KernelBytecode::Next(load_name);
*pc = KernelBytecode::Next(load_value);
ASSERT(KernelBytecode::IsLoadConstantOpcode(load_name));
ASSERT(KernelBytecode::IsLoadConstantOpcode(load_value));
const uint8_t reg = KernelBytecode::DecodeA(load_name);
ASSERT(reg == KernelBytecode::DecodeA(load_value));
(*FP)[reg] = LOAD_CONSTANT(KernelBytecode::DecodeE(load_value));
++j;
}
// If we have unprocessed passed arguments that means we have mismatch
// between formal parameters and concrete arguments. This can only
// occur if the current function is a closure.
if (i < named_count) {
return false;
}
// SP points past copied arguments.
*SP = *FP + num_fixed_params + num_opt_named_params - 1;
} else {
ASSERT(num_opt_pos_params != 0);
if (named_count != 0) {
// Function can't have both named and optional positional parameters.
// This kind of mismatch can only occur if the current function
// is a closure.
return false;
}
// Process the list of default values encoded as a sequence of
// LoadConstant instructions after EntryOpt bytecode.
// Execute only those that correspond to parameters that were not passed.
for (intptr_t i = num_fixed_params; i < pos_count; ++i) {
ASSERT(KernelBytecode::IsLoadConstantOpcode(*pc));
*pc = KernelBytecode::Next(*pc);
}
for (intptr_t i = pos_count; i < max_num_pos_args; ++i) {
const KBCInstr* load_value = *pc;
*pc = KernelBytecode::Next(load_value);
ASSERT(KernelBytecode::IsLoadConstantOpcode(load_value));
ASSERT(KernelBytecode::DecodeA(load_value) == i);
(*FP)[i] = LOAD_CONSTANT(KernelBytecode::DecodeE(load_value));
}
// SP points past the last copied parameter.
*SP = *FP + max_num_pos_args - 1;
}
return true;
}
bool Interpreter::AssertAssignable(Thread* thread,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* call_top,
ObjectPtr* args,
SubtypeTestCachePtr cache) {
ObjectPtr null_value = Object::null();
if (cache != null_value) {
InstancePtr instance = static_cast<InstancePtr>(args[0]);
TypeArgumentsPtr instantiator_type_arguments =
static_cast<TypeArgumentsPtr>(args[2]);
TypeArgumentsPtr function_type_arguments =
static_cast<TypeArgumentsPtr>(args[3]);
const intptr_t cid = InterpreterHelpers::GetClassId(instance);
TypeArgumentsPtr instance_type_arguments =
static_cast<TypeArgumentsPtr>(null_value);
ObjectPtr instance_cid_or_function;
TypeArgumentsPtr parent_function_type_arguments;
TypeArgumentsPtr delayed_function_type_arguments;
if (cid == kClosureCid) {
ClosurePtr closure = static_cast<ClosurePtr>(instance);
instance_type_arguments = closure->ptr()->instantiator_type_arguments_;
parent_function_type_arguments = closure->ptr()->function_type_arguments_;
delayed_function_type_arguments = closure->ptr()->delayed_type_arguments_;
instance_cid_or_function = closure->ptr()->function_;
} else {
instance_cid_or_function = Smi::New(cid);
ClassPtr instance_class = thread->isolate()->class_table()->At(cid);
if (instance_class->ptr()->num_type_arguments_ < 0) {
goto AssertAssignableCallRuntime;
} else if (instance_class->ptr()->num_type_arguments_ > 0) {
instance_type_arguments = reinterpret_cast<TypeArgumentsPtr*>(
instance->ptr())[instance_class->ptr()
->host_type_arguments_field_offset_in_words_];
}
parent_function_type_arguments =
static_cast<TypeArgumentsPtr>(null_value);
delayed_function_type_arguments =
static_cast<TypeArgumentsPtr>(null_value);
}
for (ObjectPtr* entries = cache->ptr()->cache_->ptr()->data();
entries[0] != null_value;
entries += SubtypeTestCache::kTestEntryLength) {
if ((entries[SubtypeTestCache::kInstanceClassIdOrFunction] ==
instance_cid_or_function) &&
(entries[SubtypeTestCache::kInstanceTypeArguments] ==
instance_type_arguments) &&
(entries[SubtypeTestCache::kInstantiatorTypeArguments] ==
instantiator_type_arguments) &&
(entries[SubtypeTestCache::kFunctionTypeArguments] ==
function_type_arguments) &&
(entries[SubtypeTestCache::kInstanceParentFunctionTypeArguments] ==
parent_function_type_arguments) &&
(entries[SubtypeTestCache::kInstanceDelayedFunctionTypeArguments] ==
delayed_function_type_arguments)) {
if (Bool::True().raw() == entries[SubtypeTestCache::kTestResult]) {
return true;
} else {
break;
}
}
}
}
AssertAssignableCallRuntime:
// args[0]: Instance.
// args[1]: Type.
// args[2]: Instantiator type args.
// args[3]: Function type args.
// args[4]: Name.
args[5] = cache;
args[6] = Smi::New(kTypeCheckFromInline);
args[7] = 0; // Unused result.
Exit(thread, FP, args + 8, pc);
NativeArguments native_args(thread, 7, args, args + 7);
return InvokeRuntime(thread, this, DRT_TypeCheck, native_args);
}
template <bool is_getter>
bool Interpreter::AssertAssignableField(Thread* thread,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP,
InstancePtr instance,
FieldPtr field,
InstancePtr value) {
AbstractTypePtr field_type = field->ptr()->type_;
// Perform type test of value if field type is not one of dynamic, object,
// or void, and if the value is not null.
if (field_type->GetClassId() == kTypeCid) {
classid_t cid = Smi::Value(
static_cast<SmiPtr>(Type::RawCast(field_type)->ptr()->type_class_id_));
// TODO(regis): Revisit shortcut for NNBD.
if (cid == kDynamicCid || cid == kInstanceCid || cid == kVoidCid) {
return true;
}
}
ObjectPtr null_value = Object::null();
if (value == null_value) {
// TODO(regis): Revisit null shortcut for NNBD.
return true;
}
SubtypeTestCachePtr cache = field->ptr()->type_test_cache_;
if (UNLIKELY(cache == null_value)) {
// Allocate new cache.
SP[1] = instance; // Preserve.
SP[2] = field; // Preserve.
SP[3] = value; // Preserve.
SP[4] = null_value; // Result slot.
Exit(thread, FP, SP + 5, pc);
if (!InvokeRuntime(thread, this, DRT_AllocateSubtypeTestCache,
NativeArguments(thread, 0, /* argv */ SP + 4,
/* retval */ SP + 4))) {
return false;
}
// Reload objects after the call which may trigger GC.
instance = static_cast<InstancePtr>(SP[1]);
field = static_cast<FieldPtr>(SP[2]);
value = static_cast<InstancePtr>(SP[3]);
cache = static_cast<SubtypeTestCachePtr>(SP[4]);
field_type = field->ptr()->type_;
field->ptr()->type_test_cache_ = cache;
}
// Push arguments of type test.
SP[1] = value;
SP[2] = field_type;
// Provide type arguments of instance as instantiator.
SP[3] = InterpreterHelpers::GetTypeArguments(thread, instance);
SP[4] = null_value; // Implicit setters cannot be generic.
SP[5] = is_getter ? Symbols::FunctionResult().raw() : field->ptr()->name_;
return AssertAssignable(thread, pc, FP, /* call_top */ SP + 5,
/* args */ SP + 1, cache);
}
ObjectPtr Interpreter::Call(const Function& function,
const Array& arguments_descriptor,
const Array& arguments,
Thread* thread) {
return Call(function.raw(), arguments_descriptor.raw(), arguments.Length(),
arguments.raw_ptr()->data(), thread);
}
// Allocate a _Mint for the given int64_t value and puts it into SP[0].
// Returns false on exception.
DART_NOINLINE bool Interpreter::AllocateMint(Thread* thread,
int64_t value,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
ASSERT(!Smi::IsValid(value));
MintPtr result;
if (TryAllocate(thread, kMintCid, Mint::InstanceSize(),
reinterpret_cast<ObjectPtr*>(&result))) {
result->ptr()->value_ = value;
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = thread->isolate()->object_store()->mint_class(); // Class object.
SP[2] = Object::null(); // Type arguments.
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
return false;
}
static_cast<MintPtr>(SP[0])->ptr()->value_ = value;
return true;
}
}
// Allocate a _Double for the given double value and put it into SP[0].
// Returns false on exception.
DART_NOINLINE bool Interpreter::AllocateDouble(Thread* thread,
double value,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
DoublePtr result;
if (TryAllocate(thread, kDoubleCid, Double::InstanceSize(),
reinterpret_cast<ObjectPtr*>(&result))) {
result->ptr()->value_ = value;
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = thread->isolate()->object_store()->double_class();
SP[2] = Object::null(); // Type arguments.
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
return false;
}
Double::RawCast(SP[0])->ptr()->value_ = value;
return true;
}
}
// Allocate a _Float32x4 for the given simd value and put it into SP[0].
// Returns false on exception.
DART_NOINLINE bool Interpreter::AllocateFloat32x4(Thread* thread,
simd128_value_t value,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
Float32x4Ptr result;
if (TryAllocate(thread, kFloat32x4Cid, Float32x4::InstanceSize(),
reinterpret_cast<ObjectPtr*>(&result))) {
value.writeTo(result->ptr()->value_);
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = thread->isolate()->object_store()->float32x4_class();
SP[2] = Object::null(); // Type arguments.
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
return false;
}
value.writeTo(Float32x4::RawCast(SP[0])->ptr()->value_);
return true;
}
}
// Allocate _Float64x2 box for the given simd value and put it into SP[0].
// Returns false on exception.
DART_NOINLINE bool Interpreter::AllocateFloat64x2(Thread* thread,
simd128_value_t value,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
Float64x2Ptr result;
if (TryAllocate(thread, kFloat64x2Cid, Float64x2::InstanceSize(),
reinterpret_cast<ObjectPtr*>(&result))) {
value.writeTo(result->ptr()->value_);
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = thread->isolate()->object_store()->float64x2_class();
SP[2] = Object::null(); // Type arguments.
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
return false;
}
value.writeTo(Float64x2::RawCast(SP[0])->ptr()->value_);
return true;
}
}
// Allocate a _List with the given type arguments and length and put it into
// SP[0]. Returns false on exception.
bool Interpreter::AllocateArray(Thread* thread,
TypeArgumentsPtr type_args,
ObjectPtr length_object,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
if (LIKELY(!length_object->IsHeapObject())) {
const intptr_t length = Smi::Value(Smi::RawCast(length_object));
if (LIKELY(Array::IsValidLength(length))) {
ArrayPtr result;
if (TryAllocate(thread, kArrayCid, Array::InstanceSize(length),
reinterpret_cast<ObjectPtr*>(&result))) {
result->ptr()->type_arguments_ = type_args;
result->ptr()->length_ = Smi::New(length);
for (intptr_t i = 0; i < length; i++) {
result->ptr()->data()[i] = Object::null();
}
SP[0] = result;
return true;
}
}
}
SP[0] = 0; // Space for the result;
SP[1] = length_object;
SP[2] = type_args;
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
return InvokeRuntime(thread, this, DRT_AllocateArray, args);
}
// Allocate a _Context with the given length and put it into SP[0].
// Returns false on exception.
bool Interpreter::AllocateContext(Thread* thread,
intptr_t num_context_variables,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
ContextPtr result;
if (TryAllocate(thread, kContextCid,
Context::InstanceSize(num_context_variables),
reinterpret_cast<ObjectPtr*>(&result))) {
result->ptr()->num_variables_ = num_context_variables;
ObjectPtr null_value = Object::null();
result->ptr()->parent_ = static_cast<ContextPtr>(null_value);
for (intptr_t i = 0; i < num_context_variables; i++) {
result->ptr()->data()[i] = null_value;
}
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = Smi::New(num_context_variables);
Exit(thread, FP, SP + 2, pc);
NativeArguments args(thread, 1, SP + 1, SP);
return InvokeRuntime(thread, this, DRT_AllocateContext, args);
}
}
// Allocate a _Closure and put it into SP[0].
// Returns false on exception.
bool Interpreter::AllocateClosure(Thread* thread,
const KBCInstr* pc,
ObjectPtr* FP,
ObjectPtr* SP) {
const intptr_t instance_size = Closure::InstanceSize();
ClosurePtr result;
if (TryAllocate(thread, kClosureCid, instance_size,
reinterpret_cast<ObjectPtr*>(&result))) {
uword start = ObjectLayout::ToAddr(result);
ObjectPtr null_value = Object::null();
for (intptr_t offset = sizeof(InstanceLayout); offset < instance_size;
offset += kWordSize) {
*reinterpret_cast<ObjectPtr*>(start + offset) = null_value;
}
SP[0] = result;
return true;
} else {
SP[0] = 0; // Space for the result.
SP[1] = thread->isolate()->object_store()->closure_class();
SP[2] = Object::null(); // Type arguments.
Exit(thread, FP, SP + 3, pc);
NativeArguments args(thread, 2, SP + 1, SP);
return InvokeRuntime(thread, this, DRT_AllocateObject, args);
}
}
ObjectPtr Interpreter::Call(FunctionPtr function,
ArrayPtr argdesc,
intptr_t argc,
ObjectPtr const* argv,
Thread* thread) {
// Interpreter state (see constants_kbc.h for high-level overview).
const KBCInstr* pc; // Program Counter: points to the next op to execute.
ObjectPtr* FP; // Frame Pointer.
ObjectPtr* SP; // Stack Pointer.
uint32_t op; // Currently executing op.
bool reentering = fp_ != NULL;
if (!reentering) {
fp_ = reinterpret_cast<ObjectPtr*>(stack_base_);
}
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("%s interpreter 0x%" Px " at fp_ 0x%" Px " exit 0x%" Px " %s\n",
reentering ? "Re-entering" : "Entering",
reinterpret_cast<uword>(this), reinterpret_cast<uword>(fp_),
thread->top_exit_frame_info(),
Function::Handle(function).ToFullyQualifiedCString());
}
#endif
// Setup entry frame:
//
// ^
// | previous Dart frames
// |
// | ........... | -+
// fp_ > | exit fp_ | saved top_exit_frame_info
// | argdesc_ | saved argdesc_ (for reentering interpreter)
// | pp_ | saved pp_ (for reentering interpreter)
// | arg 0 | -+
// | arg 1 | |
// ... |
// > incoming arguments
// |
// | arg argc-1 | -+
// | function | -+
// | code | |
// | caller PC | ---> special fake PC marking an entry frame
// SP > | fp_ | |
// FP > | ........... | > normal Dart frame (see stack_frame_kbc.h)
// |
// v
//
// A negative argc indicates reverse memory order of arguments.
const intptr_t arg_count = argc < 0 ? -argc : argc;
FP = fp_ + kKBCEntrySavedSlots + arg_count + kKBCDartFrameFixedSize;
SP = FP - 1;
// Save outer top_exit_frame_info, current argdesc, and current pp.
fp_[kKBCExitLinkSlotFromEntryFp] =
static_cast<ObjectPtr>(thread->top_exit_frame_info());
thread->set_top_exit_frame_info(0);
fp_[kKBCSavedArgDescSlotFromEntryFp] = static_cast<ObjectPtr>(argdesc_);
fp_[kKBCSavedPpSlotFromEntryFp] = static_cast<ObjectPtr>(pp_);
// Copy arguments and setup the Dart frame.
for (intptr_t i = 0; i < arg_count; i++) {
fp_[kKBCEntrySavedSlots + i] = argv[argc < 0 ? -i : i];
}
BytecodePtr bytecode = function->ptr()->bytecode_;
FP[kKBCFunctionSlotFromFp] = function;
FP[kKBCPcMarkerSlotFromFp] = bytecode;
FP[kKBCSavedCallerPcSlotFromFp] = static_cast<ObjectPtr>(kEntryFramePcMarker);
FP[kKBCSavedCallerFpSlotFromFp] =
static_cast<ObjectPtr>(reinterpret_cast<uword>(fp_));
// Load argument descriptor.
argdesc_ = argdesc;
// Ready to start executing bytecode. Load entry point and corresponding
// object pool.
pc = reinterpret_cast<const KBCInstr*>(bytecode->ptr()->instructions_);
NOT_IN_PRODUCT(pc_ = pc); // For the profiler.
NOT_IN_PRODUCT(fp_ = FP); // For the profiler.
pp_ = bytecode->ptr()->object_pool_;
// Save current VM tag and mark thread as executing Dart code. For the
// profiler, do this *after* setting up the entry frame (compare the machine
// code entry stubs).
const uword vm_tag = thread->vm_tag();
thread->set_vm_tag(VMTag::kDartInterpretedTagId);
// Save current top stack resource and reset the list.
StackResource* top_resource = thread->top_resource();
thread->set_top_resource(NULL);
// Cache some frequently used values in the frame.
BoolPtr true_value = Bool::True().raw();
BoolPtr false_value = Bool::False().raw();
ObjectPtr null_value = Object::null();
#ifdef DART_HAS_COMPUTED_GOTO
static const void* dispatch[] = {
#define TARGET(name, fmt, kind, fmta, fmtb, fmtc) &&bc##name,
KERNEL_BYTECODES_LIST(TARGET)
#undef TARGET
};
DISPATCH(); // Enter the dispatch loop.
#else
DISPATCH(); // Enter the dispatch loop.
SwitchDispatch:
switch (op & 0xFF) {
#define TARGET(name, fmt, kind, fmta, fmtb, fmtc) \
case KernelBytecode::k##name: \
goto bc##name;
KERNEL_BYTECODES_LIST(TARGET)
#undef TARGET
default:
FATAL1("Undefined opcode: %d\n", op);
}
#endif
// KernelBytecode handlers (see constants_kbc.h for bytecode descriptions).
{
BYTECODE(Entry, D);
const intptr_t num_locals = rD;
// Initialize locals with null & set SP.
for (intptr_t i = 0; i < num_locals; i++) {
FP[i] = null_value;
}
SP = FP + num_locals - 1;
DISPATCH();
}
{
BYTECODE(EntryFixed, A_E);
const intptr_t num_fixed_params = rA;
const intptr_t num_locals = rE;
const intptr_t arg_count = InterpreterHelpers::ArgDescArgCount(argdesc_);
const intptr_t pos_count = InterpreterHelpers::ArgDescPosCount(argdesc_);
if ((arg_count != num_fixed_params) || (pos_count != num_fixed_params)) {
SP[1] = FrameFunction(FP);
goto NoSuchMethodFromPrologue;
}
// Initialize locals with null & set SP.
for (intptr_t i = 0; i < num_locals; i++) {
FP[i] = null_value;
}
SP = FP + num_locals - 1;
DISPATCH();
}
{
BYTECODE(EntryOptional, A_B_C);
if (CopyParameters(thread, &pc, &FP, &SP, rA, rB, rC)) {
DISPATCH();
} else {
SP[1] = FrameFunction(FP);
goto NoSuchMethodFromPrologue;
}
}
{
BYTECODE(Frame, D);
// Initialize locals with null and increment SP.
const intptr_t num_locals = rD;
for (intptr_t i = 1; i <= num_locals; i++) {
SP[i] = null_value;
}
SP += num_locals;
DISPATCH();
}
{
BYTECODE(SetFrame, A);
SP = FP + rA - 1;
DISPATCH();
}
{
BYTECODE(CheckStack, A);
{
// Check the interpreter's own stack limit for actual interpreter's stack
// overflows, and also the thread's stack limit for scheduled interrupts.
if (reinterpret_cast<uword>(SP) >= overflow_stack_limit() ||
thread->HasScheduledInterrupts()) {
Exit(thread, FP, SP + 1, pc);
INVOKE_RUNTIME(DRT_StackOverflow,
NativeArguments(thread, 0, nullptr, nullptr));
}
}
FunctionPtr function = FrameFunction(FP);
int32_t counter = ++(function->ptr()->usage_counter_);
if (UNLIKELY(FLAG_compilation_counter_threshold >= 0 &&
counter >= FLAG_compilation_counter_threshold &&
!Function::HasCode(function))) {
SP[1] = 0; // Unused result.
SP[2] = function;
Exit(thread, FP, SP + 3, pc);
INVOKE_RUNTIME(DRT_CompileInterpretedFunction,
NativeArguments(thread, 1, SP + 2, SP + 1));
}
DISPATCH();
}
{
BYTECODE(DebugCheck, 0);
DEBUG_CHECK;
DISPATCH();
}
{
BYTECODE(CheckFunctionTypeArgs, A_E);
const intptr_t declared_type_args_len = rA;
const intptr_t first_stack_local_index = rE;
// Decode arguments descriptor's type args len.
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
if ((type_args_len != declared_type_args_len) && (type_args_len != 0)) {
SP[1] = FrameFunction(FP);
goto NoSuchMethodFromPrologue;
}
if (type_args_len > 0) {
// Decode arguments descriptor's argument count (excluding type args).
const intptr_t arg_count = InterpreterHelpers::ArgDescArgCount(argdesc_);
// Copy passed-in type args to first local slot.
FP[first_stack_local_index] = *FrameArguments(FP, arg_count + 1);
} else if (declared_type_args_len > 0) {
FP[first_stack_local_index] = Object::null();
}
DISPATCH();
}
{
BYTECODE(InstantiateType, D);
// Stack: instantiator type args, function type args
ObjectPtr type = LOAD_CONSTANT(rD);
SP[1] = type;
SP[2] = SP[-1];
SP[3] = SP[0];
Exit(thread, FP, SP + 4, pc);
{
INVOKE_RUNTIME(DRT_InstantiateType,
NativeArguments(thread, 3, SP + 1, SP - 1));
}
SP -= 1;
DISPATCH();
}
{
BYTECODE(InstantiateTypeArgumentsTOS, A_E);
// Stack: instantiator type args, function type args
TypeArgumentsPtr type_arguments =
static_cast<TypeArgumentsPtr>(LOAD_CONSTANT(rE));
ObjectPtr instantiator_type_args = SP[-1];
ObjectPtr function_type_args = SP[0];
// If both instantiators are null and if the type argument vector
// instantiated from null becomes a vector of dynamic, then use null as
// the type arguments.
if ((rA == 0) || (null_value != instantiator_type_args) ||
(null_value != function_type_args)) {
// First lookup in the cache.
ArrayPtr instantiations = type_arguments->ptr()->instantiations_;
for (intptr_t i = 0;
instantiations->ptr()->data()[i] !=
static_cast<ObjectPtr>(TypeArguments::kNoInstantiator);
i += TypeArguments::Instantiation::kSizeInWords) {
if ((instantiations->ptr()->data()
[i +
TypeArguments::Instantiation::kInstantiatorTypeArgsIndex] ==
instantiator_type_args) &&
(instantiations->ptr()->data()
[i + TypeArguments::Instantiation::kFunctionTypeArgsIndex] ==
function_type_args)) {
// Found in the cache.
SP[-1] =
instantiations->ptr()->data()[i + TypeArguments::Instantiation::
kInstantiatedTypeArgsIndex];
goto InstantiateTypeArgumentsTOSDone;
}
}
// Cache lookup failed, call runtime.
SP[1] = type_arguments;
SP[2] = instantiator_type_args;
SP[3] = function_type_args;
Exit(thread, FP, SP + 4, pc);
INVOKE_RUNTIME(DRT_InstantiateTypeArguments,
NativeArguments(thread, 3, SP + 1, SP - 1));
}
InstantiateTypeArgumentsTOSDone:
SP -= 1;
DISPATCH();
}
{
BYTECODE(Throw, A);
{
SP[1] = 0; // Space for result.
Exit(thread, FP, SP + 2, pc);
if (rA == 0) { // Throw
INVOKE_RUNTIME(DRT_Throw, NativeArguments(thread, 1, SP, SP + 1));
} else { // ReThrow
INVOKE_RUNTIME(DRT_ReThrow, NativeArguments(thread, 2, SP - 1, SP + 1));
}
}
DISPATCH();
}
{
BYTECODE(Drop1, 0);
SP--;
DISPATCH();
}
{
BYTECODE(LoadConstant, A_E);
FP[rA] = LOAD_CONSTANT(rE);
DISPATCH();
}
{
BYTECODE(PushConstant, D);
*++SP = LOAD_CONSTANT(rD);
DISPATCH();
}
{
BYTECODE(PushNull, 0);
*++SP = null_value;
DISPATCH();
}
{
BYTECODE(PushTrue, 0);
*++SP = true_value;
DISPATCH();
}
{
BYTECODE(PushFalse, 0);
*++SP = false_value;
DISPATCH();
}
{
BYTECODE(PushInt, X);
*++SP = Smi::New(rX);
DISPATCH();
}
{
BYTECODE(Push, X);
*++SP = FP[rX];
DISPATCH();
}
{
BYTECODE(StoreLocal, X);
FP[rX] = *SP;
DISPATCH();
}
{
BYTECODE(PopLocal, X);
FP[rX] = *SP--;
DISPATCH();
}
{
BYTECODE(MoveSpecial, A_Y);
ASSERT(rA < KernelBytecode::kSpecialIndexCount);
FP[rY] = special_[rA];
DISPATCH();
}
{
BYTECODE(BooleanNegateTOS, 0);
SP[0] = (SP[0] == true_value) ? false_value : true_value;
DISPATCH();
}
{
BYTECODE(DirectCall, D_F);
DEBUG_CHECK;
// Invoke target function.
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
*++SP = LOAD_CONSTANT(kidx);
ObjectPtr* call_base = SP - argc;
ObjectPtr* call_top = SP;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
if (!Invoke(thread, call_base, call_top, &pc, &FP, &SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(UncheckedDirectCall, D_F);
DEBUG_CHECK;
// Invoke target function.
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
*++SP = LOAD_CONSTANT(kidx);
ObjectPtr* call_base = SP - argc;
ObjectPtr* call_top = SP;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
if (!Invoke(thread, call_base, call_top, &pc, &FP, &SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(InterfaceCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
StringPtr target_name =
static_cast<FunctionPtr>(LOAD_CONSTANT(kidx))->ptr()->name_;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
if (!InstanceCall(thread, target_name, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(InstantiatedInterfaceCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
StringPtr target_name =
static_cast<FunctionPtr>(LOAD_CONSTANT(kidx))->ptr()->name_;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
if (!InstanceCall(thread, target_name, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(UncheckedClosureCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
ClosurePtr receiver = Closure::RawCast(*SP--);
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
if (UNLIKELY(receiver == null_value)) {
SP[0] = Symbols::Call().raw();
goto ThrowNullError;
}
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx));
call_top[0] = receiver->ptr()->function_;
if (!Invoke(thread, call_base, call_top, &pc, &FP, &SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(UncheckedInterfaceCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
StringPtr target_name =
static_cast<FunctionPtr>(LOAD_CONSTANT(kidx))->ptr()->name_;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
if (!InstanceCall(thread, target_name, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(DynamicCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
StringPtr target_name = String::RawCast(LOAD_CONSTANT(kidx));
argdesc_ = Array::RawCast(LOAD_CONSTANT(kidx + 1));
if (!InstanceCall(thread, target_name, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(NativeCall, D);
TypedDataPtr data = static_cast<TypedDataPtr>(LOAD_CONSTANT(rD));
MethodRecognizer::Kind kind = NativeEntryData::GetKind(data);
switch (kind) {
case MethodRecognizer::kObjectEquals: {
SP[-1] = SP[-1] == SP[0] ? Bool::True().raw() : Bool::False().raw();
SP--;
} break;
case MethodRecognizer::kStringBaseLength:
case MethodRecognizer::kStringBaseIsEmpty: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[String::length_offset() / kWordSize];
if (kind == MethodRecognizer::kStringBaseIsEmpty) {
SP[0] =
SP[0] == Smi::New(0) ? Bool::True().raw() : Bool::False().raw();
}
} break;
case MethodRecognizer::kGrowableArrayLength: {
GrowableObjectArrayPtr instance =
static_cast<GrowableObjectArrayPtr>(SP[0]);
SP[0] = instance->ptr()->length_;
} break;
case MethodRecognizer::kObjectArrayLength:
case MethodRecognizer::kImmutableArrayLength: {
ArrayPtr instance = static_cast<ArrayPtr>(SP[0]);
SP[0] = instance->ptr()->length_;
} break;
case MethodRecognizer::kTypedListLength:
case MethodRecognizer::kTypedListViewLength:
case MethodRecognizer::kByteDataViewLength: {
TypedDataBasePtr instance = static_cast<TypedDataBasePtr>(SP[0]);
SP[0] = instance->ptr()->length_;
} break;
case MethodRecognizer::kByteDataViewOffsetInBytes:
case MethodRecognizer::kTypedDataViewOffsetInBytes: {
TypedDataViewPtr instance = static_cast<TypedDataViewPtr>(SP[0]);
SP[0] = instance->ptr()->offset_in_bytes_;
} break;
case MethodRecognizer::kByteDataViewTypedData:
case MethodRecognizer::kTypedDataViewTypedData: {
TypedDataViewPtr instance = static_cast<TypedDataViewPtr>(SP[0]);
SP[0] = instance->ptr()->typed_data_;
} break;
case MethodRecognizer::kClassIDgetID: {
SP[0] = InterpreterHelpers::GetClassIdAsSmi(SP[0]);
} break;
case MethodRecognizer::kAsyncStackTraceHelper: {
SP[0] = Object::null();
} break;
case MethodRecognizer::kGrowableArrayCapacity: {
GrowableObjectArrayPtr instance =
static_cast<GrowableObjectArrayPtr>(SP[0]);
SP[0] = instance->ptr()->data_->ptr()->length_;
} break;
case MethodRecognizer::kListFactory: {
// factory List<E>([int length]) {
// return (:arg_desc.positional_count == 2) ? new _List<E>(length)
// : new _GrowableList<E>(0);
// }
if (InterpreterHelpers::ArgDescPosCount(argdesc_) == 2) {
TypeArgumentsPtr type_args = TypeArguments::RawCast(SP[-1]);
ObjectPtr length = SP[0];
SP--;
if (!AllocateArray(thread, type_args, length, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
} else {
ASSERT(InterpreterHelpers::ArgDescPosCount(argdesc_) == 1);
// SP[-1] is type.
// The native wrapper pushed null as the optional length argument.
ASSERT(SP[0] == null_value);
SP[0] = Smi::New(0); // Patch null length with zero length.
SP[1] = thread->isolate()->object_store()->growable_list_factory();
// Change the ArgumentsDescriptor of the call with a new cached one.
argdesc_ = ArgumentsDescriptor::NewBoxed(
0, KernelBytecode::kNativeCallToGrowableListArgc);
// Replace PC to the return trampoline so ReturnTOS would see
// a call bytecode at return address and will be able to get argc
// via DecodeArgc.
pc = KernelBytecode::GetNativeCallToGrowableListReturnTrampoline();
if (!Invoke(thread, SP - 1, SP + 1, &pc, &FP, &SP)) {
HANDLE_EXCEPTION;
}
}
} break;
case MethodRecognizer::kObjectArrayAllocate: {
TypeArgumentsPtr type_args = TypeArguments::RawCast(SP[-1]);
ObjectPtr length = SP[0];
SP--;
if (!AllocateArray(thread, type_args, length, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
} break;
case MethodRecognizer::kLinkedHashMap_getIndex: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::index_offset() / kWordSize];
} break;
case MethodRecognizer::kLinkedHashMap_setIndex: {
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
instance->ptr()->StorePointer(
reinterpret_cast<ObjectPtr*>(instance->ptr()) +
LinkedHashMap::index_offset() / kWordSize,
SP[0]);
*--SP = null_value;
} break;
case MethodRecognizer::kLinkedHashMap_getData: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::data_offset() / kWordSize];
} break;
case MethodRecognizer::kLinkedHashMap_setData: {
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
instance->ptr()->StorePointer(
reinterpret_cast<ObjectPtr*>(instance->ptr()) +
LinkedHashMap::data_offset() / kWordSize,
SP[0]);
*--SP = null_value;
} break;
case MethodRecognizer::kLinkedHashMap_getHashMask: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::hash_mask_offset() / kWordSize];
} break;
case MethodRecognizer::kLinkedHashMap_setHashMask: {
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
ASSERT(!SP[0]->IsHeapObject());
reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::hash_mask_offset() / kWordSize] =
SP[0];
*--SP = null_value;
} break;
case MethodRecognizer::kLinkedHashMap_getUsedData: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::used_data_offset() / kWordSize];
} break;
case MethodRecognizer::kLinkedHashMap_setUsedData: {
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
ASSERT(!SP[0]->IsHeapObject());
reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::used_data_offset() / kWordSize] =
SP[0];
*--SP = null_value;
} break;
case MethodRecognizer::kLinkedHashMap_getDeletedKeys: {
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::deleted_keys_offset() / kWordSize];
} break;
case MethodRecognizer::kLinkedHashMap_setDeletedKeys: {
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
ASSERT(!SP[0]->IsHeapObject());
reinterpret_cast<ObjectPtr*>(
instance->ptr())[LinkedHashMap::deleted_keys_offset() / kWordSize] =
SP[0];
*--SP = null_value;
} break;
case MethodRecognizer::kFfiAbi: {
*++SP = Smi::New(static_cast<int64_t>(compiler::ffi::TargetAbi()));
} break;
default: {
NativeEntryData::Payload* payload =
NativeEntryData::FromTypedArray(data);
intptr_t argc_tag = NativeEntryData::GetArgcTag(data);
const intptr_t num_arguments =
NativeArguments::ArgcBits::decode(argc_tag);
if (payload->trampoline == NULL) {
ASSERT(payload->native_function == NULL);
payload->trampoline = &NativeEntry::BootstrapNativeCallWrapper;
payload->native_function =
reinterpret_cast<NativeFunction>(&NativeEntry::LinkNativeCall);
}
*++SP = null_value; // Result slot.
ObjectPtr* incoming_args = SP - num_arguments;
ObjectPtr* return_slot = SP;
Exit(thread, FP, SP + 1, pc);
NativeArguments native_args(thread, argc_tag, incoming_args,
return_slot);
INVOKE_NATIVE(
payload->trampoline,
reinterpret_cast<Dart_NativeFunction>(payload->native_function),
reinterpret_cast<Dart_NativeArguments>(&native_args));
*(SP - num_arguments) = *return_slot;
SP -= num_arguments;
}
}
DISPATCH();
}
{
BYTECODE(ReturnTOS, 0);
DEBUG_CHECK;
ObjectPtr result; // result to return to the caller.
result = *SP;
// Restore caller PC.
pc = SavedCallerPC(FP);
// Check if it is a fake PC marking the entry frame.
if (IsEntryFrameMarker(pc)) {
// Pop entry frame.
ObjectPtr* entry_fp = SavedCallerFP(FP);
// Restore exit frame info saved in entry frame.
pp_ = static_cast<ObjectPoolPtr>(entry_fp[kKBCSavedPpSlotFromEntryFp]);
argdesc_ =
static_cast<ArrayPtr>(entry_fp[kKBCSavedArgDescSlotFromEntryFp]);
uword exit_fp = static_cast<uword>(entry_fp[kKBCExitLinkSlotFromEntryFp]);
thread->set_top_exit_frame_info(exit_fp);
thread->set_top_resource(top_resource);
thread->set_vm_tag(vm_tag);
fp_ = entry_fp;
NOT_IN_PRODUCT(pc_ = pc); // For the profiler.
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("Returning from interpreter 0x%" Px " at fp_ 0x%" Px
" exit 0x%" Px "\n",
reinterpret_cast<uword>(this), reinterpret_cast<uword>(fp_),
exit_fp);
}
ASSERT(HasFrame(reinterpret_cast<uword>(fp_)));
// Exception propagation should have been done.
ASSERT(!result->IsHeapObject() ||
result->GetClassId() != kUnhandledExceptionCid);
#endif
return result;
}
// Look at the caller to determine how many arguments to pop.
const uint8_t argc = KernelBytecode::DecodeArgc(pc);
// Restore SP, FP and PP. Push result and dispatch.
SP = FrameArguments(FP, argc);
FP = SavedCallerFP(FP);
NOT_IN_PRODUCT(fp_ = FP); // For the profiler.
NOT_IN_PRODUCT(pc_ = pc); // For the profiler.
pp_ = InterpreterHelpers::FrameBytecode(FP)->ptr()->object_pool_;
*SP = result;
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("Returning to %s (argc %d)\n",
Function::Handle(FrameFunction(FP)).ToFullyQualifiedCString(),
static_cast<int>(argc));
}
#endif
DISPATCH();
}
{
BYTECODE(InitLateField, D);
FieldPtr field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
intptr_t offset_in_words =
Smi::Value(field->ptr()->host_offset_or_field_id_);
instance->ptr()->StorePointer(
reinterpret_cast<ObjectPtr*>(instance->ptr()) + offset_in_words,
Object::RawCast(Object::sentinel().raw()), thread);
SP -= 1; // Drop instance.
DISPATCH();
}
{
BYTECODE(PushUninitializedSentinel, 0);
*++SP = Object::sentinel().raw();
DISPATCH();
}
{
BYTECODE(JumpIfInitialized, T);
SP -= 1;
if (SP[1] != Object::sentinel().raw()) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(StoreStaticTOS, D);
FieldPtr field = static_cast<FieldPtr>(LOAD_CONSTANT(rD));
InstancePtr value = static_cast<InstancePtr>(*SP--);
intptr_t field_id = Smi::Value(field->ptr()->host_offset_or_field_id_);
thread->field_table_values()[field_id] = value;
DISPATCH();
}
{
BYTECODE(LoadStatic, D);
FieldPtr field = static_cast<FieldPtr>(LOAD_CONSTANT(rD));
intptr_t field_id = Smi::Value(field->ptr()->host_offset_or_field_id_);
InstancePtr value = thread->field_table_values()[field_id];
ASSERT((value != Object::sentinel().raw()) &&
(value != Object::transition_sentinel().raw()));
*++SP = value;
DISPATCH();
}
{
BYTECODE(StoreFieldTOS, D);
FieldPtr field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
InstancePtr instance = static_cast<InstancePtr>(SP[-1]);
ObjectPtr value = static_cast<ObjectPtr>(SP[0]);
intptr_t offset_in_words =
Smi::Value(field->ptr()->host_offset_or_field_id_);
if (InterpreterHelpers::FieldNeedsGuardUpdate(field, value)) {
SP[1] = 0; // Unused result of runtime call.
SP[2] = field;
SP[3] = value;
Exit(thread, FP, SP + 4, pc);
if (!InvokeRuntime(thread, this, DRT_UpdateFieldCid,
NativeArguments(thread, 2, /* argv */ SP + 2,
/* retval */ SP + 1))) {
HANDLE_EXCEPTION;
}
// Reload objects after the call which may trigger GC.
field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
instance = static_cast<InstancePtr>(SP[-1]);
value = SP[0];
}
const bool unboxing =
(field->ptr()->is_nullable_ != kNullCid) &&
Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
classid_t guarded_cid = field->ptr()->guarded_cid_;
if (unboxing && (guarded_cid == kDoubleCid) && supports_unboxed_doubles_) {
double raw_value = Double::RawCast(value)->ptr()->value_;
ASSERT(*(reinterpret_cast<DoublePtr*>(instance->ptr()) +
offset_in_words) == null_value); // Initializing store.
if (!AllocateDouble(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
DoublePtr box = Double::RawCast(SP[0]);
instance = static_cast<InstancePtr>(SP[-1]);
instance->ptr()->StorePointer(
reinterpret_cast<DoublePtr*>(instance->ptr()) + offset_in_words, box,
thread);
} else if (unboxing && (guarded_cid == kFloat32x4Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
ASSERT(*(reinterpret_cast<Float32x4Ptr*>(instance->ptr()) +
offset_in_words) == null_value); // Initializing store.
if (!AllocateFloat32x4(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
Float32x4Ptr box = Float32x4::RawCast(SP[0]);
instance = static_cast<InstancePtr>(SP[-1]);
instance->ptr()->StorePointer(
reinterpret_cast<Float32x4Ptr*>(instance->ptr()) + offset_in_words,
box, thread);
} else if (unboxing && (guarded_cid == kFloat64x2Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
ASSERT(*(reinterpret_cast<Float64x2Ptr*>(instance->ptr()) +
offset_in_words) == null_value); // Initializing store.
if (!AllocateFloat64x2(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
Float64x2Ptr box = Float64x2::RawCast(SP[0]);
instance = static_cast<InstancePtr>(SP[-1]);
instance->ptr()->StorePointer(
reinterpret_cast<Float64x2Ptr*>(instance->ptr()) + offset_in_words,
box, thread);
} else {
instance->ptr()->StorePointer(
reinterpret_cast<ObjectPtr*>(instance->ptr()) + offset_in_words,
value, thread);
}
SP -= 2; // Drop instance and value.
DISPATCH();
}
{
BYTECODE(StoreContextParent, 0);
const uword offset_in_words =
static_cast<uword>(Context::parent_offset() / kWordSize);
ContextPtr instance = static_cast<ContextPtr>(SP[-1]);
ContextPtr value = static_cast<ContextPtr>(SP[0]);
SP -= 2; // Drop instance and value.
instance->ptr()->StorePointer(
reinterpret_cast<ContextPtr*>(instance->ptr()) + offset_in_words, value,
thread);
DISPATCH();
}
{
BYTECODE(StoreContextVar, A_E);
const uword offset_in_words =
static_cast<uword>(Context::variable_offset(rE) / kWordSize);
ContextPtr instance = static_cast<ContextPtr>(SP[-1]);
ObjectPtr value = static_cast<ContextPtr>(SP[0]);
SP -= 2; // Drop instance and value.
ASSERT(rE < static_cast<uint32_t>(instance->ptr()->num_variables_));
instance->ptr()->StorePointer(
reinterpret_cast<ObjectPtr*>(instance->ptr()) + offset_in_words, value,
thread);
DISPATCH();
}
{
BYTECODE(LoadFieldTOS, D);
#if defined(DEBUG)
// Currently only used to load closure fields, which are not unboxed.
// If used for general field, code for copying the mutable box must be
// added.
FieldPtr field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
const bool unboxing =
(field->ptr()->is_nullable_ != kNullCid) &&
Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
ASSERT(!unboxing);
#endif
const uword offset_in_words =
static_cast<uword>(Smi::Value(RAW_CAST(Smi, LOAD_CONSTANT(rD))));
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(instance->ptr())[offset_in_words];
DISPATCH();
}
{
BYTECODE(LoadTypeArgumentsField, D);
const uword offset_in_words =
static_cast<uword>(Smi::Value(RAW_CAST(Smi, LOAD_CONSTANT(rD))));
InstancePtr instance = static_cast<InstancePtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(instance->ptr())[offset_in_words];
DISPATCH();
}
{
BYTECODE(LoadContextParent, 0);
const uword offset_in_words =
static_cast<uword>(Context::parent_offset() / kWordSize);
ContextPtr instance = static_cast<ContextPtr>(SP[0]);
SP[0] = reinterpret_cast<ObjectPtr*>(instance->ptr())[offset_in_words];
DISPATCH();
}
{
BYTECODE(LoadContextVar, A_E);
const uword offset_in_words =
static_cast<uword>(Context::variable_offset(rE) / kWordSize);
ContextPtr instance = static_cast<ContextPtr>(SP[0]);
ASSERT(rE < static_cast<uint32_t>(instance->ptr()->num_variables_));
SP[0] = reinterpret_cast<ObjectPtr*>(instance->ptr())[offset_in_words];
DISPATCH();
}
{
BYTECODE(AllocateContext, A_E);
++SP;
const uint32_t num_context_variables = rE;
if (!AllocateContext(thread, num_context_variables, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
DISPATCH();
}
{
BYTECODE(CloneContext, A_E);
{
SP[1] = SP[0]; // Context to clone.
Exit(thread, FP, SP + 2, pc);
INVOKE_RUNTIME(DRT_CloneContext, NativeArguments(thread, 1, SP + 1, SP));
}
DISPATCH();
}
{
BYTECODE(Allocate, D);
ClassPtr cls = Class::RawCast(LOAD_CONSTANT(rD));
if (LIKELY(InterpreterHelpers::IsAllocateFinalized(cls))) {
const intptr_t class_id = cls->ptr()->id_;
const intptr_t instance_size = cls->ptr()->host_instance_size_in_words_
<< kWordSizeLog2;
ObjectPtr result;
if (TryAllocate(thread, class_id, instance_size, &result)) {
uword start = ObjectLayout::ToAddr(result);
for (intptr_t offset = sizeof(InstanceLayout); offset < instance_size;
offset += kWordSize) {
*reinterpret_cast<ObjectPtr*>(start + offset) = null_value;
}
*++SP = result;
DISPATCH();
}
}
SP[1] = 0; // Space for the result.
SP[2] = cls; // Class object.
SP[3] = null_value; // Type arguments.
Exit(thread, FP, SP + 4, pc);
INVOKE_RUNTIME(DRT_AllocateObject,
NativeArguments(thread, 2, SP + 2, SP + 1));
SP++; // Result is in SP[1].
DISPATCH();
}
{
BYTECODE(AllocateT, 0);
ClassPtr cls = Class::RawCast(SP[0]);
TypeArgumentsPtr type_args = TypeArguments::RawCast(SP[-1]);
if (LIKELY(InterpreterHelpers::IsAllocateFinalized(cls))) {
const intptr_t class_id = cls->ptr()->id_;
const intptr_t instance_size = cls->ptr()->host_instance_size_in_words_
<< kWordSizeLog2;
ObjectPtr result;
if (TryAllocate(thread, class_id, instance_size, &result)) {
uword start = ObjectLayout::ToAddr(result);
for (intptr_t offset = sizeof(InstanceLayout); offset < instance_size;
offset += kWordSize) {
*reinterpret_cast<ObjectPtr*>(start + offset) = null_value;
}
const intptr_t type_args_offset =
cls->ptr()->host_type_arguments_field_offset_in_words_
<< kWordSizeLog2;
*reinterpret_cast<ObjectPtr*>(start + type_args_offset) = type_args;
*--SP = result;
DISPATCH();
}
}
SP[1] = cls;
SP[2] = type_args;
Exit(thread, FP, SP + 3, pc);
INVOKE_RUNTIME(DRT_AllocateObject,
NativeArguments(thread, 2, SP + 1, SP - 1));
SP -= 1; // Result is in SP - 1.
DISPATCH();
}
{
BYTECODE(CreateArrayTOS, 0);
TypeArgumentsPtr type_args = TypeArguments::RawCast(SP[-1]);
ObjectPtr length = SP[0];
SP--;
if (!AllocateArray(thread, type_args, length, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
DISPATCH();
}
{
BYTECODE(AssertAssignable, A_E);
// Stack: instance, type, instantiator type args, function type args, name
ObjectPtr* args = SP - 4;
const bool may_be_smi = (rA == 1);
const bool is_smi =
((static_cast<intptr_t>(args[0]) & kSmiTagMask) == kSmiTag);
const bool smi_ok = is_smi && may_be_smi;
if (!smi_ok && (args[0] != null_value)) {
SubtypeTestCachePtr cache =
static_cast<SubtypeTestCachePtr>(LOAD_CONSTANT(rE));
if (!AssertAssignable(thread, pc, FP, SP, args, cache)) {
HANDLE_EXCEPTION;
}
}
SP -= 4; // Instance remains on stack.
DISPATCH();
}
{
BYTECODE(AssertSubtype, 0);
ObjectPtr* args = SP - 4;
// TODO(kustermann): Implement fast case for common arguments.
// The arguments on the stack look like:
// args[0] instantiator type args
// args[1] function type args
// args[2] sub_type
// args[3] super_type
// args[4] name
// This is unused, since the negative case throws an exception.
SP++;
ObjectPtr* result_slot = SP;
Exit(thread, FP, SP + 1, pc);
INVOKE_RUNTIME(DRT_SubtypeCheck,
NativeArguments(thread, 5, args, result_slot));
// Drop result slot and all arguments.
SP -= 6;
DISPATCH();
}
{
BYTECODE(AssertBoolean, A);
ObjectPtr value = SP[0];
if (rA != 0u) { // Should we perform type check?
if ((value == true_value) || (value == false_value)) {
goto AssertBooleanOk;
}
} else if (value != null_value) {
goto AssertBooleanOk;
}
// Assertion failed.
{
SP[1] = SP[0]; // instance
Exit(thread, FP, SP + 2, pc);
INVOKE_RUNTIME(DRT_NonBoolTypeError,
NativeArguments(thread, 1, SP + 1, SP));
}
AssertBooleanOk:
DISPATCH();
}
{
BYTECODE(Jump, T);
LOAD_JUMP_TARGET();
DISPATCH();
}
{
BYTECODE(JumpIfNoAsserts, T);
if (!thread->isolate()->asserts()) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfNotZeroTypeArgs, T);
if (InterpreterHelpers::ArgDescTypeArgsLen(argdesc_) != 0) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfEqStrict, T);
SP -= 2;
if (SP[1] == SP[2]) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfNeStrict, T);
SP -= 2;
if (SP[1] != SP[2]) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfTrue, T);
SP -= 1;
if (SP[1] == true_value) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfFalse, T);
SP -= 1;
if (SP[1] == false_value) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfNull, T);
SP -= 1;
if (SP[1] == null_value) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfNotNull, T);
SP -= 1;
if (SP[1] != null_value) {
LOAD_JUMP_TARGET();
}
DISPATCH();
}
{
BYTECODE(JumpIfUnchecked, T);
// Interpreter is not tracking unchecked calls, so fall through to
// parameter type checks.
DISPATCH();
}
{
BYTECODE(StoreIndexedTOS, 0);
SP -= 3;
ArrayPtr array = RAW_CAST(Array, SP[1]);
SmiPtr index = RAW_CAST(Smi, SP[2]);
ObjectPtr value = SP[3];
ASSERT(InterpreterHelpers::CheckIndex(index, array->ptr()->length_));
array->ptr()->StorePointer(array->ptr()->data() + Smi::Value(index), value,
thread);
DISPATCH();
}
{
BYTECODE(EqualsNull, 0);
DEBUG_CHECK;
SP[0] = (SP[0] == null_value) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(NullCheck, D);
if (UNLIKELY(SP[0] == null_value)) {
// Load selector.
SP[0] = LOAD_CONSTANT(rD);
goto ThrowNullError;
}
SP -= 1;
DISPATCH();
}
{
BYTECODE(NegateInt, 0);
DEBUG_CHECK;
UNBOX_INT64(value, SP[0], Symbols::UnaryMinus());
int64_t result = Utils::SubWithWrapAround(0, value);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(AddInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Plus());
UNBOX_INT64(b, SP[1], Symbols::Plus());
int64_t result = Utils::AddWithWrapAround(a, b);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(SubInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Minus());
UNBOX_INT64(b, SP[1], Symbols::Minus());
int64_t result = Utils::SubWithWrapAround(a, b);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(MulInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Star());
UNBOX_INT64(b, SP[1], Symbols::Star());
int64_t result = Utils::MulWithWrapAround(a, b);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(TruncDivInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::TruncDivOperator());
UNBOX_INT64(b, SP[1], Symbols::TruncDivOperator());
if (UNLIKELY(b == 0)) {
goto ThrowIntegerDivisionByZeroException;
}
int64_t result;
if (UNLIKELY((a == Mint::kMinValue) && (b == -1))) {
result = Mint::kMinValue;
} else {
result = a / b;
}
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(ModInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Percent());
UNBOX_INT64(b, SP[1], Symbols::Percent());
if (UNLIKELY(b == 0)) {
goto ThrowIntegerDivisionByZeroException;
}
int64_t result;
if (UNLIKELY((a == Mint::kMinValue) && (b == -1))) {
result = 0;
} else {
result = a % b;
if (result < 0) {
if (b < 0) {
result -= b;
} else {
result += b;
}
}
}
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(BitAndInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Ampersand());
UNBOX_INT64(b, SP[1], Symbols::Ampersand());
int64_t result = a & b;
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(BitOrInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::BitOr());
UNBOX_INT64(b, SP[1], Symbols::BitOr());
int64_t result = a | b;
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(BitXorInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::Caret());
UNBOX_INT64(b, SP[1], Symbols::Caret());
int64_t result = a ^ b;
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(ShlInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::LeftShiftOperator());
UNBOX_INT64(b, SP[1], Symbols::LeftShiftOperator());
if (b < 0) {
SP[0] = SP[1];
goto ThrowArgumentError;
}
int64_t result = Utils::ShiftLeftWithTruncation(a, b);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(ShrInt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::RightShiftOperator());
UNBOX_INT64(b, SP[1], Symbols::RightShiftOperator());
if (b < 0) {
SP[0] = SP[1];
goto ThrowArgumentError;
}
int64_t result = a >> Utils::Minimum<int64_t>(b, Mint::kBits);
BOX_INT64_RESULT(result);
DISPATCH();
}
{
BYTECODE(CompareIntEq, 0);
DEBUG_CHECK;
SP -= 1;
if (SP[0] == SP[1]) {
SP[0] = true_value;
} else if (!SP[0]->IsHeapObject() || !SP[1]->IsHeapObject() ||
(SP[0] == null_value) || (SP[1] == null_value)) {
SP[0] = false_value;
} else {
int64_t a = Integer::GetInt64Value(RAW_CAST(Integer, SP[0]));
int64_t b = Integer::GetInt64Value(RAW_CAST(Integer, SP[1]));
SP[0] = (a == b) ? true_value : false_value;
}
DISPATCH();
}
{
BYTECODE(CompareIntGt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::RAngleBracket());
UNBOX_INT64(b, SP[1], Symbols::RAngleBracket());
SP[0] = (a > b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareIntLt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::LAngleBracket());
UNBOX_INT64(b, SP[1], Symbols::LAngleBracket());
SP[0] = (a < b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareIntGe, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::GreaterEqualOperator());
UNBOX_INT64(b, SP[1], Symbols::GreaterEqualOperator());
SP[0] = (a >= b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareIntLe, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_INT64(a, SP[0], Symbols::LessEqualOperator());
UNBOX_INT64(b, SP[1], Symbols::LessEqualOperator());
SP[0] = (a <= b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(NegateDouble, 0);
DEBUG_CHECK;
UNBOX_DOUBLE(value, SP[0], Symbols::UnaryMinus());
double result = -value;
BOX_DOUBLE_RESULT(result);
DISPATCH();
}
{
BYTECODE(AddDouble, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::Plus());
UNBOX_DOUBLE(b, SP[1], Symbols::Plus());
double result = a + b;
BOX_DOUBLE_RESULT(result);
DISPATCH();
}
{
BYTECODE(SubDouble, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::Minus());
UNBOX_DOUBLE(b, SP[1], Symbols::Minus());
double result = a - b;
BOX_DOUBLE_RESULT(result);
DISPATCH();
}
{
BYTECODE(MulDouble, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::Star());
UNBOX_DOUBLE(b, SP[1], Symbols::Star());
double result = a * b;
BOX_DOUBLE_RESULT(result);
DISPATCH();
}
{
BYTECODE(DivDouble, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::Slash());
UNBOX_DOUBLE(b, SP[1], Symbols::Slash());
double result = a / b;
BOX_DOUBLE_RESULT(result);
DISPATCH();
}
{
BYTECODE(CompareDoubleEq, 0);
DEBUG_CHECK;
SP -= 1;
if ((SP[0] == null_value) || (SP[1] == null_value)) {
SP[0] = (SP[0] == SP[1]) ? true_value : false_value;
} else {
double a = Double::RawCast(SP[0])->ptr()->value_;
double b = Double::RawCast(SP[1])->ptr()->value_;
SP[0] = (a == b) ? true_value : false_value;
}
DISPATCH();
}
{
BYTECODE(CompareDoubleGt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::RAngleBracket());
UNBOX_DOUBLE(b, SP[1], Symbols::RAngleBracket());
SP[0] = (a > b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareDoubleLt, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::LAngleBracket());
UNBOX_DOUBLE(b, SP[1], Symbols::LAngleBracket());
SP[0] = (a < b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareDoubleGe, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::GreaterEqualOperator());
UNBOX_DOUBLE(b, SP[1], Symbols::GreaterEqualOperator());
SP[0] = (a >= b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(CompareDoubleLe, 0);
DEBUG_CHECK;
SP -= 1;
UNBOX_DOUBLE(a, SP[0], Symbols::LessEqualOperator());
UNBOX_DOUBLE(b, SP[1], Symbols::LessEqualOperator());
SP[0] = (a <= b) ? true_value : false_value;
DISPATCH();
}
{
BYTECODE(AllocateClosure, D);
++SP;
if (!AllocateClosure(thread, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
DISPATCH();
}
{
BYTECODE_ENTRY_LABEL(Trap);
#define UNIMPLEMENTED_LABEL_ORDN(Name)
#define UNIMPLEMENTED_LABEL_WIDE(Name)
#define UNIMPLEMENTED_LABEL_RESV(Name) BYTECODE_ENTRY_LABEL(Name)
#define UNIMPLEMENTED_LABEL(name, encoding, kind, op1, op2, op3) \
UNIMPLEMENTED_LABEL_##kind(name)
KERNEL_BYTECODES_LIST(UNIMPLEMENTED_LABEL)
#undef UNIMPLEMENTED_LABEL_ORDN
#undef UNIMPLEMENTED_LABEL_WIDE
#undef UNIMPLEMENTED_LABEL_RESV
#undef UNIMPLEMENTED_LABEL
UNIMPLEMENTED();
DISPATCH();
}
{
BYTECODE(VMInternal_ImplicitGetter, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kImplicitGetter);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
// Field object is cached in function's data_.
FieldPtr field = static_cast<FieldPtr>(function->ptr()->data_);
intptr_t offset_in_words =
Smi::Value(field->ptr()->host_offset_or_field_id_);
const intptr_t kArgc = 1;
InstancePtr instance =
static_cast<InstancePtr>(FrameArguments(FP, kArgc)[0]);
InstancePtr value =
reinterpret_cast<InstancePtr*>(instance->ptr())[offset_in_words];
if (UNLIKELY(value == Object::sentinel().raw())) {
SP[1] = 0; // Result slot.
SP[2] = instance;
SP[3] = field;
Exit(thread, FP, SP + 4, pc);
INVOKE_RUNTIME(
DRT_InitInstanceField,
NativeArguments(thread, 2, /* argv */ SP + 2, /* ret val */ SP + 1));
function = FrameFunction(FP);
instance = static_cast<InstancePtr>(SP[2]);
field = static_cast<FieldPtr>(SP[3]);
offset_in_words = Smi::Value(field->ptr()->host_offset_or_field_id_);
value = reinterpret_cast<InstancePtr*>(instance->ptr())[offset_in_words];
}
*++SP = value;
#if !defined(PRODUCT)
if (UNLIKELY(Field::NeedsLoadGuardBit::decode(field->ptr()->kind_bits_))) {
if (!AssertAssignableField<true>(thread, pc, FP, SP, instance, field,
value)) {
HANDLE_EXCEPTION;
}
// Reload objects after the call which may trigger GC.
field = static_cast<FieldPtr>(FrameFunction(FP)->ptr()->data_);
instance = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[0]);
value = reinterpret_cast<InstancePtr*>(instance->ptr())[offset_in_words];
}
#endif
const bool unboxing =
(field->ptr()->is_nullable_ != kNullCid) &&
Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
classid_t guarded_cid = field->ptr()->guarded_cid_;
if (unboxing && (guarded_cid == kDoubleCid) && supports_unboxed_doubles_) {
ASSERT(FlowGraphCompiler::SupportsUnboxedDoubles());
double raw_value = Double::RawCast(value)->ptr()->value_;
// AllocateDouble places result at SP[0]
if (!AllocateDouble(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
} else if (unboxing && (guarded_cid == kFloat32x4Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
// AllocateFloat32x4 places result at SP[0]
if (!AllocateFloat32x4(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
} else if (unboxing && (guarded_cid == kFloat64x2Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
// AllocateFloat64x2 places result at SP[0]
if (!AllocateFloat64x2(thread, raw_value, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(VMInternal_ImplicitSetter, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kImplicitSetter);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
// Field object is cached in function's data_.
FieldPtr field = static_cast<FieldPtr>(function->ptr()->data_);
intptr_t offset_in_words =
Smi::Value(field->ptr()->host_offset_or_field_id_);
const intptr_t kArgc = 2;
InstancePtr instance =
static_cast<InstancePtr>(FrameArguments(FP, kArgc)[0]);
InstancePtr value = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[1]);
if (!AssertAssignableField<false>(thread, pc, FP, SP, instance, field,
value)) {
HANDLE_EXCEPTION;
}
// Reload objects after the call which may trigger GC.
field = static_cast<FieldPtr>(FrameFunction(FP)->ptr()->data_);
instance = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[0]);
value = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[1]);
if (InterpreterHelpers::FieldNeedsGuardUpdate(field, value)) {
SP[1] = 0; // Unused result of runtime call.
SP[2] = field;
SP[3] = value;
Exit(thread, FP, SP + 4, pc);
if (!InvokeRuntime(thread, this, DRT_UpdateFieldCid,
NativeArguments(thread, 2, /* argv */ SP + 2,
/* retval */ SP + 1))) {
HANDLE_EXCEPTION;
}
// Reload objects after the call which may trigger GC.
field = static_cast<FieldPtr>(FrameFunction(FP)->ptr()->data_);
instance = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[0]);
value = static_cast<InstancePtr>(FrameArguments(FP, kArgc)[1]);
}
const bool unboxing =
(field->ptr()->is_nullable_ != kNullCid) &&
Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
classid_t guarded_cid = field->ptr()->guarded_cid_;
if (unboxing && (guarded_cid == kDoubleCid) && supports_unboxed_doubles_) {
double raw_value = Double::RawCast(value)->ptr()->value_;
DoublePtr box =
*(reinterpret_cast<DoublePtr*>(instance->ptr()) + offset_in_words);
ASSERT(box != null_value); // Non-initializing store.
box->ptr()->value_ = raw_value;
} else if (unboxing && (guarded_cid == kFloat32x4Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
Float32x4Ptr box =
*(reinterpret_cast<Float32x4Ptr*>(instance->ptr()) + offset_in_words);
ASSERT(box != null_value); // Non-initializing store.
raw_value.writeTo(box->ptr()->value_);
} else if (unboxing && (guarded_cid == kFloat64x2Cid) &&
supports_unboxed_simd128_) {
simd128_value_t raw_value;
raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
Float64x2Ptr box =
*(reinterpret_cast<Float64x2Ptr*>(instance->ptr()) + offset_in_words);
ASSERT(box != null_value); // Non-initializing store.
raw_value.writeTo(box->ptr()->value_);
} else {
instance->ptr()->StorePointer(
reinterpret_cast<InstancePtr*>(instance->ptr()) + offset_in_words,
value, thread);
}
*++SP = null_value;
DISPATCH();
}
{
BYTECODE(VMInternal_ImplicitStaticGetter, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kImplicitStaticGetter);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
// Field object is cached in function's data_.
FieldPtr field = static_cast<FieldPtr>(function->ptr()->data_);
intptr_t field_id = Smi::Value(field->ptr()->host_offset_or_field_id_);
InstancePtr value = thread->field_table_values()[field_id];
if (value == Object::sentinel().raw() ||
value == Object::transition_sentinel().raw()) {
SP[1] = 0; // Unused result of invoking the initializer.
SP[2] = field;
Exit(thread, FP, SP + 3, pc);
INVOKE_RUNTIME(DRT_InitStaticField,
NativeArguments(thread, 1, SP + 2, SP + 1));
// Reload objects after the call which may trigger GC.
function = FrameFunction(FP);
field = static_cast<FieldPtr>(function->ptr()->data_);
// The field is initialized by the runtime call, but not returned.
intptr_t field_id = Smi::Value(field->ptr()->host_offset_or_field_id_);
value = thread->field_table_values()[field_id];
}
// Field was initialized. Return its value.
*++SP = value;
#if !defined(PRODUCT)
if (UNLIKELY(Field::NeedsLoadGuardBit::decode(field->ptr()->kind_bits_))) {
if (!AssertAssignableField<true>(thread, pc, FP, SP,
static_cast<InstancePtr>(null_value),
field, value)) {
HANDLE_EXCEPTION;
}
}
#endif
DISPATCH();
}
{
BYTECODE(VMInternal_MethodExtractor, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kMethodExtractor);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
ASSERT(InterpreterHelpers::ArgDescTypeArgsLen(argdesc_) == 0);
++SP;
if (!AllocateClosure(thread, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
++SP;
if (!AllocateContext(thread, 1, pc, FP, SP)) {
HANDLE_EXCEPTION;
}
ContextPtr context = Context::RawCast(*SP--);
InstancePtr instance = Instance::RawCast(FrameArguments(FP, 1)[0]);
context->ptr()->StorePointer(
reinterpret_cast<InstancePtr*>(&context->ptr()->data()[0]), instance);
ClosurePtr closure = Closure::RawCast(*SP);
closure->ptr()->StorePointer(
&closure->ptr()->instantiator_type_arguments_,
InterpreterHelpers::GetTypeArguments(thread, instance));
// function_type_arguments_ is already null
closure->ptr()->delayed_type_arguments_ =
Object::empty_type_arguments().raw();
closure->ptr()->StorePointer(
&closure->ptr()->function_,
Function::RawCast(FrameFunction(FP)->ptr()->data_));
closure->ptr()->StorePointer(&closure->ptr()->context_, context);
// hash_ is already null
DISPATCH();
}
{
BYTECODE(VMInternal_InvokeClosure, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kInvokeFieldDispatcher);
const bool is_dynamic_call =
Function::IsDynamicInvocationForwarderName(function->ptr()->name_);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
ClosurePtr receiver =
Closure::RawCast(FrameArguments(FP, argc)[receiver_idx]);
SP[1] = receiver->ptr()->function_;
if (is_dynamic_call) {
{
SP[2] = null_value;
SP[3] = receiver;
SP[4] = argdesc_;
Exit(thread, FP, SP + 5, pc);
if (!InvokeRuntime(thread, this, DRT_ClosureArgumentsValid,
NativeArguments(thread, 2, SP + 3, SP + 2))) {
HANDLE_EXCEPTION;
}
receiver = Closure::RawCast(SP[3]);
argdesc_ = Array::RawCast(SP[4]);
}
if (SP[2] != Bool::True().raw()) {
goto NoSuchMethodFromPrologue;
}
// TODO(dartbug.com/40813): Move other checks that are currently
// compiled in the closure body to here as they are also moved to
// FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher.
}
goto TailCallSP1;
}
{
BYTECODE(VMInternal_InvokeField, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kInvokeFieldDispatcher);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
ObjectPtr receiver = FrameArguments(FP, argc)[receiver_idx];
// Possibly demangle field name and invoke field getter on receiver.
{
SP[1] = argdesc_; // Save argdesc_.
SP[2] = 0; // Result of runtime call.
SP[3] = receiver; // Receiver.
SP[4] = function->ptr()->name_; // Field name (may change during call).
Exit(thread, FP, SP + 5, pc);
if (!InvokeRuntime(thread, this, DRT_GetFieldForDispatch,
NativeArguments(thread, 2, SP + 3, SP + 2))) {
HANDLE_EXCEPTION;
}
function = FrameFunction(FP);
argdesc_ = Array::RawCast(SP[1]);
}
// If the field name in the arguments is different after the call, then
// this was a dynamic call.
StringPtr field_name = String::RawCast(SP[4]);
const bool is_dynamic_call = function->ptr()->name_ != field_name;
// Replace receiver with field value, keep all other arguments, and
// invoke 'call' function, or if not found, invoke noSuchMethod.
FrameArguments(FP, argc)[receiver_idx] = receiver = SP[2];
// If the field value is a closure, no need to resolve 'call' function.
if (InterpreterHelpers::GetClassId(receiver) == kClosureCid) {
SP[1] = Closure::RawCast(receiver)->ptr()->function_;
if (is_dynamic_call) {
{
SP[2] = null_value;
SP[3] = receiver;
SP[4] = argdesc_;
Exit(thread, FP, SP + 5, pc);
if (!InvokeRuntime(thread, this, DRT_ClosureArgumentsValid,
NativeArguments(thread, 2, SP + 3, SP + 2))) {
HANDLE_EXCEPTION;
}
receiver = SP[3];
argdesc_ = Array::RawCast(SP[4]);
}
if (SP[2] != Bool::True().raw()) {
goto NoSuchMethodFromPrologue;
}
// TODO(dartbug.com/40813): Move other checks that are currently
// compiled in the closure body to here as they are also moved to
// FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher.
}
goto TailCallSP1;
}
// Otherwise, call runtime to resolve 'call' function.
{
SP[1] = 0; // Result slot.
SP[2] = receiver;
SP[3] = argdesc_;
Exit(thread, FP, SP + 4, pc);
if (!InvokeRuntime(thread, this, DRT_ResolveCallFunction,
NativeArguments(thread, 2, SP + 2, SP + 1))) {
HANDLE_EXCEPTION;
}
argdesc_ = Array::RawCast(SP[3]);
function = Function::RawCast(SP[1]);
receiver = SP[2];
}
if (function != Function::null()) {
SP[1] = function;
goto TailCallSP1;
}
// Function 'call' could not be resolved for argdesc_.
// Invoke noSuchMethod.
SP[1] = null_value;
SP[2] = receiver;
SP[3] = Symbols::Call().raw(); // We failed to resolve the 'call' function.
SP[4] = argdesc_;
SP[5] = null_value; // Array of arguments (will be filled).
// Allocate array of arguments.
{
SP[6] = Smi::New(argc); // length
SP[7] = null_value; // type
Exit(thread, FP, SP + 8, pc);
if (!InvokeRuntime(thread, this, DRT_AllocateArray,
NativeArguments(thread, 2, SP + 6, SP + 5))) {
HANDLE_EXCEPTION;
}
}
// Copy arguments into the newly allocated array.
ObjectPtr* argv = FrameArguments(FP, argc);
ArrayPtr array = static_cast<ArrayPtr>(SP[5]);
ASSERT(array->GetClassId() == kArrayCid);
for (intptr_t i = 0; i < argc; i++) {
array->ptr()->data()[i] = argv[i];
}
// Invoke noSuchMethod passing down receiver, target name, argument
// descriptor, and array of arguments.
{
Exit(thread, FP, SP + 6, pc);
if (!InvokeRuntime(thread, this, DRT_InvokeNoSuchMethod,
NativeArguments(thread, 4, SP + 2, SP + 1))) {
HANDLE_EXCEPTION;
}
++SP; // Result at SP[0]
}
DISPATCH();
}
{
BYTECODE(VMInternal_ForwardDynamicInvocation, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) ==
FunctionLayout::kDynamicInvocationForwarder);
BUMP_USAGE_COUNTER_ON_ENTRY(function);
ArrayPtr checks = Array::RawCast(function->ptr()->data_);
FunctionPtr target = Function::RawCast(checks->ptr()->data()[0]);
ASSERT(Function::kind(target) !=
FunctionLayout::kDynamicInvocationForwarder);
BytecodePtr target_bytecode = target->ptr()->bytecode_;
ASSERT(target_bytecode != Bytecode::null());
ASSERT(target_bytecode->IsBytecode());
const KBCInstr* pc2 = reinterpret_cast<const KBCInstr*>(
target_bytecode->ptr()->instructions_);
if (KernelBytecode::IsEntryOptionalOpcode(pc2)) {
pp_ = target_bytecode->ptr()->object_pool_;
uint32_t rA, rB, rC;
rA = KernelBytecode::DecodeA(pc2);
rB = KernelBytecode::DecodeB(pc2);
rC = KernelBytecode::DecodeC(pc2);
pc2 = KernelBytecode::Next(pc2);
if (!CopyParameters(thread, &pc2, &FP, &SP, rA, rB, rC)) {
SP[1] = function;
goto NoSuchMethodFromPrologue;
}
}
intptr_t len = Smi::Value(checks->ptr()->length_);
SP[1] = checks;
SP[2] = argdesc_;
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
InstancePtr receiver =
Instance::RawCast(FrameArguments(FP, argc)[receiver_idx]);
SP[5] = InterpreterHelpers::GetTypeArguments(thread, receiver);
if (type_args_len > 0) {
SP[6] = FrameArguments(FP, argc)[0];
} else {
SP[6] = TypeArguments::RawCast(checks->ptr()->data()[1]);
// TODO(regis): Verify this condition; why test SP[6]?
if (SP[5] != null_value && SP[6] != null_value) {
SP[7] = SP[6]; // type_arguments
SP[8] = SP[5]; // instantiator_type_args
SP[9] = null_value; // function_type_args
Exit(thread, FP, SP + 10, pc);
INVOKE_RUNTIME(DRT_InstantiateTypeArguments,
NativeArguments(thread, 3, SP + 7, SP + 7));
SP[6] = SP[7];
}
}
for (intptr_t i = 2; i < len; i++) {
ParameterTypeCheckPtr check =
ParameterTypeCheck::RawCast(checks->ptr()->data()[i]);
if (LIKELY(check->ptr()->index_ != 0)) {
ASSERT(&FP[check->ptr()->index_] <= SP);
SP[3] = Instance::RawCast(FP[check->ptr()->index_]);
// TODO(regis): Revisit null handling once interpreter supports NNBD.
if (SP[3] == null_value) {
continue; // Not handled by AssertAssignable for some reason...
}
SP[4] = check->ptr()->type_or_bound_;
// SP[5]: Instantiator type args.
// SP[6]: Function type args.
SP[7] = check->ptr()->name_;
if (!AssertAssignable(thread, pc, FP, SP + 7, SP + 3,
check->ptr()->cache_)) {
HANDLE_EXCEPTION;
}
} else {
SP[3] = 0;
SP[4] = 0;
// SP[5]: Instantiator type args.
// SP[6]: Function type args.
SP[7] = check->ptr()->param_;
SP[8] = check->ptr()->type_or_bound_;
SP[9] = check->ptr()->name_;
SP[10] = 0;
Exit(thread, FP, SP + 11, pc);
INVOKE_RUNTIME(DRT_SubtypeCheck,
NativeArguments(thread, 5, SP + 5, SP + 10));
}
checks = Array::RawCast(SP[1]); // Reload after runtime call.
}
target = Function::RawCast(checks->ptr()->data()[0]);
argdesc_ = Array::RawCast(SP[2]);
SP = FP - 1; // Unmarshall optional parameters.
SP[1] = target;
goto TailCallSP1;
}
{
BYTECODE(VMInternal_NoSuchMethodDispatcher, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) == FunctionLayout::kNoSuchMethodDispatcher);
SP[1] = function;
goto NoSuchMethodFromPrologue;
}
{
BYTECODE(VMInternal_ImplicitStaticClosure, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) ==
FunctionLayout::kImplicitClosureFunction);
UNIMPLEMENTED();
DISPATCH();
}
{
BYTECODE(VMInternal_ImplicitInstanceClosure, 0);
FunctionPtr function = FrameFunction(FP);
ASSERT(Function::kind(function) ==
FunctionLayout::kImplicitClosureFunction);
UNIMPLEMENTED();
DISPATCH();
}
{
TailCallSP1:
FunctionPtr function = Function::RawCast(SP[1]);
for (;;) {
if (Function::HasBytecode(function)) {
ASSERT(function->IsFunction());
BytecodePtr bytecode = function->ptr()->bytecode_;
ASSERT(bytecode->IsBytecode());
FP[kKBCFunctionSlotFromFp] = function;
FP[kKBCPcMarkerSlotFromFp] = bytecode;
pp_ = bytecode->ptr()->object_pool_;
pc = reinterpret_cast<const KBCInstr*>(bytecode->ptr()->instructions_);
NOT_IN_PRODUCT(pc_ = pc); // For the profiler.
DISPATCH();
}
if (Function::HasCode(function)) {
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
ObjectPtr* argv = FrameArguments(FP, argc);
for (intptr_t i = 0; i < argc; i++) {
*++SP = argv[i];
}
ObjectPtr* call_base = SP - argc + 1;
ObjectPtr* call_top = SP + 1;
call_top[0] = function;
if (!InvokeCompiled(thread, function, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
} else {
HANDLE_RETURN;
}
DISPATCH();
}
// Compile the function to either generate code or load bytecode.
SP[1] = argdesc_;
SP[2] = 0; // Code result.
SP[3] = function;
Exit(thread, FP, SP + 4, pc);
if (!InvokeRuntime(thread, this, DRT_CompileFunction,
NativeArguments(thread, 1, /* argv */ SP + 3,
/* retval */ SP + 2))) {
HANDLE_EXCEPTION;
}
function = Function::RawCast(SP[3]);
argdesc_ = Array::RawCast(SP[1]);
ASSERT(Function::HasCode(function) || Function::HasBytecode(function));
}
}
// Helper used to handle noSuchMethod on closures. The function should be
// placed into SP[1] before jumping here, similar to TailCallSP1.
{
NoSuchMethodFromPrologue:
FunctionPtr function = Function::RawCast(SP[1]);
const intptr_t type_args_len =
InterpreterHelpers::ArgDescTypeArgsLen(argdesc_);
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
const intptr_t argc =
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
ObjectPtr* args = FrameArguments(FP, argc);
SP[1] = null_value;
SP[2] = args[receiver_idx];
SP[3] = function;
SP[4] = argdesc_;
SP[5] = null_value; // Array of arguments (will be filled).
// Allocate array of arguments.
{
SP[6] = Smi::New(argc); // length
SP[7] = null_value; // type
Exit(thread, FP, SP + 8, pc);
if (!InvokeRuntime(thread, this, DRT_AllocateArray,
NativeArguments(thread, 2, SP + 6, SP + 5))) {
HANDLE_EXCEPTION;
}
// Copy arguments into the newly allocated array.
ArrayPtr array = static_cast<ArrayPtr>(SP[5]);
ASSERT(array->GetClassId() == kArrayCid);
for (intptr_t i = 0; i < argc; i++) {
array->ptr()->data()[i] = args[i];
}
}
// Invoke noSuchMethod passing down receiver, function, argument descriptor
// and array of arguments.
{
Exit(thread, FP, SP + 6, pc);
INVOKE_RUNTIME(DRT_NoSuchMethodFromPrologue,
NativeArguments(thread, 4, SP + 2, SP + 1));
++SP; // Result at SP[0]
}
DISPATCH();
}
{
ThrowNullError:
// SP[0] contains selector.
SP[1] = 0; // Unused space for result.
Exit(thread, FP, SP + 2, pc);
INVOKE_RUNTIME(DRT_NullErrorWithSelector,
NativeArguments(thread, 1, SP, SP + 1));
UNREACHABLE();
}
{
ThrowIntegerDivisionByZeroException:
SP[0] = 0; // Unused space for result.
Exit(thread, FP, SP + 1, pc);
INVOKE_RUNTIME(DRT_IntegerDivisionByZeroException,
NativeArguments(thread, 0, SP, SP));
UNREACHABLE();
}
{
ThrowArgumentError:
// SP[0] contains value.
SP[1] = 0; // Unused space for result.
Exit(thread, FP, SP + 2, pc);
INVOKE_RUNTIME(DRT_ArgumentError, NativeArguments(thread, 1, SP, SP + 1));
UNREACHABLE();
}
// Exception handling helper. Gets handler FP and PC from the Interpreter
// where they were stored by Interpreter::Longjmp and proceeds to execute the
// handler. Corner case: handler PC can be a fake marker that marks entry
// frame, which means exception was not handled in the interpreter. In this
// case we return the caught exception from Interpreter::Call.
{
HandleException:
FP = fp_;
pc = pc_;
if (IsEntryFrameMarker(pc)) {
pp_ = static_cast<ObjectPoolPtr>(fp_[kKBCSavedPpSlotFromEntryFp]);
argdesc_ = static_cast<ArrayPtr>(fp_[kKBCSavedArgDescSlotFromEntryFp]);
uword exit_fp = static_cast<uword>(fp_[kKBCExitLinkSlotFromEntryFp]);
thread->set_top_exit_frame_info(exit_fp);
thread->set_top_resource(top_resource);
thread->set_vm_tag(vm_tag);
#if defined(DEBUG)
if (IsTracingExecution()) {
THR_Print("%" Pu64 " ", icount_);
THR_Print("Returning exception from interpreter 0x%" Px " at fp_ 0x%" Px
" exit 0x%" Px "\n",
reinterpret_cast<uword>(this), reinterpret_cast<uword>(fp_),
exit_fp);
}
#endif
ASSERT(HasFrame(reinterpret_cast<uword>(fp_)));
return special_[KernelBytecode::kExceptionSpecialIndex];
}
pp_ = InterpreterHelpers::FrameBytecode(FP)->ptr()->object_pool_;
DISPATCH();
}
UNREACHABLE();
return 0;
}
void Interpreter::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
// Walk over all setjmp buffers (simulated --> C++ transitions)
// and try to find the setjmp associated with the simulated frame pointer.
InterpreterSetjmpBuffer* buf = last_setjmp_buffer();
while ((buf->link() != NULL) && (buf->link()->fp() > fp)) {
buf = buf->link();
}
ASSERT(buf != NULL);
ASSERT(last_setjmp_buffer() == buf);
// The C++ caller has not cleaned up the stack memory of C++ frames.
// Prepare for unwinding frames by destroying all the stack resources
// in the previous C++ frames.
StackResource::Unwind(thread);
fp_ = reinterpret_cast<ObjectPtr*>(fp);
if (pc == StubCode::RunExceptionHandler().EntryPoint()) {
// The RunExceptionHandler stub is a placeholder. We implement
// its behavior here.
ObjectPtr raw_exception = thread->active_exception();
ObjectPtr raw_stacktrace = thread->active_stacktrace();
ASSERT(raw_exception != Object::null());
thread->set_active_exception(Object::null_object());
thread->set_active_stacktrace(Object::null_object());
special_[KernelBytecode::kExceptionSpecialIndex] = raw_exception;
special_[KernelBytecode::kStackTraceSpecialIndex] = raw_stacktrace;
pc_ = reinterpret_cast<const KBCInstr*>(thread->resume_pc());
} else {
pc_ = reinterpret_cast<const KBCInstr*>(pc);
}
// Set the tag.
thread->set_vm_tag(VMTag::kDartInterpretedTagId);
// Clear top exit frame.
thread->set_top_exit_frame_info(0);
buf->Longjmp();
UNREACHABLE();
}
void Interpreter::VisitObjectPointers(ObjectPointerVisitor* visitor) {
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&pp_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&argdesc_));
}
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)