Files
wehub-resource-sync 7254f7b4d1
Build / Build (macos-latest) (push) Waiting to run
Build / Build (ubuntu-latest) (push) Waiting to run
Build / Build (windows-latest) (push) Waiting to run
Build / Analyze (javascript) (push) Waiting to run
Build / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:37:45 +08:00

3474 lines
145 KiB
JavaScript

import * as fs from 'fs/promises';
import * as path from 'path';
const tablegen = {};
tablegen.Location = class {
constructor(file, line, column) {
this.file = file;
this.line = line;
this.column = column;
}
toString() {
return `${this.file}:${this.line}:${this.column}`;
}
};
tablegen.Token = class {
constructor(type, value, location) {
this.type = type;
this.value = value;
this.location = location;
}
};
tablegen.Tokenizer = class {
constructor(text, file) {
this._text = text;
this._file = file;
this._position = 0;
this._line = 1;
this._column = 1;
this._keywords = new Set([
'assert', 'bit', 'bits', 'class', 'code', 'dag', 'def', 'defm', 'defset', 'defvar',
'dump', 'else', 'false', 'field', 'foreach', 'if', 'in', 'include', 'int',
'let', 'list', 'multiclass', 'string', 'then', 'true'
]);
this._token = this._tokenize();
}
location() {
return new tablegen.Location(this._file, this._line, this._column);
}
current() {
return this._token;
}
peek(offset = 0) {
const pos = this._position + offset;
return pos < this._text.length ? this._text[pos] : null;
}
read() {
const prev = this._token;
this._token = this._tokenize();
return prev;
}
_tokenize() {
this._skipWhitespace();
if (this._position >= this._text.length) {
return new tablegen.Token('eof', null, this.location());
}
const location = this.location();
const c = this.peek();
if (c === '"') {
return this._readString(location);
}
if (c === '[' && this.peek(1) === '{') {
return this._readCodeBlock(location);
}
if (c === ':' && this.peek(1) === ':') {
this._next();
this._next();
return new tablegen.Token('::', '::', location);
}
if (c === '.' && this.peek(1) === '.' && this.peek(2) === '.') {
this._next();
this._next();
this._next();
return new tablegen.Token('...', '...', location);
}
if (c === '-' && this.peek(1) === '>') {
this._next();
this._next();
return new tablegen.Token('->', '->', location);
}
if (c === '=' && this.peek(1) === '=') {
this._next();
this._next();
return new tablegen.Token('==', '==', location);
}
if (c === '!' && this.peek(1) === '=') {
this._next();
this._next();
return new tablegen.Token('!=', '!=', location);
}
if (c === '<' && this.peek(1) === '<') {
this._next();
this._next();
return new tablegen.Token('<<', '<<', location);
}
if (this._isDigit(c)) {
let pos = 1;
while (this.peek(pos) && this._isDigit(this.peek(pos))) {
pos++;
}
if (this.peek(pos) && /[a-zA-Z_]/.test(this.peek(pos))) {
return this._readIdentifier(location);
}
return this._readNumber(location);
}
if (c === '-' && this._isDigit(this.peek(1))) {
return this._readNumber(location);
}
if (this._isIdentifierStart(c)) {
return this._readIdentifier(location);
}
if ('{}[]()<>,;:=!#$.?^'.includes(c)) {
this._next();
return new tablegen.Token(c, c, location);
}
throw new tablegen.Error(`Unexpected character '${c}' at ${location}`);
}
_next() {
if (this._position < this._text.length) {
if (this._text[this._position] === '\n') {
this._line++;
this._column = 1;
} else {
this._column++;
}
this._position++;
}
}
_skipWhitespace() {
while (this._position < this._text.length) {
const c = this.peek();
if (/\s/.test(c)) {
this._next();
continue;
}
if (c === '/' && this.peek(1) === '/') {
this._next();
this._next();
while (this._position < this._text.length && this.peek() !== '\n') {
this._next();
}
continue;
}
if (c === '/' && this.peek(1) === '*') {
this._next();
this._next();
let depth = 1;
while (this._position < this._text.length && depth > 0) {
if (this.peek() === '/' && this.peek(1) === '*') {
this._next();
this._next();
depth++;
} else if (this.peek() === '*' && this.peek(1) === '/') {
this._next();
this._next();
depth--;
} else {
this._next();
}
}
continue;
}
break;
}
}
_isDigit(c) {
return c !== null && /[0-9]/.test(c);
}
_isIdentifierStart(c) {
return c !== null && /[a-zA-Z_]/.test(c);
}
_isIdentifierChar(c) {
return c !== null && /[a-zA-Z0-9_]/.test(c);
}
_readString(location) {
let value = '';
this._next(); // opening "
while (this._position < this._text.length && this.peek() !== '"') {
if (this.peek() === '\\') {
this._next();
const c = this.peek();
switch (c) {
case 'n': value += '\n'; break;
case 't': value += '\t'; break;
case 'r': value += '\r'; break;
case '\\': value += '\\'; break;
case '"': value += '"'; break;
default: value += c; break;
}
this._next();
} else {
value += this.peek();
this._next();
}
}
if (this.peek() === '"') {
this._next();
}
return new tablegen.Token('string', value, location);
}
_readCodeBlock(location) {
let value = '';
this._next(); // [
this._next(); // {
let depth = 1;
let lineStart = true;
let lineContent = '';
while (this._position < this._text.length && depth > 0) {
const c = this.peek();
const next = this.peek(1);
if (c === '[' && next === '{') {
const trimmedLine = lineContent.trim();
if (lineStart || trimmedLine === '' || /^(let|def|class)\s/.test(trimmedLine)) {
depth++;
}
value += c;
this._next();
value += next;
this._next();
lineContent += c + next;
} else if (c === '}' && next === ']') {
depth--;
if (depth === 0) {
this._next();
this._next();
break;
}
value += c;
this._next();
value += next;
this._next();
lineContent += c + next;
} else {
if (c === '\n') {
lineStart = true;
lineContent = '';
} else if (c !== ' ' && c !== '\t') {
lineStart = false;
}
if (c !== '\n') {
lineContent += c;
}
value += c;
this._next();
}
}
return new tablegen.Token('code', value.trim(), location);
}
_readNumber(location) {
let value = '';
if (this.peek() === '-') {
value += this.peek();
this._next();
}
// Hexadecimal
if (this.peek() === '0' && this.peek(1) === 'x') {
value += this.peek();
this._next();
value += this.peek();
this._next();
while (this._position < this._text.length && /[0-9a-fA-F]/.test(this.peek())) {
value += this.peek();
this._next();
}
return new tablegen.Token('number', parseInt(value, 16), location);
}
// Binary
if (this.peek() === '0' && this.peek(1) === 'b') {
const isNegative = value === '-';
value += this.peek();
this._next();
value += this.peek();
this._next();
while (this._position < this._text.length && /[01]/.test(this.peek())) {
value += this.peek();
this._next();
}
const binaryDigits = isNegative ? value.substring(3) : value.substring(2);
const result = parseInt(binaryDigits, 2);
return new tablegen.Token('number', isNegative ? -result : result, location);
}
// Decimal
while (this._position < this._text.length && this._isDigit(this.peek())) {
value += this.peek();
this._next();
}
return new tablegen.Token('number', parseInt(value, 10), location);
}
_readIdentifier(location) {
let value = '';
while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
value += this.peek();
this._next();
}
while (this.peek() === '.' && this._isIdentifierStart(this.peek(1))) {
value += this.peek(); // add dot
this._next();
while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
value += this.peek();
this._next();
}
}
const type = this._keywords.has(value) ? 'keyword' : 'id';
return new tablegen.Token(type, value, location);
}
};
tablegen.Value = class {
constructor(type, value) {
this.type = type; // 'int', 'string', 'bit', 'bits', 'list', 'dag', 'code', 'def'
this.value = value;
}
};
tablegen.DAG = class {
constructor(operator, operands) {
this.operator = operator; // string or Value
this.operands = operands; // array of {value, name}
}
};
tablegen.Type = class {
constructor(name) {
this.name = name;
this.args = [];
}
toString() {
if (this.args.length === 0) {
return this.name;
}
return `${this.name}<${this.args.map((a) => a.toString()).join(', ')}>`;
}
};
tablegen.RecordVal = class {
constructor(name, type, value) {
this.name = name;
this.type = type; // tablegen.Type
this.value = value; // tablegen.Value or null
}
};
tablegen.Record = class {
constructor(name, parser = null) {
this.name = name;
this.parents = [];
this.fields = new Map();
this.templateArgs = [];
this.templateBindings = new Map(); // parameter name -> bound value
this.location = null;
this.parser = parser;
}
getValue(name) {
return this.fields.get(name) || null;
}
// Unified resolution function that handles both parent field copying and post-inheritance resolution
// Inspired by LLVM's Resolver pattern to avoid code duplication
_resolveInit(init, resolver, visited = new Set()) {
if (!init || typeof init !== 'object') {
return init;
}
const key = JSON.stringify(init);
if (visited.has(key)) {
return init;
}
visited.add(key);
// Resolve def/id type references (template parameters or field access)
if ((init.type === 'def' || init.type === 'id') && typeof init.value === 'string') {
// Handle field access syntax (e.g., "meta.mnemonic" or "addrKind.name")
if (init.value.includes('.')) {
const [baseName, ...fieldParts] = init.value.split('.');
// Try to resolve the base
let current = resolver.resolveReference(baseName);
if (current) {
// Walk through field access chain
for (const fieldName of fieldParts) {
// Handle def reference
if (current && current.type === 'def' && typeof current.value === 'string') {
const defName = current.value;
const def = this.parser.getDef(defName) || this.parser.getClass(defName);
if (def) {
const field = def.getValue(fieldName);
if (field && field.value) {
current = field.value;
} else {
return init; // Field not found
}
} else {
return init; // Def not found
}
} else if (current && current.type === 'dag' && current.value) {
// Handle DAG (anonymous class instantiation)
const className = current.value.operator;
const templateArgs = current.value.operands.map((op) => op.value);
// Instantiate an anonymous class with template arguments
// Used for resolving field access like meta.mnemonic where meta is ROCDL_TrLoadOpMeta<...>
let instantiated = null;
const baseClass = this.parser.getClass(className);
if (baseClass) {
// Create an anonymous record that inherits from the class
instantiated = new tablegen.Record(`<anonymous ${className}>`, this.parser);
instantiated.parents = [{ name: className, args: templateArgs }];
this.parser.addSubClass(instantiated);
instantiated.resolveReferences();
}
if (instantiated) {
const field = instantiated.getValue(fieldName);
if (field && field.value) {
current = field.value;
} else {
return init;
}
} else {
return init;
}
} else {
return init; // Can't resolve further
}
}
// Successfully resolved the field access chain
return this._resolveInit(current, resolver, visited);
}
} else {
// Simple reference without dots
const resolved = resolver.resolveReference(init.value);
if (resolved) {
return this._resolveInit(resolved, resolver, visited);
}
}
}
// Recursively resolve nested structures
if (init.type === 'dag' && init.value) {
return {
type: 'dag',
value: {
operator: init.value.operator,
operands: init.value.operands.map((op) => ({
value: this._resolveInit(op.value, resolver, visited),
name: op.name
}))
}
};
}
if (init.type === 'list' && Array.isArray(init.value)) {
return {
type: 'list',
value: init.value.map((v) => this._resolveInit(v, resolver, visited))
};
}
if (init.type === 'bang' && init.value) {
return {
type: 'bang',
value: {
op: init.value.op,
args: init.value.args.map((arg) => this._resolveInit(arg, resolver, visited))
}
};
}
if (init.type === 'concat' && Array.isArray(init.value)) {
const resolvedParts = init.value.map((v) => this._resolveInit(v, resolver, visited));
// Flatten nested concats recursively: if a resolved part is itself a concat,
// extract its parts to avoid nested concat structures that cause double evaluation
const flattenedParts = [];
const flattenConcat = (part) => {
if (part && part.type === 'concat' && Array.isArray(part.value)) {
for (const subPart of part.value) {
flattenConcat(subPart);
}
} else {
flattenedParts.push(part);
}
};
for (const part of resolvedParts) {
flattenConcat(part);
}
return {
type: 'concat',
value: flattenedParts
};
}
// For other types, return shallow copy for parent field copying, as-is for post-inheritance
return resolver.shouldCopy ? { ...init } : init;
}
// Helper to deep copy a field and resolve parameter references in a specific context
_copyAndResolveField(field, bindings, parentClass) {
const resolver = {
shouldCopy: true,
resolveReference: (name) => bindings.get(name) || parentClass.templateBindings.get(name) || null
};
return {
name: field.name,
type: field.type,
value: this._resolveInit(field.value, resolver)
};
}
// Resolve template parameter references in field values
// This matches C++ Record::resolveReferences() behavior
// After inheriting from a templated class, substitute all template parameter
// references in field values with their bound values from templateBindings
resolveReferences() {
if (this.templateBindings.size === 0) {
return; // No template parameters to substitute
}
const findTemplateBinding = (paramName, record = this, visited = new Set()) => {
if (visited.has(record.name)) {
return null;
}
visited.add(record.name);
if (record.templateBindings.has(paramName)) {
return record.templateBindings.get(paramName);
}
for (const parent of record.parents) {
const parentClass = this.parser.classes.get(parent.name);
if (parentClass) {
const paramIndex = parentClass.templateArgs.findIndex((arg) => arg.name === paramName);
if (paramIndex !== -1 && parent.args && parent.args[paramIndex]) {
return parent.args[paramIndex];
}
const binding = findTemplateBinding(paramName, parentClass, visited);
if (binding) {
return binding;
}
}
}
return null;
};
const resolver = {
shouldCopy: false,
resolveReference: (name) => {
const binding = findTemplateBinding(name);
if (binding) {
return binding;
}
const field = this.getValue(name);
if (field && field.value) {
return field.value;
}
return null;
}
};
for (const [, field] of this.fields) {
if (field.value) {
const resolved = this._resolveInit(field.value, resolver);
if (resolved !== field.value) {
field.value = resolved;
}
}
}
}
getValueAsString(fieldName) {
const field = this.getValue(fieldName);
if (!field || !field.value) {
return null;
}
const evaluated = this.evaluateValue(field.value);
if (typeof evaluated === 'string') {
return evaluated;
}
return null;
}
getValueAsBit(fieldName) {
const field = this.getValue(fieldName);
if (!field || !field.value) {
return null;
}
const evaluated = this.evaluateValue(field.value);
if (typeof evaluated === 'boolean') {
return evaluated;
}
if (typeof evaluated === 'number') {
return evaluated !== 0;
}
return null;
}
getValueAsDag(fieldName) {
const field = this.getValue(fieldName);
if (!field || !field.value) {
return null;
}
const evaluated = this.evaluateValue(field.value);
if (evaluated && typeof evaluated === 'object' && evaluated.operator) {
return evaluated;
}
return null;
}
getValueAsDef(fieldName) {
const field = this.getValue(fieldName);
if (!field || !field.value) {
return null;
}
if (field.value.type === 'def' && typeof field.value.value === 'string') {
const defName = field.value.value;
const candidates = [];
for (const def of this.parser.defs) {
if (def.name === defName) {
candidates.push(def);
}
}
for (const cls of this.parser.classes.values()) {
if (cls.name === defName) {
candidates.push(cls);
}
}
if (candidates.length === 0) {
return null;
}
if (candidates.length === 1) {
return candidates[0];
}
const sourceFile = this.location?.file || '';
if (sourceFile) {
const sourceParts = sourceFile.split('/');
let [bestMatch] = candidates;
let bestPrefixLength = -1;
for (const candidate of candidates) {
const candidateFile = candidate.location?.file || '';
const candidateParts = candidateFile.split('/');
let prefixLength = 0;
for (let i = 0; i < Math.min(sourceParts.length, candidateParts.length); i++) {
if (sourceParts[i] !== candidateParts[i]) {
break;
}
prefixLength++;
}
if (prefixLength > bestPrefixLength) {
bestPrefixLength = prefixLength;
bestMatch = candidate;
}
}
return bestMatch;
}
return candidates[0];
}
return null;
}
isEnumAttr() {
const enumBaseClasses = [
'EnumAttr', // Wrapper for dialect-specific enums
'IntEnumAttr', 'I32EnumAttr', 'I64EnumAttr',
'BitEnumAttr', 'I8BitEnumAttr', 'I16BitEnumAttr', 'I32BitEnumAttr', 'I64BitEnumAttr',
'IntEnum', 'I32IntEnum', 'I64IntEnum', // Integer enum types
'BitEnum', 'I8BitEnum', 'I16BitEnum', 'I32BitEnum', 'I64BitEnum' // Bit enum types
];
const checkParents = (record, visited = new Set()) => {
if (record && !visited.has(record.name)) {
visited.add(record.name);
if (enumBaseClasses.includes(record.name)) {
return true;
}
for (const parent of record.parents) {
const parentClass = this.parser.getClass(parent.name);
if (parentClass && checkParents(parentClass, visited)) {
return true;
}
}
}
return false;
};
return checkParents(this);
}
isEnumProp() {
const enumPropBaseClasses = [
'EnumProp', // Property wrapping an enum
'EnumPropWithAttrForm' // Property with attribute form
];
const checkParents = (record, visited = new Set()) => {
if (record && !visited.has(record.name)) {
visited.add(record.name);
if (enumPropBaseClasses.includes(record.name)) {
return true;
}
for (const parent of record.parents) {
const parentClass = this.parser.getClass(parent.name);
if (parentClass && checkParents(parentClass, visited)) {
return true;
}
}
}
return false;
};
return checkParents(this);
}
// Get enum cases from an enum attribute or property definition
getEnumCases() {
if (!this.isEnumAttr() && !this.isEnumProp()) {
return null;
}
// Handle EnumProp - the first argument is the underlying enum
if (this.isEnumProp()) {
for (const parent of this.parents) {
if (parent.name === 'EnumProp' || parent.name === 'EnumPropWithAttrForm') {
if (parent.args && parent.args.length >= 1) {
const [enumInfoArg] = parent.args;
if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
const enumName = enumInfoArg.value;
const underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
if (underlyingEnum) {
// Recursively get cases from the underlying enum
return underlyingEnum.getEnumCases();
}
}
}
}
// Recursively search parent classes
const parentClass = this.parser.getClass(parent.name);
if (parentClass && parentClass.isEnumProp()) {
const cases = parentClass.getEnumCases();
if (cases) {
return cases;
}
}
}
}
// Helper to search for EnumAttr in the parent hierarchy
const findEnumAttrParent = (record, visited = new Set()) => {
if (!record || visited.has(record.name)) {
return null;
}
visited.add(record.name);
for (const parent of record.parents) {
if (parent.name === 'EnumAttr') {
return parent;
}
// Recursively search parent classes
const parentClass = this.parser.getClass(parent.name);
if (parentClass) {
const found = findEnumAttrParent(parentClass, visited);
if (found) {
return found;
}
}
}
return null;
};
const enumAttrParent = findEnumAttrParent(this);
// Pattern 0: Handle wrapper classes like OpenMP_EnumAttr, GPU_EnumAttr, etc.
// These are classes that inherit from EnumAttr and take the underlying enum as first arg.
// When we call findEnumAttrParent, it returns EnumAttr's template params (e.g., "enumInfo"),
// not the actual values. So we need to look at this.parents to find the wrapper class
// and extract the actual enum from its args.
for (const parent of this.parents) {
const parentClass = this.parser.getClass(parent.name);
if (parentClass && parentClass !== this) {
// Check if this parent class inherits from EnumAttr
const parentEnumAttr = findEnumAttrParent(parentClass);
if (parentEnumAttr && parent.args && parent.args.length >= 1) {
// The first arg of wrapper classes is typically the underlying enum
for (const arg of parent.args) {
if (arg.type === 'def' && typeof arg.value === 'string') {
const potentialEnum = this.parser.getDef(arg.value) || this.parser.getClass(arg.value);
if (potentialEnum && potentialEnum.isEnumAttr()) {
const cases = potentialEnum.getEnumCases();
if (cases && cases.length > 0) {
return cases;
}
}
}
}
}
}
}
// Pattern 1a: EnumAttr<Dialect, EnumInfo (as def), name>
// The 2nd argument is a reference to the underlying enum (e.g., GPU_Dimension)
if (enumAttrParent && enumAttrParent.args && enumAttrParent.args.length >= 2) {
const [dialectArg, enumInfoArg] = enumAttrParent.args;
if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
// Get the expected namespace from the dialect
let expectedNamespace = null;
if (dialectArg && dialectArg.type === 'def') {
const dialectDef = this.parser.getDef(dialectArg.value) || this.parser.getClass(dialectArg.value);
if (dialectDef) {
expectedNamespace = dialectDef.getValueAsString('cppNamespace');
}
}
// Find the enum with the matching name and namespace
// Try all defs with this name in case of conflicts (different namespaces)
const enumName = enumInfoArg.value;
let underlyingEnum = null;
if (expectedNamespace) {
// Normalize namespace for comparison (handle both "vector" and "::mlir::vector" formats)
const normalizeNamespace = (ns) => {
if (!ns) {
return null;
}
// Remove leading :: and extract the last component
const parts = ns.replace(/^::/, '').split('::');
return parts[parts.length - 1];
};
const normalizedExpected = normalizeNamespace(expectedNamespace);
// Search through all defs to find one with matching name AND namespace
for (const def of this.parser.defs) {
if (def.name === enumName) {
const defNamespace = def.getValueAsString('cppNamespace');
const normalizedDef = normalizeNamespace(defNamespace);
if (normalizedDef === normalizedExpected) {
underlyingEnum = def;
break;
}
}
}
}
// Fallback to regular lookup if namespace matching fails
if (!underlyingEnum) {
underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
}
if (underlyingEnum) {
return underlyingEnum.getEnumCases();
}
}
// Pattern 1b: EnumAttr<Dialect, EnumInfo (as DAG), name>
// The 2nd argument is a DAG that instantiates an enum class template
// e.g., EnumAttr<SPIRV_Dialect, SPIRV_I32Enum<"Scope", "desc", [cases]>, "scope">
if (enumInfoArg && enumInfoArg.type === 'dag' && enumInfoArg.value) {
// The DAG operator is the enum class template (e.g., SPIRV_I32Enum)
// We need to find the actual cases by looking at this record's parent args
// which should have the instantiated template parameters
// Search through this record's parents to find one with the cases list
for (const parent of this.parents) {
if (parent.args && parent.args.length >= 3) {
// Look for a list argument that contains enum case defs
for (const arg of parent.args) {
if (arg.type === 'list' && Array.isArray(arg.value)) {
// Check if this looks like an enum case list
const [firstItem] = arg.value;
if (firstItem && firstItem.type === 'def') {
// Try to extract cases from this list
const cases = [];
for (const caseValue of arg.value) {
if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
if (caseDef) {
const str = caseDef.getValueAsString('str');
if (str) {
cases.push(str);
}
}
}
}
if (cases.length > 0) {
return cases;
}
}
}
}
}
}
}
}
// Pattern 2: I64EnumAttr<name, summary, [cases]>
// Cases are in the 3rd template argument
for (const parent of this.parents) {
// The 3rd argument should be the cases list
if (parent.args && parent.args.length >= 3) {
const [,,casesArg] = parent.args;
if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
const cases = [];
for (const caseValue of casesArg.value) {
// Each case can be either a DAG or a def reference
if (caseValue.type === 'dag' && caseValue.value) {
// DAG format: I64EnumAttrCase<"symbol", value, "string">
// The string representation is in the 3rd operand, or 1st if only 2 operands
const operands = caseValue.value.operands;
if (operands && operands.length > 0) {
// Try the 3rd operand first (string), fall back to 1st (symbol)
const strOperand = operands.length >= 3 ? operands[2] : operands[0];
if (strOperand && strOperand.value) {
const str = this.evaluateValue(strOperand.value);
if (str && typeof str === 'string') {
cases.push(str);
}
}
}
} else if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
// Def reference format
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
if (caseDef) {
const str = caseDef.getValueAsString('str');
if (str) {
cases.push(str);
}
}
}
}
return cases.length > 0 ? cases : null;
}
}
}
// Pattern 3: LLVM_EnumAttr<name, cppType, summary, [cases]>
// Cases are in the 4th template argument
for (const parent of this.parents) {
if (parent.args && parent.args.length >= 4) {
const [,,,casesArg] = parent.args;
if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
const cases = [];
for (const caseValue of casesArg.value) {
// Each case can be either a DAG or a def reference
if (caseValue.type === 'dag' && caseValue.value) {
// DAG format: I32EnumAttrCase<"symbol", value>
// The first operand is the symbol name
const operands = caseValue.value.operands;
if (operands && operands.length > 0) {
const strOperand = operands[0];
if (strOperand && strOperand.value) {
const str = this.evaluateValue(strOperand.value);
if (str && typeof str === 'string') {
cases.push(str);
}
}
}
} else if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
if (caseDef) {
const str = caseDef.getValueAsString('str');
if (str) {
cases.push(str);
}
}
}
}
return cases.length > 0 ? cases : null;
}
}
}
return null;
}
evaluateValue(value, visited = new Set()) {
if (!value) {
return null;
}
// Handle named values (e.g., { name: 'clauses', value: { type: 'list', ... } })
// These come from named arguments in template instantiation
if (!value.type && value.name && value.value) {
return this.evaluateValue(value.value, visited);
}
switch (value.type) {
case 'string':
return value.value.replace(/^"|"$/g, '');
case 'code':
return value.value;
case 'int':
return parseInt(value.value, 10);
case 'bit':
return value.value === 'true' || value.value === '1';
case 'list':
return value.value.map((v) => this.evaluateValue(v, visited));
case 'concat': {
const parts = value.value.map((v) => this.evaluateValue(v, visited)).filter((v) => v !== null && v !== undefined && v !== '');
return parts.join('');
}
case 'id': {
const fieldName = value.value;
// Guard against circular references
const idKey = `id:${fieldName}`;
if (visited.has(idKey)) {
return null;
}
visited.add(idKey);
if (this.templateBindings.has(fieldName)) {
return this.evaluateValue(this.templateBindings.get(fieldName), visited);
}
const field = this.getValue(fieldName);
if (field && field.value) {
return this.evaluateValue(field.value, visited);
}
return null;
}
case 'def': {
const defName = typeof value.value === 'string' ? value.value : value.value.value;
// Handle TableGen builtin constants
if (defName === 'true') {
return true;
}
if (defName === 'false') {
return false;
}
// Guard against circular references
const defKey = `def:${defName}`;
if (visited.has(defKey)) {
return defName; // Return the def name itself to break the cycle
}
visited.add(defKey);
if (defName.includes('.')) {
const parts = defName.split('.');
const [baseName, ...fieldPath] = parts;
let baseValue = null;
if (this.templateBindings.has(baseName)) {
baseValue = this.evaluateValue(this.templateBindings.get(baseName), visited);
} else {
const field = this.getValue(baseName);
if (field && field.value) {
baseValue = this.evaluateValue(field.value, visited);
}
}
if (typeof baseValue === 'string') {
const def = this.parser.getDef(baseValue) || this.parser.getClass(baseValue);
if (def) {
let current = def;
for (const fieldName of fieldPath) {
const field = current.getValue(fieldName);
if (!field || !field.value) {
return null;
}
const evaluated = current.evaluateValue(field.value, visited);
// If it's another def name, continue navigation
if (typeof evaluated === 'string' && (this.parser.getDef(evaluated) || this.parser.getClass(evaluated))) {
current = this.parser.getDef(evaluated) || this.parser.getClass(evaluated);
} else {
return evaluated;
}
}
return null;
}
}
// Handle DAG values representing class instantiations with template args
// e.g., OpenMP_MapClauseSkip<assemblyFormat = true> is stored as a DAG
if (baseValue && typeof baseValue === 'object' && baseValue.operator) {
const className = baseValue.operator;
const baseClass = this.parser.getClass(className);
if (baseClass) {
const templateArgs = baseValue.operands ? baseValue.operands.map((op) => {
if (op.name && op.value) {
return op;
}
return op.value || op;
}) : [];
const instantiated = new tablegen.Record(`<anonymous ${className}>`, this.parser);
instantiated.parents = [{ name: className, args: templateArgs }];
this.parser.addSubClass(instantiated);
instantiated.resolveReferences();
let current = instantiated;
for (const fieldName of fieldPath) {
const field = current.getValue(fieldName);
if (!field || !field.value) {
return null;
}
const evaluated = current.evaluateValue(field.value, visited);
if (typeof evaluated === 'string' && (this.parser.getDef(evaluated) || this.parser.getClass(evaluated))) {
current = this.parser.getDef(evaluated) || this.parser.getClass(evaluated);
} else {
return evaluated;
}
}
return null;
}
}
return null;
}
if (this.templateBindings.has(defName)) {
return this.evaluateValue(this.templateBindings.get(defName), visited);
}
const field = this.getValue(defName);
if (field && field.value) {
return this.evaluateValue(field.value, visited);
}
const def = this.parser.getDef(defName) || this.parser.getClass(defName);
return def ? defName : null;
}
case 'bang': {
const { op, args } = value.value;
switch (op) {
case 'if': {
if (args.length < 2) {
return null;
}
const condition = this.evaluateValue(args[0], visited);
if (condition) {
return args.length > 1 ? this.evaluateValue(args[1], visited) : null;
}
return args.length > 2 ? this.evaluateValue(args[2], visited) : null;
}
case 'empty': {
if (args.length < 1) {
return true;
}
const val = this.evaluateValue(args[0], visited);
if (Array.isArray(val)) {
return val.length === 0;
}
if (typeof val === 'string') {
return val.length === 0;
}
return val === null || val === undefined;
}
case 'interleave': {
if (args.length < 2) {
return '';
}
const list = this.evaluateValue(args[0], visited);
const separator = this.evaluateValue(args[1], visited);
if (!Array.isArray(list)) {
return '';
}
return list.filter((x) => x !== null && x !== '').join(separator || '');
}
case 'not':
if (args.length < 1) {
return true;
}
return !this.evaluateValue(args[0], visited);
case 'or':
for (const arg of args) {
if (this.evaluateValue(arg, visited)) {
return true;
}
}
return false;
case 'and':
for (const arg of args) {
if (!this.evaluateValue(arg, visited)) {
return false;
}
}
return true;
case 'foldl': {
// !foldl(init, list, acc, item, expr)
// Fold left: iterate over list, accumulating results
if (args.length < 5) {
return null;
}
let accumulator = this.evaluateValue(args[0], visited); // init value
const list = this.evaluateValue(args[1], visited); // list to fold over
// args[2] is the accumulator variable name (not evaluated)
// args[3] is the item variable name (not evaluated)
// args[4] is the expression to evaluate
if (!Array.isArray(list)) {
return accumulator;
}
for (const item of list) {
const accName = args[2].value; // variable name
const itemName = args[3].value; // variable name
const prevAcc = this.fields.get(accName);
const prevItem = this.fields.get(itemName);
let accValue = null;
if (accumulator && typeof accumulator === 'object' && accumulator.operator) {
// It's a DAG object
accValue = new tablegen.Value('dag', accumulator);
} else if (Array.isArray(accumulator)) {
// It's a list
accValue = new tablegen.Value('list', accumulator);
} else if (typeof accumulator === 'string') {
accValue = new tablegen.Value('string', accumulator);
} else if (typeof accumulator === 'number') {
accValue = new tablegen.Value('int', accumulator);
} else {
accValue = accumulator;
}
this.fields.set(accName, new tablegen.RecordVal(accName, null, accValue));
let itemValue = item;
if (typeof item === 'string') {
itemValue = new tablegen.Value('def', item);
} else if (typeof item === 'number') {
itemValue = new tablegen.Value('int', item);
} else if (typeof item === 'boolean') {
itemValue = new tablegen.Value('bit', item ? '1' : '0');
} else if (item && typeof item === 'object' && !item.type) {
itemValue = new tablegen.Value('dag', item);
}
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
accumulator = this.evaluateValue(args[4], new Set()); // Fresh visited set for each iteration
if (prevAcc) {
this.fields.set(accName, prevAcc);
} else {
this.fields.delete(accName);
}
if (prevItem) {
this.fields.set(itemName, prevItem);
} else {
this.fields.delete(itemName);
}
}
return accumulator;
}
case 'foreach': {
// !foreach(item, list, expr)
// Map: iterate over list, transforming each item
if (args.length < 3) {
return [];
}
const itemName = args[0].value;
const list = this.evaluateValue(args[1], visited);
const results = [];
if (Array.isArray(list)) {
for (const item of list) {
const prevItem = this.fields.get(itemName);
let itemValue = item;
if (typeof item === 'string') {
itemValue = new tablegen.Value('def', item);
} else if (typeof item === 'number') {
itemValue = new tablegen.Value('int', item);
} else if (typeof item === 'boolean') {
itemValue = new tablegen.Value('bit', item ? '1' : '0');
} else if (item && typeof item === 'object' && !item.type) {
itemValue = new tablegen.Value('dag', item);
}
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
const result = this.evaluateValue(args[2], new Set()); // Fresh visited set for each iteration
results.push(result);
if (prevItem) {
this.fields.set(itemName, prevItem);
} else {
this.fields.delete(itemName);
}
}
}
return results;
}
case 'filter': {
// !filter(item, list, predicate)
// Filter: keep items where predicate is true
if (args.length < 3) {
return [];
}
const itemName = args[0].value;
const list = this.evaluateValue(args[1], visited);
const results = [];
if (Array.isArray(list)) {
for (const item of list) {
const prevItem = this.fields.get(itemName);
// Wrap item in a Value so it can be used in expressions
let itemValue = item;
if (typeof item === 'string') {
// If it's a def name, wrap it as a 'def' Value
itemValue = new tablegen.Value('def', item);
} else if (typeof item === 'number') {
itemValue = new tablegen.Value('int', item);
} else if (typeof item === 'boolean') {
itemValue = new tablegen.Value('bit', item ? '1' : '0');
} else if (item && typeof item === 'object' && !item.type) {
// If it's a raw object (like a DAG), wrap it
itemValue = new tablegen.Value('dag', item);
}
// If item is already a Value, use it as is
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
const keep = this.evaluateValue(args[2], new Set()); // Fresh visited set for each iteration
if (keep) {
results.push(item);
}
if (prevItem) {
this.fields.set(itemName, prevItem);
} else {
this.fields.delete(itemName);
}
}
}
return results;
}
case 'con': {
// !con(dag1, dag2, ...)
// Concatenate dags - merge operands from multiple dags
if (args.length === 0) {
return new tablegen.DAG('ins', []);
}
let operator = 'ins';
const allOperands = [];
for (const arg of args) {
let dagToProcess = null;
const evaluated = this.evaluateValue(arg, visited);
if (evaluated && typeof evaluated === 'object') {
if (evaluated.operator && evaluated.operands) {
dagToProcess = evaluated;
} else if (evaluated.type === 'dag' && evaluated.value) {
dagToProcess = evaluated.value;
}
}
if (!dagToProcess && arg.type === 'dag') {
dagToProcess = arg.value;
}
if (dagToProcess && dagToProcess.operands) {
if (operator === 'ins' && dagToProcess.operator) {
operator = dagToProcess.operator;
}
allOperands.push(...dagToProcess.operands);
}
}
return new tablegen.DAG(operator, allOperands);
}
case 'listconcat': {
// !listconcat(list1, list2, ...)
// Concatenate multiple lists into one
const result = [];
for (const arg of args) {
const list = this.evaluateValue(arg, visited);
if (Array.isArray(list)) {
result.push(...list);
}
}
return result;
}
case 'listremove': {
// !listremove(list, items_to_remove)
// Remove all occurrences of items_to_remove from list
if (args.length < 2) {
return [];
}
const list = this.evaluateValue(args[0], visited);
const toRemove = this.evaluateValue(args[1], visited);
if (!Array.isArray(list)) {
return [];
}
const removeSet = new Set();
if (Array.isArray(toRemove)) {
for (const item of toRemove) {
removeSet.add(JSON.stringify(item));
}
} else {
removeSet.add(JSON.stringify(toRemove));
}
return list.filter((item) => !removeSet.has(JSON.stringify(item)));
}
case 'cast': {
// !cast<type>(value)
// Cast value to type - for now just convert to string
if (args.length < 1) {
return null;
}
const val = this.evaluateValue(args[0], visited);
if (val === null || val === undefined) {
return null;
}
return String(val);
}
case 'eq': {
// !eq(a, b)
// Return true if a equals b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a === b;
}
case 'ne': {
// !ne(a, b)
// Return true if a not equals b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a !== b;
}
case 'lt': {
// !lt(a, b)
// Return true if a < b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a < b;
}
case 'le': {
// !le(a, b)
// Return true if a <= b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a <= b;
}
case 'gt': {
// !gt(a, b)
// Return true if a > b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a > b;
}
case 'ge': {
// !ge(a, b)
// Return true if a >= b
if (args.length < 2) {
return false;
}
const a = this.evaluateValue(args[0], visited);
const b = this.evaluateValue(args[1], visited);
return a >= b;
}
case 'range': {
// !range(n) or !range(start, end) or !range(start, end, step)
// Generate a list of integers
if (args.length === 0) {
return [];
}
let start = 0;
let end = 0;
let step = 1;
if (args.length === 1) {
end = this.evaluateValue(args[0], visited);
} else if (args.length === 2) {
start = this.evaluateValue(args[0], visited);
end = this.evaluateValue(args[1], visited);
} else {
start = this.evaluateValue(args[0], visited);
end = this.evaluateValue(args[1], visited);
step = this.evaluateValue(args[2], visited);
}
const result = [];
if (step > 0) {
for (let i = start; i < end; i += step) {
result.push(i);
}
} else if (step < 0) {
for (let i = start; i > end; i += step) {
result.push(i);
}
}
return result;
}
case 'listsplat': {
// !listsplat(element, n)
// Create a list with n copies of element
if (args.length < 2) {
return [];
}
const [element] = args; // Don't evaluate yet, keep as Value
const count = this.evaluateValue(args[1], visited);
const result = [];
for (let i = 0; i < count; i++) {
result.push(element);
}
return result;
}
case 'cond': {
// !cond(condition1: value1, condition2: value2, ..., true: defaultValue)
// Evaluate conditions in order, return first matching value
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.condition) {
const condition = this.evaluateValue(arg.condition, visited);
if (condition === true || condition === 1) {
return this.evaluateValue(arg.value, visited);
}
}
}
return null;
}
case 'dag': {
// !dag(operator, operands_list, names_list)
// Construct a DAG from operator, operands, and names
if (args.length < 2) {
return new tablegen.DAG('ins', []);
}
const operatorArg = this.evaluateValue(args[0], visited);
const operator = typeof operatorArg === 'string' ? operatorArg : 'ins';
const operandsList = this.evaluateValue(args[1], visited);
const namesList = args.length > 2 ? this.evaluateValue(args[2], visited) : [];
const operands = [];
if (Array.isArray(operandsList)) {
for (let i = 0; i < operandsList.length; i++) {
const value = operandsList[i];
const name = Array.isArray(namesList) && i < namesList.length ? namesList[i] : '';
operands.push({ value, name });
}
}
return new tablegen.DAG(operator, operands);
}
case 'tolower': {
// !tolower(string)
// Convert string to lowercase
if (args.length < 1) {
return null;
}
const str = this.evaluateValue(args[0], visited);
if (typeof str === 'string') {
return str.toLowerCase();
}
return null;
}
case 'toupper': {
// !toupper(string)
// Convert string to uppercase
if (args.length < 1) {
return null;
}
const str = this.evaluateValue(args[0], visited);
if (typeof str === 'string') {
return str.toUpperCase();
}
return null;
}
case 'strconcat': {
// !strconcat(str1, str2, ...)
// Concatenate strings
const parts = [];
for (const arg of args) {
const val = this.evaluateValue(arg, visited);
if (val !== null && val !== undefined) {
parts.push(String(val));
}
}
return parts.join('');
}
case 'subst': {
// !subst(pattern, replacement, string)
// Replace all occurrences of pattern with replacement in string
if (args.length < 3) {
return null;
}
const pattern = this.evaluateValue(args[0], visited);
const replacement = this.evaluateValue(args[1], visited);
const str = this.evaluateValue(args[2], visited);
if (typeof str === 'string' && typeof pattern === 'string') {
const rep = replacement !== null && replacement !== undefined ? String(replacement) : '';
// Use split/join for global replacement
return str.split(pattern).join(rep);
}
return str;
}
default:
return null;
}
}
case 'dag': {
const dag = value.value;
const evaluatedOperands = dag.operands.map((operand) => {
if (!operand.value) {
return operand;
}
const valType = operand.value.type;
if (valType === 'def' || valType === 'id') {
const refName = operand.value.value;
if (this.templateBindings.has(refName)) {
const evaluated = this.evaluateValue(operand.value, visited);
if (typeof evaluated === 'string') {
return {
value: { type: 'def', value: evaluated },
name: operand.name
};
}
}
}
return operand;
});
return new tablegen.DAG(dag.operator, evaluatedOperands);
}
case 'uninitialized':
return null;
default:
return null;
}
}
};
tablegen.Reader = class {
constructor() {
this._paths = [];
this._includes = new Set();
this._defs = new Map();
this._defvars = new Map();
this.defs = [];
this.classes = new Map();
this.multiclasses = new Map();
}
async parse(files, paths) {
this._paths = paths || [];
for (const file of files) {
// eslint-disable-next-line no-await-in-loop
await this._parseFile(file);
}
}
getDef(name) {
// Check if this is a defvar alias and resolve it
if (this._defvars.has(name)) {
const alias = this._defvars.get(name);
if (alias && alias.type === 'def' && typeof alias.value === 'string') {
return this.getDef(alias.value) || this._defs.get(name);
}
}
return this._defs.get(name);
}
getClass(name) {
// Check if this is a defvar alias and resolve it
if (this._defvars.has(name)) {
const alias = this._defvars.get(name);
if (alias && alias.type === 'def' && typeof alias.value === 'string') {
return this.getClass(alias.value) || this.classes.get(name);
}
}
return this.classes.get(name);
}
// Add a subclass to a record, processing template parameters and copying fields
// This mirrors LLVM's TGParser::AddSubClass behavior
addSubClass(record) {
// Track which fields were explicitly defined in the def via 'let' statements
// These should never be overwritten by parent class fields
const explicitFields = new Set(record.fields.keys());
// 'let append'/'let prepend' fields concatenate with the inherited value rather
// than replacing it. Pull them out so the parent value resolves normally, then
// concatenate the child value against the fully inherited result below.
const concatFields = [];
for (const [fieldName, field] of record.fields) {
if (field.concat) {
concatFields.push({ name: fieldName, mode: field.concat, value: field.value });
explicitFields.delete(fieldName);
record.fields.delete(fieldName);
}
}
// Step 1: Build initial template bindings for this record from its immediate parents
const recordBindings = new Map();
for (const parent of record.parents) {
const parentClass = this.classes.get(parent.name);
if (parentClass && parentClass.templateArgs && parentClass.templateArgs.length > 0) {
const templateArgs = parent.args || [];
for (let i = 0; i < parentClass.templateArgs.length; i++) {
const param = parentClass.templateArgs[i];
let boundValue = null;
// Check for named argument, positional argument, or default value
const namedArg = templateArgs.find((arg) => arg.name === param.name);
if (namedArg) {
boundValue = namedArg.value;
} else if (i < templateArgs.length) {
const arg = templateArgs[i];
boundValue = arg.name ? arg.value : arg;
} else if (param.defaultValue) {
boundValue = param.defaultValue;
}
if (boundValue) {
recordBindings.set(param.name, boundValue);
}
}
}
}
record.templateBindings = recordBindings;
// Step 2: Process parents and flatten fields
// Helper to resolve Init values using a set of bindings
// Uses record._resolveInit to avoid code duplication
const resolveInitValue = (value, bindings) => {
const resolver = {
shouldCopy: false,
resolveReference: (name) => bindings.get(name) || null
};
return record._resolveInit(value, resolver);
};
const processParent = (parent, currentBindings, visited = new Set()) => {
if (visited.has(parent.name)) {
return;
}
visited.add(parent.name);
const parentClass = this.classes.get(parent.name);
if (!parentClass) {
return;
}
const parentBindings = new Map();
if (parentClass.templateArgs && parent.args) {
// Handle named and positional arguments correctly
// Named arguments have {name, value} format and should match by name
// Positional arguments should fill remaining slots in order
const templateArgs = parent.args;
for (let i = 0; i < parentClass.templateArgs.length; i++) {
const param = parentClass.templateArgs[i];
let boundValue = null;
// Check for named argument first
const namedArg = templateArgs.find((arg) => arg.name === param.name);
if (namedArg) {
boundValue = namedArg.value;
} else if (i < templateArgs.length) {
// Positional argument - use if it's not a named arg for another param
const arg = templateArgs[i];
boundValue = arg.name ? arg.value : arg;
} else if (param.defaultValue) {
boundValue = param.defaultValue;
}
if (boundValue) {
// When resolving default values, also check parentBindings for previously bound template params
// This handles cases like: class Foo<string sym, string str = sym>
// where 'str' default value references 'sym' which was bound earlier
const combinedBindings = new Map([...currentBindings, ...parentBindings]);
const resolvedArg = resolveInitValue(boundValue, combinedBindings);
parentBindings.set(param.name, resolvedArg);
}
}
}
for (const grandparent of parentClass.parents) {
processParent(grandparent, parentBindings, visited);
}
for (const [fieldName, field] of parentClass.fields) {
// Only protect fields that were explicitly defined in the def via 'let'
// Fields inherited from grandparents should be overwritten by parent class fields
if (explicitFields.has(fieldName)) {
// Check if the explicit field is empty/uninitialized - if so, copy from parent
const existingField = record.fields.get(fieldName);
const existingIsUninit = existingField.value && existingField.value.type === 'uninitialized';
const existingIsEmptyDag = existingField.value && existingField.value.type === 'dag' && existingField.value.value && existingField.value.value.operands && existingField.value.value.operands.length === 0;
const existingIsEmptyString = existingField.value && existingField.value.type === 'string' && existingField.value.value === '';
const existingIsFalseBit = existingField.value && existingField.value.type === 'int' && existingField.value.value === 0 && existingField.type && existingField.type.name === 'bit';
if (existingIsUninit || existingIsEmptyDag || existingIsEmptyString || existingIsFalseBit) {
const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
record.fields.set(fieldName, resolvedField);
}
// Otherwise, keep the explicit field (child's 'let' wins)
} else {
// Field is not explicit - copy from parent (overwrites grandparent values)
const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
record.fields.set(fieldName, resolvedField);
}
}
};
for (const parent of record.parents) {
processParent(parent, recordBindings, new Set());
}
// Concatenate 'let append'/'let prepend' values against the inherited value.
for (const { name, mode, value } of concatFields) {
const inherited = record.fields.get(name);
if (inherited && inherited.value) {
const parts = mode === 'prepend' ?
[value, inherited.value] : [inherited.value, value];
inherited.value = new tablegen.Value('concat', parts);
} else {
record.fields.set(name, new tablegen.RecordVal(name, null, value));
}
}
}
async access(file) {
try {
await fs.access(file);
return true;
} catch {
// continue regardless of error
}
return false;
}
async _parseFile(file) {
let location = null;
for (const current of this._paths) {
const test = path.join(current, file);
// eslint-disable-next-line no-await-in-loop
if (await this.access(test)) {
location = path.resolve(test);
break;
}
}
if (!location) {
throw new tablegen.Error(`File not found '${file}'.`);
}
if (!this._includes.has(location)) {
this._includes.add(location);
const content = await fs.readFile(location, 'utf-8');
this._tokenizer = new tablegen.Tokenizer(content, location);
while (!this._match('eof')) {
const token = this._tokenizer.current();
if (token.type === 'keyword') {
switch (token.value) {
case 'include':
// eslint-disable-next-line no-await-in-loop
await this._parseInclude();
break;
case 'class': this._parseClass(); break;
case 'def': this._parseDef(); break;
case 'defm': this._parseDefm(); break;
case 'let': this._parseLet(); break;
case 'multiclass': this._parseMulticlass(); break;
case 'defvar': this._parseDefvar(); break;
case 'defset': this._parseDefset(); break;
case 'foreach': this._parseForeach(); break;
default:
this._read();
break;
}
} else {
this._read();
}
}
}
}
async _parseInclude() {
this._read();
const file = this._expect('string');
const tokenizer = this._tokenizer;
await this._parseFile(file);
this._tokenizer = tokenizer;
}
_parseClass() {
this._read();
let name = null;
if (this._match('id')) {
name = this._expect('id');
} else if (this._match('number')) {
name = String(this._expect('number'));
} else {
throw new tablegen.Error(`Expected class name but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
}
const record = new tablegen.Record(name, this);
record.location = this._tokenizer.location();
if (this._match('<')) {
record.templateArgs = this._parseTemplateParams();
}
if (this._match(':')) {
record.parents = this._parseParentClassList();
}
// Don't process parent classes for class definitions - only for defs/instances
// Classes are templates that get instantiated later with concrete template arguments
// Processing parents here with unbound template parameters causes incorrect resolutions
// this.addSubClass(record);
if (this._match('{')) {
this._parseRecordBody(record);
}
this.classes.set(name, record);
}
_parseDef() {
this._read();
let name = '';
if (this._match('id')) {
name = this._read();
} else if (this._match('number')) {
name = this._read().toString();
}
const def = new tablegen.Record(name, this);
def.location = this._tokenizer.location();
if (this._match(':')) {
def.parents = this._parseParentClassList();
}
if (this._match('{')) {
this._parseRecordBody(def);
}
this.addSubClass(def);
def.resolveReferences();
if (name) {
this._defs.set(name, def);
this.defs.push(def);
}
}
_parseDefm() {
this._read(); // consume 'defm'
// Parse the defm name prefix (may be empty or a concatenated name)
const namePrefix = [];
while (!this._match(':') && !this._match(';') && !this._match('eof')) {
if (this._match('id')) {
const value = this._read();
if (this._eat('.')) {
const field = this._expect('id');
namePrefix.push({ type: 'field_access', base: value, field });
} else {
namePrefix.push({ type: 'id', value });
}
} else if (this._match('string')) {
namePrefix.push({ type: 'string', value: this._read() });
} else if (this._match('number')) {
namePrefix.push({ type: 'number', value: this._read() });
} else if (this._eat('#')) {
namePrefix.push({ type: 'concat' });
} else if (this._match('!')) {
const bangValue = this._parseValue();
if (bangValue && bangValue.type === 'bang') {
namePrefix.push({ type: 'bang', value: bangValue.value });
}
} else {
break;
}
}
// Parse the parent multiclass references
let parents = [];
if (this._match(':')) {
parents = this._parseParentClassList();
}
this._eat(';');
// Instantiate each parent multiclass
for (const parent of parents) {
this._instantiateMulticlass(parent, namePrefix, new Map());
}
}
_instantiateMulticlass(parent, namePrefix, substitutions) {
const multiclass = this.multiclasses.get(parent.name);
if (!multiclass) {
// Might be a regular class, not a multiclass - skip
return;
}
// Build template argument bindings
const bindings = new Map(substitutions);
if (multiclass.templateArgs && parent.args) {
for (let i = 0; i < multiclass.templateArgs.length && i < parent.args.length; i++) {
const param = multiclass.templateArgs[i];
const arg = parent.args[i];
// Handle both {name, value} format (named arg) and tablegen.Value format (positional arg)
// For named args like {name: 'foo', value: <Value>}, use the value property
// For positional args that are already Values, use them directly
let argValue = null;
if (arg && arg.name && arg.value !== undefined) {
// Named argument: {name: 'foo', value: <Value>}
argValue = arg.value;
} else if (arg && arg.type !== undefined) {
// This is a tablegen.Value object, use it directly
argValue = arg;
} else {
argValue = arg;
}
bindings.set(param.name, argValue);
}
}
// Process inherited parent multiclasses first
for (const mcParent of multiclass.parents) {
const resolvedParent = this._resolveMulticlassParent(mcParent, bindings);
this._instantiateMulticlass(resolvedParent, namePrefix, bindings);
}
// Process entries
for (const entry of multiclass.entries) {
if (entry.type === 'def') {
this._instantiateMulticlassDef(entry.data, namePrefix, bindings);
} else if (entry.type === 'defm') {
// Nested defm - instantiate the referenced multiclass(es)
for (const defmParent of entry.data.parents) {
const resolvedParent = this._resolveMulticlassParent(defmParent, bindings);
const combinedPrefix = [...namePrefix, ...entry.data.nameTemplate];
this._instantiateMulticlass(resolvedParent, combinedPrefix, bindings);
}
} else if (entry.type === 'foreach') {
this._resolveMulticlassForeach(entry.data, namePrefix, bindings);
} else if (entry.type === 'defvar') {
const value = this._evaluateDefvar(entry.data.value, bindings);
bindings.set(entry.data.name, value);
}
}
}
_resolveMulticlassParent(parent, bindings) {
// Resolve template arguments in parent reference
const resolvedArgs = [];
if (parent.args) {
for (const arg of parent.args) {
const argValue = arg.value === undefined ? arg : arg.value;
if (argValue && argValue.type === 'def' && bindings.has(argValue.value)) {
resolvedArgs.push({ value: bindings.get(argValue.value) });
} else {
resolvedArgs.push(arg);
}
}
}
return { name: parent.name, args: resolvedArgs };
}
_resolveMulticlassForeach(loop, namePrefix, bindings) {
if (loop.isConditional) {
const conditionResult = this._evaluateCondition(loop.condition, bindings);
if (conditionResult === false || conditionResult === null) {
return;
}
for (const entry of loop.entries) {
if (entry.type === 'def') {
this._instantiateMulticlassDef(entry.data, namePrefix, bindings);
} else if (entry.type === 'foreach') {
this._resolveMulticlassForeach(entry.data, namePrefix, bindings);
} else if (entry.type === 'defvar') {
const value = this._evaluateDefvar(entry.data.value, bindings);
bindings.set(entry.data.name, value);
}
}
return;
}
if (!loop.listValue || loop.listValue.length === 0) {
return;
}
for (const listItem of loop.listValue) {
const currentBindings = new Map(bindings);
if (loop.iterVarName) {
currentBindings.set(loop.iterVarName, listItem);
}
for (const entry of loop.entries) {
if (entry.type === 'def') {
this._instantiateMulticlassDef(entry.data, namePrefix, currentBindings);
} else if (entry.type === 'foreach') {
this._resolveMulticlassForeach(entry.data, namePrefix, currentBindings);
} else if (entry.type === 'defvar') {
const value = this._evaluateDefvar(entry.data.value, currentBindings);
currentBindings.set(entry.data.name, value);
}
}
}
}
_resolveTemplateValue(value, bindings) {
if (!value) {
return value;
}
if (value.type === 'string' || value.type === 'int' || value.type === 'code') {
return value;
}
if (value.type === 'def' || value.type === 'id') {
const name = typeof value.value === 'string' ? value.value : (value.value && value.value.value);
if (name && bindings.has(name)) {
return bindings.get(name);
}
return value;
}
if (value.type === 'bang') {
const resolved = this._evaluateDefvar(value, bindings);
return resolved;
}
if (value.type === 'list' && Array.isArray(value.value)) {
return new tablegen.Value('list', value.value.map((v) => this._resolveTemplateValue(v, bindings)));
}
return value;
}
_instantiateMulticlassDef(template, namePrefix, bindings) {
// Build the full name from prefix + template name
const fullNameTemplate = [...namePrefix, ...template.nameTemplate];
// Resolve name template to get the actual def name
let name = '';
for (const part of fullNameTemplate) {
if (part.type === 'concat') {
continue;
}
if (part.type === 'string') {
name += String(part.value).replace(/^"|"$/g, '');
} else if (part.type === 'number') {
name += String(part.value);
} else if (part.type === 'id') {
if (bindings.has(part.value)) {
const resolved = bindings.get(part.value);
if (resolved && resolved.type === 'string') {
name += String(resolved.value).replace(/^"|"$/g, '');
} else if (resolved && resolved.type === 'int') {
name += String(resolved.value);
} else if (typeof resolved === 'string') {
name += resolved;
} else {
name += part.value;
}
} else {
name += part.value;
}
} else if (part.type === 'field_access') {
const baseName = part.base;
if (bindings.has(baseName)) {
const baseValue = bindings.get(baseName);
if (baseValue && baseValue.type === 'record_instance' && baseValue.value && baseValue.value.fields) {
const fieldValue = baseValue.value.fields.get(part.field);
if (fieldValue && fieldValue.type === 'string') {
name += String(fieldValue.value).replace(/^"|"$/g, '');
}
}
}
} else if (part.type === 'bang') {
const evaluated = this._evaluateDefvar({ type: 'bang', value: part.value }, bindings);
if (evaluated && evaluated.type === 'string') {
name += String(evaluated.value).replace(/^"|"$/g, '');
}
}
}
// Skip if already defined (avoid duplicates)
if (this._defs.has(name)) {
return;
}
// Create the def record
const def = new tablegen.Record(name, this);
def.location = this._tokenizer ? this._tokenizer.location() : null;
// Store template bindings in the def for later resolution
def.templateBindings = new Map(bindings);
// Resolve parent classes with template bindings
const resolvedParents = [];
for (const parent of template.parents) {
const resolvedArgs = [];
if (parent.args) {
for (const arg of parent.args) {
// arg could be:
// - A tablegen.Value object: {type: '...', value: ...}
// - A named arg: {name: '...', value: <Value>}
let argValue = null;
let argName = null;
if (arg && arg.type !== undefined) {
// This is a tablegen.Value, use it directly
argValue = arg;
argName = null;
} else if (arg && arg.name && arg.value !== undefined) {
// This is a named arg, extract the value
argValue = arg.value;
argName = arg.name;
} else {
argValue = arg;
argName = null;
}
// Resolve the argument value with template bindings
const resolved = this._resolveTemplateValue(argValue, bindings);
// Push in format expected by addSubClass: named args as {name, value}, others as direct Value
if (argName) {
resolvedArgs.push({ name: argName, value: resolved });
} else {
resolvedArgs.push(resolved);
}
}
}
resolvedParents.push({ name: parent.name, args: resolvedArgs });
}
def.parents = resolvedParents;
// Copy body fields and resolve them
if (template.bodyFields) {
for (const [fieldName, field] of template.bodyFields) {
const resolvedValue = this._resolveTemplateValue(field.value, bindings);
def.fields.set(fieldName, new tablegen.RecordVal(fieldName, field.type, resolvedValue));
}
}
this.addSubClass(def);
def.resolveReferences();
if (name) {
this._defs.set(name, def);
this.defs.push(def);
}
}
_parseMulticlass() {
this._read(); // consume 'multiclass'
const name = this._expect('id');
const multiclass = {
name,
templateArgs: [],
parents: [],
entries: []
};
// Parse template parameters
if (this._match('<')) {
multiclass.templateArgs = this._parseTemplateParams();
}
// Parse parent multiclass references
if (this._match(':')) {
multiclass.parents = this._parseParentClassList();
}
// Parse multiclass body
if (this._eat('{')) {
while (!this._match('}') && !this._match('eof')) {
const token = this._tokenizer.current();
if (token.type === 'keyword') {
switch (token.value) {
case 'def':
multiclass.entries.push({ type: 'def', data: this._parseDefTemplate() });
break;
case 'defm':
multiclass.entries.push({ type: 'defm', data: this._parseDefmTemplate() });
break;
case 'foreach':
multiclass.entries.push({ type: 'foreach', data: this._parseForeachTemplate() });
break;
case 'if':
multiclass.entries.push({ type: 'foreach', data: this._parseIfAsLoop() });
break;
case 'let':
this._parseLet();
break;
case 'defvar':
multiclass.entries.push({ type: 'defvar', data: this._parseDefvarTemplate() });
break;
default:
this._read();
break;
}
} else {
this._read();
}
}
this._expect('}');
}
this.multiclasses.set(name, multiclass);
}
_parseDefmTemplate() {
this._read(); // consume 'defm'
const nameTemplate = [];
// Parse name template (similar to _parseDefTemplate but for nested defm)
while (!this._match(':') && !this._match(';') && !this._match('eof')) {
if (this._match('id')) {
const value = this._read();
if (this._eat('.')) {
const field = this._expect('id');
nameTemplate.push({ type: 'field_access', base: value, field });
} else {
nameTemplate.push({ type: 'id', value });
}
} else if (this._match('string')) {
nameTemplate.push({ type: 'string', value: this._read() });
} else if (this._match('number')) {
nameTemplate.push({ type: 'number', value: this._read() });
} else if (this._eat('#')) {
nameTemplate.push({ type: 'concat' });
} else if (this._match('!')) {
const bangValue = this._parseValue();
if (bangValue && bangValue.type === 'bang') {
nameTemplate.push({ type: 'bang', value: bangValue.value });
}
} else {
break;
}
}
let parents = [];
if (this._match(':')) {
parents = this._parseParentClassList();
}
this._eat(';');
return { nameTemplate, parents };
}
_parseLet() {
this._read(); // consume 'let'
// Skip until we find 'in' keyword, '{', or ';'
while (!this._match('eof') && !this._match('keyword', 'in') && !this._match('{') && !this._match(';')) {
this._read();
}
if (this._match('keyword', 'in')) {
this._read(); // consume 'in'
// After 'in', we can have:
// - '{' for a block of statements
// - ';' for no body
// - A statement like 'def', 'defm', etc.
if (this._match('{')) {
this._read();
// Parse the contents of the let block - don't skip them!
// This handles 'let opDocGroup = ... in { def X, def Y, ... }'
this._parseLetBlockBody();
this._expect('}');
} else if (this._eat(';')) {
// Just a let declaration, no body
}
// Otherwise, fall through to let the main loop handle the next statement
// (e.g., 'let ... in def X' - the def will be parsed by the main loop)
} else {
this._eat('{');
this._eat(';');
}
}
_parseLetBlockBody() {
// Parse statements inside a 'let ... in { ... }' block
while (!this._match('}') && !this._match('eof')) {
const token = this._tokenizer.current();
if (token.type === 'keyword') {
switch (token.value) {
case 'def': this._parseDef(); break;
case 'defm': this._parseDefm(); break;
case 'let': this._parseLet(); break;
case 'multiclass': this._parseMulticlass(); break;
case 'defvar': this._parseDefvar(); break;
case 'defset': this._parseDefset(); break;
case 'foreach': this._parseForeach(); break;
case 'class': this._parseClass(); break;
default:
this._read();
break;
}
} else {
this._read();
}
}
}
_parseDefvar() {
this._read(); // consume 'defvar'
const name = this._expect('id'); // variable name
this._expect('=');
// Parse and store the value - needed for resolving type aliases like
// defvar Util_GlobalRefAttr = FlatSymbolRefAttr;
const value = this._parseValue();
this._expect(';');
// Store defvar for alias resolution (e.g., type constraint lookups)
if (name && value) {
this._defvars.set(name, value);
}
}
_parseDefset() {
this._read();
let depth = 0;
while (!this._match('eof')) {
if (this._eat('{')) {
depth++;
} else if (this._eat('}')) {
depth--;
if (depth === 0) {
break;
}
} else {
this._read();
}
}
}
_parseForeach() {
const location = this._tokenizer.location();
this._read();
const iterVarName = this._expect('id');
this._expect('=');
const listValue = this._parseForeachListValue();
this._expect('keyword', 'in');
const loop = { location, iterVarName, listValue, entries: [] };
if (this._match('{')) {
this._read();
this._parseForeachBody(loop);
this._expect('}');
} else {
this._parseForeachBodyStatement(loop);
}
this._resolveForeachLoop(loop, new Map());
}
_parseForeachListValue() {
const values = [];
if (this._eat('[')) {
while (!this._match(']') && !this._match('eof')) {
const value = this._parseListItem();
if (value && value.type === 'dag') {
const instantiated = this._instantiateClassTemplate(value.value);
if (instantiated) {
values.push(instantiated);
} else {
values.push(value);
}
} else {
values.push(value);
}
this._eat(',');
}
this._expect(']');
} else if (this._eat('!')) {
const op = this._expect('id');
if (op === 'range' && this._eat('(')) {
const args = [];
while (!this._match(')') && !this._match('eof')) {
args.push(this._parseValue());
this._eat(',');
}
this._expect(')');
if (args.length >= 1) {
let start = 0;
let end = 0;
if (args.length === 1) {
end = this._evaluateSimpleValue(args[0]);
} else {
start = this._evaluateSimpleValue(args[0]);
end = this._evaluateSimpleValue(args[1]);
}
for (let i = start; i < end; i++) {
values.push(new tablegen.Value('int', i));
}
}
} else {
while (!this._match('keyword', 'in') && !this._match('eof')) {
this._read();
}
}
} else if (this._eat('{')) {
const start = this._expect('number');
if (this._eat('-') || this._eat('...')) {
const end = this._expect('number');
for (let i = start; i <= end; i++) {
values.push(new tablegen.Value('int', i));
}
} else {
// Single value: {5} means just the value 5
values.push(new tablegen.Value('int', start));
}
this._expect('}');
} else {
while (!this._match('keyword', 'in') && !this._match('eof')) {
this._read();
}
}
return values;
}
_instantiateClassTemplate(dag) {
if (!dag || !dag.operator) {
return null;
}
const className = typeof dag.operator === 'string' ? dag.operator : dag.operator.value;
const classRecord = this.classes.get(className);
if (!classRecord) {
return null;
}
const fields = new Map();
const bindings = new Map();
if (classRecord.templateArgs && dag.operands) {
for (let i = 0; i < classRecord.templateArgs.length && i < dag.operands.length; i++) {
const paramName = classRecord.templateArgs[i].name;
const argValue = dag.operands[i].value;
bindings.set(paramName, argValue);
}
}
for (const [fieldName, field] of classRecord.fields) {
let resolvedValue = field.value;
if (resolvedValue && resolvedValue.type === 'def' && bindings.has(resolvedValue.value)) {
resolvedValue = bindings.get(resolvedValue.value);
} else if (resolvedValue && resolvedValue.type === 'bang') {
resolvedValue = this._evaluateBangOp(resolvedValue, bindings);
}
fields.set(fieldName, resolvedValue);
}
return new tablegen.Value('record_instance', { className, fields });
}
_evaluateBangOp(value, bindings) {
if (!value || value.type !== 'bang') {
return value;
}
const { op, args } = value.value;
if (op === 'tolower' && args && args.length === 1) {
let [arg] = args;
if (arg.type === 'def' && bindings.has(arg.value)) {
arg = bindings.get(arg.value);
}
if (arg.type === 'string') {
const str = String(arg.value).replace(/^"|"$/g, '');
return new tablegen.Value('string', str.toLowerCase());
}
}
return value;
}
_evaluateSimpleValue(value) {
if (!value) {
return 0;
}
if (value.type === 'int') {
return typeof value.value === 'number' ? value.value : parseInt(value.value, 10);
}
if (typeof value === 'number') {
return value;
}
return 0;
}
_parseForeachBody(loop) {
while (!this._match('}') && !this._match('eof')) {
this._parseForeachBodyStatement(loop);
}
}
_parseForeachBodyStatement(loop) {
const token = this._tokenizer.current();
if (token.type === 'keyword') {
switch (token.value) {
case 'def':
loop.entries.push({ type: 'def', data: this._parseDefTemplate() });
break;
case 'defm':
this._parseDefm();
break;
case 'let':
this._parseLet();
break;
case 'defvar':
loop.entries.push({ type: 'defvar', data: this._parseDefvarTemplate() });
break;
case 'foreach':
loop.entries.push({ type: 'foreach', data: this._parseForeachTemplate() });
break;
case 'if':
loop.entries.push({ type: 'foreach', data: this._parseIfAsLoop() });
break;
default:
this._read();
break;
}
} else {
this._read();
}
}
_parseIfAsLoop() {
const location = this._tokenizer.location();
this._read();
const condition = this._parseValue();
this._expect('keyword', 'then');
const loop = {
location,
iterVarName: null,
listValue: [],
entries: [],
condition,
hasDefvar: false,
isConditional: true
};
if (this._match('{')) {
this._read();
this._parseForeachBody(loop);
this._expect('}');
} else {
this._parseForeachBodyStatement(loop);
}
if (this._match('keyword', 'else')) {
this._read();
if (this._match('{')) {
this._read();
let depth = 1;
while (depth > 0 && !this._match('eof')) {
if (this._eat('{')) {
depth++;
} else if (this._eat('}')) {
depth--;
} else {
this._read();
}
}
} else {
// Single statement else: skip until semicolon
this._skipUntil([';']);
this._eat(';');
}
}
return loop;
}
_parseDefTemplate() {
this._read();
const nameTemplate = [];
while (!this._match(':') && !this._match('{') && !this._match(';') && !this._match('eof')) {
if (this._match('id')) {
const value = this._read();
if (this._eat('.')) {
const field = this._expect('id');
nameTemplate.push({ type: 'field_access', base: value, field });
} else {
nameTemplate.push({ type: 'id', value });
}
} else if (this._match('string')) {
nameTemplate.push({ type: 'string', value: this._read() });
} else if (this._match('number')) {
nameTemplate.push({ type: 'number', value: this._read() });
} else if (this._eat('#')) {
nameTemplate.push({ type: 'concat' });
} else if (this._match('!')) {
const bangValue = this._parseValue();
if (bangValue && bangValue.type === 'bang') {
nameTemplate.push({ type: 'bang', value: bangValue.value });
}
} else {
break;
}
}
let parents = [];
if (this._match(':')) {
parents = this._parseParentClassList();
}
let bodyFields = new Map();
if (this._match('{')) {
this._read(); // consume '{'
bodyFields = this._parseRecordBodyFields();
this._expect('}');
}
this._eat(';');
return { nameTemplate, parents, bodyFields };
}
_parseForeachTemplate() {
const location = this._tokenizer.location();
this._read();
const iterVarName = this._expect('id');
this._expect('=');
const listValue = this._parseForeachListValue();
this._expect('keyword', 'in');
const loop = { location, iterVarName, listValue, entries: [] };
if (this._match('{')) {
this._read();
this._parseForeachBody(loop);
this._expect('}');
} else {
this._parseForeachBodyStatement(loop);
}
return loop;
}
_parseDefvarTemplate() {
this._read();
const name = this._expect('id');
this._expect('=');
const value = this._parseValue();
this._expect(';');
return { name, value };
}
_parseRecordBodyFields() {
const fields = new Map();
while (!this._match('}') && !this._match('eof')) {
if (this._match('keyword', 'let')) {
this._read();
const name = this._expect('id');
this._expect('=');
const value = this._parseValue();
this._eat(';');
fields.set(name, { name, type: null, value });
} else if (this._match('keyword', 'field')) {
this._read();
const type = this._parseType();
const name = this._expect('id');
let value = null;
if (this._eat('=')) {
value = this._parseValue();
}
this._eat(';');
fields.set(name, { name, type, value });
} else if (this._match('keyword', 'assert') || this._match('keyword', 'dump')) {
// Skip assert and dump statements
this._skipUntil([';']);
this._eat(';');
} else if (this._match('id') || this._match('keyword')) {
// Type followed by field name
const type = this._parseType();
const name = this._expect('id');
let value = null;
if (this._eat('=')) {
value = this._parseValue();
}
this._eat(';');
fields.set(name, { name, type, value });
} else {
this._read();
}
}
return fields;
}
_resolveForeachLoop(loop, substitutions) {
if (loop.entries.length === 0) {
return;
}
if (loop.isConditional) {
const conditionResult = this._evaluateCondition(loop.condition, substitutions);
if (conditionResult === false || conditionResult === null) {
return;
}
for (const entry of loop.entries) {
if (entry.type === 'def') {
this._instantiateDef(entry.data, substitutions);
} else if (entry.type === 'foreach') {
this._resolveForeachLoop(entry.data, substitutions);
} else if (entry.type === 'defvar') {
const value = this._evaluateDefvar(entry.data.value, substitutions);
substitutions.set(entry.data.name, value);
}
}
return;
}
if (loop.listValue.length === 0) {
return;
}
for (const listItem of loop.listValue) {
const currentSubs = new Map(substitutions);
if (loop.iterVarName) {
currentSubs.set(loop.iterVarName, listItem);
}
for (const entry of loop.entries) {
if (entry.type === 'def') {
this._instantiateDef(entry.data, currentSubs);
} else if (entry.type === 'foreach') {
this._resolveForeachLoop(entry.data, currentSubs);
} else if (entry.type === 'defvar') {
const value = this._evaluateDefvar(entry.data.value, currentSubs);
currentSubs.set(entry.data.name, value);
}
}
}
}
_evaluateCondition(condition, substitutions) {
if (!condition) {
return null;
}
if (condition.type === 'bang' && condition.value) {
const { op, args } = condition.value;
if (op === 'ne' && args.length === 2) {
const a = this._evaluateSimpleExpr(args[0], substitutions);
const b = this._evaluateSimpleExpr(args[1], substitutions);
if (a !== null && b !== null) {
return a !== b;
}
}
if (op === 'eq' && args.length === 2) {
const a = this._evaluateSimpleExpr(args[0], substitutions);
const b = this._evaluateSimpleExpr(args[1], substitutions);
if (a !== null && b !== null) {
return a === b;
}
}
}
// Can't evaluate complex conditions
return null;
}
// Evaluate a simple expression for condition evaluation
_evaluateSimpleExpr(expr, substitutions) {
if (!expr) {
return null;
}
if (expr.type === 'string') {
return String(expr.value).replace(/^"|"$/g, '');
}
if (expr.type === 'int') {
return typeof expr.value === 'number' ? expr.value : parseInt(expr.value, 10);
}
if ((expr.type === 'def' || expr.type === 'id') && typeof expr.value === 'string') {
// Handle field access like "largeT.name"
if (expr.value.includes('.')) {
const [baseName, ...fieldParts] = expr.value.split('.');
let current = substitutions.has(baseName) ? substitutions.get(baseName) : null;
if (current) {
for (const fieldName of fieldParts) {
// Handle record_instance (from _instantiateClassTemplate)
if (current && current.type === 'record_instance' && current.value && current.value.fields) {
const fieldValue = current.value.fields.get(fieldName);
if (fieldValue) {
current = fieldValue;
} else {
return null;
}
} else {
return null;
}
}
return this._evaluateSimpleExpr(current, substitutions);
}
} else if (substitutions.has(expr.value)) {
return this._evaluateSimpleExpr(substitutions.get(expr.value), substitutions);
}
}
return null;
}
_evaluateDefvar(value, substitutions) {
if (!value) {
return new tablegen.Value('string', '');
}
if (value.type === 'string') {
return value;
}
if (value.type === 'int') {
return value;
}
if ((value.type === 'def' || value.type === 'id') && substitutions.has(value.value)) {
return substitutions.get(value.value);
}
if (value.type === 'concat') {
const parts = value.value.map((part) => this._evaluateDefvar(part, substitutions));
let result = '';
for (const part of parts) {
if (part.type === 'string') {
result += String(part.value).replace(/^"|"$/g, '');
} else if (part.type === 'int') {
result += String(part.value);
} else if (part.type === 'def' || part.type === 'id') {
result += String(part.value);
}
}
return new tablegen.Value('string', result);
}
if (value.type === 'bang' && value.value) {
const { op, args } = value.value;
if (op === 'cast' && args && args.length > 0) {
const arg = this._evaluateDefvar(args[0], substitutions);
if (arg.type === 'int') {
return new tablegen.Value('string', String(arg.value));
}
return arg;
}
if (op === 'toupper' && args && args.length > 0) {
const arg = this._evaluateDefvar(args[0], substitutions);
if (arg.type === 'string') {
return new tablegen.Value('string', String(arg.value).toUpperCase());
}
return arg;
}
if (op === 'tolower' && args && args.length > 0) {
const arg = this._evaluateDefvar(args[0], substitutions);
if (arg.type === 'string') {
return new tablegen.Value('string', String(arg.value).toLowerCase());
}
return arg;
}
if (op === 'strconcat' && args && args.length > 0) {
let result = '';
for (const arg of args) {
const evaluated = this._evaluateDefvar(arg, substitutions);
if (evaluated && evaluated.type === 'string') {
result += String(evaluated.value).replace(/^"|"$/g, '');
} else if (evaluated && evaluated.type === 'int') {
result += String(evaluated.value);
}
}
return new tablegen.Value('string', result);
}
}
return value;
}
_instantiateDef(template, substitutions) {
let name = '';
for (const part of template.nameTemplate) {
if (part.type === 'concat') {
continue;
} else if (part.type === 'field_access') {
if (substitutions.has(part.base)) {
const subValue = substitutions.get(part.base);
name += this._getFieldValue(subValue, part.field);
} else {
name += `${part.base}.${part.field}`;
}
} else if (part.type === 'id') {
if (substitutions.has(part.value)) {
const subValue = substitutions.get(part.value);
name += this._valueToString(subValue);
} else {
name += part.value;
}
} else if (part.type === 'string') {
name += part.value;
} else if (part.type === 'number') {
name += String(part.value);
} else if (part.type === 'bang') {
const evaluated = this._evaluateDefvar(new tablegen.Value('bang', part.value), substitutions);
name += this._valueToString(evaluated);
}
}
const def = new tablegen.Record(name, this);
def.location = this._tokenizer.location();
def.parents = template.parents.map((parent) => ({
name: parent.name,
args: parent.args ? parent.args.map((arg) => this._substituteValue(arg, substitutions)) : []
}));
for (const [fieldName, field] of template.bodyFields) {
const resolvedValue = field.value ? this._substituteValue(field.value, substitutions) : null;
def.fields.set(fieldName, new tablegen.RecordVal(fieldName, field.type, resolvedValue));
}
this.addSubClass(def);
def.resolveReferences();
if (name) {
this._defs.set(name, def);
this.defs.push(def);
}
}
_valueToString(value) {
if (!value) {
return '';
}
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return String(value);
}
if (value.type === 'string') {
return String(value.value).replace(/^"|"$/g, '');
}
if (value.type === 'int') {
return String(value.value);
}
if (value.type === 'id' || value.type === 'def') {
return String(value.value);
}
return '';
}
_getFieldValue(value, fieldName) {
if (!value) {
return '';
}
if (value.type === 'record_instance' && value.value && value.value.fields) {
const fieldValue = value.value.fields.get(fieldName);
return this._valueToString(fieldValue);
}
return '';
}
_substituteValue(value, substitutions) {
if (!value) {
return value;
}
if (value.type === 'def' || value.type === 'id') {
const varName = value.value;
if (substitutions.has(varName)) {
return substitutions.get(varName);
}
if (typeof varName === 'string' && varName.includes('.')) {
const [base, field] = varName.split('.', 2);
if (substitutions.has(base)) {
const baseValue = substitutions.get(base);
const fieldValue = this._getFieldValue(baseValue, field);
if (fieldValue) {
return new tablegen.Value('string', fieldValue);
}
}
}
}
if (value.type === 'list' && Array.isArray(value.value)) {
return {
type: 'list',
value: value.value.map((v) => this._substituteValue(v, substitutions))
};
}
if (value.type === 'dag' && value.value) {
return {
type: 'dag',
value: {
operator: value.value.operator,
operands: value.value.operands.map((op) => ({
value: this._substituteValue(op.value, substitutions),
name: op.name
}))
}
};
}
if (value.type === 'concat' && Array.isArray(value.value)) {
return {
type: 'concat',
value: value.value.map((v) => this._substituteValue(v, substitutions))
};
}
return value;
}
_parseTemplateParams() {
this._read(); // <
const params = [];
while (!this._match('>') && !this._match('eof')) {
const type = this._parseType();
const name = this._expect('id');
let defaultValue = null;
if (this._match('=')) {
this._read();
defaultValue = this._parseValue();
}
params.push({ name, type, defaultValue });
if (this._match(',')) {
this._read();
}
}
this._expect('>');
return params;
}
_parseParentClassList() {
this._read();
const parents = [];
while (!this._match('{') && !this._match(';') && !this._match('eof')) {
const parent = this._parseType();
parents.push(parent);
if (!this._eat(',')) {
break;
}
}
return parents;
}
_parseRecordBody(record) {
this._read();
while (!this._match('}') && !this._match('eof')) {
if (this._match('keyword', 'let')) {
this._read();
// Optional 'append'/'prepend' modifier concatenates with the inherited
// value instead of replacing it (TableGen field concatenation).
let concat = null;
if (this._match('id', 'append') || this._match('id', 'prepend')) {
concat = this._read();
}
const name = this._expect('id');
this._expect('=');
const value = this._parseValue();
const field = new tablegen.RecordVal(name, null, value);
if (concat) {
field.concat = concat;
}
record.fields.set(name, field);
this._eat(';');
} else if (this._match('keyword', 'defvar')) {
this._read();
const name = this._expect('id');
this._expect('=');
const value = this._parseValue();
const field = new tablegen.RecordVal(name, null, value);
record.fields.set(name, field);
this._eat(';');
} else if (this._match('keyword', 'assert')) {
this._read();
this._parseValue(); // condition
this._eat(',');
this._parseValue(); // message
this._eat(';');
} else if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
this._match('keyword', 'code') || this._match('id')) {
const type = this._parseType();
// Skip if next token is not an id (handles edge cases in complex nested structures)
if (!this._match('id')) {
if (this._match(',') || this._match('>') || this._match(')') || this._match(']')) {
continue;
}
}
const name = this._expect('id');
let value = null;
if (this._eat('=')) {
value = this._parseValue();
}
const field = new tablegen.RecordVal(name, type, value);
record.fields.set(name, field);
this._eat(';');
} else {
this._read();
}
}
this._expect('}');
}
_parseType() {
let typeName = '';
if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
this._match('keyword', 'code')) {
typeName = this._read();
} else if (this._match('id')) {
typeName = this._read();
} else if (this._match('number')) {
typeName = this._read().toString();
if (this._match('id')) {
typeName += this._read();
}
} else {
throw new tablegen.Error(`Expected type at ${this._tokenizer.location()}`);
}
const type = new tablegen.Type(typeName);
if (this._eat('<')) {
type.args = this._parseTemplateArgList();
}
return type;
}
_parseTemplateArgList() {
// Parse template arguments directly from token stream
// Supports both positional (arg1, arg2) and named (name=value) arguments
const args = [];
while (!this._match('>') && !this._match('eof')) {
// Check if this is a named argument: id = value
if (this._match('id')) {
const name = this._read();
if (this._match('=')) {
// Named argument
this._read(); // Consume '='
const value = this._parseValue();
args.push({ name, value });
} else {
// Positional argument that starts with an id
// Reconstruct the value - id might be part of concat, field access, etc.
let value = new tablegen.Value('def', name);
// Handle < > for template instantiation - manually parse with depth tracking
if (this._eat('<')) {
const nestedArgs = [];
let depth = 1;
let currentArg = [];
while (depth > 0 && !this._match('eof')) {
if (this._match('<')) {
currentArg.push(this._read());
depth++;
} else if (this._match('>')) {
if (depth === 1) {
// End of this template arg list
if (currentArg.length > 0) {
// Parse accumulated tokens properly
const argStr = currentArg.join(' ');
// Try to parse as number first
if (/^-?\d+$/.test(argStr)) {
nestedArgs.push(new tablegen.Value('int', argStr));
} else {
nestedArgs.push(new tablegen.Value('def', argStr));
}
}
this._read(); // consume the >
depth--;
} else {
currentArg.push(this._read());
depth--;
}
} else if (this._match(',') && depth === 1) {
// Argument separator at current depth
if (currentArg.length > 0) {
// Parse accumulated tokens properly
const argStr = currentArg.join(' ');
// Try to parse as number first
if (/^-?\d+$/.test(argStr)) {
nestedArgs.push(new tablegen.Value('int', argStr));
} else {
nestedArgs.push(new tablegen.Value('def', argStr));
}
currentArg = [];
}
this._read(); // consume comma
} else {
// Regular token - add to current arg
currentArg.push(this._read());
}
}
// Convert to DAG operands
const operands = nestedArgs.map((arg) => ({ value: arg, name: null }));
value = new tablegen.Value('dag', new tablegen.DAG(name, operands));
}
// Handle field access
if (this._eat('.')) {
const field = this._expect('id');
if (value.type === 'def') {
value = new tablegen.Value('def', `${name}.${field}`);
}
}
// Handle :: suffix
if (this._eat('::')) {
const suffix = this._expect('id');
if (value.type === 'def') {
value = new tablegen.Value('def', `${name}::${suffix}`);
}
}
// Handle # concatenation
if (this._match('#')) {
const values = [value];
while (this._match('#')) {
this._read();
values.push(this._parsePrimaryValue());
}
value = new tablegen.Value('concat', values);
}
args.push(value);
}
} else {
const value = this._parseValue();
args.push(value);
}
if (!this._eat(',')) {
break;
}
}
this._expect('>');
return args;
}
_parseValue() {
const values = [];
values.push(this._parsePrimaryValue());
while (this._match('#') || (values[values.length - 1] && values[values.length - 1].type === 'string' && this._match('string'))) {
if (this._match('#')) {
this._read();
// Handle trailing # before ; (malformed TableGen but seen in some files)
if (this._match(';') || this._match(',') || this._match(')') || this._match(']') || this._match('}') || this._match('eof')) {
break;
}
}
values.push(this._parsePrimaryValue());
}
if (values.length === 1) {
return values[0];
}
return new tablegen.Value('concat', values);
}
_parseListItem() {
// Handle $variable as a standalone value in list/dag context
if (this._eat('$')) {
const name = this._expect('id');
return new tablegen.Value('var', name);
}
// Special handling for dag-like constructs (id followed by <...>)
// These need to be parsed as DAGs for trait information
if (this._match('id')) {
const name = this._read();
if (this._eat('<')) {
const templateArgs = this._parseTemplateArgList();
const operands = templateArgs.map((arg) => {
if (arg && typeof arg === 'object' && arg.name && arg.value) {
return { value: arg.value, name: arg.name };
}
return { value: arg, name: null };
});
const result = new tablegen.Value('dag', new tablegen.DAG(name, operands));
if (this._eat('.')) {
result.field = this._expect('id');
}
return result;
}
// Not a template instantiation, but might be an identifier with suffixes
// Put the token back conceptually by creating a def value and checking for suffixes
let result = new tablegen.Value('def', name);
// Check for subscripts, field access, etc.
while (this._eat('[')) {
const index = this._parseValue();
this._expect(']');
result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
}
if (this._eat('.')) {
const field = this._expect('id');
if (result.type === 'def') {
result = new tablegen.Value('def', `${result.value}.${field}`);
} else {
result.value.field = field;
}
}
return result;
}
return this._parseValue();
}
_parsePrimaryValue() {
if (this._match('string')) {
const value = this._read();
return new tablegen.Value('string', value);
}
if (this._match('number')) {
const value = this._read();
return new tablegen.Value('int', value);
}
if (this._match('true') || this._match('false')) {
const value = this._read();
return new tablegen.Value('bit', value);
}
if (this._match('code')) {
const value = this._read();
return new tablegen.Value('code', value);
}
if (this._eat('[')) {
const items = [];
while (!this._match(']') && !this._match('eof')) {
items.push(this._parseListItem());
this._eat(',');
}
this._expect(']');
if (this._match('<')) {
this._skip('<', '>');
}
return new tablegen.Value('list', items);
}
if (this._eat('(')) {
let operator = null;
let operatorName = null;
if (this._match('id')) {
operator = this._read();
// Handle template arguments on the operator: (NativeCodeCallVoid<...> ...)
if (this._match('<')) {
this._skip('<', '>');
}
// Handle operator binding: (OpQ:$op ...)
if (this._eat(':') && this._eat('$')) {
if (this._match('id') || this._match('keyword')) {
operatorName = this._read();
}
}
}
const operands = [];
while (!this._match(')') && !this._match('eof')) {
if (this._eat(',')) {
continue;
}
// Use _parseListItem() to handle template instantiations like Arg<TTG_MemDescType>
const value = this._parseListItem();
let name = null;
if (this._eat(':') && this._eat('$')) {
if (this._match('id') || this._match('keyword')) {
name = this._read();
}
}
const operand = { value, name };
operands.push(operand);
this._eat(',');
}
this._expect(')');
const dag = new tablegen.DAG(operator, operands);
if (operatorName) {
dag.operatorName = operatorName;
}
return new tablegen.Value('dag', dag);
}
if (this._eat('{')) {
const fields = new Map();
while (!this._match('}') && !this._match('eof')) {
const name = this._expect('id');
this._expect('=');
const value = this._parseValue();
fields.set(name, value);
this._eat(',');
}
this._expect('}');
return new tablegen.Value('record', fields);
}
if (this._eat('!')) {
let op = null;
if (this._match('id') || this._match('keyword')) {
op = this._read();
} else {
throw new tablegen.Error(`Expected operator after '!' but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
}
if (this._match('<')) {
this._skip('<', '>');
}
const args = [];
if (this._eat('(')) {
if (op === 'cond') {
while (!this._match(')') && !this._match('eof')) {
const condition = this._parseValue();
this._expect(':');
const value = this._parseValue();
args.push({ condition, value });
this._eat(',');
}
} else {
while (!this._match(')') && !this._match('eof')) {
args.push(this._parseValue());
this._eat(',');
}
}
this._expect(')');
}
let field = null;
if (this._eat('.')) {
field = this._expect('id');
}
return new tablegen.Value('bang', { op, args, field });
}
if (this._match('id') || this._match('keyword')) {
const value = this._read();
let result = new tablegen.Value('def', value);
// Handle various suffixes: templates, subscripts, field access, scope resolution
while (true) {
if (this._match('<')) {
// Template arguments are skipped here - they're parsed properly in
// DAG context by _parseListItem() which creates DAG values with template args
this._skip('<', '>');
} else if (this._eat('[')) {
// Array subscripting: x[0]
const index = this._parseValue();
this._expect(']');
result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
} else if (this._eat('.')) {
// Field access: x.field or x[0].field
const field = this._expect('id');
if (result.type === 'def') {
result = new tablegen.Value('def', `${result.value}.${field}`);
} else {
// For subscript results, add field access
result.value.field = field;
}
} else if (this._eat('::')) {
// Scope resolution
const suffix = this._expect('id');
if (result.type === 'def') {
result = new tablegen.Value('def', `${result.value}::${suffix}`);
}
break;
} else {
break;
}
}
return result;
}
if (this._eat('?')) {
return new tablegen.Value('uninitialized', null);
}
if (this._eat('$')) {
const name = this._expect('id');
return new tablegen.Value('var', name);
}
throw new tablegen.Error(`Unexpected value at ${this._tokenizer.location()}`);
}
_read() {
return this._tokenizer.read().value;
}
_match(type, value) {
const token = this._tokenizer.current();
return token.type === type && (!value || token.value === value);
}
_eat(type, value) {
if (this._match(type, value)) {
this._read();
return true;
}
return false;
}
_expect(type, value) {
if (this._match(type, value)) {
return this._read();
}
const token = this._tokenizer.current();
throw new tablegen.Error(`Expected '${type}' but got '${token.type}' at ${this._tokenizer.location()}`);
}
_skip(open, close) {
if (!this._match(open)) {
return;
}
this._read(); // consume opening token
let depth = 1;
while (depth > 0 && !this._match('eof')) {
if (this._match(open)) {
depth++;
} else if (this._match(close)) {
depth--;
}
this._read();
}
}
_skipUntil(types) {
while (!this._match('eof') && !types.includes(this._tokenizer.current().type)) {
this._read();
}
}
};
tablegen.Error = class extends Error {
constructor(message) {
super(message);
this.name = 'TableGen Error';
}
};
export const Reader = tablegen.Reader;