Repository: compiler-explorer/compiler-explorer
Pull request #7423 (2025-02-23 09:42:02)

Add C++ library `Proxy`
Add library Proxy since version 3.0.0.

https://github.com/compiler-explorer/infra/pull/1534


Patch:
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 2854e0f23..969be39db 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -155,3 +155,4 @@ From oldest to newest contributor, we would like to thank:
 - [Vipul Cariappa](https://github.com/Vipul-Cariappa)
 - [Niles Salter](https://github.com/Validark)
 - [Stephen Heumann](https://github.com/sheumann)
+- [Joshua Batty](https://github.com/joshuabatty)
diff --git a/etc/config/c++.amazon.properties b/etc/config/c++.amazon.properties
index 6721b3b87..89997064a 100644
--- a/etc/config/c++.amazon.properties
+++ b/etc/config/c++.amazon.properties
@@ -3979,7 +3979,7 @@ compiler.qnx800.qnxLicense=/opt/qnx/licenses
 #################################
 #################################
 # Installed libs
-libs=abseil:array:async_simple:belleviews:beman_iterator_interface:beman_optional:benchmark:benri:blaze:boost:bmulti:brigand:catch2:cccl:cctz:cereal:cmcstl2:cnl:cppcoro:cppitertools:cpptrace:crosscables:ctbignum:cthash:ctre:date:dataframe:dawjson:dlib:doctest:eastl:eigen:enoki:entt:etl:eve:expected_lite:fastor:flux:fmt:gcem:gemmlowp:glaze:glm:gnufs:gnulibbacktrace:gnuexp:googletest:gsl:hdf5:hedley:hfsm:highfive:highway:hotels-template-library:immer:jsoncons:jsoncpp:kiwaku:kokkos:kumi:kvasir:kyosu:lager:lagom:lexy:libassert:libbpf:libguarded:libsimdpp:libuv:llvm:llvmfs:lua:magic_enum:mfem:mimicpp:mlir:mp-coro:mp-units:namedtype:nanorange:nlohmann_json:nsimd:ofw:openssl:outcome:pegtl:pipes:pugixml:pybind11:python:rangesv3:raberu:reactive_plus_plus:scnlib:seastar:seqan3:simde:simdjson:sol2:spdlog:spy:stdexec:strong_type:taojson:taskflow:tbb:thinkcell:tlexpected:toml11:tomlplusplus:trompeloeil:tts:type_safe:unifex:ureact:vcl:xercesc:xsimd:xtensor:xtl:yomm2:zug:cli11:avr-libstdcpp:curl:copperspice:sqlite:ztdcuneicode:ztdencodingtables:ztdidk:ztdstaticcontainers:ztdtext:ztdplatform:qt:quill:pcre2:widberg-defs:jwt-cpp:xieite:option:mdspan
+libs=abseil:array:async_simple:belleviews:beman_iterator_interface:beman_optional:benchmark:benri:blaze:boost:bmulti:brigand:catch2:cccl:cctz:cereal:cmcstl2:cnl:cppcoro:cppitertools:cpptrace:crosscables:ctbignum:cthash:ctre:date:dataframe:dawjson:dlib:doctest:eastl:eigen:enoki:entt:etl:eve:expected_lite:fastor:flux:fmt:gcem:gemmlowp:glaze:glm:gnufs:gnulibbacktrace:gnuexp:googletest:gsl:hdf5:hedley:hfsm:highfive:highway:hotels-template-library:immer:jsoncons:jsoncpp:kiwaku:kokkos:kumi:kvasir:kyosu:lager:lagom:lexy:libassert:libbpf:libguarded:libsimdpp:libuv:llvm:llvmfs:lua:magic_enum:mfem:mimicpp:mlir:mp-coro:mp-units:namedtype:nanorange:nlohmann_json:nsimd:ofw:openssl:outcome:pegtl:pipes:proxy:pugixml:pybind11:python:rangesv3:raberu:reactive_plus_plus:scnlib:seastar:seqan3:simde:simdjson:sol2:spdlog:spy:stdexec:strong_type:taojson:taskflow:tbb:thinkcell:tlexpected:toml11:tomlplusplus:trompeloeil:tts:type_safe:unifex:ureact:vcl:xercesc:xsimd:xtensor:xtl:yomm2:zug:cli11:avr-libstdcpp:curl:copperspice:sqlite:ztdcuneicode:ztdencodingtables:ztdidk:ztdstaticcontainers:ztdtext:ztdplatform:qt:quill:pcre2:widberg-defs:jwt-cpp:xieite:option:mdspan
 
 
 libs.abseil.name=Abseil
@@ -5246,6 +5246,21 @@ libs.pipes.url=https://github.com/joboccara/Pipes
 libs.pipes.versions.trunk.version=trunk
 libs.pipes.versions.trunk.path=/opt/compiler-explorer/libs/pipes/include
 
+libs.proxy.name=Proxy
+libs.proxy.description=C++20 Polymorphism Library
+libs.proxy.versions=trunk:321:320:310:300
+libs.proxy.url=https://github.com/microsoft/proxy
+libs.proxy.versions.trunk.version=trunk
+libs.proxy.versions.trunk.path=/opt/compiler-explorer/libs/proxy/trunk/include
+libs.proxy.versions.321.version=3.2.1
+libs.proxy.versions.321.path=/opt/compiler-explorer/libs/proxy/3.2.1/include
+libs.proxy.versions.320.version=3.2.0
+libs.proxy.versions.320.path=/opt/compiler-explorer/libs/proxy/3.2.0/include
+libs.proxy.versions.310.version=3.1.0
+libs.proxy.versions.310.path=/opt/compiler-explorer/libs/proxy/3.1.0/include
+libs.proxy.versions.300.version=3.0.0
+libs.proxy.versions.300.path=/opt/compiler-explorer/libs/proxy/3.0.0/include
+
 libs.pugixml.name=pugixml
 libs.pugixml.description=pugixml is a light-weight C++ XML processing library
 libs.pugixml.examples=bE3WPhMYq
@@ -5304,7 +5319,7 @@ libs.qt.versions.680.path=/app/qt/include/QtCore
 
 libs.quill.name=Quill
 libs.quill.description=C++ Low Latency Logging Library
-libs.quill.versions=611:700:721:730:750:810
+libs.quill.versions=611:700:721:730:750:810:820
 libs.quill.url=https://github.com/odygrd/quill
 libs.quill.versions.611.version=6.1.1
 libs.quill.versions.611.path=/opt/compiler-explorer/libs/quill/v6.1.1/quill/include
@@ -5318,6 +5333,8 @@ libs.quill.versions.750.version=7.5.0
 libs.quill.versions.750.path=/opt/compiler-explorer/libs/quill/v7.5.0/include
 libs.quill.versions.810.version=8.1.0
 libs.quill.versions.810.path=/opt/compiler-explorer/libs/quill/v8.1.0/include
+libs.quill.versions.820.version=8.2.0
+libs.quill.versions.820.path=/opt/compiler-explorer/libs/quill/v8.2.0/include
 
 libs.rangesv3.name=range-v3
 libs.rangesv3.versions=trunk:030:035:036:091:0100:0110:0120
diff --git a/etc/config/coccinelle_for_c.defaults.properties b/etc/config/coccinelle_for_c.defaults.properties
new file mode 100644
index 000000000..746c1b899
--- /dev/null
+++ b/etc/config/coccinelle_for_c.defaults.properties
@@ -0,0 +1,6 @@
+compilerType=coccinelle_for_c
+compilers=spatch_for_c
+compiler.spatch_for_c.name=Spatch
+compiler.spatch_for_c.exe=spatch
+compiler.spatch_for_c.intelAsm=
+supportsBinary=false
diff --git a/etc/config/coccinelle_for_cpp.defaults.properties b/etc/config/coccinelle_for_cpp.defaults.properties
new file mode 100644
index 000000000..d5b1c4b80
--- /dev/null
+++ b/etc/config/coccinelle_for_cpp.defaults.properties
@@ -0,0 +1,6 @@
+compilerType=coccinelle_for_cpp
+compilers=spatch_for_cpp
+compiler.spatch_for_cpp.name=Spatch
+compiler.spatch_for_cpp.exe=spatch
+compiler.spatch_for_cpp.intelAsm=
+supportsBinary=false
diff --git a/etc/config/rust.amazon.properties b/etc/config/rust.amazon.properties
index 1ff689526..a7e3b0fe1 100644
--- a/etc/config/rust.amazon.properties
+++ b/etc/config/rust.amazon.properties
@@ -19,8 +19,11 @@ group.rust.licenseName=Dual-licensed under Apache 2.0 and MIT terms
 group.rust.licensePreamble=Rust is licensed under the Apache License, Version 2.0 or the MIT license
 group.rust.supportsBinary=true
 group.rust.supportsBinaryObject=true
+# Rust 1.83+ needs `libPath` for execution with dynamically linked standard library
+# see https://github.com/compiler-explorer/compiler-explorer/pull/7367
 compiler.r1850.exe=/opt/compiler-explorer/rust-1.85.0/bin/rustc
 compiler.r1850.semver=1.85.0
+compiler.r1850.libPath=${exePath}/../lib/rustlib/x86_64-unknown-linux-gnu/lib
 compiler.r1840.exe=/opt/compiler-explorer/rust-1.84.0/bin/rustc
 compiler.r1840.semver=1.84.0
 compiler.r1840.libPath=${exePath}/../lib/rustlib/x86_64-unknown-linux-gnu/lib
diff --git a/etc/config/spice.amazon.properties b/etc/config/spice.amazon.properties
index 858d7b02b..06a9c0e61 100644
--- a/etc/config/spice.amazon.properties
+++ b/etc/config/spice.amazon.properties
@@ -1,7 +1,7 @@
 compilers=&spice
-defaultCompiler=spice02101
+defaultCompiler=spice02200
 
-group.spice.compilers=spice01902:spice01903:spice01904:spice01905:spice01906:spice02000:spice02001:spice02002:spice02003:spice02004:spice02005:spice02006:spice02100:spice02101
+group.spice.compilers=spice01902:spice01903:spice01904:spice01905:spice01906:spice02000:spice02001:spice02002:spice02003:spice02004:spice02005:spice02006:spice02100:spice02101:spice02200
 group.spice.demangler=/opt/compiler-explorer/gcc-14.2.0/bin/c++filt
 group.spice.objdumper=/opt/compiler-explorer/gcc-14.2.0/bin/objdump
 group.spice.isSemVer=true
@@ -43,6 +43,8 @@ compiler.spice02100.exe=/opt/compiler-explorer/spice-0.21.0/spice
 compiler.spice02100.semver=0.21.0
 compiler.spice02101.exe=/opt/compiler-explorer/spice-0.21.1/spice
 compiler.spice02101.semver=0.21.1
+compiler.spice02200.exe=/opt/compiler-explorer/spice-0.22.0/spice
+compiler.spice02200.semver=0.22.0
 
 #################################
 #################################
diff --git a/etc/config/sway.amazon.properties b/etc/config/sway.amazon.properties
new file mode 100644
index 000000000..408ae7f58
--- /dev/null
+++ b/etc/config/sway.amazon.properties
@@ -0,0 +1,19 @@
+compilers=&sway
+defaultCompiler=swayv0667
+objdumper=/opt/compiler-explorer/gcc-14.2.0/bin/objdump
+
+group.sway.compilers=swayv0667
+group.sway.compilerType=sway-compiler
+group.sway.isSemVer=true
+group.sway.supportsIrView=true
+group.sway.irArg=--ir final
+group.sway.supportsBinary=true
+group.sway.supportsAsm=true
+group.sway.asmArg=--asm all
+
+compiler.swayv0667.exe=/opt/compiler-explorer/sway-0.66.7/forc-binaries/forc
+compiler.swayv0667.semver=0.66.7
+compiler.swayv0667.name=sway 0.66.7
+
+# Basic tools that might be useful
+tools=
diff --git a/etc/config/sway.defaults.properties b/etc/config/sway.defaults.properties
new file mode 100644
index 000000000..b6e4fef5e
--- /dev/null
+++ b/etc/config/sway.defaults.properties
@@ -0,0 +1,28 @@
+# Basic language definition
+language.sway.name=sway
+language.sway.extensions=.sw
+language.sway.monaco=sway
+
+# Add IR language definition
+language.sway-ir.name=Sway IR
+language.sway-ir.monaco=rust
+
+# Compiler settings
+compilers=&sway
+defaultCompiler=swayv0667
+compilerType=sway-compiler
+
+# Define compiler setup
+group.sway.compilers=swayv0667
+group.sway.groupName=Sway compilers
+group.sway.compilerType=sway-compiler
+group.sway.supportsIrView=true
+group.sway.irArg=--ir final
+group.sway.supportsBinary=true
+group.sway.supportsAsm=true
+group.sway.asmArg=--asm all
+group.sway.versionFlag=--version
+group.sway.isSemVer=true
+
+# No libs support yet
+libs=
diff --git a/etc/config/swift.amazon.properties b/etc/config/swift.amazon.properties
index ba2b7b266..e5e30e5d2 100644
--- a/etc/config/swift.amazon.properties
+++ b/etc/config/swift.amazon.properties
@@ -1,12 +1,24 @@
-compilers=&swift
+compilers=&swift:&arm-swift
 demangler=/opt/compiler-explorer/swift-6.0.3/usr/bin/swift-demangle
 defaultCompiler=swift603
 objdumper=/opt/compiler-explorer/gcc-snapshot/bin/objdump
 
+#################################
+# Compiler Groups
+#################################
+
+# swift (x86_64)
 group.swift.compilers=swift311:swift402:swift403:swift41:swift411:swift412:swift42:swift50:swift51:swift52:swift53:swift54:swift55:swift56:swift57:swift58:swift59:swift510:swift603:swiftdevsnapshot:swiftnightly
 group.swift.isSemVer=true
+group.swift.groupName=swift (x86-64)
 group.swift.baseName=x86-64 swiftc
 
+# arm-swift (aarch64)
+group.arm-swift.compilers=arm-swift603
+group.arm-swift.isSemVer=true
+group.arm-swift.groupName=swift (aarch64)
+group.arm-swift.baseName=aarch64 swiftc
+
 #################################
 # Development Compilers
 #################################
@@ -24,16 +36,22 @@ compiler.swiftdevsnapshot.semver=devsnapshot
 # Release Compilers
 #################################
 
-# 6.x
+# 6.x (x86)
 
 compiler.swift603.exe=/opt/compiler-explorer/swift-6.0.3/usr/bin/swiftc
 compiler.swift603.semver=6.0.3
 
+# 6.x (arm)
+
+compiler.arm-swift603.exe=/opt/compiler-explorer/swift-6.0.3/usr/bin/swiftc
+compiler.arm-swift603.semver=6.0.3
+compiler.arm-swift603.options=-target aarch64-swift-linux-musl -sdk /opt/compiler-explorer/swift-6.0.3-static-sdk/swift-linux-musl/musl-1.2.5.sdk/aarch64 -resource-dir /opt/compiler-explorer/swift-6.0.3-static-sdk/swift-linux-musl/musl-1.2.5.sdk/aarch64/usr/lib/swift_static
+
 # 5.x
 
 compiler.swift510.exe=/opt/compiler-explorer/swift-5.10/usr/bin/swiftc
 compiler.swift510.semver=5.10
-compiler.swift510.alias=switf510nightly
+compiler.swift510.alias=swift510nightly
 
 compiler.swift59.exe=/opt/compiler-explorer/swift-5.9/usr/bin/swiftc
 compiler.swift59.semver=5.9
diff --git a/etc/config/swift.defaults.properties b/etc/config/swift.defaults.properties
index d1cb80eea..34ed8aa86 100644
--- a/etc/config/swift.defaults.properties
+++ b/etc/config/swift.defaults.properties
@@ -4,6 +4,7 @@ supportsBinary=true
 supportsExecute=true
 compilerType=swift
 intelAsm=-emit-assembly -Xllvm --x86-asm-syntax=intel
+needsMulti=false
 
 
 #################################
diff --git a/etc/scripts/docenizers/docenizer-avr.py b/etc/scripts/docenizers/docenizer-avr.py
index 154c85d1a..19744f1ad 100755
--- a/etc/scripts/docenizers/docenizer-avr.py
+++ b/etc/scripts/docenizers/docenizer-avr.py
@@ -70,7 +70,12 @@ def parse_docs(docs):
             instructions[instr.mnemonic] = instr
         else:
             instr = instructions[match.group("mnemonic")]
-        if match.group("mnemonic_2"):
+        if (
+            match.group("mnemonic_2")
+            # The manual lists some instruction set names in the place where we
+            # expected to find second mnemonics.
+            and match.group("mnemonic_2") not in ("AVRe", "AVRrc")
+        ):
             instr.mnemonic_2 = match.group("mnemonic_2")
     return instructions
 
diff --git a/etc/scripts/docenizers/docenizer-power.py b/etc/scripts/docenizers/docenizer-power.py
index 3e96420df..cf12dc5e2 100644
--- a/etc/scripts/docenizers/docenizer-power.py
+++ b/etc/scripts/docenizers/docenizer-power.py
@@ -30,7 +30,7 @@ For more details, see here: https://github.com/compiler-explorer/compiler-explor
 If anyone is braver than me, feel free to try and pick up this work, and increment
 the counter below.
 
-hours_of_life_wasted = 10
+hours_of_life_wasted = 10.2
 """
 
 import argparse
@@ -235,7 +235,8 @@ links = [
     "https://www.ibm.com/docs/en/aix/7.3?topic=set-rfid-return-from-interrupt-double-word-instruction",
     "https://www.ibm.com/docs/en/aix/7.3?topic=set-rfsvc-return-from-svc-instruction",
     "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldcl-rotate-left-double-word-then-clear-left-instruction",
-    "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldicl-rotate-left-double-word-immediate-then-clear-left-instruction",
+    # Two pages document rldicl. The one suffixed `-1` is clearer.
+    "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldicl-rotate-left-double-word-immediate-then-clear-left-instruction-1",
     "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldcr-rotate-left-double-word-then-clear-right-instruction",
     "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldic-rotate-left-double-word-immediate-then-clear-instruction",
     "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldicr-rotate-left-double-word-immediate-then-clear-right-instruction",
@@ -388,7 +389,7 @@ def docenizer():
     if not os.path.exists("power/.complete-preprocess"):
         preprocess()
 
-    """
+    r"""
     # Extract instruction name from parentheses in title
     tooltip = str(re.findall(r'\(.*?\)', driver.title)).replace("(", "").replace(")", "")
 
diff --git a/etc/scripts/util/propscheck.py b/etc/scripts/util/propscheck.py
index 7e9b242f3..7182e060d 100644
--- a/etc/scripts/util/propscheck.py
+++ b/etc/scripts/util/propscheck.py
@@ -27,6 +27,7 @@ from os import listdir
 from os.path import isfile, join
 import re
 import argparse
+from argparse import Namespace
 
 parser = argparse.ArgumentParser(description='Checks for incorrect/suspicious properties.')
 parser.add_argument ('--check-suspicious-in-default-prop', required=False, action="store_true")
@@ -101,7 +102,7 @@ def check_suspicious_path_and_add(line: Line, m, s):
         s.add(Line(line.number, m.group(2)))
 
 
-def process_file(file: str, args):
+def process_file(file: str, args: Namespace):
     default_compiler = set()
 
     listed_groups = set()
@@ -270,7 +271,7 @@ def print_issue(name, result):
         print(f"{name}:\n  {sep.join(sorted([str(issue) for issue in result]))}")
 
 
-def find_orphans(args: dict):
+def find_orphans(args: Namespace):
     folder = args.config_dir
     result = sorted([(f, r) for (f, r) in process_folder(folder, args) if problems_found(r)], key=lambda x: x[0])
     if result:
diff --git a/examples/coccinelle_for_c/default.c b/examples/coccinelle_for_c/default.c
new file mode 100644
index 000000000..68affb9be
--- /dev/null
+++ b/examples/coccinelle_for_c/default.c
@@ -0,0 +1,10 @@
+// Write your C program in this section:
+int main(){ return 0; }
+
+#ifdef COCCINELLE // this keyword should only occur on this and the last line
+// Write your Coccinelle 🐞 rules in this section (and keep the ifdef):
+@patch@
+@@
+- 0
++ 1
+#endif /* COCCINELLE */
diff --git a/examples/coccinelle_for_c/rules-inheritance.c b/examples/coccinelle_for_c/rules-inheritance.c
new file mode 100644
index 000000000..209ce2886
--- /dev/null
+++ b/examples/coccinelle_for_c/rules-inheritance.c
@@ -0,0 +1,19 @@
+// Write your C program in this section:
+int main(){ return 0; }
+
+// Write your Coccinelle 🐞 rules in this section (and keep the ifdef):
+#ifdef COCCINELLE // this keyword should only occur on this and the last line
+@patch@
+constant c;
+@@
+// finds a constant and replaces with 1
+- c
++ 1
+
+@@
+constant patch.c;
+@@
+// inserts last rule's matched constant (0) next to any occurrence of 1
+- 1
++ 1 + c
+#endif /* COCCINELLE */
diff --git a/examples/coccinelle_for_cpp/default.cpp b/examples/coccinelle_for_cpp/default.cpp
new file mode 100644
index 000000000..3514ceb50
--- /dev/null
+++ b/examples/coccinelle_for_cpp/default.cpp
@@ -0,0 +1,10 @@
+// Write your C++ program in this section:
+int main(){ return 0; }
+
+#ifdef COCCINELLE // this keyword should only occur on this and the last line
+// Write your Coccinelle 🐞 rules in this section (and keep the ifdef):
+@patch@
+@@
+- 0
++ 1
+#endif /* COCCINELLE */
diff --git a/examples/sway/default.sw b/examples/sway/default.sw
new file mode 100644
index 000000000..a3e7a1514
--- /dev/null
+++ b/examples/sway/default.sw
@@ -0,0 +1,10 @@
+script;
+
+fn square(x: u64) -> u64 {
+    x * x
+}
+
+fn main() {
+    let x = square(5);
+    assert(x == 25);
+}
\ No newline at end of file
diff --git a/lib/asm-docs/generated/asm-docs-avr.ts b/lib/asm-docs/generated/asm-docs-avr.ts
index 2cdd2cbfc..1a1c54f45 100644
--- a/lib/asm-docs/generated/asm-docs-avr.ts
+++ b/lib/asm-docs/generated/asm-docs-avr.ts
@@ -474,7 +474,6 @@ export function getAsmOpcode(opcode: string | undefined): AssemblyInstructionInf
             };
 
         case "LDS":
-        case "AVRrc":
             return {
                 "html": "<p>Loads one byte from the data space to a register. The data space usually consists of the Register File, I/O memory, and SRAM, refer to the device data sheet for a detailed definition of the data space.</p><p>A 16-bit address must be supplied. Memory access is limited to the current data segment of 64 KB. The LDS instruction uses the RAMPD Register to access memory above 64 KB. To access another data segment in devices with more than 64 KB data space, the RAMPD in the register in the I/O area has to be changed.</p><p>This instruction is not available on all devices. Refer to Appendix A.</p>",
                 "tooltip": "Load Direct from Data Space",
@@ -755,7 +754,6 @@ export function getAsmOpcode(opcode: string | undefined): AssemblyInstructionInf
             };
 
         case "SPM":
-        case "AVRe":
             return {
                 "html": "<p>SPM can be used to erase a page in the program memory, to write a page in the program memory (that is already erased), and to set Boot Loader Lock bits. In some devices, the Program memory can be written one word at a time. In other devices, an entire page can be programmed simultaneously after first filling a temporary page buffer. In all cases, the program memory must be erased one page at a time. When erasing the program memory, the RAMPZ and Z-register are used as page address. When writing the program memory, the RAMPZ and Z-register are used as page or word address, and the R1:R0 register pair is used as data(1). The Flash is word-accessed for code space write operations, so the least significant bit of the RAMPZ register concatenated with the Z register should be set to ‘0’. When setting the Boot Loader Lock bits, the R1:R0 register pair is used as data. Refer to the device documentation for the detailed description of SPM usage. This instruction can address the entire program memory.</p><p>The SPM instruction is not available on all devices. Refer to Appendix A.</p><p>Note:  1. R1 determines the instruction high byte, and R0 determines the instruction low byte.</p>",
                 "tooltip": "Store Program Memory",
@@ -771,7 +769,6 @@ export function getAsmOpcode(opcode: string | undefined): AssemblyInstructionInf
             };
 
         case "STS":
-        case "AVRrc":
             return {
                 "html": "<p>Stores one byte from a Register to the data space. The data space usually consists of the Register File, I/O memory, and SRAM, refer to the device data sheet for a detailed definition of the data space.</p><p>A 16-bit address must be supplied. Memory access is limited to the current data segment of 64 KB. The STS instruction uses the RAMPD Register to access memory above 64 KB. To access another data segment in devices with more than 64 KB data space, the RAMPD in the register in the I/O area has to be changed.</p><p>This instruction is not available on all devices. Refer to Appendix A.</p>",
                 "tooltip": "Store Direct to Data Space",
diff --git a/lib/asm-docs/generated/asm-docs-power.ts b/lib/asm-docs/generated/asm-docs-power.ts
index cc9790761..6f9b967a1 100644
--- a/lib/asm-docs/generated/asm-docs-power.ts
+++ b/lib/asm-docs/generated/asm-docs-power.ts
@@ -1678,13 +1678,6 @@ export function getAsmOpcode(opcode: string | undefined): AssemblyInstructionInf
                 "tooltip": "Rotate Left Double Word then Clear Left",
                 "url": "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldcl-rotate-left-double-word-then-clear-left-instruction"
             };
-        case "RLDICL":
-        case "RLDICL.":
-            return {
-                "html": `<p>The contents of rS are rotated left the number of bits specified by operand SH. A mask is generated having 1 bits from bit MB through bit 63 and 0 bits elsewhere. The rotated data is ANDed with the generated mask and the result is placed into rA.</p>`,
-                "tooltip": "Rotate Left Double Word Immediate then Clear Left",
-                "url": "https://www.ibm.com/docs/en/aix/7.3?topic=is-rldicl-rotate-left-double-word-immediate-then-clear-left-instruction"
-            };
         case "RLDCR":
         case "RLDCR.":
             return {
diff --git a/lib/compilers/_all.ts b/lib/compilers/_all.ts
index ffc8751f5..a07abbbaf 100644
--- a/lib/compilers/_all.ts
+++ b/lib/compilers/_all.ts
@@ -44,6 +44,8 @@ export {ClangHexagonCompiler} from './clang.js';
 export {ClangHipCompiler} from './clang.js';
 export {ClangIntelCompiler} from './clang.js';
 export {CleanCompiler} from './clean.js';
+export {CoccinelleCCompiler} from './coccinelle.js';
+export {CoccinelleCPlusPlusCompiler} from './coccinelle.js';
 export {CompCertCompiler} from './compcert.js';
 export {CppFrontCompiler} from './cppfront.js';
 export {CprocCompiler} from './cproc.js';
@@ -136,6 +138,7 @@ export {SnowballCompiler} from './snowball.js';
 export {SolidityCompiler} from './solidity.js';
 export {SolidityZKsyncCompiler} from './solidity-zksync.js';
 export {SpiceCompiler} from './spice.js';
+export {SwayCompiler} from './sway.js';
 export {SwiftCompiler} from './swift.js';
 export {TIC2000} from './tic2000.js';
 export {TableGenCompiler} from './tablegen.js';
diff --git a/lib/compilers/coccinelle.ts b/lib/compilers/coccinelle.ts
new file mode 100644
index 000000000..7e14f9593
--- /dev/null
+++ b/lib/compilers/coccinelle.ts
@@ -0,0 +1,287 @@
+// Copyright (c) 2024-2025, Michele Martone and 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 {open} from 'node:fs/promises';
+import path from 'node:path';
+
+import {CacheKey, CompilationResult, ExecutionOptionsWithEnv} from '../../types/compilation/compilation.interfaces.js';
+import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
+import type {BasicExecutionResult, UnprocessedExecResult} from '../../types/execution/execution.interfaces.js';
+import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
+import {SelectedLibraryVersion} from '../../types/libraries/libraries.interfaces.js';
+import {BaseCompiler} from '../base-compiler.js';
+import {CompilationEnvironment} from '../compilation-env.js';
+import {logger} from '../logger.js';
+import * as utils from '../utils.js';
+
+export class CoccinelleCCompiler extends BaseCompiler {
+    protected spatchBaseFilename: string; // if true, doTempfolderCleanup won't clean up
+    protected joinSpatchStdinAndStderr: boolean; // dirty hopefullytemporary hack, as coccinelle dumps diagnostics on both streams
+    protected verbose: boolean; // for maintenance/development
+    protected permittedOptions: string[];
+
+    static get key() {
+        return 'coccinelle_for_c';
+    }
+
+    constructor(info: PreliminaryCompilerInfo, env: CompilationEnvironment) {
+        super(
+            {
+                disabledFilters: ['labels', 'directives', 'commentOnly', 'trim', 'debugCalls'],
+                ...info,
+            },
+            env,
+        );
+        this.compiler.supportsIntel = false;
+        this.delayCleanupTemp = false;
+        this.spatchBaseFilename = 'patch.cocci';
+        this.outputFilebase = 'output';
+        this.joinSpatchStdinAndStderr = true;
+        this.verbose = false;
+        this.permittedOptions = ['--debug', '--help', '--iso-limit=0', '--quiet', '--verbose', '--very-quiet.'];
+        // ...
+    }
+
+    override getDefaultFilters() {
+        // We want to keep the entire output of Coccinelle (so, no filter)
+        return {
+            intel: false,
+            commentOnly: false,
+            directives: false,
+            labels: false,
+            optOutput: false,
+            binary: false,
+            execute: false,
+            demangle: false,
+            libraryCode: false,
+            trim: false,
+            binaryObject: false,
+            debugCalls: false,
+        };
+    }
+
+    override async populatePossibleOverrides() {
+        await super.populatePossibleOverrides();
+    }
+
+    override getSharedLibraryPathsAsArguments(libraries: SelectedLibraryVersion[], libDownloadPath?: string) {
+        return [];
+    }
+
+    override getSharedLibraryLinks(libraries: any[]): string[] {
+        return [];
+    }
+
+    override getIncludeArguments(libraries: SelectedLibraryVersion[], dirPath: string): string[] {
+        return super.getIncludeArguments(libraries, dirPath);
+    }
+
+    override orderArguments(
+        options: string[],
+        inputFilename: string,
+        libIncludes: string[],
+        libOptions: string[],
+        libPaths: string[],
+        libLinks: string[],
+        userOptions: string[],
+        staticLibLinks: string[],
+    ) {
+        return options.concat(userOptions, libIncludes, libOptions, libPaths, libLinks, staticLibLinks, [
+            this.filename(inputFilename),
+        ]);
+    }
+
+    override optionsForBackend(backendOptions: Record<string, any>, outputFilename: string) {
+        return super.optionsForBackend(backendOptions, outputFilename);
+    }
+
+    override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions?: string[]) {
+        // coccinelle_for_c
+        return ['--sp-file', this.spatchBaseFilename, '-o', this.filename(outputFilename)];
+    }
+
+    override optionsForDemangler(filters?: ParseFiltersAndOutputOptions): string[] {
+        const options = super.optionsForDemangler(filters);
+        if (filters !== undefined && !filters.verboseDemangling) {
+            options.push('--no-verbose');
+        }
+        return options;
+    }
+
+    override getIrOutputFilename(inputFilename: string, filters: ParseFiltersAndOutputOptions): string {
+        return super.getIrOutputFilename(inputFilename, filters);
+    }
+
+    override getArgumentParserClass() {
+        return super.getArgumentParserClass();
+    }
+
+    override isCfgCompiler() {
+        return false;
+    }
+
+    override processExecutionResult(input: UnprocessedExecResult, inputFilename?: string): BasicExecutionResult {
+        return utils.processExecutionResult(input, inputFilename);
+    }
+
+    override buildExecutable(
+        compiler: string,
+        options: string[],
+        inputFilename: string,
+        execOptions: ExecutionOptionsWithEnv,
+    ) {
+        // may move here the file separation logic currently in runCompiler
+        return super.buildExecutable(compiler, options, inputFilename, execOptions);
+    }
+
+    checkCocciSource(source: string) {
+        // forbid '#spatch'-alike lines anywhere in the semantic patch
+        const re = /^\s*#\s*spatch.*/;
+        const failed: string[] = [];
+        for (const [index, line] of utils.splitLines(source).entries()) {
+            if (re.test(line)) {
+                failed.push(`<stdin>:${index + 1}:1: no #spatch lines please`);
+            }
+        }
+        if (failed.length > 0) return failed.join('\n');
+        return null;
+    }
+
+    override async runCompiler(
+        compiler: string,
+        options: string[],
+        inputFilename: string,
+        execOptions: ExecutionOptionsWithEnv,
+        filters?: ParseFiltersAndOutputOptions,
+    ): Promise<CompilationResult> {
+        // here to extract contents of source and semantic patch
+        if (!execOptions) {
+            execOptions = this.getDefaultExecOptions();
+        }
+
+        if (!execOptions.customCwd) {
+            execOptions.customCwd = path.dirname(inputFilename);
+        }
+
+        const spatchFilename = path.join(path.dirname(inputFilename), this.spatchBaseFilename);
+        let cFileContents = '';
+        let pFileContents = '';
+        let toc = true; // toc: Target Language or Coccinelle Language
+        const file = await open(inputFilename);
+        for await (const line of file.readLines()) {
+            // this separates the C/C++ portion from the SmPL
+            const ifdefCocciRegex = /^#ifdef.*COCCINELLE.*/;
+            const endifCocciRegex = /^#endif.*COCCINELLE.*/;
+
+            if (ifdefCocciRegex.test(line) && toc) {
+                toc = !toc;
+            } else {
+                if (!endifCocciRegex.test(line)) {
+                    if (toc) cFileContents += line + '\n';
+                    else pFileContents += line + '\n';
+                }
+            }
+        }
+        file.close();
+
+        // we overwrite the C/C++ + SmPL with extracted C/C++ sources (we also spare ourselves an unlink())
+        const cfile = await open(inputFilename, 'w');
+        cfile.write(cFileContents);
+        cfile.close();
+
+        const spfile = await open(spatchFilename, 'w');
+        // we save the extracted SmPL (semantic patch) sources
+        spfile.write(pFileContents);
+        spfile.close();
+
+        // check that the SmPL source does not #include anything, via CE rules
+        let cocciSourceError = this.checkSource(pFileContents);
+        if (cocciSourceError) throw cocciSourceError;
+        // use own rules, too
+        cocciSourceError = this.checkCocciSource(pFileContents);
+        if (cocciSourceError) throw cocciSourceError;
+
+        const result = await this.exec(compiler, options, execOptions);
+
+        if (this.joinSpatchStdinAndStderr && result.code === 0 && !result.timedOut) {
+            for (const opt of options)
+                if (opt == '--help')
+                    result.stdout +=
+                        '\n**** Of the above, only the following are allowed here in Compiler Explorer: ****\n' +
+                        this.permittedOptions.concat('') +
+                        '\n';
+            result.stderr += result.stdout;
+            result.stdout = result.stderr;
+            result.stderr = '';
+        }
+        return {
+            ...this.transformToCompilationResult(result, inputFilename),
+            languageId: this.getCompilerResultLanguageId(filters),
+            instructionSet: this.getInstructionSetFromCompilerArgs(options),
+        };
+    }
+
+    override getOutputFilename(dirPath: string, outputFilebase: string, key?: CacheKey): string {
+        return path.join(dirPath, `${outputFilebase}${this.lang.extensions[0]}`);
+    }
+
+    override postCompilationPreCacheHook(result: CompilationResult): CompilationResult {
+        if (result.code === 0) {
+            result.languageId = 'c'; // we produce C
+        }
+        return result;
+    }
+
+    override filterUserOptions(args: string[]) {
+        const permittedOptionsArray = new Set(this.permittedOptions);
+        return args.filter(item => {
+            if (typeof item !== 'string') return true;
+
+            if (permittedOptionsArray.has(item)) return true;
+
+            if (this.verbose) logger.warn(`User-provided option ${item} not allowed -- ignoring it.`);
+            return false;
+        });
+    }
+}
+
+export class CoccinelleCPlusPlusCompiler extends CoccinelleCCompiler {
+    static override get key() {
+        return 'coccinelle_for_cpp';
+    }
+
+    override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions?: string[]) {
+        // coccinelle_for_cpp
+        const options = super.optionsForFilter(filters, outputFilename, userOptions);
+        options.push('--c++');
+        return options;
+    }
+
+    override postCompilationPreCacheHook(result: CompilationResult): CompilationResult {
+        if (result.code === 0) {
+            result.languageId = 'cpp'; // we produce C++
+        }
+        return result;
+    }
+}
diff --git a/lib/compilers/sway.ts b/lib/compilers/sway.ts
new file mode 100644
index 000000000..5672470c8
--- /dev/null
+++ b/lib/compilers/sway.ts
@@ -0,0 +1,270 @@
+// 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 path from 'node:path';
+
+import fs from 'fs-extra';
+
+import {CompilationResult, ExecutionOptionsWithEnv} from '../../types/compilation/compilation.interfaces.js';
+import {LLVMIrBackendOptions} from '../../types/compilation/ir.interfaces.js';
+import {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
+import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
+import {ResultLine} from '../../types/resultline/resultline.interfaces.js';
+import {BaseCompiler} from '../base-compiler.js';
+import {CompilationEnvironment} from '../compilation-env.js';
+
+interface SymbolMap {
+    paths: string[];
+    map: {
+        [key: string]: {
+            path: number;
+            range: {
+                start: {line: number; col: number};
+                end: {line: number; col: number};
+            };
+        };
+    };
+}
+
+export class SwayCompiler extends BaseCompiler {
+    static get key() {
+        return 'sway-compiler';
+    }
+
+    constructor(info: PreliminaryCompilerInfo, env: CompilationEnvironment) {
+        super(info, env);
+        this.compiler.supportsIrView = true;
+        this.compiler.irArg = ['build', '--ir', 'final'];
+        this.compiler.supportsIntel = true;
+    }
+
+    override async checkOutputFileAndDoPostProcess(asmResult: CompilationResult): Promise<[any, any[], any[]]> {
+        return [asmResult, [], []];
+    }
+
+    override async processAsm(result: any) {
+        // If compilation failed or we have no assembly, return as is
+        if (result.code !== 0 || !result.asm || result.asm.length === 0) {
+            return {
+                ...result,
+                asm: [{text: '<Compilation failed>', source: null}],
+            };
+        }
+        // The asm array should already be properly formatted from runCompiler
+        return {
+            asm: result.asm,
+            labelDefinitions: {},
+        };
+    }
+
+    override async generateIR(
+        inputFilename: string,
+        options: string[],
+        irOptions: LLVMIrBackendOptions,
+        produceCfg: boolean,
+        filters: ParseFiltersAndOutputOptions,
+    ) {
+        // We can use runCompiler since it already handles all the project setup
+        const result = await this.runCompiler(
+            this.compiler.exe,
+            ['build', '--ir', 'final'],
+            inputFilename,
+            this.getDefaultExecOptions(),
+            filters,
+        );
+
+        return {
+            code: result.code,
+            stdout: [],
+            stderr: result.stderr,
+            asm: result.irOutput?.asm || [],
+            timedOut: result.timedOut,
+            execTime: result.execTime,
+            okToCache: true,
+            inputFilename: result.inputFilename,
+            dirPath: result.dirPath,
+        };
+    }
+
+    override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string): string[] {
+        // return an array of command line options for the compiler
+        return ['-o', outputFilename];
+    }
+
+    override async runCompiler(
+        compiler: string,
+        options: string[],
+        inputFilename: string,
+        execOptions: ExecutionOptionsWithEnv,
+        filters?: Partial<ParseFiltersAndOutputOptions>,
+    ): Promise<CompilationResult> {
+        // Make a temp directory for a forc project
+        const projectDir = await this.newTempDir();
+        const {symbolsPath} = await setupForcProject(projectDir, inputFilename);
+
+        // Run `forc build`
+        // "compiler" is the path to the forc binary from .properties
+        const buildResult = await this.exec(compiler, ['build', '-g', symbolsPath], {
+            ...execOptions,
+            customCwd: projectDir,
+        });
+
+        // If build succeeded, parse the bytecode
+        let asm: ResultLine[] = [];
+        if (buildResult.code === 0) {
+            const artifactPath = path.join(projectDir, 'out', 'debug', 'compiler-explorer.bin');
+
+            if (filters?.intel) {
+                const asmResult = await this.exec(compiler, ['build', '--asm', 'all'], {
+                    ...execOptions,
+                    customCwd: projectDir,
+                });
+                const lines = splitLines(asmResult.stdout);
+                const startIndex = lines.findIndex(line => line.includes(';; ASM: Virtual abstract program'));
+                const endIndex = lines.findIndex(line => line.includes('[1;32mFinished'));
+                if (startIndex === -1 || endIndex === -1 || startIndex >= endIndex) {
+                    asm = [{text: '<Error extracting ASM output>'}];
+                } else {
+                    asm = lines
+                        .slice(startIndex, endIndex)
+                        .filter(line => line.trim() !== '')
+                        .map(line => ({text: line}));
+                }
+            } else {
+                const parseResult = await this.exec(compiler, ['parse-bytecode', artifactPath], {
+                    ...execOptions,
+                    customCwd: projectDir,
+                });
+                let symbols: SymbolMap | undefined;
+                if (await fs.pathExists(symbolsPath)) {
+                    const symbolsContent = await fs.readFile(symbolsPath, 'utf8');
+                    symbols = JSON.parse(symbolsContent);
+                }
+
+                // Map the bytecode lines
+                const contentLines = splitLines(parseResult.stdout)
+                    .filter(line => line.trim() !== '')
+                    .map(line => {
+                        const match = line.match(/^\s*(\d+)\s+(\d+)\s+/);
+                        if (match && symbols) {
+                            const opcodeIndex = match[1];
+                            const symbolInfo = symbols.map[opcodeIndex];
+                            if (symbolInfo && symbolInfo.path === 1) {
+                                return {
+                                    text: line,
+                                    source: {
+                                        file: symbols.paths[symbolInfo.path],
+                                        line: symbolInfo.range.start.line,
+                                        column: symbolInfo.range.start.col,
+                                        mainsource: true,
+                                    },
+                                };
+                            }
+                        }
+                        return {text: line};
+                    });
+                asm.push(...contentLines);
+            }
+        }
+
+        // Run `forc build --ir final` to gather IR output and store it in `result.irOutput`.
+        let irLines: ResultLine[] = [];
+        if (buildResult.code === 0) {
+            const irResult = await this.exec(compiler, ['build', '--ir', 'final'], {
+                ...execOptions,
+                customCwd: projectDir,
+            });
+            const lastIrMarkerIndex = irResult.stdout.lastIndexOf('// IR: Final');
+            if (lastIrMarkerIndex >= 0) {
+                const relevantIr =
+                    irResult.stdout
+                        .slice(lastIrMarkerIndex)
+                        .split('\n')
+                        .slice(1)
+                        .join('\n')
+                        .match(/(script|library|contract|predicate)\s*{.*?^}/ms)?.[0] || '';
+                irLines = relevantIr.split('\n').map(line => ({text: line}));
+            }
+        }
+
+        // Construct and return a CompilationResult
+        const result: CompilationResult = {
+            code: buildResult.code,
+            timedOut: buildResult.timedOut ?? false,
+            stdout: splitLines(buildResult.stdout).map(line => ({text: line})),
+            stderr: splitLines(buildResult.stderr).map(line => ({text: line})),
+            asm,
+            inputFilename,
+            execTime: buildResult.execTime,
+            okToCache: true,
+            dirPath: projectDir,
+            irOutput:
+                irLines.length > 0
+                    ? {
+                          asm: irLines.map(line => ({
+                              text: line.text,
+                          })),
+                      }
+                    : undefined,
+        };
+
+        return result;
+    }
+}
+
+const FORC_TOML_CONTENT = `[project]
+entry = "main.sw"
+license = "Apache-2.0"
+name = "compiler-explorer"
+
+[dependencies]
+`;
+
+async function setupForcProject(
+    projectDir: string,
+    inputFilename: string,
+): Promise<{mainSw: string; symbolsPath: string}> {
+    const outDebugDir = path.join(projectDir, 'out', 'debug');
+    const symbolsPath = path.join(outDebugDir, 'symbols.json');
+    await fs.mkdirp(outDebugDir);
+
+    // Write Forc.toml file
+    const forcTomlPath = path.join(projectDir, 'Forc.toml');
+    await fs.writeFile(forcTomlPath, FORC_TOML_CONTENT);
+
+    // Copy input file to src/main.sw
+    const srcDir = path.join(projectDir, 'src');
+    await fs.mkdirp(srcDir);
+    const mainSw = path.join(srcDir, 'main.sw');
+    await fs.copyFile(inputFilename, mainSw);
+
+    return {mainSw, symbolsPath};
+}
+
+/**
+ * Splits a multi-line string into an array of lines, omitting the trailing newline if present.
+ */
+function splitLines(str: string): string[] {
+    return str.split(/\r?\n/);
+}
diff --git a/lib/languages.ts b/lib/languages.ts
index 96377174c..dd5db13ff 100644
--- a/lib/languages.ts
+++ b/lib/languages.ts
@@ -43,6 +43,30 @@ type DefKeys =
 type LanguageDefinition = Pick<Language, DefKeys>;
 
 const definitions: Record<LanguageKey, LanguageDefinition> = {
+    coccinelle_for_c: {
+        name: 'C with Coccinelle',
+        monaco: 'nc',
+        extensions: ['.c', '.h'],
+        alias: [],
+        logoUrl: 'c.svg',
+        logoUrlDark: null,
+        formatter: 'clangformat',
+        previewFilter: /^\s*#include/,
+        monacoDisassembly: null,
+        digitSeparator: "'",
+    },
+    coccinelle_for_cpp: {
+        name: 'C++ with Coccinelle',
+        monaco: 'cppp',
+        extensions: ['.cpp', '.h'],
+        alias: [],
+        logoUrl: 'c++.svg',
+        logoUrlDark: null,
+        formatter: 'clangformat',
+        previewFilter: /^\s*#include/,
+        monacoDisassembly: null,
+        digitSeparator: "'",
+    },
     jakt: {
         name: 'Jakt',
         monaco: 'jakt',
@@ -928,6 +952,18 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
         previewFilter: null,
         monacoDisassembly: null,
     },
+    sway: {
+        name: 'sway',
+        monaco: 'sway',
+        extensions: ['.sw'],
+        alias: [],
+        logoUrl: 'sway.svg',
+        logoUrlDark: null,
+        formatter: null,
+        previewFilter: null,
+        monacoDisassembly: null,
+        digitSeparator: '_',
+    },
 };
 
 export const languages = Object.fromEntries(
diff --git a/static/modes/_all.ts b/static/modes/_all.ts
index 6e8a641be..e5e201ec2 100644
--- a/static/modes/_all.ts
+++ b/static/modes/_all.ts
@@ -63,6 +63,7 @@ import './ptx-mode';
 import './slang-mode';
 import './spice-mode';
 import './spirv-mode';
+import './sway-mode';
 import './tablegen-mode';
 import './v-mode';
 import './vala-mode';
diff --git a/static/modes/sway-mode.ts b/static/modes/sway-mode.ts
new file mode 100644
index 000000000..f785b3dac
--- /dev/null
+++ b/static/modes/sway-mode.ts
@@ -0,0 +1,208 @@
+// 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 * as monaco from 'monaco-editor';
+
+function definition(): monaco.languages.IMonarchLanguage {
+    return {
+        keywords: [
+            'abi',
+            'as',
+            'asm',
+            'break',
+            'configurable',
+            'const',
+            'contract',
+            'continue',
+            'deref',
+            'else',
+            'enum',
+            'fn',
+            'for',
+            'if',
+            'impl',
+            'let',
+            'library',
+            'match',
+            'mod',
+            'mut',
+            'predicate',
+            'pub',
+            'ref',
+            'return',
+            'script',
+            'self',
+            'storage',
+            'struct',
+            'trait',
+            'type',
+            'use',
+            'where',
+            'while',
+        ],
+
+        typeKeywords: [
+            'u8',
+            'u16',
+            'u32',
+            'u64',
+            'u128',
+            'u256',
+            'i8',
+            'i16',
+            'i32',
+            'i64',
+            'i128',
+            'i256',
+            'b256',
+            'bool',
+            'str',
+            'Self',
+        ],
+
+        operators: [
+            '=',
+            '>',
+            '<',
+            '!',
+            '~',
+            '?',
+            ':',
+            '==',
+            '<=',
+            '>=',
+            '!=',
+            '&&',
+            '||',
+            '++',
+            '--',
+            '+',
+            '-',
+            '*',
+            '/',
+            '&',
+            '|',
+            '^',
+            '%',
+            '<<',
+            '>>',
+            '>>>',
+            '+=',
+            '-=',
+            '*=',
+            '/=',
+            '&=',
+            '|=',
+            '^=',
+            '%=',
+            '<<=',
+            '>>=',
+            '>>>=',
+        ],
+
+        symbols: /[=><!~?:&|+\-*\/\^%]+/,
+
+        escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
+
+        tokenizer: {
+            root: [
+                // identifiers and keywords
+                [
+                    /[a-z_$][\w$]*/,
+                    {
+                        cases: {
+                            '@typeKeywords': 'keyword.type',
+                            '@keywords': 'keyword',
+                            '@default': 'identifier',
+                        },
+                    },
+                ],
+                [/[A-Z][\w$]*/, 'type.identifier'],
+
+                // whitespace
+                {include: '@whitespace'},
+
+                // delimiters and operators
+                [/[{}()\[\]]/, '@brackets'],
+                [/[<>](?!@symbols)/, '@brackets'],
+                [
+                    /@symbols/,
+                    {
+                        cases: {
+                            '@operators': 'operator',
+                            '@default': '',
+                        },
+                    },
+                ],
+
+                // numbers
+                [/\d*\.\d+([eE][-+]?\d+)?/, 'number.float'],
+                [/0[xX][0-9a-fA-F]+/, 'number.hex'],
+                [/\d+/, 'number'],
+
+                // delimiter: after number because of .\d floats
+                [/[;,.]/, 'delimiter'],
+
+                // strings
+                [/"([^"\\]|\\.)*$/, 'string.invalid'],
+                [
+                    /"/,
+                    {
+                        token: 'string.quote',
+                        bracket: '@open',
+                        next: '@string',
+                    },
+                ],
+
+                // characters
+                [/'[^\\']'/, 'string'],
+                [/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
+                [/'/, 'string.invalid'],
+            ],
+
+            comment: [
+                [/[^/*]+/, 'comment'],
+                [/\/\*/, 'comment', '@push'],
+                ['\\*/', 'comment', '@pop'],
+                [/[/*]/, 'comment'],
+            ],
+
+            string: [
+                [/[^\\"]+/, 'string'],
+                [/@escapes/, 'string.escape'],
+                [/\\./, 'string.escape.invalid'],
+                [/"/, {token: 'string.quote', bracket: '@close', next: '@pop'}],
+            ],
+
+            whitespace: [
+                [/[ \t\r\n]+/, 'white'],
+                [/\/\*/, 'comment', '@comment'],
+                [/\/\/.*$/, 'comment'],
+            ],
+        },
+    };
+}
+
+monaco.languages.register({id: 'sway'});
+monaco.languages.setMonarchTokensProvider('sway', definition());
diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts
index 7a3bebca1..d1199636b 100644
--- a/types/languages.interfaces.ts
+++ b/types/languages.interfaces.ts
@@ -39,6 +39,8 @@ export type LanguageKey =
     | 'cmake'
     | 'cmakescript'
     | 'cobol'
+    | 'coccinelle_for_c'
+    | 'coccinelle_for_cpp'
     | 'cpp_for_opencl'
     | 'cppx'
     | 'snowball'
@@ -90,6 +92,7 @@ export type LanguageKey =
     | 'solidity'
     | 'spice'
     | 'spirv'
+    | 'sway'
     | 'swift'
     | 'tablegen'
     | 'toit'
diff --git a/views/resources/logos/sway.svg b/views/resources/logos/sway.svg
new file mode 100644
index 000000000..3e39af1ee
--- /dev/null
+++ b/views/resources/logos/sway.svg
@@ -0,0 +1,53 @@
+<svg width="834" height="907" viewBox="0 0 834 907" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M491.685 799.437L447.013 804.777L396.611 795.229L375.993 783.017L391.271 873.122L409.975 893.359L461.139 906.332L508.856 899.46L491.685 799.437Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M513.855 784.527L490.943 799.043L508.505 899.828L529.123 882.265L513.855 784.527Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M505.42 776.122L486.716 788.725L443.577 794.065L401.961 785.289L384.399 773.828L375.993 783.375L397.763 796.74L446.633 805.897L491.294 800.557L513.826 784.898L505.42 776.122Z" fill="#7B4A38"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M476.043 698.259L433.285 702.457L395.486 694.823L371.812 681.078L384.035 774.228L401.216 785.689L442.822 794.846L487.494 789.506L476.043 698.259Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M492.884 686.791L475.702 698.252L487.153 788.738L505.096 777.277L492.884 686.791Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M487.484 679.162L472.978 689.09L431.361 692.526L397.379 687.557L380.589 674.964L371.422 681.075L395.476 696.724L433.275 702.835L474.891 699.018L493.215 686.796L487.484 679.162Z" fill="#7B4A38"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M474.533 572.252L460.016 579.896L429.861 582.561L396.259 577.982L381.753 570.348L380.22 675.721L397.401 688.324L432.536 694.045L473.38 689.466L487.125 679.538L474.533 572.252Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M469.909 565.375L456.545 573.02L428.674 575.684L399.661 570.345L386.677 564.233L381.718 570.725L395.844 579.121L429.445 582.938L459.6 580.654L474.498 572.629L469.909 565.375Z" fill="#7B4A38"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M464.987 448.928L437.115 451.984L411.148 450.451L399.316 444.73L387.094 564.61L400.078 571.482L428.329 576.061L456.972 573.396L469.945 565.752L477.97 442.817L464.987 448.928Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M456.561 326.766L424.111 320.274L402.352 439.773L433.278 458.868L461.53 447.407L473.742 452.757L482.909 322.569L456.561 326.766Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M475.275 437.876L466.869 443.216L440.531 446.652L411.899 444.749L402.352 439.399L399.296 445.13L411.127 450.85L440.151 452.383L467.291 449.327L477.95 442.835L475.275 437.876Z" fill="#7B4A38"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M482.555 318.347L474.335 322.174L455.62 324.592L432.986 323.08L425.445 318.851L423.377 323.316L432.379 327.905L455.322 329.119L476.773 326.701L482.936 322.936L482.555 318.347Z" fill="#7B4A38"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M474.509 571.858L459.23 581.025L473.356 689.453L487.101 680.286L474.509 571.858Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M468.004 448.921L457.314 573.008L469.907 566.135L477.428 443.478L468.004 448.921Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M476.798 326.758L467.25 443.582L474.514 438.623L482.909 322.18L476.798 326.758Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M492.853 195.046C492.102 195.592 471.854 181.682 471.854 181.682L450.856 189.316L423.746 319.123L424.888 319.504L434.436 324.082L455.815 324.854L475.29 322.94L482.544 318.362L490.178 244.677C490.178 244.677 493.604 194.501 492.853 195.046Z" fill="#74402F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M482.912 268.341L476.42 322.561L482.531 318.353L487.871 272.539L482.912 268.341Z" fill="#744534"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M177.536 292.386L142.412 518.41L236.334 314.907L177.536 292.386Z" fill="#32B12E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M234.431 315.681L142.412 518.031L160.355 533.68L331.789 394.705L234.431 315.681Z" fill="#279523"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M332.901 231.275L232.868 314.507L330.988 395.455L426.823 320.238L332.901 231.275Z" fill="#29B825"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M269.546 216.794L176.766 292.393L234.03 316.066L333.683 231.301L269.546 216.794Z" fill="#30CB2B"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M431.397 208.013L269.521 216.789L330.602 232.829L426.438 320.639L449.72 243.518L463.856 238.549L464.618 226.337L431.397 208.013Z" fill="#4EE448"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M439.771 5.32934L338.349 0L367.341 31.3073L468.259 38.5194L439.771 5.32934Z" fill="#53E44E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M467.566 37.7146L367.358 31.3153L442.175 123.704L483.626 121.76L467.566 37.7146Z" fill="#44D83F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M467.16 37.7356L481.348 121.277L497.079 142.347L515.999 81.7901L467.16 37.7356Z" fill="#61E45C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M442.153 123.352L466.753 193.405L476.897 196.348L481.712 120.904L442.153 123.352Z" fill="#2DAD2A"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M481.369 120.485L476.533 196.31L481.667 199.694L497.233 141.771L481.369 120.485Z" fill="#3BC237"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M510.806 209.533L477.976 214.883L471.104 257.26L549.367 247.332L510.806 209.533Z" fill="#3DE337"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M547.463 246.958L471.104 256.887L507.75 343.936L581.435 343.174L547.463 246.958Z" fill="#3EDC39"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M581.803 343.174L506.977 343.555L574.55 537.88L587.153 529.485L581.803 343.174Z" fill="#2EB42A"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M580.67 342.77L586.781 529.091L601.668 542.075L681.464 384.386L580.67 342.77Z" fill="#289C24"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M586.815 528.338L574.212 537.494L601.702 541.702L586.815 528.338Z" fill="#31912E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M653.232 303.451L511.202 210.29L547.479 246.948L580.689 342.773L681.484 384.389L653.232 303.451Z" fill="#54E94F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M139.355 48.8041L0 139.67L150.816 64.4527L139.355 48.8041Z" fill="#38CD33"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M150.784 64.0638L0.348633 139.282L4.16559 148.057L167.966 128.211L150.784 64.0638Z" fill="#30B62C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M139.353 48.8042L150.814 64.8436L293.605 73.2389L139.353 48.8042Z" fill="#46E641"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M293.925 72.8448L150.753 64.4392L167.543 128.587L307.289 146.91L293.925 72.8448Z" fill="#44DE3F"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M160.732 37.7355L139.353 48.8057L203.5 59.1146L160.732 37.7355Z" fill="#3FD43B"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M245.092 14.0555L160.707 37.7289L201.943 59.108L294.722 73.6145L339.014 45.7435L245.092 14.0555Z" fill="#47DF42"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M426.844 141.963L373.016 70.5624L338.653 45.3664L293.601 73.2374C293.601 73.2374 421.505 156.089 444.293 172.149L426.844 141.963Z" fill="#41CF3C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M414.621 137.38L293.209 72.8622L306.954 146.928L419.971 174.418L461.587 211.075L414.621 137.38Z" fill="#54E450"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M464.62 226.723L461.184 210.303L420.329 174.037L410.781 171.743L414.979 179.767L432.551 201.527L431.78 208.4L464.62 226.723Z" fill="#2BA828"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M772.724 179.379L674.224 145.026L578.779 163.349L552.811 171.745L513.109 161.436L508.139 183.206L494.014 190.84L490.958 203.433L587.174 169.08L717.362 167.928L817.396 206.488L772.724 179.379Z" fill="#5CE857"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M817.043 206.095L717.771 167.534L726.177 224.809L803.679 248.472L809.409 256.116L833.083 261.456L817.043 206.095Z" fill="#51D84C"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M817.024 206.086L804.04 248.464L809.009 256.869L833.444 262.209L817.024 206.086Z" fill="#36BD31"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M717.749 167.915L587.18 169.068L583.363 194.644L726.916 225.19L717.749 167.915Z" fill="#36BD31"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M675.733 283.592L553.56 238.54L652.831 303.448L677.647 373.316L689.859 374.078L720.024 405.385L756.682 376.372L675.733 283.592Z" fill="#3FD43B"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M706.681 293.135L675.374 283.588L755.17 377.129L797.928 373.693L706.681 293.135Z" fill="#5FEC59"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M754.426 376.752L786.875 569.945L797.565 373.696L754.426 376.752Z" fill="#32B12E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M754.791 377.118L719.286 404.989L778.464 569.55L786.86 569.159L754.791 377.118Z" fill="#31912E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M490.589 203.046L583.749 194.65L587.566 169.074L490.589 203.046Z" fill="#2EB42A"/>
+</svg>


Test patch:
diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml
index 2d8a54ae6..0c3682ce3 100644
--- a/.github/workflows/test-and-deploy.yml
+++ b/.github/workflows/test-and-deploy.yml
@@ -18,13 +18,17 @@ jobs:
           check-latest: true
           cache: npm
       - name: Install prerequisites
-        run: make prereqs
+        run: |
+          make prereqs
+          python3 -m pip install numba
       - name: Run checks
         run: |
           npm run lint-check
           npm run test
           npm run ts-check
           python3 ./etc/scripts/util/propscheck.py
+          python3 ./etc/scripts/util/propschecktest.py
+          python3 ./etc/scripts/test_numba_wrapper.py
       - uses: codecov/codecov-action@v4
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/etc/scripts/test_numba_wrapper.py b/etc/scripts/test_numba_wrapper.py
index 6e47fe499..4efbef044 100644
--- a/etc/scripts/test_numba_wrapper.py
+++ b/etc/scripts/test_numba_wrapper.py
@@ -30,11 +30,10 @@ import os
 import sys
 import unittest
 import unittest.mock
-
+import tempfile
 import numba
-from numba.core.caching import tempfile
 
-from . import numba_wrapper
+import numba_wrapper
 
 
 class TestMain(unittest.TestCase):
@@ -188,3 +187,6 @@ class TestOpenOrStdout(unittest.TestCase):
     def test_stdout(self) -> None:
         with numba_wrapper._open_or_stdout(None) as file_:
             self.assertIs(file_, sys.stdout)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/etc/scripts/util/propschecktest.py b/etc/scripts/util/propschecktest.py
index e6f665be7..cb9da2f93 100644
--- a/etc/scripts/util/propschecktest.py
+++ b/etc/scripts/util/propschecktest.py
@@ -1,5 +1,6 @@
-import sys
+import argparse
 import os
+import sys
 import unittest
 
 from propscheck import process_file, Line
@@ -9,7 +10,8 @@ class PropsCheckTests(unittest.TestCase):
     def run_test(self, filename, expected_key, expected_contents):
         base_path = os.path.dirname(os.path.abspath(sys.argv[0]))
         test_case_file = os.path.join(base_path, 'test', 'cases', f"{filename}.properties")
-        result = process_file(test_case_file)
+        args = argparse.Namespace(check_suspicious_in_default_prop=True)
+        result = process_file(test_case_file, args)
         self.assertEqual(result[expected_key], {Line(-1, text) for text in expected_contents})
 
     def test_bad_compilers_exe(self):
@@ -77,7 +79,8 @@ class PropsCheckTests(unittest.TestCase):
     def test_good_file(self):
         base_path = os.path.dirname(os.path.abspath(sys.argv[0]))
         test_case_file = os.path.join(base_path, '..', '..', 'config', 'c++.amazon.properties')
-        result = process_file(test_case_file)
+        args = argparse.Namespace(check_suspicious_in_default_prop=False)
+        result = process_file(test_case_file, args)
         for k in result:
             self.assertEqual(result[k], set(), f"{k} has output in known good file")
 
diff --git a/test/filter-tests.ts b/test/filter-tests.ts
index 46bc0f1d9..73abd2332 100644
--- a/test/filter-tests.ts
+++ b/test/filter-tests.ts
@@ -83,15 +83,16 @@ function testFilter(filename: string, suffix: string, filters: ParseFiltersAndOu
     const testName = path.basename(filename + suffix);
     it(
         testName,
-        () => {
+        // Bump the timeout a bit so that we don't fail for slow cases
+        {timeout: 10000},
+        async () => {
             const result = processAsm(filename, filters);
             delete result.parsingTime;
             delete result.filteredCount;
             // TODO normalize line endings?
-            expect(stringifyKeysInOrder(result)).toMatchFileSnapshot(path.join(casesRoot, testName + '.json'));
+            await expect(stringifyKeysInOrder(result)).toMatchFileSnapshot(path.join(casesRoot, testName + '.json'));
         },
-        {timeout: 10000},
-    ); // Bump the timeout a bit so that we don't fail for slow cases
+    );
 }
 
 describe('Filter test cases', () => {