BPF-to-BPF Function Calls

A BPF-to-BPF call (also called a bpf2bpf call or a call into a subprogram) is an ordinary function call from one part of an eBPF program into another part of the same program — the kernel equivalent of one C function calling another C function in the same translation unit. Before this feature, an eBPF program was a single flat blob: every helper a C compiler would normally emit as a separate function had to be force-inlined, which bloated programs and burned the verifier’s instruction budget. Linux 4.16 (2018) added genuine call/return support so the JIT emits a real native call and ret, each callee gets its own fresh 512-byte-bounded stack frame, and the verifier proves each subprogram safe (commit cc8b0b92a169; ebpf.io functions). The cost is bounded by two hard limits the verifier enforces statically: a call chain may be at most 8 frames deep (MAX_CALL_FRAMES), and the summed stack usage along the deepest chain may not exceed 512 bytes (MAX_BPF_STACK) — both verified below against the v6.12 source. Unlike a tail call, a bpf2bpf call returns: control comes back to the caller with R0 holding the return value.

This note covers the function call within a program. The sibling control-flow primitive — jumping to an entirely separate program with no return — is BPF Tail Calls; the two share a combined call-depth budget and interact in subtle ways covered at the end. The register and stack conventions a call must respect are detailed in The BPF Calling Convention and Stack; the static analysis that proves each subprogram safe is eBPF Verifier.

Why Subprograms Exist — The Inlining Problem

eBPF began with no notion of a function call between user-written pieces of code. The only calls a program could make were to a fixed set of kernel helper functions through a numbered ABI. If you wrote a helper of your own in BPF C — say a parse_ethernet() routine called from three places — the Clang/LLVM BPF backend had no choice but to inline every call site, because the BPF instruction set and verifier of the day could not represent a call to a BPF address. Three call sites meant three full copies of parse_ethernet() baked into the bytecode.

This hurt in two compounding ways. First, program size: the BPF program can be at most one million instructions after the verifier expands it (BPF_COMPLEXITY_LIMIT_INSNS, 1000000, include/linux/bpf.h:1928), and inlining multiplies code. Second, and worse, verifier complexity: the verifier explores every reachable path, so an inlined function is re-analyzed at every call site, multiplying the state-space the verifier must walk and pushing programs into the dreaded “BPF program is too large” / complexity-limit wall (see Verifier Complexity Limits and State Pruning). Real programs — Cilium’s datapath, large XDP pipelines — routinely hit this. BPF-to-BPF calls were the structural fix: emit one copy of the function, call it.

flowchart LR
  subgraph BEFORE["Before 4.16 — everything inlined"]
    M1["main()"] --> I1["parse() copy 1"]
    M1 --> I2["parse() copy 2"]
    M1 --> I3["parse() copy 3"]
  end
  subgraph AFTER["4.16+ — real call/ret"]
    M2["main()"] -->|call| P["parse()<br/>(one copy)"]
    P -->|ret R0| M2
  end

Inlining versus a real call. What it shows: before 4.16 the only way to reuse a BPF C function was to duplicate its body at every call site (left); from 4.16 the JIT emits a single copy and a native call/ret (right). The insight: the win is not just smaller bytecode — it is dramatically less work for the verifier, because one shared subprogram is analyzed once (since 5.6, see below) instead of once per inlined copy. Subprograms trade a tiny per-call runtime cost (a call/ret pair) for a large reduction in verification cost and code size.

The Two Hard Limits — Verified Against v6.12 Source

Two constants bound every call chain, and the verifier proves them statically, before the program can ever run. Both were read directly from the v6.12 tree.

MAX_CALL_FRAMES = 8 — the maximum call depth. Defined in include/linux/bpf_verifier.h:350:

#define MAX_CALL_FRAMES 8

The verifier’s per-frame bookkeeping is sized to this: struct bpf_func_state *frame[MAX_CALL_FRAMES] holds the live frame stack, and the value is small enough that the frame number is packed into the low three bits of an instruction-flags field — the source asserts static_assert(INSN_F_FRAMENO_MASK + 1 >= MAX_CALL_FRAMES) because 3 bits encode 0–7, exactly eight frames. So the limit is not arbitrary: it is wired into the verifier’s data representation. Eight frames means a program counts as frame 0 and may nest seven calls deep below it.

MAX_BPF_STACK = 512 — the per-program stack size, and crucially the summed budget across a call chain. Defined in include/linux/filter.h:96:

#define MAX_BPF_STACK	512

Every BPF function gets a fresh stack frame addressed through R10, the read-only frame pointer (see The BPF Calling Convention and Stack). A single flat program is limited to 512 bytes of stack. With bpf2bpf calls, the limit applies to the sum of all frames along the deepest call path — if main uses 128 bytes, calls a using 256, which calls b using 256, that chain totals 640 and is rejected. This is what stops a deep call chain from blowing the kernel’s own limited per-thread stack (THREAD_SIZE is 16 KiB on x86-64/arm64 at 6.12, shared with the kernel’s own call frames; BPF must live within a slice of it).

How the Verifier Checks the Call Chain — check_max_stack_depth

The combined-depth proof lives in check_max_stack_depth_subprog() in kernel/bpf/verifier.c (v6.12, around line 6008). It is a deliberately small routine — the comment notes it “only needs a local stack of MAX_CALL_FRAMES to remember callsites” — and it works by walking the call graph as a depth-first traversal, accumulating stack depth as it descends and un-accumulating as it returns.

The mechanism, traced step by step from the source:

  1. It starts at a subprogram’s first instruction with depth = 0, frame = 0.
  2. At each subprogram entry (the process_func: label) it does depth += round_up_stack_depth(env, subprog[idx].stack_depth) — adding that function’s own stack usage (rounded up to a register-size boundary) to the running total.
  3. It immediately checks if (depth > MAX_BPF_STACK) and, if exceeded, emits "combined stack size of %d calls is %d. Too large" and returns -EACCES. This is the exact verifier message you see when a deep call chain overflows.
  4. It then scans the function body (continue_func:) for call instructions (bpf_pseudo_call). When it finds one, it records the return site in the local ret_insn[frame] / ret_prog[frame] arrays, resolves the callee subprogram with find_subprog(), increments frame, and checks if (frame >= MAX_CALL_FRAMES), emitting "the call stack of %d frames is too deep !" and returning -E2BIG if so.
  5. It jumps back to process_func: to descend into the callee, repeating the accumulation.
  6. When a function body ends, it unwinds: depth -= round_up_stack_depth(...), frame--, restores i/idx from the saved ret_insn/ret_prog, and continues scanning the caller after the call.

Because the traversal explores every call edge and tracks the maximum, both limits are guaranteed across all paths — there is no runtime stack-overflow check because the verifier has already proven the chain fits. This is the essence of eBPF safety: the cost is paid once at load time so the hot path runs with no guard.

Uncertain

The exact line numbers (MAX_CALL_FRAMES at bpf_verifier.h:350, MAX_BPF_STACK at filter.h:96, check_max_stack_depth_subprog near verifier.c:6008) and constant values were read directly from the v6.12 raw blobs and are solid. Line numbers can shift within the 6.12.y stable series as fixes are backported; treat the values and message strings as authoritative and the line numbers as “as of the v6.12 release tag.” uncertain

Function-by-Function Verification — Static vs Global (since 5.6)

When subprograms first landed in 4.16, the verifier re-verified each subprogram at every call site, inlining the analysis. A function called ten times was analyzed ten times — which recovered the code-size win but not the verification-cost win. Linux 5.6 introduced function-by-function verification and split BPF functions into two kinds (ebpf.io functions):

  • Static functions — marked static in C. These are still verified contextually at each call, with the verifier knowing the actual argument value ranges from the caller. They are the default for internal helpers.
  • Global functions — non-static. These undergo true function-by-function verification: the verifier analyzes each global function exactly once, even out of order, treating it as a verification boundary. This is what actually slashes verification time for large programs.

The trade-off is that across a global-function boundary the verifier assumes nothing about the arguments: a u32 parameter is taken to range over the full 0 .. 4294967295, even if every caller passes 123. So global functions often need more defensive input checking to pass — but they verify once. The original 5.6 rules also restricted global-function signatures to a scalar return and arguments that are either a pointer-to-context or scalars; later releases widened this: pointer arguments (to stack, map values, or packet data) became allowed in 5.12, and 6.8 added BTF argument annotations (__arg_ctx, __arg_nonnull, __arg_nullable, __arg_trusted, __arg_arena) that let you tell the verifier to constrain inputs (ebpf.io functions).

Global functions also unlocked global function replacement (also 5.6): a BPF_PROG_TYPE_EXT program can replace a global function in an already-loaded program at runtime. This is the mechanism libxdp uses to implement XDP program chaining from a dispatcher.

No Helper-Call Overhead — The JIT Emits Real call/ret

A subprogram call is not a helper call and carries none of a helper call’s machinery. A helper invocation crosses into hand-written kernel C through the numbered helper ABI; a bpf2bpf call is a call to BPF code and the per-architecture JIT lowers it to a native call instruction targeting the JITed address of the callee, paired with a ret on return. The kernel’s design Q&A puts the broader point plainly: JITed BPF calls into kernel functions are “indistinguishable from native kernel C code” and the helper interop is described as “zero overhead” (BPF Design Q&A).

The register conventions the call must honor come straight from The BPF Calling Convention and Stack and are enforced by the verifier:

  • R1R5 carry up to five arguments — arguments are never passed on the stack, so five is a hard ceiling; pass a pointer to a struct to exceed it (ebpf.io functions).
  • R0 holds the return value.
  • R1R5 are caller-saved (clobbered): after a call the verifier will not let the caller read them until they are re-set with a known value.
  • R6R9 are callee-saved: preserved across the call, so the JIT spills/restores them in the callee’s prologue/epilogue.
  • R10 is the read-only frame pointer; the callee gets a fresh frame, and may access the caller’s frame only if the caller passes a pointer into it as an argument.

Because these conventions were chosen to mirror native ABIs (x86-64, arm64), the JIT’s mapping of a bpf2bpf call to a native call is nearly mechanical — which is exactly why the runtime cost is just an ordinary function-call’s worth of prologue/epilogue, not a helper-trampoline’s worth.

Worked Example — Subprograms in BPF C

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
 
/* A 'static' subprogram: verified per call site, args known.
 * __noinline forces it to remain a real function rather than
 * being inlined back into the caller by the compiler. */
static __noinline int parse_ip(struct __sk_buff *skb, __u32 off)
{
    __u8 ihl_ver;
    /* bounds-checked read; verifier sees 'off' as a known value here */
    if (bpf_skb_load_bytes(skb, off, &ihl_ver, 1) < 0)
        return -1;
    return (ihl_ver & 0x0f) * 4;          /* IHL in bytes -> R0 */
}
 
/* A 'global' (non-static) subprogram: verified ONCE, out of order.
 * The verifier assumes nothing about 'len' -> must re-check it. */
__noinline int clamp_len(__u32 len)
{
    if (len > 1500)                        /* defensive: verifier knows nothing */
        len = 1500;
    return len;                            /* scalar return required for globals */
}
 
SEC("tc")
int prog(struct __sk_buff *skb)
{
    int ihl = parse_ip(skb, 14);           /* real call -> call/ret, R0 = ihl */
    if (ihl < 0)
        return TC_ACT_OK;
    int n = clamp_len(skb->len);           /* second subprogram call */
    return n > ihl ? TC_ACT_OK : TC_ACT_SHOT;
}

Line-by-line: parse_ip is static, so the verifier checks it knowing off == 14 at the call site — it can prove the bpf_skb_load_bytes read is in bounds with that concrete offset. __noinline is essential: without it the compiler may inline these back, defeating the point; with it, the JIT emits a genuine call parse_ip. clamp_len is global (no static), so it is verified exactly once with len assumed to span the full u32 range — hence the > 1500 guard is not redundant, it is what makes the function pass. Both calls return through R0. The whole program fits trivially within the 8-frame, 512-byte budget (one level of nesting, tiny frames).

Interaction With Tail Calls — The Shared, Shrunken Budget

The single most error-prone area is mixing bpf2bpf calls with tail calls. Originally the two were mutually exclusive — you picked one or the other (LWN 830520). Linux 5.10 lifted that on x86-64; other architectures followed: arm64 in 6.0, s390 in 6.3, LoongArch in 6.4 (ebpf.io functions). At 6.12, exactly four architectures support mixing — x86-64, arm64, s390, and LoongArch — which I verified directly: each of their JIT files defines bpf_jit_supports_subprog_tailcalls() (returning true), while arch/powerpc/net/bpf_jit_comp*.c does not at the v6.12 tag, so powerpc64 still rejects mixing there. Architectures whose JIT does not implement that predicate reject any program that combines the two.

The reason mixing is dangerous is stack accounting. A tail call reuses the current stack frame, but if the tail call is issued from inside a bpf2bpf subprogram, the caller’s frames cannot be unwound — they are still live below the tail-called program. The verifier’s comment in check_max_stack_depth_subprog spells out the worst case: a chain of (caller frame + tail call + caller frame + tail call …) accumulates caller stacks without reclaiming them. To bound it, the verifier halves the available stack to 256 bytes for any subprogram that can reach a tail call:

/* from check_max_stack_depth_subprog, v6.12 verifier.c ~line 6022 */
if (idx && subprog[idx].has_tail_call && depth >= 256) {
    verbose(env,
        "tail_calls are not allowed when call stack of previous frames is %d bytes. Too large\n",
        depth);
    return -EACCES;
}

With the tail-call count capped at 33 (MAX_TAIL_CALL_CNT, see BPF Tail Calls) and each contributing at most 256 bytes, the worst-case BPF stack accumulation is bounded near 8 KiB rather than the ~16 KiB that 512-byte frames would allow — keeping BPF comfortably within the kernel’s per-thread stack. The same routine sets subprog[...].tail_call_reachable = true on every frame on a path that reaches a tail call; this flag tells the JIT it must propagate the tail-call counter across the bpf2bpf call boundary (on x86-64 the counter rides in rax/R0; on arm64 in x26), so a chain of subprograms and tail calls shares one counter and cannot evade the limit. That JIT-counter detail is covered in BPF Tail Calls.

Failure Modes and How to Diagnose

  • “combined stack size of N calls is M. Too large” — your deepest call chain sums past 512 bytes of stack. Diagnose by reducing per-function stack (large local arrays/structs are the usual culprit; move them to a per-CPU map scratch buffer) or by flattening the call graph. Remember the limit is the sum along the path, not per function.
  • “the call stack of N frames is too deep !” — you nested more than 8 frames. Recursion of any meaningful depth is impossible by construction; restructure to iterate (with a bounded loop or tail call) instead of recursing.
  • “tail_calls are not allowed when call stack of previous frames is N bytes. Too large” — you combined bpf2bpf with tail calls and a caller already used ≥256 bytes of stack. Shrink the caller’s stack usage below 256 bytes on the tail-call path.
  • The function got inlined anyway — without __noinline/__attribute__((noinline)) the compiler may inline small functions, silently reintroducing the size/complexity blowup. If you intended a real subprogram, force __noinline.
  • Verifier rejects a global function that “obviously” works — a global function is verified with no assumptions about its arguments. The fix is almost always to add the input check the verifier is demanding, or to attach a __arg_* annotation (6.8+) that narrows the assumed range.

Alternatives and When to Choose Them

  • Inlining (__always_inline) — best for tiny, hot helpers where a call/ret pair and register spills are measurable overhead and the function is used in only a few places. Costs code size and verifier complexity at every site. Choose it for one-or-two-instruction accessors.
  • bpf2bpf subprograms (this note) — best for non-trivial, reused logic: real call/ret, one copy, one verification (for globals). Choose it for anything you would write as a standalone C function and call more than once or twice.
  • Tail calls — best when you want to replace the running program with a different one (program chaining, dispatch tables) and do not need to return. They reset the stack and give the next program a fresh complexity budget, so they extend a program past what one verified program can hold. Choose them for state-machine-style pipelines, not for “call this helper and continue.”
  • kfuncs / helpers — when the logic already exists in the kernel, call it directly rather than reimplementing in BPF.

Production Notes

Large eBPF datapaths lean on subprograms heavily. Cilium’s documentation describes BPF-to-BPF calls as a core technique for keeping its complex datapath within the verifier’s reach, noting they reduce generated code size and were a prerequisite for scaling its programs (Cilium BPF architecture). The practical wisdom that has accumulated: prefer static subprograms when the caller’s argument ranges genuinely help the verifier prove safety; switch to global functions when verification time is the bottleneck (large programs with hot reuse), accepting that you must add explicit argument checks. And treat the 8-frame / 512-byte budget as a design constraint from the start — deep call trees and large on-stack structures are the two things that most often turn a working program into a verifier rejection as it grows.

See Also