mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
Add baseline functional tests for AsmParser subclasses (#7779)
## Summary
These tests document the expected functional behavior of AsmParser
subclasses before refactoring work begins, focusing on public API
behavior rather than implementation details.
## Changes
**Test Coverage Added:**
- **VcAsmParser**: Tests VC assembly format with PROC/ENDP structure,
directive handling, comment filtering
- **AsmEWAVRParser**: Tests EWAVR assembly format with label structure,
RSEG/PUBLIC/EXTERN directives
- **SPIRVAsmParser**: Tests SPIR-V assembly with OpFunction/OpLabel
structure, custom %label syntax detection
- **Integration tests**: Cross-parser compatibility and real test case
handling
**Key Insights Documented:**
- VcAsmParser & AsmEWAVRParser have completely custom `processAsm()`
implementations that don't use base class methods
- SPIRVAsmParser uses base class `processAsm()` but overrides
`getUsedLabelsInLine()` for custom label detection
- Different return formats: VC/EWAVR return `{asm: [...]}`, SPIRV
returns full `ParsedAsmResult` with `labelDefinitions`
## Test Plan
```bash
npm run test -- --run vc-asm-parser-tests.ts ewavr-asm-parser-tests.ts spirv-asm-parser-tests.ts asm-parser-subclass-integration-tests.ts
```
These tests establish the baseline behavior that must be preserved
during the upcoming AsmParser refactoring work to ensure subclass
compatibility.
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
161
test/asm-parser-subclass-integration-tests.ts
Normal file
161
test/asm-parser-subclass-integration-tests.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {describe, expect, it} from 'vitest';
|
||||||
|
|
||||||
|
import {AsmEWAVRParser} from '../lib/parsers/asm-parser-ewavr.js';
|
||||||
|
import {SPIRVAsmParser} from '../lib/parsers/asm-parser-spirv.js';
|
||||||
|
import {VcAsmParser} from '../lib/parsers/asm-parser-vc.js';
|
||||||
|
import {AsmParser} from '../lib/parsers/asm-parser.js';
|
||||||
|
import * as properties from '../lib/properties.js';
|
||||||
|
|
||||||
|
// Helper functions to reduce test duplication
|
||||||
|
function initializeParserAndFindLabels<T extends AsmParser>(
|
||||||
|
ParserClass: new (...args: any[]) => T,
|
||||||
|
constructorArgs: any[],
|
||||||
|
asmLines: string[],
|
||||||
|
): Set<string> {
|
||||||
|
const parser = new ParserClass(...constructorArgs);
|
||||||
|
return parser.findUsedLabels(asmLines, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processAsmWithParser<T extends AsmParser>(
|
||||||
|
ParserClass: new (...args: any[]) => T,
|
||||||
|
constructorArgs: any[],
|
||||||
|
code: string,
|
||||||
|
options: any,
|
||||||
|
) {
|
||||||
|
const parser = new ParserClass(...constructorArgs);
|
||||||
|
return parser.processAsm(code, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AsmParser subclass compatibility', () => {
|
||||||
|
describe('labelFindFor method behavior', () => {
|
||||||
|
it('should use VcAsmParser labelFindFor override to find specific VC labels', () => {
|
||||||
|
const asmLines = ['_start:', 'mov eax, OFFSET _data', 'call _function', 'jmp _start'];
|
||||||
|
const usedLabels = initializeParserAndFindLabels(VcAsmParser, [], asmLines);
|
||||||
|
|
||||||
|
// VC-specific label detection should find these labels
|
||||||
|
expect(usedLabels.has('_data')).toBe(true);
|
||||||
|
expect(usedLabels.has('_function')).toBe(true);
|
||||||
|
expect(usedLabels.has('_start')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should demonstrate EWAVR labelFindFor bug prevents finding label usage', () => {
|
||||||
|
const asmLines = [
|
||||||
|
'_data: .word 0x1234',
|
||||||
|
'_main:',
|
||||||
|
' ldi r16, HIGH(_data)',
|
||||||
|
' ldi r17, LOW(_data)',
|
||||||
|
' call _subroutine',
|
||||||
|
' rjmp _main',
|
||||||
|
];
|
||||||
|
const usedLabels = initializeParserAndFindLabels(AsmEWAVRParser, [properties.fakeProps({})], asmLines);
|
||||||
|
|
||||||
|
// Bug: finds no labels because labelFindFor() returns definition regex
|
||||||
|
expect(usedLabels.size).toBe(0);
|
||||||
|
expect(usedLabels.has('_data')).toBe(false);
|
||||||
|
expect(usedLabels.has('_subroutine')).toBe(false);
|
||||||
|
expect(usedLabels.has('_main')).toBe(false);
|
||||||
|
|
||||||
|
// The bug is that EWAVR's labelFindFor looks for lines ending with ':'
|
||||||
|
// instead of finding label references in instructions
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show base class finds all identifier-like tokens as potential labels', () => {
|
||||||
|
const asmLines = ['_start:', ' call printf', ' mov eax, value', ' jmp _start'];
|
||||||
|
const usedLabels = initializeParserAndFindLabels(AsmParser, [], asmLines);
|
||||||
|
|
||||||
|
// Base class finds ALL identifier-like tokens, not just actual labels
|
||||||
|
expect(usedLabels.has('call')).toBe(true); // Instruction (not a label)
|
||||||
|
expect(usedLabels.has('printf')).toBe(true); // Actual label reference
|
||||||
|
expect(usedLabels.has('mov')).toBe(true); // Instruction (not a label)
|
||||||
|
expect(usedLabels.has('eax')).toBe(true); // Register (not a label)
|
||||||
|
expect(usedLabels.has('value')).toBe(true); // Actual label reference
|
||||||
|
expect(usedLabels.has('jmp')).toBe(true); // Instruction (not a label)
|
||||||
|
expect(usedLabels.has('_start')).toBe(true); // Actual label reference
|
||||||
|
|
||||||
|
// This over-matching is why subclasses override labelFindFor
|
||||||
|
// to be more specific about what constitutes a label in their syntax
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('processAsm custom implementations', () => {
|
||||||
|
it('should verify VcAsmParser has different output format than base class', () => {
|
||||||
|
const testCode = ['mov eax, OFFSET _data', 'call _function'].join('\n');
|
||||||
|
const options = {directives: true, labels: false};
|
||||||
|
|
||||||
|
const baseResult = processAsmWithParser(AsmParser, [], testCode, options);
|
||||||
|
const vcResult = processAsmWithParser(VcAsmParser, [], testCode, options);
|
||||||
|
|
||||||
|
// Base class returns labelDefinitions, VC parser does not
|
||||||
|
expect(baseResult).toHaveProperty('labelDefinitions');
|
||||||
|
expect(vcResult.labelDefinitions).toBeUndefined();
|
||||||
|
|
||||||
|
// Base parser tracks label definitions it finds
|
||||||
|
expect(baseResult.labelDefinitions).toEqual({});
|
||||||
|
|
||||||
|
// Both should preserve the actual assembly instructions
|
||||||
|
const baseMovLine = baseResult.asm.find(line => line.text?.includes('mov'));
|
||||||
|
const vcMovLine = vcResult.asm.find(line => line.text?.includes('mov'));
|
||||||
|
|
||||||
|
expect(baseMovLine?.text).toContain('OFFSET _data');
|
||||||
|
expect(vcMovLine?.text).toContain('OFFSET _data');
|
||||||
|
|
||||||
|
// Verify parsingTime and filteredCount are returned
|
||||||
|
expect(typeof baseResult.parsingTime).toBe('number');
|
||||||
|
expect(typeof baseResult.filteredCount).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify SPIRVAsmParser getUsedLabelsInLine detects percent labels', () => {
|
||||||
|
const spirvCode = ['OpBranch %exit_label', '%exit_label = OpLabel', 'OpReturn'].join('\n');
|
||||||
|
|
||||||
|
const result = processAsmWithParser(SPIRVAsmParser, [], spirvCode, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should correctly identify SPIR-V %label syntax in definitions
|
||||||
|
expect(result.labelDefinitions).toBeDefined();
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%exit_label');
|
||||||
|
expect(result.labelDefinitions!['%exit_label']).toBe(2); // Line number of definition
|
||||||
|
|
||||||
|
// Should find the branch target in the labels array
|
||||||
|
const branchLine = result.asm.find(line => line.text === 'OpBranch %exit_label');
|
||||||
|
expect(branchLine).toBeDefined();
|
||||||
|
expect(branchLine?.labels).toHaveLength(1);
|
||||||
|
expect(branchLine?.labels?.[0].name).toBe('%exit_label');
|
||||||
|
expect(branchLine?.labels?.[0].range).toBeDefined();
|
||||||
|
expect(branchLine?.labels?.[0].range.startCol).toBeGreaterThan(0);
|
||||||
|
const startCol = branchLine?.labels?.[0].range.startCol;
|
||||||
|
expect(startCol).toBeDefined();
|
||||||
|
expect(branchLine?.labels?.[0].range.endCol).toBeGreaterThan(startCol!);
|
||||||
|
|
||||||
|
// OpReturn should have no labels
|
||||||
|
const returnLine = result.asm.find(line => line.text === 'OpReturn');
|
||||||
|
expect(returnLine?.labels).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
199
test/ewavr-asm-parser-tests.ts
Normal file
199
test/ewavr-asm-parser-tests.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
|
|
||||||
|
import {AsmEWAVRParser} from '../lib/parsers/asm-parser-ewavr.js';
|
||||||
|
import * as properties from '../lib/properties.js';
|
||||||
|
|
||||||
|
describe('AsmEWAVRParser', () => {
|
||||||
|
let parser: AsmEWAVRParser;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser = new AsmEWAVRParser(properties.fakeProps({}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EWAVR assembly processing functionality', () => {
|
||||||
|
it('should process EWAVR assembly and preserve AVR instruction formats', () => {
|
||||||
|
const ewavrAssembly = [
|
||||||
|
'_main:',
|
||||||
|
' ldi r16, 0xFF',
|
||||||
|
' call _function',
|
||||||
|
' ret',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should preserve specific EWAVR/AVR instruction formats
|
||||||
|
const ldiInstruction = result.asm.find(line => line.text?.includes('ldi'));
|
||||||
|
expect(ldiInstruction?.text).toContain('r16, 0xFF');
|
||||||
|
|
||||||
|
const callInstruction = result.asm.find(line => line.text?.includes('call'));
|
||||||
|
expect(callInstruction?.text).toContain('_function');
|
||||||
|
|
||||||
|
// AsmEWAVRParser has custom processAsm that doesn't return labelDefinitions
|
||||||
|
expect(result.labelDefinitions).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle EWAVR-specific directives filtering', () => {
|
||||||
|
const ewavrAssembly = [
|
||||||
|
'RSEG CODE',
|
||||||
|
'PUBLIC _function',
|
||||||
|
'_function:',
|
||||||
|
' ldi r16, HIGH(_external_var)',
|
||||||
|
' END',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const resultWithDirectives = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: false, // Include directives
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultFilteringDirectives = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: true, // Filter out directives
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// When filtering directives, should have fewer lines
|
||||||
|
expect(resultFilteringDirectives.asm.length).toBeLessThan(resultWithDirectives.asm.length);
|
||||||
|
|
||||||
|
// Should preserve the HIGH() function call format
|
||||||
|
const hasHighFunction = resultWithDirectives.asm.some(line => line.text?.includes('HIGH('));
|
||||||
|
expect(hasHighFunction).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter EWAVR comments when commentOnly is true', () => {
|
||||||
|
const ewavrAssembly = [
|
||||||
|
'// This is a comment',
|
||||||
|
'_main:',
|
||||||
|
' ldi r16, 42',
|
||||||
|
' ret',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const resultWithComments = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false, // Include comments
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultFilteringComments = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: true, // Filter out comments
|
||||||
|
});
|
||||||
|
|
||||||
|
// When filtering comments, should have fewer lines
|
||||||
|
expect(resultFilteringComments.asm.length).toBeLessThan(resultWithComments.asm.length);
|
||||||
|
|
||||||
|
// Should still have the actual instruction
|
||||||
|
const hasLdiInstruction = resultFilteringComments.asm.some(line => line.text?.includes('ldi'));
|
||||||
|
expect(hasLdiInstruction).toBe(true);
|
||||||
|
|
||||||
|
// Should not have comment lines when filtering
|
||||||
|
const hasCommentLine = resultFilteringComments.asm.some(line => line.text?.startsWith('//'));
|
||||||
|
expect(hasCommentLine).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EWAVR assembly processing', () => {
|
||||||
|
it('should preserve EWAVR AVR instruction operands and labels', () => {
|
||||||
|
const ewavrAssembly = [
|
||||||
|
'_main:',
|
||||||
|
' ldi r16, 0xFF',
|
||||||
|
' out PORTB, r16',
|
||||||
|
' call _delay',
|
||||||
|
'delay_loop:',
|
||||||
|
' brne delay_loop',
|
||||||
|
' ret',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = parser.processAsm(ewavrAssembly, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should preserve specific AVR instruction formats
|
||||||
|
const outInstruction = result.asm.find(line => line.text?.includes('out'));
|
||||||
|
expect(outInstruction?.text).toContain('PORTB, r16');
|
||||||
|
|
||||||
|
const branchInstruction = result.asm.find(line => line.text?.includes('brne'));
|
||||||
|
expect(branchInstruction?.text).toContain('delay_loop');
|
||||||
|
|
||||||
|
// AsmEWAVRParser has custom processAsm format
|
||||||
|
expect(result.labelDefinitions).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should demonstrate EWAVR labelFindFor bug with label usage detection', () => {
|
||||||
|
const asmLines = ['ldi r16, HIGH(_data)', 'ldi r17, LOW(_data)', 'call _subroutine', 'rjmp _loop'];
|
||||||
|
|
||||||
|
const usedLabels = parser.findUsedLabels(asmLines, true);
|
||||||
|
|
||||||
|
// EWAVR labelFindFor() bug: returns definition regex instead of usage regex
|
||||||
|
// This causes findUsedLabels to find no labels in usage contexts
|
||||||
|
expect(usedLabels.has('_data')).toBe(false);
|
||||||
|
expect(usedLabels.has('_subroutine')).toBe(false);
|
||||||
|
expect(usedLabels.has('_loop')).toBe(false);
|
||||||
|
expect(usedLabels.size).toBe(0);
|
||||||
|
|
||||||
|
// The regex is designed for definitions (with colons) not usage
|
||||||
|
const labelFindRegex = parser.labelFindFor();
|
||||||
|
expect(labelFindRegex.test('_data:')).toBe(true); // Matches definitions
|
||||||
|
expect(labelFindRegex.test('_data')).toBe(false); // Doesn't match usage
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle EWAVR segment syntax and register operations', () => {
|
||||||
|
const ewavrCode = [
|
||||||
|
"CODE32 segment 'CODE'",
|
||||||
|
'public _main',
|
||||||
|
'_main:',
|
||||||
|
' ldi r16, 0xFF',
|
||||||
|
' out DDRC, r16',
|
||||||
|
' CODE32 ends',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = parser.processAsm(ewavrCode, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should preserve AVR register operations
|
||||||
|
const ldiInstruction = result.asm.find(line => line.text?.includes('ldi'));
|
||||||
|
expect(ldiInstruction?.text).toContain('r16, 0xFF');
|
||||||
|
|
||||||
|
const outInstruction = result.asm.find(line => line.text?.includes('out'));
|
||||||
|
expect(outInstruction?.text).toContain('DDRC, r16');
|
||||||
|
|
||||||
|
// AsmEWAVRParser uses custom format
|
||||||
|
expect(result.labelDefinitions).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
164
test/spirv-asm-parser-tests.ts
Normal file
164
test/spirv-asm-parser-tests.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
|
||||||
|
import {SPIRVAsmParser} from '../lib/parsers/asm-parser-spirv.js';
|
||||||
|
|
||||||
|
describe('SPIRVAsmParser', () => {
|
||||||
|
let parser: SPIRVAsmParser;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser = new SPIRVAsmParser();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUsedLabelsInLine override', () => {
|
||||||
|
it('should detect labels in OpFunctionCall', () => {
|
||||||
|
const line = 'OpFunctionCall %void %func_main';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(1);
|
||||||
|
expect(labels[0].name).toBe('%func_main');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect labels in OpBranch', () => {
|
||||||
|
const line = 'OpBranch %label_endif';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(1);
|
||||||
|
expect(labels[0].name).toBe('%label_endif');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect multiple labels in OpBranchConditional', () => {
|
||||||
|
const line = 'OpBranchConditional %cond %true_label %false_label';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(2);
|
||||||
|
expect(labels[0].name).toBe('%true_label');
|
||||||
|
expect(labels[1].name).toBe('%false_label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect labels in OpSelectionMerge', () => {
|
||||||
|
const line = 'OpSelectionMerge %merge_label None';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(1);
|
||||||
|
expect(labels[0].name).toBe('%merge_label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect labels in OpLoopMerge', () => {
|
||||||
|
const line = 'OpLoopMerge %merge_label %continue_label None';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(2);
|
||||||
|
expect(labels[0].name).toBe('%merge_label');
|
||||||
|
expect(labels[1].name).toBe('%continue_label');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect labels in OpSwitch', () => {
|
||||||
|
const line = 'OpSwitch %selector %default 1 %case1 2 %case2';
|
||||||
|
const labels = parser.getUsedLabelsInLine(line);
|
||||||
|
|
||||||
|
expect(labels).toHaveLength(3);
|
||||||
|
expect(labels[0].name).toBe('%default');
|
||||||
|
expect(labels[1].name).toBe('%case1');
|
||||||
|
expect(labels[2].name).toBe('%case2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be called during assembly processing', () => {
|
||||||
|
const spy = vi.spyOn(parser, 'getUsedLabelsInLine');
|
||||||
|
|
||||||
|
const spirvCode = `
|
||||||
|
OpBranch %exit_label
|
||||||
|
%exit_label = OpLabel
|
||||||
|
`;
|
||||||
|
|
||||||
|
parser.processAsm(spirvCode, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SPIR-V assembly processing', () => {
|
||||||
|
it('should detect SPIR-V percent labels with custom getUsedLabelsInLine', () => {
|
||||||
|
const spirvCode = [
|
||||||
|
'%main = OpFunction %void None %1',
|
||||||
|
'%entry = OpLabel',
|
||||||
|
'OpBranch %exit',
|
||||||
|
'%exit = OpLabel',
|
||||||
|
'OpReturn',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = parser.processAsm(spirvCode, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should detect SPIR-V %label definitions
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%main');
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%entry');
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%exit');
|
||||||
|
|
||||||
|
// Should detect SPIR-V %label usage in OpBranch
|
||||||
|
const branchLine = result.asm.find(line => line.text?.includes('OpBranch'));
|
||||||
|
expect(branchLine?.labels?.some(label => label.name === '%exit')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle SPIR-V control flow instructions with multiple label references', () => {
|
||||||
|
const spirvCode = [
|
||||||
|
'OpSelectionMerge %merge None',
|
||||||
|
'OpBranchConditional %condition %true_block %false_block',
|
||||||
|
'%true_block = OpLabel',
|
||||||
|
'%false_block = OpLabel',
|
||||||
|
'%merge = OpLabel',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = parser.processAsm(spirvCode, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should identify all label definitions
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%true_block');
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%false_block');
|
||||||
|
expect(result.labelDefinitions).toHaveProperty('%merge');
|
||||||
|
|
||||||
|
// Should detect multiple labels in OpBranchConditional
|
||||||
|
const branchCondLine = result.asm.find(line => line.text?.includes('OpBranchConditional'));
|
||||||
|
const labelNames = branchCondLine?.labels?.map(label => label.name) || [];
|
||||||
|
expect(labelNames).toContain('%true_block');
|
||||||
|
expect(labelNames).toContain('%false_block');
|
||||||
|
|
||||||
|
// Should detect label in OpSelectionMerge
|
||||||
|
const mergeLine = result.asm.find(line => line.text?.includes('OpSelectionMerge'));
|
||||||
|
expect(mergeLine?.labels?.some(label => label.name === '%merge')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
119
test/vc-asm-parser-tests.ts
Normal file
119
test/vc-asm-parser-tests.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
|
|
||||||
|
import {VcAsmParser} from '../lib/parsers/asm-parser-vc.js';
|
||||||
|
import {AsmParser} from '../lib/parsers/asm-parser.js';
|
||||||
|
|
||||||
|
describe('VcAsmParser', () => {
|
||||||
|
let parser: VcAsmParser;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser = new VcAsmParser();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('VC assembly processing functionality', () => {
|
||||||
|
it('should have custom processAsm that returns different format than base class', () => {
|
||||||
|
// Simple test that doesn't trigger complex VC parsing logic
|
||||||
|
const simpleAsm = 'nop';
|
||||||
|
|
||||||
|
const baseParser = new AsmParser();
|
||||||
|
const baseResult = baseParser.processAsm(simpleAsm, {
|
||||||
|
directives: false,
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Base parser returns labelDefinitions, VC parser format is different
|
||||||
|
expect(baseResult).toHaveProperty('labelDefinitions');
|
||||||
|
expect(typeof baseResult.labelDefinitions).toBe('object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle VC-specific directives correctly', () => {
|
||||||
|
const vcAssembly = ['PUBLIC _function', '_data\tSEGMENT', '_variable\tDD\t42', '_data\tENDS'].join('\n');
|
||||||
|
|
||||||
|
const resultWithDirectives = parser.processAsm(vcAssembly, {
|
||||||
|
directives: false, // Should include directives
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultFilteringDirectives = parser.processAsm(vcAssembly, {
|
||||||
|
directives: true, // Should filter out directives
|
||||||
|
labels: false,
|
||||||
|
commentOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// When filtering directives, should have fewer lines
|
||||||
|
expect(resultFilteringDirectives.asm.length).toBeLessThan(resultWithDirectives.asm.length);
|
||||||
|
|
||||||
|
// Should still preserve the data declaration when not filtering
|
||||||
|
const hasDataDecl = resultWithDirectives.asm.some(line => line.text?.includes('DD'));
|
||||||
|
expect(hasDataDecl).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly identify VC comments using commentOnly regex', () => {
|
||||||
|
const commentLine = '; This is a VC comment';
|
||||||
|
const indentedComment = ' ; Indented comment';
|
||||||
|
const codeLine = 'mov eax, ebx';
|
||||||
|
|
||||||
|
// VC commentOnly regex is /^;/ - only matches lines starting with ;
|
||||||
|
expect(parser.commentOnly.test(commentLine)).toBe(true);
|
||||||
|
expect(parser.commentOnly.test(codeLine)).toBe(false);
|
||||||
|
|
||||||
|
// VC regex doesn't match comments with leading whitespace
|
||||||
|
expect(parser.commentOnly.test(indentedComment)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('VC assembly processing', () => {
|
||||||
|
it('should recognize VC function definitions with PROC keyword', () => {
|
||||||
|
const procLine = '_function\tPROC';
|
||||||
|
const nonProcLine = '_function:';
|
||||||
|
|
||||||
|
// Test the function definition regex directly
|
||||||
|
expect(parser.definesFunction.test(procLine)).toBe(true);
|
||||||
|
expect(parser.definesFunction.test(nonProcLine)).toBe(false);
|
||||||
|
|
||||||
|
// Should extract function name
|
||||||
|
const match = procLine.match(parser.definesFunction);
|
||||||
|
expect(match?.[1]).toBe('_function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find labels in VC-specific syntax using custom labelFindFor', () => {
|
||||||
|
const asmLines = ['mov eax, OFFSET _data', 'mov ebx, DWORD PTR _variable', 'call _function'];
|
||||||
|
|
||||||
|
const usedLabels = parser.findUsedLabels(asmLines, true);
|
||||||
|
|
||||||
|
// VcAsmParser should find the main labels we're looking for
|
||||||
|
expect(usedLabels.has('_data')).toBe(true);
|
||||||
|
expect(usedLabels.has('_variable')).toBe(true);
|
||||||
|
expect(usedLabels.has('_function')).toBe(true);
|
||||||
|
|
||||||
|
// May find additional labels/symbols in VC syntax - that's expected behavior
|
||||||
|
expect(usedLabels.size).toBeGreaterThanOrEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user