-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathtest_userspace_for_codegen.ml
More file actions
407 lines (343 loc) · 13.9 KB
/
Copy pathtest_userspace_for_codegen.ml
File metadata and controls
407 lines (343 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
(*
* Copyright 2025 Multikernel Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*)
open Alcotest
open Kernelscript.Parse
(** Helper function to check if generated code contains a pattern *)
let contains_pattern code pattern =
try
let regex = Str.regexp pattern in
ignore (Str.search_forward regex code 0);
true
with Not_found -> false
(** Helper function to generate userspace code from a program with proper IR generation *)
let generate_userspace_code_from_program program_text filename =
let ast = parse_string program_text in
let symbol_table = Kernelscript.Symbol_table.build_symbol_table ast in
let (annotated_ast, _typed_programs) = Kernelscript.Type_checker.type_check_and_annotate_ast ast in
let ir = Kernelscript.Ir_generator.generate_ir annotated_ast symbol_table filename in
let temp_dir = Filename.temp_file "test_userspace_for" "" in
Unix.unlink temp_dir;
Unix.mkdir temp_dir 0o755;
let _output_file = Kernelscript.Userspace_codegen.generate_userspace_code_from_ir
ir ~output_dir:temp_dir filename in
let generated_file = Filename.concat temp_dir (filename ^ ".c") in
if Sys.file_exists generated_file then (
let ic = open_in generated_file in
let content = really_input_string ic (in_channel_length ic) in
close_in ic;
(* Cleanup *)
Unix.unlink generated_file;
Unix.rmdir temp_dir;
content
) else (
failwith "Failed to generate userspace code file"
)
(** Test 1: Basic for loop with constant bounds generates ordinary C for loop *)
let test_basic_for_loop_constant_bounds () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (i in 0..10) {
var x = 42
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_basic_for" in
(* Should generate ordinary C for loop, not unrolled or goto-based *)
check bool "generates for keyword" true (contains_pattern result "for.*(");
check bool "uses loop variable initialization" true (contains_pattern result "= 0");
check bool "has loop condition" true (contains_pattern result "<= 10");
check bool "has increment" true (contains_pattern result "\\+\\+");
check bool "has curly braces" true (contains_pattern result "{");
(* Should NOT contain unrolling patterns *)
check bool "no manual unrolling" false (contains_pattern result "x_0.*x_1.*x_2");
check bool "no eBPF loop_start labels" false (contains_pattern result "loop_start:");
(* Note: goto statements are expected for cleanup and return value propagation *)
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 2: For loop with variable bounds generates ordinary C for loop *)
let test_for_loop_variable_bounds () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn main() -> i32 {
var start = 1
var end_val = 5
for (i in start..end_val) {
var temp = i * 2
}
return 0
}
|} in
try
let ast = parse_string program_text in
let symbol_table = Kernelscript.Symbol_table.build_symbol_table ast in
let (annotated_ast, _typed_programs) = Kernelscript.Type_checker.type_check_and_annotate_ast ast in
let ir = Kernelscript.Ir_generator.generate_ir annotated_ast symbol_table "test_for_variable" in
let temp_dir = Filename.temp_file "test_userspace_for" "" in
Unix.unlink temp_dir;
Unix.mkdir temp_dir 0o755;
let _output_file = Kernelscript.Userspace_codegen.generate_userspace_code_from_ir
ir ~output_dir:temp_dir "test_for_variable.ks" in
let generated_file = Filename.concat temp_dir "test_for_variable.c" in
if Sys.file_exists generated_file then (
let ic = open_in generated_file in
let content = really_input_string ic (in_channel_length ic) in
close_in ic;
(* Cleanup *)
Unix.unlink generated_file;
Unix.rmdir temp_dir;
(* Verify ordinary C for loop generation *)
check bool "generates C for loop" true (contains_pattern content "for.*(");
check bool "no bounds checking macros" false (contains_pattern content "BPF_LOOP_BOUND_CHECK");
check bool "no verifier annotations" false (contains_pattern content "__bounded");
check bool "no eBPF goto-based loop implementation" false (contains_pattern content "goto.*loop_start");
(* Should use variables in bounds (converted to registers by IR) *)
check bool "uses variable bounds" true (contains_pattern content "var_.*var_");
) else (
fail "Failed to generate userspace code file"
);
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 3: For loop with complex expressions generates ordinary C *)
let test_for_loop_complex_expressions () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (i in 0..10) {
var doubled = i * 2
var squared = i * i
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_complex_for" in
(* Should handle complex expressions inside loop without transformation *)
check bool "generates for loop" true (contains_pattern result "for.*(");
check bool "includes doubled variable" true (contains_pattern result "var_doubled");
check bool "includes squared variable" true (contains_pattern result "var_squared");
check bool "has multiplication with user variables" true (contains_pattern result "var_i \\* ");
check bool "has temp variables for operations" true (contains_pattern result "__binop_");
(* Should not apply eBPF-specific transformations *)
check bool "no verifier hints" false (contains_pattern result "__always_inline");
check bool "no stack depth limits" false (contains_pattern result "BPF_STACK_LIMIT");
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 4: For loop with single iteration still generates C for loop *)
let test_for_loop_single_iteration () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (k in 5..5) {
var single = 99
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_single_for" in
(* Even single iteration should generate for loop, not be optimized away *)
check bool "single iteration uses for loop" true (contains_pattern result "for.*(");
check bool "condition is var <= 5" true (contains_pattern result "<= 5");
check bool "not optimized to direct assignment" false (contains_pattern result "single.*=.*99.*//.*optimized");
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 5: Large bounds should not trigger special handling *)
let test_for_loop_large_bounds () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (big in 0..1000000) {
var large = 1
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_large_for" in
(* Large bounds should not trigger unrolling limits or special handling *)
check bool "large bounds use ordinary for" true (contains_pattern result "for.*(");
check bool "no unrolling limit warnings" false (contains_pattern result "UNROLL_LIMIT_EXCEEDED");
check bool "no bounds reduction" false (contains_pattern result "Reduced bounds");
check bool "preserves original bounds" true (contains_pattern result "1000000");
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 6: Zero-iteration loop (start > end) generates valid C *)
let test_for_loop_zero_iterations () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (empty in 10..5) {
var never = 0
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_zero_for" in
(* Should generate syntactically correct C even for impossible loops *)
check bool "zero iteration generates for loop" true (contains_pattern result "for.*(");
check bool "condition respects bounds" true (contains_pattern result "<= 5");
check bool "no special case handling" false (contains_pattern result "Zero iterations");
check bool "no context-specific handling" false (contains_pattern result "Main function");
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 7: For loop in non-main function context *)
let test_for_loop_in_helper_function () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn helper() -> u32 {
for (i in 1..3) {
var helper_var = i + 10
}
return 42
}
fn main() -> i32 {
var result = helper()
return 0
}
|} in
try
let ast = parse_string program_text in
let symbol_table = Kernelscript.Symbol_table.build_symbol_table ast in
let (annotated_ast, _typed_programs) = Kernelscript.Type_checker.type_check_and_annotate_ast ast in
let ir = Kernelscript.Ir_generator.generate_ir annotated_ast symbol_table "test_helper" in
let temp_dir = Filename.temp_file "test_userspace_helper" "" in
Unix.unlink temp_dir;
Unix.mkdir temp_dir 0o755;
let _output_file = Kernelscript.Userspace_codegen.generate_userspace_code_from_ir
ir ~output_dir:temp_dir "test_helper.ks" in
let generated_file = Filename.concat temp_dir "test_helper.c" in
if Sys.file_exists generated_file then (
let ic = open_in generated_file in
let content = really_input_string ic (in_channel_length ic) in
close_in ic;
(* Cleanup *)
Unix.unlink generated_file;
Unix.rmdir temp_dir;
(* Should handle for loops in helper functions the same way *)
check bool "helper function has for loop" true (contains_pattern content "for.*(");
check bool "no context-specific handling" false (contains_pattern content "Main function");
check bool "uses return statement" true (contains_pattern content "return 42");
check bool "coordinator program structure" true (contains_pattern content "main");
) else (
fail "Failed to generate userspace code file"
);
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 8: Comparison with eBPF codegen - global functions should be different *)
let test_global_functions_vs_ebpf_for_loop_differences () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn test_func() -> u32 {
for (i in 0..100) {
var test = 1
}
return 0
}
fn main() -> i32 {
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_vs_ebpf" in
(* Global functions should NOT have eBPF-specific patterns *)
check bool "no BPF loop pragmas" false (contains_pattern result "#pragma unroll");
check bool "no verifier annotations" false (contains_pattern result "__bounded");
check bool "no BPF helper calls" false (contains_pattern result "bpf_for_each");
check bool "no instruction counting" false (contains_pattern result "INSTRUCTION_COUNT");
(* Should be plain C *)
check bool "plain C for loop" true (contains_pattern result "for.*(");
check bool "standard C increment" true (contains_pattern result "++");
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** Test 9: A later variable declaration with the same name as a for counter
should own the declaration instead of producing a duplicate function-scope
predeclaration. *)
let test_for_counter_reused_by_later_var_decl () =
let program_text = {|
@xdp fn test(ctx: *xdp_md) -> xdp_action {
return 2
}
fn main() -> i32 {
var total = 0
for (i in 0..3) {
total = total + i
}
var i = 0
while (i < 2) {
i = i + 1
}
return 0
}
|} in
try
let result = generate_userspace_code_from_program program_text "test_for_reuse" in
check bool "for loop still generated" true (contains_pattern result "for.*var_i");
check bool "later declaration generated once" true (contains_pattern result "uint32_t var_i = 0");
check bool "no duplicate predeclaration for reused counter" false
(contains_pattern result "uint32_t var_i;[\\s\\S]*uint32_t var_i = 0")
with
| exn -> fail ("Test failed with exception: " ^ Printexc.to_string exn)
(** All global function for statement codegen tests *)
let global_function_for_codegen_tests = [
"basic_for_loop_constant_bounds", `Quick, test_basic_for_loop_constant_bounds;
"for_loop_variable_bounds", `Quick, test_for_loop_variable_bounds;
"for_loop_complex_expressions", `Quick, test_for_loop_complex_expressions;
"for_loop_single_iteration", `Quick, test_for_loop_single_iteration;
"for_loop_large_bounds", `Quick, test_for_loop_large_bounds;
"for_loop_zero_iterations", `Quick, test_for_loop_zero_iterations;
"for_loop_in_helper_function", `Quick, test_for_loop_in_helper_function;
"global_functions_vs_ebpf_differences", `Quick, test_global_functions_vs_ebpf_for_loop_differences;
"for_counter_reused_by_later_var_decl", `Quick, test_for_counter_reused_by_later_var_decl;
]
let () =
run "KernelScript Global Function For Statement Codegen Tests" [
"global_function_for_codegen", global_function_for_codegen_tests;
]