diff options
author | 2021-11-01 09:02:09 +0000 | |
---|---|---|
committer | 2021-11-01 10:45:06 +0000 | |
commit | e43aa3f55fd01ce0887059b309a41b6441521e7c (patch) | |
tree | 7ba4ef9adfbb77e6baa1891c3fabd783fb03d580 | |
parent | 808d8cc8114e0c5ee3116639fe9d31be5a70403a (diff) |
Revert^2 "Inline across dex files for bootclaspath's methods"
This reverts commit 8cb989f1019c4fa30845bf2deb5bc996ed4e8966, so we
are re-enabling commit d690f8ae8f8e2675bc52089a83ac18c749f8e6d2.
Reason for revert: Failing test was fixed here
https://android-review.googlesource.com/c/platform/art/+/1873567
Bug: 154012332
Test: ART tests
Change-Id: If159b29583e35abcfe753f30483f83990208b1b9
-rw-r--r-- | compiler/optimizing/inliner.cc | 66 | ||||
-rw-r--r-- | compiler/optimizing/instruction_simplifier.cc | 45 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 18 | ||||
-rw-r--r-- | compiler/optimizing/optimizing_compiler_stats.h | 1 | ||||
-rw-r--r-- | compiler/optimizing/stack_map_stream.cc | 37 | ||||
-rw-r--r-- | runtime/entrypoints/entrypoint_utils-inl.h | 34 | ||||
-rw-r--r-- | runtime/oat.h | 4 | ||||
-rw-r--r-- | runtime/stack_map.cc | 9 | ||||
-rw-r--r-- | runtime/stack_map.h | 11 | ||||
-rw-r--r-- | test/580-checker-string-fact-intrinsics/src-art/Main.java | 8 |
10 files changed, 179 insertions, 54 deletions
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index c7426828cb..17957d8b0f 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -1701,18 +1701,26 @@ static inline Handle<T> NewHandleIfDifferent(ObjPtr<T> object, Handle<T> hint, H return (object != hint.Get()) ? graph->GetHandleCache()->NewHandle(object) : hint; } -static bool CanEncodeInlinedMethodInStackMap(const DexFile& caller_dex_file, ArtMethod* callee) +static bool CanEncodeInlinedMethodInStackMap(const DexFile& outer_dex_file, + ArtMethod* callee, + bool* out_needs_bss_check) REQUIRES_SHARED(Locks::mutator_lock_) { if (!Runtime::Current()->IsAotCompiler()) { // JIT can always encode methods in stack maps. return true; } - if (IsSameDexFile(caller_dex_file, *callee->GetDexFile())) { + if (IsSameDexFile(outer_dex_file, *callee->GetDexFile())) { return true; } + + // Inline across dexfiles if the callee's DexFile is in the bootclasspath. + if (callee->GetDeclaringClass()->GetClassLoader() == nullptr) { + *out_needs_bss_check = true; + return true; + } + // TODO(ngeoffray): Support more AOT cases for inlining: // - methods in multidex - // - methods in boot image for on-device non-PIC compilation. return false; } @@ -1822,6 +1830,11 @@ bool HInliner::CanInlineBody(const HGraph* callee_graph, return false; } + const bool too_many_registers = + total_number_of_dex_registers_ > kMaximumNumberOfCumulatedDexRegisters; + bool needs_bss_check = false; + const bool can_encode_in_stack_map = CanEncodeInlinedMethodInStackMap( + *outer_compilation_unit_.GetDexFile(), resolved_method, &needs_bss_check); size_t number_of_instructions = 0; // Skip the entry block, it does not contain instructions that prevent inlining. for (HBasicBlock* block : callee_graph->GetReversePostOrderSkipEntryBlock()) { @@ -1856,24 +1869,22 @@ bool HInliner::CanInlineBody(const HGraph* callee_graph, return false; } HInstruction* current = instr_it.Current(); - if (current->NeedsEnvironment() && - (total_number_of_dex_registers_ > kMaximumNumberOfCumulatedDexRegisters)) { - LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedEnvironmentBudget) - << "Method " << resolved_method->PrettyMethod() - << " is not inlined because its caller has reached" - << " its environment budget limit."; - return false; - } + if (current->NeedsEnvironment()) { + if (too_many_registers) { + LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedEnvironmentBudget) + << "Method " << resolved_method->PrettyMethod() + << " is not inlined because its caller has reached" + << " its environment budget limit."; + return false; + } - if (current->NeedsEnvironment() && - !CanEncodeInlinedMethodInStackMap(*caller_compilation_unit_.GetDexFile(), - resolved_method)) { - LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedStackMaps) - << "Method " << resolved_method->PrettyMethod() - << " could not be inlined because " << current->DebugName() - << " needs an environment, is in a different dex file" - << ", and cannot be encoded in the stack maps."; - return false; + if (!can_encode_in_stack_map) { + LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedStackMaps) + << "Method " << resolved_method->PrettyMethod() << " could not be inlined because " + << current->DebugName() << " needs an environment, is in a different dex file" + << ", and cannot be encoded in the stack maps."; + return false; + } } if (current->IsUnresolvedStaticFieldGet() || @@ -1887,6 +1898,21 @@ bool HInliner::CanInlineBody(const HGraph* callee_graph, << " entrypoint"; return false; } + + // We currently don't have support for inlining across dex files if the inlined method needs a + // .bss entry. This only happens when we are: + // 1) In AoT, + // 2) cross-dex inlining, and + // 3) have an instruction that needs a bss entry, which will always be + // 3)b) an instruction that needs an environment. + // TODO(solanes, 154012332): Add this support. + if (needs_bss_check && current->NeedsBss()) { + DCHECK(current->NeedsEnvironment()); + LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedBss) + << "Method " << resolved_method->PrettyMethod() + << " could not be inlined because it needs a BSS check"; + return false; + } } } diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 91b2e8bdc6..3f1f3b0f8d 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -2645,12 +2645,15 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { // Collect args and check for unexpected uses. // We expect one call to a constructor with no arguments, one constructor fence (unless // eliminated), some number of append calls and one call to StringBuilder.toString(). + bool constructor_inlined = false; bool seen_constructor = false; bool seen_constructor_fence = false; bool seen_to_string = false; uint32_t format = 0u; uint32_t num_args = 0u; HInstruction* args[StringBuilderAppend::kMaxArgs]; // Added in reverse order. + // When inlining, `maybe_new_array` tracks an environment use that we want to allow. + HInstruction* maybe_new_array = nullptr; for (HBackwardInstructionIterator iter(block->GetInstructions()); !iter.Done(); iter.Advance()) { HInstruction* user = iter.Current(); // Instructions of interest apply to `sb`, skip those that do not involve `sb`. @@ -2731,13 +2734,25 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { format = (format << StringBuilderAppend::kBitsPerArg) | static_cast<uint32_t>(arg); args[num_args] = as_invoke_virtual->InputAt(1u); ++num_args; - } else if (user->IsInvokeStaticOrDirect() && - user->AsInvokeStaticOrDirect()->GetResolvedMethod() != nullptr && - user->AsInvokeStaticOrDirect()->GetResolvedMethod()->IsConstructor() && - user->AsInvokeStaticOrDirect()->GetNumberOfArguments() == 1u) { - // After arguments, we should see the constructor. - // We accept only the constructor with no extra arguments. - DCHECK(!seen_constructor); + } else if (!seen_constructor) { + // At this point, we should see the constructor. However, we might have inlined it so we have + // to take care of both cases. We accept only the constructor with no extra arguments. This + // means that if we inline it, we have to check it is setting its field to a new array. + if (user->IsInvokeStaticOrDirect() && + user->AsInvokeStaticOrDirect()->GetResolvedMethod() != nullptr && + user->AsInvokeStaticOrDirect()->GetResolvedMethod()->IsConstructor() && + user->AsInvokeStaticOrDirect()->GetNumberOfArguments() == 1u) { + constructor_inlined = false; + } else if (user->IsInstanceFieldSet() && + user->AsInstanceFieldSet()->GetFieldType() == DataType::Type::kReference && + user->AsInstanceFieldSet()->InputAt(0) == sb && + user->AsInstanceFieldSet()->GetValue()->IsNewArray()) { + maybe_new_array = user->AsInstanceFieldSet()->GetValue(); + constructor_inlined = true; + } else { + // We were expecting a constructor but we haven't seen it. Abort optimization. + return false; + } DCHECK(!seen_constructor_fence); seen_constructor = true; } else if (user->IsConstructorFence()) { @@ -2763,6 +2778,10 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { // Accept only calls on the StringBuilder (which shall all be removed). // TODO: Carve-out for const-string? Or rely on environment pruning (to be implemented)? if (holder->InputCount() == 0 || holder->InputAt(0) != sb) { + // When inlining the constructor, we have a NewArray as an environment use. + if (constructor_inlined && holder == maybe_new_array) { + continue; + } return false; } } @@ -2796,6 +2815,18 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { while (sb->HasNonEnvironmentUses()) { block->RemoveInstruction(sb->GetUses().front().GetUser()); } + if (constructor_inlined) { + // We need to remove the inlined constructor instructions. That also removes all remaining + // environment uses. + DCHECK(sb->HasEnvironmentUses()); + DCHECK(maybe_new_array != nullptr); + DCHECK(maybe_new_array->IsNewArray()); + DCHECK(maybe_new_array->HasNonEnvironmentUses()); + HInstruction* fence = maybe_new_array->GetUses().front().GetUser(); + DCHECK(fence->IsConstructorFence()); + block->RemoveInstruction(fence); + block->RemoveInstruction(maybe_new_array); + } DCHECK(!sb->HasEnvironmentUses()); block->RemoveInstruction(sb); return true; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 978e7c419e..16e26dc7bc 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -2288,6 +2288,9 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { } virtual bool NeedsEnvironment() const { return false; } + virtual bool NeedsBss() const { + return false; + } uint32_t GetDexPc() const { return dex_pc_; } @@ -4916,6 +4919,9 @@ class HInvokeStaticOrDirect final : public HInvoke { } bool IsClonable() const override { return true; } + bool NeedsBss() const override { + return GetMethodLoadKind() == MethodLoadKind::kBssEntry; + } void SetDispatchInfo(DispatchInfo dispatch_info) { bool had_current_method_input = HasCurrentMethodInput(); @@ -5201,6 +5207,9 @@ class HInvokeInterface final : public HInvoke { } bool IsClonable() const override { return true; } + bool NeedsBss() const override { + return GetHiddenArgumentLoadKind() == MethodLoadKind::kBssEntry; + } bool CanDoImplicitNullCheckOn(HInstruction* obj) const override { // TODO: Add implicit null checks in intrinsics. @@ -6847,6 +6856,12 @@ class HLoadClass final : public HInstruction { bool NeedsEnvironment() const override { return CanCallRuntime(); } + bool NeedsBss() const override { + LoadKind load_kind = GetLoadKind(); + return load_kind == LoadKind::kBssEntry || + load_kind == LoadKind::kBssEntryPublic || + load_kind == LoadKind::kBssEntryPackage; + } void SetMustGenerateClinitCheck(bool generate_clinit_check) { SetPackedFlag<kFlagGenerateClInitCheck>(generate_clinit_check); @@ -7044,6 +7059,9 @@ class HLoadString final : public HInstruction { } bool IsClonable() const override { return true; } + bool NeedsBss() const override { + return GetLoadKind() == LoadKind::kBssEntry; + } void SetLoadKind(LoadKind load_kind); diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h index 58d65bb97d..622fec3521 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -81,6 +81,7 @@ enum class MethodCompilationStat { kSimplifyThrowingInvoke, kInstructionSunk, kNotInlinedUnresolvedEntrypoint, + kNotInlinedBss, kNotInlinedDexCache, kNotInlinedStackMaps, kNotInlinedEnvironmentBudget, diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc index e52a3ce272..964b48c6ac 100644 --- a/compiler/optimizing/stack_map_stream.cc +++ b/compiler/optimizing/stack_map_stream.cc @@ -17,10 +17,14 @@ #include "stack_map_stream.h" #include <memory> +#include <vector> #include "art_method-inl.h" #include "base/stl_util.h" +#include "class_linker.h" +#include "dex/dex_file.h" #include "dex/dex_file_types.h" +#include "optimizing/nodes.h" #include "optimizing/optimizing_compiler.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" @@ -211,12 +215,26 @@ void StackMapStream::BeginInlineInfoEntry(ArtMethod* method, entry[InlineInfo::kArtMethodHi] = High32Bits(reinterpret_cast<uintptr_t>(method)); entry[InlineInfo::kArtMethodLo] = Low32Bits(reinterpret_cast<uintptr_t>(method)); } else { - if (dex_pc != static_cast<uint32_t>(-1) && kIsDebugBuild) { + uint32_t bootclasspath_index = MethodInfo::kSameDexFile; + if (dex_pc != static_cast<uint32_t>(-1)) { ScopedObjectAccess soa(Thread::Current()); - DCHECK(IsSameDexFile(*outer_dex_file, *method->GetDexFile())); + const DexFile* dex_file = method->GetDexFile(); + if (method->GetDeclaringClass()->GetClassLoader() == nullptr) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + const std::vector<const DexFile*>& boot_class_path = class_linker->GetBootClassPath(); + auto it = std::find_if( + boot_class_path.begin(), boot_class_path.end(), [dex_file](const DexFile* df) { + return IsSameDexFile(*df, *dex_file); + }); + DCHECK(it != boot_class_path.end()); + bootclasspath_index = std::distance(boot_class_path.begin(), it); + } else { + DCHECK(IsSameDexFile(*outer_dex_file, *dex_file)); + } } uint32_t dex_method_index = method->GetDexMethodIndex(); - entry[InlineInfo::kMethodInfoIndex] = method_infos_.Dedup({dex_method_index}); + entry[InlineInfo::kMethodInfoIndex] = + method_infos_.Dedup({dex_method_index, bootclasspath_index}); } current_inline_infos_.push_back(entry); @@ -232,7 +250,18 @@ void StackMapStream::BeginInlineInfoEntry(ArtMethod* method, if (encode_art_method) { CHECK_EQ(inline_info.GetArtMethod(), method); } else { - CHECK_EQ(code_info.GetMethodIndexOf(inline_info), method->GetDexMethodIndex()); + MethodInfo method_info = code_info.GetMethodInfoOf(inline_info); + CHECK_EQ(method_info.GetMethodIndex(), method->GetDexMethodIndex()); + if (inline_info.GetDexPc() != static_cast<uint32_t>(-1)) { + ScopedObjectAccess soa(Thread::Current()); + if (method->GetDeclaringClass()->GetClassLoader() == nullptr) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + const std::vector<const DexFile*>& boot_class_path = class_linker->GetBootClassPath(); + DCHECK_LT(method_info.GetDexFileIndex(), boot_class_path.size()); + CHECK(IsSameDexFile(*boot_class_path[method_info.GetDexFileIndex()], + *method->GetDexFile())); + } + } } }); } diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 84299d5077..5b931a15be 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -80,10 +80,20 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, for (InlineInfo inline_info : inline_infos) { DCHECK(!inline_info.EncodesArtMethod()); DCHECK_NE(inline_info.GetDexPc(), static_cast<uint32_t>(-1)); - uint32_t method_index = code_info.GetMethodIndexOf(inline_info); - ArtMethod* inlined_method = class_linker->LookupResolvedMethod(method_index, - method->GetDexCache(), - method->GetClassLoader()); + MethodInfo method_info = code_info.GetMethodInfoOf(inline_info); + uint32_t method_index = method_info.GetMethodIndex(); + ArtMethod* inlined_method; + if (method_info.HasDexFileIndex()) { + const DexFile* dex_file = class_linker->GetBootClassPath()[method_info.GetDexFileIndex()]; + ObjPtr<mirror::DexCache> dex_cache = class_linker->FindDexCache(Thread::Current(), *dex_file); + // The class loader is always nullptr for this case so we can simplify the call. + DCHECK_EQ(dex_cache->GetClassLoader(), nullptr); + inlined_method = class_linker->LookupResolvedMethod(method_index, dex_cache, nullptr); + } else { + inlined_method = class_linker->LookupResolvedMethod( + method_index, outer_method->GetDexCache(), outer_method->GetClassLoader()); + } + if (UNLIKELY(inlined_method == nullptr)) { LOG(FATAL) << "Could not find an inlined method from an .oat file: " << method->GetDexFile()->PrettyMethod(method_index) << " . " @@ -91,7 +101,8 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, UNREACHABLE(); } DCHECK(!inlined_method->IsRuntimeMethod()); - if (UNLIKELY(inlined_method->GetDexFile() != method->GetDexFile())) { + if (UNLIKELY(inlined_method->GetDexFile() != method->GetDexFile() && + !method_info.HasDexFileIndex())) { // TODO: We could permit inlining within a multi-dex oat file and the boot image, // even going back from boot image methods to the same oat file. However, this is // not currently implemented in the compiler. Therefore crossing dex file boundary @@ -99,13 +110,14 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method, bool target_sdk_at_least_p = IsSdkVersionSetAndAtLeast(Runtime::Current()->GetTargetSdkVersion(), SdkVersion::kP); LOG(target_sdk_at_least_p ? FATAL : WARNING) - << "Inlined method resolution crossed dex file boundary: from " - << method->PrettyMethod() + << "Inlined method resolution crossed dex file boundary: from " << method->PrettyMethod() << " in " << method->GetDexFile()->GetLocation() << "/" - << static_cast<const void*>(method->GetDexFile()) - << " to " << inlined_method->PrettyMethod() - << " in " << inlined_method->GetDexFile()->GetLocation() << "/" - << static_cast<const void*>(inlined_method->GetDexFile()) << ". " + << static_cast<const void*>(method->GetDexFile()) << " to " + << inlined_method->PrettyMethod() << " in " << inlined_method->GetDexFile()->GetLocation() + << "/" << static_cast<const void*>(inlined_method->GetDexFile()) << ". " + << "The outermost method in the chain is: " << outer_method->PrettyMethod() << " in " + << outer_method->GetDexFile()->GetLocation() << "/" + << static_cast<const void*>(outer_method->GetDexFile()) << "This must be due to duplicate classes or playing wrongly with class loaders. " << "The runtime is in an unsafe state."; } diff --git a/runtime/oat.h b/runtime/oat.h index ac70a7755c..6a20cc1bd9 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,8 +32,8 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } }; - // Last oat version changed reason: Introduced new entry points for method entry / exit hooks. - static constexpr std::array<uint8_t, 4> kOatVersion{ {'2', '0', '4', '\0'} }; + // Last oat version changed reason: Inlining across dex files for bootclasspath methods. + static constexpr std::array<uint8_t, 4> kOatVersion{ {'2', '0', '5', '\0'} }; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; static constexpr const char* kDebuggableKey = "debuggable"; diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc index c160e2b4b3..939c4b34cf 100644 --- a/runtime/stack_map.cc +++ b/runtime/stack_map.cc @@ -347,9 +347,12 @@ void InlineInfo::Dump(VariableIndentationOutputStream* vios, ScopedObjectAccess soa(Thread::Current()); vios->Stream() << ", method=" << GetArtMethod()->PrettyMethod(); } else { - vios->Stream() - << std::dec - << ", method_index=" << code_info.GetMethodIndexOf(*this); + MethodInfo method_info = code_info.GetMethodInfoOf(*this); + vios->Stream() << std::dec << ", method_index=" << method_info.GetMethodIndex(); + if (method_info.HasDexFileIndex()) { + vios->Stream() << std::dec + << ", boot_class_path_dex_file_index=" << method_info.GetDexFileIndex(); + } } vios->Stream() << ")\n"; code_info.GetInlineDexRegisterMapOf(stack_map, *this).Dump(vios); diff --git a/runtime/stack_map.h b/runtime/stack_map.h index 103402baa6..714895744b 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -262,10 +262,13 @@ class RegisterMask : public BitTableAccessor<2> { // Method indices are not very dedup friendly. // Separating them greatly improves dedup efficiency of the other tables. -class MethodInfo : public BitTableAccessor<1> { +class MethodInfo : public BitTableAccessor<2> { public: BIT_TABLE_HEADER(MethodInfo) BIT_TABLE_COLUMN(0, MethodIndex) + BIT_TABLE_COLUMN(1, DexFileIndex) + + static constexpr uint32_t kSameDexFile = -1; }; /** @@ -360,8 +363,12 @@ class CodeInfo { return stack_maps_.NumRows(); } + MethodInfo GetMethodInfoOf(InlineInfo inline_info) const { + return method_infos_.GetRow(inline_info.GetMethodInfoIndex()); + } + uint32_t GetMethodIndexOf(InlineInfo inline_info) const { - return method_infos_.GetRow(inline_info.GetMethodInfoIndex()).GetMethodIndex(); + return GetMethodInfoOf(inline_info).GetMethodIndex(); } ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map) const { diff --git a/test/580-checker-string-fact-intrinsics/src-art/Main.java b/test/580-checker-string-fact-intrinsics/src-art/Main.java index d0750f9ae8..34af579a4b 100644 --- a/test/580-checker-string-fact-intrinsics/src-art/Main.java +++ b/test/580-checker-string-fact-intrinsics/src-art/Main.java @@ -40,16 +40,14 @@ public class Main { // java.lang.StringFactory.newStringFromChars(char[] data) // // which contains a call to the former (non-public) native method. - // However, this call will not be inlined (because it is a method in - // another Dex file and which contains a call, which needs an - // environment), so we cannot use Checker here to ensure the native - // call was intrinsified either. + // After the inliner runs, we can see the inlined call and check + // that the compiler intrinsifies it. /// CHECK-START: void Main.testNewStringFromChars() builder (after) /// CHECK-DAG: InvokeStaticOrDirect method_name:java.lang.StringFactory.newStringFromChars intrinsic:None /// CHECK-START: void Main.testNewStringFromChars() inliner (after) - /// CHECK-DAG: InvokeStaticOrDirect method_name:java.lang.StringFactory.newStringFromChars intrinsic:None + /// CHECK-DAG: InvokeStaticOrDirect method_name:java.lang.StringFactory.newStringFromChars intrinsic:StringNewStringFromChars public static void testNewStringFromChars() { char[] chars = { 'b', 'a', 'r' }; |