瀏覽代碼

Add MLIR support (#1044)

Lutz Roeder 3 月之前
父節點
當前提交
e9216ee530
共有 4 個文件被更改,包括 329 次插入176 次删除
  1. 2 2
      source/mlir-metadata.json
  2. 115 6
      source/mlir.js
  3. 84 107
      tools/mlir_script.js
  4. 128 61
      tools/tablegen.js

File diff suppressed because it is too large
+ 2 - 2
source/mlir-metadata.json


+ 115 - 6
source/mlir.js

@@ -3432,7 +3432,96 @@ mlir.Dialect = class {
     }
 
     getOperation(opName) {
-        return this._operations.get(opName);
+        const op = this._operations.get(opName);
+        if (!op) {
+            return null;
+        }
+        const parseTypeString = (typeStr) => {
+            if (!typeStr || typeof typeStr !== 'string') {
+                return { baseType: null, variadic: false, optional: false };
+            }
+            if (typeStr === 'Variadic' || typeStr.startsWith('Variadic<')) {
+                if (typeStr === 'Variadic') {
+                    return { baseType: null, variadic: true, optional: false };
+                }
+                const match = typeStr.match(/^Variadic<(.+)>$/);
+                if (match) {
+                    const inner = parseTypeString(match[1]);
+                    return { baseType: inner.baseType, variadic: true, optional: inner.optional };
+                }
+                return { baseType: null, variadic: true, optional: false };
+            }
+            if (typeStr === 'Optional' || typeStr.startsWith('Optional<')) {
+                if (typeStr === 'Optional') {
+                    return { baseType: null, variadic: false, optional: true };
+                }
+                const match = typeStr.match(/^Optional<(.+)>$/);
+                if (match) {
+                    const inner = parseTypeString(match[1]);
+                    return { baseType: inner.baseType, variadic: inner.variadic, optional: true };
+                }
+                return { baseType: null, variadic: false, optional: true };
+            }
+            if (typeStr.startsWith('Arg<')) {
+                const match = typeStr.match(/^Arg<([^,>]+)/);
+                if (match) {
+                    return parseTypeString(match[1]);
+                }
+            }
+            if (typeStr.startsWith('DefaultValuedAttr<')) {
+                const match = typeStr.match(/^DefaultValuedAttr<([^,]+)/);
+                if (match) {
+                    return parseTypeString(match[1]);
+                }
+            }
+            return { baseType: typeStr, variadic: false, optional: false };
+        };
+
+        // Enhance metadata once on first access
+        if (!op.metadata._enhanced) {
+            const enhanceOperand = (operand) => {
+                if (!operand) {
+                    return;
+                }
+                if (operand.constraint && typeof operand.constraint === 'object') {
+                    operand.variadic = operand.constraint.name === 'Variadic';
+                    operand.optional = operand.constraint.name === 'Optional';
+                    if (operand.constraint.name === 'Variadic' && operand.constraint.args && operand.constraint.args.length > 0) {
+                        const [innerConstraint] = operand.constraint.args;
+                        if (innerConstraint && typeof innerConstraint === 'object' && innerConstraint.name) {
+                            operand.type = innerConstraint.name;
+                        }
+                    } else if (operand.constraint.name === 'Optional' && operand.constraint.args && operand.constraint.args.length > 0) {
+                        const [innerConstraint] = operand.constraint.args;
+                        if (innerConstraint && typeof innerConstraint === 'object' && innerConstraint.name) {
+                            operand.type = innerConstraint.name;
+                        }
+                    } else if (!operand.type) {
+                        operand.type = operand.constraint.name;
+                    }
+                    return;
+                }
+                if (operand.type) {
+                    const info = parseTypeString(operand.type);
+                    operand.variadic = info.variadic;
+                    operand.optional = info.optional;
+                    if (info.baseType !== null && info.baseType !== operand.type) {
+                        operand.type = info.baseType;
+                    }
+                }
+            };
+            if (op.metadata.inputs) {
+                op.metadata.inputs.forEach(enhanceOperand);
+            }
+            if (op.metadata.outputs) {
+                op.metadata.outputs.forEach(enhanceOperand);
+            }
+            if (op.metadata.attributes) {
+                op.metadata.attributes.forEach(enhanceOperand);
+            }
+            op.metadata._enhanced = true;
+        }
+        return op;
     }
 
     hasParser(opName) {
@@ -3577,8 +3666,8 @@ mlir.Dialect = class {
                 let isVariadic = false;
                 if (opInfo.metadata && opInfo.metadata.inputs) {
                     const inputInfo = opInfo.metadata.inputs.find((inp) => inp.name === refName);
-                    if (inputInfo && (inputInfo.type === 'Variadic' || inputInfo.isVariadic || (typeof inputInfo.type === 'string' && inputInfo.type.includes('DynamicDims')))) {
-                        isVariadic = true;
+                    if (inputInfo) {
+                        isVariadic = inputInfo.variadic || (typeof inputInfo.type === 'string' && inputInfo.type.includes('DynamicDims'));
                     }
                 }
                 // Parse variadic operands
@@ -3661,7 +3750,14 @@ mlir.Dialect = class {
                             parser.parseArgumentTypes(op.operands);
                         } else {
                             // Parse as results (outputs)
-                            const hasVariadicResult = opMetadata && opMetadata.outputs && (opMetadata.outputs.length > 1 || (opMetadata.outputs.length === 1 && (opMetadata.outputs[0].type === 'Variadic' || opMetadata.outputs[0].isVariadic)));
+                            let hasVariadicResult = false;
+                            if (opMetadata && opMetadata.outputs) {
+                                if (opMetadata.outputs.length > 1) {
+                                    hasVariadicResult = true;
+                                } else if (opMetadata.outputs.length === 1) {
+                                    hasVariadicResult = opMetadata.outputs[0].variadic || false;
+                                }
+                            }
                             if (hasVariadicResult) {
                                 parser.parseArgumentTypes(op.results);
                             } else {
@@ -3683,7 +3779,7 @@ mlir.Dialect = class {
                             for (const output of opMetadata.outputs) {
                                 if (output.name === arg || `$${output.name}` === arg) {
                                     isResult = true;
-                                    isVariadic = output.type === 'Variadic' || output.isVariadic || false;
+                                    isVariadic = output.variadic || false;
                                     break;
                                 }
                             }
@@ -3691,7 +3787,7 @@ mlir.Dialect = class {
                         if (!isResult && opMetadata && opMetadata.inputs) {
                             for (const input of opMetadata.inputs) {
                                 if (input.name === arg || `$${input.name}` === arg) {
-                                    isVariadic = input.type === 'Variadic' || input.isVariadic || false;
+                                    isVariadic = input.variadic || false;
                                     break;
                                 }
                             }
@@ -3989,6 +4085,7 @@ mlir.Dialect = class {
     }
 
     _parseCustomTypeWithFallback(parser, type) {
+        // Type is already the base type after enhancement
         if (this._customTypes.has(type)) {
             const typeT = this._customTypes.get(type);
             return parser.parseCustomTypeWithFallback(typeT);
@@ -12473,6 +12570,18 @@ mlir.TritonGPUDialect = class extends mlir.Dialect {
 
     constructor(operations) {
         super('ttg', operations);
+        // Register custom type parser for MemDescType shorthand notation
+        this.registerCustomType('TTG_MemDescType', this._parseMemDescType.bind(this));
+    }
+
+    _parseMemDescType(parser) {
+        // Handle shorthand MemDescType notation: <dims x elementType, attributes...>
+        // Full notation would be: !ttg.memdesc<dims x elementType, attributes...>
+        if (!parser.match('<')) {
+            return null;
+        }
+        const content = parser.skip('<', '>');
+        return new mlir.Type(`!ttg.memdesc<${content}>`);
     }
 
     parseOperation(parser, opName, op) {

+ 84 - 107
tools/mlir_script.js

@@ -31,7 +31,6 @@ class Operator {
     }
 
     _extractValue(arg) {
-        // Handle both old string format and new Value object format
         if (typeof arg === 'string') {
             return arg;
         }
@@ -53,7 +52,6 @@ class Operator {
         if (!value) {
             return null;
         }
-        // Detect cycles
         const valueKey = JSON.stringify(value);
         if (visited.has(valueKey)) {
             return null;
@@ -119,10 +117,8 @@ class Operator {
             if (!mnemonic && mnemonicArg) {
                 mnemonic = this._evaluateWithSubstitutions(mnemonicArg, subs);
             }
-            // Clean up mnemonic: remove leading/trailing dots, normalize multiple dots
             if (mnemonic && typeof mnemonic === 'string') {
-                mnemonic = mnemonic.replace(/^\.+|\.+$/g, ''); // Remove leading/trailing dots
-                // Skip if mnemonic is invalid after cleanup
+                mnemonic = mnemonic.replace(/^\.+|\.+$/g, '');
                 if (!mnemonic || mnemonic.includes('..')) {
                     return null;
                 }
@@ -292,7 +288,7 @@ const main = async () => {
         'mlir/Dialect/SMT/IR/SMTArrayOps.td',
         'mlir/Dialect/SMT/IR/SMTBitVectorOps.td',
         'mlir/Dialect/SMT/IR/SMTIntOps.td',
-        // 'mlir/Dialect/OpenACC/OpenACCOps.td', // File not found 'mlir/Dialect/OpenACC/AccCommon.td' (requires full LLVM tree to generate)
+        // 'mlir/Dialect/OpenACC/OpenACCOps.td', // File not found 'mlir/Dialect/OpenACC/AccCommon.td'
         'mlir/Dialect/LLVMIR/XeVMOps.td',
         'toy/Ops.td',
         'stablehlo/dialect/StablehloOps.td',
@@ -389,21 +385,20 @@ const main = async () => {
         const operation = {
             name: operationName
         };
-        let args = def.resolveField('arguments');
-        // If the field value needs evaluation (e.g., it's a computed field), evaluate it
-        if (args && args.value && (args.value.type === 'id' || args.value.type === 'bang')) {
-            const evaluated = def.evaluateValue(args.value);
-            if (evaluated && typeof evaluated === 'object' && evaluated.operator) {
-                // The evaluation returned a DAG directly
-                args = { value: new tablegen.Value('dag', evaluated) };
+        if (operations.has(operationName)) {
+            const existing = operations.get(operationName);
+            if (existing.category) {
+                operation.category = existing.category;
             }
         }
-        if (!args || !args.value || args.value.type !== 'dag' || (args.value.value && args.value.value.operands && args.value.value.operands.length === 0)) {
+        let args = def.getValueAsDag('arguments');
+        if (!args || !args.operands || args.operands.length === 0) {
+            // Try to get from parent Arguments class
             for (const parent of def.parents) {
                 if (parent.name === 'Arguments' && parent.args && parent.args.length > 0) {
                     const [dagValue] = parent.args;
                     if (dagValue && dagValue.type === 'dag') {
-                        args = { value: dagValue };
+                        args = dagValue.value;
                     }
                     break;
                 }
@@ -423,83 +418,78 @@ const main = async () => {
         } else if (['batch_norm_inference'].includes(name)) {
             operation.category = 'Normalization';
         }
-        const summary = def.resolveField('summary');
-        if (summary && summary.value) {
-            const value = def.evaluateValue(summary.value);
-            if (value) {
-                let summary = value.trim();
-                summary = summary.replace(/^"|"$/g, '');
-                if (summary) {
-                    operation.summary = summary;
-                }
+        if (def.getValue('summary')) {
+            const summary = def.getValueAsString('summary').trim();
+            if (summary) {
+                operation.summary = summary;
             }
         }
-        const description = def.resolveField('description');
-        if (description && description.value) {
-            const value = def.evaluateValue(description.value);
-            if (value) {
-                let desc = value.trim();
-                desc = desc.replace(/^\[\{\s*|\s*\}\]$/g, '');
-                desc = desc.trim();
-                if (desc) {
-                    operation.description = desc;
-                }
+        if (def.getValue('description')) {
+            const description = def.getValueAsString('description');
+            if (description) {
+                operation.description = description;
             }
         }
+        // Convert TableGen value to constraint string
+        const toConstraintString = (value) => {
+            if (!value) {
+                return null;
+            }
+            if (value.type === 'def') {
+                return value.value;
+            }
+            if (value.type === 'string' || value.type === 'code') {
+                return value.value;
+            }
+            if (value.type === 'int') {
+                return String(value.value);
+            }
+            if (value.type === 'list') {
+                const items = value.value.map((item) => toConstraintString(item)).filter((x) => x !== null);
+                return items.length > 0 ? `[${items.join(', ')}]` : null;
+            }
+            if (value.type === 'dag' && value.value) {
+                const dag = value.value;
+                const args = dag.operands.map((op) => toConstraintString(op.value)).filter((x) => x !== null);
+                if (args.length > 0) {
+                    return `${dag.operator}<${args.join(', ')}>`;
+                }
+                return dag.operator;
+            }
+            return null;
+        };
         const attributes = [];
         const inputs = [];
         const outputs = [];
-        if (args && args.value && args.value.type === 'dag') {
-            const dag = args.value.value;
-            if (dag.operator === 'ins') {
-                for (const operand of dag.operands) {
-                    if (!operand.value || !operand.name) {
-                        continue;
-                    }
-                    let typeName = '';
-                    if (operand.value.type === 'def') {
-                        typeName = operand.value.value;
-                    } else {
-                        // Try to extract from other value types
-                        typeName = String(operand.value.value);
-                    }
-                    if (typeName.includes('Attr')) {
-                        attributes.push({
-                            name: operand.name,
-                            type: typeName
-                        });
+        if (args && args.operator === 'ins') {
+            for (const operand of args.operands) {
+                if (operand.value && operand.name) {
+                    const type = toConstraintString(operand.value);
+                    if (type && type.includes('Attr')) {
+                        attributes.push({ name: operand.name, type });
                     } else {
-                        inputs.push({
-                            name: operand.name,
-                            type: typeName
-                        });
+                        inputs.push({ name: operand.name, type });
                     }
                 }
             }
         }
-        let results = def.resolveField('results');
-        if (!results || !results.value || results.value.type !== 'dag' || (results.value.value && results.value.value.operands && results.value.value.operands.length === 0)) {
+        let results = def.getValueAsDag('results');
+        if (!results || !results.operands || results.operands.length === 0) {
+            // Try to get from parent Results class
             for (const parent of def.parents) {
                 if (parent.name === 'Results' && parent.args && parent.args.length > 0) {
                     const [dagValue] = parent.args;
                     if (dagValue && dagValue.type === 'dag') {
-                        results = { value: dagValue };
+                        results = dagValue.value;
                     }
                     break;
                 }
             }
         }
-        if (results && results.value && results.value.type === 'dag') {
-            const dag = results.value.value;
-            if (dag.operator === 'outs') {
-                for (const operand of dag.operands) {
-                    if (!operand.value || !operand.name) {
-                        continue;
-                    }
-                    if (operand.value.type !== 'def') {
-                        throw new Error('Unexpected result operand value type');
-                    }
-                    const type = operand.value.value;
+        if (results && results.operator === 'outs') {
+            for (const operand of results.operands) {
+                if (operand.value && operand.name) {
+                    const type = toConstraintString(operand.value);
                     outputs.push({ name: operand.name, type });
                 }
             }
@@ -513,38 +503,29 @@ const main = async () => {
         if (attributes.length > 0) {
             operation.attributes = attributes;
         }
-        const successors = def.resolveField('successors');
-        if (successors && successors.value && successors.value.type === 'dag') {
-            const dag = successors.value.value;
-            if (dag.operator === 'successor') {
-                const successors = [];
-                for (const operand of dag.operands) {
-                    if (operand.name) {
-                        successors.push({ name: operand.name });
-                    }
-                }
-                if (successors.length > 0) {
-                    operation.successors = successors;
+        const successors = def.getValueAsDag('successors');
+        if (successors && successors.operator === 'successor') {
+            const list = [];
+            for (const operand of successors.operands) {
+                if (operand.name) {
+                    list.push({ name: operand.name });
                 }
             }
+            if (list.length > 0) {
+                operation.successors = list;
+            }
         }
-        const assemblyFormat = def.resolveField('assemblyFormat');
-        if (assemblyFormat && assemblyFormat.value) {
-            const value = def.evaluateValue(assemblyFormat.value);
-            if (value) {
-                const format = value.trim().replace(/^\[\{\s*|\s*\}\]$/g, '');
-                if (format) {
-                    operation.assemblyFormat = format;
-                }
+        if (def.getValue('assemblyFormat')) {
+            const assemblyFormat = def.getValueAsString('assemblyFormat');
+            if (assemblyFormat) {
+                operation.assemblyFormat = assemblyFormat.trim();
             }
         }
-        const hasCustomAssemblyFormat = def.resolveField('hasCustomAssemblyFormat');
-        if (hasCustomAssemblyFormat && hasCustomAssemblyFormat.value) {
-            operation.hasCustomAssemblyFormat = def.evaluateValue(hasCustomAssemblyFormat.value);
+        if (def.getValue('hasCustomAssemblyFormat') && def.getValueAsBit('hasCustomAssemblyFormat')) {
+            operation.hasCustomAssemblyFormat = true;
         }
-        const parser = def.resolveField('parser');
-        if (parser && parser.value) {
-            operation.parser = 1;
+        if (def.getValue('parser')) {
+            operation.parser = def.getValueAsString('parser');
         }
         // Extract defaultDialect from OpAsmOpInterface
         for (const parent of def.parents) {
@@ -562,15 +543,12 @@ const main = async () => {
                                     break;
                                 }
                             }
-                            const extraClass = def.resolveField('extraClassDeclaration');
-                            if (extraClass && extraClass.value) {
-                                const code = def.evaluateValue(extraClass.value);
-                                if (code && typeof code === 'string') {
-                                    const match = code.match(/getDefaultDialect\(\)\s*\{\s*return\s+"(\w+)"/);
-                                    if (match) {
-                                        [, operation.defaultDialect] = match;
-                                        break;
-                                    }
+                            const extraClass = def.getValueAsString('extraClassDeclaration');
+                            if (extraClass) {
+                                const match = extraClass.match(/getDefaultDialect\(\)\s*\{\s*return\s+"(\w+)"/);
+                                if (match) {
+                                    [, operation.defaultDialect] = match;
+                                    break;
                                 }
                             }
                         }
@@ -584,7 +562,6 @@ const main = async () => {
                 break;
             }
         }
-        // Only add operation if it has meaningful data beyond just the name
         if (Object.keys(operation).length > 1) {
             operations.set(operationName, operation);
         }

+ 128 - 61
tools/tablegen.js

@@ -28,17 +28,22 @@ tablegen.Token = class {
 
 tablegen.Tokenizer = class {
 
-    constructor(text, filename) {
+    constructor(text, file) {
         this._text = text;
-        this._filename = filename;
+        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._filename, this._line, this._column);
+        return new tablegen.Location(this._file, this._line, this._column);
     }
 
     current() {
@@ -100,7 +105,17 @@ tablegen.Tokenizer = class {
             this._next();
             return new tablegen.Token('<<', '<<', location);
         }
-        if (this._isDigit(c) || (c === '-' && this._isDigit(this.peek(1)))) {
+        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)) {
@@ -297,7 +312,6 @@ tablegen.Tokenizer = class {
             value += this.peek();
             this._next();
         }
-        // Handle member access with dots (e.g., ElementwiseMappable.traits)
         while (this.peek() === '.' && this._isIdentifierStart(this.peek(1))) {
             value += this.peek(); // add dot
             this._next();
@@ -306,15 +320,9 @@ tablegen.Tokenizer = class {
                 this._next();
             }
         }
-        const keywords = [
-            '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'
-        ];
-        const type = keywords.includes(value) ? value : 'id';
-        const tokenValue = (type === 'true' || type === 'false') ? (value === 'true') : value;
-        return new tablegen.Token(type, tokenValue, location);
+        const type = this._keywords.has(value) ? value : 'id';
+        value = type === 'true' || type === 'false' ? value === 'true' : value;
+        return new tablegen.Token(type, value, location);
     }
 };
 
@@ -338,7 +346,7 @@ tablegen.Type = class {
 
     constructor(name) {
         this.name = name;
-        this.args = []; // template arguments
+        this.args = [];
     }
 
     toString() {
@@ -349,7 +357,7 @@ tablegen.Type = class {
     }
 };
 
-tablegen.Field = class {
+tablegen.RecordVal = class {
 
     constructor(name, type, value) {
         this.name = name;
@@ -370,32 +378,20 @@ tablegen.Record = class {
         this.parser = parser;
     }
 
-    getField(name) {
-        return this.fields.get(name);
+    // Returns the RecordVal for the given field name, or null if not found
+    // Matches C++ Record::getValue() which returns nullptr for missing fields
+    getValue(name) {
+        return this.fields.get(name) || null;
     }
 
-    hasField(name) {
-        return this.fields.has(name);
-    }
-
-    resolveField(name, visited = new Set()) {
-        if (!visited.has(this.name)) {
-            visited.add(this.name);
-            const field = this.fields.get(name);
-            if (field) {
-                return field;
-            }
-            for (const parent of this.parents) {
-                const parentRecord = this.parser.classes.get(parent.name);
-                if (parentRecord) {
-                    const parentField = parentRecord.resolveField(name, visited);
-                    if (parentField) {
-                        return parentField;
-                    }
-                }
-            }
+    // Returns the Init value for the given field name, or throws if not found
+    // Matches C++ Record::getValueInit() which throws PrintFatalError for missing fields
+    getValueInit(fieldName) {
+        const recordVal = this.getValue(fieldName);
+        if (!recordVal || !recordVal.value) {
+            throw new Error(`Record '${this.name}' does not have a field named '${fieldName}'`);
         }
-        return null;
+        return recordVal.value;
     }
 
     // Bind template parameters from parent class instantiation
@@ -432,25 +428,93 @@ tablegen.Record = class {
         }
     }
 
+    // Flatten parent class fields into this record (eager resolution)
+    // This matches the C++ TableGen behavior where parent fields are copied
+    // during record construction, eliminating the need for runtime parent walking
+    flattenParentFields() {
+        const visited = new Set();
+        const parentFields = new Map();
+
+        // Recursively collect fields from all parent classes
+        const collectParentFields = (record) => {
+            if (!record || visited.has(record.name)) {
+                return;
+            }
+            visited.add(record.name);
+
+            // Process parents first (depth-first) so closer parents override
+            for (const parent of record.parents) {
+                const parentClass = this.parser.classes.get(parent.name);
+                if (parentClass) {
+                    collectParentFields(parentClass);
+                }
+            }
+
+            // Add this record's fields to the parent fields map
+            // Later entries override earlier ones
+            for (const [name, field] of record.fields) {
+                parentFields.set(name, field);
+            }
+        };
+
+        // Collect fields from parents (not including this record itself)
+        for (const parent of this.parents) {
+            const parentClass = this.parser.classes.get(parent.name);
+            if (parentClass) {
+                collectParentFields(parentClass);
+            }
+        }
+
+        // Now merge parent fields into this record
+        // Own fields take precedence over parent fields
+        for (const [name, parentField] of parentFields) {
+            if (!this.fields.has(name)) {
+                this.fields.set(name, parentField);
+            }
+        }
+    }
+
+    // Returns evaluated string value, or null if field doesn't exist or wrong type
+    // Unlike C++ getValueAsString which throws, this returns null for convenience
     getValueAsString(fieldName) {
-        const field = this.resolveField(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;
+    }
+
+    // Returns evaluated bit value, or null if field doesn't exist or wrong type
+    // Unlike C++ getValueAsBit which throws, this returns null for convenience
+    getValueAsBit(fieldName) {
+        const field = this.getValue(fieldName);
         if (!field || !field.value) {
             return null;
         }
-        if (field.value.type === 'string') {
-            return field.value.value.replace(/^"|"$/g, '');
+        const evaluated = this.evaluateValue(field.value);
+        if (typeof evaluated === 'boolean') {
+            return evaluated;
+        }
+        if (typeof evaluated === 'number') {
+            return evaluated !== 0;
         }
         return null;
     }
 
-    getValueAsDef(fieldName) {
-        const field = this.resolveField(fieldName);
+    // Returns evaluated dag value, or null if field doesn't exist or wrong type
+    // Unlike C++ getValueAsDag which throws, this returns null for convenience
+    getValueAsDag(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;
-            return this.parser.getDef(defName) || this.parser.getClass(defName);
+        const evaluated = this.evaluateValue(field.value);
+        if (evaluated && typeof evaluated === 'object' && evaluated.operator) {
+            return evaluated;
         }
         return null;
     }
@@ -481,7 +545,7 @@ tablegen.Record = class {
                     return this.evaluateValue(this.templateBindings.get(fieldName));
                 }
                 // Otherwise, resolve as a field
-                const field = this.resolveField(fieldName);
+                const field = this.getValue(fieldName);
                 if (field && field.value) {
                     return this.evaluateValue(field.value);
                 }
@@ -501,7 +565,7 @@ tablegen.Record = class {
                         baseValue = this.evaluateValue(this.templateBindings.get(baseName));
                     } else {
                         // Try to resolve as a field
-                        const field = this.resolveField(baseName);
+                        const field = this.getValue(baseName);
                         if (field && field.value) {
                             baseValue = this.evaluateValue(field.value);
                         }
@@ -514,7 +578,7 @@ tablegen.Record = class {
                             // Navigate through the field path
                             let current = def;
                             for (const fieldName of fieldPath) {
-                                const field = current.resolveField(fieldName);
+                                const field = current.getValue(fieldName);
                                 if (!field || !field.value) {
                                     return null;
                                 }
@@ -537,7 +601,7 @@ tablegen.Record = class {
                     return this.evaluateValue(this.templateBindings.get(defName));
                 }
                 // Otherwise, resolve as a field or def
-                const field = this.resolveField(defName);
+                const field = this.getValue(defName);
                 if (field && field.value) {
                     return this.evaluateValue(field.value);
                 }
@@ -644,7 +708,7 @@ tablegen.Record = class {
                                 accValue = accumulator;
                             }
 
-                            this.fields.set(accName, new tablegen.Field(accName, null, accValue));
+                            this.fields.set(accName, new tablegen.RecordVal(accName, null, accValue));
 
                             // Wrap item in a Value so it can be used in expressions
                             let itemValue = item;
@@ -660,7 +724,7 @@ tablegen.Record = class {
                                 itemValue = new tablegen.Value('dag', item);
                             }
                             // If item is already a Value, use it as is
-                            this.fields.set(itemName, new tablegen.Field(itemName, null, itemValue));
+                            this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
 
                             // Evaluate the expression
                             accumulator = this.evaluateValue(args[4]);
@@ -714,7 +778,7 @@ tablegen.Record = class {
                                 itemValue = new tablegen.Value('dag', item);
                             }
                             // If item is already a Value, use it as is
-                            this.fields.set(itemName, new tablegen.Field(itemName, null, itemValue));
+                            this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
 
                             // Evaluate expression
                             const result = this.evaluateValue(args[2]);
@@ -761,7 +825,7 @@ tablegen.Record = class {
                                 itemValue = new tablegen.Value('dag', item);
                             }
                             // If item is already a Value, use it as is
-                            this.fields.set(itemName, new tablegen.Field(itemName, null, itemValue));
+                            this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
 
                             const keep = this.evaluateValue(args[2]);
                             if (keep) {
@@ -1006,6 +1070,8 @@ tablegen.Reader = class {
         }
         // Bind template parameters after parsing is complete
         def.bindTemplateParameters();
+        // Flatten parent fields into this record (eager resolution)
+        def.flattenParentFields();
         if (name) {
             this._defs.set(name, def);
             this.defs.push(def); // Also add to list to preserve duplicates
@@ -1132,7 +1198,7 @@ tablegen.Reader = class {
                 const name = this._expect('id');
                 this._expect('=');
                 const value = this._parseValue();
-                const field = new tablegen.Field(name, null, value);
+                const field = new tablegen.RecordVal(name, null, value);
                 record.fields.set(name, field);
                 this._eat(';');
             } else if (this._match('defvar')) {
@@ -1140,7 +1206,7 @@ tablegen.Reader = class {
                 const name = this._expect('id');
                 this._expect('=');
                 const value = this._parseValue();
-                const field = new tablegen.Field(name, null, value);
+                const field = new tablegen.RecordVal(name, null, value);
                 record.fields.set(name, field);
                 this._eat(';');
             } else if (this._match('assert')) {
@@ -1165,7 +1231,7 @@ tablegen.Reader = class {
                 if (this._eat('=')) {
                     value = this._parseValue();
                 }
-                const field = new tablegen.Field(name, type, value);
+                const field = new tablegen.RecordVal(name, type, value);
                 record.fields.set(name, field);
                 this._eat(';');
             } else {
@@ -1348,7 +1414,8 @@ tablegen.Reader = class {
                 if (this._eat(',')) {
                     continue;
                 }
-                const value = this._parseValue();
+                // 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._isKeyword(this._tokenizer.current().type)) {
@@ -1422,6 +1489,8 @@ tablegen.Reader = class {
             // 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]
@@ -1521,5 +1590,3 @@ tablegen.Error = class extends Error {
 };
 
 export const Reader = tablegen.Reader;
-export const Value = tablegen.Value;
-export const DAG = tablegen.DAG;

Some files were not shown because too many files changed in this diff