Skip to content

Commit dd5f6b7

Browse files
committed
Lower kfunc declarations into the IR
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 81f65e8 commit dd5f6b7

8 files changed

Lines changed: 151 additions & 53 deletions

File tree

src/ebpf_c_codegen.ml

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,16 @@ let escape_c_string s =
284284

285285
let ebpf_type_from_ir_type = Codegen_common.ir_type_to_c Codegen_common.EbpfKernel
286286

287+
(** Type conversion for kfunc signatures. Unlike ebpf_type_from_ir_type, this keeps
288+
[IRBool] as C [bool] rather than collapsing it to [__u8], so the emitted prototype
289+
matches the kernel's actual kfunc signature (kfunc/extern type matching is strict). *)
290+
let rec kfunc_signature_type_to_c = function
291+
| Ir.IRU8 -> "__u8" | Ir.IRU16 -> "__u16" | Ir.IRU32 -> "__u32" | Ir.IRU64 -> "__u64"
292+
| Ir.IRI8 -> "__s8" | Ir.IRI16 -> "__s16" | Ir.IRI32 -> "__s32" | Ir.IRI64 -> "__s64"
293+
| Ir.IRBool -> "bool" | Ir.IRChar -> "char" | Ir.IRVoid -> "void"
294+
| Ir.IRPointer (inner_type, _) -> sprintf "%s*" (kfunc_signature_type_to_c inner_type)
295+
| other -> ebpf_type_from_ir_type other
296+
287297
(** Generate proper C declaration for eBPF, handling function pointers correctly *)
288298
let generate_ebpf_c_declaration = Codegen_common.c_declaration Codegen_common.EbpfKernel
289299

@@ -3036,6 +3046,27 @@ let generate_declarations_in_source_order_unified ctx ir_multi_prog ~_btf_path _
30363046
(* Generate struct_ops instance *)
30373047
emit_line ctx (sprintf "/* eBPF struct_ops instance: %s */" struct_ops_instance.ir_instance_name);
30383048
emit_blank_line ctx
3049+
3050+
| Ir.IRDeclKfuncDecl kfunc_decl ->
3051+
(* Emit a function-signature declaration so eBPF programs can call the kfunc.
3052+
Kernel-provided kfuncs need `extern ... __ksym;`; locally-defined @kfunc
3053+
functions just need a forward prototype. *)
3054+
let params_str = match kfunc_decl.Ir.ikfunc_params with
3055+
| [] -> "void"
3056+
| params -> String.concat ", " (List.map (fun (name, ir_type) ->
3057+
sprintf "%s %s" (kfunc_signature_type_to_c ir_type) name
3058+
) params)
3059+
in
3060+
let return_type_str = kfunc_signature_type_to_c kfunc_decl.Ir.ikfunc_return_type in
3061+
if kfunc_decl.Ir.ikfunc_is_extern then
3062+
emit_line ctx (sprintf "extern %s %s(%s) __ksym;"
3063+
return_type_str kfunc_decl.Ir.ikfunc_name params_str)
3064+
else (
3065+
emit_line ctx "/* kfunc declaration */";
3066+
emit_line ctx (sprintf "%s %s(%s);"
3067+
return_type_str kfunc_decl.Ir.ikfunc_name params_str)
3068+
);
3069+
emit_blank_line ctx
30393070
) ir_multi_prog.Ir.source_declarations;
30403071

30413072
(* Emit callbacks at the end if no functions were found (fallback) *)
@@ -3241,7 +3272,7 @@ let collect_callback_dependencies ir_multi_prog =
32413272

32423273
(** Compile multi-program IR to eBPF C code with automatic tail call detection *)
32433274
let compile_multi_to_c_with_tail_calls
3244-
?(kfunc_declarations=[]) ?(tail_call_analysis=None) ?(btf_path=None)
3275+
?(tail_call_analysis=None) ?(btf_path=None)
32453276
(ir_multi_prog : Ir.ir_multi_program) =
32463277

32473278
let ctx = create_c_context () in
@@ -3260,30 +3291,10 @@ let compile_multi_to_c_with_tail_calls
32603291
let uses_dynptr = check_dynptr_usage ir_multi_prog in
32613292
if uses_dynptr then generate_dynptr_macros ctx;
32623293

3263-
(* Generate kfunc declarations *)
3264-
let rec ast_type_to_c_type ast_type =
3265-
match ast_type with
3266-
| Ast.U8 -> "__u8" | Ast.U16 -> "__u16" | Ast.U32 -> "__u32" | Ast.U64 -> "__u64"
3267-
| Ast.I8 -> "__s8" | Ast.I16 -> "__s16" | Ast.I32 -> "__s32" | Ast.I64 -> "__s64"
3268-
| Ast.Bool -> "bool" | Ast.Char -> "char" | Ast.Void -> "void"
3269-
| Ast.Pointer inner_type -> sprintf "%s*" (ast_type_to_c_type inner_type)
3270-
| _ -> "void"
3271-
in
3272-
List.iter (fun kfunc ->
3273-
let params_str = String.concat ", " (List.map (fun (name, param_type) ->
3274-
let c_type = ast_type_to_c_type param_type in
3275-
sprintf "%s %s" c_type name
3276-
) kfunc.Ast.func_params) in
3277-
let return_type_str = match Ast.get_return_type kfunc.Ast.func_return_type with
3278-
| Some ret_type -> ast_type_to_c_type ret_type
3279-
| None -> "void"
3280-
in
3281-
emit_line ctx (sprintf "/* kfunc declaration */");
3282-
emit_line ctx (sprintf "%s %s(%s);" return_type_str kfunc.Ast.func_name params_str);
3283-
) kfunc_declarations;
3284-
3285-
if kfunc_declarations <> [] then emit_blank_line ctx;
3286-
3294+
(* Kfunc declarations (both kernel-provided `extern` kfuncs and locally-defined
3295+
@kfunc prototypes) are carried in the IR as IRDeclKfuncDecl and emitted in
3296+
source order by generate_declarations_in_source_order_unified. *)
3297+
32873298
(* Generate string type definitions *)
32883299
generate_string_typedefs ctx ir_multi_prog;
32893300

@@ -3316,16 +3327,16 @@ let compile_multi_to_c_with_tail_calls
33163327
(final_output, final_tail_call_analysis)
33173328

33183329
(** Multi-program compilation entry point that returns both code and tail call analysis *)
3319-
let compile_multi_to_c ?(kfunc_declarations=[]) ?(tail_call_analysis=None) ?(btf_path=None) ir_multi_program =
3330+
let compile_multi_to_c ?(tail_call_analysis=None) ?(btf_path=None) ir_multi_program =
33203331
compile_multi_to_c_with_tail_calls
3321-
~kfunc_declarations ~tail_call_analysis ~btf_path ir_multi_program
3332+
~tail_call_analysis ~btf_path ir_multi_program
33223333

33233334
(** Alias for backward compatibility with existing code *)
33243335
let compile_multi_to_c_with_analysis = compile_multi_to_c
33253336

33263337
(** Generate complete C program from multiple IR programs - main interface *)
3327-
let generate_c_multi_program ?(kfunc_declarations=[]) ?(btf_path=None) ir_multi_prog =
3328-
let (c_code, _) = compile_multi_to_c ~kfunc_declarations ~btf_path ir_multi_prog in
3338+
let generate_c_multi_program ?(btf_path=None) ir_multi_prog =
3339+
let (c_code, _) = compile_multi_to_c ~btf_path ir_multi_prog in
33293340
c_code
33303341

33313342
(** Generate complete C program from IR *)

src/ir.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,18 @@ and ir_declaration_desc =
399399
| IRDeclProgramDef of ir_program
400400
| IRDeclStructOpsDef of ir_struct_ops_declaration
401401
| IRDeclStructOpsInstance of ir_struct_ops_instance
402+
| IRDeclKfuncDecl of ir_kfunc_declaration
403+
404+
(** Kfunc declaration - a function signature to declare at the top of the eBPF C file.
405+
Covers both kernel-provided kfuncs (extern ... __ksym) and locally-defined @kfunc
406+
prototypes. The full @kfunc body is emitted separately into the kernel module. *)
407+
and ir_kfunc_declaration = {
408+
ikfunc_name: string;
409+
ikfunc_params: (string * ir_type) list;
410+
ikfunc_return_type: ir_type;
411+
ikfunc_is_extern: bool; (* true: kernel-provided (extern __ksym); false: local @kfunc prototype *)
412+
ikfunc_pos: ir_position;
413+
}
402414

403415
(** Utility functions for creating IR nodes *)
404416

@@ -536,6 +548,16 @@ let make_ir_struct_ops_def_decl struct_ops_def order =
536548
let make_ir_struct_ops_instance_decl struct_ops_instance order =
537549
make_ir_source_declaration (IRDeclStructOpsInstance struct_ops_instance) order struct_ops_instance.ir_instance_pos
538550

551+
let make_ir_kfunc_decl name params return_type is_extern order pos =
552+
make_ir_source_declaration
553+
(IRDeclKfuncDecl {
554+
ikfunc_name = name;
555+
ikfunc_params = params;
556+
ikfunc_return_type = return_type;
557+
ikfunc_is_extern = is_extern;
558+
ikfunc_pos = pos;
559+
}) order pos
560+
539561
let make_ir_multi_program source_name ?(source_declarations = [])
540562
?userspace_program ?(ring_buffer_registry = create_empty_ring_buffer_registry ())
541563
pos =

src/ir_generator.ml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3375,8 +3375,32 @@ let lower_multi_program ast symbol_table source_name =
33753375
add_source_declaration (IRDeclStructOpsDef ir_struct_ops_decl) struct_def.struct_pos
33763376
| None -> ())
33773377
| Ast.AttributedFunction attr_func ->
3378-
(* Insert program entry functions at source position *)
33793378
let func_name = attr_func.attr_function.func_name in
3379+
let is_kfunc = List.exists (function
3380+
| Ast.SimpleAttribute "kfunc" -> true
3381+
| _ -> false
3382+
) attr_func.attr_list in
3383+
if is_kfunc then (
3384+
(* Locally-defined @kfunc: emit a prototype declaration so eBPF programs
3385+
can call it. The body itself goes into the separate kernel module. *)
3386+
let func = attr_func.attr_function in
3387+
let ir_params = List.map (fun (name, param_type) ->
3388+
(name, ast_type_to_ir_type param_type)
3389+
) func.func_params in
3390+
let ir_return_type = match Ast.get_return_type func.func_return_type with
3391+
| Some ret_type -> ast_type_to_ir_type ret_type
3392+
| None -> IRVoid
3393+
in
3394+
add_source_declaration
3395+
(IRDeclKfuncDecl {
3396+
ikfunc_name = func.func_name;
3397+
ikfunc_params = ir_params;
3398+
ikfunc_return_type = ir_return_type;
3399+
ikfunc_is_extern = false;
3400+
ikfunc_pos = func.func_pos;
3401+
}) func.func_pos
3402+
) else
3403+
(* Insert program entry functions at source position *)
33803404
(match Hashtbl.find_opt program_table func_name with
33813405
| Some (_prog_def, ir_program) ->
33823406
add_source_declaration (IRDeclProgramDef ir_program) ir_program.ir_pos
@@ -3406,6 +3430,23 @@ let lower_multi_program ast symbol_table source_name =
34063430
| None -> ())
34073431
| Ast.ImplStaticField _ -> ()
34083432
) impl_block.impl_items
3433+
| Ast.ExternKfuncDecl extern_decl ->
3434+
(* Kernel-provided kfunc: emit an `extern ... __ksym;` declaration *)
3435+
let ir_params = List.map (fun (name, param_type) ->
3436+
(name, ast_type_to_ir_type param_type)
3437+
) extern_decl.Ast.extern_params in
3438+
let ir_return_type = match extern_decl.Ast.extern_return_type with
3439+
| Some ret_type -> ast_type_to_ir_type ret_type
3440+
| None -> IRVoid
3441+
in
3442+
add_source_declaration
3443+
(IRDeclKfuncDecl {
3444+
ikfunc_name = extern_decl.Ast.extern_name;
3445+
ikfunc_params = ir_params;
3446+
ikfunc_return_type = ir_return_type;
3447+
ikfunc_is_extern = true;
3448+
ikfunc_pos = extern_decl.Ast.extern_pos;
3449+
}) extern_decl.Ast.extern_pos
34093450
| _ -> () )
34103451
);
34113452

src/main.ml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -888,15 +888,6 @@ let compile_source input_file output_dir _verbose generate_makefile btf_vmlinux_
888888
let _resource_plan = Multi_program_ir_optimizer.plan_system_resources (Ir.get_programs ir_with_ring_buffer_analysis) ir_with_ring_buffer_analysis in
889889
let _optimization_strategies = Multi_program_ir_optimizer.generate_optimization_strategies multi_prog_analysis in
890890

891-
(* Extract kfunc declarations from AST for eBPF C generation *)
892-
let kfunc_declarations = List.filter_map (function
893-
| Ast.AttributedFunction attr_func ->
894-
(match attr_func.attr_list with
895-
| SimpleAttribute "kfunc" :: _ -> Some attr_func.attr_function
896-
| _ -> None)
897-
| _ -> None
898-
) annotated_ast in
899-
900891
(* Perform tail call analysis on AST *)
901892
let tail_call_analysis = Tail_call_analyzer.analyze_tail_calls annotated_ast in
902893

@@ -915,9 +906,9 @@ let compile_source input_file output_dir _verbose generate_makefile btf_vmlinux_
915906
{ ir_with_ring_buffer_analysis with source_declarations = updated_source_declarations }
916907
in
917908

918-
(* Generate eBPF C code (with updated IR and kfunc declarations) *)
909+
(* Generate eBPF C code (kfunc declarations are carried in the IR) *)
919910
let (ebpf_c_code, _final_tail_call_analysis) = Ebpf_c_codegen.compile_multi_to_c_with_analysis
920-
~kfunc_declarations ~tail_call_analysis:(Some tail_call_analysis) ~btf_path:btf_vmlinux_path updated_optimized_ir in
911+
~tail_call_analysis:(Some tail_call_analysis) ~btf_path:btf_vmlinux_path updated_optimized_ir in
921912

922913
(* Analyze kfunc dependencies for automatic kernel module loading *)
923914
let ir_functions = List.map (fun prog -> prog.Ir.entry_function) (Ir.get_programs ir_with_ring_buffer_analysis) in

src/userspace_codegen.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,10 @@ let generate_declarations_in_source_order_userspace ir_multi_prog =
12741274
| Ir.IRDeclStructOpsInstance _struct_ops_instance ->
12751275
(* Skip struct_ops instances in userspace - they're handled separately *)
12761276
()
1277+
1278+
| Ir.IRDeclKfuncDecl _kfunc_decl ->
1279+
(* Skip kfunc declarations in userspace - they're eBPF-side only *)
1280+
()
12771281
) ir_multi_prog.Ir.source_declarations;
12781282

12791283
(* Return the declarations in the correct order (reverse since we prepended) *)

tests/test_definition_order.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ let get_declaration_name = function
107107
| IRDeclProgramDef program -> program.entry_function.func_name
108108
| IRDeclStructOpsDef struct_ops -> struct_ops.ir_struct_ops_name
109109
| IRDeclStructOpsInstance instance -> instance.ir_instance_name
110+
| IRDeclKfuncDecl kfunc_decl -> kfunc_decl.ikfunc_name
110111

111112
(** Test type alias order preservation *)
112113
let test_type_alias_order () =

tests/test_extern.ml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,42 @@ let test_extern_kfunc_string_representation () =
155155
check bool "Contains bpf_ktime_get_ns extern" true contains_bpf_ktime;
156156
check bool "Contains bpf_trace_printk extern" true contains_bpf_trace
157157

158+
(** Test extern kfunc declarations are emitted into generated eBPF C with __ksym *)
159+
let test_extern_kfunc_ebpf_codegen () =
160+
let program = {|
161+
extern bpf_ktime_get_ns() -> u64
162+
extern scx_bpf_dsq_insert(p: *u8, dsq_id: u64, slice: u64, enq_flags: u64) -> void
163+
164+
@xdp
165+
fn test_program(ctx: *xdp_md) -> xdp_action {
166+
var ts = bpf_ktime_get_ns()
167+
scx_bpf_dsq_insert(null, 0, ts, 0)
168+
return 2
169+
}
170+
171+
fn main() -> i32 {
172+
return 0
173+
}
174+
|} in
175+
176+
let ast = Parse.parse_string program in
177+
let symbol_table = Symbol_table.build_symbol_table ast in
178+
let (typed_ast, _) = Type_checker.type_check_and_annotate_ast ast in
179+
let ir = Ir_generator.generate_ir typed_ast symbol_table "test" in
180+
181+
(* Extern kfunc declarations are lowered into the IR; codegen needs no side-channel *)
182+
let (generated_code, _) =
183+
Ebpf_c_codegen.compile_multi_to_c_with_analysis ir in
184+
185+
let contains substr =
186+
try ignore (Str.search_forward (Str.regexp_string substr) generated_code 0); true
187+
with Not_found -> false
188+
in
189+
check bool "Contains bpf_ktime_get_ns __ksym extern" true
190+
(contains "extern __u64 bpf_ktime_get_ns(void) __ksym;");
191+
check bool "Contains scx_bpf_dsq_insert __ksym extern" true
192+
(contains "extern void scx_bpf_dsq_insert(__u8* p, __u64 dsq_id, __u64 slice, __u64 enq_flags) __ksym;")
193+
158194
(** Test extern keyword cannot be used in function definitions *)
159195
let test_extern_in_function_definition_fails () =
160196
let program = {|
@@ -233,6 +269,7 @@ let tests = [
233269
"extern kfunc type checking", `Quick, test_extern_kfunc_type_checking;
234270
"extern kfunc userspace restriction", `Quick, test_extern_kfunc_userspace_restriction;
235271
"extern kfunc string representation", `Quick, test_extern_kfunc_string_representation;
272+
"extern kfunc ebpf codegen", `Quick, test_extern_kfunc_ebpf_codegen;
236273
"extern in function definition fails", `Quick, test_extern_in_function_definition_fails;
237274
"extern with body fails", `Quick, test_extern_with_body_fails;
238275
"extern mixed keywords fails", `Quick, test_extern_mixed_keywords_fails;

tests/test_kfunc_attribute.ml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,9 @@ let test_ebpf_kfunc_declarations () =
148148
(* Use the full multi-program type checker for proper expression typing *)
149149
let (typed_ast, _) = Type_checker.type_check_and_annotate_ast ast in
150150
let ir = Ir_generator.generate_ir typed_ast symbol_table "test" in
151-
152-
(* Extract kfunc declarations *)
153-
let kfunc_declarations = List.filter_map (function
154-
| Ast.AttributedFunction attr_func ->
155-
(match attr_func.attr_list with
156-
| SimpleAttribute "kfunc" :: _ -> Some attr_func.attr_function
157-
| _ -> None)
158-
| _ -> None
159-
) typed_ast in
160-
161-
(* Generate eBPF C code *)
162-
let (generated_code, _) = Ebpf_c_codegen.compile_multi_to_c_with_analysis ~kfunc_declarations ir in
151+
152+
(* @kfunc declarations are lowered into the IR; codegen needs no side-channel *)
153+
let (generated_code, _) = Ebpf_c_codegen.compile_multi_to_c_with_analysis ir in
163154

164155
(* Check that kfunc declarations are generated *)
165156
check bool "Contains kfunc declaration" true

0 commit comments

Comments
 (0)