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