Skip to content

Add a BPF (bpfel) compilation target - #555

Merged
godofecht merged 19 commits into
mainfrom
codex/bpfel-target
Aug 18, 2026
Merged

godofecht merged 19 commits into
mainfrom
codex/bpfel-target

Conversation

@godofecht

@godofecht godofecht commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Opening a pull request for work that was sitting on codex/bpfel-target with no PR. 17 commits, last touched about a day ago. I did not write it; I found it while pruning branches and it looked finished rather than abandoned, so it deserves a decision rather than quietly rotting or being deleted.

What it adds

Five new files, no modifications to anything existing:

src/flow/bpf_target.py                314 ++++
.github/workflows/bpf.yml             116 ++++
tests/unit/test_bpf_target.py          96 ++++
tests/fixtures/bpf/answer.flow          3 ++
tests/fixtures/bpf/socket_filter.flow   3 ++

State against current main

Merged main in; it merged cleanly with zero conflicts, and everything passes:

  • Unit suite 1509 passed, 2 skipped, including its own 13 tests
  • check_doc_coverage.py clean
  • check_doc_examples.py --check-ledger clean
  • ruff check src clean

Two things a reviewer should weigh

It has no documentation. A new compilation target is exactly the kind of thing the coverage checker added in #543 is supposed to require a page for. It does not catch this one, because backend discovery matches *_generator.py, *_codegen.py and *_compiler.py, and this is bpf_target.py. I am widening that separately, which will make this PR ask for a doc home. That is the gate working rather than an objection to the change.

I cannot judge whether the target is wanted. The sibling branch codex/base-kernel had its PR (#537) closed, so there may be a reason this family of work was not pursued. If this one is not wanted either, close it and I will delete the branch.

The bpfel workflow had failed on all eight runs since the branch's first
commit, always with the same error:

    error: exported BPF entry 'socket_filter' not found as
    flow_export_socket_filter

flow_export_<name> is a C-backend alias. c_generator emits it for --export;
the MLIR path does not. Since bpf_target lowers with --llvm, the definition
in the IR carries the plain Flow name, so the search could never match.

The entry is now looked up under the explicit alias first and the plain name
second, and one symbol is chosen before any rewriting. That ordering matters:
the C backend emits both the mangled definition and the alias, so matching
both would report a duplicate entry, which is how the first version of this
fix broke an existing test.

Verified on real IR from the MLIR path: the definition is decorated with
`section "socket"` and the license constant is appended. Object emission
cannot be checked on macOS, where clang has no BPF target, so CI is the
proof for that half.

Unit 1509 passed, ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@godofecht

Copy link
Copy Markdown
Contributor Author

Correcting my description above: I said this branch "looked finished rather than abandoned". It was not finished. Its own bpfel workflow has failed on every run since the branch's first commit — eight for eight, all before I merged main in. I checked the unit suite and my own doc checkers and called it green, without noticing the branch ships a CI workflow I had not run.

I have since found and fixed why.

The bug

error: exported BPF entry 'socket_filter' not found as flow_export_socket_filter

flow_export_<name> is a C-backend alias. c_generator.py emits it for --export:

__attribute__((visibility("default"))) int32_t flow_export_socket_filter(void* context)
    { return socket_filter_ptr_void(context); }

The MLIR path emits no such alias:

define i32 @socket_filter(ptr %0) {

bpf_target.py lowers with --llvm, so it was searching LLVM IR for a symbol only the C backend produces. The search could never match, which is why every run failed identically and why the eleven commits before it are all CI-plumbing attempts.

The fix

The entry is looked up under the explicit alias first and the plain name second, and one symbol is settled on before any rewriting happens. That ordering is load-bearing: the C backend emits both the mangled definition and the alias, so matching both reports a duplicate entry. My first attempt did exactly that and broke an existing test, which is how I found it.

Verified against real IR from the MLIR path:

define i32 @socket_filter(ptr %0) section "socket" {
@_flow_bpf_license = dso_local constant [4 x i8] c"GPL\00", section "license", align 1

Object emission I cannot check locally: macOS clang has no BPF target (No available targets are compatible with triple "bpfel"), the same limitation as the wasm32 work. CI is the proof for that half, so this is worth watching rather than assuming.

Unit suite 1509 passed, ruff check src clean.

@godofecht
godofecht merged commit d21c9b5 into main Aug 18, 2026
20 checks passed
@godofecht
godofecht deleted the codex/bpfel-target branch August 18, 2026 17:53
godofecht added a commit that referenced this pull request Aug 18, 2026
Merging #555 gave the coverage checker a tenth backend, and with _target in
the suffix list it did what it was built to do:

    backend:bpf_target has no documented home

So here is the page. It covers the CLI, the fixed ABI (bpfel triple, 64-bit
pointers, the verifier's 512-byte stack limit), the section and license
metadata the kernel reads, and the symbols the target refuses because a BPF
program has no userspace runtime.

It also records the entry-point naming rule and why it exists: the target
lowers with --llvm, so the definition carries the plain Flow name, while
flow_export_<name> is a C-backend alias. Looking only for the alias is what
made every build on that branch fail.

The toolchain caveat is the same as wasm32: Apple's clang has no BPF backend,
so this is a Linux-only build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
godofecht added a commit that referenced this pull request Aug 18, 2026
…caught (#556)

* ci: let the coverage gate see every backend naming convention

Backend discovery matched *_generator.py, *_codegen.py and *_compiler.py.
A BPF target sitting unreviewed on codex/bpfel-target is named
bpf_target.py, so a whole new compilation target would have merged without
the coverage gate asking for a page.

Added _target and _backend. The nine current backends are unaffected; the
point is the next one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: document the eBPF target, which the widened gate now requires

Merging #555 gave the coverage checker a tenth backend, and with _target in
the suffix list it did what it was built to do:

    backend:bpf_target has no documented home

So here is the page. It covers the CLI, the fixed ABI (bpfel triple, 64-bit
pointers, the verifier's 512-byte stack limit), the section and license
metadata the kernel reads, and the symbols the target refuses because a BPF
program has no userspace runtime.

It also records the entry-point naming rule and why it exists: the target
lowers with --llvm, so the definition carries the plain Flow name, while
flow_export_<name> is a C-backend alias. Looking only for the alias is what
made every build on that branch fail.

The toolchain caveat is the same as wasm32: Apple's clang has no BPF backend,
so this is a Linux-only build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant