Commit bb000027 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[turbofan] Optimize CsaLoadElimination

Design doc: https://bit.ly/36MfD6Y, section "Improving Computational
Complexity of CSALoadElimination".

We optimize CsaLoadElimination::AbstractState::KillField() by
fine-graining AbstractState. We now represent it with 6 maps
corresponding to (object kind, offset kind) pairs. This makes it
possible for KillField() to manipulate the state faster. For more
information consult the above design doc.

Bug: v8:11510
Change-Id: I7d991cd47f946edb20e746bc7e6792ae3c70004f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3038521
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#76165}
parent d9c06ed1
......@@ -358,6 +358,15 @@ V8_EXPORT_PRIVATE inline constexpr int ElementSizeLog2Of(
}
}
constexpr int kMaximumReprSizeLog2 =
ElementSizeLog2Of(MachineRepresentation::kSimd128);
constexpr int kMaximumReprSizeInBytes = 1 << kTaggedSizeLog2;
STATIC_ASSERT(kMaximumReprSizeLog2 >=
ElementSizeLog2Of(MachineRepresentation::kTagged));
STATIC_ASSERT(kMaximumReprSizeLog2 >=
ElementSizeLog2Of(MachineRepresentation::kWord64));
V8_EXPORT_PRIVATE inline constexpr int ElementSizeInBytes(
MachineRepresentation rep) {
return 1 << ElementSizeLog2Of(rep);
......
......@@ -74,99 +74,261 @@ bool Subsumes(MachineRepresentation from, MachineRepresentation to) {
return false;
}
bool ObjectMayAlias(Node* a, Node* b) {
if (a != b) {
if (NodeProperties::IsFreshObject(b)) std::swap(a, b);
if (NodeProperties::IsFreshObject(a) &&
(NodeProperties::IsFreshObject(b) ||
b->opcode() == IrOpcode::kParameter ||
b->opcode() == IrOpcode::kLoadImmutable ||
IrOpcode::IsConstantOpcode(b->opcode()))) {
return false;
}
}
return true;
bool IsConstantObject(Node* object) {
return object->opcode() == IrOpcode::kParameter ||
object->opcode() == IrOpcode::kLoadImmutable ||
NodeProperties::IsConstant(object);
}
bool OffsetMayAlias(Node* offset1, MachineRepresentation repr1, Node* offset2,
MachineRepresentation repr2) {
IntPtrMatcher matcher1(offset1);
IntPtrMatcher matcher2(offset2);
// If either of the offsets is variable, accesses may alias
if (!matcher1.HasResolvedValue() || !matcher2.HasResolvedValue()) {
return true;
}
// Otherwise, we return whether accesses overlap
intptr_t start1 = matcher1.ResolvedValue();
intptr_t end1 = start1 + ElementSizeInBytes(repr1);
intptr_t start2 = matcher2.ResolvedValue();
intptr_t end2 = start2 + ElementSizeInBytes(repr2);
return !(end1 <= start2 || end2 <= start1);
bool IsFreshObject(Node* object) {
DCHECK_IMPLIES(NodeProperties::IsFreshObject(object),
!IsConstantObject(object));
return NodeProperties::IsFreshObject(object);
}
} // namespace CsaLoadEliminationHelpers
namespace Helpers = CsaLoadEliminationHelpers;
void CsaLoadElimination::AbstractState::Merge(AbstractState const* that,
Zone* zone) {
// static
template <typename OuterKey>
void CsaLoadElimination::AbstractState::IntersectWith(
OuterMap<OuterKey>& to, const OuterMap<OuterKey>& from) {
FieldInfo empty_info;
for (std::pair<Field, FieldInfo> entry : field_infos_) {
if (that->field_infos_.Get(entry.first) != entry.second) {
field_infos_.Set(entry.first, empty_info);
for (const std::pair<OuterKey, InnerMap>& to_map : to) {
InnerMap to_map_copy(to_map.second);
OuterKey key = to_map.first;
InnerMap current_map = from.Get(key);
for (std::pair<Node*, FieldInfo> info : to_map.second) {
if (current_map.Get(info.first) != info.second) {
to_map_copy.Set(info.first, empty_info);
}
}
to.Set(key, to_map_copy);
}
}
void CsaLoadElimination::AbstractState::IntersectWith(
AbstractState const* that) {
IntersectWith(fresh_entries_, that->fresh_entries_);
IntersectWith(constant_entries_, that->constant_entries_);
IntersectWith(arbitrary_entries_, that->arbitrary_entries_);
IntersectWith(fresh_unknown_entries_, that->fresh_unknown_entries_);
IntersectWith(constant_unknown_entries_, that->constant_unknown_entries_);
IntersectWith(arbitrary_unknown_entries_, that->arbitrary_unknown_entries_);
}
CsaLoadElimination::AbstractState const*
CsaLoadElimination::AbstractState::KillField(Node* kill_object,
Node* kill_offset,
MachineRepresentation kill_repr,
Zone* zone) const {
FieldInfo empty_info;
AbstractState* that = zone->New<AbstractState>(*this);
for (std::pair<Field, FieldInfo> entry : that->field_infos_) {
Field field = entry.first;
MachineRepresentation field_repr = entry.second.representation;
if (Helpers::OffsetMayAlias(kill_offset, kill_repr, field.second,
field_repr) &&
Helpers::ObjectMayAlias(kill_object, field.first)) {
that->field_infos_.Set(field, empty_info);
CsaLoadElimination::AbstractState::KillField(Node* object, Node* offset,
MachineRepresentation repr) const {
AbstractState* result = zone_->New<AbstractState>(*this);
UnknownOffsetInfos empty_unknown(zone_, InnerMap(zone_));
IntPtrMatcher m(offset);
if (m.HasResolvedValue()) {
uint32_t num_offset = static_cast<uint32_t>(m.ResolvedValue());
if (Helpers::IsFreshObject(object)) {
// May alias with:
// - The same object/offset
// - Arbitrary objects with the same offset
// - The same object, unkwown offset
// - Arbitrary objects with unkwown offset
result->KillOffsetInFresh(object, num_offset, repr);
KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
result->fresh_unknown_entries_.Set(object, InnerMap(zone_));
result->arbitrary_unknown_entries_ = empty_unknown;
} else if (Helpers::IsConstantObject(object)) {
// May alias with:
// - Constant/arbitrary objects with the same offset
// - Constant/arbitrary objects with unkwown offset
KillOffset(result->constant_entries_, num_offset, repr, zone_);
KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
result->constant_unknown_entries_ = empty_unknown;
result->arbitrary_unknown_entries_ = empty_unknown;
} else {
// May alias with:
// - Any object with the same or unknown offset
KillOffset(result->fresh_entries_, num_offset, repr, zone_);
KillOffset(result->constant_entries_, num_offset, repr, zone_);
KillOffset(result->arbitrary_entries_, num_offset, repr, zone_);
result->fresh_unknown_entries_ = empty_unknown;
result->constant_unknown_entries_ = empty_unknown;
result->arbitrary_unknown_entries_ = empty_unknown;
}
} else {
ConstantOffsetInfos empty_constant(zone_, InnerMap(zone_));
if (Helpers::IsFreshObject(object)) {
// May alias with:
// - The same object with any known/unknown offset
// - Arbitrary objects with any known/unknown offset
for (auto map : result->fresh_entries_) {
// TODO(manoskouk): Consider adding a map from fresh objects to offsets
// to implement this efficiently.
InnerMap map_copy(map.second);
map_copy.Set(object, FieldInfo());
result->fresh_entries_.Set(map.first, map_copy);
}
result->fresh_unknown_entries_.Set(object, InnerMap(zone_));
result->arbitrary_entries_ = empty_constant;
result->arbitrary_unknown_entries_ = empty_unknown;
} else if (Helpers::IsConstantObject(object)) {
// May alias with:
// - Constant/arbitrary objects with the any known/unknown offset
result->constant_entries_ = empty_constant;
result->constant_unknown_entries_ = empty_unknown;
result->arbitrary_entries_ = empty_constant;
result->arbitrary_unknown_entries_ = empty_unknown;
} else {
// May alias with anything. Clear the state.
return zone_->New<AbstractState>(zone_);
}
}
return that;
return result;
}
CsaLoadElimination::AbstractState const*
CsaLoadElimination::AbstractState::AddField(Node* object, Node* offset,
CsaLoadElimination::FieldInfo info,
Zone* zone) const {
AbstractState* that = zone->New<AbstractState>(*this);
that->field_infos_.Set({object, offset}, info);
return that;
Node* value,
MachineRepresentation repr) const {
AbstractState* new_state = zone_->New<AbstractState>(*this);
IntPtrMatcher m(offset);
if (m.HasResolvedValue()) {
uint32_t offset_num = static_cast<uint32_t>(m.ResolvedValue());
ConstantOffsetInfos& infos = Helpers::IsFreshObject(object)
? new_state->fresh_entries_
: Helpers::IsConstantObject(object)
? new_state->constant_entries_
: new_state->arbitrary_entries_;
Update(infos, offset_num, object, FieldInfo(value, repr));
} else {
UnknownOffsetInfos& infos =
Helpers::IsFreshObject(object)
? new_state->fresh_unknown_entries_
: Helpers::IsConstantObject(object)
? new_state->constant_unknown_entries_
: new_state->arbitrary_unknown_entries_;
Update(infos, object, offset, FieldInfo(value, repr));
}
return new_state;
}
CsaLoadElimination::FieldInfo CsaLoadElimination::AbstractState::Lookup(
Node* object, Node* offset) const {
if (object->IsDead()) {
return {};
IntPtrMatcher m(offset);
if (m.HasResolvedValue()) {
uint32_t num_offset = static_cast<uint32_t>(m.ResolvedValue());
const ConstantOffsetInfos& infos = Helpers::IsFreshObject(object)
? fresh_entries_
: Helpers::IsConstantObject(object)
? constant_entries_
: arbitrary_entries_;
return infos.Get(num_offset).Get(object);
} else {
const UnknownOffsetInfos& infos = Helpers::IsFreshObject(object)
? fresh_unknown_entries_
: Helpers::IsConstantObject(object)
? constant_unknown_entries_
: arbitrary_unknown_entries_;
return infos.Get(object).Get(offset);
}
return field_infos_.Get({object, offset});
}
void CsaLoadElimination::AbstractState::Print() const {
for (std::pair<Field, FieldInfo> entry : field_infos_) {
Field field = entry.first;
Node* object = field.first;
Node* offset = field.second;
FieldInfo info = entry.second;
PrintF(" #%d+#%d:%s -> #%d:%s [repr=%s]\n", object->id(), offset->id(),
object->op()->mnemonic(), info.value->id(),
info.value->op()->mnemonic(),
MachineReprToString(info.representation));
// static
// Kill all elements in {infos} that overlap with an element with {offset} and
// size {ElementSizeInBytes(repr)}.
void CsaLoadElimination::AbstractState::KillOffset(ConstantOffsetInfos& infos,
uint32_t offset,
MachineRepresentation repr,
Zone* zone) {
// All elements in the range [{offset}, {offset + ElementSizeInBytes(repr)})
// are in the killed range. We do not need to traverse the inner maps, we can
// just clear them.
for (int i = 0; i < ElementSizeInBytes(repr); i++) {
infos.Set(offset + i, InnerMap(zone));
}
// Now we have to remove all elements in earlier offsets that overlap with an
// element in {offset}.
// The earliest offset that may overlap with {offset} is
// {kMaximumReprSizeInBytes - 1} before.
uint32_t initial_offset = offset >= kMaximumReprSizeInBytes - 1
? offset - (kMaximumReprSizeInBytes - 1)
: 0;
// For all offsets from {initial_offset} to {offset}, we traverse the
// respective inner map, and reset all elements that are large enough to
// overlap with {offset}.
for (uint32_t i = initial_offset; i < offset; i++) {
InnerMap map_copy(infos.Get(i));
for (const std::pair<Node*, FieldInfo> info : infos.Get(i)) {
if (info.second.representation != MachineRepresentation::kNone &&
ElementSizeInBytes(info.second.representation) >
static_cast<int>(offset - i)) {
map_copy.Set(info.first, {});
}
}
infos.Set(i, map_copy);
}
}
void CsaLoadElimination::AbstractState::KillOffsetInFresh(
Node* const object, uint32_t offset, MachineRepresentation repr) {
for (int i = 0; i < ElementSizeInBytes(repr); i++) {
Update(fresh_entries_, offset + i, object, {});
}
uint32_t initial_offset = offset >= kMaximumReprSizeInBytes - 1
? offset - (kMaximumReprSizeInBytes - 1)
: 0;
for (uint32_t i = initial_offset; i < offset; i++) {
const FieldInfo& info = fresh_entries_.Get(i).Get(object);
if (info.representation != MachineRepresentation::kNone &&
ElementSizeInBytes(info.representation) >
static_cast<int>(offset - i)) {
Update(fresh_entries_, i, object, {});
}
}
}
// static
void CsaLoadElimination::AbstractState::Print(
const CsaLoadElimination::AbstractState::ConstantOffsetInfos& infos) {
for (const auto outer_entry : infos) {
for (const auto inner_entry : outer_entry.second) {
Node* object = inner_entry.first;
uint32_t offset = outer_entry.first;
FieldInfo info = inner_entry.second;
PrintF(" #%d+#%d:%s -> #%d:%s [repr=%s]\n", object->id(), offset,
object->op()->mnemonic(), info.value->id(),
info.value->op()->mnemonic(),
MachineReprToString(info.representation));
}
}
}
// static
void CsaLoadElimination::AbstractState::Print(
const CsaLoadElimination::AbstractState::UnknownOffsetInfos& infos) {
for (const auto outer_entry : infos) {
for (const auto inner_entry : outer_entry.second) {
Node* object = outer_entry.first;
Node* offset = inner_entry.first;
FieldInfo info = inner_entry.second;
PrintF(" #%d+#%d:%s -> #%d:%s [repr=%s]\n", object->id(), offset->id(),
object->op()->mnemonic(), info.value->id(),
info.value->op()->mnemonic(),
MachineReprToString(info.representation));
}
}
}
void CsaLoadElimination::AbstractState::Print() const {
Print(fresh_entries_);
Print(constant_entries_);
Print(arbitrary_entries_);
Print(fresh_unknown_entries_);
Print(constant_unknown_entries_);
Print(arbitrary_unknown_entries_);
}
Reduction CsaLoadElimination::ReduceLoadFromObject(Node* node,
ObjectAccess const& access) {
Node* object = NodeProperties::GetValueInput(node, 0);
......@@ -189,8 +351,7 @@ Reduction CsaLoadElimination::ReduceLoadFromObject(Node* node,
return Replace(replacement);
}
}
FieldInfo info(node, representation);
state = state->AddField(object, offset, info, zone());
state = state->AddField(object, offset, node, representation);
return UpdateState(node, state);
}
......@@ -204,9 +365,9 @@ Reduction CsaLoadElimination::ReduceStoreToObject(Node* node,
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
FieldInfo info(value, access.machine_type.representation());
state = state->KillField(object, offset, info.representation, zone());
state = state->AddField(object, offset, info, zone());
MachineRepresentation repr = access.machine_type.representation();
state = state->KillField(object, offset, repr);
state = state->AddField(object, offset, value, repr);
return UpdateState(node, state);
}
......@@ -232,12 +393,14 @@ Reduction CsaLoadElimination::ReduceEffectPhi(Node* node) {
if (node_states_.Get(effect) == nullptr) return NoChange();
}
// Make a copy of the first input's state and merge with the state
// Make a copy of the first input's state and intersect it with the state
// from other inputs.
// TODO(manoskouk): Consider computing phis for at least a subset of the
// state.
AbstractState* state = zone()->New<AbstractState>(*state0);
for (int i = 1; i < input_count; ++i) {
Node* const input = NodeProperties::GetEffectInput(node, i);
state->Merge(node_states_.Get(input), zone());
state->IntersectWith(node_states_.Get(input));
}
return UpdateState(node, state);
}
......@@ -298,11 +461,10 @@ Reduction CsaLoadElimination::PropagateInputState(Node* node) {
CsaLoadElimination::AbstractState const* CsaLoadElimination::ComputeLoopState(
Node* node, AbstractState const* state) const {
DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
Node* const control = NodeProperties::GetControlInput(node);
ZoneQueue<Node*> queue(zone());
ZoneSet<Node*> visited(zone());
visited.insert(node);
for (int i = 1; i < control->InputCount(); ++i) {
for (int i = 1; i < node->InputCount() - 1; ++i) {
queue.push(node->InputAt(i));
}
while (!queue.empty()) {
......
......@@ -61,28 +61,74 @@ class V8_EXPORT_PRIVATE CsaLoadElimination final
MachineRepresentation representation = MachineRepresentation::kNone;
};
// Design doc: https://bit.ly/36MfD6Y
class AbstractState final : public ZoneObject {
public:
explicit AbstractState(Zone* zone) : field_infos_(zone) {}
explicit AbstractState(Zone* zone)
: zone_(zone),
fresh_entries_(zone, InnerMap(zone)),
constant_entries_(zone, InnerMap(zone)),
arbitrary_entries_(zone, InnerMap(zone)),
fresh_unknown_entries_(zone, InnerMap(zone)),
constant_unknown_entries_(zone, InnerMap(zone)),
arbitrary_unknown_entries_(zone, InnerMap(zone)) {}
bool Equals(AbstractState const* that) const {
return field_infos_ == that->field_infos_;
return fresh_entries_ == that->fresh_entries_ &&
constant_entries_ == that->constant_entries_ &&
arbitrary_entries_ == that->arbitrary_entries_ &&
fresh_unknown_entries_ == that->fresh_unknown_entries_ &&
constant_unknown_entries_ == that->constant_unknown_entries_ &&
arbitrary_unknown_entries_ == that->arbitrary_unknown_entries_;
}
void Merge(AbstractState const* that, Zone* zone);
void IntersectWith(AbstractState const* that);
AbstractState const* KillField(Node* object, Node* offset,
MachineRepresentation repr,
Zone* zone) const;
AbstractState const* AddField(Node* object, Node* offset, FieldInfo info,
Zone* zone) const;
MachineRepresentation repr) const;
AbstractState const* AddField(Node* object, Node* offset, Node* value,
MachineRepresentation repr) const;
FieldInfo Lookup(Node* object, Node* offset) const;
void Print() const;
private:
using Field = std::pair<Node*, Node*>;
using FieldInfos = PersistentMap<Field, FieldInfo>;
FieldInfos field_infos_;
Zone* zone_;
using InnerMap = PersistentMap<Node*, FieldInfo>;
template <typename OuterKey>
using OuterMap = PersistentMap<OuterKey, InnerMap>;
// offset -> object -> info
using ConstantOffsetInfos = OuterMap<uint32_t>;
ConstantOffsetInfos fresh_entries_;
ConstantOffsetInfos constant_entries_;
ConstantOffsetInfos arbitrary_entries_;
// object -> offset -> info
using UnknownOffsetInfos = OuterMap<Node*>;
UnknownOffsetInfos fresh_unknown_entries_;
UnknownOffsetInfos constant_unknown_entries_;
UnknownOffsetInfos arbitrary_unknown_entries_;
// Update {map} so that {map.Get(outer_key).Get(inner_key)} returns {info}.
template <typename OuterKey>
static void Update(OuterMap<OuterKey>& map, OuterKey outer_key,
Node* inner_key, FieldInfo info) {
InnerMap map_copy(map.Get(outer_key));
map_copy.Set(inner_key, info);
map.Set(outer_key, map_copy);
}
// Kill all elements in {infos} which may alias with offset.
static void KillOffset(ConstantOffsetInfos& infos, uint32_t offset,
MachineRepresentation repr, Zone* zone);
void KillOffsetInFresh(Node* object, uint32_t offset,
MachineRepresentation repr);
template <typename OuterKey>
static void IntersectWith(OuterMap<OuterKey>& to,
const OuterMap<OuterKey>& from);
static void Print(const ConstantOffsetInfos& infos);
static void Print(const UnknownOffsetInfos& infos);
};
Reduction ReduceLoadFromObject(Node* node, ObjectAccess const& access);
......
......@@ -387,9 +387,11 @@ void PersistentMap<Key, Value, Hasher>::Set(Key key, Value value) {
if (old->more) {
*more = *old->more;
} else {
(*more)[old->key_value.key()] = old->key_value.value();
more->erase(old->key_value.key());
more->emplace(old->key_value.key(), old->key_value.value());
}
(*more)[key] = value;
more->erase(key);
more->emplace(key, value);
}
size_t size = sizeof(FocusedTree) +
std::max(0, length - 1) * sizeof(const FocusedTree*);
......
// 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: --experimental-wasm-gc
// This tests are meant to examine if Turbofan CsaLoadElimination works
// correctly for wasm. The TurboFan graphs can be examined with --trace-turbo.
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// Fresh objects, known offsets
(function LoadEliminationtFreshKnownTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true),
makeField(kWasmI32, true)]);
builder.addFunction("main", makeSig([kWasmI32], [kWasmI32]))
.addLocals(wasmOptRefType(struct), 1)
.addBody([
kExprI32Const, 10, // local1 = struct(10, 100);
kExprI32Const, 100,
kGCPrefix, kExprRttCanon, struct,
kGCPrefix, kExprStructNewWithRtt, struct,
kExprLocalSet, 1,
kExprLocalGet, 0, // Split control based on an unknown value
kExprIf, kWasmI32,
kExprLocalGet, 1, // local1.field1 = 42
kExprI32Const, 42,
kGCPrefix, kExprStructSet, struct, 1,
kExprLocalGet, 1, // local1.field1
kGCPrefix, kExprStructGet, struct, 1,
kExprElse,
kExprLocalGet, 1, // local1.field1 = 11
kExprI32Const, 11,
kGCPrefix, kExprStructSet, struct, 1,
kExprLocalGet, 1, // local1.field1 = 22
kExprI32Const, 22,
kGCPrefix, kExprStructSet, struct, 1,
kExprLocalGet, 1, // local1.field1 + local1.field1
kGCPrefix, kExprStructGet, struct, 1,
kExprLocalGet, 1,
kGCPrefix, kExprStructGet, struct, 1,
kExprI32Add,
kExprEnd,
kExprLocalGet, 1, // return if-result * (local1.field1 + local1.field0)
kGCPrefix, kExprStructGet, struct, 0,
kExprLocalGet, 1,
kGCPrefix, kExprStructGet, struct, 1,
kExprI32Add,
kExprI32Mul
])
.exportFunc();
let instance = builder.instantiate({});
assertEquals(instance.exports.main(1), 42 * (42 + 10));
assertEquals(instance.exports.main(0), (22 + 22) * (22 + 10));
})();
(function LoadEliminationtConstantKnownTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
let replaced_value = 55
let param_1_value = 42
let init_value_1 = 5
let init_value_2 = 17
let tester = builder.addFunction("tester", makeSig(
[wasmRefType(struct), wasmRefType(struct)], [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructGet, struct, 0,
kExprLocalGet, 0,
kExprI32Const, replaced_value,
kGCPrefix, kExprStructSet, struct, 0,
// We should eliminate this load and replace it with replaced_value
kExprLocalGet, 0,
kGCPrefix, kExprStructGet, struct, 0,
kExprLocalGet, 1,
kExprI32Const, param_1_value,
kGCPrefix, kExprStructSet, struct, 0,
// Although we could eliminate this load before, we cannot anymore,
// because the parameters may alias.
kExprLocalGet, 0,
kGCPrefix, kExprStructGet, struct, 0,
kExprI32Add, kExprI32Add
]);
function buildStruct(value) {
return [kExprI32Const, value, kGCPrefix, kExprRttCanon, struct,
kGCPrefix, kExprStructNewWithRtt, struct];
}
builder.addFunction("main_non_aliasing", kSig_i_v)
.addBody([
...buildStruct(init_value_1), ...buildStruct(init_value_2),
kExprCallFunction, tester.index])
.exportFunc();
builder.addFunction("main_aliasing", kSig_i_v)
.addLocals(wasmOptRefType(struct), 1)
.addBody([
...buildStruct(init_value_1), kExprLocalSet, 0,
kExprLocalGet, 0, kExprRefAsNonNull,
kExprLocalGet, 0, kExprRefAsNonNull,
kExprCallFunction, tester.index])
.exportFunc();
let instance = builder.instantiate({});
assertEquals(init_value_1 + replaced_value + replaced_value,
instance.exports.main_non_aliasing());
assertEquals(init_value_1 + replaced_value + param_1_value,
instance.exports.main_aliasing());
})();
(function LoadEliminationtArbitraryKnownTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
let initial_value = 19;
let replacing_value_1 = 55;
let replacing_value_2 = 37;
let id = builder.addFunction("id", makeSig([wasmOptRefType(struct)],
[wasmOptRefType(struct)]))
.addBody([kExprLocalGet, 0])
builder.addFunction("main", kSig_i_v)
.addLocals(wasmOptRefType(struct), 2)
.addBody([
// We store a fresh struct in local0
kExprI32Const, initial_value,
kGCPrefix, kExprRttCanon, struct,
kGCPrefix, kExprStructNewWithRtt, struct,
kExprLocalSet, 0,
// We pass it through a function and store it to local1. local1 may now
// alias with anything.
kExprLocalGet, 0, kExprCallFunction, id.index, kExprLocalSet, 1,
kExprLocalGet, 0,
kExprI32Const, replacing_value_1,
kGCPrefix, kExprStructSet, struct, 0,
// We should eliminate this load.
kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, 0,
kExprLocalGet, 1,
kExprI32Const, replacing_value_2,
kGCPrefix, kExprStructSet, struct, 0,
// We should not eliminate this load.
kExprLocalGet, 0, kGCPrefix, kExprStructGet, struct, 0,
kExprI32Add])
.exportFunc();
let instance = builder.instantiate({});
assertEquals(replacing_value_1 + replacing_value_2, instance.exports.main());
})();
(function LoadEliminationtFreshUnknownTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let array = builder.addArray(kWasmI64, true);
// parameter: unknown array index
builder.addFunction("main", makeSig([kWasmI32], [kWasmI32]))
.addLocals(wasmOptRefType(array), 1)
.addBody([
kExprI32Const, 5,
kGCPrefix, kExprRttCanon, array,
kGCPrefix, kExprArrayNewDefault, array,
kExprLocalSet, 1,
kExprLocalGet, 1, // a[i] = i for i = {0..4}
kExprI32Const, 0,
kExprI64Const, 0,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 1,
kExprI32Const, 1,
kExprI64Const, 1,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 1,
kExprI32Const, 2,
kExprI64Const, 2,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 1,
kExprI32Const, 3,
kExprI64Const, 3,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 1,
kExprI32Const, 4,
kExprI64Const, 4,
kGCPrefix, kExprArraySet, array,
// Get a constant index a[4] before setting unknown indices
kExprLocalGet, 1,
kExprI32Const, 4,
kGCPrefix, kExprArrayGet, array,
kExprLocalGet, 1, // Set a[local0] = 33
kExprLocalGet, 0,
kExprI64Const, 33,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 1, // Get a[local0]
kExprLocalGet, 0,
kGCPrefix, kExprArrayGet, array,
kExprLocalGet, 1, // Known index load cannot be eliminated anymore
kExprI32Const, 3,
kGCPrefix, kExprArrayGet, array,
// A load from different unknown index a[local0 + 1] cannot be eliminated
kExprLocalGet, 1,
kExprLocalGet, 0,
kExprI32Const, 1,
kExprI32Add,
kGCPrefix, kExprArrayGet, array,
kExprI64Add, // return a[4] * (a[local0] - (a[3] + a[local0 + 1]))
kExprI64Sub,
kExprI64Mul,
kExprI32ConvertI64 // To not have to worry about BigInts in JS world
])
.exportFunc();
let instance = builder.instantiate({});
assertEquals(4 * (33 - (3 + 1)), instance.exports.main(0));
assertEquals(4 * (33 - (3 + 2)), instance.exports.main(1));
assertEquals(4 * (33 - (3 + 3)), instance.exports.main(2));
assertEquals(4 * (33 - (33 + 4)), instance.exports.main(3));
})();
(function LoadEliminationtAllBetsAreOffTest() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
let array = builder.addArray(kWasmI32, true);
let value_0 = 19;
let value_1 = 55;
let value_2 = 2;
let id = builder.addFunction("id", makeSig([wasmOptRefType(array)],
[wasmOptRefType(array)]))
.addBody([kExprLocalGet, 0])
// parameters: array, index
let tester = builder.addFunction("tester",
makeSig([wasmRefType(array), kWasmI32], [kWasmI32]))
.addLocals(wasmOptRefType(struct), 1)
.addLocals(wasmOptRefType(array), 1)
.addBody([
// We store a fresh struct in local1
kExprI32Const, 0,
kGCPrefix, kExprRttCanon, struct,
kGCPrefix, kExprStructNewWithRtt, struct,
kExprLocalSet, 2,
// We pass the array parameter through a function and store it to local2.
kExprLocalGet, 0, kExprCallFunction, id.index, kExprLocalSet, 3,
// Set the parameter array, the fresh struct, then the arbitrary array to
// an unknown offset.
kExprLocalGet, 0,
kExprI32Const, 5,
kExprI32Const, value_0,
kGCPrefix, kExprArraySet, array,
kExprLocalGet, 2,
kExprI32Const, value_1,
kGCPrefix, kExprStructSet, struct, 0,
kExprLocalGet, 3,
kExprLocalGet, 1,
kExprI32Const, value_2,
kGCPrefix, kExprArraySet, array,
// Neither load can be eliminated.
kExprLocalGet, 0,
kExprI32Const, 5,
kGCPrefix, kExprArrayGet, array,
kExprLocalGet, 2,
kGCPrefix, kExprStructGet, struct, 0,
kExprI32Add]);
builder.addFunction("main", kSig_i_i)
.addBody([
kExprI32Const, 10, kGCPrefix, kExprRttCanon, array,
kGCPrefix, kExprArrayNewDefault, array,
kExprI32Const, 7,
kExprCallFunction, tester.index,
])
.exportFunc();
let instance = builder.instantiate({});
assertEquals(value_0 + value_1, instance.exports.main());
})();
......@@ -202,6 +202,10 @@ function makeSig_v_x(x) {
return makeSig([x], []);
}
function makeSig_x_v(x) {
return makeSig([], [x]);
}
function makeSig_v_xx(x) {
return makeSig([x, x], []);
}
......
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