• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1local ffi = require('ffi')
2local S = require('syscall')
3
4-- Normalize whitespace and remove empty lines
5local function normalize_code(c)
6	local res = {}
7	for line in string.gmatch(c,'[^\r\n]+') do
8		local op, d, s, t = line:match('(%S+)%s+(%S+)%s+(%S+)%s*([^-]*)')
9		if op then
10			t = t and t:match('^%s*(.-)%s*$')
11			table.insert(res, string.format('%s\t%s %s %s', op, d, s, t))
12		end
13	end
14	return table.concat(res, '\n')
15end
16
17-- Compile code and check result
18local function compile(t)
19	local bpf = require('bpf')
20	-- require('jit.bc').dump(t.input)
21	local code, err = bpf(t.input)
22	assert.truthy(code)
23	assert.falsy(err)
24	if code then
25		if t.expect then
26			local got = normalize_code(bpf.dump_string(code, 1, true))
27			-- if normalize_code(t.expect) ~= got then print(bpf.dump_string(code, 1)) end
28			assert.same(normalize_code(t.expect), got)
29		end
30	end
31end
32
33-- Make a mock map variable
34local function makemap(type, max_entries, key_ctype, val_ctype)
35	if not key_ctype then key_ctype = ffi.typeof('uint32_t') end
36	if not val_ctype then val_ctype = ffi.typeof('uint32_t') end
37	if not max_entries then max_entries = 4096 end
38	return {
39		__map = true,
40		max_entries = max_entries,
41		key = ffi.new(ffi.typeof('$ [1]', key_ctype)),
42		val = ffi.new(ffi.typeof('$ [1]', val_ctype)),
43		map_type = S.c.BPF_MAP[type],
44		key_type = key_ctype,
45		val_type = val_ctype,
46		fd = 42,
47	}
48end
49
50describe('codegen', function()
51	-- luacheck: ignore 113 211 212 311 511
52
53	describe('constants', function()
54		it('remove dead constant store', function()
55			compile {
56				input = function ()
57					local proto = 5
58				end,
59				expect = [[
60					MOV		R0	#0
61					EXIT	R0	#0
62				]]
63			}
64		end)
65		it('materialize constant', function()
66			compile {
67				input = function ()
68					return 5
69				end,
70				expect = [[
71					MOV		R0	#5
72					EXIT	R0	#0
73				]]
74			}
75		end)
76		it('materialize constant longer than i32', function()
77			compile {
78				input = function ()
79					return 4294967295
80				end,
81				expect = [[
82					LDDW	R0	#4294967295
83					EXIT	R0	#0
84				]]
85			}
86		end)
87		it('materialize cdata constant', function()
88			compile {
89				input = function ()
90					return 5ULL
91				end,
92				expect = [[
93					LDDW	R0	#5 -- composed instruction
94					EXIT	R0	#0
95				]]
96			}
97		end)
98		it('materialize signed cdata constant', function()
99			compile {
100				input = function ()
101					return 5LL
102				end,
103				expect = [[
104					LDDW	R0	#5 -- composed instruction
105					EXIT	R0	#0
106				]]
107			}
108		end)
109		it('materialize coercible numeric cdata constant', function()
110			compile {
111				input = function ()
112					return 0x00005
113				end,
114				expect = [[
115					MOV		R0	#5
116					EXIT	R0	#0
117				]]
118			}
119		end)
120		it('materialize constant through variable', function()
121		compile {
122			input = function ()
123				local proto = 5
124				return proto
125			end,
126			expect = [[
127				MOV		R0	#5
128				EXIT	R0	#0
129			]]
130		}
131		end)
132		it('eliminate constant expressions', function()
133			compile {
134				input = function ()
135					return 2 + 3 - 0
136				end,
137				expect = [[
138					MOV		R0	#5
139					EXIT	R0	#0
140				]]
141			}
142		end)
143		it('eliminate constant expressions (if block)', function()
144			compile {
145				input = function ()
146					local proto = 5
147					if proto == 5 then
148						proto = 1
149					end
150					return proto
151				end,
152				expect = [[
153					MOV		R0	#1
154					EXIT	R0	#0
155				]]
156			}
157		end)
158		it('eliminate negative constant expressions (if block) NYI', function()
159			-- always negative condition is not fully eliminated
160			compile {
161				input = function ()
162					local proto = 5
163					if false then
164						proto = 1
165					end
166					return proto
167				end,
168				expect = [[
169					MOV		R7		#5
170					STXDW	[R10-8] R7
171					MOV		R7		#0
172					JEQ		R7		#0 => 0005
173					LDXDW	R0 		[R10-8]
174					EXIT	R0		#0
175				]]
176			}
177		end)
178	end)
179
180	describe('variables', function()
181		it('classic packet access (fold constant offset)', function()
182			compile {
183				input = function (skb)
184					return eth.ip.tos -- constant expression will fold
185				end,
186				expect = [[
187					LDB		R0	skb[15]
188					EXIT	R0	#0
189				]]
190			}
191		end)
192		it('classic packet access (load non-constant offset)', function()
193			compile {
194				input = function (skb)
195					return eth.ip.udp.src_port -- need to skip variable-length header
196				end,
197				expect = [[
198					LDB		R0			skb[14]
199					AND		R0			#15
200					LSH		R0			#2
201					ADD		R0 			#14
202					STXDW	[R10-16]	R0 -- NYI: erase dead store
203					LDH		R0 			skb[R0+0]
204					END		R0 			R0
205					EXIT	R0 			#0
206				]]
207			}
208		end)
209		it('classic packet access (manipulate dissector offset)', function()
210			compile {
211				input = function (skb)
212					local ptr = eth.ip.udp.data + 1
213					return ptr[0] -- dereference dissector pointer
214				end,
215				expect = [[
216					LDB		R0			skb[14]
217					AND		R0			#15
218					LSH		R0			#2
219					ADD		R0			#14 -- NYI: fuse commutative operations in second pass
220					ADD		R0			#8
221					ADD		R0			#1
222					STXDW	[R10-16] 	R0
223					LDB		R0			skb[R0+0]
224					EXIT	R0			#0
225				]]
226			}
227		end)
228		it('classic packet access (multi-byte load)', function()
229			compile {
230				input = function (skb)
231					local ptr = eth.ip.udp.data
232					return ptr(1, 5) -- load 4 bytes
233				end,
234				expect = [[
235					LDB		R0			skb[14]
236					AND		R0			#15
237					LSH		R0			#2
238					ADD		R0			#14
239					ADD		R0			#8
240					MOV		R7			R0
241					STXDW	[R10-16]	R0 -- NYI: erase dead store
242					LDW		R0			skb[R7+1]
243					END		R0			R0
244					EXIT	R0			#0
245				]]
246			}
247		end)
248		it('direct skb field access', function()
249			compile {
250				input = function (skb)
251					return skb.len
252				end,
253				expect = [[
254					LDXW	R7	[R6+0]
255					MOV		R0	R7
256					EXIT	R0	#0
257				]]
258			}
259		end)
260		it('direct skb data access (manipulate offset)', function()
261			compile {
262				input = function (skb)
263					local ptr = skb.data + 5
264					return ptr[0]
265				end,
266				expect = [[
267					LDXW	R7	[R6+76]
268					ADD		R7	#5
269					LDXB	R8 	[R7+0] -- NYI: transform LD + ADD to LD + offset addressing
270					MOV		R0 	R8
271					EXIT	R0	#0
272				]]
273			}
274		end)
275		it('direct skb data access (offset boundary check)', function()
276			compile {
277				input = function (skb)
278					local ptr = skb.data + 5
279					if ptr < skb.data_end then
280						return ptr[0]
281					end
282				end,
283				expect = [[
284					LDXW	R7	[R6+76]
285					ADD		R7	#5
286					LDXW	R8	[R6+80]
287					JGE		R7	R8 => 0008
288					LDXB	R8	[R7+0]
289					MOV		R0 	R8
290					EXIT	R0	#0
291					MOV		R0	#0
292					EXIT	R0	#0
293				]]
294			}
295		end)
296		it('access stack memory (array, const load, const store)', function()
297			compile {
298				input = function (skb)
299					local mem = ffi.new('uint8_t [16]')
300					mem[0] = 5
301				end,
302				expect = [[
303					MOV		R0 			#0
304					STXDW	[R10-40] 	R0
305					STXDW	[R10-48] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
306					STB		[R10-48] 	#5
307					MOV		R0 			#0
308					EXIT	R0 			#0
309				]]
310			}
311		end)
312		it('access stack memory (array, const load, packet store)', function()
313			compile {
314				input = function (skb)
315					local mem = ffi.new('uint8_t [7]')
316					mem[0] = eth.ip.tos
317				end,
318				expect = [[
319					MOV		R0 			#0
320					STXDW	[R10-40] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
321					LDB		R0 			skb[15]
322					STXB	[R10-40] 	R0
323					MOV		R0 			#0
324					EXIT	R0 			#0
325				]]
326			}
327		end)
328		it('access stack memory (array, packet load, const store)', function()
329			compile {
330				input = function (skb)
331					local mem = ffi.new('uint8_t [1]')
332					mem[eth.ip.tos] = 5
333				end,
334				expect = [[
335					MOV		R0 			#0
336					STXDW	[R10-48] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
337					LDB		R0 			skb[15]
338					MOV		R7 			R0
339					ADD		R7 			R10
340					STB		[R7-48] 	#5
341					MOV		R0 			#0
342					EXIT	R0 			#0
343				]]
344			}
345		end)
346		it('access stack memory (array, packet load, packet store)', function()
347			compile {
348				input = function (skb)
349					local mem = ffi.new('uint8_t [7]')
350					local v = eth.ip.tos
351					mem[v] = v
352				end,
353				expect = [[
354					MOV		R0 			#0
355					STXDW	[R10-40] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
356					LDB		R0 			skb[15]
357					MOV		R7 			R0
358					ADD		R7 			R10
359					STXB	[R7-40] 	R0
360					MOV		R0 			#0
361					EXIT	R0 			#0
362				]]
363			}
364		end)
365		it('access stack memory (struct, const/packet store)', function()
366			local kv_t = 'struct { uint64_t a; uint64_t b; }'
367			compile {
368				input = function (skb)
369					local mem = ffi.new(kv_t)
370					mem.a = 5
371					mem.b = eth.ip.tos
372				end,
373				expect = [[
374					MOV		R0 			#0
375					STXDW	[R10-40] 	R0
376					STXDW	[R10-48] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
377					MOV		R7 			#5
378					STXDW	[R10-48] 	R7
379					LDB		R0 			skb[15]
380					STXDW	[R10-40] 	R0
381					MOV		R0 			#0
382					EXIT	R0 			#0
383				]]
384			}
385		end)
386		it('access stack memory (struct, const/stack store)', function()
387			local kv_t = 'struct { uint64_t a; uint64_t b; }'
388			compile {
389				input = function (skb)
390					local m1 = ffi.new(kv_t)
391					local m2 = ffi.new(kv_t)
392					m1.a = 5
393					m2.b = m1.a
394				end,
395				expect = [[
396					MOV		R0 			#0
397					STXDW	[R10-48] 	R0
398					STXDW	[R10-56] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
399					MOV		R0 			#0
400					STXDW	[R10-64] 	R0
401					STXDW	[R10-72] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
402					MOV		R7 			#5
403					STXDW	[R10-56] 	R7
404					LDXDW	R7 			[R10-56]
405					STXDW	[R10-64] 	R7
406					MOV		R0 			#0
407					EXIT	R0 			#0
408				]]
409			}
410		end)
411		it('array map (u32, const key load)', function()
412			local array_map = makemap('array', 256)
413			compile {
414				input = function (skb)
415					return array_map[0]
416				end,
417				expect = [[
418					LDDW	R1			#42
419					STW		[R10-28]	#0
420					MOV		R2			R10
421					ADD		R2			#4294967268
422					CALL	R0			#1 ; map_lookup_elem
423					JEQ		R0			#0 => 0009
424					LDXW	R0			[R0+0]
425					EXIT	R0			#0
426				]]
427			}
428		end)
429		it('array map (u32, packet key load)', function()
430			local array_map = makemap('array', 256)
431			compile {
432				input = function (skb)
433					return array_map[eth.ip.tos]
434				end,
435				expect = [[
436					LDB 	R0 			skb[15]
437					LDDW	R1			#42
438					STXW	[R10-36] 	R0
439					MOV		R2			R10
440					ADD		R2			#4294967260
441					STXDW	[R10-24] 	R0 -- NYI: erase dead store
442					CALL	R0			#1 ; map_lookup_elem
443					JEQ		R0			#0 => 0011
444					LDXW	R0			[R0+0]
445					EXIT	R0			#0
446				]]
447			}
448		end)
449		it('array map (u32, const key store, const value)', function()
450			local array_map = makemap('array', 256)
451			compile {
452				input = function (skb)
453					array_map[0] = 5
454				end,
455				expect = [[
456					LDDW	R1 			#42
457					STW		[R10-36] 	#0
458					MOV		R2 			R10
459					ADD		R2 			#4294967260
460					MOV		R4 			#0
461					STW		[R10-40] 	#5
462					MOV		R3 			R10
463					ADD		R3 			#4294967256
464					CALL	R0 			#2 ; map_update_elem
465					MOV		R0 			#0
466					EXIT	R0 			#0
467				]]
468			}
469		end)
470		it('array map (u32, const key store, packet value)', function()
471			local array_map = makemap('array', 256)
472			compile {
473				input = function (skb)
474					array_map[0] = eth.ip.tos
475				end,
476				expect = [[
477					LDB		R0 			skb[15]
478					STXDW	[R10-24] 	R0
479					LDDW	R1 			#42
480					STW		[R10-36] 	#0
481					MOV		R2 			R10
482					ADD		R2 			#4294967260
483					MOV		R4 			#0
484					MOV		R3 			R10
485					ADD		R3 			#4294967272
486					CALL	R0 			#2 ; map_update_elem
487					MOV		R0 			#0
488					EXIT	R0 			#0
489				]]
490			}
491		end)
492		it('array map (u32, const key store, map value)', function()
493			local array_map = makemap('array', 256)
494			compile {
495				input = function (skb)
496					array_map[0] = array_map[1]
497				end,
498				expect = [[
499					LDDW	R1 			#42
500					STW		[R10-36] 	#1
501					MOV		R2 			R10
502					ADD		R2 			#4294967260
503					CALL	R0 			#1 ; map_lookup_elem
504					STXDW	[R10-24] 	R0
505					LDDW	R1 			#42
506					STW		[R10-36]	#0
507					MOV		R2			R10
508					ADD		R2			#4294967260
509					MOV		R4			#0
510					LDXDW	R3			[R10-24]
511					JEQ		R3			#0 => 0017
512					LDXW	R3			[R3+0]
513					STXW	[R10-40]	R3
514					MOV		R3 			R10
515					ADD		R3 			#4294967256
516					CALL	R0 			#2 ; map_update_elem
517					MOV		R0 			#0
518					EXIT	R0 			#0
519				]]
520			}
521		end)
522		it('array map (u32, const key replace, const value)', function()
523			local array_map = makemap('array', 256)
524			compile {
525				input = function (skb)
526					local val = array_map[0]
527					if val then
528						val[0] = val[0] + 1
529					else
530						array_map[0] = 5
531					end
532				end,
533				expect = [[
534					LDDW	R1 			#42
535					STW		[R10-44] 	#0
536					MOV		R2 			R10
537					ADD		R2 			#4294967252
538					CALL	R0 			#1 ; map_lookup_elem
539					JEQ		R0 			#0 => 0013 -- if (map_value ~= NULL)
540					LDXW	R7 			[R0+0]
541					ADD		R7 			#1
542					STXW	[R0+0] 		R7
543					MOV		R7 			#0
544					JEQ		R7 			#0 => 0025 -- skip false branch
545					STXDW	[R10-16] 	R0
546					LDDW	R1 			#42
547					STW		[R10-44] 	#0
548					MOV		R2 			R10
549					ADD		R2 			#4294967252
550					MOV		R4 			#0
551					STW		[R10-48] 	#5
552					MOV		R3 			R10
553					ADD		R3 			#4294967248
554					CALL	R0 			#2 ; map_update_elem
555					LDXDW	R0 			[R10-16]
556					MOV		R0 			#0
557					EXIT	R0 			#0
558				]]
559			}
560		end)
561		it('array map (u32, const key replace xadd, const value)', function()
562			local array_map = makemap('array', 256)
563			compile {
564				input = function (skb)
565					local val = array_map[0]
566					if val then
567						xadd(val, 1)
568					else
569						array_map[0] = 5
570					end
571				end,
572				expect = [[
573					LDDW	R1 			#42
574					STW		[R10-52] 	#0
575					MOV		R2 			R10
576					ADD		R2 			#4294967244
577					CALL	R0 			#1 ; map_lookup_elem
578					JEQ		R0 			#0 => 0014 -- if (map_value ~= NULL)
579					MOV		R7 			#1
580					MOV		R8 			R0
581					STXDW	[R10-16] 	R0
582					XADDW	[R8+0] 		R7
583					MOV		R7 			#0
584					JEQ		R7 			#0 => 0025 -- skip false branch
585					STXDW	[R10-16] 	R0
586					LDDW	R1 			#42
587					STW		[R10-52] 	#0
588					MOV		R2 			R10
589					ADD		R2 			#4294967244
590					MOV		R4 			#0
591					STW		[R10-56] 	#5
592					MOV		R3 			R10
593					ADD		R3 			#4294967240
594					CALL	R0 			#2 ; map_update_elem
595					MOV		R0 			#0
596					EXIT	R0 			#0
597				]]
598			}
599		end)
600		it('array map (u32, const key replace xadd, const value) inverse nil check', function()
601			local array_map = makemap('array', 256)
602			compile {
603				input = function (skb)
604					local val = array_map[0]
605					if not val then
606						array_map[0] = 5
607					else
608						xadd(val, 1)
609					end
610				end,
611				expect = [[
612					LDDW	R1 			#42
613					STW		[R10-52] 	#0
614					MOV		R2 			R10
615					ADD		R2 			#4294967244
616					CALL	R0 			#1 ; map_lookup_elem
617					JNE		R0 			#0 => 0021
618					STXDW	[R10-16] 	R0
619					LDDW	R1 			#42
620					STW		[R10-52] 	#0
621					MOV		R2 			R10
622					ADD		R2 			#4294967244
623					MOV		R4 			#0
624					STW		[R10-56] 	#5
625					MOV		R3 			R10
626					ADD		R3 			#4294967240
627					CALL	R0 			#2 ; map_update_elem
628					MOV		R7 			#0
629					JEQ		R7 			#0 => 0025
630					MOV		R7 			#1
631					MOV		R8 			R0
632					STXDW	[R10-16] 	R0
633					XADDW	[R8+0] 		R7
634					MOV		R0 			#0
635					EXIT	R0 			#0
636				]]
637			}
638		end)
639		it('array map (struct, stack key load)', function()
640			local kv_t = 'struct { uint64_t a; uint64_t b; }'
641			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
642			compile {
643				input = function (skb)
644					local key = ffi.new(kv_t)
645					key.a = 2
646					key.b = 3
647					local val = array_map[key] -- Use composite key from stack memory
648					if val then
649						return val.a
650					end
651				end,
652				expect = [[
653					MOV		R0 			#0
654					STXDW	[R10-48] 	R0
655					STXDW	[R10-56] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
656					MOV		R7 			#2
657					STXDW	[R10-56] 	R7
658					MOV		R7 			#3
659					STXDW	[R10-48] 	R7
660					LDDW	R1			#42
661					MOV		R2			R10
662					ADD		R2			#4294967240
663					CALL	R0			#1 ; map_lookup_elem
664					JEQ		R0 			#0 => 0017
665					LDXDW	R7 			[R0+0]
666					MOV		R0 			R7
667					EXIT	R0 			#0
668					MOV		R0 			#0
669					EXIT	R0 			#0
670				]]
671			}
672		end)
673		it('array map (struct, stack key store)', function()
674			local kv_t = 'struct { uint64_t a; uint64_t b; }'
675			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
676			compile {
677				input = function (skb)
678					local key = ffi.new(kv_t)
679					key.a = 2
680					key.b = 3
681					array_map[key] = key -- Use composite key from stack memory
682				end,
683				expect = [[
684					MOV		R0 			#0
685					STXDW	[R10-40] 	R0
686					STXDW	[R10-48] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
687					MOV		R7 			#2
688					STXDW	[R10-48] 	R7
689					MOV		R7 			#3
690					STXDW	[R10-40] 	R7
691					LDDW	R1 			#42
692					MOV		R2 			R10
693					ADD		R2 			#4294967248
694					MOV		R4 			#0
695					MOV		R3 			R10
696					ADD		R3 			#4294967248
697					CALL	R0 			#2 ; map_update_elem
698					MOV		R0 			#0
699					EXIT	R0 			#0
700				]]
701			}
702		end)
703		it('array map (struct, stack/packet key update, const value)', function()
704			local kv_t = 'struct { uint64_t a; uint64_t b; }'
705			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
706			compile {
707				input = function (skb)
708					local key = ffi.new(kv_t)
709					key.a = eth.ip.tos   -- Load key part from dissector
710					local val = array_map[key]
711					if val then
712						val.a = 5
713					end
714				end,
715				expect = [[
716					MOV		R0 			#0
717					STXDW	[R10-48] 	R0
718					STXDW	[R10-56] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
719					LDB		R0 			skb[15]
720					STXDW	[R10-56] 	R0
721					LDDW	R1			#42
722					MOV		R2			R10
723					ADD		R2			#4294967240
724					CALL	R0			#1 ; map_lookup_elem
725					JEQ		R0 			#0 => 0014
726					MOV		R7 			#5
727					STXDW	[R0+0] 		R7
728					MOV		R0 			#0
729					EXIT	R0 			#0
730				]]
731			}
732		end)
733		it('array map (struct, stack/packet key update, map value)', function()
734			local kv_t = 'struct { uint64_t a; uint64_t b; }'
735			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
736			compile {
737				input = function (skb)
738					local key = ffi.new(kv_t)
739					key.a = eth.ip.tos   -- Load key part from dissector
740					local val = array_map[key]
741					if val then
742						val.a = val.b
743					end
744				end,
745				expect = [[
746					MOV		R0 			#0
747					STXDW	[R10-48] 	R0
748					STXDW	[R10-56] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
749					LDB		R0 			skb[15]
750					STXDW	[R10-56] 	R0
751					LDDW	R1			#42
752					MOV		R2			R10
753					ADD		R2			#4294967240
754					CALL	R0			#1 ; map_lookup_elem
755					JEQ		R0 			#0 => 0014
756					LDXDW	R7 			[R0+8]
757					STXDW	[R0+0] 		R7
758					MOV		R0 			#0
759					EXIT	R0 			#0
760				]]
761			}
762		end)
763		it('array map (struct, stack/packet key update, stack value)', function()
764			local kv_t = 'struct { uint64_t a; uint64_t b; }'
765			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
766			compile {
767				input = function (skb)
768					local key = ffi.new(kv_t)
769					key.a = eth.ip.tos   -- Load key part from dissector
770					local val = array_map[key]
771					if val then
772						val.a = key.b
773					end
774				end,
775				expect = [[
776					MOV		R0 			#0
777					STXDW	[R10-48] 	R0
778					STXDW	[R10-56] 	R0 -- NYI: erase zero-fill on allocation when it's loaded later
779					LDB		R0 			skb[15]
780					STXDW	[R10-56] 	R0
781					LDDW	R1			#42
782					MOV		R2			R10
783					ADD		R2			#4294967240
784					CALL	R0			#1 ; map_lookup_elem
785					JEQ		R0 			#0 => 0014
786					LDXDW	R7 			[R10-48]
787					STXDW	[R0+0] 		R7
788					MOV		R0 			#0
789					EXIT	R0 			#0
790				]]
791			}
792		end)
793		it('array map (struct, stack/packet key replace, stack value)', function()
794			local kv_t = 'struct { uint64_t a; uint64_t b; }'
795			local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t))
796			compile {
797				input = function (skb)
798					local key = ffi.new(kv_t)
799					key.a = eth.ip.tos   -- Load key part from dissector
800					local val = array_map[key]
801					if val then
802						val.a = key.b
803					else
804						array_map[key] = key
805					end
806				end,
807				expect = [[
808					MOV		R0 			#0
809					STXDW	[R10-48] 	R0
810					STXDW	[R10-56] 	R0
811					LDB		R0 			skb[15]
812					STXDW	[R10-56] 	R0
813					LDDW	R1 			#42
814					MOV		R2 			R10
815					ADD		R2 			#4294967240
816					CALL	R0 			#1 ; map_lookup_elem
817					JEQ		R0 			#0 => 0016 -- if (map_value ~= NULL)
818					LDXDW	R7 			[R10-48]
819					STXDW	[R0+0] 		R7
820					MOV		R7 			#0
821					JEQ		R7 			#0 => 0026 -- jump over false branch
822					STXDW	[R10-24] 	R0
823					LDDW	R1 			#42
824					MOV		R2 			R10
825					ADD		R2 			#4294967240
826					MOV		R4 			#0
827					MOV		R3 			R10
828					ADD		R3 			#4294967240
829					CALL	R0 			#2 ; map_update_elem
830					LDXDW	R0 			[R10-24]
831					MOV		R0 			#0
832					EXIT	R0 			#0
833				]]
834			}
835		end)
836	end)
837	describe('control flow', function()
838		it('condition with constant return', function()
839			compile {
840				input = function (skb)
841					local v = eth.ip.tos
842					if v then
843						return 1
844					else
845						return 0
846					end
847				end,
848				expect = [[
849					LDB		R0 			skb[15]
850					JEQ		R0 			#0 => 0005
851					MOV		R0 			#1
852					EXIT	R0 			#0
853					MOV		R0 			#0 -- 0005 jump target
854					EXIT	R0 			#0
855				]]
856			}
857		end)
858		it('condition with cdata constant return', function()
859			local cdata = 2ULL
860			compile {
861				input = function (skb)
862					local v = eth.ip.tos
863					if v then
864						return cdata + 1
865					else
866						return 0
867					end
868				end,
869				expect = [[
870					LDB		R0 			skb[15]
871					JEQ		R0 			#0 => 0006
872					LDDW	R0 			#3
873					EXIT	R0 			#0
874					MOV		R0 			#0 -- 0006 jump target
875					EXIT	R0 			#0
876				]]
877			}
878		end)
879		it('condition with constant return (inversed)', function()
880			compile {
881				input = function (skb)
882					local v = eth.ip.tos
883					if not v then
884						return 1
885					else
886						return 0
887					end
888				end,
889				expect = [[
890					LDB		R0 			skb[15]
891					JNE		R0 			#0 => 0005
892					MOV		R0 			#1
893					EXIT	R0 			#0
894					MOV		R0 			#0 -- 0005 jump target
895					EXIT	R0 			#0
896				]]
897			}
898		end)
899		it('condition with variable mutation', function()
900			compile {
901				input = function (skb)
902					local v = 0
903					if eth.ip.tos then
904						v = 1
905					end
906					return v
907				end,
908				expect = [[
909					LDB		R0 			skb[15]
910					MOV		R1 			#0
911					STXDW	[R10-16] 	R1
912					JEQ		R0 			#0 => 0007
913					MOV		R7 			#1
914					STXDW	[R10-16] 	R7
915					LDXDW	R0 			[R10-16]
916					EXIT	R0 			#0
917				]]
918			}
919		end)
920		it('condition with nil variable mutation', function()
921			compile {
922				input = function (skb)
923					local v -- nil, will be elided
924					if eth.ip.tos then
925						v = 1
926					else
927						v = 0
928					end
929					return v
930				end,
931				expect = [[
932					LDB		R0 			skb[15]
933					JEQ		R0 			#0 => 0007
934					MOV		R7 			#1
935					STXDW	[R10-16] 	R7
936					MOV		R7 			#0
937					JEQ		R7 			#0 => 0009
938					MOV		R7 			#0
939					STXDW	[R10-16] 	R7
940					LDXDW	R0 			[R10-16]
941					EXIT	R0 			#0
942				]]
943			}
944		end)
945		it('nested condition with variable mutation', function()
946			compile {
947				input = function (skb)
948					local v = 0
949					local tos = eth.ip.tos
950					if tos then
951						if tos > 5 then
952							v = 5
953						else
954							v = 1
955						end
956					end
957					return v
958				end,
959				expect = [[
960					LDB		R0 			skb[15]
961					MOV		R1 			#0
962					STXDW	[R10-16] 	R1 -- materialize v = 0
963					JEQ		R0 			#0 => 0013 -- if not tos
964					MOV		R7 			#5
965					JGE		R7 			R0 => 0011 -- if 5 > tos
966					MOV		R7 			#5
967					STXDW	[R10-16] 	R7 -- materialize v = 5
968					MOV		R7 			#0
969					JEQ		R7 			#0 => 0013
970					MOV		R7 			#1 -- 0011 jump target
971					STXDW	[R10-16]	R7 -- materialize v = 1
972					LDXDW	R0 			[R10-16]
973					EXIT	R0 			#0
974				]]
975			}
976		end)
977		it('nested condition with variable shadowing', function()
978			compile {
979				input = function (skb)
980					local v = 0
981					local tos = eth.ip.tos
982					if tos then
983						local v = 0 -- luacheck: ignore 231
984						if tos > 5 then
985							v = 5 -- changing shadowing variable
986						end
987					else
988						v = 1
989					end
990					return v
991				end,
992				expect = [[
993					LDB		R0 			skb[15]
994					MOV		R1 			#0
995					STXDW	[R10-16] 	R1 -- materialize v = 0
996					JEQ		R0 			#0 => 0011 -- if not tos
997					MOV		R7 			#5
998					MOV		R1 			#0
999					STXDW	[R10-32] 	R1 -- materialize shadowing variable
1000					JGE		R7 			R0 => 0013 -- if 5 > tos
1001					MOV		R7 			#0 -- erased 'v = 5' dead store
1002					JEQ		R7 			#0 => 0013
1003					MOV		R7 			#1 -- 0011 jump target
1004					STXDW	[R10-16]	R7 -- materialize v = 1
1005					LDXDW	R0 			[R10-16] -- 0013 jump target
1006					EXIT	R0 			#0
1007				]]
1008			}
1009		end)
1010		it('condition materializes shadowing variable at the end of BB', function()
1011			compile {
1012				input = function (skb)
1013					local v = time()
1014					local v1 = 0 -- luacheck: ignore 231
1015					if eth.ip.tos then
1016						v1 = v
1017					end
1018				end,
1019				expect = [[
1020					CALL	R0 			#5 ; ktime_get_ns
1021					STXDW	[R10-16] 	R0
1022					LDB		R0 			skb[15]
1023					MOV		R1 			#0
1024					STXDW	[R10-24] 	R1 -- materialize v1 = 0
1025					JEQ		R0 			#0 => 0009
1026					LDXDW	R7 			[R10-16]
1027					STXDW	[R10-24] 	R7 -- v1 = v0
1028					MOV		R0 #0
1029					EXIT	R0 #0
1030				]]
1031			}
1032		end)
1033
1034	end)
1035end)
1036