summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/optimizing/inliner.cc66
-rw-r--r--compiler/optimizing/instruction_simplifier.cc45
-rw-r--r--compiler/optimizing/nodes.h18
-rw-r--r--compiler/optimizing/optimizing_compiler_stats.h1
-rw-r--r--compiler/optimizing/stack_map_stream.cc37
-rw-r--r--runtime/entrypoints/entrypoint_utils-inl.h34
-rw-r--r--runtime/oat.h4
-rw-r--r--runtime/stack_map.cc9
-rw-r--r--runtime/stack_map.h11
-rw-r--r--test/580-checker-string-fact-intrinsics/src-art/Main.java8
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' };