xref: /aosp_15_r20/art/runtime/oat/jni_stub_hash_map.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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