Commit e926da45 authored by Patrick Thier's avatar Patrick Thier Committed by V8 LUCI CQ

Reland "[turbofan] Improve StoreStoreElimination"

This is a reland of 863bc2b8

Diff to original:
- Don't eliminate GC observable stores that were temporarily
  unobservable during traversal.
- Skip the previously added test for single-generation
- Add new test

Original change's description:
> [turbofan] Improve StoreStoreElimination
>
> Previously, StoreStoreElimination handled allocations as
> "can observe anything". This is pretty conservative and prohibits
> elimination of repeated double stores to the same field.
> With this CL allocations are changed to "observes initializing or
> transitioning stores".
> This way it is guaranteed that initializing stores to a freshly created
> object or stores that are part of a map transition are not eliminated
> before allocations (that can trigger GC), but allows elimination of
> non-initializing, non-transitioning, unobservable stores in the
> presence of allocations.
>
> Bug: v8:12200
> Change-Id: Ie1419696b9c8cb7c39aecf38d9f08102177b2c0f
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3295449
> Commit-Queue: Patrick Thier <pthier@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Reviewed-by: Maya Lekova <mslekova@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#78230}

Bug: v8:12200, chromium:1276923, v8:12477
Change-Id: Ied45ee28ac12b370f7b232d2d338f93e10fea6b4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3320460Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78349}
parent 41b174a7
......@@ -342,6 +342,7 @@ class EffectControlLinearizer {
Zone* temp_zone_;
MaintainSchedule maintain_schedule_;
RegionObservability region_observability_ = RegionObservability::kObservable;
bool inside_region_ = false;
SourcePositionTable* source_positions_;
NodeOriginTable* node_origins_;
JSHeapBroker* broker_;
......@@ -864,6 +865,7 @@ void EffectControlLinearizer::ProcessNode(Node* node, Node** frame_state) {
if (node->opcode() == IrOpcode::kFinishRegion) {
// Reset the current region observability.
region_observability_ = RegionObservability::kObservable;
inside_region_ = false;
// Update the value uses to the value input of the finish node and
// the effect uses to the effect input.
return RemoveRenameNode(node);
......@@ -874,6 +876,7 @@ void EffectControlLinearizer::ProcessNode(Node* node, Node** frame_state) {
// StoreField and other operators).
DCHECK_NE(RegionObservability::kNotObservable, region_observability_);
region_observability_ = RegionObservabilityOf(node->op());
inside_region_ = true;
// Update the value uses to the value input of the finish node and
// the effect uses to the effect input.
return RemoveRenameNode(node);
......@@ -891,6 +894,14 @@ void EffectControlLinearizer::ProcessNode(Node* node, Node** frame_state) {
return;
}
if (node->opcode() == IrOpcode::kStoreField) {
// Mark stores outside a region as non-initializing and non-transitioning.
if (!inside_region_) {
const FieldAccess access = FieldAccessOf(node->op());
NodeProperties::ChangeOp(node, simplified()->StoreField(access, false));
}
}
// The IfSuccess nodes should always start a basic block (and basic block
// start nodes are not handled in the ProcessNode method).
DCHECK_NE(IrOpcode::kIfSuccess, node->opcode());
......
......@@ -91,6 +91,9 @@ std::ostream& operator<<(std::ostream& os, FieldAccess const& access) {
if (access.is_store_in_literal) {
os << " (store in literal)";
}
if (access.maybe_initializing_or_transitioning_store) {
os << " (initializing or transitioning store)";
}
os << "]";
return os;
}
......@@ -1882,7 +1885,6 @@ const Operator* SimplifiedOperatorBuilder::SpeculativeNumberEqual(
#define ACCESS_OP_LIST(V) \
V(LoadField, FieldAccess, Operator::kNoWrite, 1, 1, 1) \
V(StoreField, FieldAccess, Operator::kNoRead, 2, 1, 0) \
V(LoadElement, ElementAccess, Operator::kNoWrite, 2, 1, 1) \
V(StoreElement, ElementAccess, Operator::kNoRead, 3, 1, 0) \
V(LoadTypedElement, ExternalArrayType, Operator::kNoWrite, 4, 1, 1) \
......@@ -1906,6 +1908,17 @@ const Operator* SimplifiedOperatorBuilder::SpeculativeNumberEqual(
ACCESS_OP_LIST(ACCESS)
#undef ACCESS
const Operator* SimplifiedOperatorBuilder::StoreField(
const FieldAccess& access, bool maybe_initializing_or_transitioning) {
FieldAccess store_access = access;
store_access.maybe_initializing_or_transitioning_store =
maybe_initializing_or_transitioning;
return zone()->New<Operator1<FieldAccess>>(
IrOpcode::kStoreField,
Operator::kNoDeopt | Operator::kNoThrow | Operator::kNoRead, "StoreField",
2, 1, 1, 0, 1, 0, store_access);
}
const Operator* SimplifiedOperatorBuilder::LoadMessage() {
return zone()->New<Operator>(IrOpcode::kLoadMessage, Operator::kEliminatable,
"LoadMessage", 1, 1, 1, 1, 1, 0);
......
......@@ -84,6 +84,10 @@ struct FieldAccess {
#ifdef V8_HEAP_SANDBOX
ExternalPointerTag external_pointer_tag = kExternalPointerNullTag;
#endif
bool maybe_initializing_or_transitioning_store; // store is potentially
// initializing a newly
// allocated object or part
// of a map transition.
FieldAccess()
: base_is_tagged(kTaggedBase),
......@@ -92,18 +96,18 @@ struct FieldAccess {
machine_type(MachineType::None()),
write_barrier_kind(kFullWriteBarrier),
const_field_info(ConstFieldInfo::None()),
is_store_in_literal(false) {}
is_store_in_literal(false),
maybe_initializing_or_transitioning_store(false) {}
FieldAccess(BaseTaggedness base_is_tagged, int offset, MaybeHandle<Name> name,
MaybeHandle<Map> map, Type type, MachineType machine_type,
WriteBarrierKind write_barrier_kind,
ConstFieldInfo const_field_info = ConstFieldInfo::None(),
bool is_store_in_literal = false
bool is_store_in_literal = false,
#ifdef V8_HEAP_SANDBOX
,
ExternalPointerTag external_pointer_tag = kExternalPointerNullTag
ExternalPointerTag external_pointer_tag = kExternalPointerNullTag,
#endif
)
bool maybe_initializing_or_transitioning_store = false)
: base_is_tagged(base_is_tagged),
offset(offset),
name(name),
......@@ -112,12 +116,12 @@ struct FieldAccess {
machine_type(machine_type),
write_barrier_kind(write_barrier_kind),
const_field_info(const_field_info),
is_store_in_literal(is_store_in_literal)
is_store_in_literal(is_store_in_literal),
#ifdef V8_HEAP_SANDBOX
,
external_pointer_tag(external_pointer_tag)
external_pointer_tag(external_pointer_tag),
#endif
{
maybe_initializing_or_transitioning_store(
maybe_initializing_or_transitioning_store) {
DCHECK_GE(offset, 0);
DCHECK_IMPLIES(
machine_type.IsMapWord(),
......@@ -1043,7 +1047,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* LoadFieldByIndex();
const Operator* LoadField(FieldAccess const&);
const Operator* StoreField(FieldAccess const&);
const Operator* StoreField(FieldAccess const&,
bool maybe_initializing_or_transitioning = true);
// load-element [base + index]
const Operator* LoadElement(ElementAccess const&);
......
This diff is collapsed.
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --verify-heap --turbo-store-elimination
// Check that transitioning stores are not eliminated.
let obj = { a: 42 }
function foo() {
// Force GC on the next allocation to trigger heap verification.
%SimulateNewspaceFull();
// Transitioning store. Must not be eliminated.
this.f = obj;
this.f = {
a: 43
};
}
%PrepareFunctionForOptimization(foo);
var a;
a = new foo();
a = new foo();
%OptimizeFunctionOnNextCall(foo);
a = new foo();
assertEquals(43, a.f.a);
......@@ -1503,6 +1503,7 @@
'compiler/test-literal-map-migration': [SKIP],
'compiler/deopt-pretenure': [SKIP],
'compiler/fast-api-sequences-x64': [SKIP],
'compiler/regress-store-store-elim': [SKIP],
# TODO(v8:12031): Reimplement elements kinds transitions when concurrent
# inlining.
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --gc-interval=10
// Base case where a GC observable store might be temporarily shadowed.
function foo() {
let i = 0.1;
eval();
if (i) {
const c = {};
eval();
}
}
%PrepareFunctionForOptimization(foo);
foo();
foo();
%OptimizeFunctionOnNextCall(foo);
foo();
// Stress execution with GCs.
function bar() {
for (let cnt = 0, i = 655; cnt < 50000 && i !== 1; cnt++, i = i / 3) {
i %= 2;
const c = { "b": 1, "a":1, "c": 1, "d": 1 };
eval();
}
}
bar();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment