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