/* ************************************************************************ * 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 "stinkytofu-opt.hpp" #include #include #include #include #include #include #include #include #include #include "stinkytofu/Version.h" #include "stinkytofu/analysis/AnalysisRegistration.hpp" #include "stinkytofu/bindings/python/Module.hpp" #include "stinkytofu/hardware/ArchHelper.hpp" #include "stinkytofu/hardware/ToolchainCaps.hpp" #include "stinkytofu/ir/asm/StinkyAsmIR.hpp" #include "stinkytofu/ir/asm/StinkySignature.hpp" #include "stinkytofu/pipeline/Backend.hpp" #include "stinkytofu/pipeline/BackendRegistry.hpp" #include "stinkytofu/serialization/asm/IRConverter.hpp" #include "stinkytofu/serialization/asm/IRParser.hpp" #include "stinkytofu/serialization/asm/RawAsmParser.hpp" #include "stinkytofu/serialization/asm/StinkyAsmEmitter.hpp" #include "stinkytofu/support/StandardInstrumentations.hpp" using namespace stinkytofu; namespace { /** * DeserializeStinkytofuIRPass is a pass that deserializes Stinkytofu IR from a file. */ class DeserializeStinkytofuIRPass : public StinkyInstPass { public: static char ID; DeserializeStinkytofuIRPass(const std::string& stinkytofuIRFile) : stinkytofuIRFile(stinkytofuIRFile) {} const char* getName() const override { return "DeserializeStinkytofuIRPass"; } PassID getPassID() const override { return &DeserializeStinkytofuIRPass::ID; } PreservedAnalyses run(Function& func, PassContext& passCtx, AnalysisManager& /*AM*/) override { GfxArchID arch = getGfxArchID(passCtx.getGemmTileConfig().arch[0], passCtx.getGemmTileConfig().arch[1], passCtx.getGemmTileConfig().arch[2]); std::string irText = readFile(stinkytofuIRFile); // Use the shared conversion logic from StinkyIRConverter (creates entry block) StinkyErrorCode result = StinkyIRConverter::populateFunctionFromString(irText, func, passCtx, arch); if (result != StinkyErrorCode::SUCCESS) { if (result == StinkyErrorCode::PASSCTX_EMPTY) { std::cerr << "No PassContext available. Call convertToIRList first.\n"; } else { std::cerr << "Error: Failed to populate IRList from string. Error code: " << static_cast(result) << "\n"; } return PreservedAnalyses::none(); } return PreservedAnalyses::none(); } private: const std::string stinkytofuIRFile; std::string readFile(const std::string& filename) { std::ifstream file(filename); std::stringstream buffer; buffer << file.rdbuf(); return buffer.str(); } }; char DeserializeStinkytofuIRPass::ID = 0; // Function to print available passes void printAvailablePasses() { std::cout << "Available passes:\n"; std::cout << "=================\n"; for (const auto& passInfo : availablePasses) { std::cout << " --" << passInfo.name << "\n"; } } // A user-specified pass: bare name + optional comma-separated argument list // parsed out of `--PassName=arg1,arg2`. struct RequestedPass { std::string name; std::vector args; }; // Function to find and create a pass by name (with optional arguments) std::unique_ptr createPassByName(const std::string& passName, const std::vector& args = {}) { for (const auto& passInfo : availablePasses) { if (passName == passInfo.name) { return passInfo.creator(args); } } return nullptr; } static void trimWhitespace(std::string& s) { while (!s.empty() && std::isspace(static_cast(s.front()))) { s.erase(0, 1); } while (!s.empty() && std::isspace(static_cast(s.back()))) { s.pop_back(); } } int extractOptLevel(int argc, char** argv) { for (int i = 1; i < argc; ++i) { std::string a = argv[i]; if (a == "-O0") return 0; if (a == "-O1") return 1; if (a == "-O2") return 2; if (a == "-O3") return 3; } return -1; } bool isKernelConfigArg(const std::string& arg) { static constexpr const char* kAssignPrefixes[] = { "--TileA0=", "--TileB0=", "--TileM0=", "--NumGRA=", "--NumGRB=", "--NumGRM=", "--NumWaves="}; for (const char* prefix : kAssignPrefixes) { if (arg.starts_with(prefix)) return true; } return arg == "--TileA0" || arg == "--TileB0" || arg == "--TileM0" || arg == "--NumGRA" || arg == "--NumGRB" || arg == "--NumGRM" || arg == "--NumWaves"; } // Function to parse command-line arguments for passes. // // Accepts both `--PassName` and `--PassName=arg1,arg2,...` forms. // In the second form, the comma-separated tokens after `=` are passed // to the pass's creator function. std::vector parsePassNames(int argc, char** argv, int startIdx) { std::vector passes; for (int i = startIdx; i < argc; ++i) { std::string arg = argv[i]; if (arg == "-O0" || arg == "-O1" || arg == "-O2" || arg == "-O3") continue; if (arg.substr(0, 2) == "--") { if (arg == "--print-output" || arg == "--emit-asm" || arg == "--remarks" || arg == "--verify-each" || arg == "--preserve-symbolic-regs" || arg == "--preserve-comments" || arg.starts_with("--ds-read-order=") || arg.starts_with("--ds-read-queue-depth=") || arg.starts_with("--ds-read-drain-latency=") || arg.starts_with("--ds-read-per-wmma=") || arg.starts_with("--global-read-queue-depth=") || arg.starts_with("--global-read-drain-latency=") || arg.starts_with("--vgpr-msb-mode=") || arg == "--from-label" || arg == "--to-label" || isKernelConfigArg(arg)) continue; // Two-arg flags: skip both the flag and its value so the value // doesn't get mistaken for a pass name and the flag doesn't get // mistaken for a missing pass (e.g. `--debug-pass FooPass` was // emitting `Warning: Unknown pass 'debug-pass'` because the // `--` prefix was stripped and `debug-pass` wasn't a known pass). if (arg == "-o" || arg == "--debug-pass" || arg == "--TileA0" || arg == "--TileB0" || arg == "--TileM0" || arg == "--NumGRA" || arg == "--NumGRB" || arg == "--NumGRM" || arg == "--NumWaves") { ++i; // skip the value argument continue; } std::string body = arg.substr(2); // strip "--" RequestedPass rp; size_t eq = body.find('='); if (eq == std::string::npos) { rp.name = std::move(body); } else { rp.name = body.substr(0, eq); std::string rest = body.substr(eq + 1); size_t start = 0; while (start <= rest.size()) { size_t comma = rest.find(',', start); std::string tok = rest.substr( start, comma == std::string::npos ? std::string::npos : comma - start); trimWhitespace(tok); if (!tok.empty()) rp.args.push_back(std::move(tok)); if (comma == std::string::npos) break; start = comma + 1; } } passes.push_back(std::move(rp)); } } return passes; } static bool parseUint32Value(const std::string& value, uint32_t& out) { if (value.empty()) return false; uint64_t result = 0; for (unsigned char c : value) { if (!std::isdigit(c)) return false; result = result * 10 + static_cast(c - '0'); if (result > std::numeric_limits::max()) return false; } out = static_cast(result); return true; } static bool parseGemmTileField(const char* flag, const std::string& value, uint32_t& field, std::string& error) { if (parseUint32Value(value, field)) return true; error = std::string("Invalid value for ") + flag; return false; } static bool parseGemmTileConfigOptions(int argc, char** argv, stinkytofu::GemmTileConfig& config, std::string& error) { config = {}; for (int i = 1; i < argc; ++i) { std::string a = argv[i]; auto parseAssign = [&](const char* prefix, const char* flag, uint32_t& field) -> bool { if (!a.starts_with(prefix)) return true; return parseGemmTileField(flag, a.substr(std::strlen(prefix)), field, error); }; auto parseNext = [&](const char* flag, uint32_t& field) -> bool { if (a != flag) return true; if (i + 1 >= argc) return true; return parseGemmTileField(flag, argv[++i], field, error); }; if (!parseAssign("--TileA0=", "--TileA0", config.TileA0)) return false; if (a.starts_with("--TileA0=")) continue; if (!parseAssign("--TileB0=", "--TileB0", config.TileB0)) return false; if (a.starts_with("--TileB0=")) continue; if (!parseAssign("--TileM0=", "--TileM0", config.TileM0)) return false; if (a.starts_with("--TileM0=")) continue; if (!parseAssign("--NumGRA=", "--NumGRA", config.NumGRA)) return false; if (a.starts_with("--NumGRA=")) continue; if (!parseAssign("--NumGRB=", "--NumGRB", config.NumGRB)) return false; if (a.starts_with("--NumGRB=")) continue; if (!parseAssign("--NumGRM=", "--NumGRM", config.NumGRM)) return false; if (a.starts_with("--NumGRM=")) continue; if (!parseAssign("--NumWaves=", "--NumWaves", config.NumWaves)) return false; if (a.starts_with("--NumWaves=")) continue; if (!parseNext("--TileA0", config.TileA0)) return false; if (a == "--TileA0") continue; if (!parseNext("--TileB0", config.TileB0)) return false; if (a == "--TileB0") continue; if (!parseNext("--TileM0", config.TileM0)) return false; if (a == "--TileM0") continue; if (!parseNext("--NumGRA", config.NumGRA)) return false; if (a == "--NumGRA") continue; if (!parseNext("--NumGRB", config.NumGRB)) return false; if (a == "--NumGRB") continue; if (!parseNext("--NumGRM", config.NumGRM)) return false; if (a == "--NumGRM") continue; if (!parseNext("--NumWaves", config.NumWaves)) return false; if (a == "--NumWaves") continue; } return true; } static void printKernelConfigHelp(std::ostream& os) { os << " Kernel configuration (GemmTileConfig fields):\n"; os << " --TileA0 (or --TileA0=)\n"; os << " --TileB0 (or --TileB0=)\n"; os << " --TileM0 (or --TileM0=)\n"; os << " --NumGRA (or --NumGRA=)\n"; os << " --NumGRB (or --NumGRB=)\n"; os << " --NumGRM (or --NumGRM=)\n"; os << " --NumWaves (or --NumWaves=)\n"; } } // namespace int main(int argc, char** argv) { BackendRegistry::registerAllBackends(); if (argc < 2) { std::cerr << "Usage: " << argv[0] << " [options] [--pass1] [--pass2] ...\n\n"; std::cerr << "Options:\n"; std::cerr << " --arch Target architecture. Supported:"; for (const auto& key : BackendRegistry::getRegisteredArchKeys()) std::cerr << " " << key; std::cerr << "\n"; std::cerr << " -O Run the registered pipeline at opt level N (0-3)\n"; std::cerr << " --remarks Enable optimization remarks on stderr\n"; std::cerr << " --verify-each Verify StinkyTofu ASM IR after every pass\n"; std::cerr << " --list-passes List all available passes\n"; std::cerr << " --version Show version information\n"; std::cerr << " --help Show this help message\n\n"; printKernelConfigHelp(std::cerr); std::cerr << "\nInput formats:\n"; std::cerr << " .stir StinkyTofu IR text format (default)\n"; std::cerr << " .s Raw GPU assembly (auto-detected by extension)\n\n"; std::cerr << "Output flags:\n"; std::cerr << " --print-output Emit optimized IR (StinkyTofu text format)\n"; std::cerr << " --emit-asm Emit optimized GPU assembly (always on for .s input)\n"; std::cerr << " -o Write output to file instead of stdout\n"; std::cerr << " --from-label