#include "src/compiler/csa-load-elimination.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
namespace v8 {
namespace internal {
namespace compiler {
Reduction CsaLoadElimination::Reduce(Node* node) {
if (v8_flags.trace_turbo_load_elimination) {
if (node->op()->EffectInputCount() > 0) {
PrintF(" visit #%d:%s", node->id(), node->op()->mnemonic());
if (node->op()->ValueInputCount() > 0) {
PrintF("(");
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
if (i > 0) PrintF(", ");
Node* const value = NodeProperties::GetValueInput(node, i);
PrintF("#%d:%s", value->id(), value->op()->mnemonic());
}
PrintF(")");
}
PrintF("\n");
for (int i = 0; i < node->op()->EffectInputCount(); ++i) {
Node* const effect = NodeProperties::GetEffectInput(node, i);
if (AbstractState const* const state = node_states_.Get(effect)) {
PrintF(" state[%i]: #%d:%s\n", i, effect->id(),
effect->op()->mnemonic());
state->mutable_state.Print();
state->immutable_state.Print();
} else {
PrintF(" no state[%i]: #%d:%s\n", i, effect->id(),
effect->op()->mnemonic());
}
}
}
}
switch (node->opcode()) {
case IrOpcode::kLoadFromObject:
case IrOpcode::kLoadImmutableFromObject:
return ReduceLoadFromObject(node, ObjectAccessOf(node->op()));
case IrOpcode::kStoreToObject:
case IrOpcode::kInitializeImmutableInObject:
return ReduceStoreToObject(node, ObjectAccessOf(node->op()));
case IrOpcode::kDebugBreak:
case IrOpcode::kAbortCSADcheck:
return PropagateInputState(node);
case IrOpcode::kCall:
return ReduceCall(node);
case IrOpcode::kEffectPhi:
return ReduceEffectPhi(node);
case IrOpcode::kDead:
return NoChange();
case IrOpcode::kStart:
return ReduceStart(node);
default:
return ReduceOtherNode(node);
}
UNREACHABLE();
}
namespace CsaLoadEliminationHelpers {
bool Subsumes(MachineRepresentation from, MachineRepresentation to) {
if (from == to) return true;
if (IsAnyTagged(from)) return IsAnyTagged(to);
if (IsIntegral(from)) {
return IsIntegral(to) && ElementSizeInBytes(from) >= ElementSizeInBytes(to);
}
return false;
}
bool IsConstantObject(Node* object) {
return object->opcode() == IrOpcode::kParameter ||
object->opcode() == IrOpcode::kLoadImmutable ||
NodeProperties::IsConstant(object);
}
bool IsFreshObject(Node* object) {
return object->opcode() == IrOpcode::kAllocate ||
object->opcode() == IrOpcode::kAllocateRaw;
}
}
namespace Helpers = CsaLoadEliminationHelpers;
template <typename OuterKey>
void CsaLoadElimination::HalfState::IntersectWith(
OuterMap<OuterKey>& to, const OuterMap<OuterKey>& from) {
FieldInfo 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::HalfState::IntersectWith(HalfState 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::HalfState const* CsaLoadElimination::HalfState::KillField(
Node* object, Node* offset, MachineRepresentation repr) const {
HalfState* result = zone_->New<HalfState>(*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)) {
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)) {
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 {
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)) {
for (auto map : result->fresh_entries_) {
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)) {
result->constant_entries_ = empty_constant;
result->constant_unknown_entries_ = empty_unknown;
result->arbitrary_entries_ = empty_constant;
result->arbitrary_unknown_entries_ = empty_unknown;
} else {
return zone_->New<HalfState>(zone_);
}
}
return result;
}
CsaLoadElimination::HalfState const* CsaLoadElimination::HalfState::AddField(
Node* object, Node* offset, Node* value, MachineRepresentation repr) const {
HalfState* new_state = zone_->New<HalfState>(*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::HalfState::Lookup(
Node* object, Node* offset) const {
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);
}
}
void CsaLoadElimination::HalfState::KillOffset(ConstantOffsetInfos& infos,
uint32_t offset,
MachineRepresentation repr,
Zone* zone) {
for (int i = 0; i < ElementSizeInBytes(repr); i++) {
infos.Set(offset + i, InnerMap(zone));
}
uint32_t initial_offset = offset >= kMaximumReprSizeInBytes - 1
? offset - (kMaximumReprSizeInBytes - 1)
: 0;
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::HalfState::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, {});
}
}
}
void CsaLoadElimination::HalfState::Print(
const CsaLoadElimination::HalfState::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:%s+(%d) -> #%d:%s [repr=%s]\n", object->id(),
object->op()->mnemonic(), offset, info.value->id(),
info.value->op()->mnemonic(),
MachineReprToString(info.representation));
}
}
}
void CsaLoadElimination::HalfState::Print(
const CsaLoadElimination::HalfState::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:%s+#%d:%s -> #%d:%s [repr=%s]\n", object->id(),
object->op()->mnemonic(), offset->id(), offset->op()->mnemonic(),
info.value->id(), info.value->op()->mnemonic(),
MachineReprToString(info.representation));
}
}
}
void CsaLoadElimination::HalfState::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::AssertUnreachable(Node* node) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* unreachable =
graph()->NewNode(jsgraph()->common()->Unreachable(), effect, control);
return Replace(unreachable);
}
Reduction CsaLoadElimination::ReduceLoadFromObject(Node* node,
ObjectAccess const& access) {
DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||
node->opcode() == IrOpcode::kLoadImmutableFromObject);
Node* object = NodeProperties::GetValueInput(node, 0);
Node* offset = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
bool is_mutable = node->opcode() == IrOpcode::kLoadFromObject;
if (!(is_mutable ? &state->immutable_state : &state->mutable_state)
->Lookup(object, offset)
.IsEmpty()) {
Node* control = NodeProperties::GetControlInput(node);
Node* unreachable =
graph()->NewNode(jsgraph()->common()->Unreachable(), effect, control);
auto rep = ObjectAccessOf(node->op()).machine_type.representation();
Node* dead_value =
graph()->NewNode(jsgraph()->common()->DeadValue(rep), unreachable);
ReplaceWithValue(node, dead_value, unreachable, control);
node->Kill();
return Replace(dead_value);
}
HalfState const* half_state =
is_mutable ? &state->mutable_state : &state->immutable_state;
MachineRepresentation representation = access.machine_type.representation();
FieldInfo lookup_result = half_state->Lookup(object, offset);
if (!lookup_result.IsEmpty()) {
MachineRepresentation from = lookup_result.representation;
if (Helpers::Subsumes(from, representation) &&
!lookup_result.value->IsDead()) {
Node* replacement =
TruncateAndExtend(lookup_result.value, from, access.machine_type);
ReplaceWithValue(node, replacement, effect);
Revisit(object);
return Replace(replacement);
}
}
half_state = half_state->AddField(object, offset, node, representation);
AbstractState const* new_state =
is_mutable
? zone()->New<AbstractState>(*half_state, state->immutable_state)
: zone()->New<AbstractState>(state->mutable_state, *half_state);
return UpdateState(node, new_state);
}
Reduction CsaLoadElimination::ReduceStoreToObject(Node* node,
ObjectAccess const& access) {
DCHECK(node->opcode() == IrOpcode::kStoreToObject ||
node->opcode() == IrOpcode::kInitializeImmutableInObject);
Node* object = NodeProperties::GetValueInput(node, 0);
Node* offset = NodeProperties::GetValueInput(node, 1);
Node* value = NodeProperties::GetValueInput(node, 2);
Node* effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
MachineRepresentation repr = access.machine_type.representation();
if (node->opcode() == IrOpcode::kStoreToObject) {
if (!(state->immutable_state.Lookup(object, offset).IsEmpty())) {
return AssertUnreachable(node);
}
HalfState const* mutable_state =
state->mutable_state.KillField(object, offset, repr);
mutable_state = mutable_state->AddField(object, offset, value, repr);
AbstractState const* new_state =
zone()->New<AbstractState>(*mutable_state, state->immutable_state);
return UpdateState(node, new_state);
} else {
if (!(state->mutable_state.Lookup(object, offset).IsEmpty())) {
return AssertUnreachable(node);
}
DCHECK(state->immutable_state.Lookup(object, offset).IsEmpty());
HalfState const* immutable_state =
state->immutable_state.AddField(object, offset, value, repr);
AbstractState const* new_state =
zone()->New<AbstractState>(state->mutable_state, *immutable_state);
return UpdateState(node, new_state);
}
}
Reduction CsaLoadElimination::ReduceEffectPhi(Node* node) {
Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
Node* const control = NodeProperties::GetControlInput(node);
AbstractState const* state0 = node_states_.Get(effect0);
if (state0 == nullptr) return NoChange();
if (control->opcode() == IrOpcode::kLoop) {
AbstractState const* state = ComputeLoopState(node, state0);
return UpdateState(node, state);
}
DCHECK_EQ(IrOpcode::kMerge, control->opcode());
int const input_count = node->op()->EffectInputCount();
for (int i = 1; i < input_count; ++i) {
Node* const effect = NodeProperties::GetEffectInput(node, i);
if (node_states_.Get(effect) == nullptr) return NoChange();
}
AbstractState* state = zone()->New<AbstractState>(*state0);
for (int i = 1; i < input_count; ++i) {
Node* const input = NodeProperties::GetEffectInput(node, i);
state->IntersectWith(node_states_.Get(input));
}
return UpdateState(node, state);
}
Reduction CsaLoadElimination::ReduceStart(Node* node) {
return UpdateState(node, empty_state());
}
Reduction CsaLoadElimination::ReduceCall(Node* node) {
Node* value = NodeProperties::GetValueInput(node, 0);
ExternalReferenceMatcher m(value);
if (m.Is(ExternalReference::check_object_type())) {
return PropagateInputState(node);
}
return ReduceOtherNode(node);
}
Reduction CsaLoadElimination::ReduceOtherNode(Node* node) {
if (node->op()->EffectInputCount() == 1 &&
node->op()->EffectOutputCount() == 1) {
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
return UpdateState(
node, node->op()->HasProperty(Operator::kNoWrite)
? state
: zone()->New<AbstractState>(HalfState(zone()),
state->immutable_state));
}
DCHECK_EQ(0, node->op()->EffectOutputCount());
return NoChange();
}
Reduction CsaLoadElimination::UpdateState(Node* node,
AbstractState const* state) {
AbstractState const* original = node_states_.Get(node);
if (state != original) {
if (original == nullptr || !state->Equals(original)) {
node_states_.Set(node, state);
return Changed(node);
}
}
return NoChange();
}
Reduction CsaLoadElimination::PropagateInputState(Node* node) {
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
return UpdateState(node, state);
}
CsaLoadElimination::AbstractState const* CsaLoadElimination::ComputeLoopState(
Node* node, AbstractState const* state) const {
DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
std::queue<Node*> queue;
std::unordered_set<Node*> visited;
visited.insert(node);
for (int i = 1; i < node->InputCount() - 1; ++i) {
queue.push(node->InputAt(i));
}
while (!queue.empty()) {
Node* const current = queue.front();
queue.pop();
if (visited.insert(current).second) {
if (current->opcode() == IrOpcode::kStoreToObject) {
Node* object = NodeProperties::GetValueInput(current, 0);
Node* offset = NodeProperties::GetValueInput(current, 1);
MachineRepresentation repr =
ObjectAccessOf(current->op()).machine_type.representation();
const HalfState* new_mutable_state =
state->mutable_state.KillField(object, offset, repr);
state = zone()->New<AbstractState>(*new_mutable_state,
state->immutable_state);
} else if (current->opcode() == IrOpcode::kInitializeImmutableInObject) {
#if DEBUG
Node* object = NodeProperties::GetValueInput(current, 0);
Node* offset = NodeProperties::GetValueInput(current, 1);
CHECK(state->immutable_state.Lookup(object, offset).IsEmpty());
#endif
} else if (!current->op()->HasProperty(Operator::kNoWrite)) {
return zone()->New<AbstractState>(HalfState(zone()),
state->immutable_state);
}
for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
queue.push(NodeProperties::GetEffectInput(current, i));
}
}
}
return state;
}
Node* CsaLoadElimination::TruncateAndExtend(Node* node,
MachineRepresentation from,
MachineType to) {
DCHECK(Helpers::Subsumes(from, to.representation()));
DCHECK_GE(ElementSizeInBytes(from), ElementSizeInBytes(to.representation()));
if (to == MachineType::Int8() || to == MachineType::Int16()) {
DCHECK_EQ(to.semantic(), MachineSemantic::kInt32);
if (from == MachineRepresentation::kWord64) {
node = graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
}
int shift = 32 - 8 * ElementSizeInBytes(to.representation());
return graph()->NewNode(machine()->Word32Sar(),
graph()->NewNode(machine()->Word32Shl(), node,
jsgraph()->Int32Constant(shift)),
jsgraph()->Int32Constant(shift));
} else if (to == MachineType::Uint8() || to == MachineType::Uint16()) {
if (from == MachineRepresentation::kWord64) {
node = graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
}
int mask = (1 << 8 * ElementSizeInBytes(to.representation())) - 1;
return graph()->NewNode(machine()->Word32And(), node,
jsgraph()->Int32Constant(mask));
} else if (from == MachineRepresentation::kWord64 &&
to.representation() == MachineRepresentation::kWord32) {
return graph()->NewNode(machine()->TruncateInt64ToInt32(), node);
} else {
DCHECK((from == to.representation() &&
(from == MachineRepresentation::kWord32 ||
from == MachineRepresentation::kWord64 || !IsIntegral(from))) ||
(IsAnyTagged(from) && IsAnyTagged(to.representation())));
return node;
}
}
CommonOperatorBuilder* CsaLoadElimination::common() const {
return jsgraph()->common();
}
MachineOperatorBuilder* CsaLoadElimination::machine() const {
return jsgraph()->machine();
}
TFGraph* CsaLoadElimination::graph() const { return jsgraph()->graph(); }
Isolate* CsaLoadElimination::isolate() const { return jsgraph()->isolate(); }
}
}
}