Skip to content

Commit 177a9e3

Browse files
committed
Add exec() builtin function for executing Python scripts.
This commit introduces the exec() function, allowing users to replace the current process with a Python script while inheriting eBPF maps. It includes validation for the argument type and generates a corresponding Python wrapper for seamless integration. Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent f62f2d4 commit 177a9e3

8 files changed

Lines changed: 909 additions & 28 deletions

File tree

examples/python_demo.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Demo Python Script - Called via exec() from KernelScript
4+
5+
This script demonstrates the CORRECT usage pattern:
6+
- Import the auto-generated wrapper (test_exec.py)
7+
- Use maps directly as module-level variables
8+
"""
9+
10+
import sys
11+
12+
# Import the auto-generated KernelScript wrapper
13+
# The wrapper handles all the file descriptor inheritance internally
14+
try:
15+
import test_exec as ks
16+
except ImportError:
17+
print("❌ Error: Could not import test_exec.py", file=sys.stderr)
18+
print(" Make sure the KernelScript wrapper was generated correctly", file=sys.stderr)
19+
sys.exit(1)
20+
21+
def main():
22+
"""Main function - called via exec() from KernelScript"""
23+
print("🚀 KernelScript Python Integration Demo")
24+
print("=" * 40)
25+
26+
# 1. Reading from maps
27+
print("\n📖 Reading from eBPF maps:")
28+
try:
29+
# Read from array map
30+
value = ks.packet_stats[0]
31+
print(f" packet_stats[0] = {value}")
32+
33+
value = ks.packet_stats[5]
34+
print(f" packet_stats[5] = {value}")
35+
except Exception as e:
36+
print(f" packet_stats read: {e}")
37+
38+
try:
39+
# Read from hash map
40+
value = ks.bandwidth_usage[1]
41+
print(f" bandwidth_usage[1] = {value}")
42+
except Exception as e:
43+
print(f" bandwidth_usage read: {e}")
44+
45+
# 2. Writing to maps
46+
print("\n✏️ Writing to eBPF maps:")
47+
try:
48+
# Write to array map
49+
ks.packet_stats[0] = 100
50+
ks.packet_stats[1] = 200
51+
print(" packet_stats[0] = 100")
52+
print(" packet_stats[1] = 200")
53+
54+
# Write to hash map
55+
ks.bandwidth_usage[10] = 1024
56+
ks.bandwidth_usage[20] = 2048
57+
print(" bandwidth_usage[10] = 1024")
58+
print(" bandwidth_usage[20] = 2048")
59+
except Exception as e:
60+
print(f" Map write error: {e}")
61+
62+
# 3. Reading back written values
63+
print("\n🔄 Reading back written values:")
64+
try:
65+
print(f" packet_stats[0] = {ks.packet_stats[0]}")
66+
print(f" packet_stats[1] = {ks.packet_stats[1]}")
67+
print(f" bandwidth_usage[10] = {ks.bandwidth_usage[10]}")
68+
print(f" bandwidth_usage[20] = {ks.bandwidth_usage[20]}")
69+
except Exception as e:
70+
print(f" Read back error: {e}")
71+
72+
# 4. Using auto-generated structs
73+
print("\n🏗️ Using auto-generated structs:")
74+
ctx = ks.xdp_md()
75+
ctx.data = 0x1000
76+
ctx.data_end = 0x2000
77+
ctx.ingress_ifindex = 5
78+
ctx.rx_queue_index = 2
79+
ctx.egress_ifindex = 8
80+
81+
packet_size = ctx.data_end - ctx.data
82+
print(f" Created xdp_md struct:")
83+
print(f" data: 0x{ctx.data:x}")
84+
print(f" data_end: 0x{ctx.data_end:x}")
85+
print(f" packet_size: {packet_size} bytes")
86+
print(f" ingress_ifindex: {ctx.ingress_ifindex}")
87+
print(f" rx_queue_index: {ctx.rx_queue_index}")
88+
print(f" egress_ifindex: {ctx.egress_ifindex}")
89+
90+
# 5. Map operations
91+
print("\n🗑️ Map operations:")
92+
try:
93+
# Delete from hash map
94+
del ks.bandwidth_usage[10]
95+
print(" Deleted bandwidth_usage[10]")
96+
97+
# Try to read deleted key
98+
try:
99+
value = ks.bandwidth_usage[10]
100+
print(f" bandwidth_usage[10] = {value}")
101+
except KeyError:
102+
print(" bandwidth_usage[10] not found (expected after deletion)")
103+
except Exception as e:
104+
print(f" Delete operation: {e}")
105+
106+
print("\n✅ Demo completed successfully!")
107+
return 0
108+
109+
if __name__ == "__main__":
110+
exit(main())

examples/test_exec.ks

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Test exec() builtin with Python integration
2+
3+
// XDP context struct (from BTF)
4+
struct xdp_md {
5+
data: u64,
6+
data_end: u64,
7+
data_meta: u64,
8+
ingress_ifindex: u32,
9+
rx_queue_index: u32,
10+
egress_ifindex: u32,
11+
}
12+
13+
// XDP action enum (from BTF)
14+
enum xdp_action {
15+
XDP_ABORTED = 0,
16+
XDP_DROP = 1,
17+
XDP_PASS = 2,
18+
XDP_REDIRECT = 3,
19+
XDP_TX = 4,
20+
}
21+
22+
// Global maps for sharing with Python
23+
var packet_stats : array<u32, u64>(256)
24+
var bandwidth_usage : hash<u32, u64>(1024)
25+
var test_map : hash<u32, u32>(100)
26+
27+
@helper
28+
fn get_packet_size() -> u32 {
29+
return 64 // Demo packet size
30+
}
31+
32+
@xdp fn packet_monitor(ctx: *xdp_md) -> xdp_action {
33+
var size = get_packet_size()
34+
var bucket = size / 64
35+
if (bucket < 256) {
36+
packet_stats[bucket] += 1
37+
}
38+
39+
var interface = ctx->ingress_ifindex
40+
var size_u64: u64 = size
41+
bandwidth_usage[interface] += size_u64
42+
43+
return XDP_PASS
44+
}
45+
46+
fn main() -> i32 {
47+
var prog = load(packet_monitor)
48+
var result = attach(prog, "lo", 0)
49+
50+
if (result == 0) {
51+
print("eBPF program attached successfully")
52+
print("Switching to Python for data analysis...")
53+
54+
// Replace current process with Python - never returns
55+
exec("./python_demo.py")
56+
} else {
57+
print("Failed to attach eBPF program")
58+
return 1
59+
}
60+
61+
return 0
62+
}

src/main.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ let compile_source input_file output_dir _verbose generate_makefile btf_vmlinux_
10161016
(* Phase 6: Advanced multi-target code generation *)
10171017
current_phase := "Code Generation";
10181018
Printf.printf "Phase 6: %s\n" !current_phase;
1019-
let _resource_plan = Multi_program_ir_optimizer.plan_system_resources ir_with_ring_buffer_analysis.programs multi_prog_analysis in
1019+
let _resource_plan = Multi_program_ir_optimizer.plan_system_resources ir_with_ring_buffer_analysis.programs ir_with_ring_buffer_analysis in
10201020
let _optimization_strategies = Multi_program_ir_optimizer.generate_optimization_strategies multi_prog_analysis in
10211021

10221022
(* Extract type aliases from original AST *)

src/multi_program_ir_optimizer.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ let validate_cross_program_constraints _programs multi_prog_analysis =
100100
Printf.printf " ⚠️ Found %d constraint issues (see above)\n" !issues
101101

102102
(** Resource planning for multi-program systems *)
103-
let plan_system_resources programs multi_prog_analysis =
103+
let plan_system_resources programs ir_multi_prog =
104104
let total_programs = List.length programs in
105-
let total_maps = List.length multi_prog_analysis.global_maps in
105+
let total_maps = List.length ir_multi_prog.Ir.global_maps in
106106
let estimated_instructions = total_programs * 1000 in
107107
let estimated_stack = total_programs * 512 in
108108
let estimated_memory = total_maps * 1024 * 1024 in
@@ -182,7 +182,7 @@ let generate_optimized_ir (annotated_ast: declaration list)
182182

183183
(* Step 5: Resource planning *)
184184
Printf.printf "Step 5: Resource planning and validation...\n";
185-
let resource_plan = plan_system_resources optimized_programs multi_prog_analysis in
185+
let resource_plan = plan_system_resources optimized_programs baseline_ir in
186186
print_resource_plan resource_plan;
187187

188188
Printf.printf "\n✅ Advanced Multi-Program IR Optimization completed successfully!\n\n";

src/stdlib.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ let validate_dispatch_function arg_types _ast_context _pos =
6161
else
6262
(false, Some "dispatch() only accepts ring buffer arguments")
6363

64+
(** Validation function for exec() - validates Python file suffix *)
65+
let validate_exec_function arg_types _ast_context _pos =
66+
if List.length arg_types <> 1 then
67+
(false, Some "exec() takes exactly one argument")
68+
else
69+
(* The argument should be a string type *)
70+
let arg_type = List.hd arg_types in
71+
match arg_type with
72+
| Str _ -> (true, None) (* Actual file suffix validation happens during codegen *)
73+
| _ -> (false, Some "exec() requires a string argument (Python file path)")
74+
6475
(** Validation function for register() - only accepts impl block arguments *)
6576
let validate_register_function arg_types ast_context _pos =
6677
if List.length arg_types <> 1 then
@@ -188,6 +199,17 @@ let builtin_functions = [
188199
kernel_impl = ""; (* Not available in kernel context *)
189200
validate = None;
190201
};
202+
{
203+
name = "exec";
204+
param_types = [Str 256]; (* Python script file path *)
205+
return_type = Void; (* Never returns - replaces current process *)
206+
description = "Replace current process with Python script, inheriting eBPF maps (userspace only)";
207+
is_variadic = false;
208+
ebpf_impl = ""; (* Not available in eBPF context *)
209+
userspace_impl = "exec_builtin"; (* Custom implementation in userspace *)
210+
kernel_impl = ""; (* Not available in kernel context *)
211+
validate = Some validate_exec_function;
212+
};
191213

192214
]
193215

0 commit comments

Comments
 (0)