Files
TheRock/rocm-libraries/shared/stinkytofu/tools/tablegen/GenPatterns.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

848 lines
34 KiB
C++

/* ************************************************************************
* Copyright (C) 2025-2026 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* ************************************************************************ */
#include "GenPatterns.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
namespace stinkytofu {
//===----------------------------------------------------------------------===//
// NOTE: Pattern parsing is now handled by PatternParser (stinkytofu/serialization/asm)
// This file only contains code generation logic.
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// PatternCodeGen Implementation
//===----------------------------------------------------------------------===//
PatternCodeGen::PatternCodeGen(const std::string& outputDir) : outputDir(outputDir) {}
std::string PatternCodeGen::toCppIdentifier(const std::string& name) {
std::string result = name;
std::replace(result.begin(), result.end(), '-', '_');
return result;
}
// Convert assembly mnemonic to IR class name
// Example: "v_add_f32" -> "VAddF32"
static std::string mnemonicToIRClassName(const std::string& mnemonic) {
std::string result;
bool capitalizeNext = true;
for (char c : mnemonic) {
if (c == '_') {
capitalizeNext = true;
} else if (capitalizeNext) {
result += std::toupper(c);
capitalizeNext = false;
} else {
result += c;
}
}
return result;
}
std::string PatternCodeGen::generateHeader(PatternType type) {
if (type == PatternType::LogicalIR) {
return R"(/* ************************************************************************
* Copyright (C) 2025 Advanced Micro Devices, Inc.
* AUTO-GENERATED FILE - DO NOT EDIT
* Generated by: tools/tablegen/GenPatterns.cpp
* ************************************************************************ */
#pragma once
#include "stinkytofu/ir/logical/LogicalInstructions.hpp"
#include <optional>
#include <unordered_map>
#include <vector>
#include <memory>
namespace stinkytofu {
namespace hlir_patterns {
// Helper to check if a register is a constant
static inline bool isConstant(const StinkyRegister& reg) {
return reg.dataType == StinkyRegister::Type::LiteralDouble ||
reg.dataType == StinkyRegister::Type::LiteralInt;
}
// Helper to get constant value
static inline double getConstantValue(const StinkyRegister& reg) {
if (reg.dataType == StinkyRegister::Type::LiteralDouble) {
return reg.literalDouble;
} else if (reg.dataType == StinkyRegister::Type::LiteralInt) {
return static_cast<double>(reg.literalInt);
}
return 0.0;
}
// Built-in constant folding functions
static inline double AddConstants(double a, double b) { return a + b; }
static inline double SubConstants(double a, double b) { return a - b; }
static inline double MulConstants(double a, double b) { return a * b; }
static inline double NegateConstant(double a) { return -a; }
//===----------------------------------------------------------------------===//
// High-Level IR Pattern Matcher Interface
//===----------------------------------------------------------------------===//
/// Base class for all high-level IR pattern matchers
/// Operates on LogicalInstruction* (architecture-independent IR)
class PatternMatcher {
public:
virtual ~PatternMatcher() = default;
/// Context provided to pattern matchers
struct MatchContext {
const std::unordered_map<StinkyRegister, LogicalInstruction*>& defMap;
const std::unordered_map<StinkyRegister, int>& useCount;
};
/// Result of a successful pattern match and rewrite
struct RewriteResult {
std::vector<LogicalInstruction*> instructionsToRemove;
bool applied = false;
};
/// Try to match and rewrite the given IR instruction
/// Returns nullopt if pattern doesn't match
/// If pattern matches, performs rewrite in-place and returns instructions to remove
virtual std::optional<RewriteResult> tryMatchAndRewrite(
LogicalInstruction* inst,
const MatchContext& context) = 0;
/// Get the name of this pattern (for debugging)
virtual const char* getName() const = 0;
};
)";
} else // PatternType::Peephole
{
return R"(/* ************************************************************************
* Copyright (C) 2025 Advanced Micro Devices, Inc.
* AUTO-GENERATED FILE - DO NOT EDIT
* Generated by: tools/tablegen/GenPatterns.cpp
* ************************************************************************ */
#pragma once
#include "stinkytofu/ir/asm/StinkyAsmIR.hpp"
#include <optional>
#include <unordered_map>
#include <vector>
#include <memory>
#include <functional>
// Note: std::hash<StinkyRegister> is defined in StinkyAsmIR.hpp
namespace stinkytofu {
namespace patterns {
// Helper to check if a register is a constant
static inline bool isConstant(const StinkyRegister& reg) {
return reg.dataType == StinkyRegister::Type::LiteralDouble ||
reg.dataType == StinkyRegister::Type::LiteralInt;
}
// Helper to get constant value
static inline double getConstantValue(const StinkyRegister& reg) {
if (reg.dataType == StinkyRegister::Type::LiteralDouble) {
return reg.literalDouble;
} else if (reg.dataType == StinkyRegister::Type::LiteralInt) {
return static_cast<double>(reg.literalInt);
}
return 0.0;
}
// Built-in constant folding functions
static inline double AddConstants(double a, double b) { return a + b; }
static inline double SubConstants(double a, double b) { return a - b; }
static inline double MulConstants(double a, double b) { return a * b; }
static inline double NegateConstant(double a) { return -a; }
//===----------------------------------------------------------------------===//
// Uniform Pattern Matcher Interface
//===----------------------------------------------------------------------===//
/// Base class for all pattern matchers
/// Provides uniform API for matching and rewriting instructions
class PatternMatcher {
public:
virtual ~PatternMatcher() = default;
/// Context provided to pattern matchers
struct MatchContext {
const std::unordered_map<StinkyRegister, StinkyInstruction*>& defMap;
const std::unordered_map<StinkyRegister, int>& useCount;
GfxArchID arch;
};
/// Result of a successful pattern match and rewrite
struct RewriteResult {
std::vector<StinkyInstruction*> instructionsToRemove;
StinkyInstruction* replacementInst = nullptr;
bool applied = false;
};
/// Try to match and rewrite the given instruction
/// Returns nullopt if pattern doesn't match
/// If pattern matches, performs rewrite in-place and returns instructions to remove
virtual std::optional<RewriteResult> tryMatchAndRewrite(
StinkyInstruction* inst,
const MatchContext& context) = 0;
/// Get the name of this pattern (for debugging)
virtual const char* getName() const = 0;
};
)";
}
}
std::string PatternCodeGen::generateFooter(const std::vector<Pattern>& patterns, PatternType type) {
std::ostringstream oss;
oss << "//===----------------------------------------------------------------------===//\n";
oss << "// Pattern Registry\n";
oss << "//===----------------------------------------------------------------------===//"
"\n\n";
oss << "/// Create all pattern matchers\n";
oss << "/// Patterns are applied in the order they appear here\n";
oss << "static inline std::vector<std::unique_ptr<PatternMatcher>> createAllPatterns() {\n";
oss << " std::vector<std::unique_ptr<PatternMatcher>> patterns;\n";
for (const auto& pattern : patterns) {
oss << " patterns.push_back(std::make_unique<" << toCppIdentifier(pattern.name)
<< ">());\n";
}
oss << " return patterns;\n";
oss << "}\n\n";
// Use correct namespace based on pattern type
if (type == PatternType::LogicalIR) {
oss << "} // namespace hlir_patterns\n";
} else {
oss << "} // namespace patterns\n";
}
oss << "} // namespace stinkytofu\n";
return oss.str();
}
std::string PatternCodeGen::generateMatchFunction(const Pattern& pattern, PatternType type) {
std::ostringstream oss;
oss << " std::optional<RewriteResult> tryMatchAndRewrite(\n";
oss << " StinkyInstruction* inst,\n";
oss << " const MatchContext& context) override\n";
oss << " {\n";
oss << " const auto& defMap = context.defMap;\n";
oss << " const auto& useCount = context.useCount;\n\n";
// We match the LAST instruction first (bottom-up matching)
const auto& lastMatch = pattern.match.back();
oss << " // Match pattern root: " << lastMatch.opcode << "\n";
oss << " if (inst->getUnifiedOpcode() != GFX::" << lastMatch.opcode << ")\n";
oss << " return std::nullopt;\n\n";
// Assign the root instruction to its variable name
oss << " // Root instruction\n";
oss << " StinkyInstruction* " << lastMatch.instVar << " = inst;\n\n";
oss << " // Extract operands from " << lastMatch.opcode << "\n";
oss << " const auto& destRegs = " << lastMatch.instVar << "->getDestRegs();\n";
oss << " const auto& srcRegs = " << lastMatch.instVar << "->getSrcRegs();\n";
oss << " if (destRegs.empty() || srcRegs.size() < " << (lastMatch.operands.size() - 1)
<< ")\n";
oss << " return std::nullopt;\n\n";
// Bind operands
for (size_t i = 0; i < lastMatch.operands.size(); i++) {
if (i == 0) {
oss << " StinkyRegister " << lastMatch.operands[i] << " = destRegs[0];\n";
} else {
oss << " StinkyRegister " << lastMatch.operands[i] << " = srcRegs[" << (i - 1)
<< "];\n";
}
}
oss << "\n";
// Match previous instructions (if any)
if (pattern.match.size() > 1) {
for (size_t i = 0; i < pattern.match.size() - 1; i++) {
const auto& matchStmt = pattern.match[i];
// Find which operand from the last instruction references this instruction's result
std::string resultVar = matchStmt.operands[0]; // First operand is destination
oss << " // Find defining instruction for " << resultVar << "\n";
oss << " auto defIt = defMap.find(" << resultVar << ");\n";
oss << " if (defIt == defMap.end())\n";
oss << " return std::nullopt;\n";
oss << " StinkyInstruction* " << matchStmt.instVar << " = defIt->second;\n\n";
oss << " // Check opcode: " << matchStmt.opcode << "\n";
oss << " if (" << matchStmt.instVar
<< "->getUnifiedOpcode() != GFX::" << matchStmt.opcode << ")\n";
oss << " return std::nullopt;\n\n";
// Extract operands from this instruction (direct field access)
oss << " // Extract operands from " << matchStmt.opcode << "\n";
oss << " const auto& " << matchStmt.instVar << "_src = " << matchStmt.instVar
<< "->getSrcRegs();\n";
oss << " if (" << matchStmt.instVar << "_src.size() < "
<< (matchStmt.operands.size() - 1) << ")\n";
oss << " return std::nullopt;\n\n";
for (size_t j = 1; j < matchStmt.operands.size(); j++) {
oss << " StinkyRegister " << matchStmt.operands[j] << " = "
<< matchStmt.instVar << "_src[" << (j - 1) << "];\n";
}
oss << "\n";
}
}
// Check constraints
if (!pattern.constraints.empty()) {
oss << " // Check constraints\n";
for (const auto& constraint : pattern.constraints) {
if (constraint.function == "HasOneUse") {
oss << " {\n";
oss << " auto it = useCount.find(" << constraint.args[0] << ");\n";
oss << " if (it == useCount.end() || it->second != 1)\n";
oss << " return std::nullopt;\n";
oss << " }\n";
} else if (constraint.function == "IsConstant") {
oss << " if (!isConstant(" << constraint.args[0] << "))\n";
oss << " return std::nullopt;\n";
} else if (constraint.function == "SameValue") {
oss << " if (!(" << constraint.args[0] << " == " << constraint.args[1]
<< "))\n";
oss << " return std::nullopt;\n";
} else if (constraint.function == "DifferentValue") {
oss << " if (" << constraint.args[0] << " == " << constraint.args[1]
<< ")\n";
oss << " return std::nullopt;\n";
}
}
oss << "\n";
}
// Apply rewrite inline
oss << " // Match successful - apply rewrite!\n\n";
// Process rewrite statements
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall) {
// Constant folding: compute new constant
oss << " // Fold constants: " << stmt.lhs << " = " << stmt.function << "(...)\n";
oss << " double " << stmt.lhs << " = " << stmt.function << "(";
for (size_t i = 0; i < stmt.operands.size(); i++) {
if (i > 0) oss << ", ";
oss << "getConstantValue(" << stmt.operands[i] << ")";
}
oss << ");\n\n";
}
}
// Find the instruction to modify and the instruction to remove
// Look at the rewrite block to determine which instruction is replaced and which is removed
std::string instToModify;
std::string instToRemove;
if (pattern.match.size() == 2) {
// Look for 'replace' and 'remove' statements in rewrite block
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::Replace) {
instToModify = stmt.oldVar; // The instruction being replaced/modified
} else if (stmt.kind == RewriteStmt::Kind::Remove) {
instToRemove = stmt.instVar; // The instruction being removed
}
}
// Fallback: if no explicit replace/remove, use default behavior (modify first, remove last)
if (instToModify.empty() && instToRemove.empty()) {
instToModify = pattern.match[0].instVar;
instToRemove = pattern.match[1].instVar;
}
}
// Apply the rewrite: update instruction in-place
oss << " // Update " << instToModify << " in-place\n";
// Find the CreateInst rewrite statement to see what the new instruction should look like
const RewriteStmt* createStmt = nullptr;
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::CreateInst) {
createStmt = &stmt;
break;
}
}
if (createStmt) {
// Change the opcode to the new instruction type by updating the HwInstDesc
oss << " // Change opcode from old instruction to " << createStmt->opcode << "\n";
oss << " const HwInstDesc* newDesc = getMCIDByUOp(GFX::" << createStmt->opcode
<< ", context.arch);\n";
oss << " " << instToModify << "->updateHwInstDesc(newDesc);\n\n";
// Update destination register if present
// Pattern syntax: $dst = opcode $operands... or opcode $operands...
// First operand is destination if the instruction writes to a register
oss << " // Update destination register\n";
oss << " if (!" << instToModify << "->getDestRegs().empty()) {\n";
oss << " " << instToModify << "->setDestReg(0, " << createStmt->operands[0]
<< ");\n";
oss << " }\n\n";
// Calculate number of source operands
// If instruction has destination, sources start at operands[1]
// Otherwise, all operands are sources (for instructions like stores)
oss << " // Update source registers\n";
oss << " size_t firstSrcIdx = " << instToModify
<< "->getDestRegs().empty() ? 0 : 1;\n";
oss << " size_t numSrcRegs = " << createStmt->operands.size() << " - firstSrcIdx;\n";
oss << " if (" << instToModify << "->getSrcRegs().size() < numSrcRegs) {\n";
oss << " " << instToModify << "->resizeSrcRegs(numSrcRegs);\n";
oss << " }\n\n";
// Update source registers based on the rewrite statement
// Operands[0] is destination (if instruction has one), operands[1..] are sources
oss << " size_t srcIdx = 0;\n";
for (size_t i = 1; i < createStmt->operands.size(); ++i) {
const std::string& operand = createStmt->operands[i];
// Check if this operand is a folded constant from a builtin call
bool isBuiltinResult = false;
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall && stmt.lhs == operand) {
oss << " " << instToModify
<< "->setSrcReg(srcIdx++, StinkyRegister(static_cast<double>(" << operand
<< ")));\n";
isBuiltinResult = true;
break;
}
}
// If not a builtin result, it's a direct operand reference
if (!isBuiltinResult) {
oss << " " << instToModify << "->setSrcReg(srcIdx++, " << operand << ");\n";
}
}
} else {
// Fallback: old behavior for backward compatibility
oss << " " << instToModify << "->setDestReg(0, " << lastMatch.operands[0] << ");\n";
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall) {
oss << " " << instToModify
<< "->setSrcReg(0, StinkyRegister(static_cast<double>(" << stmt.lhs << ")));\n";
break;
}
}
}
oss << "\n";
// Return result with instruction to remove
oss << " // Return result\n";
oss << " RewriteResult result;\n";
oss << " result.instructionsToRemove = {" << instToRemove << "};\n";
oss << " result.replacementInst = " << instToModify << ";\n";
oss << " result.applied = true;\n";
oss << " return result;\n";
oss << " }\n";
return oss.str();
}
std::string PatternCodeGen::generateMatcherClass(const Pattern& pattern, PatternType type) {
std::ostringstream oss;
oss << "//===----------------------------------------------------------------------===//\n";
oss << "// Pattern: " << pattern.name << "\n";
oss << "//===----------------------------------------------------------------------===//"
"\n\n";
oss << "class " << toCppIdentifier(pattern.name) << " : public PatternMatcher {\n";
oss << "public:\n";
// tryMatchAndRewrite function (match + rewrite combined)
oss << generateMatchFunction(pattern, type);
oss << "\n";
// getName function
oss << " const char* getName() const override {\n";
oss << " return \"" << pattern.name << "\";\n";
oss << " }\n";
oss << "};\n\n";
return oss.str();
}
//===----------------------------------------------------------------------===//
// High-Level IR Pattern Generation (architecture-independent)
//===----------------------------------------------------------------------===//
std::string PatternCodeGen::generateMatchFunctionHLIR(const Pattern& pattern) {
std::ostringstream oss;
oss << " std::optional<RewriteResult> tryMatchAndRewrite(\n";
oss << " LogicalInstruction* inst,\n";
oss << " const MatchContext& context) override\n";
oss << " {\n";
oss << " const auto& defMap = context.defMap;\n";
oss << " const auto& useCount = context.useCount;\n\n";
// We match the LAST instruction first (bottom-up matching)
const auto& lastMatch = pattern.match.back();
oss << " // Match pattern root: " << lastMatch.opcode << "\n";
oss << " if (inst->getOpcode() != logical::" << mnemonicToIRClassName(lastMatch.opcode)
<< ")\n";
oss << " return std::nullopt;\n\n";
// Assign the root instruction to its variable name
oss << " // Root instruction\n";
oss << " LogicalInstruction* " << lastMatch.instVar << " = inst;\n\n";
oss << " // Extract operands from " << lastMatch.opcode << "\n";
oss << " const auto& destRegs = " << lastMatch.instVar << "->dests;\n";
oss << " const auto& srcRegs = " << lastMatch.instVar << "->srcs;\n";
oss << " if (destRegs.empty() || srcRegs.size() < " << (lastMatch.operands.size() - 1)
<< ")\n";
oss << " return std::nullopt;\n\n";
// Bind operands
for (size_t i = 0; i < lastMatch.operands.size(); i++) {
if (i == 0) {
oss << " StinkyRegister " << lastMatch.operands[i] << " = destRegs[0];\n";
} else {
oss << " StinkyRegister " << lastMatch.operands[i] << " = srcRegs[" << (i - 1)
<< "];\n";
}
}
oss << "\n";
// Match previous instructions (if any)
if (pattern.match.size() > 1) {
for (size_t i = 0; i < pattern.match.size() - 1; i++) {
const auto& matchStmt = pattern.match[i];
// Find which operand from the last instruction references this instruction's result
std::string resultVar = matchStmt.operands[0]; // First operand is destination
oss << " // Find defining instruction for " << resultVar << "\n";
oss << " auto defIt = defMap.find(" << resultVar << ");\n";
oss << " if (defIt == defMap.end())\n";
oss << " return std::nullopt;\n";
oss << " LogicalInstruction* " << matchStmt.instVar << " = defIt->second;\n\n";
oss << " // Check opcode: " << matchStmt.opcode << "\n";
oss << " if (" << matchStmt.instVar
<< "->getOpcode() != logical::" << mnemonicToIRClassName(matchStmt.opcode) << ")\n";
oss << " return std::nullopt;\n\n";
// Extract operands from this instruction (direct field access)
oss << " // Extract operands from " << matchStmt.opcode << "\n";
oss << " const auto& " << matchStmt.instVar << "_src = " << matchStmt.instVar
<< "->srcs;\n";
oss << " if (" << matchStmt.instVar << "_src.size() < "
<< (matchStmt.operands.size() - 1) << ")\n";
oss << " return std::nullopt;\n\n";
for (size_t j = 1; j < matchStmt.operands.size(); j++) {
oss << " StinkyRegister " << matchStmt.operands[j] << " = "
<< matchStmt.instVar << "_src[" << (j - 1) << "];\n";
}
oss << "\n";
}
}
// Check constraints
if (!pattern.constraints.empty()) {
oss << " // Check constraints\n";
for (const auto& constraint : pattern.constraints) {
if (constraint.function == "HasOneUse") {
oss << " {\n";
oss << " auto it = useCount.find(" << constraint.args[0] << ");\n";
oss << " if (it == useCount.end() || it->second != 1)\n";
oss << " return std::nullopt;\n";
oss << " }\n";
} else if (constraint.function == "IsConstant") {
oss << " if (!isConstant(" << constraint.args[0] << "))\n";
oss << " return std::nullopt;\n";
} else if (constraint.function == "SameValue") {
oss << " if (!(" << constraint.args[0] << " == " << constraint.args[1]
<< "))\n";
oss << " return std::nullopt;\n";
} else if (constraint.function == "DifferentValue") {
oss << " if (" << constraint.args[0] << " == " << constraint.args[1]
<< ")\n";
oss << " return std::nullopt;\n";
}
}
oss << "\n";
}
// Apply rewrite inline
oss << " // Match successful - apply rewrite!\n\n";
// Process rewrite statements
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall) {
// Constant folding: compute new constant
oss << " // Fold constants: " << stmt.lhs << " = " << stmt.function << "(...)\n";
oss << " double " << stmt.lhs << " = " << stmt.function << "(";
for (size_t i = 0; i < stmt.operands.size(); i++) {
if (i > 0) oss << ", ";
oss << "getConstantValue(" << stmt.operands[i] << ")";
}
oss << ");\n\n";
}
}
// Find the instruction to modify and the instruction to remove
std::string instToModify;
std::string instToRemove;
if (pattern.match.size() == 2) {
// Look for 'replace' and 'remove' statements in rewrite block
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::Replace) {
instToModify = stmt.oldVar; // The instruction being replaced/modified
} else if (stmt.kind == RewriteStmt::Kind::Remove) {
instToRemove = stmt.instVar; // The instruction being removed
}
}
// Fallback: if no explicit replace/remove, use default behavior (modify first, remove last)
if (instToModify.empty() && instToRemove.empty()) {
instToModify = pattern.match[0].instVar;
instToRemove = pattern.match[1].instVar;
}
}
// Apply the rewrite: update instruction in-place
oss << " // Update " << instToModify << " in-place\n";
// Find the CreateInst rewrite statement
const RewriteStmt* createStmt = nullptr;
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::CreateInst) {
createStmt = &stmt;
break;
}
}
if (createStmt) {
// For high-level IR, we need to replace the instruction with a new one
// since we can't just change the opcode (different types)
oss << " // Create new instruction: " << createStmt->opcode << "\n";
oss << " // Note: For high-level IR, instruction replacement will be handled\n";
oss << " // by the pass infrastructure (removing old, adding new)\n\n";
// Update destination register
oss << " // Update destination register\n";
oss << " if (!" << instToModify << "->dests.empty()) {\n";
oss << " " << instToModify << "->dests[0] = " << createStmt->operands[0]
<< ";\n";
oss << " }\n\n";
// Update source registers
oss << " // Update source registers\n";
oss << " " << instToModify << "->srcs.clear();\n";
// Add all source operands
for (size_t i = 1; i < createStmt->operands.size(); ++i) {
const std::string& operand = createStmt->operands[i];
// Check if this operand is a folded constant from a builtin call
bool isBuiltinResult = false;
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall && stmt.lhs == operand) {
oss << " " << instToModify
<< "->srcs.push_back(StinkyRegister(static_cast<double>(" << operand
<< ")));\n";
isBuiltinResult = true;
break;
}
}
// If not a builtin result, it's a direct operand reference
if (!isBuiltinResult) {
oss << " " << instToModify << "->srcs.push_back(" << operand << ");\n";
}
}
// Change opcode by creating a new instruction of the correct type
oss << "\n // TODO: Change opcode from old type to " << createStmt->opcode << "\n";
oss << " // This requires creating a new IR instruction of the target type\n";
oss << " // For now, we just modify operands in-place\n";
} else {
// Fallback: old behavior for backward compatibility
oss << " " << instToModify << "->dests[0] = " << lastMatch.operands[0] << ";\n";
for (const auto& stmt : pattern.rewrite) {
if (stmt.kind == RewriteStmt::Kind::BuiltinCall) {
oss << " " << instToModify
<< "->srcs[0] = StinkyRegister(static_cast<double>(" << stmt.lhs << "));\n";
break;
}
}
}
oss << "\n";
// Return result with instruction to remove
oss << " // Return result\n";
oss << " RewriteResult result;\n";
oss << " result.instructionsToRemove = {" << instToRemove << "};\n";
oss << " result.applied = true;\n";
oss << " return result;\n";
oss << " }\n";
return oss.str();
}
std::string PatternCodeGen::generateMatcherClassHLIR(const Pattern& pattern) {
std::ostringstream oss;
oss << "//===----------------------------------------------------------------------===//\n";
oss << "// Pattern: " << pattern.name << "\n";
oss << "//===----------------------------------------------------------------------===//"
"\n\n";
oss << "class " << toCppIdentifier(pattern.name) << " : public PatternMatcher {\n";
oss << "public:\n";
// tryMatchAndRewrite function (match + rewrite combined)
oss << generateMatchFunctionHLIR(pattern);
oss << "\n";
// getName function
oss << " const char* getName() const override {\n";
oss << " return \"" << pattern.name << "\";\n";
oss << " }\n";
oss << "};\n\n";
return oss.str();
}
bool PatternCodeGen::generateMatchers(const std::vector<Pattern>& patterns, PatternType type) {
// Filter patterns by type
std::vector<Pattern> filteredPatterns;
for (const auto& pattern : patterns) {
if (pattern.type == type) {
filteredPatterns.push_back(pattern);
}
}
if (filteredPatterns.empty()) {
// Normal: pattern file may only define one type (e.g. Peephole only). Skip silently.
return true;
}
// Determine output file name based on pattern type
std::string outputPath;
if (type == PatternType::LogicalIR) {
outputPath = outputDir + "/LogicalIRPatterns.inc";
} else if (type == PatternType::Peephole) {
outputPath = outputDir + "/PeepholePatterns.inc";
} else {
std::cerr << "Unsupported pattern type for code generation\n";
return false;
}
std::ofstream out(outputPath);
if (!out.is_open()) {
std::cerr << "Failed to open output file: " << outputPath << "\n";
return false;
}
out << generateHeader(type);
// Generate pattern matchers based on type
if (type == PatternType::LogicalIR) {
// Generate matchers for high-level IR (architecture-independent)
for (const auto& pattern : filteredPatterns) {
out << generateMatcherClassHLIR(pattern);
}
out << generateFooter(filteredPatterns, type);
} else {
// Generate full matchers for assembly IR (architecture-specific)
for (const auto& pattern : filteredPatterns) {
out << generateMatcherClass(pattern, type);
}
out << generateFooter(filteredPatterns, type);
}
std::cout << "Generated " << filteredPatterns.size() << " pattern matchers: " << outputPath
<< "\n";
return true;
}
//===----------------------------------------------------------------------===//
// Public API
//===----------------------------------------------------------------------===//
bool genPeepholePatterns(const std::string& patternFile, const std::string& outdir) {
std::cout << "Parsing patterns from: " << patternFile << "\n";
// Use the PatternParser from stinkytofu/serialization/asm/PatternParser.hpp
auto patterns = parsePatternFile(patternFile);
if (patterns.empty()) {
std::cerr << "No patterns found or parsing failed: " << patternFile << "\n";
return false;
}
std::cout << "Found " << patterns.size() << " pattern(s)\n";
// Create output directory if it doesn't exist
std::filesystem::create_directories(outdir);
PatternCodeGen codegen(outdir);
// Generate matchers for both pattern types
bool success = true;
success &= codegen.generateMatchers(patterns, PatternType::Peephole);
success &= codegen.generateMatchers(patterns, PatternType::LogicalIR);
return success;
}
} // namespace stinkytofu