| local ffi = require('ffi') |
| local S = require('syscall') |
| |
| -- Normalize whitespace and remove empty lines |
| local function normalize_code(c) |
| local res = {} |
| for line in string.gmatch(c,'[^\r\n]+') do |
| local op, d, s, t = line:match('(%S+)%s+(%S+)%s+(%S+)%s*([^-]*)') |
| if op then |
| t = t and t:match('^%s*(.-)%s*$') |
| table.insert(res, string.format('%s\t%s %s %s', op, d, s, t)) |
| end |
| end |
| return table.concat(res, '\n') |
| end |
| |
| -- Compile code and check result |
| local function compile(t) |
| local bpf = require('bpf') |
| -- require('jit.bc').dump(t.input) |
| local code, err = bpf(t.input) |
| assert.truthy(code) |
| assert.falsy(err) |
| if code then |
| if t.expect then |
| local got = normalize_code(bpf.dump_string(code, 1, true)) |
| -- if normalize_code(t.expect) ~= got then print(bpf.dump_string(code, 1)) end |
| assert.same(normalize_code(t.expect), got) |
| end |
| end |
| end |
| |
| -- Make a mock map variable |
| local function makemap(type, max_entries, key_ctype, val_ctype) |
| if not key_ctype then key_ctype = ffi.typeof('uint32_t') end |
| if not val_ctype then val_ctype = ffi.typeof('uint32_t') end |
| if not max_entries then max_entries = 4096 end |
| return { |
| __map = true, |
| max_entries = max_entries, |
| key = ffi.new(ffi.typeof('$ [1]', key_ctype)), |
| val = ffi.new(ffi.typeof('$ [1]', val_ctype)), |
| map_type = S.c.BPF_MAP[type], |
| key_type = key_ctype, |
| val_type = val_ctype, |
| fd = 42, |
| } |
| end |
| |
| describe('codegen', function() |
| -- luacheck: ignore 113 211 212 311 511 |
| |
| describe('constants', function() |
| it('remove dead constant store', function() |
| compile { |
| input = function () |
| local proto = 5 |
| end, |
| expect = [[ |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize constant', function() |
| compile { |
| input = function () |
| return 5 |
| end, |
| expect = [[ |
| MOV R0 #5 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize constant longer than i32', function() |
| compile { |
| input = function () |
| return 4294967295 |
| end, |
| expect = [[ |
| LDDW R0 #4294967295 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize cdata constant', function() |
| compile { |
| input = function () |
| return 5ULL |
| end, |
| expect = [[ |
| LDDW R0 #5 -- composed instruction |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize signed cdata constant', function() |
| compile { |
| input = function () |
| return 5LL |
| end, |
| expect = [[ |
| LDDW R0 #5 -- composed instruction |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize coercible numeric cdata constant', function() |
| compile { |
| input = function () |
| return 0x00005 |
| end, |
| expect = [[ |
| MOV R0 #5 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('materialize constant through variable', function() |
| compile { |
| input = function () |
| local proto = 5 |
| return proto |
| end, |
| expect = [[ |
| MOV R0 #5 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('eliminate constant expressions', function() |
| compile { |
| input = function () |
| return 2 + 3 - 0 |
| end, |
| expect = [[ |
| MOV R0 #5 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('eliminate constant expressions (if block)', function() |
| compile { |
| input = function () |
| local proto = 5 |
| if proto == 5 then |
| proto = 1 |
| end |
| return proto |
| end, |
| expect = [[ |
| MOV R0 #1 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('eliminate negative constant expressions (if block) NYI', function() |
| -- always negative condition is not fully eliminated |
| compile { |
| input = function () |
| local proto = 5 |
| if false then |
| proto = 1 |
| end |
| return proto |
| end, |
| expect = [[ |
| MOV R7 #5 |
| STXDW [R10-8] R7 |
| MOV R7 #0 |
| JEQ R7 #0 => 0005 |
| LDXDW R0 [R10-8] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| end) |
| |
| describe('variables', function() |
| it('classic packet access (fold constant offset)', function() |
| compile { |
| input = function (skb) |
| return eth.ip.tos -- constant expression will fold |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('classic packet access (load non-constant offset)', function() |
| compile { |
| input = function (skb) |
| return eth.ip.udp.src_port -- need to skip variable-length header |
| end, |
| expect = [[ |
| LDB R0 skb[14] |
| AND R0 #15 |
| LSH R0 #2 |
| ADD R0 #14 |
| STXDW [R10-16] R0 -- NYI: erase dead store |
| LDH R0 skb[R0+0] |
| END R0 R0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('classic packet access (manipulate dissector offset)', function() |
| compile { |
| input = function (skb) |
| local ptr = eth.ip.udp.data + 1 |
| return ptr[0] -- dereference dissector pointer |
| end, |
| expect = [[ |
| LDB R0 skb[14] |
| AND R0 #15 |
| LSH R0 #2 |
| ADD R0 #14 -- NYI: fuse commutative operations in second pass |
| ADD R0 #8 |
| ADD R0 #1 |
| STXDW [R10-16] R0 |
| LDB R0 skb[R0+0] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('classic packet access (multi-byte load)', function() |
| compile { |
| input = function (skb) |
| local ptr = eth.ip.udp.data |
| return ptr(1, 5) -- load 4 bytes |
| end, |
| expect = [[ |
| LDB R0 skb[14] |
| AND R0 #15 |
| LSH R0 #2 |
| ADD R0 #14 |
| ADD R0 #8 |
| MOV R7 R0 |
| STXDW [R10-16] R0 -- NYI: erase dead store |
| LDW R0 skb[R7+1] |
| END R0 R0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('direct skb field access', function() |
| compile { |
| input = function (skb) |
| return skb.len |
| end, |
| expect = [[ |
| LDXW R7 [R6+0] |
| MOV R0 R7 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('direct skb data access (manipulate offset)', function() |
| compile { |
| input = function (skb) |
| local ptr = skb.data + 5 |
| return ptr[0] |
| end, |
| expect = [[ |
| LDXW R7 [R6+76] |
| ADD R7 #5 |
| LDXB R8 [R7+0] -- NYI: transform LD + ADD to LD + offset addressing |
| MOV R0 R8 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('direct skb data access (offset boundary check)', function() |
| compile { |
| input = function (skb) |
| local ptr = skb.data + 5 |
| if ptr < skb.data_end then |
| return ptr[0] |
| end |
| end, |
| expect = [[ |
| LDXW R7 [R6+76] |
| ADD R7 #5 |
| LDXW R8 [R6+80] |
| JGE R7 R8 => 0008 |
| LDXB R8 [R7+0] |
| MOV R0 R8 |
| EXIT R0 #0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (array, const load, const store)', function() |
| compile { |
| input = function (skb) |
| local mem = ffi.new('uint8_t [16]') |
| mem[0] = 5 |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-40] R0 |
| STXDW [R10-48] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| STB [R10-48] #5 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (array, const load, packet store)', function() |
| compile { |
| input = function (skb) |
| local mem = ffi.new('uint8_t [7]') |
| mem[0] = eth.ip.tos |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-40] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| STXB [R10-40] R0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (array, packet load, const store)', function() |
| compile { |
| input = function (skb) |
| local mem = ffi.new('uint8_t [1]') |
| mem[eth.ip.tos] = 5 |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| MOV R7 R0 |
| ADD R7 R10 |
| STB [R7-48] #5 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (array, packet load, packet store)', function() |
| compile { |
| input = function (skb) |
| local mem = ffi.new('uint8_t [7]') |
| local v = eth.ip.tos |
| mem[v] = v |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-40] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| MOV R7 R0 |
| ADD R7 R10 |
| STXB [R7-40] R0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (struct, const/packet store)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| compile { |
| input = function (skb) |
| local mem = ffi.new(kv_t) |
| mem.a = 5 |
| mem.b = eth.ip.tos |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-40] R0 |
| STXDW [R10-48] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| MOV R7 #5 |
| STXDW [R10-48] R7 |
| LDB R0 skb[15] |
| STXDW [R10-40] R0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('access stack memory (struct, const/stack store)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| compile { |
| input = function (skb) |
| local m1 = ffi.new(kv_t) |
| local m2 = ffi.new(kv_t) |
| m1.a = 5 |
| m2.b = m1.a |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| MOV R0 #0 |
| STXDW [R10-64] R0 |
| STXDW [R10-72] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| MOV R7 #5 |
| STXDW [R10-56] R7 |
| LDXDW R7 [R10-56] |
| STXDW [R10-64] R7 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key load)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| return array_map[0] |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-28] #0 |
| MOV R2 R10 |
| ADD R2 #4294967268 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0009 |
| LDXW R0 [R0+0] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, packet key load)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| return array_map[eth.ip.tos] |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| LDDW R1 #42 |
| STXW [R10-36] R0 |
| MOV R2 R10 |
| ADD R2 #4294967260 |
| STXDW [R10-24] R0 -- NYI: erase dead store |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0011 |
| LDXW R0 [R0+0] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key store, const value)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| array_map[0] = 5 |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-36] #0 |
| MOV R2 R10 |
| ADD R2 #4294967260 |
| MOV R4 #0 |
| STW [R10-40] #5 |
| MOV R3 R10 |
| ADD R3 #4294967256 |
| CALL R0 #2 ; map_update_elem |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key store, packet value)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| array_map[0] = eth.ip.tos |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| STXDW [R10-24] R0 |
| LDDW R1 #42 |
| STW [R10-36] #0 |
| MOV R2 R10 |
| ADD R2 #4294967260 |
| MOV R4 #0 |
| MOV R3 R10 |
| ADD R3 #4294967272 |
| CALL R0 #2 ; map_update_elem |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key store, map value)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| array_map[0] = array_map[1] |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-36] #1 |
| MOV R2 R10 |
| ADD R2 #4294967260 |
| CALL R0 #1 ; map_lookup_elem |
| STXDW [R10-24] R0 |
| LDDW R1 #42 |
| STW [R10-36] #0 |
| MOV R2 R10 |
| ADD R2 #4294967260 |
| MOV R4 #0 |
| LDXDW R3 [R10-24] |
| JEQ R3 #0 => 0017 |
| LDXW R3 [R3+0] |
| STXW [R10-40] R3 |
| MOV R3 R10 |
| ADD R3 #4294967256 |
| CALL R0 #2 ; map_update_elem |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key replace, const value)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| local val = array_map[0] |
| if val then |
| val[0] = val[0] + 1 |
| else |
| array_map[0] = 5 |
| end |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-44] #0 |
| MOV R2 R10 |
| ADD R2 #4294967252 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0013 -- if (map_value ~= NULL) |
| LDXW R7 [R0+0] |
| ADD R7 #1 |
| STXW [R0+0] R7 |
| MOV R7 #0 |
| JEQ R7 #0 => 0025 -- skip false branch |
| STXDW [R10-16] R0 |
| LDDW R1 #42 |
| STW [R10-44] #0 |
| MOV R2 R10 |
| ADD R2 #4294967252 |
| MOV R4 #0 |
| STW [R10-48] #5 |
| MOV R3 R10 |
| ADD R3 #4294967248 |
| CALL R0 #2 ; map_update_elem |
| LDXDW R0 [R10-16] |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key replace xadd, const value)', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| local val = array_map[0] |
| if val then |
| xadd(val, 1) |
| else |
| array_map[0] = 5 |
| end |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-52] #0 |
| MOV R2 R10 |
| ADD R2 #4294967244 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0014 -- if (map_value ~= NULL) |
| MOV R7 #1 |
| MOV R8 R0 |
| STXDW [R10-16] R0 |
| XADDW [R8+0] R7 |
| MOV R7 #0 |
| JEQ R7 #0 => 0025 -- skip false branch |
| STXDW [R10-16] R0 |
| LDDW R1 #42 |
| STW [R10-52] #0 |
| MOV R2 R10 |
| ADD R2 #4294967244 |
| MOV R4 #0 |
| STW [R10-56] #5 |
| MOV R3 R10 |
| ADD R3 #4294967240 |
| CALL R0 #2 ; map_update_elem |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (u32, const key replace xadd, const value) inverse nil check', function() |
| local array_map = makemap('array', 256) |
| compile { |
| input = function (skb) |
| local val = array_map[0] |
| if not val then |
| array_map[0] = 5 |
| else |
| xadd(val, 1) |
| end |
| end, |
| expect = [[ |
| LDDW R1 #42 |
| STW [R10-52] #0 |
| MOV R2 R10 |
| ADD R2 #4294967244 |
| CALL R0 #1 ; map_lookup_elem |
| JNE R0 #0 => 0021 |
| STXDW [R10-16] R0 |
| LDDW R1 #42 |
| STW [R10-52] #0 |
| MOV R2 R10 |
| ADD R2 #4294967244 |
| MOV R4 #0 |
| STW [R10-56] #5 |
| MOV R3 R10 |
| ADD R3 #4294967240 |
| CALL R0 #2 ; map_update_elem |
| MOV R7 #0 |
| JEQ R7 #0 => 0025 |
| MOV R7 #1 |
| MOV R8 R0 |
| STXDW [R10-16] R0 |
| XADDW [R8+0] R7 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack key load)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = 2 |
| key.b = 3 |
| local val = array_map[key] -- Use composite key from stack memory |
| if val then |
| return val.a |
| end |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| MOV R7 #2 |
| STXDW [R10-56] R7 |
| MOV R7 #3 |
| STXDW [R10-48] R7 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0017 |
| LDXDW R7 [R0+0] |
| MOV R0 R7 |
| EXIT R0 #0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack key store)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = 2 |
| key.b = 3 |
| array_map[key] = key -- Use composite key from stack memory |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-40] R0 |
| STXDW [R10-48] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| MOV R7 #2 |
| STXDW [R10-48] R7 |
| MOV R7 #3 |
| STXDW [R10-40] R7 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967248 |
| MOV R4 #0 |
| MOV R3 R10 |
| ADD R3 #4294967248 |
| CALL R0 #2 ; map_update_elem |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack/packet key update, const value)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = eth.ip.tos -- Load key part from dissector |
| local val = array_map[key] |
| if val then |
| val.a = 5 |
| end |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| STXDW [R10-56] R0 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0014 |
| MOV R7 #5 |
| STXDW [R0+0] R7 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack/packet key update, map value)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = eth.ip.tos -- Load key part from dissector |
| local val = array_map[key] |
| if val then |
| val.a = val.b |
| end |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| STXDW [R10-56] R0 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0014 |
| LDXDW R7 [R0+8] |
| STXDW [R0+0] R7 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack/packet key update, stack value)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = eth.ip.tos -- Load key part from dissector |
| local val = array_map[key] |
| if val then |
| val.a = key.b |
| end |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 -- NYI: erase zero-fill on allocation when it's loaded later |
| LDB R0 skb[15] |
| STXDW [R10-56] R0 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0014 |
| LDXDW R7 [R10-48] |
| STXDW [R0+0] R7 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('array map (struct, stack/packet key replace, stack value)', function() |
| local kv_t = 'struct { uint64_t a; uint64_t b; }' |
| local array_map = makemap('array', 256, ffi.typeof(kv_t), ffi.typeof(kv_t)) |
| compile { |
| input = function (skb) |
| local key = ffi.new(kv_t) |
| key.a = eth.ip.tos -- Load key part from dissector |
| local val = array_map[key] |
| if val then |
| val.a = key.b |
| else |
| array_map[key] = key |
| end |
| end, |
| expect = [[ |
| MOV R0 #0 |
| STXDW [R10-48] R0 |
| STXDW [R10-56] R0 |
| LDB R0 skb[15] |
| STXDW [R10-56] R0 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| CALL R0 #1 ; map_lookup_elem |
| JEQ R0 #0 => 0016 -- if (map_value ~= NULL) |
| LDXDW R7 [R10-48] |
| STXDW [R0+0] R7 |
| MOV R7 #0 |
| JEQ R7 #0 => 0026 -- jump over false branch |
| STXDW [R10-24] R0 |
| LDDW R1 #42 |
| MOV R2 R10 |
| ADD R2 #4294967240 |
| MOV R4 #0 |
| MOV R3 R10 |
| ADD R3 #4294967240 |
| CALL R0 #2 ; map_update_elem |
| LDXDW R0 [R10-24] |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| end) |
| describe('control flow', function() |
| it('condition with constant return', function() |
| compile { |
| input = function (skb) |
| local v = eth.ip.tos |
| if v then |
| return 1 |
| else |
| return 0 |
| end |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| JEQ R0 #0 => 0005 |
| MOV R0 #1 |
| EXIT R0 #0 |
| MOV R0 #0 -- 0005 jump target |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('condition with cdata constant return', function() |
| local cdata = 2ULL |
| compile { |
| input = function (skb) |
| local v = eth.ip.tos |
| if v then |
| return cdata + 1 |
| else |
| return 0 |
| end |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| JEQ R0 #0 => 0006 |
| LDDW R0 #3 |
| EXIT R0 #0 |
| MOV R0 #0 -- 0006 jump target |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('condition with constant return (inversed)', function() |
| compile { |
| input = function (skb) |
| local v = eth.ip.tos |
| if not v then |
| return 1 |
| else |
| return 0 |
| end |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| JNE R0 #0 => 0005 |
| MOV R0 #1 |
| EXIT R0 #0 |
| MOV R0 #0 -- 0005 jump target |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('condition with variable mutation', function() |
| compile { |
| input = function (skb) |
| local v = 0 |
| if eth.ip.tos then |
| v = 1 |
| end |
| return v |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| MOV R1 #0 |
| STXDW [R10-16] R1 |
| JEQ R0 #0 => 0007 |
| MOV R7 #1 |
| STXDW [R10-16] R7 |
| LDXDW R0 [R10-16] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('condition with nil variable mutation', function() |
| compile { |
| input = function (skb) |
| local v -- nil, will be elided |
| if eth.ip.tos then |
| v = 1 |
| else |
| v = 0 |
| end |
| return v |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| JEQ R0 #0 => 0007 |
| MOV R7 #1 |
| STXDW [R10-16] R7 |
| MOV R7 #0 |
| JEQ R7 #0 => 0009 |
| MOV R7 #0 |
| STXDW [R10-16] R7 |
| LDXDW R0 [R10-16] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('nested condition with variable mutation', function() |
| compile { |
| input = function (skb) |
| local v = 0 |
| local tos = eth.ip.tos |
| if tos then |
| if tos > 5 then |
| v = 5 |
| else |
| v = 1 |
| end |
| end |
| return v |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| MOV R1 #0 |
| STXDW [R10-16] R1 -- materialize v = 0 |
| JEQ R0 #0 => 0013 -- if not tos |
| MOV R7 #5 |
| JGE R7 R0 => 0011 -- if 5 > tos |
| MOV R7 #5 |
| STXDW [R10-16] R7 -- materialize v = 5 |
| MOV R7 #0 |
| JEQ R7 #0 => 0013 |
| MOV R7 #1 -- 0011 jump target |
| STXDW [R10-16] R7 -- materialize v = 1 |
| LDXDW R0 [R10-16] |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('nested condition with variable shadowing', function() |
| compile { |
| input = function (skb) |
| local v = 0 |
| local tos = eth.ip.tos |
| if tos then |
| local v = 0 -- luacheck: ignore 231 |
| if tos > 5 then |
| v = 5 -- changing shadowing variable |
| end |
| else |
| v = 1 |
| end |
| return v |
| end, |
| expect = [[ |
| LDB R0 skb[15] |
| MOV R1 #0 |
| STXDW [R10-16] R1 -- materialize v = 0 |
| JEQ R0 #0 => 0011 -- if not tos |
| MOV R7 #5 |
| MOV R1 #0 |
| STXDW [R10-32] R1 -- materialize shadowing variable |
| JGE R7 R0 => 0013 -- if 5 > tos |
| MOV R7 #0 -- erased 'v = 5' dead store |
| JEQ R7 #0 => 0013 |
| MOV R7 #1 -- 0011 jump target |
| STXDW [R10-16] R7 -- materialize v = 1 |
| LDXDW R0 [R10-16] -- 0013 jump target |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| it('condition materializes shadowing variable at the end of BB', function() |
| compile { |
| input = function (skb) |
| local v = time() |
| local v1 = 0 -- luacheck: ignore 231 |
| if eth.ip.tos then |
| v1 = v |
| end |
| end, |
| expect = [[ |
| CALL R0 #5 ; ktime_get_ns |
| STXDW [R10-16] R0 |
| LDB R0 skb[15] |
| MOV R1 #0 |
| STXDW [R10-24] R1 -- materialize v1 = 0 |
| JEQ R0 #0 => 0009 |
| LDXDW R7 [R10-16] |
| STXDW [R10-24] R7 -- v1 = v0 |
| MOV R0 #0 |
| EXIT R0 #0 |
| ]] |
| } |
| end) |
| |
| end) |
| end) |