blob: d49260e56aa32d16716db9f1e169e8e05b03dd43 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Mterp entry point and support functions.
*/
#include "nterp.h"
#include "base/quasi_atomic.h"
#include "class_linker-inl.h"
#include "dex/dex_instruction_utils.h"
#include "debugger.h"
#include "entrypoints/entrypoint_utils-inl.h"
#include "interpreter/interpreter_cache-inl.h"
#include "interpreter/interpreter_common.h"
#include "interpreter/interpreter_intrinsics.h"
#include "interpreter/shadow_frame-inl.h"
#include "mirror/string-alloc-inl.h"
#include "nterp_helpers.h"
namespace art {
namespace interpreter {
bool IsNterpSupported() {
return !kPoisonHeapReferences && kUseReadBarrier;
}
bool CanRuntimeUseNterp() REQUIRES_SHARED(Locks::mutator_lock_) {
Runtime* runtime = Runtime::Current();
instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
// If the runtime is interpreter only, we currently don't use nterp as some
// parts of the runtime (like instrumentation) make assumption on an
// interpreter-only runtime to always be in a switch-like interpreter.
return IsNterpSupported() &&
!instr->InterpretOnly() &&
!runtime->IsAotCompiler() &&
!runtime->GetInstrumentation()->IsActive() &&
// nterp only knows how to deal with the normal exits. It cannot handle any of the
// non-standard force-returns.
!runtime->AreNonStandardExitsEnabled() &&
// An async exception has been thrown. We need to go to the switch interpreter. nterp doesn't
// know how to deal with these so we could end up never dealing with it if we are in an
// infinite loop.
!runtime->AreAsyncExceptionsThrown() &&
(runtime->GetJit() == nullptr || !runtime->GetJit()->JitAtFirstUse());
}
// The entrypoint for nterp, which ArtMethods can directly point to.
extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_);
const void* GetNterpEntryPoint() {
return reinterpret_cast<const void*>(interpreter::ExecuteNterpImpl);
}
/*
* Verify some constants used by the nterp interpreter.
*/
void CheckNterpAsmConstants() {
/*
* If we're using computed goto instruction transitions, make sure
* none of the handlers overflows the byte limit. This won't tell
* which one did, but if any one is too big the total size will
* overflow.
*/
const int width = kNterpHandlerSize;
ptrdiff_t interp_size = reinterpret_cast<uintptr_t>(artNterpAsmInstructionEnd) -
reinterpret_cast<uintptr_t>(artNterpAsmInstructionStart);
if ((interp_size == 0) || (interp_size != (art::kNumPackedOpcodes * width))) {
LOG(FATAL) << "ERROR: unexpected asm interp size " << interp_size
<< "(did an instruction handler exceed " << width << " bytes?)";
}
}
inline void UpdateHotness(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
// The hotness we will add to a method when we perform a
// field/method/class/string lookup.
constexpr uint16_t kNterpHotnessLookup = 0xf;
method->UpdateCounter(kNterpHotnessLookup);
}
template<typename T>
inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T value) {
DCHECK(kUseReadBarrier) << "Nterp only works with read barriers";
self->GetInterpreterCache()->Set(self, dex_pc_ptr, value);
}
template<typename T>
inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T* value) {
UpdateCache(self, dex_pc_ptr, reinterpret_cast<size_t>(value));
}
#ifdef __arm__
extern "C" void NterpStoreArm32Fprs(const char* shorty,
uint32_t* registers,
uint32_t* stack_args,
const uint32_t* fprs) {
// Note `shorty` has already the returned type removed.
ScopedAssertNoThreadSuspension sants("In nterp");
uint32_t arg_index = 0;
uint32_t fpr_double_index = 0;
uint32_t fpr_index = 0;
for (uint32_t shorty_index = 0; shorty[shorty_index] != '\0'; ++shorty_index) {
char arg_type = shorty[shorty_index];
switch (arg_type) {
case 'D': {
// Double should not overlap with float.
fpr_double_index = std::max(fpr_double_index, RoundUp(fpr_index, 2));
if (fpr_double_index < 16) {
registers[arg_index] = fprs[fpr_double_index++];
registers[arg_index + 1] = fprs[fpr_double_index++];
} else {
registers[arg_index] = stack_args[arg_index];
registers[arg_index + 1] = stack_args[arg_index + 1];
}
arg_index += 2;
break;
}
case 'F': {
if (fpr_index % 2 == 0) {
fpr_index = std::max(fpr_double_index, fpr_index);
}
if (fpr_index < 16) {
registers[arg_index] = fprs[fpr_index++];
} else {
registers[arg_index] = stack_args[arg_index];
}
arg_index++;
break;
}
case 'J': {
arg_index += 2;
break;
}
default: {
arg_index++;
break;
}
}
}
}
extern "C" void NterpSetupArm32Fprs(const char* shorty,
uint32_t dex_register,
uint32_t stack_index,
uint32_t* fprs,
uint32_t* registers,
uint32_t* stack_args) {
// Note `shorty` has already the returned type removed.
ScopedAssertNoThreadSuspension sants("In nterp");
uint32_t fpr_double_index = 0;
uint32_t fpr_index = 0;
for (uint32_t shorty_index = 0; shorty[shorty_index] != '\0'; ++shorty_index) {
char arg_type = shorty[shorty_index];
switch (arg_type) {
case 'D': {
// Double should not overlap with float.
fpr_double_index = std::max(fpr_double_index, RoundUp(fpr_index, 2));
if (fpr_double_index < 16) {
fprs[fpr_double_index++] = registers[dex_register++];
fprs[fpr_double_index++] = registers[dex_register++];
stack_index += 2;
} else {
stack_args[stack_index++] = registers[dex_register++];
stack_args[stack_index++] = registers[dex_register++];
}
break;
}
case 'F': {
if (fpr_index % 2 == 0) {
fpr_index = std::max(fpr_double_index, fpr_index);
}
if (fpr_index < 16) {
fprs[fpr_index++] = registers[dex_register++];
stack_index++;
} else {
stack_args[stack_index++] = registers[dex_register++];
}
break;
}
case 'J': {
stack_index += 2;
dex_register += 2;
break;
}
default: {
stack_index++;
dex_register++;
break;
}
}
}
}
#endif
extern "C" const dex::CodeItem* NterpGetCodeItem(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
return method->GetCodeItem();
}
extern "C" const char* NterpGetShorty(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
return method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty();
}
extern "C" const char* NterpGetShortyFromMethodId(ArtMethod* caller, uint32_t method_index)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
return caller->GetDexFile()->GetMethodShorty(method_index);
}
extern "C" const char* NterpGetShortyFromInvokePolymorphic(ArtMethod* caller, uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
const Instruction* inst = Instruction::At(dex_pc_ptr);
dex::ProtoIndex proto_idx(inst->Opcode() == Instruction::INVOKE_POLYMORPHIC
? inst->VRegH_45cc()
: inst->VRegH_4rcc());
return caller->GetDexFile()->GetShorty(proto_idx);
}
extern "C" const char* NterpGetShortyFromInvokeCustom(ArtMethod* caller, uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
const Instruction* inst = Instruction::At(dex_pc_ptr);
uint16_t call_site_index = (inst->Opcode() == Instruction::INVOKE_CUSTOM
? inst->VRegB_35c()
: inst->VRegB_3rc());
const DexFile* dex_file = caller->GetDexFile();
dex::ProtoIndex proto_idx = dex_file->GetProtoIndexForCallSite(call_site_index);
return dex_file->GetShorty(proto_idx);
}
FLATTEN
extern "C" size_t NterpGetMethodSlowPath(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
UpdateHotness(caller);
const Instruction* inst = Instruction::At(dex_pc_ptr);
InvokeType invoke_type = kStatic;
uint16_t method_index = 0;
switch (inst->Opcode()) {
case Instruction::INVOKE_DIRECT: {
method_index = inst->VRegB_35c();
invoke_type = kDirect;
break;
}
case Instruction::INVOKE_INTERFACE: {
method_index = inst->VRegB_35c();
invoke_type = kInterface;
break;
}
case Instruction::INVOKE_STATIC: {
method_index = inst->VRegB_35c();
invoke_type = kStatic;
break;
}
case Instruction::INVOKE_SUPER: {
method_index = inst->VRegB_35c();
invoke_type = kSuper;
break;
}
case Instruction::INVOKE_VIRTUAL: {
method_index = inst->VRegB_35c();
invoke_type = kVirtual;
break;
}
case Instruction::INVOKE_DIRECT_RANGE: {
method_index = inst->VRegB_3rc();
invoke_type = kDirect;
break;
}
case Instruction::INVOKE_INTERFACE_RANGE: {
method_index = inst->VRegB_3rc();
invoke_type = kInterface;
break;
}
case Instruction::INVOKE_STATIC_RANGE: {
method_index = inst->VRegB_3rc();
invoke_type = kStatic;
break;
}
case Instruction::INVOKE_SUPER_RANGE: {
method_index = inst->VRegB_3rc();
invoke_type = kSuper;
break;
}
case Instruction::INVOKE_VIRTUAL_RANGE: {
method_index = inst->VRegB_3rc();
invoke_type = kVirtual;
break;
}
default:
LOG(FATAL) << "Unknown instruction " << inst->Opcode();
}
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
ArtMethod* resolved_method = caller->SkipAccessChecks()
? class_linker->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
self, method_index, caller, invoke_type)
: class_linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
self, method_index, caller, invoke_type);
if (resolved_method == nullptr) {
DCHECK(self->IsExceptionPending());
return 0;
}
// ResolveMethod returns the method based on the method_id. For super invokes
// we must use the executing class's context to find the right method.
if (invoke_type == kSuper) {
ObjPtr<mirror::Class> executing_class = caller->GetDeclaringClass();
ObjPtr<mirror::Class> referenced_class = class_linker->LookupResolvedType(
executing_class->GetDexFile().GetMethodId(method_index).class_idx_,
executing_class->GetDexCache(),
executing_class->GetClassLoader());
DCHECK(referenced_class != nullptr); // We have already resolved a method from this class.
if (!referenced_class->IsAssignableFrom(executing_class)) {
// We cannot determine the target method.
ThrowNoSuchMethodError(invoke_type,
resolved_method->GetDeclaringClass(),
resolved_method->GetName(),
resolved_method->GetSignature());
return 0;
}
if (referenced_class->IsInterface()) {
resolved_method = referenced_class->FindVirtualMethodForInterfaceSuper(
resolved_method, class_linker->GetImagePointerSize());
} else {
uint16_t vtable_index = resolved_method->GetMethodIndex();
ObjPtr<mirror::Class> super_class = executing_class->GetSuperClass();
if (super_class == nullptr ||
!super_class->HasVTable() ||
vtable_index >= static_cast<uint32_t>(super_class->GetVTableLength())) {
// Behavior to agree with that of the verifier.
ThrowNoSuchMethodError(invoke_type,
resolved_method->GetDeclaringClass(),
resolved_method->GetName(),
resolved_method->GetSignature());
return 0;
} else {
resolved_method = executing_class->GetSuperClass()->GetVTableEntry(
vtable_index, class_linker->GetImagePointerSize());
}
}
}
if (invoke_type == kInterface) {
size_t result = 0u;
if (resolved_method->GetDeclaringClass()->IsObjectClass()) {
// Set the low bit to notify the interpreter it should do a vtable call.
DCHECK_LT(resolved_method->GetMethodIndex(), 0x10000);
result = (resolved_method->GetMethodIndex() << 16) | 1U;
} else {
DCHECK(resolved_method->GetDeclaringClass()->IsInterface());
DCHECK(!resolved_method->IsCopied());
if (!resolved_method->IsAbstract()) {
// Set the second bit to notify the interpreter this is a default
// method.
result = reinterpret_cast<size_t>(resolved_method) | 2U;
} else {
result = reinterpret_cast<size_t>(resolved_method);
}
}
UpdateCache(self, dex_pc_ptr, result);
return result;
} else if (resolved_method->GetDeclaringClass()->IsStringClass()
&& !resolved_method->IsStatic()
&& resolved_method->IsConstructor()) {
CHECK_NE(invoke_type, kSuper);
resolved_method = WellKnownClasses::StringInitToStringFactory(resolved_method);
// Or the result with 1 to notify to nterp this is a string init method. We
// also don't cache the result as we don't want nterp to have its fast path always
// check for it, and we expect a lot more regular calls than string init
// calls.
return reinterpret_cast<size_t>(resolved_method) | 1;
} else if (invoke_type == kVirtual) {
UpdateCache(self, dex_pc_ptr, resolved_method->GetMethodIndex());
return resolved_method->GetMethodIndex();
} else {
UpdateCache(self, dex_pc_ptr, resolved_method);
return reinterpret_cast<size_t>(resolved_method);
}
}
extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
InterpreterCache* cache = self->GetInterpreterCache();
size_t cached_value;
if (LIKELY(cache->Get</*kSkipThreadLocal=*/true>(self, dex_pc_ptr, &cached_value))) {
return cached_value;
}
return NterpGetMethodSlowPath(self, caller, dex_pc_ptr); // Tail call.
}
FLATTEN
static ArtField* ResolveFieldWithAccessChecks(Thread* self,
ClassLinker* class_linker,
uint16_t field_index,
ArtMethod* caller,
bool is_static,
bool is_put,
size_t resolve_field_type) // Resolve if not zero
REQUIRES_SHARED(Locks::mutator_lock_) {
if (caller->SkipAccessChecks()) {
return class_linker->ResolveField(field_index, caller, is_static);
}
caller = caller->GetInterfaceMethodIfProxy(kRuntimePointerSize);
StackHandleScope<2> hs(self);
Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(caller->GetDexCache()));
Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(caller->GetClassLoader()));
ArtField* resolved_field = class_linker->ResolveFieldJLS(field_index,
h_dex_cache,
h_class_loader);
if (resolved_field == nullptr) {
return nullptr;
}
ObjPtr<mirror::Class> fields_class = resolved_field->GetDeclaringClass();
if (UNLIKELY(resolved_field->IsStatic() != is_static)) {
ThrowIncompatibleClassChangeErrorField(resolved_field, is_static, caller);
return nullptr;
}
ObjPtr<mirror::Class> referring_class = caller->GetDeclaringClass();
if (UNLIKELY(!referring_class->CheckResolvedFieldAccess(fields_class,
resolved_field,
caller->GetDexCache(),
field_index))) {
return nullptr;
}
if (UNLIKELY(is_put && resolved_field->IsFinal() && (fields_class != referring_class))) {
ThrowIllegalAccessErrorFinalField(caller, resolved_field);
return nullptr;
}
if (resolve_field_type != 0u && resolved_field->ResolveType() == nullptr) {
DCHECK(self->IsExceptionPending());
return nullptr;
}
return resolved_field;
}
extern "C" size_t NterpGetStaticFieldSlowPath(Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr,
size_t resolve_field_type) // Resolve if not zero
REQUIRES_SHARED(Locks::mutator_lock_) {
UpdateHotness(caller);
const Instruction* inst = Instruction::At(dex_pc_ptr);
uint16_t field_index = inst->VRegB_21c();
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
ArtField* resolved_field = ResolveFieldWithAccessChecks(
self,
class_linker,
field_index,
caller,
/* is_static */ true,
/* is_put */ IsInstructionSPut(inst->Opcode()),
resolve_field_type);
if (resolved_field == nullptr) {
DCHECK(self->IsExceptionPending());
return 0;
}
if (UNLIKELY(!resolved_field->GetDeclaringClass()->IsVisiblyInitialized())) {
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(resolved_field->GetDeclaringClass()));
if (UNLIKELY(!class_linker->EnsureInitialized(
self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
DCHECK(self->IsExceptionPending());
return 0;
}
DCHECK(h_class->IsInitializing());
}
if (resolved_field->IsVolatile()) {
// Or the result with 1 to notify to nterp this is a volatile field. We
// also don't cache the result as we don't want nterp to have its fast path always
// check for it.
return reinterpret_cast<size_t>(resolved_field) | 1;
} else {
UpdateCache(self, dex_pc_ptr, resolved_field);
return reinterpret_cast<size_t>(resolved_field);
}
}
extern "C" size_t NterpGetStaticField(Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr,
size_t resolve_field_type) // Resolve if not zero
REQUIRES_SHARED(Locks::mutator_lock_) {
InterpreterCache* cache = self->GetInterpreterCache();
size_t cached_value;
if (LIKELY(cache->Get</*kSkipThreadLocal=*/true>(self, dex_pc_ptr, &cached_value))) {
return cached_value;
}
return NterpGetStaticFieldSlowPath(self, caller, dex_pc_ptr, resolve_field_type);
}
extern "C" uint32_t NterpGetInstanceFieldOffsetSlowPath(
Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr,
size_t resolve_field_type) // Resolve if not zero
REQUIRES_SHARED(Locks::mutator_lock_) {
UpdateHotness(caller);
const Instruction* inst = Instruction::At(dex_pc_ptr);
uint16_t field_index = inst->VRegC_22c();
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
ArtField* resolved_field = ResolveFieldWithAccessChecks(
self,
class_linker,
field_index,
caller,
/* is_static */ false,
/* is_put */ IsInstructionIPut(inst->Opcode()),
resolve_field_type);
if (resolved_field == nullptr) {
DCHECK(self->IsExceptionPending());
return 0;
}
if (resolved_field->IsVolatile()) {
// Don't cache for a volatile field, and return a negative offset as marker
// of volatile.
return -resolved_field->GetOffset().Uint32Value();
}
UpdateCache(self, dex_pc_ptr, resolved_field->GetOffset().Uint32Value());
return resolved_field->GetOffset().Uint32Value();
}
extern "C" uint32_t NterpGetInstanceFieldOffset(Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr,
size_t resolve_field_type) // Resolve if not zero
REQUIRES_SHARED(Locks::mutator_lock_) {
InterpreterCache* cache = self->GetInterpreterCache();
size_t cached_value;
if (LIKELY(cache->Get</*kSkipThreadLocal=*/true>(self, dex_pc_ptr, &cached_value))) {
return cached_value;
}
return NterpGetInstanceFieldOffsetSlowPath(self, caller, dex_pc_ptr, resolve_field_type);
}
extern "C" mirror::Object* NterpGetClassOrAllocateObject(Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
UpdateHotness(caller);
const Instruction* inst = Instruction::At(dex_pc_ptr);
dex::TypeIndex index;
switch (inst->Opcode()) {
case Instruction::NEW_INSTANCE:
index = dex::TypeIndex(inst->VRegB_21c());
break;
case Instruction::CHECK_CAST:
index = dex::TypeIndex(inst->VRegB_21c());
break;
case Instruction::INSTANCE_OF:
index = dex::TypeIndex(inst->VRegC_22c());
break;
case Instruction::CONST_CLASS:
index = dex::TypeIndex(inst->VRegB_21c());
break;
case Instruction::NEW_ARRAY:
index = dex::TypeIndex(inst->VRegC_22c());
break;
default:
LOG(FATAL) << "Unreachable";
}
ObjPtr<mirror::Class> c =
ResolveVerifyAndClinit(index,
caller,
self,
/* can_run_clinit= */ false,
/* verify_access= */ !caller->SkipAccessChecks());
if (c == nullptr) {
DCHECK(self->IsExceptionPending());
return nullptr;
}
if (inst->Opcode() == Instruction::NEW_INSTANCE) {
gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
if (UNLIKELY(c->IsStringClass())) {
// We don't cache the class for strings as we need to special case their
// allocation.
return mirror::String::AllocEmptyString(self, allocator_type).Ptr();
} else {
if (!c->IsFinalizable() && c->IsInstantiable()) {
// Cache non-finalizable classes for next calls.
UpdateCache(self, dex_pc_ptr, c.Ptr());
}
return AllocObjectFromCode(c, self, allocator_type).Ptr();
}
} else {
// For all other cases, cache the class.
UpdateCache(self, dex_pc_ptr, c.Ptr());
}
return c.Ptr();
}
extern "C" mirror::Object* NterpLoadObject(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
const Instruction* inst = Instruction::At(dex_pc_ptr);
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
switch (inst->Opcode()) {
case Instruction::CONST_STRING:
case Instruction::CONST_STRING_JUMBO: {
UpdateHotness(caller);
dex::StringIndex string_index(
(inst->Opcode() == Instruction::CONST_STRING)
? inst->VRegB_21c()
: inst->VRegB_31c());
ObjPtr<mirror::String> str = class_linker->ResolveString(string_index, caller);
if (str == nullptr) {
DCHECK(self->IsExceptionPending());
return nullptr;
}
UpdateCache(self, dex_pc_ptr, str.Ptr());
return str.Ptr();
}
case Instruction::CONST_METHOD_HANDLE: {
// Don't cache: we don't expect this to be performance sensitive, and we
// don't want the cache to conflict with a performance sensitive entry.
return class_linker->ResolveMethodHandle(self, inst->VRegB_21c(), caller).Ptr();
}
case Instruction::CONST_METHOD_TYPE: {
// Don't cache: we don't expect this to be performance sensitive, and we
// don't want the cache to conflict with a performance sensitive entry.
return class_linker->ResolveMethodType(
self, dex::ProtoIndex(inst->VRegB_21c()), caller).Ptr();
}
default:
LOG(FATAL) << "Unreachable";
}
return nullptr;
}
extern "C" void NterpUnimplemented() {
LOG(FATAL) << "Unimplemented";
}
static mirror::Object* DoFilledNewArray(Thread* self,
ArtMethod* caller,
uint16_t* dex_pc_ptr,
uint32_t* regs,
bool is_range)
REQUIRES_SHARED(Locks::mutator_lock_) {
const Instruction* inst = Instruction::At(dex_pc_ptr);
if (kIsDebugBuild) {
if (is_range) {
DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY_RANGE);
} else {
DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY);
}
}
const int32_t length = is_range ? inst->VRegA_3rc() : inst->VRegA_35c();
DCHECK_GE(length, 0);
if (!is_range) {
// Checks FILLED_NEW_ARRAY's length does not exceed 5 arguments.
DCHECK_LE(length, 5);
}
uint16_t type_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
ObjPtr<mirror::Class> array_class =
ResolveVerifyAndClinit(dex::TypeIndex(type_idx),
caller,
self,
/* can_run_clinit= */ true,
/* verify_access= */ !caller->SkipAccessChecks());
if (UNLIKELY(array_class == nullptr)) {
DCHECK(self->IsExceptionPending());
return nullptr;
}
DCHECK(array_class->IsArrayClass());
ObjPtr<mirror::Class> component_class = array_class->GetComponentType();
const bool is_primitive_int_component = component_class->IsPrimitiveInt();
if (UNLIKELY(component_class->IsPrimitive() && !is_primitive_int_component)) {
if (component_class->IsPrimitiveLong() || component_class->IsPrimitiveDouble()) {
ThrowRuntimeException("Bad filled array request for type %s",
component_class->PrettyDescriptor().c_str());
} else {
self->ThrowNewExceptionF(
"Ljava/lang/InternalError;",
"Found type %s; filled-new-array not implemented for anything but 'int'",
component_class->PrettyDescriptor().c_str());
}
return nullptr;
}
ObjPtr<mirror::Object> new_array = mirror::Array::Alloc(
self,
array_class,
length,
array_class->GetComponentSizeShift(),
Runtime::Current()->GetHeap()->GetCurrentAllocator());
if (UNLIKELY(new_array == nullptr)) {
self->AssertPendingOOMException();
return nullptr;
}
uint32_t arg[Instruction::kMaxVarArgRegs]; // only used in filled-new-array.
uint32_t vregC = 0; // only used in filled-new-array-range.
if (is_range) {
vregC = inst->VRegC_3rc();
} else {
inst->GetVarArgs(arg);
}
for (int32_t i = 0; i < length; ++i) {
size_t src_reg = is_range ? vregC + i : arg[i];
if (is_primitive_int_component) {
new_array->AsIntArray()->SetWithoutChecks</* kTransactionActive= */ false>(i, regs[src_reg]);
} else {
new_array->AsObjectArray<mirror::Object>()->SetWithoutChecks</* kTransactionActive= */ false>(
i, reinterpret_cast<mirror::Object*>(regs[src_reg]));
}
}
return new_array.Ptr();
}
extern "C" mirror::Object* NterpFilledNewArray(Thread* self,
ArtMethod* caller,
uint32_t* registers,
uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
return DoFilledNewArray(self, caller, dex_pc_ptr, registers, /* is_range= */ false);
}
extern "C" mirror::Object* NterpFilledNewArrayRange(Thread* self,
ArtMethod* caller,
uint32_t* registers,
uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
return DoFilledNewArray(self, caller, dex_pc_ptr, registers, /* is_range= */ true);
}
extern "C" jit::OsrData* NterpHotMethod(ArtMethod* method, uint16_t* dex_pc_ptr, uint32_t* vregs)
REQUIRES_SHARED(Locks::mutator_lock_) {
// It is important this method is not suspended because it can be called on
// method entry and async deoptimization does not expect runtime methods other than the
// suspend entrypoint before executing the first instruction of a Java
// method.
ScopedAssertNoThreadSuspension sants("In nterp");
Runtime* runtime = Runtime::Current();
method->ResetCounter(runtime->GetJITOptions()->GetWarmupThreshold());
jit::Jit* jit = runtime->GetJit();
if (jit != nullptr && jit->UseJitCompilation()) {
// Nterp passes null on entry where we don't want to OSR.
if (dex_pc_ptr != nullptr) {
// This could be a loop back edge, check if we can OSR.
CodeItemInstructionAccessor accessor(method->DexInstructions());
uint32_t dex_pc = dex_pc_ptr - accessor.Insns();
jit::OsrData* osr_data = jit->PrepareForOsr(
method->GetInterfaceMethodIfProxy(kRuntimePointerSize), dex_pc, vregs);
if (osr_data != nullptr) {
return osr_data;
}
}
jit->EnqueueCompilation(method, Thread::Current());
}
return nullptr;
}
extern "C" ssize_t NterpDoPackedSwitch(const uint16_t* switchData, int32_t testVal)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
const int kInstrLen = 3;
/*
* Packed switch data format:
* ushort ident = 0x0100 magic value
* ushort size number of entries in the table
* int first_key first (and lowest) switch case value
* int targets[size] branch targets, relative to switch opcode
*
* Total size is (4+size*2) 16-bit code units.
*/
uint16_t signature = *switchData++;
DCHECK_EQ(signature, static_cast<uint16_t>(art::Instruction::kPackedSwitchSignature));
uint16_t size = *switchData++;
int32_t firstKey = *switchData++;
firstKey |= (*switchData++) << 16;
int index = testVal - firstKey;
if (index < 0 || index >= size) {
return kInstrLen;
}
/*
* The entries are guaranteed to be aligned on a 32-bit boundary;
* we can treat them as a native int array.
*/
const int32_t* entries = reinterpret_cast<const int32_t*>(switchData);
return entries[index];
}
/*
* Find the matching case. Returns the offset to the handler instructions.
*
* Returns 3 if we don't find a match (it's the size of the sparse-switch
* instruction).
*/
extern "C" ssize_t NterpDoSparseSwitch(const uint16_t* switchData, int32_t testVal)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension sants("In nterp");
const int kInstrLen = 3;
uint16_t size;
const int32_t* keys;
const int32_t* entries;
/*
* Sparse switch data format:
* ushort ident = 0x0200 magic value
* ushort size number of entries in the table; > 0
* int keys[size] keys, sorted low-to-high; 32-bit aligned
* int targets[size] branch targets, relative to switch opcode
*
* Total size is (2+size*4) 16-bit code units.
*/
uint16_t signature = *switchData++;
DCHECK_EQ(signature, static_cast<uint16_t>(art::Instruction::kSparseSwitchSignature));
size = *switchData++;
/* The keys are guaranteed to be aligned on a 32-bit boundary;
* we can treat them as a native int array.
*/
keys = reinterpret_cast<const int32_t*>(switchData);
/* The entries are guaranteed to be aligned on a 32-bit boundary;
* we can treat them as a native int array.
*/
entries = keys + size;
/*
* Binary-search through the array of keys, which are guaranteed to
* be sorted low-to-high.
*/
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
int mid = (lo + hi) >> 1;
int32_t foundVal = keys[mid];
if (testVal < foundVal) {
hi = mid - 1;
} else if (testVal > foundVal) {
lo = mid + 1;
} else {
return entries[mid];
}
}
return kInstrLen;
}
extern "C" void NterpFree(void* val) {
free(val);
}
} // namespace interpreter
} // namespace art