Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Algol68
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
C with Coccinelle
C++ with Coccinelle
C++ (Circle)
CIRCT
Clean
CMake
CMakeScript
COBOL
C++ for OpenCL
MLIR
Cppx
Cppx-Blue
Cppx-Gold
Cpp2-cppfront
Crystal
C#
CUDA C++
D
Dart
Elixir
Erlang
Fortran
F#
GLSL
Go
Haskell
HLSL
Hook
Hylo
IL
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Mojo
Nim
Numba
Nix
Objective-C
Objective-C++
OCaml
Odin
OpenCL C
Pascal
Pony
PTX
Python
Racket
Raku
Ruby
Rust
Sail
Snowball
Scala
Slang
Solidity
Spice
SPIR-V
Swift
LLVM TableGen
Toit
Triton
TypeScript Native
V
Vala
Visual Basic
Vyper
WASM
Zig
Javascript
GIMPLE
Ygen
sway
c source #1
Output
Compile to binary object
Link to binary
Execute the code
Intel asm syntax
Demangle identifiers
Verbose demangling
Filters
Unused labels
Library functions
Directives
Comments
Horizontal whitespace
Debug intrinsics
Compiler
6502 cc65 2.17
6502 cc65 2.18
6502 cc65 2.19
6502 cc65 trunk
ARM GCC 10.2.0 (linux)
ARM GCC 10.2.1 (none)
ARM GCC 10.3.0 (linux)
ARM GCC 10.3.1 (2021.07 none)
ARM GCC 10.3.1 (2021.10 none)
ARM GCC 10.5.0
ARM GCC 11.1.0 (linux)
ARM GCC 11.2.0 (linux)
ARM GCC 11.2.1 (none)
ARM GCC 11.3.0 (linux)
ARM GCC 11.4.0
ARM GCC 12.1.0 (linux)
ARM GCC 12.2.0 (linux)
ARM GCC 12.3.0
ARM GCC 12.4.0
ARM GCC 12.5.0
ARM GCC 13.1.0 (linux)
ARM GCC 13.2.0
ARM GCC 13.2.0 (unknown-eabi)
ARM GCC 13.3.0
ARM GCC 13.3.0 (unknown-eabi)
ARM GCC 13.4.0
ARM GCC 13.4.0 (unknown-eabi)
ARM GCC 14.1.0
ARM GCC 14.1.0 (unknown-eabi)
ARM GCC 14.2.0
ARM GCC 14.2.0 (unknown-eabi)
ARM GCC 14.3.0
ARM GCC 14.3.0 (unknown-eabi)
ARM GCC 15.1.0
ARM GCC 15.1.0 (unknown-eabi)
ARM GCC 15.2.0
ARM GCC 15.2.0 (unknown-eabi)
ARM GCC 4.5.4 (linux)
ARM GCC 4.6.4 (linux)
ARM GCC 5.4 (linux)
ARM GCC 5.4.1 (none)
ARM GCC 6.3.0 (linux)
ARM GCC 6.4.0 (linux)
ARM GCC 7.2.1 (none)
ARM GCC 7.3.0 (linux)
ARM GCC 7.5.0 (linux)
ARM GCC 8.2.0 (WinCE)
ARM GCC 8.2.0 (linux)
ARM GCC 8.3.1 (none)
ARM GCC 8.5.0 (linux)
ARM GCC 9.2.1 (none)
ARM GCC 9.3.0 (linux)
ARM GCC trunk (linux)
ARM msvc v19.0 (ex-WINE)
ARM msvc v19.10 (ex-WINE)
ARM msvc v19.14 (ex-WINE)
ARM64 GCC 10.2.0
ARM64 GCC 10.3.0
ARM64 GCC 10.4.0
ARM64 GCC 10.5.0
ARM64 GCC 11.1.0
ARM64 GCC 11.2.0
ARM64 GCC 11.3.0
ARM64 GCC 11.4.0
ARM64 GCC 12.1.0
ARM64 GCC 12.2.0
ARM64 GCC 12.3.0
ARM64 GCC 12.4.0
ARM64 GCC 12.5.0
ARM64 GCC 13.1.0
ARM64 GCC 13.2.0
ARM64 GCC 13.3.0
ARM64 GCC 13.4.0
ARM64 GCC 14.1.0
ARM64 GCC 14.2.0
ARM64 GCC 14.3.0
ARM64 GCC 15.1.0
ARM64 GCC 15.2.0
ARM64 GCC 4.9.4
ARM64 GCC 5.4
ARM64 GCC 5.5.0
ARM64 GCC 6.3
ARM64 GCC 6.4.0
ARM64 GCC 7.3.0
ARM64 GCC 7.5.0
ARM64 GCC 8.2.0
ARM64 GCC 8.5.0
ARM64 GCC 9.3.0
ARM64 GCC 9.4.0
ARM64 GCC 9.5.0
ARM64 GCC trunk
ARM64 Morello GCC 10.1.0 Alpha 1
ARM64 Morello GCC 10.1.2 Alpha 2
ARM64 msvc v19.14 (ex-WINE)
AVR gcc 10.3.0
AVR gcc 11.1.0
AVR gcc 12.1.0
AVR gcc 12.2.0
AVR gcc 12.3.0
AVR gcc 12.4.0
AVR gcc 12.5.0
AVR gcc 13.1.0
AVR gcc 13.2.0
AVR gcc 13.3.0
AVR gcc 13.4.0
AVR gcc 14.1.0
AVR gcc 14.2.0
AVR gcc 14.3.0
AVR gcc 15.1.0
AVR gcc 15.2.0
AVR gcc 4.5.4
AVR gcc 4.6.4
AVR gcc 5.4.0
AVR gcc 9.2.0
AVR gcc 9.3.0
Arduino Mega (1.8.9)
Arduino Uno (1.8.9)
BPF clang (trunk)
BPF clang 13.0.0
BPF clang 14.0.0
BPF clang 15.0.0
BPF clang 16.0.0
BPF clang 17.0.1
BPF clang 18.1.0
BPF clang 19.1.0
BPF clang 20.1.0
BPF clang 21.1.0
BPF gcc 13.1.0
BPF gcc 13.2.0
BPF gcc 13.3.0
BPF gcc 13.4.0
BPF gcc 14.1.0
BPF gcc 14.2.0
BPF gcc 14.3.0
BPF gcc 15.1.0
BPF gcc 15.2.0
BPF gcc trunk
C2Rust (master)
Chibicc 2020-12-07
FRC 2019
FRC 2020
FRC 2023
HPPA gcc 14.2.0
HPPA gcc 14.3.0
HPPA gcc 15.1.0
HPPA gcc 15.2.0
K1C gcc 7.4
K1C gcc 7.5
KVX ACB 4.1.0 (GCC 7.5.0)
KVX ACB 4.1.0-cd1 (GCC 7.5.0)
KVX ACB 4.10.0 (GCC 10.3.1)
KVX ACB 4.11.1 (GCC 10.3.1)
KVX ACB 4.12.0 (GCC 11.3.0)
KVX ACB 4.2.0 (GCC 7.5.0)
KVX ACB 4.3.0 (GCC 7.5.0)
KVX ACB 4.4.0 (GCC 7.5.0)
KVX ACB 4.6.0 (GCC 9.4.1)
KVX ACB 4.8.0 (GCC 9.4.1)
KVX ACB 4.9.0 (GCC 9.4.1)
KVX ACB 5.0.0 (GCC 12.2.1)
KVX ACB 5.2.0 (GCC 13.2.1)
LC3 (trunk)
M68K clang (trunk)
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 13.3.0
M68K gcc 13.4.0
M68K gcc 14.1.0
M68K gcc 14.2.0
M68K gcc 14.3.0
M68K gcc 15.1.0
M68K gcc 15.2.0
MRISC32 gcc (trunk)
MSP430 gcc 12.1.0
MSP430 gcc 12.2.0
MSP430 gcc 12.3.0
MSP430 gcc 12.4.0
MSP430 gcc 12.5.0
MSP430 gcc 13.1.0
MSP430 gcc 13.2.0
MSP430 gcc 13.3.0
MSP430 gcc 13.4.0
MSP430 gcc 14.1.0
MSP430 gcc 14.2.0
MSP430 gcc 14.3.0
MSP430 gcc 15.1.0
MSP430 gcc 15.2.0
MSP430 gcc 4.5.3
MSP430 gcc 5.3.0
MSP430 gcc 6.2.1
MinGW clang 14.0.3
MinGW clang 14.0.6
MinGW clang 15.0.7
MinGW clang 16.0.0
MinGW clang 16.0.2
MinGW gcc 11.3.0
MinGW gcc 12.1.0
MinGW gcc 12.2.0
MinGW gcc 13.1.0
MinGW gcc 14.3.0
MinGW gcc 15.2.0
ORCA/C 2.1.0
ORCA/C 2.2.0
ORCA/C 2.2.1
POWER64 gcc 11.2.0
POWER64 gcc 12.1.0
POWER64 gcc 12.2.0
POWER64 gcc 12.3.0
POWER64 gcc 12.4.0
POWER64 gcc 12.5.0
POWER64 gcc 13.1.0
POWER64 gcc 13.2.0
POWER64 gcc 13.3.0
POWER64 gcc 13.4.0
POWER64 gcc 14.1.0
POWER64 gcc 14.2.0
POWER64 gcc 14.3.0
POWER64 gcc 15.1.0
POWER64 gcc 15.2.0
POWER64 gcc trunk
RISC-V (32-bits) gcc (trunk)
RISC-V (32-bits) gcc 10.2.0
RISC-V (32-bits) gcc 10.3.0
RISC-V (32-bits) gcc 11.2.0
RISC-V (32-bits) gcc 11.3.0
RISC-V (32-bits) gcc 11.4.0
RISC-V (32-bits) gcc 12.1.0
RISC-V (32-bits) gcc 12.2.0
RISC-V (32-bits) gcc 12.3.0
RISC-V (32-bits) gcc 12.4.0
RISC-V (32-bits) gcc 12.5.0
RISC-V (32-bits) gcc 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 13.3.0
RISC-V (32-bits) gcc 13.4.0
RISC-V (32-bits) gcc 14.1.0
RISC-V (32-bits) gcc 14.2.0
RISC-V (32-bits) gcc 14.3.0
RISC-V (32-bits) gcc 15.1.0
RISC-V (32-bits) gcc 15.2.0
RISC-V (32-bits) gcc 8.2.0
RISC-V (32-bits) gcc 8.5.0
RISC-V (32-bits) gcc 9.4.0
RISC-V (64-bits) gcc (trunk)
RISC-V (64-bits) gcc 10.2.0
RISC-V (64-bits) gcc 10.3.0
RISC-V (64-bits) gcc 11.2.0
RISC-V (64-bits) gcc 11.3.0
RISC-V (64-bits) gcc 11.4.0
RISC-V (64-bits) gcc 12.1.0
RISC-V (64-bits) gcc 12.2.0
RISC-V (64-bits) gcc 12.3.0
RISC-V (64-bits) gcc 12.4.0
RISC-V (64-bits) gcc 12.5.0
RISC-V (64-bits) gcc 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 13.3.0
RISC-V (64-bits) gcc 13.4.0
RISC-V (64-bits) gcc 14.1.0
RISC-V (64-bits) gcc 14.2.0
RISC-V (64-bits) gcc 14.3.0
RISC-V (64-bits) gcc 15.1.0
RISC-V (64-bits) gcc 15.2.0
RISC-V (64-bits) gcc 8.2.0
RISC-V (64-bits) gcc 8.5.0
RISC-V (64-bits) gcc 9.4.0
RISC-V rv32gc clang (trunk)
RISC-V rv32gc clang 10.0.0
RISC-V rv32gc clang 10.0.1
RISC-V rv32gc clang 11.0.0
RISC-V rv32gc clang 11.0.1
RISC-V rv32gc clang 12.0.0
RISC-V rv32gc clang 12.0.1
RISC-V rv32gc clang 13.0.0
RISC-V rv32gc clang 13.0.1
RISC-V rv32gc clang 14.0.0
RISC-V rv32gc clang 15.0.0
RISC-V rv32gc clang 16.0.0
RISC-V rv32gc clang 17.0.1
RISC-V rv32gc clang 18.1.0
RISC-V rv32gc clang 19.1.0
RISC-V rv32gc clang 20.1.0
RISC-V rv32gc clang 21.1.0
RISC-V rv32gc clang 9.0.0
RISC-V rv32gc clang 9.0.1
RISC-V rv64gc clang (trunk)
RISC-V rv64gc clang 10.0.0
RISC-V rv64gc clang 10.0.1
RISC-V rv64gc clang 11.0.0
RISC-V rv64gc clang 11.0.1
RISC-V rv64gc clang 12.0.0
RISC-V rv64gc clang 12.0.1
RISC-V rv64gc clang 13.0.0
RISC-V rv64gc clang 13.0.1
RISC-V rv64gc clang 14.0.0
RISC-V rv64gc clang 15.0.0
RISC-V rv64gc clang 16.0.0
RISC-V rv64gc clang 17.0.1
RISC-V rv64gc clang 18.1.0
RISC-V rv64gc clang 19.1.0
RISC-V rv64gc clang 20.1.0
RISC-V rv64gc clang 21.1.0
RISC-V rv64gc clang 9.0.0
RISC-V rv64gc clang 9.0.1
Raspbian Buster
Raspbian Stretch
SDCC 4.0.0
SDCC 4.1.0
SDCC 4.2.0
SDCC 4.3.0
SDCC 4.4.0
SDCC 4.5.0
SPARC LEON gcc 12.2.0
SPARC LEON gcc 12.3.0
SPARC LEON gcc 12.4.0
SPARC LEON gcc 12.5.0
SPARC LEON gcc 13.1.0
SPARC LEON gcc 13.2.0
SPARC LEON gcc 13.3.0
SPARC LEON gcc 13.4.0
SPARC LEON gcc 14.1.0
SPARC LEON gcc 14.2.0
SPARC LEON gcc 14.3.0
SPARC LEON gcc 15.1.0
SPARC LEON gcc 15.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 12.4.0
SPARC gcc 12.5.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 13.3.0
SPARC gcc 13.4.0
SPARC gcc 14.1.0
SPARC gcc 14.2.0
SPARC gcc 14.3.0
SPARC gcc 15.1.0
SPARC gcc 15.2.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 12.4.0
SPARC64 gcc 12.5.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 13.3.0
SPARC64 gcc 13.4.0
SPARC64 gcc 14.1.0
SPARC64 gcc 14.2.0
SPARC64 gcc 14.3.0
SPARC64 gcc 15.1.0
SPARC64 gcc 15.2.0
TCC (trunk)
TCC 0.9.27
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 12.4.0
TI C6x gcc 12.5.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 13.3.0
TI C6x gcc 13.4.0
TI C6x gcc 14.1.0
TI C6x gcc 14.2.0
TI C6x gcc 14.3.0
TI C6x gcc 15.1.0
TI C6x gcc 15.2.0
TI CL430 21.6.1
Tricore gcc 11.3.0 (EEESlab)
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
VAX gcc NetBSDELF 12.4.0 (Apr 16 05:27 2025)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
Xtensa ESP32 gcc 14.2.0 (20241119)
Xtensa ESP32 gcc 8.2.0 (2019r2)
Xtensa ESP32 gcc 8.2.0 (2020r1)
Xtensa ESP32 gcc 8.2.0 (2020r2)
Xtensa ESP32 gcc 8.4.0 (2020r3)
Xtensa ESP32 gcc 8.4.0 (2021r1)
Xtensa ESP32 gcc 8.4.0 (2021r2)
Xtensa ESP32-S2 gcc 11.2.0 (2022r1)
Xtensa ESP32-S2 gcc 12.2.0 (20230208)
Xtensa ESP32-S2 gcc 14.2.0 (20241119)
Xtensa ESP32-S2 gcc 8.2.0 (2019r2)
Xtensa ESP32-S2 gcc 8.2.0 (2020r1)
Xtensa ESP32-S2 gcc 8.2.0 (2020r2)
Xtensa ESP32-S2 gcc 8.4.0 (2020r3)
Xtensa ESP32-S2 gcc 8.4.0 (2021r1)
Xtensa ESP32-S2 gcc 8.4.0 (2021r2)
Xtensa ESP32-S3 gcc 11.2.0 (2022r1)
Xtensa ESP32-S3 gcc 12.2.0 (20230208)
Xtensa ESP32-S3 gcc 14.2.0 (20241119)
Xtensa ESP32-S3 gcc 8.4.0 (2020r3)
Xtensa ESP32-S3 gcc 8.4.0 (2021r1)
Xtensa ESP32-S3 gcc 8.4.0 (2021r2)
arm64 msvc v19.20 VS16.0
arm64 msvc v19.21 VS16.1
arm64 msvc v19.22 VS16.2
arm64 msvc v19.23 VS16.3
arm64 msvc v19.24 VS16.4
arm64 msvc v19.25 VS16.5
arm64 msvc v19.27 VS16.7
arm64 msvc v19.28 VS16.8
arm64 msvc v19.28 VS16.9
arm64 msvc v19.29 VS16.10
arm64 msvc v19.29 VS16.11
arm64 msvc v19.30 VS17.0
arm64 msvc v19.31 VS17.1
arm64 msvc v19.32 VS17.2
arm64 msvc v19.33 VS17.3
arm64 msvc v19.34 VS17.4
arm64 msvc v19.35 VS17.5
arm64 msvc v19.36 VS17.6
arm64 msvc v19.37 VS17.7
arm64 msvc v19.38 VS17.8
arm64 msvc v19.39 VS17.9
arm64 msvc v19.40 VS17.10
arm64 msvc v19.41 VS17.11
arm64 msvc v19.42 VS17.12
arm64 msvc v19.43 VS17.13
arm64 msvc v19.latest
armv7-a clang (trunk)
armv7-a clang 10.0.0
armv7-a clang 10.0.1
armv7-a clang 11.0.0
armv7-a clang 11.0.1
armv7-a clang 12.0.0
armv7-a clang 12.0.1
armv7-a clang 13.0.0
armv7-a clang 13.0.1
armv7-a clang 14.0.0
armv7-a clang 15.0.0
armv7-a clang 16.0.0
armv7-a clang 17.0.1
armv7-a clang 18.1.0
armv7-a clang 19.1.0
armv7-a clang 20.1.0
armv7-a clang 21.1.0
armv7-a clang 9.0.0
armv7-a clang 9.0.1
armv8-a clang (all architectural features, trunk)
armv8-a clang (trunk)
armv8-a clang 10.0.0
armv8-a clang 10.0.1
armv8-a clang 11.0.0
armv8-a clang 11.0.1
armv8-a clang 12.0.0
armv8-a clang 12.0.1
armv8-a clang 13.0.0
armv8-a clang 13.0.1
armv8-a clang 14.0.0
armv8-a clang 15.0.0
armv8-a clang 16.0.0
armv8-a clang 17.0.1
armv8-a clang 18.1.0
armv8-a clang 19.1.0
armv8-a clang 20.1.0
armv8-a clang 21.1.0
armv8-a clang 9.0.0
armv8-a clang 9.0.1
clang 12 for DPU (rel 2023.2.0)
cproc-master
ez80-clang 15.0.0
ez80-clang 15.0.7
llvm-mos commander X16
llvm-mos commodore 64
llvm-mos mega65
llvm-mos nes-cnrom
llvm-mos nes-mmc1
llvm-mos nes-mmc3
llvm-mos nes-nrom
llvm-mos osi-c1p
loongarch64 gcc 12.2.0
loongarch64 gcc 12.3.0
loongarch64 gcc 12.4.0
loongarch64 gcc 12.5.0
loongarch64 gcc 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 13.3.0
loongarch64 gcc 13.4.0
loongarch64 gcc 14.1.0
loongarch64 gcc 14.2.0
loongarch64 gcc 14.3.0
loongarch64 gcc 15.1.0
loongarch64 gcc 15.2.0
mips (el) gcc 12.1.0
mips (el) gcc 12.2.0
mips (el) gcc 12.3.0
mips (el) gcc 12.4.0
mips (el) gcc 12.5.0
mips (el) gcc 13.1.0
mips (el) gcc 13.2.0
mips (el) gcc 13.3.0
mips (el) gcc 13.4.0
mips (el) gcc 14.1.0
mips (el) gcc 14.2.0
mips (el) gcc 14.3.0
mips (el) gcc 15.1.0
mips (el) gcc 15.2.0
mips (el) gcc 4.9.4
mips (el) gcc 5.4
mips (el) gcc 5.5.0
mips (el) gcc 9.5.0
mips clang 13.0.0
mips clang 14.0.0
mips clang 15.0.0
mips clang 16.0.0
mips clang 17.0.1
mips clang 18.1.0
mips clang 19.1.0
mips clang 20.1.0
mips clang 21.1.0
mips gcc 11.2.0
mips gcc 12.1.0
mips gcc 12.2.0
mips gcc 12.3.0
mips gcc 12.4.0
mips gcc 12.5.0
mips gcc 13.1.0
mips gcc 13.2.0
mips gcc 13.3.0
mips gcc 13.4.0
mips gcc 14.1.0
mips gcc 14.2.0
mips gcc 14.3.0
mips gcc 15.1.0
mips gcc 15.2.0
mips gcc 4.9.4
mips gcc 5.4
mips gcc 5.5.0
mips gcc 9.3.0 (codescape)
mips gcc 9.5.0
mips64 (el) gcc 12.1.0
mips64 (el) gcc 12.2.0
mips64 (el) gcc 12.3.0
mips64 (el) gcc 12.4.0
mips64 (el) gcc 12.5.0
mips64 (el) gcc 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 13.3.0
mips64 (el) gcc 13.4.0
mips64 (el) gcc 14.1.0
mips64 (el) gcc 14.2.0
mips64 (el) gcc 14.3.0
mips64 (el) gcc 15.1.0
mips64 (el) gcc 15.2.0
mips64 (el) gcc 4.9.4
mips64 (el) gcc 5.4.0
mips64 (el) gcc 5.5.0
mips64 (el) gcc 9.5.0
mips64 clang 13.0.0
mips64 clang 14.0.0
mips64 clang 15.0.0
mips64 clang 16.0.0
mips64 clang 17.0.1
mips64 clang 18.1.0
mips64 clang 19.1.0
mips64 clang 20.1.0
mips64 clang 21.1.0
mips64 gcc 11.2.0
mips64 gcc 12.1.0
mips64 gcc 12.2.0
mips64 gcc 12.3.0
mips64 gcc 12.4.0
mips64 gcc 12.5.0
mips64 gcc 13.1.0
mips64 gcc 13.2.0
mips64 gcc 13.3.0
mips64 gcc 13.4.0
mips64 gcc 14.1.0
mips64 gcc 14.2.0
mips64 gcc 14.3.0
mips64 gcc 15.1.0
mips64 gcc 15.2.0
mips64 gcc 4.9.4
mips64 gcc 5.4
mips64 gcc 5.5.0
mips64 gcc 9.5.0
mips64el clang 13.0.0
mips64el clang 14.0.0
mips64el clang 15.0.0
mips64el clang 16.0.0
mips64el clang 17.0.1
mips64el clang 18.1.0
mips64el clang 19.1.0
mips64el clang 20.1.0
mips64el clang 21.1.0
mipsel clang 13.0.0
mipsel clang 14.0.0
mipsel clang 15.0.0
mipsel clang 16.0.0
mipsel clang 17.0.1
mipsel clang 18.1.0
mipsel clang 19.1.0
mipsel clang 20.1.0
mipsel clang 21.1.0
movfuscator (trunk)
nanoMIPS gcc 6.3.0
power gcc 11.2.0
power gcc 12.1.0
power gcc 12.2.0
power gcc 12.3.0
power gcc 12.4.0
power gcc 12.5.0
power gcc 13.1.0
power gcc 13.2.0
power gcc 13.3.0
power gcc 13.4.0
power gcc 14.1.0
power gcc 14.2.0
power gcc 14.3.0
power gcc 15.1.0
power gcc 15.2.0
power gcc 4.8.5
power64 AT12.0 (gcc8)
power64 AT13.0 (gcc9)
power64le AT12.0 (gcc8)
power64le AT13.0 (gcc9)
power64le clang (trunk)
power64le gcc 11.2.0
power64le gcc 12.1.0
power64le gcc 12.2.0
power64le gcc 12.3.0
power64le gcc 12.4.0
power64le gcc 12.5.0
power64le gcc 13.1.0
power64le gcc 13.2.0
power64le gcc 13.3.0
power64le gcc 13.4.0
power64le gcc 14.1.0
power64le gcc 14.2.0
power64le gcc 14.3.0
power64le gcc 15.1.0
power64le gcc 15.2.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
ppci 0.5.5
s390x gcc 11.2.0
s390x gcc 12.1.0
s390x gcc 12.2.0
s390x gcc 12.3.0
s390x gcc 12.4.0
s390x gcc 12.5.0
s390x gcc 13.1.0
s390x gcc 13.2.0
s390x gcc 13.3.0
s390x gcc 13.4.0
s390x gcc 14.1.0
s390x gcc 14.2.0
s390x gcc 14.3.0
s390x gcc 15.1.0
s390x gcc 15.2.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 12.4.0
sh gcc 12.5.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 13.3.0
sh gcc 13.4.0
sh gcc 14.1.0
sh gcc 14.2.0
sh gcc 14.3.0
sh gcc 15.1.0
sh gcc 15.2.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (ex-WINE)
x64 msvc v19.10 (ex-WINE)
x64 msvc v19.14 (ex-WINE)
x64 msvc v19.20 VS16.0
x64 msvc v19.21 VS16.1
x64 msvc v19.22 VS16.2
x64 msvc v19.23 VS16.3
x64 msvc v19.24 VS16.4
x64 msvc v19.25 VS16.5
x64 msvc v19.27 VS16.7
x64 msvc v19.28 VS16.8
x64 msvc v19.28 VS16.9
x64 msvc v19.29 VS16.10
x64 msvc v19.29 VS16.11
x64 msvc v19.30 VS17.0
x64 msvc v19.31 VS17.1
x64 msvc v19.32 VS17.2
x64 msvc v19.33 VS17.3
x64 msvc v19.34 VS17.4
x64 msvc v19.35 VS17.5
x64 msvc v19.36 VS17.6
x64 msvc v19.37 VS17.7
x64 msvc v19.38 VS17.8
x64 msvc v19.39 VS17.9
x64 msvc v19.40 VS17.10
x64 msvc v19.41 VS17.11
x64 msvc v19.42 VS17.12
x64 msvc v19.43 VS17.13
x64 msvc v19.latest
x86 CompCert 3.10
x86 CompCert 3.11
x86 CompCert 3.12
x86 CompCert 3.9
x86 gcc 1.27
x86 msvc v19.0 (ex-WINE)
x86 msvc v19.10 (ex-WINE)
x86 msvc v19.14 (ex-WINE)
x86 msvc v19.20 VS16.0
x86 msvc v19.21 VS16.1
x86 msvc v19.22 VS16.2
x86 msvc v19.23 VS16.3
x86 msvc v19.24 VS16.4
x86 msvc v19.25 VS16.5
x86 msvc v19.27 VS16.7
x86 msvc v19.28 VS16.8
x86 msvc v19.28 VS16.9
x86 msvc v19.29 VS16.10
x86 msvc v19.29 VS16.11
x86 msvc v19.30 VS17.0
x86 msvc v19.31 VS17.1
x86 msvc v19.32 VS17.2
x86 msvc v19.33 VS17.3
x86 msvc v19.34 VS17.4
x86 msvc v19.35 VS17.5
x86 msvc v19.36 VS17.6
x86 msvc v19.37 VS17.7
x86 msvc v19.38 VS17.8
x86 msvc v19.39 VS17.9
x86 msvc v19.40 VS17.10
x86 msvc v19.41 VS17.11
x86 msvc v19.42 VS17.12
x86 msvc v19.43 VS17.13
x86 msvc v19.latest
x86 nvc 24.11
x86 nvc 24.9
x86 nvc 25.1
x86 nvc 25.3
x86 nvc 25.5
x86 nvc 25.7
x86 nvc 25.9
x86 tendra (trunk)
x86-64 clang (assertions trunk)
x86-64 clang (thephd.dev)
x86-64 clang (trunk)
x86-64 clang (widberg)
x86-64 clang 10.0.0
x86-64 clang 10.0.1
x86-64 clang 11.0.0
x86-64 clang 11.0.1
x86-64 clang 12.0.0
x86-64 clang 12.0.1
x86-64 clang 13.0.0
x86-64 clang 13.0.1
x86-64 clang 14.0.0
x86-64 clang 15.0.0
x86-64 clang 16.0.0
x86-64 clang 17.0.1
x86-64 clang 18.1.0
x86-64 clang 19.1.0
x86-64 clang 20.1.0
x86-64 clang 21.1.0
x86-64 clang 3.0.0
x86-64 clang 3.1
x86-64 clang 3.2
x86-64 clang 3.3
x86-64 clang 3.4.1
x86-64 clang 3.5
x86-64 clang 3.5.1
x86-64 clang 3.5.2
x86-64 clang 3.6
x86-64 clang 3.7
x86-64 clang 3.7.1
x86-64 clang 3.8
x86-64 clang 3.8.1
x86-64 clang 3.9.0
x86-64 clang 3.9.1
x86-64 clang 4.0.0
x86-64 clang 4.0.1
x86-64 clang 5.0.0
x86-64 clang 5.0.1
x86-64 clang 5.0.2
x86-64 clang 6.0.0
x86-64 clang 6.0.1
x86-64 clang 7.0.0
x86-64 clang 7.0.1
x86-64 clang 7.1.0
x86-64 clang 8.0.0
x86-64 clang 8.0.1
x86-64 clang 9.0.0
x86-64 clang 9.0.1
x86-64 gcc (trunk)
x86-64 gcc 10.1
x86-64 gcc 10.2
x86-64 gcc 10.3
x86-64 gcc 10.3 (assertions)
x86-64 gcc 10.4
x86-64 gcc 10.4 (assertions)
x86-64 gcc 10.5
x86-64 gcc 10.5 (assertions)
x86-64 gcc 11.1
x86-64 gcc 11.1 (assertions)
x86-64 gcc 11.2
x86-64 gcc 11.2 (assertions)
x86-64 gcc 11.3
x86-64 gcc 11.3 (assertions)
x86-64 gcc 11.4
x86-64 gcc 11.4 (assertions)
x86-64 gcc 12.1
x86-64 gcc 12.1 (assertions)
x86-64 gcc 12.2
x86-64 gcc 12.2 (assertions)
x86-64 gcc 12.3
x86-64 gcc 12.3 (assertions)
x86-64 gcc 12.4
x86-64 gcc 12.4 (assertions)
x86-64 gcc 12.5
x86-64 gcc 12.5 (assertions)
x86-64 gcc 13.1
x86-64 gcc 13.1 (assertions)
x86-64 gcc 13.2
x86-64 gcc 13.2 (assertions)
x86-64 gcc 13.3
x86-64 gcc 13.3 (assertions)
x86-64 gcc 13.4
x86-64 gcc 13.4 (assertions)
x86-64 gcc 14.1
x86-64 gcc 14.1 (assertions)
x86-64 gcc 14.2
x86-64 gcc 14.2 (assertions)
x86-64 gcc 14.3
x86-64 gcc 14.3 (assertions)
x86-64 gcc 15.1
x86-64 gcc 15.1 (assertions)
x86-64 gcc 15.2
x86-64 gcc 15.2 (assertions)
x86-64 gcc 3.4.6
x86-64 gcc 4.0.4
x86-64 gcc 4.1.2
x86-64 gcc 4.4.7
x86-64 gcc 4.5.3
x86-64 gcc 4.6.4
x86-64 gcc 4.7.1
x86-64 gcc 4.7.2
x86-64 gcc 4.7.3
x86-64 gcc 4.7.4
x86-64 gcc 4.8.1
x86-64 gcc 4.8.2
x86-64 gcc 4.8.3
x86-64 gcc 4.8.4
x86-64 gcc 4.8.5
x86-64 gcc 4.9.0
x86-64 gcc 4.9.1
x86-64 gcc 4.9.2
x86-64 gcc 4.9.3
x86-64 gcc 4.9.4
x86-64 gcc 5.1
x86-64 gcc 5.2
x86-64 gcc 5.3
x86-64 gcc 5.4
x86-64 gcc 6.1
x86-64 gcc 6.2
x86-64 gcc 6.3
x86-64 gcc 6.5
x86-64 gcc 7.1
x86-64 gcc 7.2
x86-64 gcc 7.3
x86-64 gcc 7.4
x86-64 gcc 7.5
x86-64 gcc 8.1
x86-64 gcc 8.2
x86-64 gcc 8.3
x86-64 gcc 8.4
x86-64 gcc 8.5
x86-64 gcc 9.1
x86-64 gcc 9.2
x86-64 gcc 9.3
x86-64 gcc 9.4
x86-64 gcc 9.5
x86-64 icc 13.0.1
x86-64 icc 16.0.3
x86-64 icc 17.0.0
x86-64 icc 18.0.0
x86-64 icc 19.0.0
x86-64 icc 19.0.1
x86-64 icc 2021.1.2
x86-64 icc 2021.10.0
x86-64 icc 2021.2.0
x86-64 icc 2021.3.0
x86-64 icc 2021.4.0
x86-64 icc 2021.5.0
x86-64 icc 2021.6.0
x86-64 icc 2021.7.0
x86-64 icc 2021.7.1
x86-64 icc 2021.8.0
x86-64 icc 2021.9.0
x86-64 icx (latest)
x86-64 icx 2021.1.2
x86-64 icx 2021.2.0
x86-64 icx 2021.3.0
x86-64 icx 2021.4.0
x86-64 icx 2022.0.0
x86-64 icx 2022.1.0
x86-64 icx 2022.2.0
x86-64 icx 2022.2.1
x86-64 icx 2023.0.0
x86-64 icx 2023.1.0
x86-64 icx 2024.0.0
x86_64 CompCert 3.10
x86_64 CompCert 3.11
x86_64 CompCert 3.12
x86_64 CompCert 3.9
z180-clang 15.0.0
z180-clang 15.0.7
z80-clang 15.0.0
z80-clang 15.0.7
z88dk 2.2
zig cc 0.10.0
zig cc 0.11.0
zig cc 0.12.0
zig cc 0.12.1
zig cc 0.13.0
zig cc 0.14.0
zig cc 0.14.1
zig cc 0.15.1
zig cc 0.6.0
zig cc 0.7.0
zig cc 0.7.1
zig cc 0.8.0
zig cc 0.9.0
zig cc trunk
Options
Source code
#define DECLARE_ITERATOR(identifier, type) CONTAINER_GENERATOR_ITERATOR(identifier, type) #define DEFINE_ITERATOR(identifier, type) CONTAINER_GENERATOR_ITERATOR(identifier, type, with_defs) #define EXPAND_ITERATOR(identifier) iterator_##identifier##_t #define ITERATOR(identifier) EXPAND_ITERATOR(identifier) #define ITERATOR_ACTIVE(iterator) ((iterator).container != nullptr) #define ITERATOR_GET(iterator) (iterator).element.value #define ITERATOR_NEXT(iterator) ((iterator).container == nullptr || (iterator).next == nullptr ? false : (iterator).next(&(iterator))) #define CONTAINER_GENERATOR_ITERATOR(identifier, type, ...) \ typedef struct iterator_##identifier ITERATOR(identifier); \ typedef bool iterator_##identifier##_next_t(ITERATOR(identifier) *iterator); \ \ __VA_OPT__( \ struct iterator_##identifier { \ void *container; \ union state { \ void *ptr; \ long value; \ } state; \ union identifier##_element { \ void *address; \ type value; \ } element; \ iterator_##identifier##_next_t *next; \ }; \ ) \ \ struct iterator_##identifier DEFINE_ITERATOR(void, void *); #define DECLARE_PAIR(first_identifier, second_identifier, first_type, second_type) CONTAINER_GENERATOR_PAIR(first_identifier, second_identifier, first_type, second_type) #define DEFINE_PAIR(first_identifier, second_identifier, first_type, second_type) CONTAINER_GENERATOR_PAIR(first_identifier, second_identifier, first_type, second_type, with_defs) #define EXPAND_PAIR(first_identifier, second_identifier) pair_##first_identifier##_##second_identifier##_t #define PAIR(first_identifier, second_identifier) EXPAND_PAIR(first_identifier, second_identifier) #define PAIR_FIRST(pair) (pair).value.first #define PAIR_SECOND(pair) (pair).value.second #define CONTAINER_GENERATOR_PAIR(first_identifier, second_identifier, first_type, second_type, ...) \ typedef union pair_##first_identifier##_##second_identifier PAIR(first_identifier, second_identifier); \ \ __VA_OPT__( \ union pair_##first_identifier##_##second_identifier { \ struct void_value_##first_identifier##_##second_identifier { \ void *first; \ void *second; \ } void_value; \ struct value_##first_identifier##_##second_identifier { \ first_type first; \ second_type second; \ } value; \ }; \ ) \ \ union pair_##first_identifier##_##second_identifier DEFINE_PAIR(void, void, void *, void *); #include <stddef.h> #include <stdint.h> typedef uint64_t hash_64_t(const void *data, size_t size); constexpr size_t HASH_NT = SIZE_MAX; constexpr size_t HASH_OBJECT = 0; extern hash_64_t *const HASH_DEFAULT; extern hash_64_t fnv1a_64; extern hash_64_t sip_64; #include <assert.h> #include <limits.h> #include <stddef.h> #include <stdint.h> #include <string.h> #ifndef C_ROUNDS #define C_ROUNDS 2 #endif #ifndef D_ROUNDS #define D_ROUNDS 4 #endif #define ROTATE(x, b) (((x) << (b)) | ((x) >> (UINT64_WIDTH - (b)))) #define SIP_ROUND(v0, v1, v2, v3) { \ v0 += v1; \ v2 += v3; \ v1 = ROTATE(v1, 13) ^ v0; \ v3 = ROTATE(v3, 16) ^ v2; \ v0 = ROTATE(v0, 32); \ v0 += v3; \ v2 += v1; \ v1 = ROTATE(v1, 17) ^ v2; \ v3 = ROTATE(v3, 21) ^ v0; \ v2 = ROTATE(v2, 32); \ } constexpr uint64_t FNV_64_BASIS = UINT64_C(14695981039346656037); constexpr uint64_t FNV_64_PRIME = 1099511628211; constexpr char KEY[/*16*/] = "Dark Forces RPG!"; static_assert(sizeof(KEY) == 17); hash_64_t *const HASH_DEFAULT; static uint64_t fnv1a_64_nts(const char *str); hash_64_t *const HASH_DEFAULT = &sip_64; hash_64_t fnv1a_64; uint64_t fnv1a_64(const void *data, size_t size) { if (data == nullptr) return 0; else if (size == HASH_NT) return fnv1a_64_nts(data); else if (size == 0) return 0; uint64_t hash = FNV_64_BASIS; for (int i = 0; i < size; i++) { hash ^= ((unsigned char *)data)[i++]; hash *= FNV_64_PRIME; } return hash; } static uint64_t fnv1a_64_nts(const char *str) { uint64_t hash = FNV_64_BASIS; int i = 0; while (str[i] != '\0') { hash ^= str[i++]; hash *= FNV_64_PRIME; } return hash; } hash_64_t sip_64; uint64_t sip_64(const void *data, size_t size) { if (data == nullptr) return 0; else if (size == HASH_NT) size = strlen(data); if (size == 0) return 0; uint64_t key_lower = ((uint64_t *)KEY)[0]; uint64_t key_upper = ((uint64_t *)KEY)[1]; uint64_t v0 = key_lower ^ 0x736f6d6570736575; uint64_t v1 = key_upper ^ 0x646f72616e646f6d; uint64_t v2 = key_lower ^ 0x6c7967656e657261; uint64_t v3 = key_upper ^ 0x7465646279746573; const unsigned char *pos = data; for (int i = 0; i < size / sizeof(uint64_t); i++) { uint64_t next_bytes = 0; for (int j = 0; j < sizeof(uint64_t); j++) next_bytes |= ((uint64_t)pos[j]) << (j * UINT8_WIDTH); v3 ^= next_bytes; for (int j = 0; j < C_ROUNDS; j++) SIP_ROUND(v0, v1, v2, v3); v0 ^= next_bytes; pos += sizeof(uint64_t); } uint64_t last_bytes = size << (SIZE_WIDTH - UINT8_WIDTH); for (int i = 0; i < size % sizeof(uint64_t); i++) last_bytes |= ((uint64_t)pos[i]) << (i * UINT8_WIDTH); v3 ^= last_bytes; for (int i = 0; i < C_ROUNDS; i++) SIP_ROUND(v0, v1, v2, v3); v0 ^= last_bytes; v2 ^= UINT8_MAX; for (int i = 0; i < D_ROUNDS; i++) SIP_ROUND(v0, v1, v2, v3); return v0 ^ v1 ^ v2 ^ v3; } #include <stdlib.h> #include <string.h> #define DECLARE_LIST(identifier, type) CONTAINER_GENERATOR_LIST(identifier, type) #define DEFINE_LIST(identifier, type) CONTAINER_GENERATOR_LIST(identifier, type, with_defs) #define EXPAND_LIST(identifier) list_##identifier##_t #define EXPAND_LIST_NEW(identifier) list_##identifier##_new() #define LIST(identifier) EXPAND_LIST(identifier) #define LIST_APPEND(list, element) ((list) == nullptr ? false : (list)->vtable->append(list, element)) #define LIST_DESTROY(list) ((list) == nullptr ? (void)0 : (list)->vtable->destroy(list)) #define LIST_GET_ITERATOR(list) ((list) == nullptr ? ((typeof(list->associated_types->iterator)){.container = nullptr}) : (list)->vtable->get_iterator(list)) #define LIST_NEW(identifier) EXPAND_LIST_NEW(identifier) #define LIST_REMOVE(list, iterator) ((list) == nullptr ? false : (list)->vtable->remove(list, &iterator)) #define CONTAINER_GENERATOR_LIST(identifier, type, ...) \ typedef struct list_##identifier *LIST(identifier); \ struct list_##identifier##_vtable; \ \ DECLARE_ITERATOR(identifier, type); \ \ typedef bool list_##identifier##_append_t(LIST(identifier) list, type element); \ typedef void list_##identifier##_destroy_t(LIST(identifier) list); \ typedef ITERATOR(identifier) list_##identifier##_get_iterator_t(LIST(identifier) list); \ typedef bool list_##identifier##_remove_t(LIST(identifier) list, ITERATOR(identifier) *iterator); \ \ __VA_OPT__( \ DEFINE_ITERATOR(identifier, type); \ \ struct list_##identifier { \ const struct list_##identifier##_vtable *vtable; \ LIST(void) list; \ struct { \ ITERATOR(identifier) iterator; \ } *associated_types; \ }; \ \ struct list_##identifier##_vtable { \ list_##identifier##_append_t *append; \ list_##identifier##_destroy_t *destroy; \ list_##identifier##_get_iterator_t *get_iterator; \ list_##identifier##_remove_t *remove; \ }; \ \ static const struct list_##identifier##_vtable LIST_##identifier##_VTABLE; \ ) \ \ static iterator_##identifier##_next_t list_iterator_##identifier##_next; \ [[maybe_unused]] static inline list_##identifier##_append_t list_##identifier##_append; \ [[maybe_unused]] static inline list_##identifier##_destroy_t list_##identifier##_destroy; \ [[maybe_unused]] static inline list_##identifier##_get_iterator_t list_##identifier##_get_iterator; \ [[maybe_unused]] static inline LIST(identifier) list_##identifier##_new(); \ [[maybe_unused]] static inline list_##identifier##_remove_t list_##identifier##_remove; \ \ __VA_OPT__( \ static const struct list_##identifier##_vtable LIST_##identifier##_VTABLE = { \ .append = &list_##identifier##_append, \ .destroy = &list_##identifier##_destroy, \ .get_iterator = &list_##identifier##_get_iterator, \ .remove = &list_##identifier##_remove \ }; \ \ static iterator_##identifier##_next_t list_iterator_##identifier##_next; \ static bool list_iterator_##identifier##_next(ITERATOR(identifier) *iterator) { \ if (!list_iterator_void_next((ITERATOR(void) *)iterator)) \ return false; \ else if (sizeof(type) <= sizeof(void *)) \ memcpy(&iterator->element.value, &iterator->element.address, sizeof(type)); \ else \ memcpy(&iterator->element.value, iterator->element.address, sizeof(type)); \ return true; \ } \ \ static inline list_##identifier##_append_t list_##identifier##_append; \ static inline bool list_##identifier##_append(LIST(identifier) list, type element) { \ void *copy; \ if (sizeof(type) <= sizeof(void *)) { \ memcpy(©, &element, sizeof(type)); \ return list_void_append(list->list, copy); \ } else { \ copy = malloc(sizeof(type)); \ if (copy == nullptr) \ return false; \ memcpy(copy, &element, sizeof(type)); \ if (list_void_append(list->list, copy)) \ return true; \ else { \ free(copy); \ return false; \ } \ } \ } \ \ static inline list_##identifier##_destroy_t list_##identifier##_destroy; \ static inline void list_##identifier##_destroy(LIST(identifier) list) { \ if (sizeof(type) <= sizeof(void *)) \ list_void_destroy(list->list); \ else \ list_void_destroy_free_elements(list->list); \ } \ \ static inline list_##identifier##_get_iterator_t list_##identifier##_get_iterator; \ static inline ITERATOR(identifier) list_##identifier##_get_iterator(LIST(identifier) list) { \ ITERATOR(void) iterator = list_void_get_iterator(list->list); \ ITERATOR(identifier) ret = { \ .container = iterator.container, \ .next = &list_iterator_##identifier##_next, \ .state.ptr = iterator.state.ptr \ }; \ if (sizeof(type) <= sizeof(void *)) \ memcpy(&ret.element.value, &iterator.element.address, sizeof(type)); \ else \ memcpy(&ret.element.value, iterator.element.address, sizeof(type)); \ return ret; \ } \ \ static inline LIST(identifier) list_##identifier##_new() { \ LIST(identifier) ret = malloc(sizeof(*ret)); \ if (ret == nullptr) \ return nullptr; \ ret->list = list_void_new(); \ if (ret->list == nullptr) { \ free(ret); \ return nullptr; \ } \ ret->vtable = &LIST_##identifier##_VTABLE; \ return ret; \ } \ \ static inline list_##identifier##_remove_t list_##identifier##_remove; \ static inline bool list_##identifier##_remove(LIST(identifier) list, ITERATOR(identifier) *iterator) { \ if (sizeof(type) <= sizeof(void *)) \ return list_void_remove(list->list, (ITERATOR(void) *)iterator); \ else \ return list_void_remove_and_free(list->list, (ITERATOR(void) *)iterator); \ } \ ) \ \ struct list_##identifier typedef struct list_void *LIST(void); struct list_void_vtable; typedef bool list_void_append_t(LIST(void) list, void *element); typedef void list_void_destroy_t(LIST(void) list); typedef ITERATOR(void) list_void_get_iterator_t(LIST(void) list); typedef bool list_void_remove_t(LIST(void) list, ITERATOR(void) *iterator); struct list_void { const struct list_void_vtable *vtable; struct { ITERATOR(void) iterator; } *associated_types; }; struct list_void_vtable { list_void_append_t *append; list_void_destroy_t *destroy; list_void_get_iterator_t *get_iterator; list_void_remove_t *remove; }; extern iterator_void_next_t list_iterator_void_next; extern list_void_append_t list_void_append; extern list_void_destroy_t list_void_destroy; extern list_void_destroy_t list_void_destroy_free_elements; extern list_void_get_iterator_t list_void_get_iterator; extern LIST(void) list_void_new(); extern list_void_remove_t list_void_remove; extern list_void_remove_t list_void_remove_and_free; #include <assert.h> #include <stddef.h> #include <stdlib.h> #define DECLARE_MAP(key_identifier, value_identifier, key_type, value_type) CONTAINER_GENERATOR_MAP(key_identifier, value_identifier, key_type, value_type) #define DEFINE_MAP(key_identifier, value_identifier, key_type, value_type) CONTAINER_GENERATOR_MAP(key_identifier, value_identifier, key_type, value_type, with_defs) #define EXPAND_MAP(key_identifier, value_identifier) map_##key_identifier##_##value_identifier##_t #define MAP(key_identifier, value_identifier) EXPAND_MAP(key_identifier, value_identifier) #define MAP_DESTROY(map) ((map) == nullptr ? (void)0 : (map)->vtable->destroy(map)) #define MAP_GET(map, key) ((map) == nullptr ? (typeof((map)->vtable->get((map), (key)))){0} : (map)->vtable->get((map), (key))) #define MAP_GET_ENTRY_ITERATOR(map) ((map) == nullptr ? ((typeof((map)->associated_types->entry_iterator)){.container = nullptr}) : (map)->vtable->get_entry_iterator(map)) #define MAP_GET_KEY_ITERATOR(map) ((map) == nullptr ? ((typeof((map)->associated_types->key_iterator)){.container = nullptr}) : (map)->vtable->get_key_iterator(map)) #define MAP_GET_VALUE_ITERATOR(map) ((map) == nullptr ? ((typeof((map)->associated_types->value_iterator)){.container = nullptr}) : (map)->vtable->get_value_iterator(map)) #define MAP_INSERT(map, key, element) ((map) == nullptr ? false : (map)->vtable->insert((map), (key), (element))) #define MAP_NEW(key_identifier, value_identifier, key_length, ...) MAP_NEW_REAL##__VA_OPT__(_WITH_HASH_FN)(key_identifier, value_identifier, key_length __VA_OPT__(, __VA_ARGS__)) #define MAP_NEW_REAL(key_identifier, value_identifier, key_length) map_##key_identifier##_##value_identifier##_new(HASH_DEFAULT, key_length) #define MAP_NEW_REAL_WITH_HASH_FN(key_identifier, value_identifier, hash_fn, key_length) map_##key_identifier##_##value_identifier##_new(hash_fn, key_length) #define MAP_REMOVE(map, key) ((map) == nullptr ? (typeof((map)->vtable->remove((map), (key)))){0} : (map)->vtable->remove((map), (key))) #define MAP_SIZE(map) ((map) == nullptr ? 0 : (map)->vtable->size(map)) #define CONTAINER_GENERATOR_MAP(key_identifier, value_identifier, key_type, value_type, ...) \ typedef struct map_##key_identifier##_##value_identifier *MAP(key_identifier, value_identifier); \ struct map_##key_identifier##_##value_identifier##_vtable; \ \ DECLARE_PAIR(key_identifier, value_identifier, key_type, value_type); \ DECLARE_ITERATOR(PAIR(key_identifier, value_identifier), PAIR(key_identifier, value_identifier)); \ DECLARE_ITERATOR(key_identifier, key_type); \ DECLARE_ITERATOR(value_identifier, value_type); \ \ typedef void map_##key_identifier##_##value_identifier##_destroy_t(MAP(key_identifier, value_identifier) map); \ typedef ITERATOR(PAIR(key_identifier, value_identifier)) map_##key_identifier##_##value_identifier##_get_entry_iterator_t(MAP(key_identifier, value_identifier) map); \ typedef ITERATOR(key_identifier) map_##key_identifier##_##value_identifier##_get_key_iterator_t(MAP(key_identifier, value_identifier) map); \ typedef ITERATOR(value_identifier) map_##key_identifier##_##value_identifier##_get_value_iterator_t(MAP(key_identifier, value_identifier) map); \ typedef value_type map_##key_identifier##_##value_identifier##_get_t(MAP(key_identifier, value_identifier) map, key_type key); \ typedef bool map_##key_identifier##_##value_identifier##_insert_t(MAP(key_identifier, value_identifier) map, key_type key, value_type element); \ typedef value_type map_##key_identifier##_##value_identifier##_remove_t(MAP(key_identifier, value_identifier) map, key_type key); \ typedef int map_##key_identifier##_##value_identifier##_size_t(MAP(key_identifier, value_identifier) map); \ \ __VA_OPT__( \ DEFINE_PAIR(key_identifier, value_identifier, key_type, value_type); \ \ DEFINE_ITERATOR(PAIR(key_identifier, value_identifier), PAIR(key_identifier, value_identifier)); \ DEFINE_ITERATOR(key_identifier, key_type); \ DEFINE_ITERATOR(value_identifier, value_type); \ \ struct map_##key_identifier##_##value_identifier { \ const struct map_##key_identifier##_##value_identifier##_vtable *vtable; \ MAP(void, void) map; \ struct { \ ITERATOR(PAIR(key_identifier, value_identifier)) entry_iterator; \ ITERATOR(key_identifier) key_iterator; \ ITERATOR(value_identifier) value_iterator; \ } *associated_types; \ }; \ \ struct map_##key_identifier##_##value_identifier##_vtable { \ map_##key_identifier##_##value_identifier##_destroy_t *destroy; \ map_##key_identifier##_##value_identifier##_get_t *get; \ map_##key_identifier##_##value_identifier##_get_entry_iterator_t *get_entry_iterator; \ map_##key_identifier##_##value_identifier##_get_key_iterator_t *get_key_iterator; \ map_##key_identifier##_##value_identifier##_get_value_iterator_t *get_value_iterator; \ map_##key_identifier##_##value_identifier##_insert_t *insert; \ map_##key_identifier##_##value_identifier##_remove_t *remove; \ map_##key_identifier##_##value_identifier##_size_t *size; \ }; \ \ static const struct map_##key_identifier##_##value_identifier##_vtable MAP_##key_identifier##_##value_identifier##_VTABLE; \ ) \ \ static iterator_pair_##key_identifier##_##value_identifier##_t_next_t map_entry_iterator_pair_##key_identifier##_##value_identifier##_t_next; \ static iterator_##key_identifier##_next_t map_key_iterator_##key_identifier##_##value_identifier##_next; \ static iterator_##value_identifier##_next_t map_value_iterator_##key_identifier##_##value_identifier##_next; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_destroy_t map_##key_identifier##_##value_identifier##_destroy; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_get_t map_##key_identifier##_##value_identifier##_get; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_get_entry_iterator_t map_##key_identifier##_##value_identifier##_get_entry_iterator; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_get_key_iterator_t map_##key_identifier##_##value_identifier##_get_key_iterator; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_get_value_iterator_t map_##key_identifier##_##value_identifier##_get_value_iterator; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_insert_t map_##key_identifier##_##value_identifier##_insert; \ [[maybe_unused]] static inline MAP(key_identifier, value_identifier) map_##key_identifier##_##value_identifier##_new(hash_64_t *const hash_fn, size_t key_length); \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_remove_t map_##key_identifier##_##value_identifier##_remove; \ [[maybe_unused]] static inline map_##key_identifier##_##value_identifier##_size_t map_##key_identifier##_##value_identifier##_size; \ \ __VA_OPT__( \ static const struct map_##key_identifier##_##value_identifier##_vtable MAP_##key_identifier##_##value_identifier##_VTABLE = { \ .destroy = &map_##key_identifier##_##value_identifier##_destroy, \ .get = &map_##key_identifier##_##value_identifier##_get, \ .get_entry_iterator = &map_##key_identifier##_##value_identifier##_get_entry_iterator, \ .get_key_iterator = &map_##key_identifier##_##value_identifier##_get_key_iterator, \ .get_value_iterator = &map_##key_identifier##_##value_identifier##_get_value_iterator, \ .insert = &map_##key_identifier##_##value_identifier##_insert, \ .remove = &map_##key_identifier##_##value_identifier##_remove, \ .size = &map_##key_identifier##_##value_identifier##_size \ }; \ \ static iterator_pair_##key_identifier##_##value_identifier##_t_next_t map_entry_iterator_pair_##key_identifier##_##value_identifier##_t_next; \ static bool map_entry_iterator_pair_##key_identifier##_##value_identifier##_t_next(ITERATOR(PAIR(key_identifier, value_identifier)) *iterator) { \ MAP(void, void) map = (MAP(void, void))iterator->container; \ if (!map_entry_iterator_pair_void_void_t_next((ITERATOR(PAIR(void, void)) *)iterator)) \ return false; \ void *second_address = iterator->element.value.void_value.second; \ if (map->hash_object) \ memcpy(&iterator->element.value.value.first, iterator->element.value.void_value.first, sizeof(key_type)); \ if (sizeof(value_type) <= sizeof(void *)) \ memcpy(&iterator->element.value.value.second, &second_address, sizeof(value_type)); \ else \ memcpy(&iterator->element.value.value.second, second_address, sizeof(value_type)); \ return true; \ } \ \ static iterator_##key_identifier##_next_t map_key_iterator_##key_identifier##_##value_identifier##_next; \ static bool map_key_iterator_##key_identifier##_##value_identifier##_next(ITERATOR(key_identifier) *iterator) { \ MAP(void, void) map = (MAP(void, void))iterator->container; \ if (!map_key_iterator_void_next((ITERATOR(void) *)iterator)) \ return false; \ else if (map->hash_object) \ memcpy(&iterator->element.value, iterator->element.address, sizeof(key_type)); \ return true; \ } \ \ static iterator_##value_identifier##_next_t map_value_iterator_##key_identifier##_##value_identifier##_next; \ static bool map_value_iterator_##key_identifier##_##value_identifier##_next(ITERATOR(value_identifier) *iterator) { \ if (!map_value_iterator_void_next((ITERATOR(void) *)iterator)) \ return false; \ else if (sizeof(value_type) <= sizeof(void *)) \ memcpy(&iterator->element.value, &iterator->element.address, sizeof(value_type)); \ else \ memcpy(&iterator->element.value, iterator->element.address, sizeof(value_type)); \ return true; \ } \ \ static inline map_##key_identifier##_##value_identifier##_destroy_t map_##key_identifier##_##value_identifier##_destroy; \ static inline void map_##key_identifier##_##value_identifier##_destroy(MAP(key_identifier, value_identifier) map) { \ if (sizeof(value_type) <= sizeof(void *)) \ map_void_void_destroy(map->map); \ else \ map_void_void_destroy_and_free(map->map); \ } \ \ static inline map_##key_identifier##_##value_identifier##_get_t map_##key_identifier##_##value_identifier##_get; \ static inline value_type map_##key_identifier##_##value_identifier##_get(MAP(key_identifier, value_identifier) map, key_type key) { \ void *void_key; \ if (map->map->hash_object) \ void_key = &key; \ else { \ assert(sizeof(key) == sizeof(void_key)); \ memcpy(&void_key, &key, sizeof(void_key)); \ } \ void *void_value = map_void_void_get(map->map, void_key); \ value_type value; \ if (sizeof(value_type) <= sizeof(void *)) \ memcpy(&value, &void_value, sizeof(value_type)); \ else \ memcpy(&value, void_value, sizeof(value_type)); \ return value; \ } \ \ static inline map_##key_identifier##_##value_identifier##_get_entry_iterator_t map_##key_identifier##_##value_identifier##_get_entry_iterator; \ static inline ITERATOR(PAIR(key_identifier, value_identifier)) map_##key_identifier##_##value_identifier##_get_entry_iterator(MAP(key_identifier, value_identifier) map) { \ ITERATOR(PAIR(void, void)) iterator = map_void_void_get_entry_iterator(map->map); \ ITERATOR(PAIR(key_identifier, value_identifier)) ret = { \ .container = iterator.container, \ .next = &map_entry_iterator_pair_##key_identifier##_##value_identifier##_t_next, \ .state = iterator.state \ }; \ if (map->map->hash_object) \ memcpy(&ret.element.value.value.first, iterator.element.value.value.first, sizeof(key_type)); \ else \ memcpy(&ret.element.value.value.first, &iterator.element.value.value.first, sizeof(key_type)); \ if (sizeof(value_type) <= sizeof (void *)) \ memcpy(&ret.element.value.value.second, &iterator.element.value.value.second, sizeof(value_type)); \ else \ memcpy(&ret.element.value.value.second, iterator.element.value.value.second, sizeof(value_type)); \ return ret; \ } \ \ static inline map_##key_identifier##_##value_identifier##_get_key_iterator_t map_##key_identifier##_##value_identifier##_get_key_iterator; \ static inline ITERATOR(key_identifier) map_##key_identifier##_##value_identifier##_get_key_iterator(MAP(key_identifier, value_identifier) map) { \ ITERATOR(void) iterator = map_void_void_get_key_iterator(map->map); \ ITERATOR(key_identifier) ret = { \ .container = iterator.container, \ .next = &map_key_iterator_##key_identifier##_##value_identifier##_next, \ .state = iterator.state \ }; \ if (map->map->hash_object) \ memcpy(&ret.element.value, iterator.element.address, sizeof(key_type)); \ else \ memcpy(&ret.element.value, &iterator.element.value, sizeof(key_type)); \ return ret; \ } \ \ static inline map_##key_identifier##_##value_identifier##_get_value_iterator_t map_##key_identifier##_##value_identifier##_get_value_iterator; \ static inline ITERATOR(value_identifier) map_##key_identifier##_##value_identifier##_get_value_iterator(MAP(key_identifier, value_identifier) map) { \ ITERATOR(void) iterator = map_void_void_get_value_iterator(map->map); \ ITERATOR(value_identifier) ret = { \ .container = iterator.container, \ .next = &map_value_iterator_##key_identifier##_##value_identifier##_next, \ .state = iterator.state \ }; \ if (sizeof(value_type) <= sizeof (void *)) \ memcpy(&ret.element.value, &iterator.element.address, sizeof(value_type)); \ else \ memcpy(&ret.element.value, iterator.element.address, sizeof(value_type)); \ return ret; \ } \ \ static inline map_##key_identifier##_##value_identifier##_insert_t map_##key_identifier##_##value_identifier##_insert; \ static inline bool map_##key_identifier##_##value_identifier##_insert(MAP(key_identifier, value_identifier) map, key_type key, value_type element) { \ void *void_key; \ if (map->map->hash_object) \ void_key = &key; \ else if (sizeof(key) != sizeof(void_key)) \ return false; \ else \ memcpy(&void_key, &key, sizeof(void_key)); \ void *copy; \ if (sizeof(value_type) <= sizeof(void *)) { \ memcpy(©, &element, sizeof(value_type)); \ return map_void_void_insert(map->map, void_key, copy); \ } else { \ copy = malloc(sizeof(value_type)); \ if (copy == nullptr) \ return false; \ memcpy(copy, &element, sizeof(value_type)); \ if (map_void_void_insert_after_free(map->map, void_key, copy)) \ return true; \ else { \ free(copy); \ return false; \ } \ } \ } \ \ static inline MAP(key_identifier, value_identifier) map_##key_identifier##_##value_identifier##_new(hash_64_t *const hash_fn, size_t key_length) { \ if (sizeof(key_type) > sizeof(void *) && key_length != HASH_OBJECT) \ return nullptr; \ MAP(key_identifier, value_identifier) ret = malloc(sizeof(*ret)); \ if (ret == nullptr) \ return nullptr; \ bool hash_object = key_length == HASH_OBJECT; \ if (hash_object) \ key_length = sizeof(key_type); \ ret->map = map_void_void_new(hash_fn, key_length); \ if (ret->map == nullptr) { \ free(ret); \ return nullptr; \ } \ ret->map->hash_object = hash_object; \ ret->vtable = &MAP_##key_identifier##_##value_identifier##_VTABLE; \ return ret; \ } \ \ static inline map_##key_identifier##_##value_identifier##_remove_t map_##key_identifier##_##value_identifier##_remove; \ static inline value_type map_##key_identifier##_##value_identifier##_remove(MAP(key_identifier, value_identifier) map, key_type key) { \ void *void_key; \ if (map->map->hash_object) \ void_key = &key; \ else { \ assert(sizeof(key) == sizeof(void_key)); \ memcpy(&void_key, &key, sizeof(void_key)); \ } \ void *void_value = map_void_void_remove(map->map, void_key); \ value_type value; \ if (sizeof(value_type) <= sizeof(void *)) \ memcpy(&value, &void_value, sizeof(value_type)); \ else { \ memcpy(&value, void_value, sizeof(value_type)); \ free(void_value); \ } \ return value; \ } \ \ static inline map_##key_identifier##_##value_identifier##_size_t map_##key_identifier##_##value_identifier##_size; \ static inline int map_##key_identifier##_##value_identifier##_size(MAP(key_identifier, value_identifier) map) { \ return map_void_void_size(map->map); \ } \ ) \ \ struct map_##key_identifier##_##value_identifier DEFINE_ITERATOR(PAIR(void, void), PAIR(void, void)); typedef struct map_void_void *MAP(void, void); struct map_void_void_vtable; typedef void map_void_void_destroy_t(MAP(void, void) map); typedef void *map_void_void_get_t(MAP(void, void) map, void *key); typedef ITERATOR(PAIR(void, void)) map_void_void_get_entry_iterator_t(MAP(void, void) map); typedef ITERATOR(void) map_void_void_get_key_iterator_t(MAP(void, void) map); typedef ITERATOR(void) map_void_void_get_value_iterator_t(MAP(void, void) map); typedef bool map_void_void_insert_t(MAP(void, void) map, void *key, void *element); typedef void *map_void_void_remove_t(MAP(void, void) map, void *key); typedef int map_void_void_size_t(MAP(void, void) map); struct map_void_void { const struct map_void_void_vtable *vtable; struct { ITERATOR(PAIR(void, void)) entry_iterator; ITERATOR(void) key_iterator; ITERATOR(void) value_iterator; } *associated_types; bool hash_object; }; struct map_void_void_vtable { map_void_void_destroy_t *destroy; map_void_void_get_t *get; map_void_void_get_entry_iterator_t *get_entry_iterator; map_void_void_get_key_iterator_t *get_key_iterator; map_void_void_get_value_iterator_t *get_value_iterator; map_void_void_insert_t *insert; map_void_void_remove_t *remove; map_void_void_size_t *size; }; extern iterator_pair_void_void_t_next_t map_entry_iterator_pair_void_void_t_next; extern iterator_void_next_t map_key_iterator_void_next; extern iterator_void_next_t map_value_iterator_void_next; extern map_void_void_destroy_t map_void_void_destroy; extern map_void_void_destroy_t map_void_void_destroy_and_free; extern map_void_void_get_t map_void_void_get; extern map_void_void_get_entry_iterator_t map_void_void_get_entry_iterator; extern map_void_void_get_key_iterator_t map_void_void_get_key_iterator; extern map_void_void_get_value_iterator_t map_void_void_get_value_iterator; extern map_void_void_insert_t map_void_void_insert; extern map_void_void_insert_t map_void_void_insert_after_free; extern MAP(void, void) map_void_void_new(hash_64_t *const hash_fn, size_t key_length); extern map_void_void_remove_t map_void_void_remove; extern map_void_void_size_t map_void_void_size; #include <stdlib.h> typedef struct list_void_internal list_t; typedef struct node node_t; struct list_void_internal { struct list_void list; node_t *head; node_t *tail; }; struct node { void *element; node_t *next; node_t *prev; }; static const struct list_void_vtable LIST_VOID_VTABLE; static const struct list_void_vtable LIST_VOID_VTABLE = { .append = &list_void_append, .destroy = &list_void_destroy, .get_iterator = &list_void_get_iterator, .remove = &list_void_remove }; iterator_void_next_t list_iterator_void_next; bool list_iterator_void_next(ITERATOR(void) *iterator) { if (iterator == nullptr || iterator->container == nullptr) return false; if (iterator->state.ptr == nullptr) iterator->state.ptr = ((list_t *)iterator->container)->head; else iterator->state.ptr = ((node_t *)iterator->state.ptr)->next; if (iterator->state.ptr == nullptr) { iterator->container = nullptr; return false; } iterator->element.address = ((node_t *)iterator->state.ptr)->element; return true; } list_void_append_t list_void_append; bool list_void_append(LIST(void) list, void *element) { if (list == nullptr) return false; node_t *node = malloc(sizeof(*node)); if (node == nullptr) return false; list_t *internal = (list_t *)list; if (internal->head == nullptr) internal->head = node; else internal->tail->next = node; node->element = element; node->next = nullptr; node->prev = internal->tail; internal->tail = node; return true; } list_void_destroy_t list_void_destroy; void list_void_destroy(LIST(void) list) { if (list == nullptr) return; for (node_t *node = ((list_t *)list)->head, *next_node; node != nullptr; node = next_node) { next_node = node->next; free(node); } free(list); } list_void_destroy_t list_void_destroy_free_elements; void list_void_destroy_free_elements(LIST(void) list) { if (list == nullptr) return; for (node_t *node = ((list_t *)list)->head, *next_node; node != nullptr; node = next_node) { next_node = node->next; free(node->element); free(node); } free(list); } list_void_get_iterator_t list_void_get_iterator; ITERATOR(void) list_void_get_iterator(LIST(void) list) { if (list == nullptr || ((list_t *)list)->head == nullptr) { return (ITERATOR(void)) { .container = nullptr }; } else { return (ITERATOR(void)) { .container = list, .element.address = ((list_t *)list)->head->element, .next = &list_iterator_void_next, .state.ptr = ((list_t *)list)->head }; } } LIST(void) list_void_new() { list_t *list = malloc(sizeof(*list)); if (list == nullptr) return nullptr; list->head = nullptr; list->tail = nullptr; list->list.vtable = &LIST_VOID_VTABLE; return &list->list; } list_void_remove_t list_void_remove; bool list_void_remove(LIST(void) list, ITERATOR(void) *iterator) { if (list == nullptr || iterator == nullptr || iterator->container == nullptr || iterator->state.ptr == nullptr) return false; node_t *removed = iterator->state.ptr; list_t *internal = (list_t *)list; node_t *next = removed->next; node_t *prev = removed->prev; if (next == nullptr) internal->tail = prev; else next->prev = prev; if (prev == nullptr) internal->head = next; else prev->next = next; iterator->state.ptr = removed->prev; free(removed); return true; } list_void_remove_t list_void_remove_and_free; bool list_void_remove_and_free(LIST(void) list, ITERATOR(void) *iterator) { if (iterator == nullptr || iterator->container != list) return false; void *element = ((node_t *)iterator->state.ptr)->element; if (!list_void_remove(list, iterator)) return false; free(element); return true; } #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> typedef struct entry entry_t; typedef struct entry_location entry_location_t; typedef struct map_void_void_internal map_t; typedef struct page page_t; struct entry { void *key; void *element; uint64_t hash; unsigned char probe_distance; }; struct entry_location { int page; int slot; }; struct map_void_void_internal { struct map_void_void map; hash_64_t *hash_fn; size_t key_length; page_t *pages; int num_expansions; enum { MAP_GROW, MAP_INIT, MAP_SHRINK } last_resize; }; struct page { bool dirty; int num_entries; entry_t *entries; }; //TODO: should probably pull this from the system, though 4k is unlikely to ever end up not being the right size constexpr int PAGE_SIZE = 4096; constexpr int ENTRIES_PER_PAGE = PAGE_SIZE / sizeof(entry_t); static_assert(ENTRIES_PER_PAGE < (typeof((entry_t){}.probe_distance))UINT64_MAX); constexpr int MAX_ENTRIES_PER_PAGE = ENTRIES_PER_PAGE / 2; constexpr int MIN_ENTRIES_PER_PAGE = ENTRIES_PER_PAGE / 4; static const struct map_void_void_vtable MAP_VOID_VOID_VTABLE; static entry_location_t find_entry(map_t *map, void *key); static bool map_grow(map_t *map); static void map_migrate_dirty(map_t *map, page_t *dirty_page); static void map_shrink(map_t *map); static bool map_void_void_insert_real(MAP(void, void) map, void *key, void *element, bool free_previous_element); static void map_void_void_insert_slot(map_t *map, void *key, uint64_t hash, void *element, bool free_previous_element); static const struct map_void_void_vtable MAP_VOID_VOID_VTABLE = { .destroy = &map_void_void_destroy, .get = &map_void_void_get, .get_entry_iterator = &map_void_void_get_entry_iterator, .get_key_iterator = &map_void_void_get_key_iterator, .get_value_iterator = &map_void_void_get_value_iterator, .insert = &map_void_void_insert, .remove = &map_void_void_remove, .size = &map_void_void_size }; static entry_location_t find_entry(map_t *map, void *key) { uint64_t hash = map->hash_fn(key, map->key_length); int page = map->num_expansions == 0 ? 0 : hash >> UINT64_WIDTH - map->num_expansions; if (map->last_resize == MAP_GROW && map->pages[page & ~1].dirty) map_migrate_dirty(map, &map->pages[page & ~1]); else if (map->last_resize == MAP_SHRINK && map->pages[page + (1 << map->num_expansions)].dirty) map_migrate_dirty(map, &map->pages[page + (1 << map->num_expansions)]); int slot = (hash & UINT64_MAX >> map->num_expansions) % ENTRIES_PER_PAGE; int i = slot; do { if (map->pages[page].entries[i].key == nullptr) return (entry_location_t){.page = -1}; else if (map->pages[page].entries[i].hash == hash) return (entry_location_t){.page = page, .slot = i}; i = (i + 1) % ENTRIES_PER_PAGE; } while (i != slot); return (entry_location_t){.page = -1}; } iterator_pair_void_void_t_next_t map_entry_iterator_pair_void_void_t_next; bool map_entry_iterator_pair_void_void_t_next(ITERATOR(PAIR(void, void)) *iterator) { if (iterator == nullptr || iterator->container == nullptr) return false; map_t *map = iterator->container; for (int entry = iterator->state.value + 1; entry < (1 << map->num_expansions) * ENTRIES_PER_PAGE; entry++) { int page = entry / ENTRIES_PER_PAGE; if (map->pages[page].dirty) map_migrate_dirty(map, &map->pages[page]); if (map->pages[page].entries[entry % ENTRIES_PER_PAGE].key != nullptr) { iterator->state.value = entry; iterator->element.value.value.first = map->pages[page].entries[entry % ENTRIES_PER_PAGE].key; iterator->element.value.value.second = map->pages[page].entries[entry % ENTRIES_PER_PAGE].element; return true; } } iterator->container = nullptr; return false; } static bool map_grow(map_t *map) { //TODO: can potentially shortcut some migration work if last_resize == MAP_SHRINK for (int i = 0; i < 1 << map->num_expansions; i++) if (map->pages[i].dirty) map_migrate_dirty(map, &map->pages[i]); entry_t *entries; if (map->last_resize == MAP_SHRINK) entries = map->pages[1 << map->num_expansions].entries; else { page_t *pages = realloc(map->pages, sizeof(map->pages[0]) * (1 << map->num_expansions + 1)); if (pages == nullptr) return false; map->pages = pages; entries = aligned_alloc(PAGE_SIZE, (1 << map->num_expansions) * PAGE_SIZE); if (entries == nullptr) return false; } for (int i = (1 << map->num_expansions) - 1; i >= 0; i--) { map->pages[i * 2] = map->pages[i]; map->pages[i * 2].dirty = true; map->pages[i * 2 + 1].dirty = false; map->pages[i * 2 + 1].num_entries = 0; map->pages[i * 2 + 1].entries = (entry_t *)(((unsigned char *)entries) + i * PAGE_SIZE); for (int j = 0; j < ENTRIES_PER_PAGE; j++) map->pages[i * 2 + 1].entries[j].key = nullptr; } map->num_expansions++; map->last_resize = MAP_GROW; return true; } iterator_void_next_t map_key_iterator_void_next; bool map_key_iterator_void_next(ITERATOR(void) *iterator) { if (iterator == nullptr || iterator->container == nullptr) return false; map_t *map = iterator->container; for (int entry = iterator->state.value + 1; entry < (1 << map->num_expansions) * ENTRIES_PER_PAGE; entry++) { int page = entry / ENTRIES_PER_PAGE; if (map->pages[page].dirty) map_migrate_dirty(map, &map->pages[page]); if (map->pages[page].entries[entry % ENTRIES_PER_PAGE].key != nullptr) { iterator->state.value = entry; iterator->element.value = map->pages[page].entries[entry % ENTRIES_PER_PAGE].key; return true; } } iterator->container = nullptr; return false; } static void map_migrate_dirty(map_t *map, page_t *dirty_page) { entry_t copy[ENTRIES_PER_PAGE]; memcpy(copy, dirty_page->entries, sizeof(entry_t) * ENTRIES_PER_PAGE); for (int i = 0; i < ENTRIES_PER_PAGE; i++) dirty_page->entries[i].key = nullptr; dirty_page->num_entries = 0; dirty_page->dirty = false; for (int i = 0; i < ENTRIES_PER_PAGE; i++) if (copy[i].key != nullptr) map_void_void_insert_slot(map, copy[i].key, copy[i].hash, copy[i].element, false); } static void map_shrink(map_t *map) { //TODO: can potentially shortcut some migration work if last_resize == MAP_GROW for (int i = 0; i < 1 << map->num_expansions + (map->last_resize == MAP_SHRINK); i++) if (map->pages[i].dirty) map_migrate_dirty(map, &map->pages[i]); if (map->last_resize == MAP_SHRINK) { entry_t *entries = map->pages[1 << map->num_expansions].entries; page_t *pages = realloc(map->pages, sizeof(map->pages[0]) * (1 << map->num_expansions)); if (pages == nullptr) return; free(entries); map->pages = pages; } if (map->num_expansions >= 2) { page_t page_copies[1 << map->num_expansions - 2]; for (int i = 0; i < 1 << map->num_expansions - 2; i++) { map->pages[i * 2] = map->pages[i * 4]; page_copies[i] = map->pages[i * 2 + 1]; map->pages[i * 2 + 1] = map->pages[i * 4 + 2]; } for (int i = 0; i < (1 << map->num_expansions - 2) - 1; i++) map->pages[(1 << map->num_expansions) - i - 2] = map->pages[(1 << map->num_expansions) - i * 2 - 3]; memcpy(&map->pages[1 << map->num_expansions - 1], page_copies, sizeof(page_copies)); } for (int i = 1 << map->num_expansions - 1; i < 1 << map->num_expansions; i++) map->pages[i].dirty = true; map->num_expansions--; map->last_resize = MAP_SHRINK; } iterator_void_next_t map_value_iterator_void_next; bool map_value_iterator_void_next(ITERATOR(void) *iterator) { if (iterator == nullptr || iterator->container == nullptr) return false; map_t *map = iterator->container; for (int entry = iterator->state.value + 1; entry < (1 << map->num_expansions) * ENTRIES_PER_PAGE; entry++) { int page = entry / ENTRIES_PER_PAGE; if (map->pages[page].dirty) map_migrate_dirty(map, &map->pages[page]); if (map->pages[page].entries[entry % ENTRIES_PER_PAGE].key != nullptr) { iterator->state.value = entry; iterator->element.value = map->pages[page].entries[entry % ENTRIES_PER_PAGE].element; return true; } } iterator->container = nullptr; return false; } map_void_void_destroy_t map_void_void_destroy; void map_void_void_destroy(MAP(void, void) map) { if (map == nullptr) return; map_t *internal = (map_t *)map; for (int i = 0; i < 1 << internal->num_expansions; i++) for (int j = 0; j < ENTRIES_PER_PAGE; j++) free(internal->pages[i].entries[j].key); free(internal->pages[0].entries); for (int i = 0; i < internal->num_expansions; i++) free(internal->pages[1 << i].entries); free(internal->pages); free(internal); } map_void_void_destroy_t map_void_void_destroy_and_free; void map_void_void_destroy_and_free(MAP(void, void) map) { if (map == nullptr) return; map_t *internal = (map_t *)map; for (int i = 0; i < 1 << internal->num_expansions; i++) { for (int j = 0; j < ENTRIES_PER_PAGE; j++) { if (internal->pages[i].entries[j].key != nullptr) { free(internal->pages[i].entries[j].key); free(internal->pages[i].entries[j].element); } } } free(internal->pages[0].entries); for (int i = 0; i < internal->num_expansions; i++) free(internal->pages[1 << i].entries); free(internal->pages); free(internal); } map_void_void_get_t map_void_void_get; void *map_void_void_get(MAP(void, void) map, void *key) { if (map == nullptr || key == nullptr) return nullptr; map_t *internal = (map_t *)map; entry_location_t entry = find_entry(internal, key); if (entry.page == -1) return nullptr; else return internal->pages[entry.page].entries[entry.slot].element; } map_void_void_get_entry_iterator_t map_void_void_get_entry_iterator; ITERATOR(PAIR(void, void)) map_void_void_get_entry_iterator(MAP(void, void) map) { if (map == nullptr) { return (ITERATOR(PAIR(void, void))) { .container = nullptr }; } else { map_t *internal = (map_t *)map; if (internal->pages[0].dirty) map_migrate_dirty(internal, &internal->pages[0]); ITERATOR(PAIR(void, void)) iterator = { .container = map, .next = &map_entry_iterator_pair_void_void_t_next, .state.value = 0 }; if (internal->pages[0].entries[0].key == nullptr) iterator.next(&iterator); else { iterator.element.value.value.first = internal->pages[0].entries[0].key; iterator.element.value.value.second = internal->pages[0].entries[0].element; } return iterator; } } map_void_void_get_key_iterator_t map_void_void_get_key_iterator; ITERATOR(void) map_void_void_get_key_iterator(MAP(void, void) map) { if (map == nullptr) { return (ITERATOR(void)) { .container = nullptr }; } else { map_t *internal = (map_t *)map; if (internal->pages[0].dirty) map_migrate_dirty(internal, &internal->pages[0]); ITERATOR(void) iterator = { .container = map, .next = &map_key_iterator_void_next, .state.value = 0 }; if (internal->pages[0].entries[0].key == nullptr) iterator.next(&iterator); else iterator.element.value = internal->pages[0].entries[0].key; return iterator; } } map_void_void_get_value_iterator_t map_void_void_get_value_iterator; ITERATOR(void) map_void_void_get_value_iterator(MAP(void, void) map) { if (map == nullptr) { return (ITERATOR(void)) { .container = nullptr }; } else { map_t *internal = (map_t *)map; if (internal->pages[0].dirty) map_migrate_dirty(internal, &internal->pages[0]); ITERATOR(void) iterator = { .container = map, .next = &map_value_iterator_void_next, .state.value = 0 }; if (internal->pages[0].entries[0].key == nullptr) iterator.next(&iterator); else iterator.element.value = internal->pages[0].entries[0].element; return iterator; } } map_void_void_insert_t map_void_void_insert; bool map_void_void_insert(MAP(void, void) map, void *key, void *element) { return map_void_void_insert_real(map, key, element, false); } map_void_void_insert_t map_void_void_insert_after_free; bool map_void_void_insert_after_free(MAP(void, void) map, void *key, void *element) { return map_void_void_insert_real(map, key, element, true); } static bool map_void_void_insert_real(MAP(void, void) map, void *key, void *element, bool free_previous_element) { if (map == nullptr || key == nullptr) return false; map_t *internal = (map_t *)map; void *key_copy; if (internal->key_length == HASH_NT) key_copy = strdup(key); else key_copy = malloc(internal->key_length); if (key_copy == nullptr) return false; else if (internal->key_length != HASH_NT) memcpy(key_copy, key, internal->key_length); uint64_t hash = internal->hash_fn(key, internal->key_length); int page = internal->num_expansions == 0 ? 0 : hash >> UINT64_WIDTH - internal->num_expansions; if (internal->pages[page].num_entries + 1 > MAX_ENTRIES_PER_PAGE) { //TODO: should this be allowed to still insert if more space exists but the grow failed? if (!map_grow(internal)) { free(key_copy); return false; } page = internal->num_expansions == 0 ? 0 : hash >> UINT64_WIDTH - internal->num_expansions; } if (internal->pages[page & ~1].dirty) map_migrate_dirty(internal, &internal->pages[page & ~1]); if (internal->pages[page].num_entries == ENTRIES_PER_PAGE) return false; map_void_void_insert_slot(internal, key_copy, hash, element, free_previous_element); return true; } static void map_void_void_insert_slot(map_t *map, void *key, uint64_t hash, void *element, bool free_previous_element) { int page = map->num_expansions == 0 ? 0 : hash >> UINT64_WIDTH - map->num_expansions; int slot = (hash & UINT64_MAX >> map->num_expansions) % ENTRIES_PER_PAGE; int i = slot; do { if (map->pages[page].entries[i].key == nullptr) { map->pages[page].entries[i].key = key; map->pages[page].entries[i].element = element; map->pages[page].entries[i].hash = hash; map->pages[page].entries[i].probe_distance = (i + ENTRIES_PER_PAGE - slot) % ENTRIES_PER_PAGE; map->pages[page].num_entries++; return; } else if (map->pages[page].entries[i].hash == hash) { free(map->pages[page].entries[i].key); map->pages[page].entries[i].key = key; if (free_previous_element) free(map->pages[page].entries[i].element); map->pages[page].entries[i].element = element; return; } else if (map->pages[page].entries[i].probe_distance < (i + ENTRIES_PER_PAGE - slot) % ENTRIES_PER_PAGE) { entry_t swapped = map->pages[page].entries[i]; map->pages[page].entries[i].key = key; map->pages[page].entries[i].element = element; map->pages[page].entries[i].hash = hash; map->pages[page].entries[i].probe_distance = (i + ENTRIES_PER_PAGE - slot) % ENTRIES_PER_PAGE; key = swapped.key; element = swapped.element; hash = swapped.hash; slot = (i + ENTRIES_PER_PAGE - swapped.probe_distance) % ENTRIES_PER_PAGE; } i = (i + 1) % ENTRIES_PER_PAGE; } while (i != slot); } //TODO: should HASH_OBJECT be allowed through here and still give valid results? MAP(void, void) map_void_void_new(hash_64_t *const hash_fn, size_t key_length) { if (hash_fn == nullptr || key_length != HASH_NT && key_length == 0) return nullptr; map_t *map = malloc(sizeof(*map)); if (map == nullptr) return nullptr; map->pages = malloc(sizeof(map->pages[0])); if (map->pages == nullptr) goto free_map; entry_t *entries = aligned_alloc(PAGE_SIZE, PAGE_SIZE); if (entries == nullptr) goto free_pages; for (int i = 0; i < ENTRIES_PER_PAGE; i++) entries[i].key = nullptr; map->pages[0].entries = entries; map->pages[0].num_entries = 0; map->pages[0].dirty = false; map->num_expansions = 0; map->last_resize = MAP_INIT; map->hash_fn = hash_fn; map->key_length = key_length; map->map.vtable = &MAP_VOID_VOID_VTABLE; return &map->map; free_pages: free(map->pages); free_map: free(map); return nullptr; } map_void_void_remove_t map_void_void_remove; void *map_void_void_remove(MAP(void, void) map, void *key) { if (map == nullptr || key == nullptr) return nullptr; map_t *internal = (map_t *)map; entry_location_t entry = find_entry(internal, key); if (entry.page == -1) return nullptr; void *value = internal->pages[entry.page].entries[entry.slot].element; free(internal->pages[entry.page].entries[entry.slot].key); internal->pages[entry.page].num_entries--; //TODO: is it actually possible to continue past the removed slot's probe distance location? for (int i = entry.slot, next_slot = (i + 1) % ENTRIES_PER_PAGE; next_slot != entry.slot; i = next_slot, next_slot = (next_slot + 1) % ENTRIES_PER_PAGE) { if (internal->pages[entry.page].entries[next_slot].key == nullptr || internal->pages[entry.page].entries[next_slot].probe_distance == 0) { internal->pages[entry.page].entries[i].key = nullptr; if (internal->pages[entry.page].num_entries < MIN_ENTRIES_PER_PAGE && internal->num_expansions > 0) { for (int i = 0; i < 1 << internal->num_expansions; i += 2) if (internal->pages[i].num_entries + internal->pages[i + 1].num_entries > MAX_ENTRIES_PER_PAGE) return value; map_shrink(internal); } return value; } internal->pages[entry.page].entries[i] = internal->pages[entry.page].entries[next_slot]; internal->pages[entry.page].entries[i].probe_distance--; } internal->pages[entry.page].entries[(entry.slot + ENTRIES_PER_PAGE - 1) % ENTRIES_PER_PAGE].key = nullptr; return value; } map_void_void_size_t map_void_void_size; int map_void_void_size(MAP(void, void) map) { if (map == nullptr) return 0; map_t *internal = (map_t *)map; int size = 0; for (int page = 0; page < 1 << internal->num_expansions; page++) size += internal->pages[page].num_entries; return size; } #include <stdio.h> struct foo { char bar[16]; }; DEFINE_LIST(int, int); DEFINE_LIST(intptr, int *); DEFINE_LIST(foo, struct foo); DEFINE_LIST(string, char *); DEFINE_LIST(LIST(string), LIST(string)); DEFINE_LIST(LIST(LIST(string)), LIST(LIST(string))); DEFINE_MAP(short, long, short, long); DEFINE_MAP(string, string, char *, char *); DEFINE_MAP(int, string, int, char *); DEFINE_MAP(int, foo, int, struct foo); DEFINE_MAP(string, LIST(string), char *, LIST(string)); int main() { LIST(foo) foolist = LIST_NEW(foo); struct foo foo = {"123456789ABCDEF"}; LIST_APPEND(foolist, foo); LIST_APPEND(foolist, foo); for (ITERATOR(foo) iterator = LIST_GET_ITERATOR(foolist); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%s\n", ITERATOR_GET(iterator).bar); LIST_DESTROY(foolist); printf("\n"); LIST(int) intlist = LIST_NEW(int); LIST_APPEND(intlist, 4); LIST_APPEND(intlist, 9); LIST_APPEND(intlist, 2); LIST_APPEND(intlist, 7); for (ITERATOR(int) iterator = LIST_GET_ITERATOR(intlist); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) { printf("%d\n", ITERATOR_GET(iterator)); if (ITERATOR_GET(iterator) >= 5) LIST_REMOVE(intlist, iterator); } printf("\n"); for (ITERATOR(int) iterator = LIST_GET_ITERATOR(intlist); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%d\n", ITERATOR_GET(iterator)); printf("\n"); LIST(string) stringlist = LIST_NEW(string); LIST_APPEND(stringlist, "foo"); LIST_APPEND(stringlist, "bar"); LIST_APPEND(stringlist, "fur"); LIST_APPEND(stringlist, "bur"); for (ITERATOR(string) iterator = LIST_GET_ITERATOR(stringlist); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%s\n", ITERATOR_GET(iterator)); printf("\n"); LIST(intptr) intptrlist = LIST_NEW(intptr); int intvalues[4] = {4, 9, 2, 7}; LIST_APPEND(intptrlist, &intvalues[0]); LIST_APPEND(intptrlist, &intvalues[1]); LIST_APPEND(intptrlist, &intvalues[2]); LIST_APPEND(intptrlist, &intvalues[3]); for (ITERATOR(intptr) iterator = LIST_GET_ITERATOR(intptrlist); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%d\n", *ITERATOR_GET(iterator)); printf("\n"); LIST(LIST(string)) lists = LIST_NEW(LIST(string)); for (int i = 0; i < 10; i++) LIST_APPEND(lists, LIST_NEW(string)); LIST(LIST(LIST(string))) bigger_list = LIST_NEW(LIST(LIST(string))); LIST_APPEND(bigger_list, lists); MAP(short, long) shortmap = MAP_NEW(short, long, HASH_OBJECT); MAP_INSERT(shortmap, 2, 11); MAP_INSERT(shortmap, 7, 14); MAP_INSERT(shortmap, -3, 29); for (ITERATOR(long) iterator = MAP_GET_VALUE_ITERATOR(shortmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%ld\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(short) iterator = MAP_GET_KEY_ITERATOR(shortmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%d\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(PAIR(short, long)) iterator = MAP_GET_ENTRY_ITERATOR(shortmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) { PAIR(short, long) pair = ITERATOR_GET(iterator); printf("%d -> %ld\n", PAIR_FIRST(pair), PAIR_SECOND(pair)); } printf("\n"); MAP(string, string) stringmap = MAP_NEW(string, string, HASH_NT); MAP_INSERT(stringmap, "foo", "bar"); MAP_INSERT(stringmap, "fur", "bur"); MAP_INSERT(stringmap, "will", "remove"); for (ITERATOR(string) iterator = MAP_GET_VALUE_ITERATOR(stringmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%s\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(string) iterator = MAP_GET_KEY_ITERATOR(stringmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%s\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(PAIR(string, string)) iterator = MAP_GET_ENTRY_ITERATOR(stringmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) { PAIR(string, string) pair = ITERATOR_GET(iterator); printf("%s -> %s\n", PAIR_FIRST(pair), PAIR_SECOND(pair)); } printf("\n"); MAP_REMOVE(stringmap, "will"); for (ITERATOR(PAIR(string, string)) iterator = MAP_GET_ENTRY_ITERATOR(stringmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) { PAIR(string, string) pair = ITERATOR_GET(iterator); printf("%s -> %s\n", PAIR_FIRST(pair), PAIR_SECOND(pair)); } printf("\n"); MAP(int, string) intmap = MAP_NEW(int, string, HASH_OBJECT); MAP_INSERT(intmap, 4, "foo bar"); MAP_INSERT(intmap, 8, "fur bur"); for (ITERATOR(string) iterator = MAP_GET_VALUE_ITERATOR(intmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%s\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(int) iterator = MAP_GET_KEY_ITERATOR(intmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) printf("%d\n", ITERATOR_GET(iterator)); printf("\n"); for (ITERATOR(PAIR(int, string)) iterator = MAP_GET_ENTRY_ITERATOR(intmap); ITERATOR_ACTIVE(iterator); ITERATOR_NEXT(iterator)) { PAIR(int, string) pair = ITERATOR_GET(iterator); printf("%d -> %s\n", PAIR_FIRST(pair), PAIR_SECOND(pair)); } printf("\n"); printf("MAP_GET(intmap, 4) = %s\n", MAP_GET(intmap, 4)); MAP(int, foo) foomap = MAP_NEW(int, foo, HASH_OBJECT); MAP_INSERT(foomap, 9, foo); MAP_INSERT(foomap, 9, foo); printf("MAP_GET(foomap, 9).bar = %s\n", MAP_GET(foomap, 9).bar); MAP_DESTROY(foomap); printf("\n"); LIST(string) more_strings = LIST_NEW(string); LIST_APPEND(more_strings, "meow"); LIST_APPEND(more_strings, "woof"); MAP(string, LIST(string)) map_of_lists = MAP_NEW(string, LIST(string), HASH_NT); MAP_INSERT(map_of_lists, "mylist", stringlist); MAP_INSERT(map_of_lists, "animals", more_strings); for (ITERATOR(LIST(string)) map_iter = MAP_GET_VALUE_ITERATOR(map_of_lists); ITERATOR_ACTIVE(map_iter); ITERATOR_NEXT(map_iter)) for (ITERATOR(string) list_iter = LIST_GET_ITERATOR(ITERATOR_GET(map_iter)); ITERATOR_ACTIVE(list_iter); ITERATOR_NEXT(list_iter)) printf("%s\n", ITERATOR_GET(list_iter)); return 0; }
Become a Patron
Sponsor on GitHub
Donate via PayPal
Compiler Explorer Shop
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
CE on Bluesky
Statistics
Changelog
Version tree