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
2145 lines
98 KiB
C++
2145 lines
98 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.
|
|
*
|
|
* ************************************************************************ */
|
|
|
|
/**
|
|
* @file GenInstructions.cpp
|
|
* @brief Generates instruction metadata from .def files
|
|
*
|
|
* This generator:
|
|
* 1. Parses format definitions (DEF_FORMAT) from GfxXXXFormats.def
|
|
* 2. Parses instruction definitions (DEF_T) from GfxXXXInstructions.def
|
|
* 3. Applies format inheritance (instructions inherit format defaults)
|
|
* 4. Generates .inc files:
|
|
* - *_costs.inc: Instruction cost table
|
|
* - *_operands.inc: Operand requirements
|
|
* - *_init.inc: DEF_T initialization calls
|
|
*
|
|
* Usage: tablegen --gen-instructions --arch=gfx1250 --input-dir=hardware/src/gfx
|
|
* --output-dir=<build>/hardware (per-arch .def in GfxXXX/ subdirs)
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace stinkytofu {
|
|
|
|
//==========================================================================
|
|
// DATA STRUCTURES
|
|
//==========================================================================
|
|
|
|
// Operand specification
|
|
struct OperandSpec {
|
|
bool isDest = false;
|
|
int width = 1; // Register width (number of consecutive registers)
|
|
std::vector<std::string> valueTypes; // e.g., {"Register", "LiteralInt"}
|
|
std::vector<std::string> regTypes; // e.g., {"VGPR", "SGPR"}
|
|
|
|
// Empty vectors mean "any type"
|
|
bool isAnyValueType() const {
|
|
return valueTypes.empty();
|
|
}
|
|
bool isAnyRegType() const {
|
|
return regTypes.empty();
|
|
}
|
|
};
|
|
|
|
// HWREG name table entry parsed from DEF_HWREG(NAME, ID) lines.
|
|
struct HwRegEntry {
|
|
std::string name; // e.g. "HW_REG_WAVE_MODE"
|
|
int id = 0; // numeric slot, 0..63
|
|
};
|
|
|
|
// Architecture metadata (from DEF_ARCH in Formats.def)
|
|
struct ArchDef {
|
|
std::string name;
|
|
int major = 0;
|
|
int minor = 0;
|
|
int stepping = 0;
|
|
int wavefront = 64;
|
|
int maxVGPR = 256;
|
|
int maxSGPR = 102;
|
|
int maxAGPR = 0;
|
|
int totalVgprPerSimd = 0;
|
|
int vgprAllocGranule = 0;
|
|
int defaultCycle = 4;
|
|
int defaultLatency = 4;
|
|
// ECC presence: D16 VMEM zero-fills the non-data half and True16 VALU does
|
|
// a HW RMW at full-DWORD granularity.
|
|
int d16Writes32BitVgpr = 0;
|
|
std::vector<HwRegEntry> hwRegs;
|
|
};
|
|
|
|
// Operand field description.
|
|
//
|
|
// Full entry (in .fields or .operand_fields):
|
|
// {Pos, EncodeField, Type, Size [, Options...]}
|
|
// e.g. {D0, vdata, vgpr, 32, RW}
|
|
// e.g. {D0, vdst, vcc, M64} — M64 in size position implies 64
|
|
//
|
|
// Pos D0, D1, ... (dest) or S0, S1, ... (src)
|
|
// EncodeField hardware encoding field (vdata, simm16, ssrc0, ...)
|
|
// Type operand type (vgpr, sreg, label, wait_alu, ...)
|
|
// Size field size in bits (16, 32, 64, 128, ...) or M64
|
|
// Options RW (read-write), M64 (64-bit lane mask, truncatable in wave32)
|
|
//
|
|
// Partial override (in .operand_fields only) — inherits from format .fields
|
|
// and selectively overrides individual properties by name:
|
|
// {S0, .size=64} — override size only
|
|
// {S0, .type=wait_alu} — override type only
|
|
// {D0, .size=64, .type=vgpr} — override both
|
|
struct OperandFieldEntry {
|
|
bool isDest = false;
|
|
bool isReadWrite = false;
|
|
bool isM64 = false;
|
|
std::string encodeField; // "vdata", "vaddr", "rsrc", "soffset", "simm16", ...
|
|
std::string fieldType; // "vgpr", "sreg", "label", "wait_alu", ...
|
|
int sizeBits = 0;
|
|
std::string positionKey; // "D0", "S0", etc. — non-empty for partial overrides
|
|
};
|
|
|
|
// Format definition (provides defaults for instructions)
|
|
// Hardware formats (VOP1, VOP3P, MUBUF, SMRD, etc.) are defined first; opcode-family
|
|
// formats (e.g. MFMA on CDNA) can inherit from a hardware format via .parent.
|
|
struct FormatDef {
|
|
std::string name;
|
|
std::string parent; // e.g. "VOP3P" for MFMA (inherits encoding, etc.)
|
|
std::string microcode; // microcode format (e.g. "MC_VOP3P", "MC_SMEM")
|
|
std::string unit;
|
|
std::vector<int> encoding;
|
|
int maxOperands = 0;
|
|
int cycle = 0; // 0 = not specified (inherit from parent or arch default)
|
|
int latency = 0;
|
|
int coIssueWindow = -1; // -1 = not specified (inherit from parent); VALU co-issue window
|
|
std::vector<std::string> flags;
|
|
|
|
// Default operand field descriptions for instructions using this format
|
|
std::vector<OperandFieldEntry> fields;
|
|
|
|
// Promoted (wider) encoding format name (e.g., "VOP3" for a VOP2 format)
|
|
std::string promotedFormat;
|
|
|
|
// Operand field descriptions for the promoted encoding
|
|
std::vector<OperandFieldEntry> altFields;
|
|
};
|
|
|
|
// Cost override: when modifier matches (e.g. MatrixFmtModifiers(FP4, FP4)), use (cycle, latency)
|
|
struct CostOverrideEntry {
|
|
std::string modifierType; // e.g. "MatrixFmtModifiers"
|
|
std::vector<std::string> args; // e.g. {"FP4", "FP4"}
|
|
int cycle = 1;
|
|
int latency = 1;
|
|
};
|
|
|
|
// Instruction definition
|
|
struct InstructionDef {
|
|
std::string instClass; // e.g., "FloatAddInst"
|
|
std::string mnemonic; // e.g., "v_add_f32"
|
|
int lineNumber = 0; // 1-based source line for error reporting
|
|
|
|
// Optional fields (inherit from format if not specified)
|
|
std::string format; // e.g., "VOP3"
|
|
int cycle = 0; // 0 = not specified (inherit from format, then arch default)
|
|
int latency = 0;
|
|
int coIssueWindow = -1; // -1 = not specified (inherit from format); VALU co-issue window
|
|
std::vector<CostOverrideEntry> costOverrides; // modifier-keyed overrides
|
|
std::vector<OperandSpec> operands; // Operand specifications
|
|
std::vector<OperandFieldEntry> operandFields; // Operand field descriptions
|
|
std::vector<OperandFieldEntry> altOperandFields; // Promoted encoding field overrides
|
|
std::vector<std::string> flags; // Instruction-specific flags
|
|
std::vector<std::string> logical; // Logical IR names mapping to this mnemonic: .logical =
|
|
// {"A", "B"} or .logical = "A"
|
|
std::string unit; // Override format unit
|
|
|
|
// After format inheritance applied
|
|
std::vector<std::string> finalFlags; // Merged format + instance flags
|
|
std::string finalUnit;
|
|
std::string finalMicrocode; // Microcode format (e.g. "MC_VOP3P")
|
|
int finalEncoding = 0; // Encoding size in bits
|
|
std::vector<OperandFieldEntry> finalOperandFields; // After format inheritance
|
|
std::string finalPromotedFormat; // Promoted encoding format
|
|
std::vector<OperandFieldEntry> finalPromotedFields; // Promoted encoding fields
|
|
int encodingBits = 32; // From format .encoding (bits); sizeInBytes = encodingBits/8
|
|
};
|
|
|
|
//==========================================================================
|
|
// HELPERS
|
|
//==========================================================================
|
|
|
|
// Return indices into `v` sorted by `key(v[i])`. Lets a generator iterate the
|
|
// input vector in sorted order without mutating it, then emit a sorted table.
|
|
template <typename T, typename KeyFn>
|
|
static std::vector<size_t> sortedIndices(const std::vector<T>& v, KeyFn key) {
|
|
std::vector<size_t> idx(v.size());
|
|
for (size_t i = 0; i < v.size(); ++i) idx[i] = i;
|
|
std::sort(idx.begin(), idx.end(), [&](size_t a, size_t b) { return key(v[a]) < key(v[b]); });
|
|
return idx;
|
|
}
|
|
|
|
//==========================================================================
|
|
// PARSER
|
|
//==========================================================================
|
|
|
|
class DefTParser {
|
|
public:
|
|
DefTParser(const std::string& arch) : archName_(arch) {
|
|
arch_.name = arch;
|
|
}
|
|
|
|
// Parse format definitions file
|
|
bool parseFormats(const std::string& formatFile) {
|
|
std::ifstream ifs(formatFile);
|
|
if (!ifs) {
|
|
std::cerr << "Error: Cannot open format file: " << formatFile << "\n";
|
|
return false;
|
|
}
|
|
|
|
std::string content((std::istreambuf_iterator<char>(ifs)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
if (!parseFormatContent(content, formatFile)) return false;
|
|
std::cout << std::filesystem::path(formatFile).filename().string() << ": parsed "
|
|
<< formats_.size() << " formats\n";
|
|
return true;
|
|
}
|
|
|
|
// Parse instruction definitions file
|
|
bool parseInstructions(const std::string& instFile) {
|
|
std::ifstream ifs(instFile);
|
|
if (!ifs) {
|
|
std::cerr << "Error: Cannot open instruction file: " << instFile << "\n";
|
|
return false;
|
|
}
|
|
|
|
std::string content((std::istreambuf_iterator<char>(ifs)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
lastInstFile_ = instFile;
|
|
size_t prevSize = instructions_.size();
|
|
if (!parseBatchContent(content, instFile)) return false;
|
|
if (!parseInstructionContent(content, instFile)) return false;
|
|
// Stable-sort newly added instructions by line number so that
|
|
// DEF_BATCH and DEF_T entries appear in document order.
|
|
std::stable_sort(instructions_.begin() + prevSize, instructions_.end(),
|
|
[](const InstructionDef& a, const InstructionDef& b) {
|
|
return a.lineNumber < b.lineNumber;
|
|
});
|
|
std::cout << std::filesystem::path(instFile).filename().string() << ": parsed "
|
|
<< instructions_.size() << " instructions\n";
|
|
return true;
|
|
}
|
|
|
|
// Resolve a format by merging with its parent chain recursively.
|
|
FormatDef getResolvedFormat(const std::string& name) const {
|
|
auto it = formats_.find(name);
|
|
if (it == formats_.end()) return FormatDef{};
|
|
FormatDef fmt = it->second;
|
|
if (fmt.parent.empty()) return fmt;
|
|
// Recursively resolve the parent first so grandparent fields propagate.
|
|
FormatDef p = getResolvedFormat(fmt.parent);
|
|
// Child overrides where non-empty
|
|
if (fmt.microcode.empty()) fmt.microcode = p.microcode;
|
|
if (fmt.unit.empty()) fmt.unit = p.unit;
|
|
if (fmt.maxOperands == 0) fmt.maxOperands = p.maxOperands;
|
|
if (fmt.encoding.empty()) fmt.encoding = p.encoding;
|
|
if (fmt.cycle == 0) fmt.cycle = p.cycle;
|
|
if (fmt.latency == 0) fmt.latency = p.latency;
|
|
if (fmt.coIssueWindow < 0) fmt.coIssueWindow = p.coIssueWindow;
|
|
// Flags: parent first, then child (child adds MFMA etc.)
|
|
fmt.flags = p.flags;
|
|
fmt.flags.insert(fmt.flags.end(), it->second.flags.begin(), it->second.flags.end());
|
|
// Fields: child overrides parent if non-empty
|
|
if (fmt.fields.empty()) fmt.fields = p.fields;
|
|
// Promoted format: child overrides parent if non-empty
|
|
if (fmt.promotedFormat.empty()) fmt.promotedFormat = p.promotedFormat;
|
|
if (fmt.altFields.empty()) fmt.altFields = p.altFields;
|
|
return fmt;
|
|
}
|
|
|
|
// Apply format inheritance to all instructions
|
|
bool applyFormatDefaults() {
|
|
for (auto& inst : instructions_) {
|
|
if (inst.format.empty()) {
|
|
// No format: use instruction's own fields so final* are always set
|
|
inst.finalFlags = inst.flags;
|
|
inst.finalUnit = inst.unit;
|
|
inst.finalOperandFields = inst.operandFields;
|
|
if (inst.cycle == 0) inst.cycle = arch_.defaultCycle;
|
|
if (inst.latency == 0) inst.latency = arch_.defaultLatency;
|
|
if (inst.coIssueWindow < 0) inst.coIssueWindow = 0;
|
|
continue;
|
|
}
|
|
|
|
FormatDef fmt = getResolvedFormat(inst.format);
|
|
if (fmt.name.empty()) {
|
|
if (!lastInstFile_.empty() && inst.lineNumber > 0)
|
|
std::cerr << lastInstFile_ << ":" << inst.lineNumber << ": ";
|
|
std::cerr << "Error: Unknown format '" << inst.format << "' for instruction '"
|
|
<< inst.mnemonic << "'\n";
|
|
return false;
|
|
}
|
|
|
|
// Merge flags: format flags + instance flags
|
|
inst.finalFlags = fmt.flags;
|
|
inst.finalFlags.insert(inst.finalFlags.end(), inst.flags.begin(), inst.flags.end());
|
|
|
|
// Apply unit (instance overrides format)
|
|
inst.finalUnit = inst.unit.empty() ? fmt.unit : inst.unit;
|
|
|
|
// Apply microcode format (from format definition)
|
|
inst.finalMicrocode = fmt.microcode;
|
|
|
|
// Apply encoding (first element of encoding vector)
|
|
if (!fmt.encoding.empty()) inst.finalEncoding = fmt.encoding[0];
|
|
|
|
// Apply cost: explicit instruction cost > format cost > arch default
|
|
if (inst.cycle == 0 && fmt.cycle > 0) inst.cycle = fmt.cycle;
|
|
if (inst.latency == 0 && fmt.latency > 0) inst.latency = fmt.latency;
|
|
if (inst.cycle == 0) inst.cycle = arch_.defaultCycle;
|
|
if (inst.latency == 0) inst.latency = arch_.defaultLatency;
|
|
if (inst.coIssueWindow < 0 && fmt.coIssueWindow >= 0)
|
|
inst.coIssueWindow = fmt.coIssueWindow;
|
|
if (inst.coIssueWindow < 0) inst.coIssueWindow = 0;
|
|
if (inst.coIssueWindow > 0xFFFF) {
|
|
std::cerr << "error: " << inst.mnemonic << ": .coissue value 0x" << std::hex
|
|
<< inst.coIssueWindow << " exceeds uint16_t range\n";
|
|
return false;
|
|
}
|
|
|
|
// Apply operand fields: instruction overrides format
|
|
if (!inst.operandFields.empty()) {
|
|
bool isPartial = !inst.operandFields[0].positionKey.empty();
|
|
if (isPartial && !fmt.fields.empty()) {
|
|
// Merge: start from format defaults, apply named overrides
|
|
inst.finalOperandFields = fmt.fields;
|
|
int destIdx = 0, srcIdx = 0;
|
|
std::unordered_map<std::string, size_t> keyToIdx;
|
|
for (size_t fi = 0; fi < inst.finalOperandFields.size(); ++fi) {
|
|
std::string key = inst.finalOperandFields[fi].isDest
|
|
? "D" + std::to_string(destIdx++)
|
|
: "S" + std::to_string(srcIdx++);
|
|
keyToIdx[key] = fi;
|
|
}
|
|
for (const auto& ov : inst.operandFields) {
|
|
auto it = keyToIdx.find(ov.positionKey);
|
|
if (it != keyToIdx.end()) {
|
|
if (ov.sizeBits != 0)
|
|
inst.finalOperandFields[it->second].sizeBits = ov.sizeBits;
|
|
if (!ov.fieldType.empty())
|
|
inst.finalOperandFields[it->second].fieldType = ov.fieldType;
|
|
}
|
|
}
|
|
} else {
|
|
inst.finalOperandFields = inst.operandFields;
|
|
}
|
|
} else if (!fmt.fields.empty())
|
|
inst.finalOperandFields = fmt.fields;
|
|
|
|
// Apply promoted format and alt fields
|
|
inst.finalPromotedFormat = fmt.promotedFormat;
|
|
|
|
if (!inst.altOperandFields.empty()) {
|
|
bool isAltPartial = !inst.altOperandFields[0].positionKey.empty();
|
|
if (isAltPartial && !fmt.altFields.empty()) {
|
|
inst.finalPromotedFields = fmt.altFields;
|
|
int destIdx = 0, srcIdx = 0;
|
|
std::unordered_map<std::string, size_t> keyToIdx;
|
|
for (size_t fi = 0; fi < inst.finalPromotedFields.size(); ++fi) {
|
|
std::string key = inst.finalPromotedFields[fi].isDest
|
|
? "D" + std::to_string(destIdx++)
|
|
: "S" + std::to_string(srcIdx++);
|
|
keyToIdx[key] = fi;
|
|
}
|
|
for (const auto& ov : inst.altOperandFields) {
|
|
auto it2 = keyToIdx.find(ov.positionKey);
|
|
if (it2 != keyToIdx.end()) {
|
|
if (ov.sizeBits != 0)
|
|
inst.finalPromotedFields[it2->second].sizeBits = ov.sizeBits;
|
|
if (!ov.fieldType.empty())
|
|
inst.finalPromotedFields[it2->second].fieldType = ov.fieldType;
|
|
}
|
|
}
|
|
} else {
|
|
inst.finalPromotedFields = inst.altOperandFields;
|
|
}
|
|
} else if (!fmt.altFields.empty()) {
|
|
// Start from format alt_fields, apply same size overrides
|
|
// from operandFields (sizes are instruction-level, not encoding-specific)
|
|
inst.finalPromotedFields = fmt.altFields;
|
|
if (!inst.operandFields.empty() && !inst.operandFields[0].positionKey.empty()) {
|
|
int destIdx = 0, srcIdx = 0;
|
|
std::unordered_map<std::string, size_t> keyToIdx;
|
|
for (size_t fi = 0; fi < inst.finalPromotedFields.size(); ++fi) {
|
|
std::string key = inst.finalPromotedFields[fi].isDest
|
|
? "D" + std::to_string(destIdx++)
|
|
: "S" + std::to_string(srcIdx++);
|
|
keyToIdx[key] = fi;
|
|
}
|
|
for (const auto& ov : inst.operandFields) {
|
|
auto it2 = keyToIdx.find(ov.positionKey);
|
|
if (it2 != keyToIdx.end()) {
|
|
if (ov.sizeBits != 0)
|
|
inst.finalPromotedFields[it2->second].sizeBits = ov.sizeBits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Get parsed data
|
|
const ArchDef& getArch() const {
|
|
return arch_;
|
|
}
|
|
const std::map<std::string, FormatDef>& getFormats() const {
|
|
return formats_;
|
|
}
|
|
const std::vector<InstructionDef>& getInstructions() const {
|
|
return instructions_;
|
|
}
|
|
|
|
private:
|
|
std::string archName_;
|
|
ArchDef arch_;
|
|
std::map<std::string, FormatDef> formats_;
|
|
std::vector<InstructionDef> instructions_;
|
|
std::string lastInstFile_; // for error reporting in applyFormatDefaults
|
|
|
|
static int getLineNumber(const std::string& content, size_t pos) {
|
|
int line = 1;
|
|
for (size_t i = 0; i < pos && i < content.size(); ++i)
|
|
if (content[i] == '\n') ++line;
|
|
return line;
|
|
}
|
|
|
|
bool parseFormatContent(const std::string& content,
|
|
const std::string& formatFile = std::string()) {
|
|
// Parse DEF_ARCH(...) first (optional; overrides arch name and metadata)
|
|
size_t defArchPos = content.find("DEF_ARCH(");
|
|
if (defArchPos != std::string::npos) {
|
|
size_t nameStart = defArchPos + 9;
|
|
size_t nameEnd = content.find(',', nameStart);
|
|
if (nameEnd != std::string::npos) {
|
|
arch_.name = content.substr(nameStart, nameEnd - nameStart);
|
|
arch_.name.erase(0, arch_.name.find_first_not_of(" \t\n\r"));
|
|
arch_.name.erase(arch_.name.find_last_not_of(" \t\n\r") + 1);
|
|
|
|
size_t blockEnd = findMatchingParen(content, nameEnd);
|
|
if (blockEnd != std::string::npos) {
|
|
std::string block = content.substr(nameEnd, blockEnd - nameEnd);
|
|
parseFieldInt(block, ".major", arch_.major);
|
|
parseFieldInt(block, ".minor", arch_.minor);
|
|
parseFieldInt(block, ".stepping", arch_.stepping);
|
|
parseFieldInt(block, ".wavefront", arch_.wavefront);
|
|
parseFieldInt(block, ".maxVGPR", arch_.maxVGPR);
|
|
parseFieldInt(block, ".maxSGPR", arch_.maxSGPR);
|
|
parseFieldInt(block, ".maxAGPR", arch_.maxAGPR);
|
|
parseFieldInt(block, ".totalVgprPerSimd", arch_.totalVgprPerSimd);
|
|
parseFieldInt(block, ".vgprAllocGranule", arch_.vgprAllocGranule);
|
|
parseFieldInt(block, ".defaultCycle", arch_.defaultCycle);
|
|
parseFieldInt(block, ".defaultLatency", arch_.defaultLatency);
|
|
parseFieldInt(block, ".d16Writes32BitVgpr", arch_.d16Writes32BitVgpr);
|
|
}
|
|
}
|
|
} else {
|
|
arch_.major = 9;
|
|
arch_.minor = 4;
|
|
arch_.stepping = 0;
|
|
arch_.wavefront = 64;
|
|
arch_.maxVGPR = 256;
|
|
arch_.maxSGPR = 102;
|
|
arch_.maxAGPR = 0;
|
|
arch_.defaultCycle = 4;
|
|
arch_.defaultLatency = 4;
|
|
}
|
|
|
|
// Parse DEF_HWREG(NAME, ID) — one HWREG entry per line, terse form.
|
|
// Lookup table for s_setreg/s_getreg is emitted from these per arch.
|
|
{
|
|
size_t hwregPos = 0;
|
|
while ((hwregPos = content.find("DEF_HWREG(", hwregPos)) != std::string::npos) {
|
|
size_t argsStart = hwregPos + 10; // After "DEF_HWREG("
|
|
size_t argsEnd = content.find(')', argsStart);
|
|
if (argsEnd == std::string::npos) {
|
|
int line = getLineNumber(content, argsStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: DEF_HWREG(...): missing closing ')'.\n";
|
|
return false;
|
|
}
|
|
std::string args = content.substr(argsStart, argsEnd - argsStart);
|
|
size_t comma = args.find(',');
|
|
if (comma == std::string::npos) {
|
|
int line = getLineNumber(content, argsStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: DEF_HWREG(NAME, ID): missing ',' between name and id.\n";
|
|
return false;
|
|
}
|
|
HwRegEntry entry;
|
|
entry.name = args.substr(0, comma);
|
|
entry.name.erase(0, entry.name.find_first_not_of(" \t\n\r"));
|
|
entry.name.erase(entry.name.find_last_not_of(" \t\n\r") + 1);
|
|
std::string idStr = args.substr(comma + 1);
|
|
idStr.erase(0, idStr.find_first_not_of(" \t\n\r"));
|
|
idStr.erase(idStr.find_last_not_of(" \t\n\r") + 1);
|
|
char* end = nullptr;
|
|
long parsed = std::strtol(idStr.c_str(), &end, 0);
|
|
if (idStr.empty() || end != idStr.c_str() + idStr.size()) {
|
|
int line = getLineNumber(content, argsStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: DEF_HWREG(" << entry.name << ", " << idStr
|
|
<< "): id is not a valid integer.\n";
|
|
return false;
|
|
}
|
|
entry.id = static_cast<int>(parsed);
|
|
arch_.hwRegs.push_back(std::move(entry));
|
|
hwregPos = argsEnd + 1;
|
|
}
|
|
}
|
|
|
|
// Parse DEF_FORMAT(...) blocks
|
|
size_t pos = 0;
|
|
while ((pos = content.find("DEF_FORMAT(", pos)) != std::string::npos) {
|
|
size_t nameStart = pos + 11; // After "DEF_FORMAT("
|
|
size_t firstComma = content.find(',', nameStart);
|
|
size_t firstDot = content.find('.', nameStart);
|
|
// Comma must appear before any field (.xxx = ...)
|
|
if (firstDot != std::string::npos &&
|
|
(firstComma == std::string::npos || firstDot < firstComma)) {
|
|
size_t end = firstDot;
|
|
while (end > nameStart && (content[end - 1] == ' ' || content[end - 1] == '\t'))
|
|
--end;
|
|
std::string formatName = content.substr(nameStart, end - nameStart);
|
|
formatName.erase(0, formatName.find_first_not_of(" \t\n\r"));
|
|
formatName.erase(formatName.find_last_not_of(" \t\n\r") + 1);
|
|
int line = getLineNumber(content, nameStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: DEF_FORMAT(" << formatName
|
|
<< "): missing ',' after format name.\n";
|
|
return false;
|
|
}
|
|
if (firstComma == std::string::npos) {
|
|
int line = getLineNumber(content, nameStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: DEF_FORMAT(...): missing ',' after format name.\n";
|
|
return false;
|
|
}
|
|
size_t nameEnd = firstComma;
|
|
|
|
std::string formatName = content.substr(nameStart, nameEnd - nameStart);
|
|
formatName.erase(0, formatName.find_first_not_of(" \t\n\r"));
|
|
formatName.erase(formatName.find_last_not_of(" \t\n\r") + 1);
|
|
|
|
FormatDef fmt;
|
|
fmt.name = formatName;
|
|
|
|
// Find the closing paren for this DEF_FORMAT
|
|
size_t blockEnd = findMatchingParen(content, nameEnd);
|
|
if (blockEnd == std::string::npos) {
|
|
int line = getLineNumber(content, nameStart);
|
|
if (!formatFile.empty()) std::cerr << formatFile << ":" << line << ": ";
|
|
std::cerr << "Error: No matching paren for DEF_FORMAT " << formatName << "\n";
|
|
return false;
|
|
}
|
|
|
|
std::string block = content.substr(nameEnd, blockEnd - nameEnd);
|
|
|
|
// Parse fields (simple substring matching for proof-of-concept)
|
|
parseField(block, ".parent", fmt.parent);
|
|
parseField(block, ".microcode", fmt.microcode);
|
|
parseField(block, ".unit", fmt.unit);
|
|
parseFieldInt(block, ".maxOperands", fmt.maxOperands);
|
|
parseFieldCost(block, ".cost", fmt.cycle, fmt.latency);
|
|
parseFieldIntAuto(block, ".coissue", fmt.coIssueWindow);
|
|
parseFieldEncoding(block, ".encoding", fmt.encoding);
|
|
parseFieldFlags(block, ".flags", fmt.flags);
|
|
parseFieldOperandFields(block, ".fields", fmt.fields);
|
|
parseField(block, ".promotedFormat", fmt.promotedFormat);
|
|
parseFieldOperandFields(block, ".alt_fields", fmt.altFields);
|
|
|
|
formats_[formatName] = fmt;
|
|
pos = blockEnd;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse DEF_BATCH(...) blocks.
|
|
//
|
|
// Syntax — shared header fields + entries using DEF_T argument syntax:
|
|
// DEF_BATCH(.format = FMT, [.flags = {F1, F2},]
|
|
// [.microcode = U, .unit = X, .encoding = {N},]
|
|
// [.maxOperands = N,]
|
|
// ClassName, "mnemonic",
|
|
// ClassName, "mnemonic", .logical = "LogicalName",
|
|
// ClassName, "mnemonic", .logical = {"L1", "L2"}, .flags = {Extra},
|
|
// )
|
|
//
|
|
// Each entry uses the same field syntax as DEF_T arguments (without the
|
|
// DEF_T() wrapper). Shared header fields are inherited by all entries;
|
|
// per-entry fields override or extend them (flags are additive).
|
|
//
|
|
// The header supports all FormatDef fields. If format-level fields beyond
|
|
// .format/.flags are given, an anonymous format is created automatically.
|
|
bool parseBatchContent(const std::string& content,
|
|
const std::string& instFile = std::string()) {
|
|
size_t pos = 0;
|
|
int batchIdx = 0;
|
|
while ((pos = content.find("DEF_BATCH(", pos)) != std::string::npos) {
|
|
int defLine = getLineNumber(content, pos);
|
|
size_t argsStart = pos + 10; // After "DEF_BATCH("
|
|
|
|
size_t blockEnd = findMatchingParen(content, argsStart);
|
|
if (blockEnd == std::string::npos) {
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << defLine << ": ";
|
|
std::cerr << "Error: No matching paren for DEF_BATCH\n";
|
|
return false;
|
|
}
|
|
|
|
std::string body = content.substr(argsStart, blockEnd - argsStart);
|
|
|
|
// --- Find header portion (before the first entry) ---
|
|
// Entries start with Identifier, "mnemonic". Header fields start
|
|
// with '.'. We extract the header text so shared field parsing
|
|
// doesn't accidentally pick up per-entry fields.
|
|
std::vector<size_t> tmpStarts;
|
|
findBatchEntries(body, tmpStarts);
|
|
std::string header = tmpStarts.empty() ? body : body.substr(0, tmpStarts[0]);
|
|
|
|
// --- Parse shared header fields ---
|
|
std::string hdrFormat;
|
|
std::vector<std::string> hdrFlags;
|
|
std::string hdrMicrocode;
|
|
std::string hdrUnit;
|
|
std::string hdrEncodingStr;
|
|
int hdrMaxOperands = 0;
|
|
|
|
parseField(header, ".format", hdrFormat);
|
|
parseFieldFlags(header, ".flags", hdrFlags);
|
|
parseField(header, ".microcode", hdrMicrocode);
|
|
parseField(header, ".unit", hdrUnit);
|
|
parseField(header, ".encoding", hdrEncodingStr);
|
|
parseFieldInt(header, ".maxOperands", hdrMaxOperands);
|
|
|
|
bool hasFormatFields = !hdrMicrocode.empty() || !hdrUnit.empty() ||
|
|
!hdrEncodingStr.empty() || hdrMaxOperands != 0;
|
|
|
|
std::string effectiveFormat = hdrFormat;
|
|
|
|
if (hasFormatFields) {
|
|
FormatDef anonFmt;
|
|
anonFmt.name =
|
|
"__batch_" + std::to_string(defLine) + "_" + std::to_string(batchIdx++);
|
|
anonFmt.parent = hdrFormat;
|
|
if (!hdrMicrocode.empty()) anonFmt.microcode = hdrMicrocode;
|
|
if (!hdrUnit.empty()) anonFmt.unit = hdrUnit;
|
|
if (!hdrEncodingStr.empty()) {
|
|
std::string enc = hdrEncodingStr;
|
|
enc.erase(std::remove(enc.begin(), enc.end(), '{'), enc.end());
|
|
enc.erase(std::remove(enc.begin(), enc.end(), '}'), enc.end());
|
|
enc.erase(0, enc.find_first_not_of(" \t"));
|
|
enc.erase(enc.find_last_not_of(" \t") + 1);
|
|
if (!enc.empty()) anonFmt.encoding.push_back(std::stoi(enc));
|
|
}
|
|
anonFmt.maxOperands = hdrMaxOperands;
|
|
anonFmt.flags = hdrFlags;
|
|
|
|
formats_[anonFmt.name] = anonFmt;
|
|
effectiveFormat = anonFmt.name;
|
|
hdrFlags.clear();
|
|
}
|
|
|
|
if (effectiveFormat.empty()) {
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << defLine << ": ";
|
|
std::cerr << "Error: DEF_BATCH needs .format or format-level fields\n";
|
|
return false;
|
|
}
|
|
|
|
// Reuse entry positions found during header extraction.
|
|
std::vector<size_t>& entryStarts = tmpStarts;
|
|
|
|
if (entryStarts.empty()) {
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << defLine << ": ";
|
|
std::cerr << "Error: DEF_BATCH contains no entries\n";
|
|
return false;
|
|
}
|
|
|
|
// --- Parse each entry ---
|
|
for (size_t ei = 0; ei < entryStarts.size(); ++ei) {
|
|
size_t entryPos = entryStarts[ei];
|
|
size_t entryEnd = (ei + 1 < entryStarts.size()) ? entryStarts[ei + 1] : body.size();
|
|
|
|
// Extract ClassName
|
|
size_t idEnd = body.find_first_not_of(
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", entryPos);
|
|
std::string instClass = body.substr(entryPos, idEnd - entryPos);
|
|
|
|
// Expect comma after ClassName
|
|
size_t commaPos = body.find_first_not_of(" \t", idEnd);
|
|
if (commaPos == std::string::npos || body[commaPos] != ',') {
|
|
int entryLine = defLine + getLineNumber(body.substr(0, entryPos), 0) - 1;
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << entryLine << ": ";
|
|
std::cerr << "Error: Expected ',' after class name '" << instClass
|
|
<< "' in DEF_BATCH\n";
|
|
return false;
|
|
}
|
|
|
|
// Expect "mnemonic" after comma
|
|
size_t qStart = body.find('"', commaPos + 1);
|
|
if (qStart == std::string::npos || qStart >= entryEnd) {
|
|
int entryLine = defLine + getLineNumber(body.substr(0, entryPos), 0) - 1;
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << entryLine << ": ";
|
|
std::cerr << "Error: Expected '\"mnemonic\"' after '" << instClass
|
|
<< ",' in DEF_BATCH\n";
|
|
return false;
|
|
}
|
|
size_t qEnd = body.find('"', qStart + 1);
|
|
if (qEnd == std::string::npos) {
|
|
int entryLine = defLine + getLineNumber(body.substr(0, entryPos), 0) - 1;
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << entryLine << ": ";
|
|
std::cerr << "Error: Unterminated mnemonic string for '" << instClass
|
|
<< "' in DEF_BATCH\n";
|
|
return false;
|
|
}
|
|
std::string mnem = body.substr(qStart + 1, qEnd - qStart - 1);
|
|
|
|
// Everything between the mnemonic close-quote and the next
|
|
// entry (or end of batch) is this entry's optional fields.
|
|
std::string entryFields = body.substr(qEnd + 1, entryEnd - qEnd - 1);
|
|
|
|
InstructionDef inst;
|
|
inst.instClass = instClass;
|
|
inst.mnemonic = mnem;
|
|
inst.lineNumber = defLine + getLineNumber(body.substr(0, entryPos), 0) - 1;
|
|
inst.format = effectiveFormat;
|
|
|
|
// Start with shared flags, then append per-entry flags
|
|
inst.flags = hdrFlags;
|
|
std::vector<std::string> entryFlags;
|
|
parseFieldFlags(entryFields, ".flags", entryFlags);
|
|
inst.flags.insert(inst.flags.end(), entryFlags.begin(), entryFlags.end());
|
|
|
|
// Parse per-entry optional fields (same helpers as DEF_T)
|
|
parseFieldCost(entryFields, ".cost", inst.cycle, inst.latency);
|
|
parseFieldIntAuto(entryFields, ".coissue", inst.coIssueWindow);
|
|
parseFieldCostOverrides(entryFields, inst.costOverrides);
|
|
parseFieldOperandFields(entryFields, ".operand_fields", inst.operandFields);
|
|
parseFieldOperandFields(entryFields, ".alt_operand_fields", inst.altOperandFields);
|
|
parseFieldLogical(entryFields, inst.logical);
|
|
|
|
// Validate: per-entry must NOT redefine .format (use header)
|
|
std::string entryFmt;
|
|
parseField(entryFields, ".format", entryFmt);
|
|
if (!entryFmt.empty()) {
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << inst.lineNumber << ": ";
|
|
std::cerr << "Error: Entry '" << instClass
|
|
<< "' in DEF_BATCH must not specify .format "
|
|
<< "(use the batch header instead)\n";
|
|
return false;
|
|
}
|
|
|
|
instructions_.push_back(inst);
|
|
}
|
|
|
|
pos = blockEnd + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Find the start positions of all entries inside a DEF_BATCH body.
|
|
// An entry is: Identifier , "mnemonic" [, .field = ...]
|
|
// Header fields start with '.' and are skipped.
|
|
void findBatchEntries(const std::string& body, std::vector<size_t>& starts) const {
|
|
for (size_t i = 0; i < body.size(); ++i) {
|
|
char c = body[i];
|
|
|
|
// Skip whitespace and commas
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',') continue;
|
|
|
|
// Skip // comments
|
|
if (c == '/' && i + 1 < body.size() && body[i + 1] == '/') {
|
|
size_t eol = body.find('\n', i);
|
|
i = (eol == std::string::npos) ? body.size() : eol;
|
|
continue;
|
|
}
|
|
|
|
// Skip .field = value (header or per-entry fields)
|
|
if (c == '.') {
|
|
i = skipDotField(body, i);
|
|
continue;
|
|
}
|
|
|
|
// Potential entry: Identifier , "mnemonic"
|
|
if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') {
|
|
size_t idEnd = i + 1;
|
|
while (
|
|
idEnd < body.size() &&
|
|
(std::isalnum(static_cast<unsigned char>(body[idEnd])) || body[idEnd] == '_'))
|
|
idEnd++;
|
|
|
|
size_t afterId = body.find_first_not_of(" \t", idEnd);
|
|
if (afterId != std::string::npos && body[afterId] == ',') {
|
|
size_t afterComma = body.find_first_not_of(" \t", afterId + 1);
|
|
if (afterComma != std::string::npos && body[afterComma] == '"') {
|
|
starts.push_back(i);
|
|
// Advance past the mnemonic to find per-entry fields
|
|
size_t qEnd = body.find('"', afterComma + 1);
|
|
i = (qEnd != std::string::npos) ? qEnd : idEnd;
|
|
continue;
|
|
}
|
|
}
|
|
i = idEnd - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip a .field = value expression, returning position of its last char.
|
|
size_t skipDotField(const std::string& body, size_t dotPos) const {
|
|
size_t i = dotPos + 1;
|
|
// Skip field name
|
|
while (i < body.size() &&
|
|
(std::isalnum(static_cast<unsigned char>(body[i])) || body[i] == '_'))
|
|
i++;
|
|
// Skip whitespace and '='
|
|
i = body.find_first_not_of(" \t", i);
|
|
if (i == std::string::npos) return body.size() - 1;
|
|
if (body[i] == '=') {
|
|
i = body.find_first_not_of(" \t", i + 1);
|
|
if (i == std::string::npos) return body.size() - 1;
|
|
}
|
|
// If value starts with '{', skip to matching '}'
|
|
if (body[i] == '{') {
|
|
size_t close = findMatchingBrace(body, i);
|
|
return (close != std::string::npos) ? close : body.size() - 1;
|
|
}
|
|
// Otherwise skip to next comma or newline
|
|
size_t end = body.find_first_of(",\n", i);
|
|
return (end != std::string::npos) ? end - 1 : body.size() - 1;
|
|
}
|
|
|
|
bool parseInstructionContent(const std::string& content,
|
|
const std::string& instFile = std::string()) {
|
|
// Minimal parser for proof-of-concept
|
|
// Parse DEF_T(...) blocks
|
|
size_t pos = 0;
|
|
while ((pos = content.find("DEF_T(", pos)) != std::string::npos) {
|
|
int defLine = getLineNumber(content, pos);
|
|
size_t argsStart = pos + 6; // After "DEF_T("
|
|
size_t firstComma = content.find(',', argsStart);
|
|
if (firstComma == std::string::npos) break;
|
|
|
|
// Parse: DEF_T(InstClass, "mnemonic", ...)
|
|
std::string instClass = content.substr(argsStart, firstComma - argsStart);
|
|
instClass.erase(0, instClass.find_first_not_of(" \t\n\r"));
|
|
instClass.erase(instClass.find_last_not_of(" \t\n\r") + 1);
|
|
|
|
size_t mnemonicStart = content.find('"', firstComma);
|
|
size_t mnemonicEnd = content.find('"', mnemonicStart + 1);
|
|
if (mnemonicStart == std::string::npos || mnemonicEnd == std::string::npos) break;
|
|
|
|
std::string mnemonic =
|
|
content.substr(mnemonicStart + 1, mnemonicEnd - mnemonicStart - 1);
|
|
|
|
InstructionDef inst;
|
|
inst.instClass = instClass;
|
|
inst.mnemonic = mnemonic;
|
|
inst.lineNumber = defLine;
|
|
|
|
// Find the closing paren
|
|
size_t blockEnd = findMatchingParen(content, mnemonicEnd);
|
|
if (blockEnd == std::string::npos) {
|
|
if (!instFile.empty()) std::cerr << instFile << ":" << defLine << ": ";
|
|
std::cerr << "Error: No matching paren for DEF_T " << mnemonic << "\n";
|
|
return false;
|
|
}
|
|
|
|
std::string block = content.substr(mnemonicEnd, blockEnd - mnemonicEnd);
|
|
|
|
// Parse optional fields
|
|
parseField(block, ".format", inst.format);
|
|
parseFieldCost(block, ".cost", inst.cycle, inst.latency);
|
|
parseFieldIntAuto(block, ".coissue", inst.coIssueWindow);
|
|
parseFieldCostOverrides(block, inst.costOverrides);
|
|
parseFieldFlags(block, ".flags", inst.flags);
|
|
parseFieldOperandFields(block, ".operand_fields", inst.operandFields);
|
|
parseFieldOperandFields(block, ".alt_operand_fields", inst.altOperandFields);
|
|
parseFieldLogical(block, inst.logical);
|
|
|
|
instructions_.push_back(inst);
|
|
pos = blockEnd;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Helper: Find matching closing paren
|
|
size_t findMatchingParen(const std::string& content, size_t start) {
|
|
int depth = 1;
|
|
for (size_t i = start; i < content.size(); ++i) {
|
|
if (content[i] == '(')
|
|
depth++;
|
|
else if (content[i] == ')') {
|
|
depth--;
|
|
if (depth == 0) return i;
|
|
}
|
|
}
|
|
return std::string::npos;
|
|
}
|
|
|
|
// Helper: Parse simple field
|
|
void parseField(const std::string& block, const std::string& fieldName, std::string& out) {
|
|
size_t pos = block.find(fieldName);
|
|
if (pos == std::string::npos) return;
|
|
|
|
size_t eqPos = block.find('=', pos);
|
|
if (eqPos == std::string::npos) return;
|
|
|
|
size_t valStart = eqPos + 1;
|
|
size_t valEnd = block.find_first_of(",\n)", valStart);
|
|
if (valEnd == std::string::npos) valEnd = block.size();
|
|
|
|
out = block.substr(valStart, valEnd - valStart);
|
|
out.erase(0, out.find_first_not_of(" \t\n\r"));
|
|
out.erase(out.find_last_not_of(" \t\n\r,") + 1);
|
|
// Strip surrounding quotes for string values (e.g. .logical = "VCmpEQF32")
|
|
if (out.size() >= 2 && out.front() == '"' && out.back() == '"')
|
|
out = out.substr(1, out.size() - 2);
|
|
}
|
|
|
|
// Helper: Parse integer field
|
|
void parseFieldInt(const std::string& block, const std::string& fieldName, int& out) {
|
|
std::string val;
|
|
parseField(block, fieldName, val);
|
|
if (!val.empty()) out = std::stoi(val);
|
|
}
|
|
|
|
// Helper: Parse integer field with auto base detection (handles 0x hex prefix)
|
|
void parseFieldIntAuto(const std::string& block, const std::string& fieldName, int& out) {
|
|
std::string val;
|
|
parseField(block, fieldName, val);
|
|
if (!val.empty()) out = std::stoi(val, nullptr, 0);
|
|
}
|
|
|
|
// Parse .encoding = {32} or .encoding = {64} (instruction size in bits)
|
|
void parseFieldEncoding(const std::string& block, const std::string& fieldName,
|
|
std::vector<int>& out) {
|
|
size_t pos = block.find(fieldName);
|
|
if (pos == std::string::npos) return;
|
|
size_t lbrace = block.find('{', pos);
|
|
size_t rbrace = block.find('}', lbrace);
|
|
if (lbrace == std::string::npos || rbrace == std::string::npos) return;
|
|
std::string inner = block.substr(lbrace + 1, rbrace - lbrace - 1);
|
|
out.clear();
|
|
size_t start = 0;
|
|
while (start < inner.size()) {
|
|
size_t end = inner.find(',', start);
|
|
if (end == std::string::npos) end = inner.size();
|
|
std::string num = inner.substr(start, end - start);
|
|
num.erase(0, num.find_first_not_of(" \t\n\r"));
|
|
num.erase(num.find_last_not_of(" \t\n\r") + 1);
|
|
if (!num.empty()) out.push_back(std::stoi(num));
|
|
start = end + 1;
|
|
}
|
|
}
|
|
|
|
// Helper: Parse cost field (.cost = {cycle, latency})
|
|
void parseFieldCost(const std::string& block, const std::string& fieldName, int& cycle,
|
|
int& latency) {
|
|
size_t pos = block.find(fieldName);
|
|
if (pos == std::string::npos) return;
|
|
|
|
size_t lbrace = block.find('{', pos);
|
|
size_t rbrace = block.find('}', lbrace);
|
|
if (lbrace == std::string::npos || rbrace == std::string::npos) return;
|
|
|
|
std::string costStr = block.substr(lbrace + 1, rbrace - lbrace - 1);
|
|
size_t comma = costStr.find(',');
|
|
if (comma != std::string::npos) {
|
|
cycle = std::stoi(costStr.substr(0, comma));
|
|
latency = std::stoi(costStr.substr(comma + 1));
|
|
}
|
|
}
|
|
|
|
// Helper: Find matching '}' for '{' at start (skips nested braces)
|
|
size_t findMatchingBrace(const std::string& s, size_t start) const {
|
|
int depth = 1;
|
|
for (size_t i = start + 1; i < s.size(); ++i) {
|
|
if (s[i] == '{')
|
|
depth++;
|
|
else if (s[i] == '}') {
|
|
depth--;
|
|
if (depth == 0) return i;
|
|
}
|
|
}
|
|
return std::string::npos;
|
|
}
|
|
|
|
// Helper: Parse .costOverrides = { { MatrixFmtModifiers(FP4, FP4), 6, 24 }, ... }
|
|
void parseFieldCostOverrides(const std::string& block, std::vector<CostOverrideEntry>& out) {
|
|
size_t pos = block.find(".costOverrides");
|
|
if (pos == std::string::npos) return;
|
|
size_t eqPos = block.find('=', pos);
|
|
if (eqPos == std::string::npos) return;
|
|
size_t lbrace = block.find('{', eqPos);
|
|
if (lbrace == std::string::npos) return;
|
|
size_t rbrace = findMatchingBrace(block, lbrace);
|
|
if (rbrace == std::string::npos) return;
|
|
std::string content = block.substr(lbrace + 1, rbrace - lbrace - 1);
|
|
size_t entryStart = 0;
|
|
while (entryStart < content.size()) {
|
|
size_t entryL = content.find('{', entryStart);
|
|
if (entryL == std::string::npos) break;
|
|
size_t entryR = findMatchingBrace(content, entryL);
|
|
if (entryR == std::string::npos) break;
|
|
std::string entry = content.substr(entryL + 1, entryR - entryL - 1);
|
|
// entry is "MatrixFmtModifiers(FP4, FP4), 6, 24"
|
|
size_t sep = entry.find("),");
|
|
if (sep == std::string::npos) {
|
|
entryStart = entryR + 1;
|
|
continue;
|
|
}
|
|
std::string modPart = entry.substr(0, sep + 1); // include ')'
|
|
std::string costPart = entry.substr(sep + 2);
|
|
size_t lparen = modPart.find('(');
|
|
if (lparen == std::string::npos) {
|
|
entryStart = entryR + 1;
|
|
continue;
|
|
}
|
|
size_t rparen = findMatchingParen(modPart, lparen + 1);
|
|
if (rparen == std::string::npos) {
|
|
entryStart = entryR + 1;
|
|
continue;
|
|
}
|
|
CostOverrideEntry e;
|
|
e.modifierType = modPart.substr(0, lparen);
|
|
e.modifierType.erase(0, e.modifierType.find_first_not_of(" \t\n\r"));
|
|
e.modifierType.erase(e.modifierType.find_last_not_of(" \t\n\r") + 1);
|
|
std::string argsStr = modPart.substr(lparen + 1, rparen - lparen - 1);
|
|
size_t as = 0;
|
|
while (as < argsStr.size()) {
|
|
size_t ac = argsStr.find(',', as);
|
|
if (ac == std::string::npos) ac = argsStr.size();
|
|
std::string arg = argsStr.substr(as, ac - as);
|
|
arg.erase(0, arg.find_first_not_of(" \t\n\r"));
|
|
arg.erase(arg.find_last_not_of(" \t\n\r") + 1);
|
|
if (!arg.empty()) e.args.push_back(arg);
|
|
as = ac + 1;
|
|
}
|
|
costPart.erase(0, costPart.find_first_not_of(" \t\n\r"));
|
|
size_t cc = costPart.find(',');
|
|
if (cc != std::string::npos) {
|
|
e.cycle = std::stoi(costPart.substr(0, cc));
|
|
e.latency = std::stoi(costPart.substr(cc + 1));
|
|
}
|
|
out.push_back(e);
|
|
entryStart = entryR + 1;
|
|
}
|
|
}
|
|
|
|
// Helper: Parse flags field (.flags = {flag1, flag2})
|
|
void parseFieldFlags(const std::string& block, const std::string& fieldName,
|
|
std::vector<std::string>& flags) {
|
|
size_t pos = block.find(fieldName);
|
|
if (pos == std::string::npos) return;
|
|
|
|
size_t lbrace = block.find('{', pos);
|
|
size_t rbrace = block.find('}', lbrace);
|
|
if (lbrace == std::string::npos || rbrace == std::string::npos) return;
|
|
|
|
std::string flagsStr = block.substr(lbrace + 1, rbrace - lbrace - 1);
|
|
|
|
// Split by comma
|
|
size_t start = 0;
|
|
while (start < flagsStr.size()) {
|
|
size_t end = flagsStr.find(',', start);
|
|
if (end == std::string::npos) end = flagsStr.size();
|
|
|
|
std::string flag = flagsStr.substr(start, end - start);
|
|
flag.erase(0, flag.find_first_not_of(" \t\n\r"));
|
|
flag.erase(flag.find_last_not_of(" \t\n\r") + 1);
|
|
|
|
if (!flag.empty()) flags.push_back(flag);
|
|
|
|
start = end + 1;
|
|
}
|
|
}
|
|
|
|
// Helper: Parse .logical = "X" or .logical = {"X", "Y"}
|
|
void parseFieldLogical(const std::string& block, std::vector<std::string>& out) {
|
|
size_t pos = block.find(".logical");
|
|
if (pos == std::string::npos) return;
|
|
|
|
size_t eqPos = block.find('=', pos);
|
|
if (eqPos == std::string::npos) return;
|
|
|
|
size_t valStart = eqPos + 1;
|
|
valStart = block.find_first_not_of(" \t\n\r", valStart);
|
|
if (valStart == std::string::npos) return;
|
|
|
|
if (block[valStart] == '{') {
|
|
// .logical = {"X", "Y"}
|
|
size_t rbrace = block.find('}', valStart);
|
|
if (rbrace == std::string::npos) return;
|
|
std::string inner = block.substr(valStart + 1, rbrace - valStart - 1);
|
|
size_t start = 0;
|
|
while (start < inner.size()) {
|
|
size_t end = inner.find(',', start);
|
|
if (end == std::string::npos) end = inner.size();
|
|
std::string s = inner.substr(start, end - start);
|
|
s.erase(0, s.find_first_not_of(" \t\n\r\""));
|
|
s.erase(s.find_last_not_of(" \t\n\r\"") + 1);
|
|
if (!s.empty()) out.push_back(s);
|
|
start = end + 1;
|
|
}
|
|
} else if (block[valStart] == '"') {
|
|
// .logical = "X"
|
|
size_t endQuote = block.find('"', valStart + 1);
|
|
if (endQuote != std::string::npos)
|
|
out.push_back(block.substr(valStart + 1, endQuote - valStart - 1));
|
|
}
|
|
}
|
|
|
|
// Parse .fields or .operand_fields:
|
|
//
|
|
// Full entry (format-level .fields or instruction-level .operand_fields):
|
|
// {Pos, EncodeField, Name, Type, Size [, Options...]}
|
|
// e.g. {D0, vdata, vgpr_d, vgpr, 32, RW}
|
|
// e.g. {S0, simm16, label, label, 16}
|
|
//
|
|
// Partial override (.operand_fields only — merges with format .fields):
|
|
// {S0, .type=wait_alu} — override type
|
|
// {D0, .size=64} — override size
|
|
// {D0, .size=64, .type=vgpr} — override both
|
|
size_t findMatchingBrace(const std::string& s, size_t openPos) {
|
|
int depth = 1;
|
|
for (size_t i = openPos + 1; i < s.size(); ++i) {
|
|
if (s[i] == '{')
|
|
depth++;
|
|
else if (s[i] == '}') {
|
|
depth--;
|
|
if (depth == 0) return i;
|
|
}
|
|
}
|
|
return std::string::npos;
|
|
}
|
|
|
|
void parseFieldOperandFields(const std::string& block, const std::string& fieldKey,
|
|
std::vector<OperandFieldEntry>& out) {
|
|
// Search for fieldKey, ensuring it's not a substring of a longer key
|
|
// (e.g., ".operand_fields" must not match ".alt_operand_fields")
|
|
size_t pos = 0;
|
|
while (pos < block.size()) {
|
|
pos = block.find(fieldKey, pos);
|
|
if (pos == std::string::npos) return;
|
|
if (pos > 0 && (std::isalnum(block[pos - 1]) || block[pos - 1] == '_')) {
|
|
pos += fieldKey.size();
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
size_t eqPos = block.find('=', pos);
|
|
if (eqPos == std::string::npos) return;
|
|
size_t outerStart = block.find('{', eqPos);
|
|
if (outerStart == std::string::npos) return;
|
|
size_t outerEnd = findMatchingBrace(block, outerStart);
|
|
if (outerEnd == std::string::npos) return;
|
|
|
|
size_t i = outerStart + 1;
|
|
while (i < outerEnd) {
|
|
size_t innerStart = block.find('{', i);
|
|
if (innerStart == std::string::npos || innerStart >= outerEnd) break;
|
|
size_t innerEnd = block.find('}', innerStart);
|
|
if (innerEnd == std::string::npos || innerEnd >= outerEnd) break;
|
|
|
|
std::string entry = block.substr(innerStart + 1, innerEnd - innerStart - 1);
|
|
|
|
// Tokenize by comma
|
|
std::vector<std::string> tokens;
|
|
size_t ts = 0;
|
|
while (ts < entry.size()) {
|
|
size_t te = entry.find(',', ts);
|
|
if (te == std::string::npos) te = entry.size();
|
|
std::string tok = entry.substr(ts, te - ts);
|
|
tok.erase(0, tok.find_first_not_of(" \t\n\r"));
|
|
tok.erase(tok.find_last_not_of(" \t\n\r") + 1);
|
|
if (!tok.empty()) tokens.push_back(tok);
|
|
ts = te + 1;
|
|
}
|
|
|
|
if (tokens.size() >= 4 && tokens[1].find('.') == std::string::npos) {
|
|
// Full entry: {D0, vdata, vgpr, 32 [, RW|M64]}
|
|
// M64 can appear as the size field (implies 64) or as an option.
|
|
OperandFieldEntry e;
|
|
e.isDest = (!tokens[0].empty() && tokens[0][0] == 'D');
|
|
e.encodeField = tokens[1];
|
|
e.fieldType = tokens[2];
|
|
if (tokens[3] == "M64") {
|
|
e.sizeBits = 64;
|
|
e.isM64 = true;
|
|
} else {
|
|
e.sizeBits = std::stoi(tokens[3]);
|
|
}
|
|
for (size_t ti = 4; ti < tokens.size(); ++ti) {
|
|
if (tokens[ti] == "RW")
|
|
e.isReadWrite = true;
|
|
else if (tokens[ti] == "M64")
|
|
e.isM64 = true;
|
|
}
|
|
out.push_back(e);
|
|
} else if (tokens.size() >= 2 && tokens[1].find('.') != std::string::npos) {
|
|
// Named-field partial override: {S0, .size=64, .type=wait_alu}
|
|
OperandFieldEntry e;
|
|
e.positionKey = tokens[0];
|
|
for (size_t ti = 1; ti < tokens.size(); ++ti) {
|
|
size_t eq = tokens[ti].find('=');
|
|
if (eq == std::string::npos) continue;
|
|
std::string key = tokens[ti].substr(0, eq);
|
|
std::string val = tokens[ti].substr(eq + 1);
|
|
if (key == ".size")
|
|
e.sizeBits = std::stoi(val);
|
|
else if (key == ".type")
|
|
e.fieldType = val;
|
|
}
|
|
out.push_back(e);
|
|
}
|
|
|
|
i = innerEnd + 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
//==========================================================================
|
|
// CODE GENERATORS
|
|
//==========================================================================
|
|
|
|
class InstructionCodeGen {
|
|
public:
|
|
InstructionCodeGen(const std::string& arch, const std::vector<InstructionDef>& instructions,
|
|
const ArchDef& archDef)
|
|
: arch_(arch), instructions_(instructions), archDef_(archDef) {}
|
|
|
|
// Generate cost table file
|
|
bool generateCostTable(const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
|
|
emitHeader(out, "Instruction Cost Table");
|
|
out << "// Cost table: {mnemonic, cycle, latency}\n";
|
|
out << "// Only non-default costs are listed (default: cycle=" << archDef_.defaultCycle
|
|
<< ", latency=" << archDef_.defaultLatency << ")\n\n";
|
|
|
|
out << "constexpr InstructionCost " << arch_ << "_GENERATED_COSTS[] = {\n";
|
|
|
|
for (const auto& inst : instructions_) {
|
|
if (inst.cycle != archDef_.defaultCycle || inst.latency != archDef_.defaultLatency) {
|
|
out << " {\"" << inst.mnemonic << "\", " << inst.cycle << ", " << inst.latency
|
|
<< "},\n";
|
|
}
|
|
}
|
|
|
|
out << "};\n\n";
|
|
|
|
// Emit cost overrides keyed by modifier (e.g. MatrixFmtModifiers(a, b)); runtime uses for
|
|
// modifier-dependent cost
|
|
std::vector<const InstructionDef*> withOverrides;
|
|
for (const auto& inst : instructions_) {
|
|
if (!inst.costOverrides.empty()) withOverrides.push_back(&inst);
|
|
}
|
|
if (!withOverrides.empty()) {
|
|
out << "// Cost overrides: when modifier matches (e.g. MatrixFmtModifiers), use "
|
|
"(cycle, latency).\n"
|
|
"// fmtA/fmtB values match stinkytofu::MatrixFmt enum (LLVM "
|
|
"WMMA::MatrixFMT).\n";
|
|
out << "struct InstructionCostOverrideMatrixFmt { const char* mnemonic; uint8_t "
|
|
"fmtA; uint8_t fmtB; uint16_t cycle; uint16_t latency; };\n";
|
|
out << "constexpr InstructionCostOverrideMatrixFmt " << arch_
|
|
<< "_COST_OVERRIDES_MATRIX_FMT[] = {\n";
|
|
for (const auto* inst : withOverrides) {
|
|
for (const auto& ov : inst->costOverrides) {
|
|
if (ov.modifierType != "MatrixFmtModifiers" || ov.args.size() < 2) continue;
|
|
// Map .def format names to MatrixFmt enum values (LLVM WMMA::MatrixFMT)
|
|
auto toFmt = [](const std::string& s) -> int {
|
|
if (s == "FP8") return 0;
|
|
if (s == "BF8") return 1;
|
|
if (s == "FP6") return 2;
|
|
if (s == "BF6") return 3;
|
|
if (s == "FP4") return 4;
|
|
return 0xFF; // NONE
|
|
};
|
|
out << " {\"" << inst->mnemonic << "\", " << toFmt(ov.args[0]) << ", "
|
|
<< toFmt(ov.args[1]) << ", " << ov.cycle << ", " << ov.latency << "},\n";
|
|
}
|
|
}
|
|
out << "};\n\n";
|
|
}
|
|
|
|
out << "// Total generated instructions: " << instructions_.size() << "\n";
|
|
return true;
|
|
}
|
|
|
|
static std::string encodeFieldToCpp(const std::string& f) {
|
|
if (f == "vdata") return "EncodeField::vdata";
|
|
if (f == "vaddr") return "EncodeField::vaddr";
|
|
if (f == "rsrc") return "EncodeField::rsrc";
|
|
if (f == "soffset") return "EncodeField::soffset";
|
|
if (f == "vdst") return "EncodeField::vdst";
|
|
if (f == "vsrc") return "EncodeField::vsrc";
|
|
if (f == "saddr") return "EncodeField::saddr";
|
|
if (f == "addr") return "EncodeField::addr";
|
|
if (f == "data0") return "EncodeField::data0";
|
|
if (f == "data1") return "EncodeField::data1";
|
|
if (f == "src0") return "EncodeField::src0";
|
|
if (f == "src1") return "EncodeField::src1";
|
|
if (f == "src2") return "EncodeField::src2";
|
|
if (f == "scale_src0") return "EncodeField::scale_src0";
|
|
if (f == "scale_src1") return "EncodeField::scale_src1";
|
|
if (f == "sdst") return "EncodeField::sdst";
|
|
if (f == "ssrc1") return "EncodeField::ssrc1";
|
|
if (f == "literal") return "EncodeField::literal";
|
|
if (f == "simm16") return "EncodeField::simm16";
|
|
if (f == "simm32") return "EncodeField::simm32";
|
|
if (f == "ssrc0") return "EncodeField::ssrc0";
|
|
if (f == "sdata") return "EncodeField::sdata";
|
|
if (f == "ioffset") return "EncodeField::ioffset";
|
|
if (f == "sbase") return "EncodeField::sbase";
|
|
if (f == "vsrc1") return "EncodeField::vsrc1";
|
|
if (f == "vaddr0") return "EncodeField::vaddr0";
|
|
if (f == "vaddr1") return "EncodeField::vaddr1";
|
|
if (f == "vaddr2") return "EncodeField::vaddr2";
|
|
if (f == "vaddr3") return "EncodeField::vaddr3";
|
|
return "EncodeField::None";
|
|
}
|
|
|
|
static std::string fieldTypeToCpp(const std::string& t) {
|
|
if (t == "vgpr") return "FieldType::vgpr";
|
|
if (t == "sreg") return "FieldType::sreg";
|
|
if (t == "sreg_m0") return "FieldType::sreg_m0";
|
|
if (t == "label") return "FieldType::label";
|
|
if (t == "simm16") return "FieldType::simm16";
|
|
if (t == "simm32") return "FieldType::simm32";
|
|
if (t == "simm24") return "FieldType::simm24";
|
|
if (t == "simm5") return "FieldType::simm5";
|
|
if (t == "sdst") return "FieldType::sdst";
|
|
if (t == "ssrc") return "FieldType::ssrc";
|
|
if (t == "hwreg") return "FieldType::hwreg";
|
|
if (t == "delay") return "FieldType::delay";
|
|
if (t == "set_vgpr_msb") return "FieldType::set_vgpr_msb";
|
|
if (t == "sleep") return "FieldType::sleep";
|
|
if (t == "ssrc_barrier_id") return "FieldType::ssrc_barrier_id";
|
|
if (t == "src_vgpr") return "FieldType::src_vgpr";
|
|
if (t == "src_vgpr_or_inline") return "FieldType::src_vgpr_or_inline";
|
|
if (t == "src_simple") return "FieldType::src_simple";
|
|
if (t == "wait_alu") return "FieldType::wait_alu";
|
|
if (t == "wait_mem_ds") return "FieldType::wait_mem_ds";
|
|
if (t == "smem_offset") return "FieldType::smem_offset";
|
|
if (t == "smem_offset_nok") return "FieldType::smem_offset_nok";
|
|
if (t == "src") return "FieldType::src";
|
|
if (t == "vcc") return "FieldType::vcc";
|
|
if (t == "exec") return "FieldType::exec";
|
|
if (t == "sgpr") return "FieldType::sgpr";
|
|
return "FieldType::None";
|
|
}
|
|
|
|
static std::string mnemonicToArraySuffix(const std::string& mnemonic) {
|
|
std::string s;
|
|
for (char c : mnemonic) s += (c == '_' ? '_' : (char)std::tolower(c));
|
|
return s;
|
|
}
|
|
|
|
// Generate operand requirements file (included by ArchInfo getMCIDTable())
|
|
bool generateOperandRequirements(const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
|
|
emitHeader(out, "Operand Requirements");
|
|
out << "// Operand field requirements for IR verifier.\n";
|
|
out << "// Included inside getMCIDTable(); defines "
|
|
"instFieldRequirements[].\n\n";
|
|
|
|
// --- Operand field description arrays ---
|
|
constexpr int kFieldSizeBitsMax = (1 << 12) - 1; // 4095
|
|
|
|
std::vector<const InstructionDef*> withFields;
|
|
for (const auto& inst : instructions_) {
|
|
if (!inst.finalOperandFields.empty()) withFields.push_back(&inst);
|
|
}
|
|
|
|
if (withFields.empty()) {
|
|
out << "// No instructions with operand field descriptions in this arch\n";
|
|
out << "static constexpr struct {\n";
|
|
out << " const char* mnemonic;\n";
|
|
out << " stinkytofu::span<const stinkytofu::HwInstDesc::OperandFieldDesc> "
|
|
"fields;\n";
|
|
out << "} instFieldRequirements[] = {};\n";
|
|
} else {
|
|
for (const auto* inst : withFields) {
|
|
std::string arrayName = "operand_fields_" + mnemonicToArraySuffix(inst->mnemonic);
|
|
out << "static constexpr stinkytofu::HwInstDesc::OperandFieldDesc " << arrayName
|
|
<< "[] = {\n";
|
|
for (const auto& e : inst->finalOperandFields) {
|
|
if (e.sizeBits > kFieldSizeBitsMax) {
|
|
std::cerr << "error: " << inst->mnemonic
|
|
<< " operand sizeBits=" << e.sizeBits << " exceeds 12-bit limit ("
|
|
<< kFieldSizeBitsMax << ")\n";
|
|
return false;
|
|
}
|
|
out << " {" << encodeFieldToCpp(e.encodeField) << ", "
|
|
<< fieldTypeToCpp(e.fieldType) << ", " << e.sizeBits << ", "
|
|
<< (e.isDest ? 1 : 0) << ", " << (e.isReadWrite ? 1 : 0) << ", "
|
|
<< (e.isM64 ? 1 : 0) << "},\n";
|
|
}
|
|
out << "};\n\n";
|
|
}
|
|
|
|
out << "static constexpr struct {\n";
|
|
out << " const char* mnemonic;\n";
|
|
out << " stinkytofu::span<const stinkytofu::HwInstDesc::OperandFieldDesc> "
|
|
"fields;\n";
|
|
out << "} instFieldRequirements[] = {\n";
|
|
for (size_t i = 0; i < withFields.size(); ++i) {
|
|
const auto* inst = withFields[i];
|
|
std::string arrayName = "operand_fields_" + mnemonicToArraySuffix(inst->mnemonic);
|
|
out << " {\"" << inst->mnemonic << "\", " << arrayName << "}";
|
|
out << (i + 1 < withFields.size() ? ",\n" : "\n");
|
|
}
|
|
out << "};\n";
|
|
}
|
|
|
|
// --- Promoted (alt) operand field description arrays ---
|
|
std::vector<const InstructionDef*> withPromoted;
|
|
for (const auto& inst : instructions_) {
|
|
if (!inst.finalPromotedFields.empty()) withPromoted.push_back(&inst);
|
|
}
|
|
|
|
out << "\n";
|
|
if (withPromoted.empty()) {
|
|
out << "// No instructions with promoted operand field descriptions in this arch\n";
|
|
out << "static constexpr struct {\n";
|
|
out << " const char* mnemonic;\n";
|
|
out << " stinkytofu::MicrocodeFormat promotedFormat;\n";
|
|
out << " stinkytofu::span<const stinkytofu::HwInstDesc::OperandFieldDesc> "
|
|
"fields;\n";
|
|
out << "} instPromotedFieldRequirements[] = {};\n";
|
|
} else {
|
|
for (const auto* inst : withPromoted) {
|
|
std::string arrayName = "promoted_fields_" + mnemonicToArraySuffix(inst->mnemonic);
|
|
out << "static constexpr stinkytofu::HwInstDesc::OperandFieldDesc " << arrayName
|
|
<< "[] = {\n";
|
|
for (const auto& e : inst->finalPromotedFields) {
|
|
if (e.sizeBits > kFieldSizeBitsMax) {
|
|
std::cerr << "error: " << inst->mnemonic
|
|
<< " promoted operand sizeBits=" << e.sizeBits
|
|
<< " exceeds 12-bit limit (" << kFieldSizeBitsMax << ")\n";
|
|
return false;
|
|
}
|
|
out << " {" << encodeFieldToCpp(e.encodeField) << ", "
|
|
<< fieldTypeToCpp(e.fieldType) << ", " << e.sizeBits << ", "
|
|
<< (e.isDest ? 1 : 0) << ", " << (e.isReadWrite ? 1 : 0) << ", "
|
|
<< (e.isM64 ? 1 : 0) << "},\n";
|
|
}
|
|
out << "};\n\n";
|
|
}
|
|
|
|
out << "static constexpr struct {\n";
|
|
out << " const char* mnemonic;\n";
|
|
out << " stinkytofu::MicrocodeFormat promotedFormat;\n";
|
|
out << " stinkytofu::span<const stinkytofu::HwInstDesc::OperandFieldDesc> "
|
|
"fields;\n";
|
|
out << "} instPromotedFieldRequirements[] = {\n";
|
|
for (size_t i = 0; i < withPromoted.size(); ++i) {
|
|
const auto* inst = withPromoted[i];
|
|
std::string arrayName = "promoted_fields_" + mnemonicToArraySuffix(inst->mnemonic);
|
|
std::string pfmt = inst->finalPromotedFormat.empty()
|
|
? "MicrocodeFormat::NONE"
|
|
: "MicrocodeFormat::" + inst->finalPromotedFormat;
|
|
out << " {\"" << inst->mnemonic << "\", " << "stinkytofu::" << pfmt << ", "
|
|
<< arrayName << "}";
|
|
out << (i + 1 < withPromoted.size() ? ",\n" : "\n");
|
|
}
|
|
out << "};\n";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Generate initialization file (DEF_T calls)
|
|
bool generateInitFile(const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
|
|
emitHeader(out, "Instruction Initialization");
|
|
out << "// Generated DEF_T initialization calls\n\n";
|
|
|
|
for (const auto& inst : instructions_) {
|
|
std::string flagArgs = flagsToInitArgs(inst.finalFlags);
|
|
out << "DEF_T(\"" << inst.mnemonic << "\"";
|
|
if (!flagArgs.empty()) out << ", " << flagArgs;
|
|
out << ");\n";
|
|
}
|
|
|
|
out << "\n// Total generated instructions: " << instructions_.size() << "\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit two per-arch sorted (name, id) tables and the two binary-search lookup
|
|
// functions that consume them. HwReg.cpp dispatches on arch to nameToId<Arch>
|
|
// / idToName<Arch>.
|
|
//
|
|
// kHwregByName_<Arch>[] — sorted by name; backs nameToId<Arch>
|
|
// kHwregById_<Arch>[] — sorted by id; backs idToName<Arch>
|
|
// nameToId<Arch>(name, out) — std::lower_bound + equality check
|
|
// idToName<Arch>(id) — std::lower_bound + equality check
|
|
//
|
|
// Each table is guarded by a static_assert(is_sorted) so any future drift fails to compile.
|
|
bool generateHwregTable(const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
emitHeader(out, "HWREG Name Tables");
|
|
out << "// HWREG (name, id) rows + per-arch lookup functions for "
|
|
"s_setreg / s_getreg.\n"
|
|
<< "// Included inside HwReg.cpp at namespace stinkytofu::HwReg scope.\n\n";
|
|
|
|
auto emitTable = [&](const std::string& suffix, const std::vector<size_t>& idx,
|
|
const std::string& sortKey) {
|
|
const std::string symbol = "kHwreg" + suffix + "_" + arch_;
|
|
out << "static constexpr ::stinkytofu::HwReg::NamedId " << symbol << "[] = {\n";
|
|
for (size_t k : idx) {
|
|
const auto& e = archDef_.hwRegs[k];
|
|
out << " {\"" << e.name << "\", static_cast<::stinkytofu::HwReg::Id>(" << e.id
|
|
<< ")},\n";
|
|
}
|
|
out << "};\n";
|
|
out << "static_assert(std::is_sorted(std::begin(" << symbol << "), std::end(" << symbol
|
|
<< "),\n"
|
|
<< " [](const ::stinkytofu::HwReg::NamedId& a,\n"
|
|
<< " const ::stinkytofu::HwReg::NamedId& b) {\n"
|
|
<< " return " << sortKey << ";\n"
|
|
<< " }),\n"
|
|
<< " \"" << symbol << " must be sorted by " << suffix << "\");\n\n";
|
|
};
|
|
|
|
auto byName = sortedIndices(archDef_.hwRegs, [](const HwRegEntry& e) { return e.name; });
|
|
emitTable("ByName", byName, "a.name < b.name");
|
|
|
|
auto byId = sortedIndices(archDef_.hwRegs, [](const HwRegEntry& e) { return e.id; });
|
|
emitTable("ById", byId, "static_cast<uint16_t>(a.id) < static_cast<uint16_t>(b.id)");
|
|
|
|
// nameToId<Arch>: binary-search the by-name table.
|
|
out << "inline bool nameToId" << arch_
|
|
<< "(std::string_view name, ::stinkytofu::HwReg::Id& out) {\n"
|
|
<< " const auto* end = std::end(kHwregByName_" << arch_ << ");\n"
|
|
<< " const auto* it = std::lower_bound(\n"
|
|
<< " std::begin(kHwregByName_" << arch_ << "), end, name,\n"
|
|
<< " [](const ::stinkytofu::HwReg::NamedId& e, std::string_view target) {\n"
|
|
<< " return e.name < target;\n"
|
|
<< " });\n"
|
|
<< " if (it != end && it->name == name) {\n"
|
|
<< " out = it->id;\n"
|
|
<< " return true;\n"
|
|
<< " }\n"
|
|
<< " return false;\n"
|
|
<< "}\n\n";
|
|
|
|
// idToName<Arch>: binary-search the by-id table.
|
|
out << "inline std::string_view idToName" << arch_ << "(uint16_t id) {\n"
|
|
<< " const auto* end = std::end(kHwregById_" << arch_ << ");\n"
|
|
<< " const auto* it = std::lower_bound(\n"
|
|
<< " std::begin(kHwregById_" << arch_ << "), end, id,\n"
|
|
<< " [](const ::stinkytofu::HwReg::NamedId& e, uint16_t target) {\n"
|
|
<< " return static_cast<uint16_t>(e.id) < target;\n"
|
|
<< " });\n"
|
|
<< " if (it != end && static_cast<uint16_t>(it->id) == id) return it->name;\n"
|
|
<< " return {};\n"
|
|
<< "}\n\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static std::string flagsToInitArgs(const std::vector<std::string>& flags) {
|
|
if (flags.empty()) return "";
|
|
std::ostringstream os;
|
|
for (size_t i = 0; i < flags.size(); ++i) os << (i ? ", " : "") << "IF_" << flags[i];
|
|
return os.str();
|
|
}
|
|
std::string arch_;
|
|
const std::vector<InstructionDef>& instructions_;
|
|
ArchDef archDef_;
|
|
|
|
void emitHeader(std::ofstream& out, const std::string& description) {
|
|
out << "//===----------------------------------------------------------------------===/"
|
|
"/\n"
|
|
<< "// Auto-generated " << description << " for " << arch_ << "\n"
|
|
<< "//\n"
|
|
<< "// DO NOT EDIT MANUALLY!\n"
|
|
<< "// Generated by tablegen --gen-instructions\n"
|
|
<< "//===----------------------------------------------------------------------===/"
|
|
"/\n\n";
|
|
}
|
|
};
|
|
|
|
//==========================================================================
|
|
// ISA .inc EMISSION (from .def only; no gfxisa dependency)
|
|
//==========================================================================
|
|
|
|
// Convert microcode string (e.g. "MC_VOP3P") to C++ enum literal "MicrocodeFormat::MC_VOP3P"
|
|
static std::string microcodeToCpp(const std::string& microcode) {
|
|
if (microcode.empty()) return "MicrocodeFormat::NONE";
|
|
return "MicrocodeFormat::" + microcode;
|
|
}
|
|
|
|
// Convert unit string (e.g. "VALU") to C++ enum literal "ExecUnit::VALU"
|
|
static std::string unitToCpp(const std::string& unit) {
|
|
if (unit.empty()) return "ExecUnit::NONE";
|
|
return "ExecUnit::" + unit;
|
|
}
|
|
|
|
// Convert finalFlags to C++ makeFlagSet({ IF_XXX, ... }) content (IF_ prefix per flag)
|
|
static std::string flagsToMakeFlagSetContent(const std::vector<std::string>& flags) {
|
|
if (flags.empty()) return "";
|
|
std::ostringstream os;
|
|
for (size_t i = 0; i < flags.size(); ++i) os << (i ? ", " : "") << "IF_" << flags[i];
|
|
return os.str();
|
|
}
|
|
|
|
// Emit one arch's Isa.inc (opcode enum, MCIDTable, getArchOpcode, MnemonicToIsaOpcodeMap).
|
|
// unifiedOpcodeMap: mnemonic -> global unified opcode index.
|
|
static bool emitArchIsaFile(const std::string& arch,
|
|
const std::vector<InstructionDef>& instructions,
|
|
const std::unordered_map<std::string, int>& unifiedOpcodeMap,
|
|
const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
|
|
size_t maxMnemonicLen = 0;
|
|
for (const auto& inst : instructions)
|
|
maxMnemonicLen = std::max(maxMnemonicLen, inst.mnemonic.size());
|
|
|
|
out << "//===----------------------------------------------------------------------===//\n"
|
|
<< "// Auto-generated ISA header for " << arch << " (from .def, tablegen_inst_gen)\n"
|
|
<< "// DO NOT EDIT MANUALLY! DO NOT USE #pragma once IN THIS FILE!\n"
|
|
<< "//===----------------------------------------------------------------------===//"
|
|
"\n\n";
|
|
|
|
#define EMIT_GUARD(MACRO) out << "#ifdef " << (MACRO) << "\n#undef " << (MACRO) << "\n\n"
|
|
|
|
// Opcode enumeration
|
|
EMIT_GUARD("GET_ISAINFO_OPCODE_ENUMERATION");
|
|
out << "enum " << arch << " : uint16_t {\n";
|
|
for (size_t i = 0; i < instructions.size(); ++i)
|
|
out << " " << instructions[i].mnemonic << ", // " << i << "\n";
|
|
out << "};\n\n";
|
|
out << "#endif // GET_ISAINFO_OPCODE_ENUMERATION\n\n";
|
|
|
|
// MCIDTable (HwInstDesc: isaOpcode, unifiedOpcode, issue, latency, mnemonic, flags, microcode,
|
|
// encoding bits, unit, operandFields placeholder)
|
|
EMIT_GUARD("GET_ISAINFO_HWINSTDESC_TABLE");
|
|
out << "// MCIDTable: operandFields set by ArchInfo getMCIDTable()\n"
|
|
<< "static HwInstDesc MCIDTable[] = {\n";
|
|
for (size_t i = 0; i < instructions.size(); ++i) {
|
|
const auto& inst = instructions[i];
|
|
int uop = 0;
|
|
auto it = unifiedOpcodeMap.find(inst.mnemonic);
|
|
if (it != unifiedOpcodeMap.end()) uop = it->second;
|
|
std::string flagStr = flagsToMakeFlagSetContent(inst.finalFlags);
|
|
out << " { " << std::setw(3) << i << ", " << std::setw(5) << uop << ", " << std::setw(3)
|
|
<< inst.cycle << ", " << std::setw(4) << inst.latency << ", " << "0x" << std::hex
|
|
<< std::setfill('0') << std::setw(4)
|
|
<< (inst.coIssueWindow >= 0 ? inst.coIssueWindow : 0) << std::dec << std::setfill(' ')
|
|
<< ", " << "\"" << inst.mnemonic << "\", " << "makeFlagSet({"
|
|
<< (flagStr.empty() ? "" : flagStr) << "}), " << microcodeToCpp(inst.finalMicrocode)
|
|
<< ", " << inst.finalEncoding << ", " << unitToCpp(inst.finalUnit) << ", " << "{} },\n";
|
|
}
|
|
out << "};\n\n";
|
|
out << "#endif // GET_ISAINFO_HWINSTDESC_TABLE\n\n";
|
|
|
|
// getArchOpcode(unifiedOpcode) - binary search table
|
|
EMIT_GUARD("GET_ISAINFO_UOP_MAPPINGS");
|
|
// Sort by unified opcode for binary search
|
|
std::vector<size_t> idx(instructions.size());
|
|
for (size_t i = 0; i < instructions.size(); ++i) idx[i] = i;
|
|
std::sort(idx.begin(), idx.end(), [&](size_t a, size_t b) {
|
|
int ua = 0, ub = 0;
|
|
auto ita = unifiedOpcodeMap.find(instructions[a].mnemonic);
|
|
auto itb = unifiedOpcodeMap.find(instructions[b].mnemonic);
|
|
if (ita != unifiedOpcodeMap.end()) ua = ita->second;
|
|
if (itb != unifiedOpcodeMap.end()) ub = itb->second;
|
|
return ua < ub;
|
|
});
|
|
out << "uint16_t get" << arch << "Opcode(uint16_t unifiedOpcode) {\n"
|
|
<< " static constexpr uint16_t Table[][2] = {\n";
|
|
for (size_t k = 0; k < idx.size(); ++k) {
|
|
size_t i = idx[k];
|
|
int uop = 0;
|
|
auto it = unifiedOpcodeMap.find(instructions[i].mnemonic);
|
|
if (it != unifiedOpcodeMap.end()) uop = it->second;
|
|
out << " { " << uop << ", " << i << " }, // " << instructions[i].mnemonic << "\n";
|
|
}
|
|
out << " };\n"
|
|
<< " unsigned low = 0, high = " << (instructions.size() - 1) << ";\n"
|
|
<< " while(low <= high) {\n"
|
|
<< " unsigned mid = low + (high - low) / 2;\n"
|
|
<< " if(Table[mid][0] == unifiedOpcode) return Table[mid][1];\n"
|
|
<< " if(Table[mid][0] < unifiedOpcode) low = mid + 1; else high = mid - 1;\n"
|
|
<< " }\n"
|
|
<< " return GFX::INVALID;\n"
|
|
<< "}\n\n";
|
|
out << "#endif // GET_ISAINFO_UOP_MAPPINGS\n\n";
|
|
|
|
// MnemonicToIsaOpcodeMap
|
|
EMIT_GUARD("GET_ISAINFO_MNEMONIC_TO_OPCODE_MAPPINGS");
|
|
out << "static const std::unordered_map<std::string, IsaOpcode> MnemonicToIsaOpcodeMap = "
|
|
"{\n";
|
|
for (size_t i = 0; i < instructions.size(); ++i)
|
|
out << " {\"" << instructions[i].mnemonic << "\", " << i << "},\n";
|
|
out << "};\n\n";
|
|
out << "#endif // GET_ISAINFO_MNEMONIC_TO_OPCODE_MAPPINGS\n\n";
|
|
|
|
#undef EMIT_GUARD
|
|
return true;
|
|
}
|
|
|
|
// Emit hardware/gfxIsa.inc (GFX unified opcode enum)
|
|
static bool emitGfxIsaFile(const std::vector<std::string>& unifiedMnemonics,
|
|
const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "//===----------------------------------------------------------------------===//\n"
|
|
<< "// Auto-generated unified opcodes (from .def, tablegen_inst_gen)\n"
|
|
<< "// DO NOT EDIT MANUALLY! DO NOT USE #pragma once IN THIS FILE!\n"
|
|
<< "//===----------------------------------------------------------------------===//"
|
|
"\n\n";
|
|
out << "#ifdef GET_ISAINFO_UNIFIED_OPCODES\n"
|
|
<< "#undef GET_ISAINFO_UNIFIED_OPCODES\n\n";
|
|
out << "enum GFX : uint16_t {\n";
|
|
for (size_t i = 0; i < unifiedMnemonics.size(); ++i)
|
|
out << " " << unifiedMnemonics[i] << ", // " << i << "\n";
|
|
out << "};\n\n#endif // GET_ISAINFO_UNIFIED_OPCODES\n\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit GfxXXX.hpp (ArchInfo class) - replaces GfxArch.hpp.in
|
|
static bool emitArchHeader(const ArchDef& arch, const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "/* ************************************************************************\n"
|
|
<< " * Copyright (C) 2025-2026 Advanced Micro Devices, Inc.\n"
|
|
<< " *\n"
|
|
<< " * Permission is hereby granted, free of charge, to any person obtaining a copy\n"
|
|
<< " * of this software and associated documentation files (the \"Software\"), to "
|
|
"deal\n"
|
|
<< " * in the Software without restriction, including without limitation the rights\n"
|
|
<< " * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
|
|
<< " * copies of the Software, and to permit persons to whom the Software is\n"
|
|
<< " * furnished to do so, subject to the following conditions:\n"
|
|
<< " *\n"
|
|
<< " * THE SOFTWARE IS PROVIDED \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
|
|
<< " * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
|
|
<< " * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n"
|
|
<< " * ************************************************************************ */\n"
|
|
<< "#pragma once\n\n"
|
|
<< "#include \"stinkytofu/hardware/ArchHelper.hpp\"\n"
|
|
<< "#include \"stinkytofu/ir/asm/StinkyAsmIR.hpp\"\n\n"
|
|
<< "#include <mutex>\n\n"
|
|
<< "// Auto-generated from Formats.def + Instructions.def - do not edit manually.\n\n"
|
|
<< "namespace\n{\n\n"
|
|
<< "#define GET_ISAINFO_UOP_MAPPINGS\n"
|
|
<< "#include \"hardware/" << arch.name << "Isa.inc\"\n\n"
|
|
<< "}\n\n"
|
|
<< "using namespace stinkytofu;\n\n"
|
|
<< "// clang-format off\n"
|
|
<< "struct " << arch.name << "ArchInfo : public ArchHelper::ArchInfo\n"
|
|
<< "{\n"
|
|
<< " " << arch.name << "ArchInfo()\n"
|
|
<< " : ArchInfo(" << arch.major << ", " << arch.minor << ", " << arch.stepping
|
|
<< ", " << arch.wavefront << " /* waveFrontSize */" << ", " << arch.totalVgprPerSimd
|
|
<< " /* totalVgprPerSimd */" << ", " << arch.vgprAllocGranule
|
|
<< " /* vgprAllocGranule */)\n"
|
|
<< " {\n"
|
|
<< " }\n\n"
|
|
<< " IsaOpcode getIsaOpcode(UnifiedOpcode unifiedOpcode) const override\n"
|
|
<< " {\n"
|
|
<< " return get" << arch.name << "Opcode(unifiedOpcode);\n"
|
|
<< " }\n\n"
|
|
<< " bool hasD16Writes32BitVgpr() const override\n"
|
|
<< " {\n"
|
|
<< " return " << (arch.d16Writes32BitVgpr ? "true" : "false") << ";\n"
|
|
<< " }\n\n"
|
|
<< " const HwInstDesc* getMCIDTable() const override\n"
|
|
<< " {\n"
|
|
<< "#define GET_ISAINFO_HWINSTDESC_TABLE\n"
|
|
<< "#include \"hardware/" << arch.name << "Isa.inc\"\n"
|
|
<< "#include \"hardware/generated/" << arch.name << "_operands.inc\"\n\n"
|
|
<< " static std::once_flag once;\n"
|
|
<< " std::call_once(once, [] {\n"
|
|
<< " for(const auto& req : instFieldRequirements)\n"
|
|
<< " {\n"
|
|
<< " for(size_t i = 0; i < sizeof(MCIDTable) / sizeof(MCIDTable[0]); "
|
|
"++i)\n"
|
|
<< " {\n"
|
|
<< " if(MCIDTable[i].mnemonic && std::string(MCIDTable[i].mnemonic) "
|
|
"== req.mnemonic)\n"
|
|
<< " {\n"
|
|
<< " const_cast<HwInstDesc&>(MCIDTable[i]).operandFields = "
|
|
"req.fields;\n"
|
|
<< " break;\n"
|
|
<< " }\n"
|
|
<< " }\n"
|
|
<< " }\n"
|
|
<< " for(const auto& req : instPromotedFieldRequirements)\n"
|
|
<< " {\n"
|
|
<< " for(size_t i = 0; i < sizeof(MCIDTable) / sizeof(MCIDTable[0]); "
|
|
"++i)\n"
|
|
<< " {\n"
|
|
<< " if(MCIDTable[i].mnemonic && std::string(MCIDTable[i].mnemonic) "
|
|
"== req.mnemonic)\n"
|
|
<< " {\n"
|
|
<< " const_cast<HwInstDesc&>(MCIDTable[i]).promotedFormat = "
|
|
"req.promotedFormat;\n"
|
|
<< " const_cast<HwInstDesc&>(MCIDTable[i]).promotedFields = "
|
|
"req.fields;\n"
|
|
<< " break;\n"
|
|
<< " }\n"
|
|
<< " }\n"
|
|
<< " }\n"
|
|
<< " });\n\n"
|
|
<< " return MCIDTable;\n"
|
|
<< " }\n\n"
|
|
<< " const std::unordered_map<std::string, uint16_t>& getMnemonicToIsaOpcodeMap() "
|
|
"const override\n"
|
|
<< " {\n"
|
|
<< "#define GET_ISAINFO_MNEMONIC_TO_OPCODE_MAPPINGS\n"
|
|
<< "#include \"hardware/" << arch.name << "Isa.inc\"\n"
|
|
<< " return MnemonicToIsaOpcodeMap;\n"
|
|
<< " }\n"
|
|
<< "};\n"
|
|
<< "// clang-format on\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit GfxXXX_block.inc (defineGfxXXXInsts) - replaces GfxArchDefines_block.inc.in
|
|
static bool emitArchDefinesBlock(const ArchDef& arch, const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "// clang-format off\n"
|
|
<< "// Per-arch block: costs + define" << arch.name << "Insts().\n"
|
|
<< "namespace {\n"
|
|
<< "#include \"hardware/generated/" << arch.name << "_costs.inc\"\n"
|
|
<< "}\n\n"
|
|
<< "void define" << arch.name << "Insts(GpuArch& registry)\n"
|
|
<< "{\n"
|
|
<< " registry.setWaveFrontSize(" << arch.wavefront << ");\n"
|
|
<< " GpuArch::RegisterLimits limits;\n"
|
|
<< " limits.maxVGPR = " << arch.maxVGPR << ";\n"
|
|
<< " limits.maxSGPR = " << arch.maxSGPR << ";\n"
|
|
<< " limits.maxAGPR = " << arch.maxAGPR << ";\n"
|
|
<< " if(limits.maxVGPR == 0 || limits.maxSGPR == 0)\n"
|
|
<< " {\n"
|
|
<< " std::cerr << \"FATAL: " << arch.name
|
|
<< " must define maxVGPR, maxSGPR > 0 in Formats.def (maxAGPR may be 0)\\n\";\n"
|
|
<< " return;\n"
|
|
<< " }\n"
|
|
<< " registry.setRegisterLimits(limits);\n"
|
|
<< " // Instruction definitions: generated from " << arch.name << "Instructions.def\n"
|
|
<< "#include \"hardware/generated/" << arch.name << "_init.inc\"\n"
|
|
<< " registry.setDefaultCosts(" << arch.defaultCycle << ", " << arch.defaultLatency
|
|
<< ");\n"
|
|
<< " for(const auto& cost : " << arch.name << "_GENERATED_COSTS)\n"
|
|
<< " {\n"
|
|
<< " registry.setInstructionCost(cost.opcode, cost.cycle, cost.latency);\n"
|
|
<< " }\n"
|
|
<< " if(!registry.applyInstructionCosts())\n"
|
|
<< " {\n"
|
|
<< " std::cerr << \"FATAL: Failed to apply instruction costs for " << arch.name
|
|
<< "\\n\";\n"
|
|
<< " return;\n"
|
|
<< " }\n"
|
|
<< "}\n"
|
|
<< "// clang-format on\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit GfxArchDefines.cpp - replaces GfxArchDefines.cpp.in
|
|
static bool emitGfxArchDefinesCpp(const std::vector<std::string>& archs,
|
|
const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "/* ************************************************************************\n"
|
|
<< " * Copyright (C) 2025-2026 Advanced Micro Devices, Inc.\n"
|
|
<< " * ...\n"
|
|
<< " * ************************************************************************ */\n\n"
|
|
<< "#include <iostream>\n\n"
|
|
<< "#include \"gfx/InstDefDSL.hpp\"\n"
|
|
<< "#include \"gfx/InstructionCost.hpp\"\n\n"
|
|
<< "// clang-format off\n"
|
|
<< "namespace stinkytofu\n"
|
|
<< "{\n";
|
|
for (const auto& arch : archs)
|
|
out << " #include \"hardware/generated/" << arch << "_block.inc\"\n";
|
|
out << "}\n"
|
|
<< "// clang-format on\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit GfxLogicalMaps.cpp - setGfxXXXLogicalToArchMap from .logical in DEF_T
|
|
static bool emitGfxLogicalMapsCpp(
|
|
const std::map<std::string, std::vector<InstructionDef>>& archInstructions,
|
|
const std::vector<std::string>& archs, const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "/* ************************************************************************\n"
|
|
<< " * Copyright (C) 2025-2026 Advanced Micro Devices, Inc.\n"
|
|
<< " * Auto-generated from .logical in Instructions.def - do not edit manually.\n"
|
|
<< " * ************************************************************************ */\n\n"
|
|
<< "#include <string>\n"
|
|
<< "#include <unordered_map>\n\n"
|
|
<< "#include \"gfx/InstDefDSL.hpp\"\n\n"
|
|
<< "namespace stinkytofu\n{\n";
|
|
|
|
for (const auto& arch : archs) {
|
|
auto it = archInstructions.find(arch);
|
|
if (it == archInstructions.end()) continue;
|
|
const auto& insts = it->second;
|
|
out << "\nvoid set" << arch << "LogicalToArchMap(GpuArch& registry)\n{\n"
|
|
<< " std::unordered_map<std::string, std::string> logicalToHwInstMap = {\n";
|
|
size_t count = 0;
|
|
for (const auto& inst : insts) {
|
|
for (const auto& logicalName : inst.logical) {
|
|
if (!logicalName.empty()) {
|
|
if (count > 0) out << ",\n";
|
|
out << " {\"" << logicalName << "\", \"" << inst.mnemonic << "\"}";
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
out << "\n };\n"
|
|
<< " registry.setLogicalToArchMap(std::move(logicalToHwInstMap));\n"
|
|
<< "}\n";
|
|
}
|
|
out << "}\n";
|
|
return true;
|
|
}
|
|
|
|
// Emit ArchHelper_includes.inc
|
|
static bool emitArchHelperIncludes(const std::vector<std::string>& archs,
|
|
const std::string& outputPath) {
|
|
std::ofstream out(outputPath);
|
|
if (!out) {
|
|
std::cerr << "Error: Cannot write " << outputPath << "\n";
|
|
return false;
|
|
}
|
|
out << "/* Architecture-specific ArchInfo headers. */\n";
|
|
for (const auto& arch : archs) {
|
|
std::string archUpper = arch;
|
|
for (char& c : archUpper)
|
|
c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
|
out << "#ifdef STINKYTOFU_ARCH_" << archUpper << "\n"
|
|
<< "#include \"" << arch << ".hpp\"\n"
|
|
<< "#endif\n\n";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
// MAIN ENTRY POINT
|
|
//==========================================================================
|
|
|
|
// Normalize arch to match .def filenames (e.g. gfx1250 -> Gfx1250)
|
|
static std::string normalizeArch(const std::string& arch) {
|
|
if (arch.empty()) return arch;
|
|
std::string s = arch;
|
|
if (s.size() >= 1) s[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(s[0])));
|
|
return s;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
|
bool genInstructions(const std::string& arch, const std::string& inputDir,
|
|
const std::string& outputDir) {
|
|
std::string normArch = normalizeArch(arch);
|
|
std::cout << "Generating instruction metadata for " << normArch << "...\n";
|
|
|
|
// Construct file paths: inputDir is base (e.g. hardware/src/gfx), .def files in arch subdir
|
|
std::string formatFile = inputDir + "/" + normArch + "/" + normArch + "Formats.def";
|
|
std::string instFile = inputDir + "/" + normArch + "/" + normArch + "Instructions.def";
|
|
std::string outputBase = outputDir + "/" + normArch;
|
|
|
|
// Parse format definitions
|
|
DefTParser parser(normArch);
|
|
if (!parser.parseFormats(formatFile)) {
|
|
std::cerr << "Error: Failed to parse format file\n";
|
|
return false;
|
|
}
|
|
|
|
// Parse instruction definitions
|
|
if (!parser.parseInstructions(instFile)) {
|
|
std::cerr << "Error: Failed to parse instruction file\n";
|
|
return false;
|
|
}
|
|
|
|
// Apply format inheritance
|
|
if (!parser.applyFormatDefaults()) return false;
|
|
|
|
// Generate output files
|
|
InstructionCodeGen codegen(normArch, parser.getInstructions(), parser.getArch());
|
|
|
|
bool success = true;
|
|
success &= codegen.generateCostTable(outputBase + "_costs.inc");
|
|
success &= codegen.generateOperandRequirements(outputBase + "_operands.inc");
|
|
success &= codegen.generateInitFile(outputBase + "_init.inc");
|
|
success &= codegen.generateHwregTable(outputBase + "_hwreg.inc");
|
|
|
|
if (success) {
|
|
std::cout << "Successfully generated instruction metadata for " << normArch << "\n";
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Generate for all archs from .def and emit ISA .inc (so full tablegen does not need gfxisa for
|
|
// ISA). Single run: *.def -> costs, init, operands, *Isa.inc, gfxIsa.inc, GfxXXX.hpp, *_block.inc,
|
|
// GfxArchDefines.cpp -> one build.
|
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
|
bool genAllInstructions(const std::string& inputDir, const std::string& outputDir) {
|
|
const std::vector<std::string> archs = {"Gfx1250"};
|
|
|
|
std::map<std::string, std::vector<InstructionDef>> archInstructions;
|
|
std::map<std::string, ArchDef> archDefs;
|
|
for (const std::string& arch : archs) {
|
|
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
|
|
std::string formatFile = inputDir + "/" + arch + "/" + arch + "Formats.def";
|
|
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
|
|
std::string instFile = inputDir + "/" + arch + "/" + arch + "Instructions.def";
|
|
DefTParser parser(arch);
|
|
if (!parser.parseFormats(formatFile)) {
|
|
std::cerr << "Error: Failed to parse format file " << formatFile << "\n";
|
|
return false;
|
|
}
|
|
if (!parser.parseInstructions(instFile)) {
|
|
std::cerr << "Error: Failed to parse instruction file " << instFile << "\n";
|
|
return false;
|
|
}
|
|
if (!parser.applyFormatDefaults()) return false;
|
|
archInstructions[arch] = parser.getInstructions();
|
|
archDefs[arch] = parser.getArch();
|
|
}
|
|
|
|
// Build unified mnemonic list (sorted, + LABEL, INVALID) and map mnemonic -> index
|
|
std::set<std::string> allMnemonics;
|
|
for (const auto& p : archInstructions)
|
|
for (const auto& inst : p.second) allMnemonics.insert(inst.mnemonic);
|
|
std::vector<std::string> unifiedList(allMnemonics.begin(), allMnemonics.end());
|
|
std::sort(unifiedList.begin(), unifiedList.end());
|
|
unifiedList.push_back("FENCE");
|
|
unifiedList.push_back("LABEL");
|
|
unifiedList.push_back("PHI");
|
|
// Pseudo: function ASM placement marker for FlattenCalleesPass (never emitted).
|
|
unifiedList.push_back("FUNCTION_ASM_PLACEMENT_MARKER");
|
|
unifiedList.push_back("EXEC_GROUP");
|
|
unifiedList.push_back("INVALID");
|
|
std::unordered_map<std::string, int> unifiedOpcodeMap;
|
|
for (size_t i = 0; i < unifiedList.size(); ++i)
|
|
unifiedOpcodeMap[unifiedList[i]] = static_cast<int>(i);
|
|
|
|
std::filesystem::path outPath(outputDir);
|
|
if (!outPath.is_absolute()) outPath = std::filesystem::absolute(outPath);
|
|
std::filesystem::path hwDir = outPath / "hardware";
|
|
std::filesystem::path genDir = hwDir / "generated";
|
|
std::filesystem::path archHdrDir = hwDir / "arch_headers";
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(hwDir, ec);
|
|
std::filesystem::create_directories(genDir, ec);
|
|
std::filesystem::create_directories(archHdrDir, ec);
|
|
if (!std::filesystem::is_directory(genDir) || !std::filesystem::is_directory(hwDir)) {
|
|
std::cerr << "Error: Output directories missing or not usable: " << genDir.string() << " "
|
|
<< hwDir.string() << "\n";
|
|
return false;
|
|
}
|
|
|
|
bool success = true;
|
|
for (const std::string& arch : archs) {
|
|
const std::vector<InstructionDef>& insts = archInstructions[arch];
|
|
const ArchDef& ad = archDefs[arch];
|
|
InstructionCodeGen codegen(arch, insts, ad);
|
|
success &= codegen.generateCostTable((genDir / (arch + "_costs.inc")).string());
|
|
success &=
|
|
codegen.generateOperandRequirements((genDir / (arch + "_operands.inc")).string());
|
|
success &= codegen.generateInitFile((genDir / (arch + "_init.inc")).string());
|
|
success &= codegen.generateHwregTable((genDir / (arch + "_hwreg.inc")).string());
|
|
success &=
|
|
emitArchIsaFile(arch, insts, unifiedOpcodeMap, (hwDir / (arch + "Isa.inc")).string());
|
|
success &= emitArchHeader(ad, (archHdrDir / (arch + ".hpp")).string());
|
|
success &= emitArchDefinesBlock(ad, (genDir / (arch + "_block.inc")).string());
|
|
}
|
|
success &= emitGfxIsaFile(unifiedList, (hwDir / "gfxIsa.inc").string());
|
|
success &= emitGfxArchDefinesCpp(archs, (genDir / "GfxArchDefines.cpp").string());
|
|
success &=
|
|
emitGfxLogicalMapsCpp(archInstructions, archs, (genDir / "GfxLogicalMaps.cpp").string());
|
|
success &= emitArchHelperIncludes(archs, (archHdrDir / "ArchHelper_includes.inc").string());
|
|
|
|
if (success) std::cout << "Successfully generated instruction metadata and ISA for all archs\n";
|
|
return success;
|
|
}
|
|
|
|
} // namespace stinkytofu
|