blob: bbdcab02469dccb71f97b34352e195e7805d888c [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/ir/transform/value_to_let.h"
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/validator.h"
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
namespace tint::core::ir::transform {
namespace {
/// Access is an enumerator of memory access operations
enum class Access : uint8_t { kLoad, kStore };
/// Accesses is a set of of Access
using Accesses = EnumSet<Access>;
/// @returns the accesses that may be performed by the instruction @p inst
Accesses AccessesFor(ir::Instruction* inst) {
return tint::Switch<Accesses>(
inst, //
[&](const ir::Load*) { return Access::kLoad; }, //
[&](const ir::LoadVectorElement*) { return Access::kLoad; }, //
[&](const ir::Store*) { return Access::kStore; }, //
[&](const ir::StoreVectorElement*) { return Access::kStore; }, //
[&](const ir::Call*) {
return Accesses{Access::kLoad, Access::kStore};
},
[&](Default) { return Accesses{}; });
}
/// PIMPL state for the transform.
struct State {
/// The IR module.
Module& ir;
/// The IR builder.
Builder b{ir};
/// The type manager.
core::type::Manager& ty{ir.Types()};
/// Process the module.
void Process() {
// Process each block.
for (auto* block : ir.blocks.Objects()) {
Process(block);
}
}
private:
void Process(ir::Block* block) {
// A set of possibly-inlinable values returned by a instructions that has not yet been
// marked-for or ruled-out-for inlining.
Hashset<ir::InstructionResult*, 32> pending_resolution;
// The accesses of the values in pending_resolution.
Access pending_access = Access::kLoad;
auto put_pending_in_lets = [&] {
for (auto* pending : pending_resolution) {
PutInLet(pending);
}
pending_resolution.Clear();
};
auto maybe_put_in_let = [&](auto* inst) {
if (auto* result = inst->Result(0)) {
auto& usages = result->Usages();
switch (usages.Count()) {
case 0: // No usage
break;
case 1: { // Single usage
auto* usage = (*usages.begin()).instruction;
if (usage->Block() == inst->Block()) {
// Usage in same block. Assign to pending_resolution, as we don't
// know whether its safe to inline yet.
pending_resolution.Add(result);
} else {
// Usage from another block. Cannot inline.
inst = PutInLet(result);
}
break;
}
default: // Value has multiple usages. Cannot inline.
inst = PutInLet(result);
break;
}
}
};
for (ir::Instruction* inst = block->Front(); inst; inst = inst->next) {
// This transform assumes that all multi-result instructions have been replaced
TINT_ASSERT(inst->Results().Length() < 2);
// The memory accesses of this instruction
auto accesses = AccessesFor(inst);
for (auto* operand : inst->Operands()) {
// If the operand is in pending_resolution, then we know it has a single use and
// because it hasn't been removed with put_pending_in_lets(), we know its safe to
// inline without breaking access ordering. By inlining the operand, we are pulling
// the operand's instruction into the same statement as this instruction, so this
// instruction adopts the access of the operand.
if (auto* result = As<InstructionResult>(operand)) {
if (pending_resolution.Remove(result)) {
// Var and Let are always statements, and so can never be inlined. As such,
// they do not need to propagate the pending resolution through them.
if (!inst->IsAnyOf<Var, Let>()) {
accesses.Add(pending_access);
}
}
}
}
if (accesses.Contains(Access::kStore)) { // Note: Also handles load + store
put_pending_in_lets();
maybe_put_in_let(inst);
} else if (accesses.Contains(Access::kLoad)) {
if (pending_access != Access::kLoad) {
put_pending_in_lets();
pending_access = Access::kLoad;
}
maybe_put_in_let(inst);
}
}
}
/// PutInLet places the value into a new 'let' instruction, immediately after the value's
/// instruction
/// @param value the value to place into the 'let'
/// @return the created 'let' instruction.
ir::Let* PutInLet(ir::InstructionResult* value) {
auto* inst = value->Instruction();
auto* let = b.Let(value->Type());
value->ReplaceAllUsesWith(let->Result(0));
let->SetValue(value);
let->InsertAfter(inst);
if (auto name = b.ir.NameOf(value); name.IsValid()) {
b.ir.SetName(let->Result(0), name);
b.ir.ClearName(value);
}
return let;
}
};
} // namespace
Result<SuccessType> ValueToLet(Module& ir) {
auto result = ValidateAndDumpIfNeeded(ir, "ValueToLet transform");
if (result != Success) {
return result;
}
State{ir}.Process();
return Success;
}
} // namespace tint::core::ir::transform