1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "jni_stub_hash_map.h"
18
19 #include "arch/arm64/jni_frame_arm64.h"
20 #include "arch/instruction_set.h"
21 #include "arch/riscv64/jni_frame_riscv64.h"
22 #include "arch/x86_64/jni_frame_x86_64.h"
23 #include "base/macros.h"
24
25 namespace art HIDDEN {
26
TranslateArgToJniShorty(char ch)27 static char TranslateArgToJniShorty(char ch) {
28 // Byte, char, int, short, boolean are treated the same(e.g., Wx registers for arm64) when
29 // generating JNI stub, so their JNI shorty characters are same.
30 // ABCDEFGHIJKLMNOPQRSTUVWXYZ
31 static constexpr char kTranslations[] = ".PPD.F..PJ.L......P......P";
32 DCHECK_GE(ch, 'A');
33 DCHECK_LE(ch, 'Z');
34 DCHECK_NE(kTranslations[ch - 'A'], '.');
35 return kTranslations[ch - 'A'];
36 }
37
TranslateReturnTypeToJniShorty(char ch,InstructionSet isa=InstructionSet::kNone)38 static char TranslateReturnTypeToJniShorty(char ch, InstructionSet isa = InstructionSet::kNone) {
39 // For all archs, reference type has a different JNI shorty character than others as it needs to
40 // be decoded in stub.
41 // For arm64, small return types need sign-/zero-extended.
42 // For x86_64, small return types need sign-/zero-extended, and RAX needs to be preserved and
43 // restored when thread state changes.
44 // Other archs keeps untranslated for simplicity.
45 // TODO: support riscv64 with an optimized version.
46 // ABCDEFGHIJKLMNOPQRSTUVWXYZ
47 static constexpr char kArm64Translations[] = ".BCP.P..PP.L......S..P...Z";
48 static constexpr char kX86_64Translations[] = ".BCP.P..RR.L......S..P...Z";
49 static constexpr char kOtherTranslations[] = ".BCD.F..IJ.L......S..V...Z";
50 DCHECK_GE(ch, 'A');
51 DCHECK_LE(ch, 'Z');
52 switch (isa) {
53 case InstructionSet::kArm64:
54 DCHECK_NE(kArm64Translations[ch - 'A'], '.');
55 return kArm64Translations[ch - 'A'];
56 case InstructionSet::kX86_64:
57 DCHECK_NE(kX86_64Translations[ch - 'A'], '.');
58 return kX86_64Translations[ch - 'A'];
59 default:
60 DCHECK_NE(kOtherTranslations[ch - 'A'], '.');
61 return kOtherTranslations[ch - 'A'];
62 }
63 }
64
GetMaxIntLikeRegisterArgs(InstructionSet isa)65 static constexpr size_t GetMaxIntLikeRegisterArgs(InstructionSet isa) {
66 switch (isa) {
67 case InstructionSet::kArm64:
68 return arm64::kMaxIntLikeRegisterArguments;
69 case InstructionSet::kX86_64:
70 return x86_64::kMaxIntLikeRegisterArguments;
71 default:
72 LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__;
73 UNREACHABLE();
74 }
75 }
76
GetMaxFloatOrDoubleRegisterArgs(InstructionSet isa)77 static constexpr size_t GetMaxFloatOrDoubleRegisterArgs(InstructionSet isa) {
78 switch (isa) {
79 case InstructionSet::kArm64:
80 return arm64::kMaxFloatOrDoubleRegisterArguments;
81 case InstructionSet::kX86_64:
82 return x86_64::kMaxFloatOrDoubleRegisterArguments;
83 default:
84 LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__;
85 UNREACHABLE();
86 }
87 }
88
StackOffset(char ch)89 static size_t StackOffset(char ch) {
90 if (ch == 'J' || ch == 'D') {
91 return 8;
92 } else {
93 return 4;
94 }
95 }
96
IsFloatOrDoubleArg(char ch)97 static bool IsFloatOrDoubleArg(char ch) {
98 return ch == 'F' || ch == 'D';
99 }
100
IsIntegralArg(char ch)101 static bool IsIntegralArg(char ch) {
102 return ch == 'B' || ch == 'C' || ch == 'I' || ch == 'J' || ch == 'S' || ch == 'Z';
103 }
104
IsReferenceArg(char ch)105 static bool IsReferenceArg(char ch) {
106 return ch == 'L';
107 }
108
109 template<InstructionSet kIsa>
JniStubKeyOptimizedHash(const JniStubKey & key)110 size_t JniStubKeyOptimizedHash(const JniStubKey& key) {
111 bool is_static = key.Flags() & kAccStatic;
112 std::string_view shorty = key.Shorty();
113 size_t result = key.Flags();
114 result ^= TranslateReturnTypeToJniShorty(shorty[0], kIsa);
115 constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa);
116 constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa);
117 size_t float_or_double_args = 0;
118 // ArtMethod* and 'Object* this' for non-static method.
119 // ArtMethod* for static method.
120 size_t int_like_args = is_static ? 1 : 2;
121 size_t stack_offset = 0;
122 for (char c : shorty.substr(1u)) {
123 bool stack_offset_matters = false;
124 stack_offset += StackOffset(c);
125 if (IsFloatOrDoubleArg(c)) {
126 ++float_or_double_args;
127 if (float_or_double_args > kMaxFloatOrDoubleRegisterArgs) {
128 // Stack offset matters if we run out of float-like (float, double) argument registers
129 // because the subsequent float-like args should be passed on the stack.
130 stack_offset_matters = true;
131 } else {
132 // Floating-point register arguments are not touched when generating JNI stub, so could be
133 // ignored when calculating hash value.
134 continue;
135 }
136 } else {
137 ++int_like_args;
138 if (int_like_args > kMaxIntLikeRegisterArgs || IsReferenceArg(c)) {
139 // Stack offset matters if we run out of integer-like (pointer, object, long, int, short,
140 // bool, etc) argument registers because the subsequent integer-like args should be passed
141 // on the stack. It also matters if current arg is reference type because it needs to be
142 // spilled as raw data even if it's in a register.
143 stack_offset_matters = true;
144 } else if (!is_static) {
145 // For non-static method, two managed arguments 'ArtMethod*' and 'Object* this' correspond
146 // to two native arguments 'JNIEnv*' and 'jobject'. So trailing integral (long, int, short,
147 // bool, etc) arguments will remain in the same registers, which do not need any generated
148 // code.
149 // But for static method, we have only one leading managed argument 'ArtMethod*' but two
150 // native arguments 'JNIEnv*' and 'jclass'. So trailing integral arguments are always
151 // shuffled around and affect the generated code.
152 continue;
153 }
154 }
155 // int_like_args is needed for reference type because it will determine from which register
156 // we take the value to construct jobject.
157 if (IsReferenceArg(c)) {
158 result = result * 31u * int_like_args ^ TranslateArgToJniShorty(c);
159 } else {
160 result = result * 31u ^ TranslateArgToJniShorty(c);
161 }
162 if (stack_offset_matters) {
163 result += stack_offset;
164 }
165 }
166 return result;
167 }
168
JniStubKeyGenericHash(const JniStubKey & key)169 size_t JniStubKeyGenericHash(const JniStubKey& key) {
170 std::string_view shorty = key.Shorty();
171 size_t result = key.Flags();
172 result ^= TranslateReturnTypeToJniShorty(shorty[0]);
173 for (char c : shorty.substr(1u)) {
174 result = result * 31u ^ TranslateArgToJniShorty(c);
175 }
176 return result;
177 }
178
JniStubKeyHash(InstructionSet isa)179 JniStubKeyHash::JniStubKeyHash(InstructionSet isa) {
180 switch (isa) {
181 case InstructionSet::kArm:
182 case InstructionSet::kThumb2:
183 case InstructionSet::kRiscv64:
184 case InstructionSet::kX86:
185 hash_func_ = JniStubKeyGenericHash;
186 break;
187 case InstructionSet::kArm64:
188 hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kArm64>;
189 break;
190 case InstructionSet::kX86_64:
191 hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kX86_64>;
192 break;
193 case InstructionSet::kNone:
194 LOG(FATAL) << "No instruction set given for " << __FUNCTION__;
195 UNREACHABLE();
196 }
197 }
198
199 template<InstructionSet kIsa>
JniStubKeyOptimizedEquals(const JniStubKey & lhs,const JniStubKey & rhs)200 bool JniStubKeyOptimizedEquals(const JniStubKey& lhs, const JniStubKey& rhs) {
201 if (lhs.Flags() != rhs.Flags()) {
202 return false;
203 }
204 std::string_view shorty_lhs = lhs.Shorty();
205 std::string_view shorty_rhs = rhs.Shorty();
206 if (TranslateReturnTypeToJniShorty(shorty_lhs[0], kIsa) !=
207 TranslateReturnTypeToJniShorty(shorty_rhs[0], kIsa)) {
208 return false;
209 }
210 bool is_static = lhs.Flags() & kAccStatic;
211 constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa);
212 constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa);
213 size_t float_or_double_args_lhs = 0;
214 size_t float_or_double_args_rhs = 0;
215 size_t int_like_args_lhs = is_static ? 1 : 2;
216 size_t int_like_args_rhs = is_static ? 1 : 2;
217 size_t stack_offset_lhs = 0;
218 size_t stack_offset_rhs = 0;
219 size_t i = 1;
220 size_t j = 1;
221 while (i < shorty_lhs.length() && j < shorty_rhs.length()) {
222 bool should_skip = false;
223 bool stack_offset_matters = false;
224 char ch_lhs = shorty_lhs[i];
225 char ch_rhs = shorty_rhs[j];
226
227 if (IsFloatOrDoubleArg(ch_lhs) &&
228 float_or_double_args_lhs < kMaxFloatOrDoubleRegisterArgs) {
229 // Skip float-like register arguments.
230 ++i;
231 ++float_or_double_args_lhs;
232 stack_offset_lhs += StackOffset(ch_lhs);
233 should_skip = true;
234 } else if (IsIntegralArg(ch_lhs) &&
235 int_like_args_lhs < kMaxIntLikeRegisterArgs) {
236 if (!is_static) {
237 // Skip integral register arguments for non-static method.
238 ++i;
239 ++int_like_args_lhs;
240 stack_offset_lhs += StackOffset(ch_lhs);
241 should_skip = true;
242 }
243 } else {
244 stack_offset_matters = true;
245 }
246
247 if (IsFloatOrDoubleArg(ch_rhs) &&
248 float_or_double_args_rhs < kMaxFloatOrDoubleRegisterArgs) {
249 // Skip float-like register arguments.
250 ++j;
251 ++float_or_double_args_rhs;
252 stack_offset_rhs += StackOffset(ch_rhs);
253 should_skip = true;
254 } else if (IsIntegralArg(ch_rhs) &&
255 int_like_args_rhs < kMaxIntLikeRegisterArgs) {
256 if (!is_static) {
257 // Skip integral register arguments for non-static method.
258 ++j;
259 ++int_like_args_rhs;
260 stack_offset_rhs += StackOffset(ch_rhs);
261 should_skip = true;
262 }
263 } else {
264 stack_offset_matters = true;
265 }
266
267 if (should_skip) {
268 continue;
269 }
270 if (TranslateArgToJniShorty(ch_lhs) != TranslateArgToJniShorty(ch_rhs)) {
271 return false;
272 }
273 if (stack_offset_matters && stack_offset_lhs != stack_offset_rhs) {
274 return false;
275 }
276 // int_like_args needs to be compared for reference type because it will determine from
277 // which register we take the value to construct jobject.
278 if (IsReferenceArg(ch_lhs) && int_like_args_lhs != int_like_args_rhs) {
279 return false;
280 }
281 // Passed character comparison.
282 ++i;
283 ++j;
284 stack_offset_lhs += StackOffset(ch_lhs);
285 stack_offset_rhs += StackOffset(ch_rhs);
286 DCHECK_EQ(IsFloatOrDoubleArg(ch_lhs), IsFloatOrDoubleArg(ch_rhs));
287 if (IsFloatOrDoubleArg(ch_lhs)) {
288 ++float_or_double_args_lhs;
289 ++float_or_double_args_rhs;
290 } else {
291 ++int_like_args_lhs;
292 ++int_like_args_rhs;
293 }
294 }
295 auto remaining_shorty =
296 i < shorty_lhs.length() ? shorty_lhs.substr(i) : shorty_rhs.substr(j);
297 size_t float_or_double_args =
298 i < shorty_lhs.length() ? float_or_double_args_lhs : float_or_double_args_rhs;
299 size_t int_like_args = i < shorty_lhs.length() ? int_like_args_lhs : int_like_args_rhs;
300 for (char c : remaining_shorty) {
301 if (IsFloatOrDoubleArg(c) && float_or_double_args < kMaxFloatOrDoubleRegisterArgs) {
302 ++float_or_double_args;
303 continue;
304 }
305 if (!is_static && IsIntegralArg(c) && int_like_args < kMaxIntLikeRegisterArgs) {
306 ++int_like_args;
307 continue;
308 }
309 return false;
310 }
311 return true;
312 }
313
JniStubKeyGenericEquals(const JniStubKey & lhs,const JniStubKey & rhs)314 bool JniStubKeyGenericEquals(const JniStubKey& lhs, const JniStubKey& rhs) {
315 if (lhs.Flags() != rhs.Flags()) {
316 return false;
317 }
318 std::string_view shorty_lhs = lhs.Shorty();
319 std::string_view shorty_rhs = rhs.Shorty();
320 if (TranslateReturnTypeToJniShorty(shorty_lhs[0]) !=
321 TranslateReturnTypeToJniShorty(shorty_rhs[0])) {
322 return false;
323 }
324 if (shorty_lhs.length() != shorty_rhs.length()) {
325 return false;
326 }
327 for (size_t i = 1; i < shorty_lhs.length(); ++i) {
328 if (TranslateArgToJniShorty(shorty_lhs[i]) != TranslateArgToJniShorty(shorty_rhs[i])) {
329 return false;
330 }
331 }
332 return true;
333 }
334
JniStubKeyEquals(InstructionSet isa)335 JniStubKeyEquals::JniStubKeyEquals(InstructionSet isa) {
336 switch (isa) {
337 case InstructionSet::kArm:
338 case InstructionSet::kThumb2:
339 case InstructionSet::kRiscv64:
340 case InstructionSet::kX86:
341 equals_func_ = JniStubKeyGenericEquals;
342 break;
343 case InstructionSet::kArm64:
344 equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kArm64>;
345 break;
346 case InstructionSet::kX86_64:
347 equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kX86_64>;
348 break;
349 case InstructionSet::kNone:
350 LOG(FATAL) << "No instruction set given for " << __FUNCTION__;
351 UNREACHABLE();
352 }
353 }
354
355 } // namespace art
356