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
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 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
// Basically this: // - https://github.com/ennorehling/dlmalloc/blob/master/malloc.c // - https://sourceware.org/glibc/wiki/MallocInternals // but worse. #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include <Windows.h> typedef unsigned char u8; typedef char i8; typedef unsigned short u16; typedef short i16; typedef unsigned int u32; typedef int i32; typedef unsigned long long u64; typedef long long i64; typedef unsigned long long usize; typedef long long isize; #if 1 #define FMT_USIZE_T usize #define FMT_ISIZE_T isize #define FMT_IMPLEMENTATION #include "fmt.h" #undef bool #undef true #undef false #undef USIZE_MAX #endif typedef _Bool bool; #define false 0 #define true 1 #if defined(__GNUC__) && !defined(__clang__) #define GCC_COMPILER true #else #define GCC_COMPILER false #endif #if defined(_MSC_VER) && !defined(__clang__) #define MSVC_COMPILER true #else #define MSVC_COMPILER false #endif #if defined(__clang__) #define CLANG_COMPILER true #else #define CLANG_COMPILER false #endif #if GCC_COMPILER || CLANG_COMPILER #define NO_RETURN __attribute__((noreturn)) #endif #if MSVC_COMPILER #define NO_RETURN __declspec(noreturn) #endif #if defined(NDEBUG) && !defined(_DEBUG) #define IS_DEBUG false #else #define IS_DEBUG true #endif NO_RETURN void exit_process(i32 exit_code) { ExitProcess(exit_code); } #define ARRAY_SIZE(array) ((isize) sizeof(array) / sizeof((array)[0])) // ================================================================================================= // Configuration // ================================================================================================= // clang-format off // Using an integer type smaller than the word size (e.g. i32 and u32) may reduce allocation // overhead for some allocators (e.g. chunk header size in the heap allocator has the same size as // the size of this type). #ifndef ALLOC_SIZE_T #define ALLOC_SIZE_T isize #define ALLOC_SIZE_MAX ISIZE_MAX #define ALLOC_USIZE_T usize #define ALLOC_USIZE_MAX USIZE_MAX #endif // The default alignment of memory returned by the heap allocator. Must be a power of two >= 8 (on // both 32-bit and 64-bit platforms). // // The ">= 8" restriction exists because the heap allocator organizes memory in sequential chunks // that are sized in multiples of 8. If the alignment requirement was lower, it could lead to an // 8-byte unaligned chunk, causing all subsequent chunks to also be unaligned, as the chunk // addresses increase in increments of 8. Consequently, if we later needed to allocate a chunk with // a higher alignment, it would be impossible to do so within this "unaligned" sequence of chunks. // // This restriction also covers the alignment requirements for the HeapAllocatorData struct // (internal heap allocator state) which is stored within a regular heap allocator chunk. #ifndef HEAP_ALLOC_ALIGNMENT #define HEAP_ALLOC_ALIGNMENT ((usize) sizeof(usize)) #endif #ifndef ALLOC_ASSERT #if IS_DEBUG #define ALLOC_ASSERT(condition) assert_impl(condition, #condition, __FILE__, __LINE__) #else #define ALLOC_ASSERT(condition) #endif #endif #ifndef ALLOC_MEMCPY #define ALLOC_MEMCPY nostd_memcpy #endif #ifndef ALLOC_MEMSET #define ALLOC_MEMSET nostd_memset #endif // clang-format on // ================================================================================================= // Common code // ================================================================================================= #define U32_MAX ((u32) 0 - 1) #define I32_MAX ((i32) (U32_MAX >> 1)) #define USIZE_MAX ((usize) 0 - 1) #define ISIZE_MAX ((isize) (USIZE_MAX >> 1)) bool usize_is_power_of_two(usize self) { return (self & (self - 1)) == 0; } #define UNUSED(x) ((void) (x)) void assert_impl(bool assertion, char const *assertion_string, char const *file_path, isize line) { if (!assertion) { #ifdef FMT_H FMT_STDERR( "[ERROR] Assertion '{}' failed at {}:{}\n", FMT(String, assertion_string), FMT(String, file_path), FMT(isize, line), ); #else UNUSED(assertion_string); UNUSED(file_path); UNUSED(line); #endif exit_process(1); } } #define UNIMPLEMENTED() unimplemented_impl(__FUNCTION__, __FILE__, __LINE__) NO_RETURN void unimplemented_impl(char const *function_name, char const *file_path, isize line) { #ifdef FMT_H FMT_STDERR( "[ERROR] Function '{}' not implemented at {}:{}\n", FMT(String, function_name), FMT(String, file_path), FMT(isize, line), ); #else UNUSED(function_name); UNUSED(file_path); UNUSED(line); #endif exit_process(1); } // "usize size" is for the compatibility with memcpy from "string.h". void *nostd_memcpy(void *dest_void, void const *source_void, usize size) { u8 *dest = dest_void; u8 const *source = source_void; for (usize i = 0; i < size; i += 1) { dest[i] = source[i]; } return dest_void; } // "i32 value" and "usize size" is for the compatibility with memset from "string.h". void *nostd_memset(void *dest_void, i32 value, usize size) { u8 *dest = dest_void; for (usize i = 0; i < size; i += 1) { dest[i] = (u8) value; } return dest; } // Not entirely sure, but it seems like taking an arbitrary char pointer and trying to read or write // to it as if it was a pointer to an integer of a bigger size is considered UB in C, even if the // pointer is properly aligned and there is enough memory for the bigger integer value. // // I don't know if it actually matters in practice, but I tried to avoid the UB by using these // helper functions for reading and writing integers at random memory locations instead of // type-punning. Usually compilers are smart enough to optimize these function calls down to a // single MOV instruction, though, using a custom memcpy implementation might confuse them (e.g. // MSVC doesn't perform the optimization unless you use the "real" memcpy). static inline void usize_write(u8 *ptr, usize value) { ALLOC_MEMCPY(ptr, &value, sizeof(usize)); } static inline usize usize_read(u8 *ptr) { usize value; ALLOC_MEMCPY(&value, ptr, sizeof(usize)); return value; } static inline void isize_write(u8 *ptr, isize value) { ALLOC_MEMCPY(ptr, &value, sizeof(isize)); } static inline isize isize_read(u8 *ptr) { isize value; ALLOC_MEMCPY(&value, ptr, sizeof(isize)); return value; } // ================================================================================================= // Generic allocator interface // ================================================================================================= typedef ALLOC_SIZE_T AllocSize; typedef ALLOC_USIZE_T AllocUSize; // clang-format off #ifdef FMT_H #define FMT_AllocSize(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_AllocUSize(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #endif // clang-format on static inline void alloc_size_write(u8 *ptr, AllocSize value) { ALLOC_MEMCPY(ptr, &value, sizeof(AllocSize)); } static inline AllocSize alloc_size_read(u8 *ptr) { AllocSize value; ALLOC_MEMCPY(&value, ptr, sizeof(AllocSize)); return value; } AllocSize alloc_size_align_up(AllocSize size, usize alignment) { ALLOC_ASSERT(alignment > 0 && usize_is_power_of_two(alignment)); usize alignment_mask = alignment - 1; usize aligned_size = ((usize) size + alignment_mask) & ~alignment_mask; if (aligned_size > ALLOC_SIZE_MAX) { exit_process(1); } return (AllocSize) aligned_size; } AllocSize alloc_size_align_down(AllocSize size, usize alignment) { ALLOC_ASSERT(alignment > 0 && usize_is_power_of_two(alignment)); usize alignment_mask = alignment - 1; usize aligned_size = (usize) size & ~alignment_mask; return (AllocSize) aligned_size; } typedef void AllocatorData; typedef enum { ALLOC_OP_ALLOC, ALLOC_OP_ALLOC_ALIGNED, ALLOC_OP_RESIZE, ALLOC_OP_DEALLOC, ALLOC_OP_DEALLOC_ALL, ALLOC_OP_GET_INFO, } AllocatorOperation; typedef struct AllocatorInfo { usize min_alignment; usize max_alignment; u32 ops_support_bitfield; } AllocatorInfo; // I stole the idea of merging all of the allocator operations into a single function from Odin: // https://pkg.odin-lang.org/base/runtime/#heap_allocator_proc // // Not sure what was the original vision, but I just find it convenient that this way an allocator // can be represented just with two pointers. typedef void *(*AllocatorProcedure)( AllocatorData *, AllocatorOperation, AllocSize size, usize alignment, void *old_memory ); typedef struct Allocator { AllocatorData *data; AllocatorProcedure procedure; } Allocator; void *alloc(Allocator allocator, AllocSize size) { return allocator.procedure(allocator.data, ALLOC_OP_ALLOC, size, 0, NULL); } void *alloc_aligned(Allocator allocator, AllocSize size, usize alignment) { return allocator.procedure(allocator.data, ALLOC_OP_ALLOC_ALIGNED, size, alignment, NULL); } void *resize_alloc(Allocator allocator, void *old_memory, AllocSize new_size, usize alignment) { return allocator.procedure(allocator.data, ALLOC_OP_RESIZE, new_size, alignment, old_memory); } void dealloc(Allocator allocator, void *old_memory) { allocator.procedure(allocator.data, ALLOC_OP_DEALLOC, 0, 0, old_memory); } void dealloc_all(Allocator allocator) { allocator.procedure(allocator.data, ALLOC_OP_DEALLOC_ALL, 0, 0, NULL); } void allocator_get_info(Allocator allocator, AllocatorInfo *allocator_info) { allocator.procedure(allocator.data, ALLOC_OP_GET_INFO, 0, 0, allocator_info); } bool allocator_supports_operation(AllocatorInfo *alloc_info, AllocatorOperation operation) { u32 operation_mask = (u32) 1 << (i32) operation; return (alloc_info->ops_support_bitfield & operation_mask) != 0; } // ================================================================================================= // System allocator // ================================================================================================= typedef struct SystemAllocatorData { usize granularity; } SystemAllocatorData; void *sys_alloc(SystemAllocatorData *alloc_data, AllocSize requested_size) { UNUSED(alloc_data); return VirtualAlloc(NULL, requested_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); } void *sys_alloc_aligned( SystemAllocatorData *alloc_data, AllocSize requested_size, usize alignment ) { if (alignment > alloc_data->granularity) { return NULL; } return sys_alloc(alloc_data, requested_size); } void sys_dealloc(SystemAllocatorData *alloc_data, void *memory) { UNUSED(alloc_data); VirtualFree(memory, 0, MEM_RELEASE); } void sys_alloc_get_info(SystemAllocatorData *alloc_data, AllocatorInfo *alloc_info) { alloc_info->min_alignment = alloc_data->granularity; alloc_info->max_alignment = alloc_data->granularity; alloc_info->ops_support_bitfield = 0; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_ALLOC; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_ALLOC_ALIGNED; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_DEALLOC; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_GET_INFO; } void *sys_alloc_procedure( AllocatorData *alloc_data, AllocatorOperation alloc_op, AllocSize size, usize alignment, void *old_memory ) { switch (alloc_op) { case ALLOC_OP_ALLOC: { return sys_alloc(alloc_data, size); } break; case ALLOC_OP_ALLOC_ALIGNED: { return sys_alloc_aligned(alloc_data, size, alignment); } break; case ALLOC_OP_DEALLOC: { sys_dealloc(alloc_data, old_memory); } break; case ALLOC_OP_GET_INFO: { sys_alloc_get_info(alloc_data, old_memory); } break; case ALLOC_OP_RESIZE: case ALLOC_OP_DEALLOC_ALL: { return NULL; } break; } return NULL; } void sys_alloc_create(Allocator *allocator) { SystemAllocatorData *alloc_data = HeapAlloc(GetProcessHeap(), 0, sizeof(SystemAllocatorData)); if (alloc_data == NULL) { exit_process(1); } SYSTEM_INFO system_info; GetSystemInfo(&system_info); alloc_data->granularity = system_info.dwAllocationGranularity; allocator->data = alloc_data; allocator->procedure = sys_alloc_procedure; } void sys_alloc_destroy(Allocator allocator) { HeapFree(GetProcessHeap(), 0, allocator.data); } // ================================================================================================= // Heap allocator // ================================================================================================= #define HEAP_ALLOC_ALIGNMENT_MASK (HEAP_ALLOC_ALIGNMENT - 1) // Whenever you see a pointer to Chunk, it's a pointer to this structure: // +---------------------+ // | ChunkHeader header | // |---------------------| __ // | Chunk *prev_free | \ // | Chunk *next_free | \ user data // | ... | / section // | AllocSize prev_size | __/ // +---------------------+ // // prev_free, next_free, and prev_size are only stored when the chunk is not used. // header is stored for both used and unused chunks. // Chunk size includes the size of the header. typedef void Chunk; typedef AllocUSize ChunkHeader; // Chunk sizes need to be divisible at least by 8, so that the lower 3 bits of the size could be // used to store flags. #define CHUNK_SIZE_MIN_ALIGNMENT ((usize) 8) #define CHUNK_HEADER_PREV_IN_USE_BIT ((ChunkHeader) 1 << 0) #define CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT ((ChunkHeader) 1 << 1) #define CHUNK_HEADER_IS_FIRST_BIT ((ChunkHeader) 1 << 2) #define CHUNK_HEADER_SIZE_MASK (~( \ CHUNK_HEADER_PREV_IN_USE_BIT \ | CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT \ | CHUNK_HEADER_IS_FIRST_BIT \ )) // By making chunk sizes divisible by the memory alignment, only the first chunk within the system // chunk needs to be manually aligned. This ensures that all subsequent chunks are automatically // aligned, as each next chunk's address is offset by a multiple of the memory alignment. #define CHUNK_SIZE_ALIGNMENT HEAP_ALLOC_ALIGNMENT #define CHUNK_SIZE_ALIGNMENT_MASK HEAP_ALLOC_ALIGNMENT_MASK // "2 * sizeof(usize)" is for the prev_free and next_free pointers. // "sizeof(AllocSize)" is for the prev_size boundary tag. #define CHUNK_MIN_SIZE_UNALIGNED (sizeof(ChunkHeader) + 2 * sizeof(usize) + sizeof(AllocSize)) #define CHUNK_MIN_SIZE ((AllocSize) ( \ ((AllocUSize) CHUNK_MIN_SIZE_UNALIGNED + CHUNK_SIZE_ALIGNMENT_MASK) \ & ~CHUNK_SIZE_ALIGNMENT_MASK \ )) #define GUARD_CHUNK_SIZE ((AllocSize) \ ((AllocUSize) sizeof(ChunkHeader) + (AllocUSize) (CHUNK_SIZE_MIN_ALIGNMENT - 1)) \ & ~((AllocUSize) (CHUNK_SIZE_MIN_ALIGNMENT - 1)) \ ) static inline void chunk_header_write(u8 *ptr, ChunkHeader value) { ALLOC_MEMCPY(ptr, &value, sizeof(ChunkHeader)); } static inline ChunkHeader chunk_header_read(u8 *ptr) { ChunkHeader value; ALLOC_MEMCPY(&value, ptr, sizeof(ChunkHeader)); return value; } static inline AllocSize chunk_get_size(Chunk *chunk) { return (AllocSize) (chunk_header_read((u8 *) chunk) & CHUNK_HEADER_SIZE_MASK); } static inline void chunk_header_set_size(Chunk *chunk, AllocSize size) { ChunkHeader header = chunk_header_read((u8 *) chunk); ChunkHeader prev_in_use = header & CHUNK_HEADER_PREV_IN_USE_BIT; ChunkHeader allocated_directly = header & CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT; ChunkHeader is_first = header & CHUNK_HEADER_IS_FIRST_BIT; chunk_header_write( (u8 *) chunk, (ChunkHeader) size | is_first | allocated_directly | prev_in_use ); } void chunk_tail_set_size(Chunk *chunk, AllocSize size) { u8 *tail_size_ptr = (u8 *) chunk + chunk_get_size(chunk) - sizeof(AllocSize); alloc_size_write(tail_size_ptr, size); } bool chunk_prev_in_use(Chunk *chunk) { return (chunk_header_read((u8 *) chunk) & CHUNK_HEADER_PREV_IN_USE_BIT) != 0; } void chunk_set_prev_in_use(Chunk *chunk, bool prev_in_use) { ChunkHeader header = chunk_header_read((u8 *) chunk); if (prev_in_use) { chunk_header_write((u8 *) chunk, header | CHUNK_HEADER_PREV_IN_USE_BIT); } else { chunk_header_write((u8 *) chunk, header & ~CHUNK_HEADER_PREV_IN_USE_BIT); } } bool chunk_allocated_directly(Chunk *chunk) { return (chunk_header_read((u8 *) chunk) & CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT) != 0; } void chunk_set_allocated_directly(Chunk *chunk, bool allocated_directly) { ChunkHeader header = chunk_header_read((u8 *) chunk); if (allocated_directly) { chunk_header_write((u8 *) chunk, header | CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT); } else { chunk_header_write((u8 *) chunk, header & ~CHUNK_HEADER_ALLOCATED_DIRECTLY_BIT); } } bool chunk_is_first(Chunk *chunk) { return (chunk_header_read((u8 *) chunk) & CHUNK_HEADER_IS_FIRST_BIT) != 0; } void chunk_set_is_first(Chunk *chunk, bool is_first) { ChunkHeader header = chunk_header_read((u8 *) chunk); if (is_first) { chunk_header_write((u8 *) chunk, header | CHUNK_HEADER_IS_FIRST_BIT); } else { chunk_header_write((u8 *) chunk, header & ~CHUNK_HEADER_IS_FIRST_BIT); } } void *chunk_to_memory(Chunk *chunk) { return (u8 *) chunk + sizeof(ChunkHeader); } Chunk *memory_to_chunk(void *memory) { return (Chunk *) ((u8 *) memory - sizeof(ChunkHeader)); } AllocSize chunk_prev_size(Chunk *chunk) { u8 *prev_size_ptr = (u8 *) chunk - sizeof(AllocSize); return alloc_size_read(prev_size_ptr); } Chunk *chunk_prev_free(Chunk *chunk) { u8 *prev_free_ptr = (u8 *) chunk_to_memory(chunk) + sizeof(Chunk *); return (Chunk *) usize_read(prev_free_ptr); } void chunk_set_prev_free(Chunk *chunk, Chunk *prev_free) { u8 *prev_free_ptr = (u8 *) chunk_to_memory(chunk) + sizeof(Chunk *); usize_write(prev_free_ptr, (usize) prev_free); } Chunk *chunk_next_free(Chunk *chunk) { u8 *next_free_ptr = (u8 *) chunk_to_memory(chunk); return (Chunk *) usize_read(next_free_ptr); } void chunk_set_next_free(Chunk *chunk, Chunk *next_free) { u8 *next_free_ptr = (u8 *) chunk_to_memory(chunk); usize_write(next_free_ptr, (usize) next_free); } Chunk *chunk_prev(Chunk *chunk) { u8 *prev_ptr = (u8 *) chunk - chunk_prev_size(chunk); return (Chunk *) prev_ptr; } Chunk *chunk_next(Chunk *chunk) { u8 *next_ptr = (u8 *) chunk + chunk_get_size(chunk); return (Chunk *) next_ptr; } #define FAST_BINS_COUNT ((isize) 20) #define FAST_BIN_MAX_SIZE ((AllocSize) 160) #define SMALL_BINS_COUNT ((isize) 63) #define LARGE_BIN_MIN_SIZE ((AllocSize) 512) #define LARGE_BINS_COUNT ((isize) 63) #define ALLOC_DIRECTLY_MIN_SIZE ((AllocSize) 1024 * 1024) #define SIZED_BINS_COUNT (SMALL_BINS_COUNT + LARGE_BINS_COUNT) // A circular doubly-linked list of free chunks which is represented by a dummy head node which is // kept in a list at all times. The benefit of a dummy node is that deletion of the first/last // elements is not different from deleting any other element. typedef Chunk ChunksBin; // A singly-linked list of free chunks which is repesented by a dummy head node. typedef Chunk FastBin; typedef struct SystemChunk SystemChunk; struct SystemChunk { isize size; SystemChunk *next, *prev; Chunk *first_chunk; u8 data[]; }; typedef struct SystemChunksList { SystemChunk *first, *last; } SystemChunksList; // Buffer sizes to hold dummy head elements for the linked lists. sizeof(ChunkHeader) is needed // because prev/next pointers within chunks point at the other chunks' headers. #define SINGLY_LINKED_BINS_SIZE(bins_count) (sizeof(ChunkHeader) + (bins_count) * sizeof(usize)) #define DOUBLY_LINKED_BINS_SIZE(bins_count) (sizeof(ChunkHeader) + 2 * (bins_count) * sizeof(usize)) typedef struct HeapAllocatorData { Allocator system_allocator; SystemChunksList system_chunks; // An array of doubly-linked lists each of which contains chunks of similar sizes. First // SMALL_BINS_COUNT lists each contains chunks of the same size, next LARGE_BINS_COUNT have // chunk sizes distributed ~ logarithmically within them. u8 bins[DOUBLY_LINKED_BINS_SIZE(SIZED_BINS_COUNT)]; // Chunks that have been recently freed or split off larger chunks and have not yet been // combined with neighboring free chunks or sorted into size-specific bins. u8 unsorted_bin[DOUBLY_LINKED_BINS_SIZE(1)]; // An array of singly-linked lists each of which contains chunks of the same size which are kept // separate even if they are adjacent to other chunks. Fast path for small // deallocations/allocations. u8 fast_bins[SINGLY_LINKED_BINS_SIZE(FAST_BINS_COUNT)]; } HeapAllocatorData; SystemChunk *system_chunk_alloc(Allocator system_allocator, isize min_system_chunk_data_size) { AllocatorInfo system_allocator_info; allocator_get_info(system_allocator, &system_allocator_info); isize system_chunk_size = sizeof(SystemChunk); system_chunk_size += min_system_chunk_data_size; usize alignment_mask = (usize) system_allocator_info.min_alignment - 1; system_chunk_size = (isize) (((usize) system_chunk_size + alignment_mask) & ~alignment_mask); SystemChunk *system_chunk; if (system_allocator_info.min_alignment >= sizeof(usize)) { system_chunk = alloc(system_allocator, system_chunk_size); } else { system_chunk = alloc_aligned(system_allocator, system_chunk_size, sizeof(usize)); } if (system_chunk == NULL) { return NULL; } system_chunk->size = system_chunk_size; system_chunk->next = NULL; system_chunk->prev = NULL; system_chunk->first_chunk = NULL; return system_chunk; } void heap_alloc_print_chunks(HeapAllocatorData *alloc_data); void heap_alloc_print_bins(HeapAllocatorData *alloc_data); isize bin_index_by_chunk_size(AllocSize chunk_size) { if (chunk_size >= ALLOC_DIRECTLY_MIN_SIZE) { isize largest_bin_index = SIZED_BINS_COUNT - 1; return largest_bin_index; } if (chunk_size < LARGE_BIN_MIN_SIZE) { isize small_bin_index = (chunk_size - CHUNK_SIZE_MIN_ALIGNMENT) / CHUNK_SIZE_MIN_ALIGNMENT; return small_bin_index; } AllocSize spacing = CHUNK_SIZE_MIN_ALIGNMENT; AllocSize const spacing_multiplier = CHUNK_SIZE_MIN_ALIGNMENT; AllocSize size_from, size_to = LARGE_BIN_MIN_SIZE; isize bins_count = 32; isize base_large_bin_index = 0; while (bins_count > 0) { spacing *= spacing_multiplier; size_from = size_to; size_to += spacing * bins_count; if (chunk_size < size_to) { isize large_bin_index = base_large_bin_index + (chunk_size - size_from) / spacing; return SMALL_BINS_COUNT + large_bin_index; } base_large_bin_index += bins_count; bins_count /= 2; } bins_count /= 2; isize largest_bin_index = SIZED_BINS_COUNT - 1; return largest_bin_index; } bool chunk_sizes_interval_by_bin_index( isize bin_index, AllocSize *chunk_size_from, AllocSize *chunk_size_to ) { if (bin_index < 0 || bin_index >= SIZED_BINS_COUNT) { return false; } if (bin_index < SMALL_BINS_COUNT) { *chunk_size_from = (bin_index + 1) * CHUNK_SIZE_MIN_ALIGNMENT; *chunk_size_to = (bin_index + 2) * CHUNK_SIZE_MIN_ALIGNMENT; return true; } bin_index -= SMALL_BINS_COUNT; AllocSize spacing = CHUNK_SIZE_MIN_ALIGNMENT * CHUNK_SIZE_MIN_ALIGNMENT; *chunk_size_from = LARGE_BIN_MIN_SIZE; // How many bins which have the same spacing between chunks sizes do we have in front of us // (e.g. in the begining we have 32 bins for chunk sizes X..X+64). isize next_bins_batch_size = 32; while (next_bins_batch_size > 0) { if (bin_index < next_bins_batch_size) { *chunk_size_from += bin_index * spacing; break; } *chunk_size_from += next_bins_batch_size * spacing; spacing *= CHUNK_SIZE_MIN_ALIGNMENT; bin_index -= next_bins_batch_size; next_bins_batch_size /= 2; } *chunk_size_to = *chunk_size_from + spacing; if (*chunk_size_to > ALLOC_DIRECTLY_MIN_SIZE) { *chunk_size_to = ALLOC_DIRECTLY_MIN_SIZE; } return true; } ChunksBin *sized_bin_by_index(HeapAllocatorData *alloc_data, isize bin_index) { return (ChunksBin *) (alloc_data->bins + 2 * sizeof(usize) * bin_index); } FastBin *fast_bin_by_index(HeapAllocatorData *alloc_data, isize bin_index) { return (ChunksBin *) (alloc_data->fast_bins + sizeof(usize) * bin_index); } ChunksBin *sized_bin_by_chunk_size(HeapAllocatorData *alloc_data, AllocSize chunk_size) { return sized_bin_by_index(alloc_data, bin_index_by_chunk_size(chunk_size)); } FastBin *fast_bin_by_chunk_size(HeapAllocatorData *alloc_data, AllocSize chunk_size) { ALLOC_ASSERT(chunk_size <= FAST_BIN_MAX_SIZE); return fast_bin_by_index(alloc_data, chunk_size / CHUNK_SIZE_MIN_ALIGNMENT - 1); } bool bin_is_empty(ChunksBin *bin) { return bin == chunk_next_free(bin); } Chunk *bin_first_chunk(ChunksBin *bin) { return chunk_next_free(bin); } Chunk *bin_last_chunk(ChunksBin *bin) { return chunk_prev_free(bin); } Chunk *bin_end(ChunksBin *bin) { return (Chunk *) bin; } // Does not work for fast bins, because they are neither doubly-linked nor circular. isize bin_chunks_count(ChunksBin *bin) { isize chunks_count = 0; Chunk *chunks_iter = bin_first_chunk(bin); while (chunks_iter != bin_end(bin)) { chunks_count += 1; chunks_iter = chunk_next_free(chunks_iter); } return chunks_count; } void bin_chunk_append(ChunksBin *bin, Chunk *new_chunk) { Chunk *last_chunk = bin_last_chunk(bin); chunk_set_next_free(last_chunk, new_chunk); chunk_set_prev_free(new_chunk, last_chunk); chunk_set_next_free(new_chunk, bin); chunk_set_prev_free(bin, new_chunk); } void bin_chunk_append_sorted(ChunksBin *bin, Chunk *new_chunk) { Chunk *chunks_iter = bin_first_chunk(bin); while (chunks_iter != bin_end(bin)) { if (chunk_get_size(new_chunk) > chunk_get_size(chunks_iter)) { chunk_set_next_free(chunk_prev_free(chunks_iter), new_chunk); chunk_set_prev_free(chunks_iter, new_chunk); chunk_set_prev_free(new_chunk, chunk_prev_free(chunks_iter)); chunk_set_next_free(new_chunk, chunks_iter); return; } chunks_iter = chunk_next_free(chunks_iter); } bin_chunk_append(bin, new_chunk); } void chunk_remove_from_free_list(Chunk *chunk) { if (chunk_prev_free(chunk) != NULL) { chunk_set_next_free(chunk_prev_free(chunk), chunk_next_free(chunk)); } if (chunk_next_free(chunk) != NULL) { chunk_set_prev_free(chunk_next_free(chunk), chunk_prev_free(chunk)); } chunk_set_next_free(chunk, NULL); chunk_set_prev_free(chunk, NULL); } void *heap_alloc(HeapAllocatorData *alloc_data, AllocSize requested_size) { if (requested_size == 0) { return NULL; } AllocSize max_overhead_size = CHUNK_MIN_SIZE + sizeof(ChunkHeader) + CHUNK_SIZE_ALIGNMENT_MASK; if (requested_size > ALLOC_SIZE_MAX - max_overhead_size) { return NULL; } AllocSize requested_chunk_size = sizeof(ChunkHeader) + requested_size; if (requested_chunk_size < CHUNK_MIN_SIZE) { requested_chunk_size = CHUNK_MIN_SIZE; } requested_chunk_size = alloc_size_align_up(requested_chunk_size, CHUNK_SIZE_ALIGNMENT); if (requested_chunk_size <= FAST_BIN_MAX_SIZE) { FastBin *fast_bin = fast_bin_by_chunk_size(alloc_data, requested_chunk_size); if (chunk_next_free(fast_bin) != NULL) { Chunk *victim = chunk_next_free(fast_bin); chunk_set_next_free(fast_bin, chunk_next_free(victim)); return chunk_to_memory(victim); } } if (requested_chunk_size >= ALLOC_DIRECTLY_MIN_SIZE) { isize system_chunk_data_size = 0; system_chunk_data_size += HEAP_ALLOC_ALIGNMENT_MASK; // Offset from the chunk to the system chunk start system_chunk_data_size += sizeof(isize); system_chunk_data_size += requested_chunk_size; SystemChunk *system_chunk = system_chunk_alloc(alloc_data->system_allocator, system_chunk_data_size); if (system_chunk == NULL) { return NULL; } // There is always at least one system chunk in the list (the one used to store // HeapAllocatorData), so no need to check for empty list. system_chunk->prev = alloc_data->system_chunks.last; alloc_data->system_chunks.last->next = system_chunk; alloc_data->system_chunks.last = system_chunk; usize chunk_addr = (usize) ((u8 *) system_chunk + sizeof(SystemChunk) + sizeof(isize)); chunk_addr += sizeof(ChunkHeader); chunk_addr = (chunk_addr + HEAP_ALLOC_ALIGNMENT_MASK) & ~HEAP_ALLOC_ALIGNMENT_MASK; chunk_addr -= sizeof(ChunkHeader); Chunk *chunk = (Chunk *) chunk_addr; isize chunk_size = ((u8 *) system_chunk + system_chunk->size - (u8 *) (chunk)); ALLOC_MEMSET(chunk, 0, sizeof(ChunkHeader)); chunk_header_set_size(chunk, chunk_size); chunk_set_prev_in_use(chunk, true); chunk_set_allocated_directly(chunk, true); chunk_set_is_first(chunk, true); system_chunk->first_chunk = chunk; isize_write((u8 *) chunk - sizeof(isize), (u8 *) chunk - (u8 *) system_chunk); return chunk_to_memory(chunk); } if (requested_chunk_size < LARGE_BIN_MIN_SIZE) { ChunksBin *small_bin = sized_bin_by_chunk_size(alloc_data, requested_chunk_size); if (!bin_is_empty(small_bin)) { Chunk *small_chunk = bin_first_chunk(small_bin); chunk_remove_from_free_list(small_chunk); chunk_set_prev_in_use(chunk_next(small_chunk), true); return chunk_to_memory(small_chunk); } } // Consolidate chunks from fast bins and put them into the unsorted bin before proceeding // further to avoid fragmentation. // for (isize fast_bin_index = 0; fast_bin_index < FAST_BINS_COUNT; fast_bin_index += 1) { // FastBin *fast_bin = fast_bin_by_index(alloc_data, fast_bin_index); // // if (chunk_next_free(fast_bin) == NULL) { // continue; // } // // Chunk *chunks_iter = chunk_next_free(fast_bin); // while (chunks_iter != NULL) { // chunks_iter = chunk_next_free(chunks_iter); // // TODO: Continue from here. // UNIMPLEMENTED(); // } // } Chunk *chunks_iter = bin_first_chunk(alloc_data->unsorted_bin); Chunk *unsorted_bin_end = bin_end(alloc_data->unsorted_bin); while (chunks_iter != unsorted_bin_end) { if (chunk_get_size(chunks_iter) == requested_chunk_size) { Chunk *victim_chunk = chunks_iter; chunk_remove_from_free_list(victim_chunk); chunk_set_prev_in_use(chunk_next(victim_chunk), true); return chunk_to_memory(victim_chunk); } else { Chunk *chunk = chunks_iter; chunks_iter = chunk_next_free(chunks_iter); chunk_remove_from_free_list(chunk); ChunksBin *bin = sized_bin_by_chunk_size(alloc_data, chunk_get_size(chunk)); if (chunk_get_size(chunks_iter) < LARGE_BIN_MIN_SIZE) { bin_chunk_append(bin, chunk); } else { bin_chunk_append_sorted(bin, chunk); } } } isize bin_index = bin_index_by_chunk_size(requested_chunk_size); while (bin_index < SIZED_BINS_COUNT) { ChunksBin *bin = sized_bin_by_index(alloc_data, bin_index); if (!bin_is_empty(bin)) { Chunk *chunks_iter = bin_first_chunk(bin); while (chunks_iter != bin_end(bin)) { if (chunk_get_size(chunks_iter) < requested_chunk_size) { chunks_iter = chunk_next_free(chunks_iter); continue; } Chunk *victim_chunk = chunks_iter; chunk_remove_from_free_list(victim_chunk); if (chunk_get_size(victim_chunk) - requested_chunk_size >= CHUNK_MIN_SIZE) { Chunk *remainder_chunk = (Chunk *) ((u8 *) victim_chunk + requested_chunk_size); ALLOC_MEMSET(remainder_chunk, 0, sizeof(ChunkHeader)); chunk_header_set_size( remainder_chunk, chunk_get_size(victim_chunk) - requested_chunk_size ); chunk_set_prev_in_use(remainder_chunk, true); chunk_set_allocated_directly(remainder_chunk, false); chunk_set_is_first(remainder_chunk, false); chunk_set_prev_free(remainder_chunk, NULL); chunk_set_next_free(remainder_chunk, NULL); chunk_tail_set_size(remainder_chunk, chunk_get_size(remainder_chunk)); bin_chunk_append(&alloc_data->unsorted_bin, remainder_chunk); chunk_header_set_size(victim_chunk, requested_chunk_size); return chunk_to_memory(victim_chunk); } else { chunk_set_prev_in_use(chunk_next(victim_chunk), true); return chunk_to_memory(victim_chunk); } } } bin_index += 1; } isize system_chunk_data_size = 0; system_chunk_data_size += HEAP_ALLOC_ALIGNMENT_MASK; // Offset from the first chunk to the system chunk start system_chunk_data_size += sizeof(isize); system_chunk_data_size += requested_chunk_size; // 2 guard chunks at the end of the system chunk system_chunk_data_size += 2 * GUARD_CHUNK_SIZE; SystemChunk *new_system_chunk = system_chunk_alloc(alloc_data->system_allocator, system_chunk_data_size); if (new_system_chunk == NULL) { return NULL; } usize first_chunk_addr = (usize) ((u8 *) new_system_chunk + sizeof(SystemChunk) + sizeof(isize)); first_chunk_addr += sizeof(ChunkHeader); first_chunk_addr = (first_chunk_addr + HEAP_ALLOC_ALIGNMENT_MASK) & ~HEAP_ALLOC_ALIGNMENT_MASK; first_chunk_addr -= sizeof(ChunkHeader); Chunk *first_chunk = (Chunk *) first_chunk_addr; // How much memory from the begining of the first chunk to the begining of the first of the two // guard chunks? AllocSize max_first_chunk_size = (u8 *) new_system_chunk + new_system_chunk->size - (u8 *) first_chunk - 2 * GUARD_CHUNK_SIZE; AllocSize first_chunk_size; if (max_first_chunk_size - requested_chunk_size >= CHUNK_MIN_SIZE) { first_chunk_size = requested_chunk_size; } else { first_chunk_size = max_first_chunk_size; } ALLOC_MEMSET(first_chunk, 0, sizeof(ChunkHeader)); chunk_header_set_size(first_chunk, first_chunk_size); chunk_set_prev_in_use(first_chunk, true); chunk_set_allocated_directly(first_chunk, false); chunk_set_is_first(first_chunk, true); new_system_chunk->first_chunk = first_chunk; isize_write((u8 *) first_chunk - sizeof(isize), (u8 *) first_chunk - (u8 *) new_system_chunk); Chunk *remainder_chunk = NULL; if (first_chunk_size != max_first_chunk_size) { remainder_chunk = chunk_next(first_chunk); ALLOC_MEMSET(remainder_chunk, 0, sizeof(ChunkHeader)); // Ensure that the remainder size has its lower bits set to zero, but we don't really need // to align its size to CHUNK_SIZE_ALIGNMENT here, because this is the last chunk within the // system chunk. AllocSize remainder_chunk_size = alloc_size_align_down( max_first_chunk_size - first_chunk_size, CHUNK_SIZE_MIN_ALIGNMENT ); ALLOC_ASSERT(remainder_chunk_size != 0); chunk_header_set_size(remainder_chunk, remainder_chunk_size); chunk_set_prev_in_use(remainder_chunk, true); chunk_set_allocated_directly(remainder_chunk, false); chunk_set_is_first(remainder_chunk, false); chunk_set_prev_free(remainder_chunk, NULL); chunk_set_next_free(remainder_chunk, NULL); bin_chunk_append(alloc_data->unsorted_bin, remainder_chunk); } Chunk *first_guard_chunk; if (remainder_chunk != NULL) { first_guard_chunk = chunk_next(remainder_chunk); } else { first_guard_chunk = chunk_next(first_chunk); } chunk_header_set_size(first_guard_chunk, GUARD_CHUNK_SIZE); chunk_set_allocated_directly(first_guard_chunk, false); chunk_set_is_first(first_guard_chunk, false); if (first_chunk_size == max_first_chunk_size) { // Could not fit a remainder chunk, the only one is going to get allocated right now. chunk_set_prev_in_use(first_guard_chunk, true); } else { // Could fit a remainder chunk which is going into the unsorted bin. chunk_set_prev_in_use(first_guard_chunk, false); } Chunk *second_guard_chunk = chunk_next(first_guard_chunk); chunk_header_set_size(second_guard_chunk, GUARD_CHUNK_SIZE); chunk_set_prev_in_use(second_guard_chunk, true); chunk_set_allocated_directly(second_guard_chunk, false); chunk_set_is_first(second_guard_chunk, false); ALLOC_ASSERT(alloc_data->system_chunks.last != NULL); alloc_data->system_chunks.last->next = new_system_chunk; new_system_chunk->prev = alloc_data->system_chunks.last->next; alloc_data->system_chunks.last = new_system_chunk; return chunk_to_memory(first_chunk); } void *heap_alloc_aligned( HeapAllocatorData *alloc_data, AllocSize requested_size, AllocSize alignment ) { UNUSED(alloc_data); UNUSED(alignment); if (requested_size == 0) { return NULL; } UNIMPLEMENTED(); } void *heap_alloc_resize( HeapAllocatorData *alloc_data, void *old_memory, AllocSize requested_new_size ) { UNUSED(alloc_data); UNUSED(old_memory); UNUSED(requested_new_size); UNIMPLEMENTED(); } void heap_dealloc(HeapAllocatorData *alloc_data, void *memory) { if (memory == NULL) { return; } Chunk *chunk = memory_to_chunk(memory); if (chunk_allocated_directly(chunk)) { SystemChunk *system_chunk = (SystemChunk *) ((u8 *) chunk - isize_read((u8 *) chunk - sizeof(isize))); if (system_chunk->prev == NULL) { alloc_data->system_chunks.first = system_chunk->next; } if (system_chunk->next == NULL) { alloc_data->system_chunks.last = system_chunk->prev; } if (system_chunk->prev != NULL) { system_chunk->prev->next = system_chunk->next; } if (system_chunk->next != NULL) { system_chunk->next->prev = system_chunk->prev; } dealloc(alloc_data->system_allocator, system_chunk); return; } // if (chunk_get_size(chunk) <= FAST_BIN_MAX_SIZE) { // FastBin *fast_bin = fast_bin_by_chunk_size(alloc_data, chunk_get_size(chunk)); // // // Fast bins are singly-linked lists, elements are added in front of the list. // chunk_set_next_free(chunk, chunk_next_free(fast_bin)); // chunk_set_next_free(fast_bin, chunk); // // return; // } if (!chunk_prev_in_use(chunk)) { Chunk *prev_chunk = chunk_prev(chunk); chunk_remove_from_free_list(prev_chunk); chunk_header_set_size(prev_chunk, chunk_get_size(prev_chunk) + chunk_get_size(chunk)); chunk = prev_chunk; } if (!chunk_prev_in_use(chunk_next(chunk_next(chunk)))) { Chunk *next_chunk = chunk_next(chunk); chunk_remove_from_free_list(next_chunk); chunk_header_set_size(chunk, chunk_get_size(chunk) + chunk_get_size(next_chunk)); } chunk_set_prev_in_use(chunk_next(chunk), false); chunk_tail_set_size(chunk, chunk_get_size(chunk)); bin_chunk_append(&alloc_data->unsorted_bin, chunk); } void heap_dealloc_all(HeapAllocatorData *alloc_data) { UNUSED(alloc_data); UNIMPLEMENTED(); } void heap_alloc_get_info(HeapAllocatorData *alloc_data, AllocatorInfo *alloc_info) { UNUSED(alloc_data); alloc_info->min_alignment = HEAP_ALLOC_ALIGNMENT; alloc_info->max_alignment = 0; alloc_info->ops_support_bitfield = 0; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_ALLOC; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_ALLOC_ALIGNED; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_RESIZE; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_DEALLOC; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_DEALLOC_ALL; alloc_info->ops_support_bitfield |= (u32) 1 << ALLOC_OP_GET_INFO; } void *heap_alloc_procedure( AllocatorData *alloc_data, AllocatorOperation alloc_op, AllocSize size, usize alignment, void *old_memory ) { switch (alloc_op) { case ALLOC_OP_ALLOC: { return heap_alloc(alloc_data, size); } break; case ALLOC_OP_ALLOC_ALIGNED: { return heap_alloc_aligned(alloc_data, size, alignment); } break; case ALLOC_OP_RESIZE: { if (alignment <= HEAP_ALLOC_ALIGNMENT) { return heap_alloc_resize(alloc_data, old_memory, size); } else { return NULL; } } break; case ALLOC_OP_DEALLOC: { heap_dealloc(alloc_data, old_memory); } break; case ALLOC_OP_DEALLOC_ALL: { heap_dealloc_all(alloc_data); } break; case ALLOC_OP_GET_INFO: { heap_alloc_get_info(alloc_data, old_memory); } break; } return NULL; } // Heap allocator inner state (HeapAllocatorData) is stored within a regular chunk: // // +-----[System chunk]------+ // | Header | // |-------------------------| // | ... | // | Padding to reach | // | default alignment | // | ... | // | +-------------------+ | // | | Offset to the | | // | | system chunk | | // | | start | | // | +-------------------+ | // | +--[Regular chunk]--+ | // | | Header | | // | |-------------------| | // | | HeapAllocatorData | | // | +-------------------+ | // | +--[Regular chunk]--+ | // | | Header | | // | |-------------------| | // | | Remaining free | | // | | space within the | | // | | system chunk | | // | +-------------------+ | // | +---[Guard chunk]---+ | // | | Header | | // | +-------------------+ | // | +---[Guard chunk]---+ | // | | Header | | // | +-------------------+ | // +-------------------------+ // I don't think we can retrieve the pointer to the system chunk header just from the pointer to the // first chunk within it alone (+ the constants such as default alignment and headers' sizes). // // For example, let's say: // - header size = 8 bytes // - default alignment = 16 bytes // - system chunk requires 8-byte alignment // // Then there could be two different placements of a system chunk header and the first chunk within // it relative to each other, depending on where the system chunk header got allocated in memory: // // --------------------------------------------------- // [System chunk header] [Header] [User data] // | | | // Alignment: 16 8 16 // --------------------------------------------------- // [System chunk header] [Header] [User data] // | | | // Alignment: 16 8 16 // --------------------------------------------------- // // So, basically, the problem is that the system chunk header might have weaker alignment than the // alignment what we might want from the heap allocator by default. // // Which means that we need to store the offset from the first chunk to the start of the system // chunk to be able to retrieve the pointer to the system chunk from the first chunk stored within. void heap_alloc_create(Allocator system_allocator, Allocator *allocator) { AllocSize first_chunk_size = sizeof(ChunkHeader) + sizeof(HeapAllocatorData); if (first_chunk_size < CHUNK_MIN_SIZE) { first_chunk_size = CHUNK_MIN_SIZE; } first_chunk_size = alloc_size_align_up(first_chunk_size, CHUNK_SIZE_ALIGNMENT); isize system_chunk_data_size = 0; system_chunk_data_size += HEAP_ALLOC_ALIGNMENT_MASK; // Offset from the first chunk to the system chunk start system_chunk_data_size += sizeof(isize); system_chunk_data_size += first_chunk_size; // At least one free chunk that we could place into unsorted bin system_chunk_data_size += CHUNK_MIN_SIZE; // 2 guard chunks at the end of the system chunk system_chunk_data_size += 2 * GUARD_CHUNK_SIZE; SystemChunk *system_chunk = system_chunk_alloc(system_allocator, system_chunk_data_size); if (system_chunk == NULL) { exit_process(1); } usize first_chunk_addr = (usize) ((u8 *) system_chunk + sizeof(SystemChunk) + sizeof(isize)); first_chunk_addr += sizeof(ChunkHeader); first_chunk_addr = (first_chunk_addr + HEAP_ALLOC_ALIGNMENT_MASK) & ~HEAP_ALLOC_ALIGNMENT_MASK; first_chunk_addr -= sizeof(ChunkHeader); Chunk *first_chunk = (Chunk *) first_chunk_addr; ALLOC_MEMSET(first_chunk, 0, sizeof(ChunkHeader)); chunk_header_set_size(first_chunk, first_chunk_size); chunk_set_prev_in_use(first_chunk, true); chunk_set_allocated_directly(first_chunk, false); chunk_set_is_first(first_chunk, true); system_chunk->first_chunk = first_chunk; isize_write((u8 *) first_chunk - sizeof(isize), (u8 *) first_chunk - (u8 *) system_chunk); Chunk *remainder_chunk = chunk_next(first_chunk); ALLOC_MEMSET(remainder_chunk, 0, sizeof(ChunkHeader)); // Allocate the rest of the memory to the remainder chunk... AllocSize remainder_chunk_size = (AllocSize) (((u8 *) system_chunk + system_chunk->size) - (u8 *) remainder_chunk); // ... and two guard chunks at the end. remainder_chunk_size -= 2 * GUARD_CHUNK_SIZE; remainder_chunk_size = alloc_size_align_down(remainder_chunk_size, CHUNK_SIZE_MIN_ALIGNMENT); chunk_header_set_size(remainder_chunk, remainder_chunk_size); chunk_set_prev_in_use(remainder_chunk, true); chunk_set_allocated_directly(remainder_chunk, false); chunk_set_is_first(remainder_chunk, false); chunk_set_prev_free(remainder_chunk, NULL); chunk_set_next_free(remainder_chunk, NULL); Chunk *first_guard_chunk = chunk_next(remainder_chunk); chunk_header_set_size(first_guard_chunk, GUARD_CHUNK_SIZE); chunk_set_prev_in_use(first_guard_chunk, false); chunk_set_allocated_directly(first_guard_chunk, false); chunk_set_is_first(first_guard_chunk, false); Chunk *second_guard_chunk = chunk_next(first_guard_chunk); chunk_header_set_size(second_guard_chunk, GUARD_CHUNK_SIZE); chunk_set_prev_in_use(second_guard_chunk, true); chunk_set_allocated_directly(second_guard_chunk, false); chunk_set_is_first(second_guard_chunk, false); // HeapAllocatorData alignment requirements should be satisified by the HEAP_ALLOC_ALIGNMENT // default alignment (it must be >= 8). HeapAllocatorData *alloc_data = chunk_to_memory(first_chunk); ALLOC_MEMSET(alloc_data, 0, sizeof(HeapAllocatorData)); alloc_data->system_allocator = system_allocator; // No need to initialize fast bins they are singly linked and elements are always added to the // front of the list. for (isize bin_index = 0; bin_index < SIZED_BINS_COUNT; bin_index += 1) { ChunksBin *bin = sized_bin_by_index(alloc_data, bin_index); chunk_set_next_free(bin, bin); chunk_set_prev_free(bin, bin); } chunk_set_next_free(alloc_data->unsorted_bin, alloc_data->unsorted_bin); chunk_set_prev_free(alloc_data->unsorted_bin, alloc_data->unsorted_bin); bin_chunk_append(alloc_data->unsorted_bin, remainder_chunk); alloc_data->system_chunks.first = system_chunk; alloc_data->system_chunks.last = system_chunk; allocator->data = alloc_data; allocator->procedure = heap_alloc_procedure; } void heap_alloc_destroy(Allocator allocator) { UNUSED(allocator); UNIMPLEMENTED(); } // ================================================================================================= // Heap allocator debug functions // ================================================================================================= #ifdef FMT_H void heap_alloc_print_chunk_bins_sizes(HeapAllocatorData *alloc_data) { typedef enum { BIN_TYPE_NONE, BIN_TYPE_SMALL, BIN_TYPE_LARGE, } BinType; char const *bin_type_string[] = { [BIN_TYPE_NONE] = "none", [BIN_TYPE_SMALL] = "small", [BIN_TYPE_LARGE] = "large", }; // Print which allocation sizes correspond to which chunk bins { BinType prev_bin_type = BIN_TYPE_NONE; ChunksBin *prev_bin = NULL; isize prev_bin_index = 0; AllocSize chunk_size_from = CHUNK_SIZE_MIN_ALIGNMENT, chunk_size_to = CHUNK_SIZE_MIN_ALIGNMENT; while (true) { ChunksBin *bin = sized_bin_by_index(alloc_data, bin_index_by_chunk_size(chunk_size_to)); isize bin_index = bin_index_by_chunk_size(chunk_size_to); BinType bin_type; if (bin_index < SMALL_BINS_COUNT) { bin_type = BIN_TYPE_SMALL; } else { bin_type = BIN_TYPE_LARGE; bin_index -= SMALL_BINS_COUNT; } if (prev_bin != bin && prev_bin != NULL || chunk_size_to == ALLOC_DIRECTLY_MIN_SIZE) { u8 interval[64]; isize interval_size = FMT_STRING( interval, sizeof(interval), "{}..{}", FMT(AllocSize, chunk_size_from), FMT(AllocSize, chunk_size_to), ); FMT_STDOUT( "{} chunk sizes | {} bytes spacing | {} bin #{}\n", FMT(StringView, interval, .size = interval_size, .pad = 16), FMT(AllocSize, chunk_size_to - chunk_size_from, .pad = 8), FMT(String, bin_type_string[prev_bin_type]), FMT(isize, prev_bin_index), ); chunk_size_from = chunk_size_to; } if (chunk_size_to == ALLOC_DIRECTLY_MIN_SIZE) { break; } prev_bin_type = bin_type; prev_bin = bin; prev_bin_index = bin_index; chunk_size_to += CHUNK_SIZE_MIN_ALIGNMENT; } } } void heap_alloc_print_chunks(HeapAllocatorData *alloc_data) { SystemChunk *system_chunks_iter = alloc_data->system_chunks.first; while (system_chunks_iter != NULL) { FMT_STDOUT( "System chunk (0x{address}):\n" " size = {size} bytes\n" "\n", FMT_KEY(usize, address, (usize) system_chunks_iter, .hex = true), FMT_KEY(isize, size, system_chunks_iter->size), ); u8 *system_chunk_end_ptr = (u8 *) system_chunks_iter + system_chunks_iter->size; Chunk *chunks_iter = system_chunks_iter->first_chunk; bool met_first_guard_chunk = false, met_second_guard_chunk = false; while ((u8 *) chunks_iter < system_chunk_end_ptr) { // Quit early in case we met two guard chunks and there is definitely not enough memory // for more chunks. if (met_first_guard_chunk && met_second_guard_chunk && system_chunk_end_ptr - (u8 *) chunks_iter < CHUNK_SIZE_ALIGNMENT) { break; } if (chunk_to_memory(chunks_iter) == alloc_data) { FMT_STDOUT( " Chunk with allocator data (0x{address})\n" " user data starts at 0x{user_data}\n" " user data size = {size} bytes\n" "\n", FMT_KEY(usize, address, (usize) chunks_iter, .hex = true), FMT_KEY(isize, size, (isize) chunk_get_size(chunks_iter) - sizeof(ChunkHeader)), FMT_KEY(usize, user_data, (usize) chunk_to_memory(chunks_iter), .hex = true), ); } else if (chunk_allocated_directly(chunks_iter)) { FMT_STDOUT( " Directly allocated chunk (0x{address})\n" " user data starts at 0x{user_data}\n" " user data size = {size} bytes\n" " is in use = true\n" "\n", FMT_KEY(usize, address, (usize) chunks_iter, .hex = true), FMT_KEY(isize, size, (isize) chunk_get_size(chunks_iter) - sizeof(ChunkHeader)), FMT_KEY(usize, user_data, (usize) chunk_to_memory(chunks_iter), .hex = true), ); // If the chunk is marked as "allocated directly" then it means that it is the only // one within the system chunk. break; } else if (chunk_get_size(chunks_iter) >= CHUNK_MIN_SIZE) { FMT_STDOUT( " Chunk (0x{address}):\n" " user data starts at 0x{user_data}\n" " user data size = {size} bytes\n" " is in use = {is_used}\n" "\n", FMT_KEY(usize, address, (usize) chunks_iter, .hex = true), FMT_KEY(isize, size, (isize) chunk_get_size(chunks_iter) - sizeof(ChunkHeader)), FMT_KEY(bool, is_used, chunk_prev_in_use(chunk_next(chunks_iter))), FMT_KEY(usize, user_data, (usize) chunk_to_memory(chunks_iter), .hex = true), ); } else { FMT_STDOUT( " Guard chunk (0x{address})\n" "\n", FMT_KEY(usize, address, (usize) chunks_iter, .hex = true), ); if (!met_first_guard_chunk) { met_first_guard_chunk = true; } else if (!met_second_guard_chunk) { met_second_guard_chunk = true; } } chunks_iter = chunk_next(chunks_iter); } system_chunks_iter = system_chunks_iter->next; } } void heap_alloc_print_bins(HeapAllocatorData *alloc_data) { bool all_bins_empty = true; for (isize fast_bin_index = 0; fast_bin_index < FAST_BINS_COUNT; fast_bin_index += 1) { ChunksBin *fast_bin = fast_bin_by_index(alloc_data, fast_bin_index); if (chunk_next_free(fast_bin) == NULL) { continue; } else { all_bins_empty = false; } isize chunks_count = 0; { Chunk *chunks_iter = chunk_next_free(fast_bin); while (chunks_iter != NULL) { chunks_count += 1; chunks_iter = chunk_next_free(chunks_iter); } } FMT_STDOUT( "Fast bin #{} for chunks of size {} bytes ({} chunks):\n", FMT(isize, fast_bin_index), FMT(AllocSize, (AllocSize) ((fast_bin_index + 1) * CHUNK_SIZE_MIN_ALIGNMENT)), FMT(isize, chunks_count), ); Chunk *chunks_iter = chunk_next_free(fast_bin); while (chunks_iter != NULL) { FMT_STDOUT( " Chunk of size {} bytes (0x{})\n", FMT(isize, chunk_get_size(chunks_iter)), FMT(usize, (usize) chunks_iter, .hex = true), ); chunks_iter = chunk_next_free(chunks_iter); } } for (isize bin_index = 0; bin_index < SIZED_BINS_COUNT; bin_index += 1) { ChunksBin *bin = sized_bin_by_index(alloc_data, bin_index); if (bin_is_empty(bin)) { continue; } else { all_bins_empty = false; } AllocSize chunks_size_from, chunks_size_to; chunk_sizes_interval_by_bin_index(bin_index, &chunks_size_from, &chunks_size_to); if (bin_index < SMALL_BINS_COUNT) { FMT_STDOUT( "Small bin #{} for chunks of size {} bytes ({} chunks):\n", FMT(isize, bin_index), FMT(AllocSize, chunks_size_from), FMT(isize, bin_chunks_count(bin)), ); } else { FMT_STDOUT( "Large bin #{} for chunks with sizes from {} to {} bytes ({} chunks):\n", FMT(isize, bin_index - SMALL_BINS_COUNT), FMT(AllocSize, chunks_size_from), FMT(AllocSize, chunks_size_to), FMT(isize, bin_chunks_count(bin)), ); } Chunk *chunks_iter = bin_first_chunk(bin); while (chunks_iter != bin_end(bin)) { FMT_STDOUT( " Chunk of size {} bytes (0x{})\n", FMT(isize, chunk_get_size(chunks_iter)), FMT(usize, (usize) chunks_iter, .hex = true), ); chunks_iter = chunk_next_free(chunks_iter); } } if (!bin_is_empty(alloc_data->unsorted_bin)) { all_bins_empty = false; FMT_STDOUT( "Unsorted bin ({} chunks):\n", FMT(isize, bin_chunks_count(alloc_data->unsorted_bin)), ); { Chunk *chunks_iter = bin_first_chunk(alloc_data->unsorted_bin); Chunk *unsorted_bin_end = bin_end(alloc_data->unsorted_bin); while (chunks_iter != unsorted_bin_end) { FMT_STDOUT( " Chunk of size {} bytes (0x{})\n", FMT(isize, chunk_get_size(chunks_iter)), FMT(usize, (usize) chunks_iter, .hex = true), ); chunks_iter = chunk_next_free(chunks_iter); } } } if (all_bins_empty) { FMT_STDOUT("{}", FMT(String, "All bins are empty.\n")); } FMT_STDOUT("{}", FMT(char, '\n')); } #endif // ================================================================================================= // Examples and tests // ================================================================================================= i32 main(void) { Allocator system_allocator; sys_alloc_create(&system_allocator); Allocator allocator; heap_alloc_create(system_allocator, &allocator); FMT_STDOUT( "{hr}\n Small and large bins' sizes\n{hr}\n\n", FMT_KEY(String, hr, "", .pad = 80, .padder = '='), ); heap_alloc_print_chunk_bins_sizes(allocator.data); FMT_STDOUT("{}", FMT(char, '\n')); // Allocate 0 bytes. { void *ptr = alloc(allocator, 0); ALLOC_ASSERT(ptr == NULL); dealloc(allocator, ptr); } // Try to allocate so much memory that it could cause an integer overflow within internal // computations. { void *ptr = alloc(allocator, ALLOC_SIZE_MAX - sizeof(ChunkHeader)); ALLOC_ASSERT(ptr == NULL); } // Try to allocate a bunch of integers one at a time and then dealloc them. { i64 *ptrs[16]; for (isize i = 0; i < ARRAY_SIZE(ptrs); i += 1) { ptrs[i] = alloc(allocator, sizeof(i64)); ALLOC_ASSERT(ptrs[i] != NULL); *ptrs[i] = (i64) i; } i64 control_sum = 0; for (isize i = 0; i < ARRAY_SIZE(ptrs); i += 1) { control_sum += *ptrs[i]; } ALLOC_ASSERT(control_sum == ARRAY_SIZE(ptrs) * (ARRAY_SIZE(ptrs) - 1) / 2); for (isize i = 0; i < ARRAY_SIZE(ptrs) / 4; i += 2) { dealloc(allocator, ptrs[4 * i]); dealloc(allocator, ptrs[4 * i + 1]); dealloc(allocator, ptrs[4 * i + 2]); dealloc(allocator, ptrs[4 * i + 3]); } // Test allocating from a small bin { // Trigger collecting all chunks into sized bins void *sligtly_large_memory = alloc(allocator, 200); isize memory_size = 120; void *memory[4]; for (isize i = 0; i < ARRAY_SIZE(memory); i += 1) { memory[i] = alloc(allocator, memory_size); ALLOC_ASSERT(memory[i] != NULL); ALLOC_MEMSET(memory[i], 0, memory_size); } for (isize i = 0; i < ARRAY_SIZE(memory); i += 1) { dealloc(allocator, memory[i]); } dealloc(allocator, sligtly_large_memory); } FMT_STDOUT( "{hr}\n Allocator state after allocating and deallocating a bunch of stuff\n{hr}\n\n", FMT_KEY(String, hr, "", .pad = 80, .padder = '='), ); heap_alloc_print_bins(allocator.data); heap_alloc_print_chunks(allocator.data); for (isize i = 1; i < ARRAY_SIZE(ptrs) / 4; i += 2) { dealloc(allocator, ptrs[4 * i]); dealloc(allocator, ptrs[4 * i + 1]); dealloc(allocator, ptrs[4 * i + 2]); dealloc(allocator, ptrs[4 * i + 3]); } } // Try to alocate and deallocate a bunch of very large chunks. { AllocSize memory_size = 2 * ALLOC_DIRECTLY_MIN_SIZE; void *memory[4]; for (isize i = 0; i < ARRAY_SIZE(memory); i += 1) { memory[i] = alloc(allocator, memory_size); ALLOC_ASSERT(memory[i] != NULL); ALLOC_MEMSET(memory[i], 0, memory_size); } dealloc(allocator, memory[1]); dealloc(allocator, memory[2]); dealloc(allocator, memory[0]); dealloc(allocator, memory[3]); } // Try to allocate a chunk of memory so large that we don't have one available in the free list { void *memory = alloc(allocator, 100 * 1024); ALLOC_ASSERT(memory != NULL); dealloc(allocator, memory); } FMT_STDOUT( "{hr}\n Final allocator state\n{hr}\n\n", FMT_KEY(String, hr, "", .pad = 80, .padder = '='), ); heap_alloc_print_bins(allocator.data); heap_alloc_print_chunks(allocator.data); return 0; }
c source #2
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
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 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
// fmt.h // // Include library implementation into the translation unit: // #define FMT_IMPLEMENTATION #ifndef FMT_H #define FMT_H // ================================================================================================= // Configuration // ================================================================================================= // Automatically flush file and console writers after each newline. Otherwise, by default, writers // are only auto-flushed once the internal buffer gets full. // // Enable this option, so you don't have to manually flush the writers. // // #define FMT_FLUSH_AFTER_NEWLINES // Include an interface for dynamic allocation of writers: // #define FMT_DYN_INTERFACE // Define a custom interface implementation for writing into console and files. // // Otherwise, by default: // - WriteFile from kernel32 is used on Windows. // - write from <unistd.h> is used on Linux. #ifndef FMT_FILE_T #define FMT_USE_DEFAULT_FILE_INTERFACE #if defined(_WIN32) #define FMT_FILE_T void * #define FMT_FILE_STDOUT fmt__win32_console_handle(FMT_CONSOLE_STDOUT) #define FMT_FILE_STDERR fmt__win32_console_handle(FMT_CONSOLE_STDERR) #define FMT_FILE_WRITE(file, ptr, size) fmt__file_write(file, (u8 const *) (ptr), size) #elif defined(linux) #define FMT_FILE_T int #define FMT_FILE_STDOUT ((FMT_FILE_T) 1) #define FMT_FILE_STDERR ((FMT_FILE_T) 2) #define FMT_FILE_WRITE(file, ptr, size) fmt__file_write(file, (u8 const *) (ptr), size) #else #error "This platform is not supported by default. " \ "You need to manually define an interface for writing into console and files." #endif #endif // Define a custom allocator for dynamic allocation of writers. // // Otherwise, by default: // - HeapAlloc/HeapFree from kernel32 is used on Windows. // - malloc/free from <stdlib.h> is used on Linux. #if defined(FMT_DYN_INTERFACE) && !defined(FMT_ALLOCATOR_T) #define FMT_USE_DEFAULT_ALLOCATOR #if defined(_WIN32) #define FMT_ALLOCATOR_T void * #define FMT_ALLOCATOR NULL #define FMT_ALLOC(allocator, size) \ ((void) (allocator), HeapAlloc(GetProcessHeap(), 0, size)) #define FMT_DEALLOC(allocator, ptr) \ ((void) (allocator), HeapFree(GetProcessHeap(), 0, ptr)) #elif defined(linux) #define FMT_ALLOCATOR_T void * #define FMT_ALLOCATOR NULL #define FMT_ALLOC(allocator, size) ((void) (allocator), malloc(size)) #define FMT_DEALLOC(allocator, ptr) ((void) (allocator), free(ptr)) #else #error "This platform is not supported by default. " \ \ "You need to either define a custom allocator or disable the interface for " \ "dynamic allocation of writers." #endif #endif #ifndef FMT_BUFFER_SIZE #define FMT_BUFFER_SIZE ((isize) 4096) #endif // Make implementation private to the translation unit: #ifdef FMT_STATIC_IMPLEMENTATION #define FMT_API static #else #define FMT_API #endif #ifndef FMT_USIZE_T #define FMT_USIZE_T unsigned long #define FMT_ISIZE_T long #endif #ifndef FMT_U8_T #define FMT_U8_T unsigned char #define FMT_I8_T char #define FMT_U16_T unsigned short #define FMT_I16_T short #define FMT_U32_T unsigned int #define FMT_I32_T int #define FMT_U64_T unsigned long long #define FMT_I64_T long long #endif #ifndef FMT_MEMCPY #define FMT_MEMCPY fmt__nostd_memcpy #endif #ifndef FMT_MEMMOVE #define FMT_MEMMOVE fmt__nostd_memmove #endif // ================================================================================================= // Usage examples // ================================================================================================= // clang-format off // Formatting a variety of different types of values. #if 0 #define FMT_IMPLEMENTATION #include "fmt.h" int main(void) { FMT_STDOUT("Bool: {} or {}\n", FMT(bool, true), FMT(bool, false)); FMT_STDOUT("ASCII char: '{}'\n", FMT(char, '@')); FMT_STDOUT("Unicode code point: '{}'\n", FMT(CodePoint, 0x1f41b)); FMT_STDOUT("C-style string: \"{}\"\n", FMT(String, "hello")); FMT_STDOUT( "UTF-16 string: \"{}\"\n", FMT(U16String, u"Оказывается, под линуксом строковые литералы с префиксом L кодируются в UTF-32. " u"А этот UTF-16 префикс появился только в C11."), ); FMT_STDOUT("Max 64-bit int: {}\n", FMT(i64, I64_MAX, .plus = true)); FMT_STDOUT("Min 64-bit int: {}\n", FMT(i64, I64_MIN)); FMT_STDOUT( "Hex int: 0x{}\n", FMT(u32, 0xababa, .hex = true, .uppercase = true, .precision = 8) ); FMT_STDOUT("Negative int in binary: 0b{}\n", FMT(i16, -64, .bin = true)); // This writer is stack-allocated. FmtWriter console = FMT_STDOUT_WRITER(); i32 cell_width = 5; i32 table_size = 21; for (i32 i = 0; i < table_size * table_size; i += 1) { i32 x = i % table_size, y = i / table_size; if (x == 0 && y == 0) { fmt_write_arg(&console, &FMT(String, "", .pad = cell_width)); } else if (x == 0 || y == 0) { fmt_write_arg(&console, &FMT(i32, x | y, .pad = cell_width, .padder = ' ')); } else { fmt_write_arg(&console, &FMT(i32, x * y, .pad = cell_width, .padder = ' ')); } if (x == table_size - 1) { fmt_write_char(&console, '\n'); } } fmt_flush(&console); } #endif // Defining a custom formatter. #if 0 #define FMT_IMPLEMENTATION #include "fmt.h" typedef struct { i32 x, y; } Vec2; #define FMT_Vec2(type, name, value, ...) \ FMT_Pointer(type, name, value, .fmt = fmt_vec2, __VA_ARGS__) isize fmt_vec2(FmtWriter *writer, void const *vector) { Vec2 const *v = vector; return FMT_WRITE(writer, "vec2({}, {})", FMT(i32, v->x), FMT(i32, v->y)); } int main(void) { Vec2 vector = {42, -24}; FMT_STDOUT("{}\n", FMT(Vec2, &vector)); } #endif // Defining a custom allocator. #if 0 #include <stddef.h> // NULL #include <stdint.h> // uint8_t, intptr_t #include <stdlib.h> // exit #define FMT_ALLOCATOR_T MyAllocator * typedef struct { uint8_t buffer[8192]; intptr_t size; } MyAllocator; #define FMT_ALLOCATOR (&my_allocator) MyAllocator my_allocator = {.size = 0}; #define FMT_ALLOC my_alloc void *my_alloc(MyAllocator *my_alloc, intptr_t size) { if (my_alloc->size + size <= sizeof(my_alloc->buffer)) { uint8_t *ptr = my_alloc->buffer + my_alloc->size; my_alloc->size += size; return ptr; } else { return NULL; } } #define FMT_DEALLOC my_dealloc void my_dealloc(MyAllocator *my_alloc, void *ptr) { (void) my_alloc; (void) ptr; } #define FMT_IMPLEMENTATION #define FMT_DYN_INTERFACE #include "fmt.h" FmtWriter get_console(void) { FmtWriter console; if (!fmt_dyn_console_writer(FMT_ALLOCATOR, FMT_CONSOLE_STDOUT, &console)) { exit(1); } return console; } int main(void) { FmtWriter console = get_console(); fmt_write_string(&console, "Hey\n"); fmt_flush(&console); fmt_dyn_console_writer_destroy(FMT_ALLOCATOR, &console); } #endif // Rendering to a string + an example of how to use named args. #if 0 #include <stdlib.h> // malloc/free #define FMT_IMPLEMENTATION #include "fmt.h" int main(void) { char const *format = "{pill}Rendering{heart}to{heart}a{heart}string{heart}example{pill}\n"; FormatArg format_args[2] = { FMT_KEY(CodePoint, pill, 0x1f48a, .pad = 3, .align = FMT_ALIGN_CENTER), FMT_KEY(U16String, heart, u"🩷", .pad = 3, .align = FMT_ALIGN_CENTER), }; isize required_string_size = fmt_string_write_args(NULL, 0, format, format_args, 2); char *string = malloc(required_string_size); if (string == NULL) { return 1; } fmt_string_write_args(string, required_string_size, format, format_args, 2); FMT_STDOUT("{}", FMT(StringView, string, .size = required_string_size)); free(string); return 0; } #endif // clang-format on // ================================================================================================= // Typedefs used within the library interface // ================================================================================================= // I tried to make the library implementation compilable with a C++ compiler, however, heavy use of // compound literals in macros (e.g. within FMT and FMT_STDOUT) makes it mostly useless in C++. #ifdef __cplusplus extern "C" { #endif // "ifdef __STDC_VERSION__" check is there because MSVC doesn't define it unless you specify the // "/std" option explicitly. It would obviously be much better to include <stdbool.h> here instead // of reinventing the wheel, but I couldn't care less about C standard library headers, I'm sorry. // // "#define bool _Bool" instead of "typedef _Bool bool", so that you could #undef my bool, if you // don't like it. #if !defined(__cplusplus) && !defined(bool) \ && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202300L) #define true 1 #define false 0 #define bool _Bool #endif // I don't want to prefix these types, so configure them to be the same as in the code where the // header gets included, if you happen to use the same alias names. (C doesn't complain when you // repeat the same typedef multiple times.) typedef FMT_U8_T u8; typedef FMT_I8_T i8; typedef FMT_U16_T u16; typedef FMT_I16_T i16; typedef FMT_U32_T u32; typedef FMT_I32_T i32; typedef FMT_U64_T u64; typedef FMT_I64_T i64; typedef FMT_USIZE_T usize; typedef FMT_ISIZE_T isize; // ================================================================================================= // Generic polymorphic writer interface // ================================================================================================= typedef void (*FmtWriteFunction)(void *writer_data, u8 const *data, isize size); typedef void (*FmtFlushFunction)(void *writer_data); typedef struct { void *data; FmtWriteFunction write; FmtFlushFunction flush; } FmtWriter; FMT_API void fmt_write(FmtWriter *writer, u8 const *data, isize size); FMT_API void fmt_flush(FmtWriter *writer); // ================================================================================================= // Specific writers' implementations // ================================================================================================= typedef struct { u8 *data; isize size; isize capacity; } FmtBuffer; typedef FMT_FILE_T FmtFile; typedef struct { FmtFile file; FmtBuffer buffer; } FmtFileWriter; FMT_API void fmt_file_writer_write(void *writer_data, u8 const *data, isize size); FMT_API void fmt_file_writer_flush(void *writer_data); typedef enum { FMT_CONSOLE_STDOUT, FMT_CONSOLE_STDERR, } FmtConsoleOutputType; FMT_API FmtFile fmt_console_handle(FmtConsoleOutputType console_type); typedef FmtFileWriter FmtConsoleWriter; typedef FmtBuffer FmtStringWriter; FMT_API void fmt_string_writer_write(void *writer_data, u8 const *data, isize size); FMT_API void fmt_string_writer_flush(void *writer_data); // ================================================================================================= // Dynamically allocated writers (they can be copied around and stored anywhere) // ================================================================================================= #ifdef FMT_DYN_INTERFACE typedef FMT_ALLOCATOR_T FmtAllocator; FMT_API bool fmt_dyn_file_writer(FmtAllocator allocator, FmtFile file, FmtWriter *writer); FMT_API void fmt_dyn_file_writer_destroy(FmtAllocator allocator, FmtWriter *writer); FMT_API bool fmt_dyn_console_writer( FmtAllocator allocator, FmtConsoleOutputType console_type, FmtWriter *writer ); FMT_API void fmt_dyn_console_writer_destroy(FmtAllocator allocator, FmtWriter *writer); FMT_API bool fmt_dyn_string_writer( FmtAllocator allocator, char *string, isize string_capacity, FmtWriter *writer ); FMT_API void fmt_dyn_string_writer_destroy(FmtAllocator allocator, FmtWriter *writer); #endif // FMT_DYN_INTERFACE // ================================================================================================= // Stack-allocated writers (they don't survive getting out of the outer scope) // ================================================================================================= // https://en.cppreference.com/w/c/language/compound_literal // > The unnamed object to which the compound literal evaluates has static storage duration if the // > compound literal occurs at file scope and automatic storage duration if the compound literal // > occurs at block scope (in which case the object's lifetime ends at the end of the enclosing // > block). // // Also: // https://x.com/mono_morphic/status/1869303586502397994 #define FMT_FILE_WRITER(file_) (FmtWriter) { \ .data = &(FmtFileWriter) { \ .file = (file_), \ .buffer = { \ .data = (u8[FMT_BUFFER_SIZE]){}, \ .size = 0, \ .capacity = FMT_BUFFER_SIZE, \ }, \ }, \ .write = fmt_file_writer_write, \ .flush = fmt_file_writer_flush, \ } #define FMT_CONSOLE_WRITER(console_type) (FmtWriter) { \ .data = &(FmtConsoleWriter) { \ .file = fmt_console_handle(console_type), \ .buffer = { \ .data = (u8[FMT_BUFFER_SIZE]){}, \ .size = 0, \ .capacity = FMT_BUFFER_SIZE, \ }, \ }, \ .write = fmt_file_writer_write, \ .flush = fmt_file_writer_flush, \ } #define FMT_STDOUT_WRITER() FMT_CONSOLE_WRITER(FMT_CONSOLE_STDOUT) #define FMT_STDERR_WRITER() FMT_CONSOLE_WRITER(FMT_CONSOLE_STDERR) #define FMT_STRING_WRITER(string, string_capacity) (FmtWriter) { \ .data = &(FmtStringWriter) { \ .data = (u8 *) (string), \ .size = 0, \ .capacity = (string_capacity), \ }, \ .write = fmt_string_writer_write, \ .flush = fmt_string_writer_flush, \ } // ================================================================================================= // Functions for writing primitive values // ================================================================================================= typedef u32 FmtCodePoint; FMT_API isize fmt_write_char(FmtWriter *writer, char value); FMT_API isize fmt_write_char_repeat(FmtWriter *writer, isize repeat_count, char value); FMT_API isize fmt_write_code_point(FmtWriter *writer, FmtCodePoint code_point); FMT_API isize fmt_write_sv(FmtWriter *writer, u8 const *data, isize size); FMT_API isize fmt_write_u16_sv(FmtWriter *writer, u16 const *data, isize size); FMT_API isize fmt_write_string(FmtWriter *writer, char const *data); FMT_API isize fmt_write_u16_string(FmtWriter *writer, u16 const *data); FMT_API isize fmt_write_bool(FmtWriter *writer, bool value); typedef struct { u64 unsigned_value; isize size; bool is_negative; } FmtIntValue; typedef struct { u64 base; bool make_uppercase; bool include_plus; isize precision; } FmtIntParams; FMT_API isize fmt_write_int(FmtWriter *writer, FmtIntValue const *value, FmtIntParams const *params); typedef enum { FMT_ALIGN_RIGHT, FMT_ALIGN_CENTER, FMT_ALIGN_LEFT, } FormatAlignment; FMT_API isize fmt_left_padding_amount(isize content_width, isize padding, FormatAlignment alignment); FMT_API isize fmt_right_padding_amount(isize content_width, isize padding, FormatAlignment alignment); // ================================================================================================= // Format arguments // ================================================================================================= typedef enum { FORMAT_ARG_INT, FORMAT_ARG_BOOL, FORMAT_ARG_CHAR, FORMAT_ARG_CODE_POINT, FORMAT_ARG_STRING_VIEW, FORMAT_ARG_U16_STRING_VIEW, FORMAT_ARG_STRING, FORMAT_ARG_U16_STRING, FORMAT_ARG_POINTER, } FormatArgKind; #define FMT_PADDING_PARAMS \ isize pad; \ char padder; \ FormatAlignment align typedef struct { FMT_PADDING_PARAMS; } FormatArgPaddingParams; typedef struct { FMT_PADDING_PARAMS; u64 value; isize size; bool is_negative; isize precision; bool plus; bool uppercase; bool hex; bool octal; bool bin; } FormatArgInt; typedef struct { FMT_PADDING_PARAMS; bool value; } FormatArgBool; typedef struct { FMT_PADDING_PARAMS; char value; } FormatArgChar; typedef struct { FMT_PADDING_PARAMS; FmtCodePoint value; } FormatArgCodePoint; typedef struct { FMT_PADDING_PARAMS; char const *value; isize max_width; } FormatArgString; typedef struct { FMT_PADDING_PARAMS; u16 const *value; isize max_width; } FormatArgU16String; typedef struct { FMT_PADDING_PARAMS; char const *value; isize size; isize max_width; } FormatArgStringView; typedef struct { FMT_PADDING_PARAMS; u16 const *value; isize size; isize max_width; } FormatArgU16StringView; typedef isize (*FmtPointerWriteFunction)(FmtWriter *writer, void const *pointer); typedef struct { FMT_PADDING_PARAMS; void const *value; FmtPointerWriteFunction fmt; } FormatArgPointer; typedef struct { FormatArgKind kind; char const *name; union { FormatArgPaddingParams padding_params; FormatArgInt int_arg; FormatArgBool bool_arg; FormatArgChar char_arg; FormatArgCodePoint code_point_arg; FormatArgString string_arg; FormatArgU16String u16_string_arg; FormatArgStringView string_view_arg; FormatArgU16StringView u16_string_view_arg; FormatArgPointer pointer_arg; } as; } FormatArg; #define FMT(type, value, ...) FMT_##type(type, NULL, value, __VA_ARGS__) #define FMT_KEY(type, name, value, ...) FMT_##type(type, #name, value, __VA_ARGS__) #define FMT_PLAIN_VALUE(type, type_enum_id, type_union_id, name_, value_, ...) \ (FormatArg) { \ .kind = FORMAT_ARG_##type_enum_id, \ .name = (name_), \ .as.type_union_id##_arg = { \ .value = (value_), \ __VA_ARGS__ \ } \ } FMT_API u64 fmt__i64_abs_as_u64(i64 value); #define FMT_INT(type, name_, value_, ...) \ (FormatArg) { \ .kind = FORMAT_ARG_INT, \ .name = (name_), \ .as.int_arg = { \ .value = (value_) >= 0 ? (u64) (value_) : fmt__i64_abs_as_u64((i64) (value_)), \ .size = sizeof(type), \ .is_negative = (value_) < 0, \ __VA_ARGS__ \ }, \ } #define FMT_bool(type, name, value, ...) \ FMT_PLAIN_VALUE(type, BOOL, bool, name, value, __VA_ARGS__) #define FMT_char(type, name, value, ...) \ FMT_PLAIN_VALUE(type, CHAR, char, name, value, __VA_ARGS__) #define FMT_CodePoint(type, name, value, ...) \ FMT_PLAIN_VALUE(FmtCodePoint, CODE_POINT, code_point, name, value, __VA_ARGS__) #define FMT_String(type, name, value, ...) \ FMT_PLAIN_VALUE(char const *, STRING, string, name, value, __VA_ARGS__) #define FMT_U16String(type, name, value, ...) \ FMT_PLAIN_VALUE(u16 const *, U16_STRING, u16_string, name, value, __VA_ARGS__) #define FMT_StringView(type, name, value, ...) \ FMT_PLAIN_VALUE(char const *, STRING_VIEW, string_view, name, value, __VA_ARGS__) #define FMT_U16StringView(type, name, value, ...) \ FMT_PLAIN_VALUE(u16 const *, U16_STRING_VIEW, u16_string_view, name, value, __VA_ARGS__) #define FMT_Pointer(type, name, value, ...) \ FMT_PLAIN_VALUE(void *, POINTER, pointer, name, value, __VA_ARGS__) // By default MSVC doesn't like "#define FMT_u8(...) FMT_INT(__VA_ARGS__)", unless you specify some // weird flags (-Zc:preprocessor). #define FMT_u8(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_i8(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_u16(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_i16(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_u32(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_i32(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_u64(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_i64(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_usize(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) #define FMT_isize(type, name, value, ...) FMT_INT(type, name, value, __VA_ARGS__) // ================================================================================================= // Format arguments writer function // ================================================================================================= // +1 dummy format arg in the array and then -1 afterwards are there to top MSVC from complaining // about sizeof returning 0 (C4034 warning), when there are no format args. #define FMT_FORMAT_ARGS_COUNT(...) \ ((isize) sizeof((FormatArg[]) {(FormatArg) {}, __VA_ARGS__}) / sizeof(FormatArg) - 1) FMT_API isize fmt_write_arg(FmtWriter *writer, FormatArg const *format_arg); FMT_API isize fmt_write_args( FmtWriter *writer, char const *format, FormatArg const *format_args, isize format_args_count ); // I don't really like that this macro pastes a list of format arguments twice into the // preprocessed source code, but I think in C99 there is no way to avoid this. // // One solution is to save a list of format args into a local variable and then compute the number // of args using this variable, but then I'm not going to be able to "return" the number of bytes // written into the writer from this macro. // // Another solution is to use a sentinel-terminated list of format args, but then there comes the // problem of appending a sentinel element to an empty __VA_ARGS__ list, which is impossible to // solve without something like __VA_OPT__. (Ugly solutions like this one // https://stackoverflow.com/a/11172679 are out of the question.) #define FMT_WRITE(writer, format, ...) \ fmt_write_args( \ writer, \ format, \ (FormatArg[]) {__VA_ARGS__}, \ FMT_FORMAT_ARGS_COUNT(__VA_ARGS__) \ ) // ================================================================================================= // Helpers for quickly writing and immediately flushing // ================================================================================================= FMT_API isize fmt_console_write_args( FmtConsoleOutputType output_type, char const *format, FormatArg const *format_args, isize format_args_count ); #define FMT_STDOUT(format, ...) \ fmt_console_write_args( \ FMT_CONSOLE_STDOUT, \ format, \ (FormatArg[]) {__VA_ARGS__}, \ FMT_FORMAT_ARGS_COUNT(__VA_ARGS__) \ ) #define FMT_STDERR(format, ...) \ fmt_console_write_args( \ FMT_CONSOLE_STDERR, \ format, \ (FormatArg[]) {__VA_ARGS__}, \ FMT_FORMAT_ARGS_COUNT(__VA_ARGS__) \ ) FMT_API isize fmt_string_write_args( char *string, isize capacity, char const *format, FormatArg const *format_args, isize format_args_count ); #define FMT_STRING(string, capacity, format, ...) \ fmt_string_write_args( \ string, \ capacity, \ format, \ (FormatArg[]) {__VA_ARGS__}, \ FMT_FORMAT_ARGS_COUNT(__VA_ARGS__) \ ) FMT_API isize fmt_file_write_args( FmtFile file, char const *format, FormatArg const *format_args, isize format_args_count ); #define FMT_FILE(file, format, ...) \ fmt_file_write_args( \ file, \ format, \ (FormatArg[]) {__VA_ARGS__}, \ FMT_FORMAT_ARGS_COUNT(__VA_ARGS__) \ ) #ifdef __cplusplus } #endif #endif // FMT_H // ================================================================================================= // Implementation section // ================================================================================================= #ifdef FMT_IMPLEMENTATION #undef FMT_IMPLEMENTATION #ifdef __cplusplus #define restrict #endif // It's probably a bad behavior on behalf of a single-header library to leak these unprefixed // #defines into the user's source code, but I just hate the idea of having to prefix simple types // like bool or u64, or constants like NULL or I64_MAX. // // It makes sense to prefix private functions, since in case of a name colision you can't do much, // because of the ODR. On the other hand, when you have colliding #defines, you can detect the // presence of some alien #defines with an #ifdef and then #undef and replace them with your own // implementations. // // I was contemplating doing something like this: // // #ifndef NULL // #define NULL ((void *) 0) // #define FMT_PRIVATE_NULL // #endif // // ... // // #ifdef FMT_PRIVATE_NULL // #undef NULL // #endif // // But it adds way too much complexity, it's even worse than having to include the prefixes. #ifndef NULL #define NULL ((void *) 0) #endif #ifndef UNUSED #define UNUSED(x) ((void) (x)) #endif #ifndef USIZE_MAX #define USIZE_MAX (~((usize) 0)) #endif #ifndef ISIZE_MAX #define ISIZE_MAX ((isize) (USIZE_MAX >> 1)) #endif #ifndef U64_MAX #define U64_MAX (~((u64) 0)) #endif #ifndef I64_MAX #define I64_MAX ((i64) (U64_MAX >> 1)) #define I64_MIN (-I64_MAX - 1) #endif // ================================================================================================= // OS layer // ================================================================================================= #ifdef FMT_USE_DEFAULT_FILE_INTERFACE #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include <Windows.h> #endif #ifdef linux #include <errno.h> #include <unistd.h> #endif #endif #ifdef FMT_USE_DEFAULT_ALLOCATOR #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include <Windows.h> #endif #ifdef linux #include <stdlib.h> #endif #endif // ================================================================================================= // Windows // ================================================================================================= #if defined(FMT_USE_DEFAULT_FILE_INTERFACE) && defined(_WIN32) static FmtFile fmt__win32_console_handle(FmtConsoleOutputType console_type) { FmtFile handle; switch (console_type) { case FMT_CONSOLE_STDOUT: { handle = GetStdHandle(STD_OUTPUT_HANDLE); } break; case FMT_CONSOLE_STDERR: { handle = GetStdHandle(STD_ERROR_HANDLE); } break; default: { handle = INVALID_HANDLE_VALUE; } break; } if (handle == INVALID_HANDLE_VALUE) { ExitProcess(1); } SetConsoleOutputCP(CP_UTF8); return handle; } static void fmt__file_write(FmtFile file, u8 const *data, isize size) { u8 const *data_iter = data; u8 const *data_end = data + size; while (data_iter < data_end) { DWORD bytes_written; BOOL write_result = WriteFile(file, data_iter, data_end - data_iter, &bytes_written, NULL); if (!write_result) { break; } data_iter += bytes_written; } } #endif // defined(FMT_USE_DEFAULT_FILE_INTERFACE) && defined(_WIN32) // ================================================================================================= // Linux // ================================================================================================= #if defined(FMT_USE_DEFAULT_FILE_INTERFACE) && defined(linux) static void fmt__file_write(FmtFile file, u8 const *data, isize size) { u8 const *data_iter = data; u8 const *data_end = data + size; while (data_iter < data_end) { isize write_result = write(file, data_iter, data_end - data_iter); if (write_result == -1) { if (errno == EINTR) { continue; } break; } data_iter += write_result; } } #endif // defined(FMT_USE_DEFAULT_FILE_INTERFACE) && defined(linux) // ================================================================================================= // Functions for working with memory // ================================================================================================= static void *fmt__nostd_memcpy( void *restrict dest_void, void const *restrict source_void, usize size ) { u8 const *source_iter = (u8 const *) source_void; u8 *dest_iter = (u8 *) dest_void; u8 *dest_end = (u8 *) dest_void + size; while (dest_iter < dest_end) { *dest_iter = *source_iter; source_iter += 1; dest_iter += 1; } return dest_void; } static void *fmt__nostd_memmove(void *dest_void, void const *source_void, usize size) { u8 const *source = (u8 const *) source_void; u8 *dest = (u8 *) dest_void; if (source == dest) { return dest_void; } if (dest < source) { u8 const *source_iter = source; u8 *dest_iter = dest; u8 *dest_end = dest + size; while (dest_iter < dest_end) { *dest_iter = *source_iter; source_iter += 1; dest_iter += 1; } } else { u8 const *source_iter = source + size - 1; u8 *dest_iter = dest + size - 1; while (dest_iter >= dest) { *dest_iter = *source_iter; source_iter -= 1; dest_iter -= 1; } } return dest_void; } // ================================================================================================= // Integers-related functions // ================================================================================================= FMT_API u64 fmt__i64_abs_as_u64(i64 self) { if (self >= 0) { return (u64) self; } else if (self != I64_MIN) { return (u64) (-self); } else { return (u64) 1 << 63; } } static isize fmt__isize_min(isize self, isize other) { return self < other ? self : other; } static isize fmt__isize_max(isize self, isize other) { return self > other ? self : other; } // ================================================================================================= // Unicode // ================================================================================================= #define FMT_MAX_CODE_POINT ((FmtCodePoint) 0x10ffff) #define FMT_REPLACEMENT_CHARACTER_CP ((FmtCodePoint) 0xfffd) // ================================================================================================= // UTF-8 // ================================================================================================= static isize fmt__code_point_utf8_size(FmtCodePoint code_point) { if (code_point < 0x80) { return 1; } else if (code_point < 0x800) { return 2; } else if (code_point < 0x10000) { return 3; } else if (code_point <= FMT_MAX_CODE_POINT) { return 4; } else { return fmt__code_point_utf8_size(FMT_REPLACEMENT_CHARACTER_CP); } } static isize fmt__code_point_to_utf8(FmtCodePoint code_point, u8 *data) { if (code_point > FMT_MAX_CODE_POINT) { return fmt__code_point_to_utf8(FMT_REPLACEMENT_CHARACTER_CP, data); } isize utf8_size = fmt__code_point_utf8_size(code_point); if (utf8_size == 1) { data[0] = (u8) code_point; } else if (utf8_size == 2) { data[0] = 0xc0 | (u8) ((code_point >> 6) & (FmtCodePoint) 0x1f); data[1] = 0x80 | (u8) ((code_point >> 0) & (FmtCodePoint) 0x3f); } else if (utf8_size == 3) { data[0] = 0xe0 | (u8) ((code_point >> 12) & (FmtCodePoint) 0x0f); data[1] = 0x80 | (u8) ((code_point >> 6) & (FmtCodePoint) 0x3f); data[2] = 0x80 | (u8) ((code_point >> 0) & (FmtCodePoint) 0x3f); } else { data[0] = 0xf0 | (u8) ((code_point >> 18) & (FmtCodePoint) 0x07); data[1] = 0x80 | (u8) ((code_point >> 12) & (FmtCodePoint) 0x3f); data[2] = 0x80 | (u8) ((code_point >> 6) & (FmtCodePoint) 0x3f); data[3] = 0x80 | (u8) ((code_point >> 0) & (FmtCodePoint) 0x3f); } return utf8_size; } static isize fmt__utf8_size(u8 first_byte) { if ((first_byte & 0x80) == 0) { return 1; } else if ((first_byte & 0xe0) == 0xc0) { return 2; } else if ((first_byte & 0xf0) == 0xe0) { return 3; } else if ((first_byte & 0xf8) == 0xf0) { return 4; } else { // If this function is used to iterate a string, keep going over invalid bytes. return 1; } } static bool fmt__utf8_is_start(u8 byte) { return (byte & 0x80) == 0 || (byte & 0xe0) == 0xc0 || (byte & 0xf0) == 0xe0 || (byte & 0xf8) == 0xf0; } // ================================================================================================= // UTF-16 // ================================================================================================= static isize fmt__utf16_size(u16 first_piece) { if (first_piece < 0xd800 || first_piece > 0xdfff) { return 1; } else if (first_piece < 0xdc00) { return 2; } else { // If this function is used to iterate a string, keep going over invalid pieces. return 1; } } static bool fmt__utf16_is_start(u16 piece) { return piece < 0xdc00 || piece > 0xdfff; } static bool fmt__utf16_is_high_surrogate(u16 piece) { return 0xd800 <= piece && piece < 0xdc00; } static bool fmt__utf16_is_low_surrogate(u16 piece) { return 0xdc00 <= piece && piece < 0xe000; } // ================================================================================================= // UTF-8 String[View] // ================================================================================================= static isize fmt__string_size(char const *string) { u8 const *string_iter = (u8 const *) string; while (*string_iter != 0) { string_iter += 1; } return string_iter - (u8 const *) string; } typedef struct { u8 const *data; isize size; } FmtStringView; static bool fmt__sv_equals(FmtStringView self, FmtStringView other) { if (self.size != other.size) { return false; } u8 const *self_iter = self.data; u8 const *self_end = self.data + self.size; u8 const *other_iter = other.data; while (self_iter < self_end) { if (*self_iter != *other_iter) { return false; } self_iter += 1; other_iter += 1; } return true; } static isize fmt__sv_chars_count(FmtStringView string_view) { isize chars_count = 0; u8 const *string_iter = string_view.data; u8 const *string_end = string_view.data + string_view.size; while (string_iter < string_end) { string_iter += fmt__utf8_size(*string_iter); chars_count += 1; } return chars_count; } static FmtStringView fmt__sv_chop_chars(FmtStringView *string_view, isize chars_count) { u8 const *string_iter = string_view->data; u8 const *string_end = string_view->data + string_view->size; if (string_iter < string_end) { isize chars_left = chars_count; while (chars_left > 0) { isize next_char_size = fmt__utf8_size(*string_iter); if (string_iter + next_char_size > string_end) { break; } string_iter += next_char_size; chars_left -= 1; } } FmtStringView chopped_string = { .data = string_view->data, .size = string_iter - string_view->data, }; string_view->data = string_iter; string_view->size = string_end - string_iter; return chopped_string; } static FmtStringView fmt__sv_from_string(char const *string) { FmtStringView string_view = { .data = (u8 const *) string, .size = fmt__string_size(string), }; return string_view; } // ================================================================================================= // UTF-16 String[View] // ================================================================================================= static isize fmt__u16_string_size(u16 const *string) { u16 const *string_iter = string; while (*string_iter != 0) { string_iter += 1; } return string_iter - string; } typedef struct { u16 const *data; isize size; } FmtU16StringView; static isize fmt__u16_sv_chars_count(FmtU16StringView string_view) { isize chars_count = 0; u16 const *string_iter = string_view.data; u16 const *string_end = string_view.data + string_view.size; while (string_iter < string_end) { string_iter += fmt__utf16_size(*string_iter); chars_count += 1; } return chars_count; } static FmtU16StringView fmt__u16_sv_chop_chars(FmtU16StringView *string_view, isize chars_count) { u16 const *string_iter = string_view->data; u16 const *string_end = string_view->data + string_view->size; if (string_iter < string_end) { isize chars_left = chars_count; while (chars_left > 0) { isize next_char_size = fmt__utf16_size(*string_iter); if (string_iter + next_char_size > string_end) { break; } string_iter += next_char_size; chars_left -= 1; } } FmtU16StringView chopped_string = { .data = string_view->data, .size = string_iter - string_view->data, }; string_view->data = string_iter; string_view->size = string_end - string_iter; return chopped_string; } static FmtU16StringView fmt__u16_sv_from_string(u16 const *string) { FmtU16StringView string_view = { .data = string, .size = fmt__u16_string_size(string), }; return string_view; } static bool fmt__u16_sv_chop_code_point(FmtU16StringView *string_view, FmtCodePoint *code_point) { u16 const *string_iter = string_view->data; u16 const *string_end = string_view->data + string_view->size; if (string_iter == string_end) { return false; } // Skip one u16 piece, if it is invalid. if (!fmt__utf16_is_start(*string_iter)) { *code_point = FMT_REPLACEMENT_CHARACTER_CP; string_iter += 1; string_view->data = string_iter; string_view->size = string_end - string_iter; return true; } // Exit, if the string ends abruptly. isize utf16_size = fmt__utf16_size(*string_iter); if (string_iter + utf16_size > string_end) { return false; } if (utf16_size == 1) { *code_point = (FmtCodePoint) *string_iter; } else if (fmt__utf16_is_low_surrogate(*(string_iter + 1))) { FmtCodePoint high_part = (*string_iter & 0x03ff) << 10; FmtCodePoint low_part = *(string_iter + 1) & 0x03ff; *code_point = (high_part | low_part) + 0x10000; } else { *code_point = FMT_REPLACEMENT_CHARACTER_CP; } string_iter += utf16_size; string_view->data = string_iter; string_view->size = string_end - string_iter; return true; } static isize fmt__u16_sv_utf8_size(FmtU16StringView string) { isize utf8_size = 0; FmtCodePoint next_code_point; while (fmt__u16_sv_chop_code_point(&string, &next_code_point)) { utf8_size += fmt__code_point_utf8_size(next_code_point); } return utf8_size; } static FmtStringView fmt__u16_sv_chop_into_u8_buffer( FmtU16StringView *string_view, u8 *dest, isize dest_size ) { u8 *dest_iter = dest; u8 *dest_end = dest + dest_size; FmtCodePoint next_cp; while (dest_end - dest_iter >= 4 && fmt__u16_sv_chop_code_point(string_view, &next_cp)) { dest_iter += fmt__code_point_to_utf8(next_cp, dest_iter); } FmtStringView result = { .data = dest, .size = dest_iter - dest, }; return result; } // ================================================================================================= // Logging // ================================================================================================= static void fmt__log_error(char const *message) { u8 buffer_data[FMT_BUFFER_SIZE]; FmtConsoleWriter writer_data = { fmt_console_handle(FMT_CONSOLE_STDERR), {buffer_data, 0, FMT_BUFFER_SIZE}, }; FmtWriter writer = {&writer_data, fmt_file_writer_write, fmt_file_writer_flush}; fmt_write_string(&writer, "[ERROR] "); fmt_write_string(&writer, message); fmt_write_char(&writer, '\n'); fmt_flush(&writer); } // ================================================================================================= // Buffer helper // ================================================================================================= static isize fmt_buffer_write(FmtBuffer *buffer, u8 const *data, isize size) { u8 *buffer_begin = buffer->data + buffer->size; u8 *buffer_end = buffer->data + buffer->capacity; isize bytes_to_write = fmt__isize_max(fmt__isize_min(size, buffer_end - buffer_begin), 0); FMT_MEMCPY(buffer_begin, data, bytes_to_write); buffer->size += bytes_to_write; return bytes_to_write; } static bool fmt_buffer_is_full(FmtBuffer *buffer) { return buffer->size == buffer->capacity; } static void fmt_buffer_reset(FmtBuffer *buffer) { buffer->size = 0; } // ================================================================================================= // Generic writer helpers // ================================================================================================= FMT_API void fmt_write(FmtWriter *writer, u8 const *data, isize size) { writer->write(writer->data, data, size); } FMT_API void fmt_flush(FmtWriter *writer) { writer->flush(writer->data); } // ================================================================================================= // Specific writers' implementations // ================================================================================================= FMT_API FmtFile fmt_console_handle(FmtConsoleOutputType console_type) { if (console_type == FMT_CONSOLE_STDOUT) { return FMT_FILE_STDOUT; } else { return FMT_FILE_STDERR; } } #ifdef FMT_FLUSH_AFTER_NEWLINES FMT_API void fmt_file_writer_flush(void *file_writer_void) { FmtFileWriter *file_writer = (FmtFileWriter *) file_writer_void; u8 const *buffer_iter = file_writer->buffer.data; u8 const *buffer_end = file_writer->buffer.data + file_writer->buffer.size; u8 const *line_begin = file_writer->buffer.data; // Write the buffer to the file line by line. while (buffer_iter < buffer_end) { if (*buffer_iter == '\n') { FMT_FILE_WRITE(file_writer->file, line_begin, buffer_iter - line_begin + 1); line_begin = buffer_iter + 1; } buffer_iter += 1; } if (buffer_iter - line_begin == file_writer->buffer.capacity) { // Buffer was not large enough to hold a whole line of text. FMT_FILE_WRITE(file_writer->file, file_writer->buffer.data, file_writer->buffer.size); fmt_buffer_reset(&file_writer->buffer); } else if (line_begin != buffer_end) { // There is an unfinished line at the end of the buffer. FMT_MEMMOVE(file_writer->buffer.data, line_begin, buffer_end - line_begin); file_writer->buffer.size = buffer_end - line_begin; } else { // We exhausted the whole buffer. fmt_buffer_reset(&file_writer->buffer); } } #else FMT_API void fmt_file_writer_flush(void *file_writer_void) { FmtFileWriter *file_writer = (FmtFileWriter *) file_writer_void; FMT_FILE_WRITE(file_writer->file, file_writer->buffer.data, file_writer->buffer.size); fmt_buffer_reset(&file_writer->buffer); } #endif // FMT_FLUSH_AFTER_NEWLINES FMT_API void fmt_file_writer_write(void *file_writer_void, u8 const *data, isize size) { FmtFileWriter *file_writer = (FmtFileWriter *) file_writer_void; u8 const *data_iter = data; u8 const *data_end = data + size; while (data_iter < data_end) { isize bytes_written = fmt_buffer_write(&file_writer->buffer, data_iter, data_end - data_iter); data_iter += bytes_written; if (fmt_buffer_is_full(&file_writer->buffer)) { fmt_file_writer_flush(file_writer); } } #ifdef FMT_FLUSH_AFTER_NEWLINES fmt_file_writer_flush(file_writer); #endif } FMT_API void fmt_string_writer_write(void *string_writer_void, u8 const *data, isize size) { FmtStringWriter *string_writer = (FmtStringWriter *) string_writer_void; fmt_buffer_write(string_writer, data, size); } FMT_API void fmt_string_writer_flush(void *string_writer_void) { UNUSED(string_writer_void); } // ================================================================================================= // Dynamically allocated writers // ================================================================================================= #ifdef FMT_DYN_INTERFACE FMT_API bool fmt_dyn_file_writer(FmtAllocator allocator, FmtFile file, FmtWriter *writer) { FmtFileWriter *writer_data = (FmtFileWriter *) FMT_ALLOC(allocator, sizeof(FmtFileWriter)); if (writer_data == NULL) { return false; } u8 *writer_buffer = (u8 *) FMT_ALLOC(allocator, FMT_BUFFER_SIZE); if (writer_buffer == NULL) { FMT_DEALLOC(allocator, writer_data); return false; } writer_data->file = file; writer_data->buffer.data = writer_buffer; writer_data->buffer.size = 0; writer_data->buffer.capacity = FMT_BUFFER_SIZE; writer->data = writer_data; writer->write = fmt_file_writer_write; writer->flush = fmt_file_writer_flush; return true; } FMT_API void fmt_dyn_file_writer_destroy(FmtAllocator allocator, FmtWriter *writer) { FmtFileWriter *writer_data = (FmtFileWriter *) writer->data; FMT_DEALLOC(allocator, writer_data->buffer.data); FMT_DEALLOC(allocator, writer_data); } FMT_API bool fmt_dyn_console_writer( FmtAllocator allocator, FmtConsoleOutputType console_type, FmtWriter *writer ) { return fmt_dyn_file_writer(allocator, fmt_console_handle(console_type), writer); } FMT_API void fmt_dyn_console_writer_destroy(FmtAllocator allocator, FmtWriter *writer) { fmt_dyn_file_writer_destroy(allocator, writer); } FMT_API bool fmt_dyn_string_writer( FmtAllocator allocator, char *string, isize capacity, FmtWriter *writer ) { FmtStringWriter *string_writer = (FmtStringWriter *) FMT_ALLOC(allocator, sizeof(FmtStringWriter)); if (string_writer == NULL) { return false; } string_writer->data = (u8 *) string; string_writer->capacity = capacity; writer->data = string_writer; writer->write = fmt_string_writer_write; writer->flush = fmt_string_writer_flush; return true; } FMT_API void fmt_dyn_string_writer_destroy(FmtAllocator allocator, FmtWriter *writer) { FMT_DEALLOC(allocator, writer->data); } #endif // FMT_DYN_INTERFACE // ================================================================================================= // Functions' implementations for writing primitive values // ================================================================================================= FMT_API isize fmt_write_char(FmtWriter *writer, char value) { isize data_size = sizeof(char); if (writer != NULL) { fmt_write(writer, (u8 const *) &value, data_size); } return data_size; } FMT_API isize fmt_write_char_repeat(FmtWriter *writer, isize repeat_count, char value) { isize data_size = repeat_count * sizeof(char); if (writer != NULL) { u8 buffer[512]; for (isize i = 0; i < fmt__isize_min(repeat_count, sizeof(buffer)); i += 1) { buffer[i] = value; } isize bytes_left = data_size; while (bytes_left > 0) { fmt_write(writer, buffer, fmt__isize_min(bytes_left, sizeof(buffer))); bytes_left -= sizeof(buffer); } } return data_size; } FMT_API isize fmt_write_code_point(FmtWriter *writer, FmtCodePoint code_point) { u8 data[4]; isize data_size = fmt__code_point_to_utf8(code_point, data); if (writer != NULL) { fmt_write(writer, data, data_size); } return data_size; } FMT_API isize fmt_write_sv(FmtWriter *writer, u8 const *data, isize size) { if (writer != NULL) { fmt_write(writer, (u8 const *) data, size); } return size; } FMT_API isize fmt_write_string(FmtWriter *writer, char const *string) { isize string_size = fmt__string_size(string); return fmt_write_sv(writer, (u8 const *) string, string_size); } FMT_API isize fmt_write_u16_sv(FmtWriter *writer, u16 const *data, isize size) { FmtU16StringView u16_string_view = { .data = data, .size = size, }; if (writer == NULL) { return fmt__u16_sv_utf8_size(u16_string_view); } u8 utf8_buffer[512]; isize bytes_written = 0; while (u16_string_view.size > 0) { FmtStringView string_view = fmt__u16_sv_chop_into_u8_buffer(&u16_string_view, utf8_buffer, sizeof(utf8_buffer)); bytes_written += fmt_write_sv(writer, string_view.data, string_view.size); } return bytes_written; } FMT_API isize fmt_write_u16_string(FmtWriter *writer, u16 const *string) { isize string_size = fmt__u16_string_size(string); return fmt_write_u16_sv(writer, string, string_size); } FMT_API isize fmt_write_bool(FmtWriter *writer, bool value) { if (value) { return fmt_write_string(writer, "true"); } else { return fmt_write_string(writer, "false"); } } FMT_API isize fmt_write_int(FmtWriter *writer, FmtIntValue const *value, FmtIntParams const *params) { if (params->base < 2 || params->base > 16) { fmt__log_error("Invalid integer base."); return 0; } u8 alphabet_lowercase[] = "0123456789abcdef", alphabet_uppercase[] = "0123456789ABCDEF"; u8 *alphabet = alphabet_lowercase; if (params->make_uppercase) { alphabet = alphabet_uppercase; } u64 unsigned_value = value->unsigned_value; bool print_as_negative = value->is_negative; if (params->base != 10 && print_as_negative) { unsigned_value = ~unsigned_value + 1; if (value->size < sizeof(u64)) { u64 smaller_int_mask = (((u64) 1) << (value->size * 8)) - 1; unsigned_value = unsigned_value & smaller_int_mask; } print_as_negative = false; } u8 string_buffer[sizeof(u64) * 8]; u8 *string_begin = string_buffer + sizeof(string_buffer); u8 *string_end = string_buffer + sizeof(string_buffer); if (unsigned_value == 0) { string_begin -= 1; *string_begin = alphabet[0]; } else { while (unsigned_value > 0) { string_begin -= 1; *string_begin = alphabet[unsigned_value % params->base]; unsigned_value /= params->base; } } isize bytes_written = 0; if (print_as_negative) { bytes_written += fmt_write_char(writer, '-'); } else if (params->include_plus && value->unsigned_value != 0) { bytes_written += fmt_write_char(writer, '+'); } bytes_written += fmt_write_char_repeat( writer, fmt__isize_max(0, params->precision - (string_end - string_begin)), '0' ); bytes_written += fmt_write_sv(writer, string_begin, string_end - string_begin); return bytes_written; } static isize fmt__write_int_arg(FmtWriter *writer, FormatArgInt const *arg) { FmtIntParams int_params = { .base = arg->bin ? (u64) 2 : arg->octal ? (u64) 8 : arg->hex ? (u64) 16 : (u64) 10, .make_uppercase = arg->uppercase, .include_plus = arg->plus, .precision = arg->precision, }; FmtIntValue int_value = { .unsigned_value = arg->value, .size = arg->size, .is_negative = arg->is_negative, }; return fmt_write_int(writer, &int_value, &int_params); } FMT_API isize fmt_left_padding_amount(isize content_width, isize padding, FormatAlignment alignment) { isize padding_amount = 0; if (alignment == FMT_ALIGN_RIGHT) { padding_amount = fmt__isize_max(padding - content_width, 0); } else if (alignment == FMT_ALIGN_CENTER) { isize total_padding_amount = fmt__isize_max(padding - content_width, 0); padding_amount = total_padding_amount / 2; } return padding_amount; } FMT_API isize fmt_right_padding_amount(isize content_width, isize padding, FormatAlignment alignment) { isize padding_amount = 0; if (alignment == FMT_ALIGN_LEFT) { padding_amount = fmt__isize_max(padding - content_width, 0); } else if (alignment == FMT_ALIGN_CENTER) { isize total_padding_amount = fmt__isize_max(padding - content_width, 0); padding_amount = total_padding_amount - total_padding_amount / 2; } return padding_amount; } // ================================================================================================= // Utility writer implementation for counting chars in formatted values // ================================================================================================= typedef struct { isize chars_count; } FmtCharsCounter; static void fmt__chars_counter_write(void *writer_data_void, u8 const *data, isize size) { u8 const *data_end = data + size; while (data < data_end && !fmt__utf8_is_start(*data)) { data += 1; } FmtCharsCounter *writer_data = (FmtCharsCounter *) writer_data_void; FmtStringView string_view = {data, data_end - data}; writer_data->chars_count += fmt__sv_chars_count(string_view); } static void fmt__chars_counter_flush(void *writer_data_void) { UNUSED(writer_data_void); } static isize fmt__arg_content_width(FormatArg const *fmt_arg) { FmtCharsCounter writer_data = { .chars_count = 0, }; FmtWriter writer = { .data = &writer_data, .write = fmt__chars_counter_write, .flush = fmt__chars_counter_flush, }; switch (fmt_arg->kind) { case FORMAT_ARG_INT: { FormatArgInt arg = fmt_arg->as.int_arg; fmt__write_int_arg(&writer, &arg); } break; case FORMAT_ARG_BOOL: { FormatArgBool arg = fmt_arg->as.bool_arg; if (arg.value) { return sizeof("true") - 1; } else { return sizeof("false") - 1; } } break; case FORMAT_ARG_CHAR: case FORMAT_ARG_CODE_POINT: { return 1; } break; case FORMAT_ARG_STRING: { FormatArgString arg = fmt_arg->as.string_arg; FmtStringView string_view = fmt__sv_from_string(arg.value); isize chars_count = fmt__sv_chars_count(string_view); if (arg.max_width == 0) { return chars_count; } else { return fmt__isize_min(chars_count, arg.max_width); } } break; case FORMAT_ARG_U16_STRING: { FormatArgU16String arg = fmt_arg->as.u16_string_arg; FmtU16StringView string_view = fmt__u16_sv_from_string(arg.value); isize chars_count = fmt__u16_sv_chars_count(string_view); if (arg.max_width == 0) { return chars_count; } else { return fmt__isize_min(chars_count, arg.max_width); } } break; case FORMAT_ARG_STRING_VIEW: { FormatArgStringView arg = fmt_arg->as.string_view_arg; FmtStringView string_view = {(u8 const *) arg.value, arg.size}; isize chars_count = fmt__sv_chars_count(string_view); if (arg.max_width == 0) { return chars_count; } else { return fmt__isize_min(chars_count, arg.max_width); } } break; case FORMAT_ARG_U16_STRING_VIEW: { FormatArgU16StringView arg = fmt_arg->as.u16_string_view_arg; FmtU16StringView string_view = {arg.value, arg.size}; isize chars_count = fmt__u16_sv_chars_count(string_view); if (arg.max_width == 0) { return chars_count; } else { return fmt__isize_min(chars_count, arg.max_width); } } break; case FORMAT_ARG_POINTER: { FormatArgPointer arg = fmt_arg->as.pointer_arg; arg.fmt(&writer, arg.value); } break; } return writer_data.chars_count; } // ================================================================================================= // Format arguments writer function // ================================================================================================= FMT_API isize fmt_write_arg(FmtWriter *writer, FormatArg const *format_arg) { isize bytes_written = 0; // All of the structs within the FormatArg union share a common sequence of padding parameters // in the begining, so it seems like this cast is not even a UB? // // https://stackoverflow.com/a/20752261 // > One special guarantee is made in order to simplify the use of unions: if a union contains // > several structures that share a common initial sequence (see below), and if the union // > object currently contains one of these structures, it is permitted to inspect the common // > initial part of any of them anywhere that a declaration of the completed type of the union // > is visible. Two structures share a common initial sequence if corresponding members have // > compatible types (and, for bit-fields, the same widths) for a sequence of one or more // > initial members. FormatArgPaddingParams padding_params = format_arg->as.padding_params; char padder = padding_params.padder == 0 ? ' ' : padding_params.padder; isize content_width = fmt__arg_content_width(format_arg); isize left_padding = fmt_left_padding_amount(content_width, padding_params.pad, padding_params.align); bytes_written += fmt_write_char_repeat(writer, left_padding, padder); switch (format_arg->kind) { case FORMAT_ARG_INT: { bytes_written += fmt__write_int_arg(writer, &format_arg->as.int_arg); } break; case FORMAT_ARG_BOOL: { bytes_written += fmt_write_bool(writer, format_arg->as.bool_arg.value); } break; case FORMAT_ARG_CHAR: { bytes_written += fmt_write_char(writer, format_arg->as.char_arg.value); } break; case FORMAT_ARG_CODE_POINT: { bytes_written += fmt_write_code_point(writer, format_arg->as.code_point_arg.value); } break; case FORMAT_ARG_STRING: { FormatArgString arg = format_arg->as.string_arg; FmtStringView string_view = fmt__sv_from_string(arg.value); if (arg.max_width != 0) { string_view = fmt__sv_chop_chars(&string_view, arg.max_width); } bytes_written += fmt_write_sv(writer, string_view.data, string_view.size); } break; case FORMAT_ARG_U16_STRING: { FormatArgU16String arg = format_arg->as.u16_string_arg; FmtU16StringView string_view = fmt__u16_sv_from_string(arg.value); if (arg.max_width != 0) { string_view = fmt__u16_sv_chop_chars(&string_view, arg.max_width); } bytes_written += fmt_write_u16_sv(writer, string_view.data, string_view.size); } break; case FORMAT_ARG_STRING_VIEW: { FormatArgStringView arg = format_arg->as.string_view_arg; FmtStringView string_view = {(u8 const *) arg.value, arg.size}; if (arg.max_width != 0) { string_view = fmt__sv_chop_chars(&string_view, arg.max_width); } bytes_written += fmt_write_sv(writer, string_view.data, string_view.size); } break; case FORMAT_ARG_U16_STRING_VIEW: { FormatArgU16StringView arg = format_arg->as.u16_string_view_arg; FmtU16StringView string_view = {arg.value, arg.size}; if (arg.max_width != 0) { string_view = fmt__u16_sv_chop_chars(&string_view, arg.max_width); } bytes_written += fmt_write_u16_sv(writer, string_view.data, string_view.size); } break; case FORMAT_ARG_POINTER: { FormatArgPointer arg = format_arg->as.pointer_arg; bytes_written += arg.fmt(writer, arg.value); } break; } isize right_padding = fmt_right_padding_amount(content_width, padding_params.pad, padding_params.align); bytes_written += fmt_write_char_repeat(writer, right_padding, padder); return bytes_written; } static FormatArg const *fmt__format_args_get_by_name( FormatArg const *haystack, isize haystack_size, FmtStringView needle ) { FormatArg const *haystack_iter = haystack; while (haystack_iter < haystack + haystack_size) { if (fmt__sv_equals(fmt__sv_from_string(haystack_iter->name), needle)) { return haystack_iter; } haystack_iter += 1; } return NULL; } FMT_API isize fmt_write_args( FmtWriter *writer, char const *format, FormatArg const *format_args, isize format_args_count ) { isize bytes_written = 0; FormatArg const *format_args_iter = format_args; FormatArg const *format_args_end = format_args + format_args_count; char const *format_iter = format; char const *format_begin = format; while (*format_iter != '\0') { if (*format_iter == '{' && *(format_iter + 1) == '\0') { fmt__log_error("An unpaired '{' encountered in the format string."); return bytes_written; } if (*format_iter == '}' && *(format_iter + 1) != '}') { fmt__log_error("An unescaped '}' encountered in the format string."); return bytes_written; } if (*format_iter == '{' && *(format_iter + 1) == '{' || *format_iter == '}' && *(format_iter + 1) == '}') { bytes_written += fmt_write_sv(writer, (u8 const *) format_begin, format_iter - format_begin); bytes_written += fmt_write_char(writer, *format_iter); format_iter += 2; format_begin = format_iter; continue; } if (*format_iter == '{') { bytes_written += fmt_write_sv(writer, (u8 const *) format_begin, format_iter - format_begin); format_iter += 1; FmtStringView arg_name; { char const *arg_name_begin = format_iter; while (*format_iter != '\0' && *format_iter != '}') { format_iter += 1; } if (*format_iter == '\0') { fmt__log_error("An unpaired '{' encountered in the format string."); return bytes_written; } arg_name.data = (u8 const *) arg_name_begin; arg_name.size = format_iter - arg_name_begin; // Skip the closing curly bracket format_iter += 1; format_begin = format_iter; } FormatArg const *current_format_arg; if (arg_name.size == 0) { while (format_args_iter < format_args_end) { if (format_args_iter->name == NULL) { break; } else { format_args_iter += 1; } } if (format_args_iter == format_args_end) { fmt__log_error("Not enough format arguments."); return 0; } current_format_arg = format_args_iter; format_args_iter += 1; } else { current_format_arg = fmt__format_args_get_by_name(format_args, format_args_count, arg_name); if (current_format_arg == NULL) { fmt__log_error("Format arg is not found by name."); return 0; } } bytes_written += fmt_write_arg(writer, current_format_arg); continue; } format_iter += 1; } if (format_begin < format_iter) { bytes_written += fmt_write_sv(writer, (u8 const *) format_begin, format_iter - format_begin); } return bytes_written; } FMT_API isize fmt_console_write_args( FmtConsoleOutputType console_type, char const *format, FormatArg const *format_args, isize format_args_count ) { u8 buffer_data[FMT_BUFFER_SIZE]; FmtConsoleWriter writer_data = { fmt_console_handle(console_type), {buffer_data, 0, FMT_BUFFER_SIZE}, }; FmtWriter writer = {&writer_data, fmt_file_writer_write, fmt_file_writer_flush}; isize bytes_written = fmt_write_args(&writer, format, format_args, format_args_count); fmt_flush(&writer); return bytes_written; } FMT_API isize fmt_string_write_args( char *string, isize capacity, char const *format, FormatArg const *format_args, isize format_args_count ) { if (string == NULL) { return fmt_write_args(NULL, format, format_args, format_args_count); } FmtStringWriter writer_data = {(u8 *) string, 0, capacity}; FmtWriter writer = {&writer_data, fmt_string_writer_write, fmt_string_writer_flush}; isize bytes_written = fmt_write_args(&writer, format, format_args, format_args_count); fmt_flush(&writer); return bytes_written; } FMT_API isize fmt_file_write_args( FmtFile file, const char *format, const FormatArg *format_args, isize format_args_count ) { u8 buffer_data[FMT_BUFFER_SIZE]; FmtConsoleWriter writer_data = {file, {buffer_data, 0, FMT_BUFFER_SIZE}}; FmtWriter writer = {&writer_data, fmt_file_writer_write, fmt_file_writer_flush}; isize bytes_written = fmt_write_args(&writer, format, format_args, format_args_count); fmt_flush(&writer); return bytes_written; } #ifdef __cplusplus #undef restrict #endif #endif // FMT_IMPLEMENTATION
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