blob: add100529186a3c0030989b95b51622d7957d2c2 [file]
/*
* Copyright (C) 2024 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.
*/
#include "jni_stub_hash_map.h"
#include "arch/arm64/jni_frame_arm64.h"
#include "arch/instruction_set.h"
#include "arch/riscv64/jni_frame_riscv64.h"
#include "arch/x86_64/jni_frame_x86_64.h"
#include "base/macros.h"
#include "dex/modifiers.h"
namespace art HIDDEN {
static char TranslateArgToJniShorty(char ch) {
// Byte, char, int, short, boolean are treated the same(e.g., Wx registers for arm64) when
// generating JNI stub, so their JNI shorty characters are same.
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
static constexpr char kTranslations[] = ".PPD.F..PJ.L......P......P";
DCHECK_GE(ch, 'A');
DCHECK_LE(ch, 'Z');
DCHECK_NE(kTranslations[ch - 'A'], '.');
return kTranslations[ch - 'A'];
}
static char TranslateReturnTypeToJniShorty(char ch, InstructionSet isa = InstructionSet::kNone) {
// For all archs, reference type has a different JNI shorty character than others as it needs to
// be decoded in stub.
// For arm64, small return types need sign-/zero-extended.
// For x86_64, small return types need sign-/zero-extended, and RAX needs to be preserved and
// restored when thread state changes.
// Other archs keeps untranslated for simplicity.
// TODO: support riscv64 with an optimized version.
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
static constexpr char kArm64Translations[] = ".BCP.P..PP.L......S..P...Z";
static constexpr char kX86_64Translations[] = ".BCP.P..RR.L......S..P...Z";
static constexpr char kOtherTranslations[] = ".BCD.F..IJ.L......S..V...Z";
DCHECK_GE(ch, 'A');
DCHECK_LE(ch, 'Z');
switch (isa) {
case InstructionSet::kArm64:
DCHECK_NE(kArm64Translations[ch - 'A'], '.');
return kArm64Translations[ch - 'A'];
case InstructionSet::kX86_64:
DCHECK_NE(kX86_64Translations[ch - 'A'], '.');
return kX86_64Translations[ch - 'A'];
default:
DCHECK_NE(kOtherTranslations[ch - 'A'], '.');
return kOtherTranslations[ch - 'A'];
}
}
static constexpr size_t GetMaxIntLikeRegisterArgs(InstructionSet isa) {
switch (isa) {
case InstructionSet::kArm64:
return arm64::kMaxIntLikeRegisterArguments;
case InstructionSet::kX86_64:
return x86_64::kMaxIntLikeRegisterArguments;
default:
LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__;
UNREACHABLE();
}
}
static constexpr size_t GetMaxFloatOrDoubleRegisterArgs(InstructionSet isa) {
switch (isa) {
case InstructionSet::kArm64:
return arm64::kMaxFloatOrDoubleRegisterArguments;
case InstructionSet::kX86_64:
return x86_64::kMaxFloatOrDoubleRegisterArguments;
default:
LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__;
UNREACHABLE();
}
}
static size_t StackOffset(char ch) {
if (ch == 'J' || ch == 'D') {
return 8;
} else {
return 4;
}
}
static bool IsFloatOrDoubleArg(char ch) {
return ch == 'F' || ch == 'D';
}
static bool IsIntegralArg(char ch) {
return ch == 'B' || ch == 'C' || ch == 'I' || ch == 'J' || ch == 'S' || ch == 'Z';
}
static bool IsReferenceArg(char ch) {
return ch == 'L';
}
template<InstructionSet kIsa>
size_t JniStubKeyOptimizedHash(const JniStubKey& key) {
bool is_static = key.Flags() & kAccStatic;
std::string_view shorty = key.Shorty();
size_t result = key.Flags();
result ^= TranslateReturnTypeToJniShorty(shorty[0], kIsa);
constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa);
constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa);
size_t float_or_double_args = 0;
// ArtMethod* and 'Object* this' for non-static method.
// ArtMethod* for static method.
size_t int_like_args = is_static ? 1 : 2;
size_t stack_offset = 0;
for (char c : shorty.substr(1u)) {
bool stack_offset_matters = false;
stack_offset += StackOffset(c);
if (IsFloatOrDoubleArg(c)) {
++float_or_double_args;
if (float_or_double_args > kMaxFloatOrDoubleRegisterArgs) {
// Stack offset matters if we run out of float-like (float, double) argument registers
// because the subsequent float-like args should be passed on the stack.
stack_offset_matters = true;
} else {
// Floating-point register arguments are not touched when generating JNI stub, so could be
// ignored when calculating hash value.
continue;
}
} else {
++int_like_args;
if (int_like_args > kMaxIntLikeRegisterArgs || IsReferenceArg(c)) {
// Stack offset matters if we run out of integer-like (pointer, object, long, int, short,
// bool, etc) argument registers because the subsequent integer-like args should be passed
// on the stack. It also matters if current arg is reference type because it needs to be
// spilled as raw data even if it's in a register.
stack_offset_matters = true;
} else if (!is_static) {
// For non-static method, two managed arguments 'ArtMethod*' and 'Object* this' correspond
// to two native arguments 'JNIEnv*' and 'jobject'. So trailing integral (long, int, short,
// bool, etc) arguments will remain in the same registers, which do not need any generated
// code.
// But for static method, we have only one leading managed argument 'ArtMethod*' but two
// native arguments 'JNIEnv*' and 'jclass'. So trailing integral arguments are always
// shuffled around and affect the generated code.
continue;
}
}
// int_like_args is needed for reference type because it will determine from which register
// we take the value to construct jobject.
if (IsReferenceArg(c)) {
result = result * 31u * int_like_args ^ TranslateArgToJniShorty(c);
} else {
result = result * 31u ^ TranslateArgToJniShorty(c);
}
if (stack_offset_matters) {
result += stack_offset;
}
}
return result;
}
size_t JniStubKeyGenericHash(const JniStubKey& key) {
std::string_view shorty = key.Shorty();
size_t result = key.Flags();
result ^= TranslateReturnTypeToJniShorty(shorty[0]);
for (char c : shorty.substr(1u)) {
result = result * 31u ^ TranslateArgToJniShorty(c);
}
return result;
}
JniStubKeyHash::JniStubKeyHash(InstructionSet isa) {
switch (isa) {
case InstructionSet::kArm:
case InstructionSet::kThumb2:
case InstructionSet::kRiscv64:
case InstructionSet::kX86:
hash_func_ = JniStubKeyGenericHash;
break;
case InstructionSet::kArm64:
hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kArm64>;
break;
case InstructionSet::kX86_64:
hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kX86_64>;
break;
case InstructionSet::kNone:
LOG(FATAL) << "No instruction set given for " << __FUNCTION__;
UNREACHABLE();
}
}
template<InstructionSet kIsa>
bool JniStubKeyOptimizedEquals(const JniStubKey& lhs, const JniStubKey& rhs) {
if (lhs.Flags() != rhs.Flags()) {
return false;
}
std::string_view shorty_lhs = lhs.Shorty();
std::string_view shorty_rhs = rhs.Shorty();
if (TranslateReturnTypeToJniShorty(shorty_lhs[0], kIsa) !=
TranslateReturnTypeToJniShorty(shorty_rhs[0], kIsa)) {
return false;
}
bool is_static = lhs.Flags() & kAccStatic;
constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa);
constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa);
size_t float_or_double_args_lhs = 0;
size_t float_or_double_args_rhs = 0;
size_t int_like_args_lhs = is_static ? 1 : 2;
size_t int_like_args_rhs = is_static ? 1 : 2;
size_t stack_offset_lhs = 0;
size_t stack_offset_rhs = 0;
size_t i = 1;
size_t j = 1;
while (i < shorty_lhs.length() && j < shorty_rhs.length()) {
bool should_skip = false;
bool stack_offset_matters = false;
char ch_lhs = shorty_lhs[i];
char ch_rhs = shorty_rhs[j];
if (IsFloatOrDoubleArg(ch_lhs) &&
float_or_double_args_lhs < kMaxFloatOrDoubleRegisterArgs) {
// Skip float-like register arguments.
++i;
++float_or_double_args_lhs;
stack_offset_lhs += StackOffset(ch_lhs);
should_skip = true;
} else if (IsIntegralArg(ch_lhs) &&
int_like_args_lhs < kMaxIntLikeRegisterArgs) {
if (!is_static) {
// Skip integral register arguments for non-static method.
++i;
++int_like_args_lhs;
stack_offset_lhs += StackOffset(ch_lhs);
should_skip = true;
}
} else {
stack_offset_matters = true;
}
if (IsFloatOrDoubleArg(ch_rhs) &&
float_or_double_args_rhs < kMaxFloatOrDoubleRegisterArgs) {
// Skip float-like register arguments.
++j;
++float_or_double_args_rhs;
stack_offset_rhs += StackOffset(ch_rhs);
should_skip = true;
} else if (IsIntegralArg(ch_rhs) &&
int_like_args_rhs < kMaxIntLikeRegisterArgs) {
if (!is_static) {
// Skip integral register arguments for non-static method.
++j;
++int_like_args_rhs;
stack_offset_rhs += StackOffset(ch_rhs);
should_skip = true;
}
} else {
stack_offset_matters = true;
}
if (should_skip) {
continue;
}
if (TranslateArgToJniShorty(ch_lhs) != TranslateArgToJniShorty(ch_rhs)) {
return false;
}
if (stack_offset_matters && stack_offset_lhs != stack_offset_rhs) {
return false;
}
// int_like_args needs to be compared for reference type because it will determine from
// which register we take the value to construct jobject.
if (IsReferenceArg(ch_lhs) && int_like_args_lhs != int_like_args_rhs) {
return false;
}
// Passed character comparison.
++i;
++j;
stack_offset_lhs += StackOffset(ch_lhs);
stack_offset_rhs += StackOffset(ch_rhs);
DCHECK_EQ(IsFloatOrDoubleArg(ch_lhs), IsFloatOrDoubleArg(ch_rhs));
if (IsFloatOrDoubleArg(ch_lhs)) {
++float_or_double_args_lhs;
++float_or_double_args_rhs;
} else {
++int_like_args_lhs;
++int_like_args_rhs;
}
}
auto remaining_shorty =
i < shorty_lhs.length() ? shorty_lhs.substr(i) : shorty_rhs.substr(j);
size_t float_or_double_args =
i < shorty_lhs.length() ? float_or_double_args_lhs : float_or_double_args_rhs;
size_t int_like_args = i < shorty_lhs.length() ? int_like_args_lhs : int_like_args_rhs;
for (char c : remaining_shorty) {
if (IsFloatOrDoubleArg(c) && float_or_double_args < kMaxFloatOrDoubleRegisterArgs) {
++float_or_double_args;
continue;
}
if (!is_static && IsIntegralArg(c) && int_like_args < kMaxIntLikeRegisterArgs) {
++int_like_args;
continue;
}
return false;
}
return true;
}
bool JniStubKeyGenericEquals(const JniStubKey& lhs, const JniStubKey& rhs) {
if (lhs.Flags() != rhs.Flags()) {
return false;
}
std::string_view shorty_lhs = lhs.Shorty();
std::string_view shorty_rhs = rhs.Shorty();
if (TranslateReturnTypeToJniShorty(shorty_lhs[0]) !=
TranslateReturnTypeToJniShorty(shorty_rhs[0])) {
return false;
}
if (shorty_lhs.length() != shorty_rhs.length()) {
return false;
}
for (size_t i = 1; i < shorty_lhs.length(); ++i) {
if (TranslateArgToJniShorty(shorty_lhs[i]) != TranslateArgToJniShorty(shorty_rhs[i])) {
return false;
}
}
return true;
}
JniStubKeyEquals::JniStubKeyEquals(InstructionSet isa) {
switch (isa) {
case InstructionSet::kArm:
case InstructionSet::kThumb2:
case InstructionSet::kRiscv64:
case InstructionSet::kX86:
equals_func_ = JniStubKeyGenericEquals;
break;
case InstructionSet::kArm64:
equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kArm64>;
break;
case InstructionSet::kX86_64:
equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kX86_64>;
break;
case InstructionSet::kNone:
LOG(FATAL) << "No instruction set given for " << __FUNCTION__;
UNREACHABLE();
}
}
} // namespace art