optimizing: add block-scoped constructor fence merging pass
Introduce a new "Constructor Fence Redundancy Elimination" pass.
The pass currently performs local optimization only, i.e. within instructions
in the same basic block.
All constructor fences preceding a publish (e.g. store, invoke) get
merged into one instruction.
==============
OptStat#ConstructorFenceGeneratedNew: 43825
OptStat#ConstructorFenceGeneratedFinal: 17631 <+++
OptStat#ConstructorFenceRemovedLSE: 164
OptStat#ConstructorFenceRemovedPFRA: 9391
OptStat#ConstructorFenceRemovedCFRE: 16133 <---
Removes ~91.5% of the 'final' constructor fences in RitzBenchmark:
(We do not distinguish the exact reason that a fence was created, so
it's possible some "new" fences were also removed.)
==============
Test: art/test/run-test --host --optimizing 476-checker-ctor-fence-redun-elim
Bug: 36656456
Change-Id: I8020217b448ad96ce9b7640aa312ae784690ad99
diff --git a/compiler/Android.bp b/compiler/Android.bp
index f11d256..d060dd4 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -54,6 +54,7 @@
"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 6c3a9fd..b558eb1 100644
--- a/compiler/optimizing/code_sinking.cc
+++ b/compiler/optimizing/code_sinking.cc
@@ -64,6 +64,11 @@
// 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 0000000..ff7ce60
--- /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 0000000..d89210c
--- /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 8644f67..e34d4a2 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -1271,18 +1271,59 @@
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 29be8ac..3e4928b 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -6634,13 +6634,24 @@
// 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 435ca1c..b45f3c6 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 @@
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 @@
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 @@
// 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 d6da73c..af7ab2f 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -91,6 +91,7 @@
kConstructorFenceGeneratedFinal,
kConstructorFenceRemovedLSE,
kConstructorFenceRemovedPFRA,
+ kConstructorFenceRemovedCFRE,
kLastStat
};
@@ -211,6 +212,7 @@
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 148ef86..8738adf 100644
--- a/runtime/base/arena_allocator.cc
+++ b/runtime/base/arena_allocator.cc
@@ -73,6 +73,7 @@
"BCE ",
"DCE ",
"LSE ",
+ "CFRE ",
"LICM ",
"LoopOpt ",
"SsaLiveness ",
diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h
index 0b1a3ba..212edfb 100644
--- a/runtime/base/arena_allocator.h
+++ b/runtime/base/arena_allocator.h
@@ -80,6 +80,7 @@
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 0000000..e69de29
--- /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 0000000..46d62f7
--- /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 0000000..05f2f7c
--- /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();
+ }
+}