diff options
-rw-r--r-- | compiler/Android.bp | 1 | ||||
-rw-r--r-- | compiler/optimizing/code_sinking.cc | 5 | ||||
-rw-r--r-- | compiler/optimizing/constructor_fence_redundancy_elimination.cc | 261 | ||||
-rw-r--r-- | compiler/optimizing/constructor_fence_redundancy_elimination.h | 64 | ||||
-rw-r--r-- | compiler/optimizing/nodes.cc | 53 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 13 | ||||
-rw-r--r-- | compiler/optimizing/optimizing_compiler.cc | 7 | ||||
-rw-r--r-- | compiler/optimizing/optimizing_compiler_stats.h | 2 | ||||
-rw-r--r-- | runtime/base/arena_allocator.cc | 1 | ||||
-rw-r--r-- | runtime/base/arena_allocator.h | 1 | ||||
-rw-r--r-- | test/476-checker-ctor-fence-redun-elim/expected.txt | 0 | ||||
-rw-r--r-- | test/476-checker-ctor-fence-redun-elim/info.txt | 2 | ||||
-rw-r--r-- | test/476-checker-ctor-fence-redun-elim/src/Main.java | 844 |
13 files changed, 1247 insertions, 7 deletions
diff --git a/compiler/Android.bp b/compiler/Android.bp index f11d25675c..d060dd49de 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -54,6 +54,7 @@ art_cc_defaults { "optimizing/code_generator_utils.cc", "optimizing/code_sinking.cc", "optimizing/constant_folding.cc", + "optimizing/constructor_fence_redundancy_elimination.cc", "optimizing/dead_code_elimination.cc", "optimizing/escape.cc", "optimizing/graph_checker.cc", diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc index 6c3a9fd6b5..b558eb17a7 100644 --- a/compiler/optimizing/code_sinking.cc +++ b/compiler/optimizing/code_sinking.cc @@ -64,6 +64,11 @@ static bool IsInterestingInstruction(HInstruction* instruction) { // A fence with "0" inputs is dead and should've been removed in a prior pass. DCHECK_NE(0u, ctor_fence->InputCount()); + // TODO: this should be simplified to 'return true' since it's + // potentially pessimizing any code sinking for inlined constructors with final fields. + // TODO: double check that if the final field assignments are not moved, + // then the fence is not moved either. + return ctor_fence->GetAssociatedAllocation() != nullptr; } diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.cc b/compiler/optimizing/constructor_fence_redundancy_elimination.cc new file mode 100644 index 0000000000..ff7ce60905 --- /dev/null +++ b/compiler/optimizing/constructor_fence_redundancy_elimination.cc @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "constructor_fence_redundancy_elimination.h" + +#include "base/arena_allocator.h" + +namespace art { + +static constexpr bool kCfreLogFenceInputCount = false; + +// TODO: refactor this code by reusing escape analysis. +class CFREVisitor : public HGraphVisitor { + public: + CFREVisitor(HGraph* graph, OptimizingCompilerStats* stats) + : HGraphVisitor(graph), + scoped_allocator_(graph->GetArena()->GetArenaPool()), + candidate_fences_(scoped_allocator_.Adapter(kArenaAllocCFRE)), + candidate_fence_targets_(scoped_allocator_.Adapter(kArenaAllocCFRE)), + stats_(stats) {} + + void VisitBasicBlock(HBasicBlock* block) OVERRIDE { + // Visit all instructions in block. + HGraphVisitor::VisitBasicBlock(block); + + // If there were any unmerged fences left, merge them together, + // the objects are considered 'published' at the end of the block. + MergeCandidateFences(); + } + + void VisitConstructorFence(HConstructorFence* constructor_fence) OVERRIDE { + candidate_fences_.push_back(constructor_fence); + + for (size_t input_idx = 0; input_idx < constructor_fence->InputCount(); ++input_idx) { + candidate_fence_targets_.Insert(constructor_fence->InputAt(input_idx)); + } + } + + void VisitBoundType(HBoundType* bound_type) OVERRIDE { + VisitAlias(bound_type); + } + + void VisitNullCheck(HNullCheck* null_check) OVERRIDE { + VisitAlias(null_check); + } + + void VisitSelect(HSelect* select) OVERRIDE { + VisitAlias(select); + } + + void VisitInstanceFieldSet(HInstanceFieldSet* instruction) OVERRIDE { + HInstruction* value = instruction->InputAt(1); + VisitSetLocation(instruction, value); + } + + void VisitStaticFieldSet(HStaticFieldSet* instruction) OVERRIDE { + HInstruction* value = instruction->InputAt(1); + VisitSetLocation(instruction, value); + } + + void VisitArraySet(HArraySet* instruction) OVERRIDE { + HInstruction* value = instruction->InputAt(2); + VisitSetLocation(instruction, value); + } + + void VisitDeoptimize(HDeoptimize* instruction ATTRIBUTE_UNUSED) { + // Pessimize: Merge all fences. + MergeCandidateFences(); + } + + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE { + HandleInvoke(invoke); + } + + void VisitInvokeVirtual(HInvokeVirtual* invoke) OVERRIDE { + HandleInvoke(invoke); + } + + void VisitInvokeInterface(HInvokeInterface* invoke) OVERRIDE { + HandleInvoke(invoke); + } + + void VisitInvokeUnresolved(HInvokeUnresolved* invoke) OVERRIDE { + HandleInvoke(invoke); + } + + void VisitInvokePolymorphic(HInvokePolymorphic* invoke) OVERRIDE { + HandleInvoke(invoke); + } + + void VisitClinitCheck(HClinitCheck* clinit) OVERRIDE { + HandleInvoke(clinit); + } + + void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* instruction) OVERRIDE { + // Conservatively treat it as an invocation. + HandleInvoke(instruction); + } + + void VisitUnresolvedInstanceFieldSet(HUnresolvedInstanceFieldSet* instruction) OVERRIDE { + // Conservatively treat it as an invocation. + HandleInvoke(instruction); + } + + void VisitUnresolvedStaticFieldGet(HUnresolvedStaticFieldGet* instruction) OVERRIDE { + // Conservatively treat it as an invocation. + HandleInvoke(instruction); + } + + void VisitUnresolvedStaticFieldSet(HUnresolvedStaticFieldSet* instruction) OVERRIDE { + // Conservatively treat it as an invocation. + HandleInvoke(instruction); + } + + private: + void HandleInvoke(HInstruction* invoke) { + // An object is considered "published" if it escapes into an invoke as any of the parameters. + if (HasInterestingPublishTargetAsInput(invoke)) { + MergeCandidateFences(); + } + } + + // Called by any instruction visitor that may create an alias. + // These instructions may create an alias: + // - BoundType + // - NullCheck + // - Select + // + // These also create an alias, but are not handled by this function: + // - Phi: propagates values across blocks, but we always merge at the end of a block. + // - Invoke: this is handled by HandleInvoke. + void VisitAlias(HInstruction* aliasing_inst) { + // An object is considered "published" if it becomes aliased by other instructions. + if (HasInterestingPublishTargetAsInput(aliasing_inst)) { + // Note that constructing a "NullCheck" for new-instance, new-array, + // or a 'this' (receiver) reference is impossible. + // + // If by some reason we actually encounter such a NullCheck(FenceTarget), + // we LOG(WARNING). + if (UNLIKELY(aliasing_inst->IsNullCheck())) { + LOG(kIsDebugBuild ? FATAL : WARNING) + << "Unexpected instruction: NullCheck; should not be legal in graph"; + // We then do a best-effort to handle this case. + } + MergeCandidateFences(); + } + } + + void VisitSetLocation(HInstruction* inst ATTRIBUTE_UNUSED, HInstruction* store_input) { + // An object is considered "published" if it's stored onto the heap. + // Sidenote: A later "LSE" pass can still remove the fence if it proves the + // object doesn't actually escape. + if (IsInterestingPublishTarget(store_input)) { + // Merge all constructor fences that we've seen since + // the last interesting store (or since the beginning). + MergeCandidateFences(); + } + } + + bool HasInterestingPublishTargetAsInput(HInstruction* inst) { + for (size_t input_count = 0; input_count < inst->InputCount(); ++input_count) { + if (IsInterestingPublishTarget(inst->InputAt(input_count))) { + return true; + } + } + + return false; + } + + // Merges all the existing fences we've seen so far into the last-most fence. + // + // This resets the list of candidate fences and their targets back to {}. + void MergeCandidateFences() { + if (candidate_fences_.empty()) { + // Nothing to do, need 1+ fences to merge. + return; + } + + // The merge target is always the "last" candidate fence. + HConstructorFence* merge_target = candidate_fences_[candidate_fences_.size() - 1]; + + for (HConstructorFence* fence : candidate_fences_) { + MaybeMerge(merge_target, fence); + } + + if (kCfreLogFenceInputCount) { + LOG(INFO) << "CFRE-MergeCandidateFences: Post-merge fence input count " + << merge_target->InputCount(); + } + + // Each merge acts as a cut-off point. The optimization is reset completely. + // In theory, we could push the fence as far as its publish, but in practice + // there is no benefit to this extra complexity unless we also reordered + // the stores to come later. + candidate_fences_.clear(); + candidate_fence_targets_.Clear(); + } + + // A publishing 'store' is only interesting if the value being stored + // is one of the fence `targets` in `candidate_fences`. + bool IsInterestingPublishTarget(HInstruction* store_input) const { + return candidate_fence_targets_.Find(store_input) != candidate_fence_targets_.end(); + } + + void MaybeMerge(HConstructorFence* target, HConstructorFence* src) { + if (target == src) { + return; // Don't merge a fence into itself. + // This is mostly for stats-purposes, we don't want to count merge(x,x) + // as removing a fence because it's a no-op. + } + + target->Merge(src); + + MaybeRecordStat(stats_, MethodCompilationStat::kConstructorFenceRemovedCFRE); + } + + // Phase-local heap memory allocator for CFRE optimizer. Storage obtained + // through this allocator is immediately released when the CFRE optimizer is done. + ArenaAllocator scoped_allocator_; + + // Set of constructor fences that we've seen in the current block. + // Each constructor fences acts as a guard for one or more `targets`. + // There exist no stores to any `targets` between any of these fences. + // + // Fences are in succession order (e.g. fence[i] succeeds fence[i-1] + // within the same basic block). + ArenaVector<HConstructorFence*> candidate_fences_; + + // Stores a set of the fence targets, to allow faster lookup of whether + // a detected publish is a target of one of the candidate fences. + ArenaHashSet<HInstruction*> candidate_fence_targets_; + + // Used to record stats about the optimization. + OptimizingCompilerStats* const stats_; + + DISALLOW_COPY_AND_ASSIGN(CFREVisitor); +}; + +void ConstructorFenceRedundancyElimination::Run() { + CFREVisitor cfre_visitor(graph_, stats_); + + // Arbitrarily visit in reverse-post order. + // The exact block visit order does not matter, as the algorithm + // only operates on a single block at a time. + cfre_visitor.VisitReversePostOrder(); +} + +} // namespace art diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.h b/compiler/optimizing/constructor_fence_redundancy_elimination.h new file mode 100644 index 0000000000..d89210cd1c --- /dev/null +++ b/compiler/optimizing/constructor_fence_redundancy_elimination.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_ +#define ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_ + +#include "optimization.h" + +namespace art { + +/* + * Constructor Fence Redundancy Elimination (CFRE). + * + * A local optimization pass that merges redundant constructor fences + * together within the same basic block. + * + * Abbreviations: + * - CF: Constructor Fence + * - CFS: Constructor Fence Set + * - CFTargets: The unique set of the inputs of all the instructions in CFS. + * + * Given any CFS = { CF(x), CF(y), CF(z), ... }, define CFTargets = { x, y, z, ... }. + * - Publish(R) must not exist for any R in CFTargets if this Publish(R) is between any CF in CFS. + * - This type of Publish(R) is called an "interesting publish". + * + * A Publish(R) is considered any instruction at which the reference to "R" + * may escape (e.g. invoke, store, return, etc) to another thread. + * + * Starting at the beginning of the block: + * - Find the largest contiguous CFS. + * - If we see an interesting publish, merge all instructions in CFS into a single CF(CFTargets). + * - Repeat until the block is fully visited. + * - At the end of the block, merge all instructions in CFS into a single CF(CFTargets). + */ +class ConstructorFenceRedundancyElimination : public HOptimization { + public: + ConstructorFenceRedundancyElimination(HGraph* graph, + OptimizingCompilerStats* stats) + : HOptimization(graph, kPassName, stats) {} + + void Run() OVERRIDE; + + static constexpr const char* kPassName = "constructor_fence_redundancy_elimination"; + + private: + DISALLOW_COPY_AND_ASSIGN(ConstructorFenceRedundancyElimination); +}; + +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_ diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 8644f676e8..e34d4a2be6 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1271,18 +1271,59 @@ size_t HConstructorFence::RemoveConstructorFences(HInstruction* instruction) { return remove_count; } -HInstruction* HConstructorFence::GetAssociatedAllocation() { +void HConstructorFence::Merge(HConstructorFence* other) { + // Do not delete yourself from the graph. + DCHECK(this != other); + // Don't try to merge with an instruction not associated with a block. + DCHECK(other->GetBlock() != nullptr); + // A constructor fence's return type is "kPrimVoid" + // and therefore it cannot have any environment uses. + DCHECK(!other->HasEnvironmentUses()); + + auto has_input = [](HInstruction* haystack, HInstruction* needle) { + // Check if `haystack` has `needle` as any of its inputs. + for (size_t input_count = 0; input_count < haystack->InputCount(); ++input_count) { + if (haystack->InputAt(input_count) == needle) { + return true; + } + } + return false; + }; + + // Add any inputs from `other` into `this` if it wasn't already an input. + for (size_t input_count = 0; input_count < other->InputCount(); ++input_count) { + HInstruction* other_input = other->InputAt(input_count); + if (!has_input(this, other_input)) { + AddInput(other_input); + } + } + + other->GetBlock()->RemoveInstruction(other); +} + +HInstruction* HConstructorFence::GetAssociatedAllocation(bool ignore_inputs) { HInstruction* new_instance_inst = GetPrevious(); // Check if the immediately preceding instruction is a new-instance/new-array. // Otherwise this fence is for protecting final fields. if (new_instance_inst != nullptr && (new_instance_inst->IsNewInstance() || new_instance_inst->IsNewArray())) { - // TODO: Need to update this code to handle multiple inputs. - DCHECK_EQ(InputCount(), 1u); - return new_instance_inst; - } else { - return nullptr; + if (ignore_inputs) { + // If inputs are ignored, simply check if the predecessor is + // *any* HNewInstance/HNewArray. + // + // Inputs are normally only ignored for prepare_for_register_allocation, + // at which point *any* prior HNewInstance/Array can be considered + // associated. + return new_instance_inst; + } else { + // Normal case: There must be exactly 1 input and the previous instruction + // must be that input. + if (InputCount() == 1u && InputAt(0) == new_instance_inst) { + return new_instance_inst; + } + } } + return nullptr; } #define DEFINE_ACCEPT(name, super) \ diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 29be8acc0d..3e4928bd65 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -6634,13 +6634,24 @@ class HConstructorFence FINAL : public HVariableInputSizeInstruction { // Returns how many HConstructorFence instructions were removed from graph. static size_t RemoveConstructorFences(HInstruction* instruction); + // Combine all inputs of `this` and `other` instruction and remove + // `other` from the graph. + // + // Inputs are unique after the merge. + // + // Requirement: `this` must not be the same as `other. + void Merge(HConstructorFence* other); + // Check if this constructor fence is protecting // an HNewInstance or HNewArray that is also the immediate // predecessor of `this`. // + // If `ignore_inputs` is true, then the immediate predecessor doesn't need + // to be one of the inputs of `this`. + // // Returns the associated HNewArray or HNewInstance, // or null otherwise. - HInstruction* GetAssociatedAllocation(); + HInstruction* GetAssociatedAllocation(bool ignore_inputs = false); DECLARE_INSTRUCTION(ConstructorFence); diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 435ca1cad4..b45f3c6b33 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -55,6 +55,7 @@ #include "compiled_method.h" #include "compiler.h" #include "constant_folding.h" +#include "constructor_fence_redundancy_elimination.h" #include "dead_code_elimination.h" #include "debug/elf_debug_writer.h" #include "debug/method_debug_info.h" @@ -516,6 +517,8 @@ static HOptimization* BuildOptimization( return new (arena) CHAGuardOptimization(graph); } else if (opt_name == CodeSinking::kCodeSinkingPassName) { return new (arena) CodeSinking(graph, stats); + } else if (opt_name == ConstructorFenceRedundancyElimination::kPassName) { + return new (arena) ConstructorFenceRedundancyElimination(graph, stats); #ifdef ART_ENABLE_CODEGEN_arm } else if (opt_name == arm::InstructionSimplifierArm::kInstructionSimplifierArmPassName) { return new (arena) arm::InstructionSimplifierArm(graph, stats); @@ -786,6 +789,8 @@ void OptimizingCompiler::RunOptimizations(HGraph* graph, IntrinsicsRecognizer* intrinsics = new (arena) IntrinsicsRecognizer(graph, stats); CHAGuardOptimization* cha_guard = new (arena) CHAGuardOptimization(graph); CodeSinking* code_sinking = new (arena) CodeSinking(graph, stats); + ConstructorFenceRedundancyElimination* cfre = + new (arena) ConstructorFenceRedundancyElimination(graph, stats); HOptimization* optimizations1[] = { intrinsics, @@ -823,6 +828,8 @@ void OptimizingCompiler::RunOptimizations(HGraph* graph, // can satisfy. For example, the code generator does not expect to see a // HTypeConversion from a type to the same type. simplify4, + cfre, // Eliminate constructor fences after code sinking to avoid + // complicated sinking logic to split a fence with many inputs. }; RunOptimizations(optimizations2, arraysize(optimizations2), pass_observer); diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h index d6da73cc1c..af7ab2f1a0 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -91,6 +91,7 @@ enum MethodCompilationStat { kConstructorFenceGeneratedFinal, kConstructorFenceRemovedLSE, kConstructorFenceRemovedPFRA, + kConstructorFenceRemovedCFRE, kLastStat }; @@ -211,6 +212,7 @@ class OptimizingCompilerStats { case kConstructorFenceGeneratedFinal: name = "ConstructorFenceGeneratedFinal"; break; case kConstructorFenceRemovedLSE: name = "ConstructorFenceRemovedLSE"; break; case kConstructorFenceRemovedPFRA: name = "ConstructorFenceRemovedPFRA"; break; + case kConstructorFenceRemovedCFRE: name = "ConstructorFenceRemovedCFRE"; break; case kLastStat: LOG(FATAL) << "invalid stat " diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc index 148ef86059..8738adfada 100644 --- a/runtime/base/arena_allocator.cc +++ b/runtime/base/arena_allocator.cc @@ -73,6 +73,7 @@ const char* const ArenaAllocatorStatsImpl<kCount>::kAllocNames[] = { "BCE ", "DCE ", "LSE ", + "CFRE ", "LICM ", "LoopOpt ", "SsaLiveness ", diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h index 0b1a3bac9c..212edfbdb1 100644 --- a/runtime/base/arena_allocator.h +++ b/runtime/base/arena_allocator.h @@ -80,6 +80,7 @@ enum ArenaAllocKind { kArenaAllocBoundsCheckElimination, kArenaAllocDCE, kArenaAllocLSE, + kArenaAllocCFRE, kArenaAllocLICM, kArenaAllocLoopOptimization, kArenaAllocSsaLiveness, diff --git a/test/476-checker-ctor-fence-redun-elim/expected.txt b/test/476-checker-ctor-fence-redun-elim/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/476-checker-ctor-fence-redun-elim/expected.txt diff --git a/test/476-checker-ctor-fence-redun-elim/info.txt b/test/476-checker-ctor-fence-redun-elim/info.txt new file mode 100644 index 0000000000..46d62f76e0 --- /dev/null +++ b/test/476-checker-ctor-fence-redun-elim/info.txt @@ -0,0 +1,2 @@ +Tests to ensure constructor fences (after new-instance, new-array, or final fields) are properly +merged together by the compiler when they are redundant. diff --git a/test/476-checker-ctor-fence-redun-elim/src/Main.java b/test/476-checker-ctor-fence-redun-elim/src/Main.java new file mode 100644 index 0000000000..05f2f7c5cf --- /dev/null +++ b/test/476-checker-ctor-fence-redun-elim/src/Main.java @@ -0,0 +1,844 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +// Baseline class. This has no final fields, so there are no additional freezes +// in its constructor. +// +// The new-instance itself always has 1 freeze for the happens-before on the object header +// write (i.e. [obj.class = X] happens-before any access to obj). +// +// Total freezes for "new Base()": 1. +class Base { + int w0; + int w1; + int w2; + int w3; + + @Override + public String toString() { + return getClass().getName() + "(" + baseString() + ")"; + } + + protected String baseString() { + return String.format("w0: %d, w1: %d, w2: %d, w3: %d", w0, w1, w2, w3); + } +} + +// This has a final field in its constructor, so there must be a field freeze +// at the end of <init>. +// +// Total freezes for "new OneFinal()": 2. +class OneFinal extends Base { + final int x; + OneFinal(int x) { + this.x = x; + } + + @Override + protected String baseString() { + return String.format("%s, x: %d", super.baseString(), x); + } +} + +class Assert { + public static void stringEquals(String expected, Object actual) { + stringEquals$noinline$(expected, actual); + } + + // Forbid compiler from inlining this to avoid overly clever optimizations. + private static void stringEquals$noinline$(String expected, Object actual) { + String actualStr = Main.valueToString(actual); + if (!expected.equals(actualStr)) { + throw new AssertionError("Expected: " + expected + ", actual: " + actualStr); + } + } +} + +interface Test { + public void exercise(); + public void check(); +} + +class TestOneFinal implements Test { + // Initialize at least once before actual test. + public static Object external; + + /// CHECK-START: void TestOneFinal.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + + /// CHECK-START: void TestOneFinal.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + @Override + public void exercise() { + Base b = new OneFinal(1); + // 1 store, 2 freezes. + + // Stores to 'b' do not escape b. + b.w0 = 1; + b.w1 = 2; + b.w2 = 3; + + // Publish the result to a global so that it is not LSE-eliminated. + external = b; + } + + @Override + public void check() { + Assert.stringEquals("OneFinal(w0: 1, w1: 2, w2: 3, w3: 0, x: 1)", external); + } +} + +// This has a final field in its constructor, so there must be a field freeze +// at the end of <init>. The previous base class's freezes accumulate on top +// of this one. +// +// Total freezes for "new TwoFinal()": 3. +class TwoFinal extends OneFinal { + final int y; + TwoFinal(int x, int y) { + super(x); + this.y = y; + } + + @Override + protected String baseString() { + return String.format("%s, y: %d", super.baseString(), y); + } +} + +// This has a final field in its constructor, so there must be a field freeze +// at the end of <init>. The previous base class's freezes accumulate on top +// of this one. +// +// Total freezes for "new ThreeFinal()": 4. +class ThreeFinal extends TwoFinal { + final int z; + ThreeFinal(int x, int y, int z) { + super(x, y); + this.z = z; + } + + @Override + protected String baseString() { + return String.format("%s, z: %d", super.baseString(), z); + } +} + +class TestThreeFinal implements Test { + // Initialize at least once before actual test. + public static Object external; + + /// CHECK-START: void TestThreeFinal.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + + /// CHECK-START: void TestThreeFinal.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + @Override + public void exercise() { + Base b = new ThreeFinal(1, 1, 2); + // 3 store, 4 freezes. + + // Stores to 'b' do not escape b. + b.w0 = 3; + + // Publish the result to a global so that it is not LSE-eliminated. + external = b; + } + + @Override + public void check() { + Assert.stringEquals("ThreeFinal(w0: 3, w1: 0, w2: 0, w3: 0, x: 1, y: 1, z: 2)", external); + } +} + +// Ensure "freezes" between multiple new-instances are optimized out. +class TestMultiAlloc implements Test { + public static Object external; + public static Object external2; + + /// CHECK-START: void TestMultiAlloc.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + + /// CHECK-START: void TestMultiAlloc.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + @Override + public void exercise() { + // 1 freeze + Base b = new Base(); + // 1 freeze + Base b2 = new Base(); + + // Merge 2 freezes above into 1 constructor fence. + external = b; + external2 = b2; + } + + @Override + public void check() { + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2); + } +} + +// Ensure "freezes" between multiple new-instances are optimized out. +class TestThreeFinalTwice implements Test { + // Initialize at least once before actual test. + public static Object external; + public static Object external2; + + /// CHECK-START: void TestThreeFinalTwice.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance>>] + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + + /// CHECK-START: void TestThreeFinalTwice.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + @Override + public void exercise() { + Base b = new ThreeFinal(1, 1, 2); + // 3 store, 4 freezes. + + // Stores to 'b' do not escape b. + b.w0 = 3; + + Base b2 = new ThreeFinal(4, 5, 6); + // 3 store, 4 freezes. + + // Stores to 'b2' do not escape b2. + b2.w0 = 7; + + // Publish the result to a global so that it is not LSE-eliminated. + // Publishing is done at the end to give freezes above a chance to merge. + external = b; + external2 = b2; + } + + @Override + public void check() { + Assert.stringEquals("ThreeFinal(w0: 3, w1: 0, w2: 0, w3: 0, x: 1, y: 1, z: 2)", external); + Assert.stringEquals("ThreeFinal(w0: 7, w1: 0, w2: 0, w3: 0, x: 4, y: 5, z: 6)", external2); + } +} + +class TestNonEscaping { + // Prevent constant folding. + static boolean test; + + static Object external; + static Object external2; + static Object external3; + static Object external4; + + static class Invoke implements Test { + /// CHECK-START: void TestNonEscaping$Invoke.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK: InvokeStaticOrDirect + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestNonEscaping$Invoke.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: InvokeStaticOrDirect + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // b cannot possibly escape into this invoke because it hasn't escaped onto the heap earlier, + // and the invoke doesn't take it as a parameter. + noEscape$noinline$(); + + // Remove the Constructor Fence for b, merging into the fence for b2. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external = b; + external2 = b2; + } + + @Override + public void check() { + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2); + } + } + + public static int[] array = new int[1]; + static Base base = new Base(); + + static class Store implements Test { + /// CHECK-START: void TestNonEscaping$Store.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ArraySet + /// CHECK-DAG: StaticFieldSet + /// CHECK-DAG: InstanceFieldSet + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestNonEscaping$Store.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // Stores of inputs other than the fence target do not publish 'b'. + array[0] = b.w0; // aput + external = array; // sput + base.w0 = b.w0; // iput + + // Remove the Constructor Fence for b, merging into the fence for b2. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals("[0]", array); + Assert.stringEquals("[0]", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", base); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + private static void noEscape$noinline$() { + } +} + +class TestDontOptimizeAcrossBlocks implements Test { + // Prevent constant folding. + static boolean test; + + static Object external; + static Object external3; + + /// CHECK-START: void TestDontOptimizeAcrossBlocks.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + + /// CHECK-START: void TestDontOptimizeAcrossBlocks.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>] + @Override + public void exercise() { + Base b = new Base(); + + // Do not move constructor fence across this block, even though 'b' is not published yet. + if (test) { + external = null; + } + + Base b2 = new Base(); + external = b2; + external3 = b; + } + + @Override + public void check() { + Assert.stringEquals("false", test); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + } +} + +class TestDontOptimizeAcrossEscape { + // Prevent constant folding. + static boolean test; + + static Object external; + static Object external2; + static Object external3; + static Object external4; + + static class Invoke implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$Invoke.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK: InvokeStaticOrDirect + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$Invoke.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK: InvokeStaticOrDirect + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + // Do not optimize across invokes into which the fence target escapes. + invoke$noinline$(b); + + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external = b; + external2 = b2; + } + + private static void invoke$noinline$(Object b) { + // Even though 'b' does not escape this method, we conservatively assume all parameters + // of an invoke escape. + } + + @Override + public void check() { + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2); + } + } + + public static Object[] array = new Object[3]; + static Base base = new Base(); + + static class InstanceEscaper { + public Object holder; + + @Override + public String toString() { + return getClass().getName() + "(" + baseString() + ")"; + } + + protected String baseString() { + return String.format("holder: %s", Main.valueToString(holder)); + } + } + + static InstanceEscaper instanceEscaper = new InstanceEscaper(); + + static class StoreIput implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreIput.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: InstanceFieldSet + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreIput.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // A store of 'b' into another instance will publish 'b'. + instanceEscaper.holder = b; + + // Do not remove any constructor fences above. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals( + "TestDontOptimizeAcrossEscape$InstanceEscaper(holder: Base(w0: 0, w1: 0, w2: 0, w3: 0))", + instanceEscaper); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + static class StoreAput implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreAput.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: ArraySet + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreAput.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // A store of 'b' into another array will publish 'b'. + array[0] = b; // aput + + // Do not remove any constructor fences above. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals("[Base(w0: 0, w1: 0, w2: 0, w3: 0),<null>,<null>]", array); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + static class StoreSput implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreSput.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: StaticFieldSet + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreSput.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // A store of 'b' into a static will publish 'b'. + external = b; + + // Do not remove any constructor fences above. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + static class Deopt implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$Deopt.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: Deoptimize + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$Deopt.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + // An array access generates a Deopt to avoid doing bounds check. + array[0] = external; // aput + array[1] = external; // aput + array[2] = external; // aput + + // Do not remove any constructor fences above. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals("[Base(w0: 0, w1: 0, w2: 0, w3: 0)," + + "Base(w0: 0, w1: 0, w2: 0, w3: 0)," + + "Base(w0: 0, w1: 0, w2: 0, w3: 0)]", + array); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + static class Select implements Test { + /// CHECK-START: void TestDontOptimizeAcrossEscape$Select.exercise() constructor_fence_redundancy_elimination (before) + /// CHECK: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: Select + /// CHECK: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$Select.exercise() constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance + /// CHECK-DAG: ConstructorFence [<<NewInstance2>>] + /// CHECK-NOT: ConstructorFence + @Override + public void exercise() { + Base b = new Base(); + + boolean localTest = test; + Object localExternal = external3; + + // Selecting 'b' creates an alias, which we conservatively assume escapes immediately. + external = localTest ? b : localExternal; + + // Do not remove any constructor fences above. + Base b2 = new Base(); + + // Do not LSE-eliminate b,b2 + external3 = b; + external4 = b2; + } + + @Override + public void check() { + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3); + Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4); + } + } + + static class MakeBoundTypeTest implements Test { + public static Object makeBoundType; + public static Object makeBoundTypeSub; + + @Override + public void exercise() { + // Note: MakeBoundType is special and we have to call the constructor directly + // to prevent inlining it. + try { + makeBoundType = exerciseNewInstance(MakeBoundType.class, 123); + makeBoundTypeSub = exerciseNewInstance(MakeBoundTypeSub.class, 123); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void check() { + Assert.stringEquals( + "TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType(abcdefgh: 123, x: 2)", + makeBoundType); + Assert.stringEquals( + "TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundTypeSub(abcdefgh: 123, x: 1)", + makeBoundTypeSub); + } + + // Make a new instance of 'klass'. + private static <T> T exerciseNewInstance(Class<T> klass, int params) throws Exception { + return klass.cast(klass.getDeclaredConstructor(int.class).newInstance(params)); + } + + /// CHECK-START: void TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType.<init>(int) constructor_fence_redundancy_elimination (before) + /// CHECK-DAG: <<This:l\d+>> ParameterValue + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: BoundType + /// CHECK-DAG: ConstructorFence [<<This>>] + /// CHECK-NOT: ConstructorFence + + /// CHECK-START: void TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType.<init>(int) constructor_fence_redundancy_elimination (after) + /// CHECK-DAG: <<This:l\d+>> ParameterValue + /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance + /// CHECK: ConstructorFence [<<NewInstance>>] + /// CHECK-DAG: BoundType + /// CHECK-DAG: ConstructorFence [<<This>>] + /// CHECK-NOT: ConstructorFence + static class MakeBoundType { + final int abcdefgh; + int x; + + MakeBoundType(int param) { + abcdefgh = param; + + Base b = new Base(); + // constructor-fence(b) + + if (this instanceof MakeBoundTypeSub) { + // Create a "BoundType(this)" which prevents + // a merged constructor-fence(this, b) + x = 1; + } else { + x = 2; + } + + // publish(b). + external = b; + + // constructor-fence(this) + } + + @Override + public String toString() { + return getClass().getName() + "(" + baseString() + ")"; + } + + protected String baseString() { + return String.format("abcdefgh: %d, x: %d", abcdefgh, x); + } + } + + static class MakeBoundTypeSub extends MakeBoundType { + MakeBoundTypeSub(int xyz) { + super(xyz); + } + } + } +} + +public class Main { + public static void main(String[] args) throws Exception { + // Ensure that all of this code does not get optimized out into a no-op + // by actually running the code with reflection, then validating + // the result by asserting it against a string. + Class<? extends Test>[] testClasses = new Class[] { + TestOneFinal.class, + TestThreeFinal.class, + TestMultiAlloc.class, + TestThreeFinalTwice.class, + TestNonEscaping.Invoke.class, + TestNonEscaping.Store.class, + TestDontOptimizeAcrossBlocks.class, + TestDontOptimizeAcrossEscape.Invoke.class, + TestDontOptimizeAcrossEscape.StoreIput.class, + TestDontOptimizeAcrossEscape.StoreAput.class, + TestDontOptimizeAcrossEscape.StoreSput.class, + TestDontOptimizeAcrossEscape.Deopt.class, + TestDontOptimizeAcrossEscape.Select.class, + TestDontOptimizeAcrossEscape.MakeBoundTypeTest.class, + }; + + for (Class<? extends Test> klass : testClasses) { + exerciseTestClass(klass); + } + } + + /** + * Invoke Test#exercise(), then Test#check(). + * @throws AssertionError if test fails. + */ + private static void exerciseTestClass(Class<? extends Test> klass) throws Exception { + Test instance = klass.cast(klass.getDeclaredConstructor().newInstance()); + + // Use reflection as a best-effort to avoid compiler optimizations (e.g. inlining). + instance.getClass().getDeclaredMethod("exercise").invoke(instance); + instance.getClass().getDeclaredMethod("check").invoke(instance); + } + + // Print an object, with special handling for array and null. + public static String valueToString(Object val) { + if (val == null) { + return "<null>"; + } + if (val.getClass().isArray()) { + String fmt = "["; + int length = Array.getLength(val); + for (int i = 0; i < length; ++i) { + Object arrayElement = Array.get(val, i); + fmt += valueToString(arrayElement); + + if (i != length - 1) { + fmt += ","; + } + } + fmt += "]"; + + return fmt; + } + + return val.toString(); + } +} |