Skip to content

Commit 6699975

Browse files
committed
Implement include system in KernelScript.
This update introduces a new `include` keyword for including header files (.kh) alongside the existing `import` functionality for modules. Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 3fa7e90 commit 6699975

File tree

15 files changed

+596
-11
lines changed

15 files changed

+596
-11
lines changed

SPEC.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ fn main(args: Args) -> i32 {
8989
}
9090
```
9191

92-
### 1.4 Unified Import System
92+
### 1.4 Unified Import and Include System
9393

94-
KernelScript supports importing both KernelScript modules and external language modules using a unified syntax. Import behavior is automatically determined by file extension:
94+
KernelScript supports both importing modules and including headers using distinct keywords for different use cases:
9595

9696
```kernelscript
9797
// Import KernelScript modules (.ks files)
@@ -132,6 +132,30 @@ fn main() -> i32 {
132132
}
133133
```
134134

135+
#### Include System for Headers (.kh files)
136+
137+
```kernelscript
138+
// Include KernelScript headers (.kh files) - declarations only, flattened into global namespace
139+
include "generated/common_kfuncs.kh" // extern kfunc declarations
140+
include "generated/xdp_kfuncs.kh" // XDP-specific kfuncs
141+
include "types/networking.kh" // Type definitions
142+
143+
@xdp
144+
fn packet_processor(ctx: *xdp_md) -> xdp_action {
145+
// Direct access to included extern kfuncs (no namespace)
146+
var timestamp = bpf_ktime_get_ns() // From common_kfuncs.kh
147+
bpf_xdp_adjust_head(ctx, -14) // From xdp_kfuncs.kh
148+
149+
return XDP_PASS
150+
}
151+
```
152+
153+
**Key Distinctions:**
154+
- **`import name from "file"`**: Creates namespace, works with full implementations (.ks/.py files)
155+
- **`include "file"`**: Flattens into global namespace, works with headers only (.kh files)
156+
- **Use cases**: Import for libraries/modules, include for extern declarations and types
157+
- **Validation**: Include validates that .kh files contain only declarations (no function bodies)
158+
135159
## 2. Lexical Structure
136160

137161
### 2.1 Keywords
@@ -141,7 +165,7 @@ pin type struct enum if else
141165
while loop break continue return import
142166
pub priv impl true false null
143167
try catch throw defer delete match
144-
extern
168+
extern include
145169
```
146170

147171
**Note**: The `pin` keyword is used for both maps and global variables to enable filesystem persistence.
@@ -4824,16 +4848,23 @@ import_declaration = "import" identifier "from" string_literal
48244848
(* External kernel function declarations - for importing existing kernel kfuncs *)
48254849
extern_declaration = "extern" identifier "(" parameter_list ")" [ "->" type_annotation ]
48264850
4851+
(* Include declarations - for KernelScript headers (.kh files) *)
4852+
include_declaration = "include" string_literal
4853+
48274854
(* Examples:
48284855
import utils from "./common/utils.ks" // KernelScript import
48294856
import ml_analysis from "./ml/threat.py" // Python import (userspace only)
48304857
48314858
extern bpf_ktime_get_ns() -> u64 // Import existing kernel kfunc
48324859
extern bpf_trace_printk(fmt: *u8, fmt_size: u32) -> i32 // Import with parameters
48334860
4861+
include "common_kfuncs.kh" // Include header with extern declarations
4862+
include "types/networking.kh" // Include header with type definitions
4863+
48344864
Import behavior is determined by file extension:
48354865
- .ks files: Import KernelScript symbols (functions, types, maps, configs)
48364866
- .py files: Import Python functions with automatic FFI bridging (userspace only)
4867+
- .kh files: Include headers with declarations only (flattened into global namespace)
48374868
*)
48384869
48394870
(* Identifiers and basic tokens *)

examples/common_kfuncs.kh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Common kernel function declarations
2+
extern bpf_ktime_get_ns() -> u64
3+
extern bpf_trace_printk(fmt: *u8, fmt_size: u32) -> i32
4+
extern bpf_get_current_pid_tgid() -> u64
5+
6+
// Common type definitions
7+
type Timestamp = u64
8+
type ProcessId = u32

examples/include_demo.ks

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Example demonstrating include functionality
2+
// This shows how to include KernelScript headers (.kh files)
3+
4+
// Include declarations from header files
5+
include "common_kfuncs.kh"
6+
include "xdp_kfuncs.kh"
7+
8+
// XDP program that uses included kfunc declarations
9+
@xdp
10+
fn packet_processor(ctx: *xdp_md) -> xdp_action {
11+
// These functions are available from the included headers
12+
var timestamp = bpf_ktime_get_ns()
13+
var pid_tgid = bpf_get_current_pid_tgid()
14+
var result = bpf_xdp_adjust_head(ctx, -14)
15+
16+
// Use the timestamp and pid to suppress unused variable warnings
17+
var action: XdpAction = 2 // XDP_PASS
18+
if (timestamp > 0 && pid_tgid > 0 && result >= 0) {
19+
return action
20+
}
21+
22+
return 2 // XDP_PASS
23+
}
24+
25+
fn main() -> i32 {
26+
return 0
27+
}

examples/xdp_kfuncs.kh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// XDP-specific kernel function declarations
2+
extern bpf_xdp_adjust_head(ctx: *xdp_md, delta: i32) -> i32
3+
extern bpf_xdp_adjust_tail(ctx: *xdp_md, delta: i32) -> i32
4+
5+
// XDP-specific types
6+
type XdpAction = u32

src/ast.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,12 @@ type extern_kfunc_declaration = {
351351
extern_pos: position;
352352
}
353353

354+
(** Include declaration - for KernelScript headers (.kh files) *)
355+
type include_declaration = {
356+
include_path: string; (* Path to .kh file *)
357+
include_pos: position;
358+
}
359+
354360
(** Top-level declarations *)
355361
type declaration =
356362
| AttributedFunction of attributed_function
@@ -363,6 +369,7 @@ type declaration =
363369
| ImplBlock of impl_block
364370
| ImportDecl of import_declaration
365371
| ExternKfuncDecl of extern_kfunc_declaration
372+
| IncludeDecl of include_declaration
366373

367374
(** Complete AST *)
368375
type ast = declaration list
@@ -456,6 +463,11 @@ let make_extern_kfunc_declaration name params return_type pos = {
456463
extern_pos = pos;
457464
}
458465

466+
let make_include_declaration path pos = {
467+
include_path = path;
468+
include_pos = pos;
469+
}
470+
459471
let make_type_def def = def
460472

461473
let make_enum_def name values = EnumDef (name, values)
@@ -904,6 +916,8 @@ let string_of_declaration = function
904916
| None -> ""
905917
in
906918
Printf.sprintf "extern %s(%s)%s;" extern_decl.extern_name params_str return_str
919+
| IncludeDecl include_decl ->
920+
Printf.sprintf "include \"%s\"" include_decl.include_path
907921

908922
let string_of_ast ast =
909923
String.concat "\n\n" (List.map string_of_declaration ast)

src/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
userspace_codegen evaluator safety_checker stdlib test_codegen
77
tail_call_analyzer kernel_module_codegen dynptr_bridge
88
btf_parser btf_binary_parser kernel_types struct_ops_registry
9-
import_resolver python_bridge kernelscript_bridge)
9+
import_resolver include_resolver python_bridge kernelscript_bridge)
1010
(libraries unix str kernelscript_context)
1111
(foreign_stubs
1212
(language c)

src/include_resolver.ml

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
(*
2+
* Copyright 2025 Multikernel Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*)
16+
17+
(** Include Resolution for KernelScript Headers (.kh files)
18+
19+
This module handles including KernelScript header files (.kh files)
20+
that contain only declarations (extern, type, struct, enum, config).
21+
It validates that included files contain no function implementations.
22+
*)
23+
24+
open Ast
25+
26+
(** Include validation errors *)
27+
type include_validation_error =
28+
| FunctionBodyFound of string (* function name with body *)
29+
| InvalidExtension of string (* non-.ksh extension *)
30+
| InvalidDeclaration of string (* unsupported declaration type *)
31+
32+
(** Include resolution errors *)
33+
exception Include_error of string * position
34+
exception Include_validation_error of include_validation_error * string * position
35+
36+
let include_error msg pos = raise (Include_error (msg, pos))
37+
38+
(** Validate that a declaration is allowed in header files *)
39+
let validate_header_declaration decl =
40+
match decl with
41+
| TypeDef _ -> true
42+
| StructDecl _ -> true
43+
| ConfigDecl _ -> true
44+
| ExternKfuncDecl _ -> true
45+
| IncludeDecl _ -> true (* Allow nested includes *)
46+
| GlobalVarDecl _ -> true (* Allow global variable declarations *)
47+
| AttributedFunction attr_func ->
48+
(* Check if this is just a declaration (no body) *)
49+
(match attr_func.attr_function.func_body with
50+
| [] -> true (* Empty body = declaration only *)
51+
| _ -> false) (* Has body = implementation *)
52+
| GlobalFunction func ->
53+
(* Check if this is just a declaration (no body) *)
54+
(match func.func_body with
55+
| [] -> true (* Empty body = declaration only *)
56+
| _ -> false) (* Has body = implementation *)
57+
| MapDecl _ -> true (* Allow map declarations *)
58+
| ImplBlock _ -> false (* Impl blocks not allowed in headers *)
59+
| ImportDecl _ -> true (* Allow imports in headers *)
60+
61+
(** Validate that included file contains only valid header declarations *)
62+
let validate_header_file file_path ast =
63+
let validate_decl decl =
64+
if not (validate_header_declaration decl) then
65+
let error_msg = match decl with
66+
| AttributedFunction attr_func ->
67+
FunctionBodyFound attr_func.attr_function.func_name
68+
| GlobalFunction func ->
69+
FunctionBodyFound func.func_name
70+
| ImplBlock impl_block ->
71+
InvalidDeclaration ("impl block '" ^ impl_block.impl_name ^ "' not allowed in headers")
72+
| _ ->
73+
InvalidDeclaration "unknown invalid declaration type"
74+
in
75+
let pos = { line = 0; column = 0; filename = file_path } in
76+
raise (Include_validation_error (error_msg, file_path, pos))
77+
in
78+
List.iter validate_decl ast
79+
80+
(** Validate file extension is .kh *)
81+
let validate_file_extension file_path =
82+
if not (Filename.check_suffix file_path ".kh") then
83+
let pos = { line = 0; column = 0; filename = file_path } in
84+
raise (Include_validation_error (InvalidExtension file_path, file_path, pos))
85+
86+
(** Resolve a single include declaration *)
87+
let resolve_include include_decl base_path =
88+
(* Resolve relative paths *)
89+
let file_path = if Filename.is_relative include_decl.include_path then
90+
Filename.concat (Filename.dirname base_path) include_decl.include_path
91+
else
92+
include_decl.include_path
93+
in
94+
95+
(* Validate file extension *)
96+
validate_file_extension file_path;
97+
98+
(* Check if file exists *)
99+
if not (Sys.file_exists file_path) then
100+
include_error ("Include file not found: " ^ file_path) include_decl.include_pos;
101+
102+
try
103+
(* Parse the included file *)
104+
let ic = open_in file_path in
105+
let content = really_input_string ic (in_channel_length ic) in
106+
close_in ic;
107+
108+
let lexbuf = Lexing.from_string content in
109+
let ast = Parser.program Lexer.token lexbuf in
110+
111+
(* Validate that it's a proper header file *)
112+
validate_header_file file_path ast;
113+
114+
(* Return the parsed declarations *)
115+
ast
116+
with
117+
| Include_validation_error (err, file_path, pos) ->
118+
let error_msg = match err with
119+
| FunctionBodyFound func_name ->
120+
Printf.sprintf "Header file '%s' contains function implementation '%s'. Header files (.kh) should only contain declarations. Move implementations to .ks files." file_path func_name
121+
| InvalidExtension file_path ->
122+
Printf.sprintf "Include directive can only include .kh header files, but found: %s" file_path
123+
| InvalidDeclaration desc ->
124+
Printf.sprintf "Header file '%s' contains invalid declaration: %s" file_path desc
125+
in
126+
include_error error_msg pos
127+
| Sys_error msg ->
128+
let pos = { line = 0; column = 0; filename = file_path } in
129+
include_error ("Cannot read header file: " ^ msg) pos
130+
| Parsing.Parse_error ->
131+
let pos = { line = 0; column = 0; filename = file_path } in
132+
include_error ("Parse error in header file: " ^ file_path) pos
133+
134+
(** Process all includes in an AST and return expanded AST with included declarations *)
135+
let process_includes ast base_path =
136+
let rec process_decls decls =
137+
List.fold_left (fun acc decl ->
138+
match decl with
139+
| IncludeDecl include_decl ->
140+
(* Resolve the include and get its declarations *)
141+
let included_ast = resolve_include include_decl base_path in
142+
(* Recursively process includes in the included file *)
143+
let processed_included = process_decls included_ast in
144+
(* Add the included declarations to our AST (flatten) *)
145+
acc @ processed_included
146+
| _ ->
147+
(* Keep non-include declarations as-is *)
148+
acc @ [decl]
149+
) [] decls
150+
in
151+
process_decls ast
152+
153+
(** Get all include declarations from an AST *)
154+
let get_includes ast =
155+
List.filter_map (function
156+
| IncludeDecl include_decl -> Some include_decl
157+
| _ -> None
158+
) ast

src/lexer.mll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
let lookup_keyword = function
7474
| "fn" -> FN
7575
| "extern" -> EXTERN
76+
| "include" -> INCLUDE
7677
| "pin" -> PIN
7778
| "type" -> TYPE
7879
| "struct" -> STRUCT

src/main.ml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,16 @@ let compile_source input_file output_dir _verbose generate_makefile btf_vmlinux_
571571
) resolved_imports;
572572
Printf.printf "\n";
573573

574+
(* Phase 1.6: Include Processing *)
575+
Printf.printf "Phase 1.6: Include Processing\n";
576+
let includes = Include_resolver.get_includes ast in
577+
let expanded_ast = Include_resolver.process_includes ast input_file in
578+
Printf.printf "✅ Processed %d includes, expanded to %d declarations\n" (List.length includes) (List.length expanded_ast);
579+
List.iter (fun include_decl ->
580+
Printf.printf " 📄 Header: %s\n" include_decl.Ast.include_path
581+
) includes;
582+
Printf.printf "\n";
583+
574584
(* Determine output directory early *)
575585
let actual_output_dir = match output_dir with
576586
| Some dir -> dir
@@ -606,16 +616,17 @@ let compile_source input_file output_dir _verbose generate_makefile btf_vmlinux_
606616
in
607617
copy_python_files resolved_imports actual_output_dir;
608618

609-
(* Store original AST before any filtering *)
610-
let original_ast = ast in
619+
(* Store original AST before any filtering and use expanded AST with includes *)
620+
let _original_ast = ast in
621+
let ast_with_includes = expanded_ast in (* Use expanded AST with included declarations *)
611622

612623
(* Test mode: Filter AST for @test functions *)
613624
let filtered_ast = if test_mode then
614-
Test_codegen.filter_ast_for_testing ast input_file
615-
else original_ast in
625+
Test_codegen.filter_ast_for_testing ast_with_includes input_file
626+
else ast_with_includes in
616627

617-
(* For regular eBPF compilation, always use original AST *)
618-
let compilation_ast = original_ast in
628+
(* Use the filtered AST (which includes expanded includes) for compilation *)
629+
let compilation_ast = filtered_ast in
619630

620631
(* Extract base name for project name *)
621632
let base_name = Filename.remove_extension (Filename.basename input_file) in

src/parse.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ let validate_ast ast =
152152
) impl_block.impl_items
153153
| ImportDecl _ -> true (* Import declarations are always valid once parsed *)
154154
| ExternKfuncDecl _ -> true (* Extern kfunc declarations are always valid once parsed *)
155+
| IncludeDecl _ -> true (* Include declarations are always valid once parsed *)
155156
in
156157

157158
List.for_all validate_declaration ast

0 commit comments

Comments
 (0)