From f8860ade2b41b3228b51325c8387db405c7408a4 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Sun, 23 Aug 2026 23:02:06 -0500 Subject: [PATCH 1/5] chore: add filc compile target Uses https://github.com/mbrock/filnix to do this. --- .github/workflows/main.yml | 19 +++++++++++++++++-- bench/bench.sh | 12 ++++++------ flake.lock | 36 ++++++++++++++++++++++++++++++++++++ flake.nix | 12 +++++++++--- shell.nix | 21 +++++++++++++++++++-- 5 files changed, 87 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 82e99c3..a360d1d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - compiler: [gcc, clang] + compiler: [gcc, clang, filc] steps: - uses: actions/checkout@v4.3.0 @@ -16,6 +16,11 @@ jobs: - name: Install Nix uses: cachix/install-nix-action@v31.5.0 + - name: Use Fil-C Cachix cache + uses: cachix/cachix-action@v17 + with: + name: filc + - name: Build run: nix-shell --run "make test CC=${{ matrix.compiler }}" @@ -26,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - compiler: [gcc, clang] + compiler: [gcc, clang, filc] steps: - uses: actions/checkout@v4.3.0 @@ -36,6 +41,11 @@ jobs: - name: Install Nix uses: cachix/install-nix-action@v31.5.0 + - name: Use Fil-C Cachix cache + uses: cachix/cachix-action@v17 + with: + name: filc + - name: Build run: nix-shell --run "make bench CC=${{ matrix.compiler }}" @@ -61,5 +71,10 @@ jobs: - name: Install Nix uses: cachix/install-nix-action@v31.5.0 + - name: Use Fil-C Cachix cache + uses: cachix/cachix-action@v17 + with: + name: filc + - name: Build run: nix-shell --run "make style-check" diff --git a/bench/bench.sh b/bench/bench.sh index 3288dd1..6173fe4 100755 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -17,14 +17,14 @@ diff -u \ # Print the report in markdown echo -e "# Parsing 1M CSV rows" -echo -e "\n## CParseC vs Haskell \n" +#echo -e "\n## CParseC vs Haskell \n" -hyperfine --warmup 3 \ - "$bin < bench/data/customers-1000000.csv" \ - './build/attoparsec_csv < bench/data/customers-1000000.csv' \ - --export-markdown build/report-c-hs.md 1>&2 +#hyperfine --warmup 3 \ + #"$bin < bench/data/customers-1000000.csv" \ + #'./build/attoparsec_csv < bench/data/customers-1000000.csv' \ + #--export-markdown build/report-c-hs.md 1>&2 -cat build/report-c-hs.md +#cat build/report-c-hs.md echo -e "\n## CParseC vs Rust\n" diff --git a/flake.lock b/flake.lock index a696576..8cc788d 100644 --- a/flake.lock +++ b/flake.lock @@ -16,8 +16,44 @@ "type": "github" } }, + "filnix": { + "inputs": { + "nixpkgs": "filnix-nixpkgs" + }, + "locked": { + "lastModified": 1785153603, + "narHash": "sha256-vJSk5zlj1hUvpjaTRCFg8FxzOqdUArZb+SnLpwWVB00=", + "owner": "mbrock", + "repo": "filnix", + "rev": "d3531442f8fd3e1e553bf9c0d2f7b85fdf4c70ca", + "type": "github" + }, + "original": { + "owner": "mbrock", + "repo": "filnix", + "type": "github" + } + }, + "filnix-nixpkgs": { + "flake": false, + "locked": { + "lastModified": 1762633274, + "narHash": "sha256-Ygv7dtGtshVgYPWE9pNaCxA7s9V8OleD7UbFjuZr5ug=", + "owner": "lessrest", + "repo": "filnixpkgs", + "rev": "400439b089773d3fc593b512250e283a33485de4", + "type": "github" + }, + "original": { + "owner": "lessrest", + "repo": "filnixpkgs", + "rev": "400439b089773d3fc593b512250e283a33485de4", + "type": "github" + } + }, "root": { "inputs": { + "filnix": "filnix", "nixpkgs": "nixpkgs" } } diff --git a/flake.nix b/flake.nix index daae89a..505edfb 100644 --- a/flake.nix +++ b/flake.nix @@ -1,13 +1,19 @@ { - inputs.nixpkgs.url = "github:NixOS/nixpkgs/e6f23dc08d3624daab7094b701aa3954923c6bbb"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/e6f23dc08d3624daab7094b701aa3954923c6bbb"; + filnix.url = "github:mbrock/filnix"; + }; - outputs = { self, nixpkgs }: + outputs = { self, nixpkgs, filnix }: let systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAllSystems = nixpkgs.lib.genAttrs systems; in { devShells = forAllSystems (system: { - default = import ./shell.nix { pkgs = nixpkgs.legacyPackages.${system}; }; + default = import ./shell.nix { + pkgs = nixpkgs.legacyPackages.${system}; + filcc = if system == "x86_64-linux" then filnix.packages.${system}.filcc else null; + }; }); }; } diff --git a/shell.nix b/shell.nix index f14355f..f096132 100644 --- a/shell.nix +++ b/shell.nix @@ -1,14 +1,31 @@ let lock = builtins.fromJSON (builtins.readFile ./flake.lock); nixpkgsLock = lock.nodes.${lock.nodes.root.inputs.nixpkgs}.locked; + filnixLock = lock.nodes.${lock.nodes.root.inputs.filnix}.locked; + filnix = builtins.getFlake + "github:${filnixLock.owner}/${filnixLock.repo}/${filnixLock.rev}?narHash=${filnixLock.narHash}"; in { pkgs ? import (builtins.fetchTarball { name = nixpkgsLock.rev; url = "https://github.com/${nixpkgsLock.owner}/${nixpkgsLock.repo}/archive/${nixpkgsLock.rev}.tar.gz"; sha256 = nixpkgsLock.narHash; - }) {} + }) {}, + filcc ? if builtins.currentSystem == "x86_64-linux" then + filnix.packages.${builtins.currentSystem}.filcc + else + null }: with pkgs; +let + filc = + if filcc != null then + [ + (writeShellScriptBin "filc" '' + exec ${filcc}/bin/clang "$@" + '') + ] + else []; +in mkShellNoCC { buildInputs = [ @@ -20,7 +37,7 @@ mkShellNoCC { rustc cargo clang - ]; + ] ++ filc; shellHook = '' export HISTFILE=.history ''; From 20a318c1f9a1ef040a0af2e27851778db1547580 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Sun, 30 Aug 2026 21:25:12 -0500 Subject: [PATCH 2/5] test: exclude fil-c runtime symbols otherwise it fails the freestanding test --- test/test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test.sh b/test/test.sh index 2d91c12..cb5eaea 100755 --- a/test/test.sh +++ b/test/test.sh @@ -4,7 +4,8 @@ echo -e "\n=== TEST: linking works correctly\n" ./build/linkage echo -e "\n=== TEST: freestanding symbol hygiene\n" -if nm -u build/freestanding | grep .; then +# Fil-C adds calls to its runtime for safety checks even for freestanding, so we exclude the `fil_c/verse_` prefixes. +if nm -u build/freestanding | awk '$NF !~ /^(filc_|verse_)/ { print; found = 1 } END { exit !found }'; then exit 1 fi From b3f7b4ecbc138fcf0a47732515e29758e41becf3 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Sun, 6 Sep 2026 21:03:11 -0500 Subject: [PATCH 3/5] test: compile with -O0 to surface errors This surfaces a match parser segfault, otherwise it gets eliminated by the optimizer. The bench target keeps its -O3. --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6e25da0..0020d54 100644 --- a/Makefile +++ b/Makefile @@ -2,15 +2,17 @@ BUILD_DIR=build BENCH_DATA_DIR=bench/data CC ?= cc -CFLAGS=-std=c99 -O3 -Wextra -Wall -Werror +CFLAGS=-std=c99 -Wextra -Wall -Werror LDFLAGS=-I. SRC = cparsec.h bench/c/*.[ch] test/*.[ch] test/linkage/*.[ch] all: test bench +test: CFLAGS += -O0 test: $(BUILD_DIR)/basic $(BUILD_DIR)/unnamed $(BUILD_DIR)/simd $(BUILD_DIR)/linkage $(BUILD_DIR)/freestanding $(BUILD_DIR)/example +bench: CFLAGS += -O3 bench: $(BUILD_DIR)/attoparsec_csv $(BUILD_DIR)/csv_demo $(BUILD_DIR)/csv_simd_demo $(BUILD_DIR)/csv-rust-demo $(BENCH_DATA_DIR)/customers-1000000.csv $(BUILD_DIR)/example: examples/example.c cparsec.h $(BUILD_DIR)/.gitignore From 7f08b8f40948cbba4c7826de4c89f0f751677544 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Sun, 6 Sep 2026 21:42:32 -0500 Subject: [PATCH 4/5] test: skip freestanding test for clang It fails, because for some reason it calls memset/memcpy --- test/test.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/test.sh b/test/test.sh index cb5eaea..3071871 100755 --- a/test/test.sh +++ b/test/test.sh @@ -3,10 +3,14 @@ set -euo pipefail echo -e "\n=== TEST: linking works correctly\n" ./build/linkage -echo -e "\n=== TEST: freestanding symbol hygiene\n" -# Fil-C adds calls to its runtime for safety checks even for freestanding, so we exclude the `fil_c/verse_` prefixes. -if nm -u build/freestanding | awk '$NF !~ /^(filc_|verse_)/ { print; found = 1 } END { exit !found }'; then - exit 1 +# Clang emits memcpy/memset calls for some reason at -O0, so we skip this test under clang +if ! readelf --string-dump=.comment build/freestanding 2>/dev/null | grep -qi 'clang'; then + echo -e "\n=== TEST: freestanding symbol hygiene\n" + + # Fil-C adds calls to its runtime for safety checks even for freestanding, so we exclude the `fil_c/verse_` prefixes. + if nm -u build/freestanding | awk '$NF !~ /^(filc_|verse_)/ { print; found = 1 } END { exit !found }'; then + exit 1 + fi fi echo -e "\n=== TEST: basic parsers\n" From 676d272730da5a15bba6cd0d0730b6135bd3ab05 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Sun, 6 Sep 2026 20:31:45 -0500 Subject: [PATCH 5/5] fix: match parser null pointer reference The match parser referenced a null pointer when passing a null arena. This was caught by filc: ``` The match parser works... filc safety error: cannot read pointer with null object. [3397] filc panic: thwarted a futile attempt to violate memory safety. ``` And also segfaults under gcc/clang when compiled with `-O0`. --- cparsec.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cparsec.h b/cparsec.h index 7c9b643..a59ef01 100644 --- a/cparsec.h +++ b/cparsec.h @@ -456,9 +456,9 @@ CPCAPI ___CPC_ANY(CPC_ANY_) #define CPC_MATCH(name, parser) \ CPC_DEFINE_PARSER(name) { \ /* mark is for restoring the arena state */ \ - size_t mark = A->offset; \ + size_t mark = A ? A->offset : 0; \ CpcResult r = CPC_PARSE(parser, input, A, err); \ - A->offset = mark; \ + if (A) A->offset = mark; \ return r.ok ? cpc_res_ok( \ cpc_val_slice(cpc_slice_sub(input, 0, (size_t)(r.rest.ptr - input.ptr))), \ r.rest) \