Repository: compiler-explorer/compiler-explorer (ts)

Issue:
[BUG]: i386-windows mangled symbols disappear
### Describe the bug

:seal:

### Steps to reproduce

```c
// Type your code here, or load an example.
void __attribute__((cdecl)) line(int num) {}
void __attribute__((stdcall)) square(int num) {}
void __attribute__((fastcall)) cube(int num) {}
void __attribute__((vectorcall)) tesseract(int num) {}
```
-xc -m32 -O2, with mingw clang or mingw gcc

### Expected behavior

```
_line:
        ret
_square@4:
        ret     4
@cube@4:
        ret
tesseract@@4:
        ret
```
Actual:
```
_line:
        ret

        ret     4

        ret

        ret
```
(The tesseract label shows up under GCC, but only because it doesn't recognize the vectorcall attribute at all.)

### Reproduction link

https://godbolt.org/z/E351vE5fK

### Screenshots

Not applicable

### Operating System

_No response_

### Browser version

_No response_

Pull request description:
Fix to ensure labels are not mistaken for comments
Ensured labels are not mistaken for comments by skipping comment filtering when the line matches a label definition

Added a regression test verifying that labels starting with “@” remain visible even when comment filtering is enabled

Fixes #7889 

Tested with 
npm run lint
npm run ts-check
npm run test-min
npm run check-frontend-imports
make check


Patch:
diff --git a/lib/cfg/cfg-parsers/llvm-ir.ts b/lib/cfg/cfg-parsers/llvm-ir.ts
index 4074007a6..021dd07b9 100644
--- a/lib/cfg/cfg-parsers/llvm-ir.ts
+++ b/lib/cfg/cfg-parsers/llvm-ir.ts
@@ -48,7 +48,7 @@ export class LlvmIrCfgParser extends BaseCFGParser {
         super(instructionSetInfo);
         this.functionDefinition = /^define .+ @("?[^"]+"?)\(/;
         this.labelRe = /^("?[\w$.-]+"?):\s*(;.*)?$/;
-        this.labelReference = /%("?[\w$.<>-]+"?)/g;
+        this.labelReference = /%"?([^, ]+)"?/g;
     }
 
     override filterData(asmArr: AssemblyLine[]) {
diff --git a/lib/cfg/instruction-sets/_all.ts b/lib/cfg/instruction-sets/_all.ts
index e9f3cc6e0..6caad4cb3 100644
--- a/lib/cfg/instruction-sets/_all.ts
+++ b/lib/cfg/instruction-sets/_all.ts
@@ -24,5 +24,6 @@
 
 export {ArmInstructionSetInfo} from './arm.js';
 export {BaseInstructionSetInfo} from './base.js';
+export {XtensaInstructionSetInfo} from './xtensa.js';
 export {LlvmIrInstructionSetInfo} from './llvm-ir.js';
 export {PythonInstructionSetInfo} from './python.js';
diff --git a/lib/cfg/instruction-sets/xtensa.ts b/lib/cfg/instruction-sets/xtensa.ts
new file mode 100644
index 000000000..0a6efce61
--- /dev/null
+++ b/lib/cfg/instruction-sets/xtensa.ts
@@ -0,0 +1,52 @@
+// 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 {InstructionSet} from '../../../types/instructionsets.js';
+import {BaseInstructionSetInfo, InstructionType} from './base.js';
+
+export class XtensaInstructionSetInfo extends BaseInstructionSetInfo {
+    static override get key(): InstructionSet {
+        return 'xtensa';
+    }
+
+    // Full list:  BALL, BNALL, BANY, BNONE, BBC, BBCI, BBS, BBSI, BEQ, BEQI, BEQZ, BNE, BNEI, BNEZ
+    // BGE, BGEI, BGEU, BGEUI, BGEZ, BLT, BLTI, BLTU, BLTUI, BLTZ
+    static conditionalJumps = /^\s*b/;
+
+    // whitespaces followed by 'j' or 'jx'
+    static unconditionalJumps = /^\s*jx?/;
+    static returnInstruction = /^\s*ret/;
+
+    override isJmpInstruction(instruction: string) {
+        const instype = this.getInstructionType(instruction);
+        return instype === InstructionType.jmp || instype === InstructionType.conditionalJmpInst;
+    }
+
+    override getInstructionType(instruction: string) {
+        if (XtensaInstructionSetInfo.conditionalJumps.test(instruction)) return InstructionType.conditionalJmpInst;
+        if (XtensaInstructionSetInfo.unconditionalJumps.test(instruction)) return InstructionType.jmp;
+        if (XtensaInstructionSetInfo.returnInstruction.test(instruction)) return InstructionType.retInst;
+        return InstructionType.notRetInst;
+    }
+}
diff --git a/lib/compilers/zig.ts b/lib/compilers/zig.ts
index 4370fff2f..4567ea547 100644
--- a/lib/compilers/zig.ts
+++ b/lib/compilers/zig.ts
@@ -119,11 +119,24 @@ export class ZigCompiler extends BaseCompiler {
         // strip '.s' if we aren't executing
         const name = filters.execute ? desiredName : desiredName.slice(0, -2);
 
+        // '-fno-strip' doesn't work on versions 0.7.0 - 0.9.0
+        if (this.compiler.semver) {
+            const ver = asSafeVer(this.compiler.semver);
+            if (Semver.gt(ver, '0.9.0', true) || Semver.lt(ver, '0.7.0', true)) {
+                options.push('-fno-strip');
+            }
+            if (Semver.gte(ver, '0.11.0', true) && !filters.binary) {
+                // Ensures assembtly dump, not needed up to 0.14.1 but added just in case
+                // (as of Jul 2025 needed for trunk)
+                options.push('-fllvm');
+            }
+        }
+
         if (this.self_hosted_cli) {
             // Versions after 0.6.0 use a different command line interface.
             const outputDir = path.dirname(outputFilename);
             // -fno-strip: Do not strip debug info
-            options.push('--cache-dir', outputDir, '--name', name, '-fno-strip');
+            options.push('--cache-dir', outputDir, '--name', name);
 
             if (filters.binary) {
                 options.push('-femit-bin=' + desiredName);
diff --git a/lib/parsers/asm-parser.ts b/lib/parsers/asm-parser.ts
index fe048ebbb..d18671bc9 100644
--- a/lib/parsers/asm-parser.ts
+++ b/lib/parsers/asm-parser.ts
@@ -261,6 +261,9 @@ export class AsmParser extends AsmRegex implements IAsmParser {
     }
 
     private shouldSkipCommentOnlyLine(filters: ParseFiltersAndOutputOptions, line: string): boolean {
+        if (this.labelDef.test(line)) {
+            return false;
+        }
         return Boolean(
             filters.commentOnly &&
                 ((this.commentOnly.test(line) && !this.parsingState.inNvccCode) ||
diff --git a/static/widgets/libs-widget.ts b/static/widgets/libs-widget.ts
index 9eb8f1279..ee77fe464 100644
--- a/static/widgets/libs-widget.ts
+++ b/static/widgets/libs-widget.ts
@@ -306,10 +306,10 @@ export class LibsWidget {
         this.setFavorites(faves);
     }
 
-    newFavoriteLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<Node> {
+    newFavoriteLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<HTMLElement> {
         const template = $('#lib-favorite-tpl');
 
-        const libDiv = $(template.children()[0].cloneNode(true));
+        const libDiv = $(template.children().eq(0).clone());
 
         const quickSelectButton = libDiv.find('.lib-name-and-version');
         quickSelectButton.html(lib.name + ' ' + version.version);
@@ -334,7 +334,7 @@ export class LibsWidget {
                 if (lib) {
                     if (versionId in lib.versions) {
                         const version = lib.versions[versionId];
-                        const div: any = this.newFavoriteLibDiv(libId, versionId, lib, version);
+                        const div = this.newFavoriteLibDiv(libId, versionId, lib, version);
                         favoritesDiv.append(div);
                     }
                 }
@@ -354,10 +354,10 @@ export class LibsWidget {
         this.searchResults.html('');
     }
 
-    newSelectedLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<Node> {
+    newSelectedLibDiv(libId: string, versionId: string, lib: Library, version: LibraryVersion): JQuery<HTMLElement> {
         const template = $('#lib-selected-tpl');
 
-        const libDiv = $(template.children()[0].cloneNode(true));
+        const libDiv = $(template.children().eq(0).clone());
 
         const detailsButton = libDiv.find('.lib-name-and-version');
         detailsButton.html(lib.name + ' ' + version.version);
@@ -379,7 +379,7 @@ export class LibsWidget {
         return libDiv;
     }
 
-    conjureUpExamples(result: JQuery<Node>, lib: Library) {
+    conjureUpExamples(result: JQuery<HTMLElement>, lib: Library) {
         const examples = result.find('.lib-examples');
         if (lib.examples && lib.examples.length > 0) {
             examples.append($('<b>Examples</b>'));
@@ -435,10 +435,10 @@ export class LibsWidget {
         $('#' + popupId).html(libInfoText);
     }
 
-    newSearchResult(libId: string, lib: Library): JQuery<Node> {
+    newSearchResult(libId: string, lib: Library): JQuery<HTMLElement> {
         const template = $('#lib-search-result-tpl');
 
-        const result = $($(template.children()[0].cloneNode(true)));
+        const result = $(template.children().eq(0).clone());
         result.find('.lib-name').html(lib.name || libId);
         if (!lib.description) {
             result.find('.lib-description').hide();
@@ -564,9 +564,7 @@ export class LibsWidget {
     }
 
     addSearchResult(libId: string, library: Library) {
-        // FIXME: Type mismatch.
-        // The any here stops TS from complaining
-        const result: any = this.newSearchResult(libId, library);
+        const result = this.newSearchResult(libId, library);
         this.searchResults.append(result);
     }
 
@@ -582,7 +580,7 @@ export class LibsWidget {
 
         const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
         if (Object.keys(currentAvailableLibs).length === 0) {
-            const nolibsMessage: any = $($('#libs-dropdown').children()[0].cloneNode(true));
+            const nolibsMessage = $('#libs-dropdown').children().eq(0).clone();
             this.searchResults.append(nolibsMessage);
             return;
         }
@@ -609,7 +607,7 @@ export class LibsWidget {
             const lib = this.availableLibs[this.currentLangId][this.currentCompilerId][libId];
             const version = lib.versions[versionId];
 
-            const libDiv: any = this.newSelectedLibDiv(libId, versionId, lib, version);
+            const libDiv = this.newSelectedLibDiv(libId, versionId, lib, version);
             items.append(libDiv);
         }
     }
@@ -619,7 +617,7 @@ export class LibsWidget {
 
         const currentAvailableLibs = this.availableLibs[this.currentLangId][this.currentCompilerId];
         if (Object.keys(currentAvailableLibs).length === 0) {
-            const nolibsMessage: any = $($('#libs-dropdown').children()[0].cloneNode(true));
+            const nolibsMessage = $('#libs-dropdown').children().eq(0).clone();
             this.searchResults.append(nolibsMessage);
             return;
         }
@@ -629,7 +627,7 @@ export class LibsWidget {
 
             if ('autodetect' in library.versions) continue;
 
-            const card: any = this.newSearchResult(libId, library);
+            const card = this.newSearchResult(libId, library);
             this.searchResults.append(card);
         }
     }


Test patch:
diff --git a/test/asm-parser-tests.ts b/test/asm-parser-tests.ts
index df4e2e111..b6956c2cf 100644
--- a/test/asm-parser-tests.ts
+++ b/test/asm-parser-tests.ts
@@ -43,6 +43,17 @@ describe('AsmParser tests', () => {
     });
 });
 
+describe('AsmParser comment filtering', () => {
+    const parser = new AsmParser();
+    it('should keep label lines starting with @ when filtering comments', () => {
+        const input = '@cube@4:\n    ret';
+        const result = parser.processAsm(input, {commentOnly: true});
+        const lines = result.asm.map(line => line.text);
+        expect(lines[0]).toBe('@cube@4:');
+        expect(lines[1]).toBe('    ret');
+    });
+});
+
 describe('PTXAsmParser tests', () => {
     const parser = new PTXAsmParser();
 
diff --git a/test/cfg-cases/cfg-xtensa.loop.json b/test/cfg-cases/cfg-xtensa.loop.json
new file mode 100644
index 000000000..eed500f5a
--- /dev/null
+++ b/test/cfg-cases/cfg-xtensa.loop.json
@@ -0,0 +1,177 @@
+{
+    "asm": [
+        {
+            "text": "square(int, int*, int&):",
+            "source": null,
+            "labels": []
+        },
+        {
+            "text": "        entry   sp, 32",
+            "source": {
+                "file": null,
+                "line": 2,
+                "column": 37
+            },
+            "labels": []
+        },
+        {
+            "text": "        blti    a2, 1, .L1",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": [
+                {
+                    "name": ".L1",
+                    "range": {
+                        "startCol": 24,
+                        "endCol": 27
+                    }
+                }
+            ]
+        },
+        {
+            "text": "        slli    a8, a2, 2",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": "        addi    a8, a8, -4",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": "        srli    a8, a8, 2",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": "        addi.n  a8, a8, 1",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": "        loop    a8, .L3_LEND",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": "        l32i.n  a2, a3, 0",
+            "source": {
+                "file": null,
+                "line": 4,
+                "column": 14
+            },
+            "labels": []
+        },
+        {
+            "text": "        l32i.n  a9, a4, 0",
+            "source": {
+                "file": null,
+                "line": 4,
+                "column": 14
+            },
+            "labels": []
+        },
+        {
+            "text": "        add.n   a2, a2, a9",
+            "source": {
+                "file": null,
+                "line": 4,
+                "column": 14
+            },
+            "labels": []
+        },
+        {
+            "text": "        s32i.n  a2, a3, 0",
+            "source": {
+                "file": null,
+                "line": 4,
+                "column": 14
+            },
+            "labels": []
+        },
+        {
+            "text": "        addi.n  a3, a3, 4",
+            "source": {
+                "file": null,
+                "line": 3,
+                "column": 20
+            },
+            "labels": []
+        },
+        {
+            "text": ".L1:",
+            "source": null,
+            "labels": []
+        },
+        {
+            "text": "        retw.n",
+            "source": {
+                "file": null,
+                "line": 5,
+                "column": 1
+            },
+            "labels": []
+        }
+    ],
+    "cfg": {
+        "square(int, int*, int&):": {
+            "nodes": [
+                {
+                    "id": "square(int, int*, int&):",
+                    "label": "square(int, int*, int&):\n        entry   sp, 32\n        blti    a2, 1, .L1"
+                },
+                {
+                    "id": "square(int, int*, int&):@3",
+                    "label": "square(int, int*, int&):@3\n        slli    a8, a2, 2\n        addi    a8, a8, -4\n        srli    a8, a8, 2\n        addi.n  a8, a8, 1\n        loop    a8, .L3_LEND\n        l32i.n  a2, a3, 0\n        l32i.n  a9, a4, 0\n        add.n   a2, a2, a9\n        s32i.n  a2, a3, 0\n        addi.n  a3, a3, 4"
+                },
+                {
+                    "id": ".L1:",
+                    "label": ".L1:\n        retw.n"
+                }
+            ],
+            "edges": [
+                {
+                    "from": "square(int, int*, int&):",
+                    "to": ".L1:",
+                    "arrows": "to",
+                    "color": "green"
+                },
+                {
+                    "from": "square(int, int*, int&):",
+                    "to": "square(int, int*, int&):@3",
+                    "arrows": "to",
+                    "color": "red"
+                },
+                {
+                    "from": "square(int, int*, int&):@3",
+                    "to": ".L1:",
+                    "arrows": "to",
+                    "color": "grey"
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/cfg-tests.ts b/test/cfg-tests.ts
index 809994957..57bb35cf3 100644
--- a/test/cfg-tests.ts
+++ b/test/cfg-tests.ts
@@ -91,4 +91,20 @@ describe('Cfg test cases', () => {
             });
         }
     });
+
+    describe('xtensa', () => {
+        // instructionSet is a real value, group/version/compilerType just need to be distinct from others
+        const xtensaCompilerInfo = makeFakeCompilerInfo({
+            instructionSet: 'xtensa',
+            group: 'xtensa',
+            version: 'xtensa',
+            compilerType: 'xtensa',
+        });
+
+        for (const filename of files.filter(x => x.includes('xtensa'))) {
+            it(filename, async () => {
+                await DoCfgTest('python', path.join(testcasespath, filename), false, xtensaCompilerInfo);
+            });
+        }
+    });
 });