builtins

--[[
]]
local ffi = require('ffi')
local bit = require('bit')
local cdef = require('bpf.cdef')
local BPF, HELPER = ffi.typeof('struct bpf'), ffi.typeof('struct bpf_func_id')
local const_width = {
	[1] = BPF.B, [2] = BPF.H, [4] = BPF.W, [8] = BPF.DW,
}
local const_width_type = {
	[1] = ffi.typeof('uint8_t'), [2] = ffi.typeof('uint16_t'), [4] = ffi.typeof('uint32_t'), [8] = ffi.typeof('uint64_t'),
}
-- Built-ins that will be translated into BPF instructions
-- i.e. bit.bor(0xf0, 0x0f) becomes {'alu64, or, k', reg(0xf0), reg(0x0f), 0, 0}
local builtins = {
	[bit.lshift]  = 'LSH',
	[bit.rshift]  = 'RSH',
	[bit.band]    = 'AND',
	[bit.bnot]    = 'NEG',
	[bit.bor]     = 'OR',
	[bit.bxor]    = 'XOR',
	[bit.arshift] = 'ARSH',
	-- Extensions and intrinsics
}
local function width_type(w)
	-- Note: ffi.typeof doesn't accept '?' as template
	return const_width_type[w] or ffi.typeof(string.format('uint8_t [%d]', w))
end
builtins.width_type = width_type
-- Return struct member size/type (requires LuaJIT 2.1+)
-- I am ashamed that there's no easier way around it.
local function sizeofattr(ct, name)
	if not ffi.typeinfo then error('LuaJIT 2.1+ is required for ffi.typeinfo') end
	local cinfo = ffi.typeinfo(ct)
	while true do
		cinfo = ffi.typeinfo(cinfo.sib)
		if not cinfo then return end
		if cinfo.name == name then break end
	end
	local size = math.max(1, ffi.typeinfo(cinfo.sib or ct).size - cinfo.size)
	-- Guess type name
	return size, builtins.width_type(size)
end
builtins.sizeofattr = sizeofattr
-- Byte-order conversions for little endian
local function ntoh(x, w)
	if w then x = ffi.cast(const_width_type[w/8], x) end
	return bit.bswap(x)
end
local function hton(x, w) return ntoh(x, w) end
builtins.ntoh = ntoh
builtins.hton = hton
builtins[ntoh] = function (e, dst, a, w)
	-- This is trickery, but TO_LE means cpu_to_le(),
	-- and we want exactly the opposite as network is always 'be'
	w = w or ffi.sizeof(e.V[a].type)*8
	if w == 8 then return end -- NOOP
	assert(w <= 64, 'NYI: hton(a[, width]) - operand larger than register width')
	-- Allocate registers and execute
	e.vcopy(dst, a)
	e.emit(BPF.ALU + BPF.END + BPF.TO_BE, e.vreg(dst), 0, 0, w)
end
builtins[hton] = function (e, dst, a, w)
	w = w or ffi.sizeof(e.V[a].type)*8
	if w == 8 then return end -- NOOP
	assert(w <= 64, 'NYI: hton(a[, width]) - operand larger than register width')
	-- Allocate registers and execute
	e.vcopy(dst, a)
	e.emit(BPF.ALU + BPF.END + BPF.TO_LE, e.vreg(dst), 0, 0, w)
end
-- Byte-order conversions for big endian are no-ops
if ffi.abi('be') then
	ntoh = function (x, w)
		return w and ffi.cast(const_width_type[w/8], x) or x
	end
	hton = ntoh
	builtins[ntoh] = function(_, _, _) return end
	builtins[hton] = function(_, _, _) return end
end
-- Other built-ins
local function xadd() error('NYI') end
builtins.xadd = xadd
builtins[xadd] = function (e, ret, a, b, off)
	local vinfo = e.V[a].const
	assert(vinfo and vinfo.__dissector, 'xadd(a, b[, offset]) called on non-pointer')
	local w = ffi.sizeof(vinfo.__dissector)
	-- Calculate structure attribute offsets
	if e.V[off] and type(e.V[off].const) == 'string' then
		local ct, field = vinfo.__dissector, e.V[off].const
		off = ffi.offsetof(ct, field)
		assert(off, 'xadd(a, b, offset) - offset is not valid in given structure')
		w = sizeofattr(ct, field)
	end
	assert(w == 4 or w == 8, 'NYI: xadd() - 1 and 2 byte atomic increments are not supported')
	-- Allocate registers and execute
	local src_reg = e.vreg(b)
	local dst_reg = e.vreg(a)
	-- Set variable for return value and call
	e.vset(ret)
	e.vreg(ret, 0, true, ffi.typeof('int32_t'))
	-- Optimize the NULL check away if provably not NULL
	if not e.V[a].source or e.V[a].source:find('_or_null', 1, true) then
		e.emit(BPF.JMP + BPF.JEQ + BPF.K, dst_reg, 0, 1, 0) -- if (dst != NULL)
	end
	e.emit(BPF.XADD + BPF.STX + const_width[w], dst_reg, src_reg, off or 0, 0)
end
local function probe_read() error('NYI') end
builtins.probe_read = probe_read
builtins[probe_read] = function (e, ret, dst, src, vtype, ofs)
	e.reg_alloc(e.tmpvar, 1)
	-- Load stack pointer to dst, since only load to stack memory is supported
	-- we have to use allocated stack memory or create a new allocation and convert
	-- to pointer type
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0)
	if not e.V[dst].const or not e.V[dst].const.__base > 0 then
		builtins[ffi.new](e, dst, vtype) -- Allocate stack memory
	end
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base)
	-- Set stack memory maximum size bound
	e.reg_alloc(e.tmpvar, 2)
	if not vtype then
		vtype = cdef.typename(e.V[dst].type)
		-- Dereference pointer type to pointed type for size calculation
		if vtype:sub(-1) == '*' then vtype = vtype:sub(0, -2) end
	end
	local w = ffi.sizeof(vtype)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, w)
	-- Set source pointer
	if e.V[src].reg then
		e.reg_alloc(e.tmpvar, 3) -- Copy from original register
		e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, e.V[src].reg, 0, 0)
	else
		e.vreg(src, 3)
		e.reg_spill(src) -- Spill to avoid overwriting
	end
	if ofs and ofs > 0 then
		e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 3, 0, 0, ofs)
	end
	-- Call probe read helper
	ret = ret or e.tmpvar
	e.vset(ret)
	e.vreg(ret, 0, true, ffi.typeof('int32_t'))
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.probe_read)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
builtins[ffi.cast] = function (e, dst, ct, x)
	assert(e.V[ct].const, 'ffi.cast(ctype, x) called with bad ctype')
	e.vcopy(dst, x)
	if e.V[x].const and type(e.V[x].const) == 'table' then
		e.V[dst].const.__dissector = ffi.typeof(e.V[ct].const)
	end
	e.V[dst].type = ffi.typeof(e.V[ct].const)
	-- Specific types also encode source of the data
	-- This is because BPF has different helpers for reading
	-- different data sources, so variables must track origins.
	-- struct pt_regs - source of the data is probe
	-- struct skb     - source of the data is socket buffer
	-- struct X       - source of the data is probe/tracepoint
	if ffi.typeof(e.V[ct].const) == ffi.typeof('struct pt_regs') then
		e.V[dst].source = 'ptr_to_probe'
	end
end
builtins[ffi.new] = function (e, dst, ct, x)
	if type(ct) == 'number' then
		ct = ffi.typeof(e.V[ct].const) -- Get ctype from variable
	end
	assert(not x, 'NYI: ffi.new(ctype, ...) - initializer is not supported')
	assert(not cdef.isptr(ct, true), 'NYI: ffi.new(ctype, ...) - ctype MUST NOT be a pointer')
	e.vset(dst, nil, ct)
	e.V[dst].source = 'ptr_to_stack'
	e.V[dst].const = {__base = e.valloc(ffi.sizeof(ct), true), __dissector = ct}
	-- Set array dissector if created an array
	-- e.g. if ct is 'char [2]', then dissector is 'char'
	local elem_type = tostring(ct):match('ctype<(.+)%s%[(%d+)%]>')
	if elem_type then
		e.V[dst].const.__dissector = ffi.typeof(elem_type)
	end
end
builtins[ffi.copy] = function (e, ret, dst, src)
	assert(cdef.isptr(e.V[dst].type), 'ffi.copy(dst, src) - dst MUST be a pointer type')
	assert(cdef.isptr(e.V[src].type), 'ffi.copy(dst, src) - src MUST be a pointer type')
	-- Specific types also encode source of the data
	-- struct pt_regs - source of the data is probe
	-- struct skb     - source of the data is socket buffer
	if e.V[src].source and e.V[src].source:find('ptr_to_probe', 1, true) then
		e.reg_alloc(e.tmpvar, 1)
		-- Load stack pointer to dst, since only load to stack memory is supported
		-- we have to either use spilled variable or allocated stack memory offset
		e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0)
		if e.V[dst].spill then
			e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].spill)
		elseif e.V[dst].const.__base then
			e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base)
		else error('ffi.copy(dst, src) - can\'t get stack offset of dst') end
		-- Set stack memory maximum size bound
		local dst_tname = cdef.typename(e.V[dst].type)
		if dst_tname:sub(-1) == '*' then dst_tname = dst_tname:sub(0, -2) end
		e.reg_alloc(e.tmpvar, 2)
		e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(dst_tname))
		-- Set source pointer
		if e.V[src].reg then
			e.reg_alloc(e.tmpvar, 3) -- Copy from original register
			e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, e.V[src].reg, 0, 0)
		else
			e.vreg(src, 3)
			e.reg_spill(src) -- Spill to avoid overwriting
		end
		-- Call probe read helper
		e.vset(ret)
		e.vreg(ret, 0, true, ffi.typeof('int32_t'))
		e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.probe_read)
		e.V[e.tmpvar].reg = nil  -- Free temporary registers
	elseif e.V[src].const and e.V[src].const.__map then
		error('NYI: ffi.copy(dst, src) - src is backed by BPF map')
	elseif e.V[src].const and e.V[src].const.__dissector then
		error('NYI: ffi.copy(dst, src) - src is backed by socket buffer')
	else
		-- TODO: identify cheap register move
		-- TODO: identify copy to/from stack
		error('NYI: ffi.copy(dst, src) - src is neither BPF map/socket buffer or probe')
	end
end
-- print(format, ...) builtin changes semantics from Lua print(...)
-- the first parameter has to be format and only reduced set of conversion specificers
-- is allowed: %d %u %x %ld %lu %lx %lld %llu %llx %p %s
builtins[print] = function (e, ret, fmt, a1, a2, a3)
	-- Load format string and length
	e.reg_alloc(e.V[e.tmpvar], 1)
	e.reg_alloc(e.V[e.tmpvar+1], 1)
	if type(e.V[fmt].const) == 'string' then
		local src = e.V[fmt].const
		local len = #src + 1
		local dst = e.valloc(len, src)
		-- TODO: this is materialize step
		e.V[fmt].const = {__base=dst}
		e.V[fmt].type = ffi.typeof('char ['..len..']')
	elseif e.V[fmt].const.__base then -- luacheck: ignore
		-- NOP
	else error('NYI: print(fmt, ...) - format variable is not literal/stack memory') end
	-- Prepare helper call
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0)
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[fmt].const.__base)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(e.V[fmt].type))
	if a1 then
		local args = {a1, a2, a3}
		assert(#args <= 3, 'print(fmt, ...) - maximum of 3 arguments supported')
		for i, arg in ipairs(args) do
			e.vcopy(e.tmpvar, arg)  -- Copy variable
			e.vreg(e.tmpvar, 3+i-1) -- Materialize it in arg register
		end
	end
	-- Call helper
	e.vset(ret)
	e.vreg(ret, 0, true, ffi.typeof('int32_t')) -- Return is integer
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.trace_printk)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
-- Implements bpf_perf_event_output(ctx, map, flags, var, vlen) on perf event map
local function perf_submit(e, dst, map_var, src)
	-- Set R2 = map fd (indirect load)
	local map = e.V[map_var].const
	e.vcopy(e.tmpvar, map_var)
	e.vreg(e.tmpvar, 2, true, ffi.typeof('uint64_t'))
	e.LD_IMM_X(2, BPF.PSEUDO_MAP_FD, map.fd, ffi.sizeof('uint64_t'))
	-- Set R1 = ctx
	e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy
	-- Set R3 = flags
	e.vset(e.tmpvar, nil, 0) -- BPF_F_CURRENT_CPU
	e.vreg(e.tmpvar, 3, false, ffi.typeof('uint64_t'))
	-- Set R4 = pointer to src on stack
	assert(e.V[src].const.__base, 'NYI: submit(map, var) - variable is not on stack')
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 4, 10, 0, 0)
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 4, 0, 0, -e.V[src].const.__base)
	-- Set R5 = src length
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 5, 0, 0, ffi.sizeof(e.V[src].type))
	-- Set R0 = ret and call
	e.vset(dst)
	e.vreg(dst, 0, true, ffi.typeof('int32_t')) -- Return is integer
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.perf_event_output)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
-- Implements bpf_skb_load_bytes(ctx, off, var, vlen) on skb->data
local function load_bytes(e, dst, off, var)
	-- Set R2 = offset
	e.vset(e.tmpvar, nil, off)
	e.vreg(e.tmpvar, 2, false, ffi.typeof('uint64_t'))
	-- Set R1 = ctx
	e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy
	-- Set R3 = pointer to var on stack
	assert(e.V[var].const.__base, 'NYI: load_bytes(off, var, len) - variable is not on stack')
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, 10, 0, 0)
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 3, 0, 0, -e.V[var].const.__base)
	-- Set R4 = var length
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 4, 0, 0, ffi.sizeof(e.V[var].type))
	-- Set R0 = ret and call
	e.vset(dst)
	e.vreg(dst, 0, true, ffi.typeof('int32_t')) -- Return is integer
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.skb_load_bytes)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
-- Implements bpf_get_stack_id()
local function stack_id(e, ret, map_var, key)
	-- Set R2 = map fd (indirect load)
	local map = e.V[map_var].const
	e.vcopy(e.tmpvar, map_var)
	e.vreg(e.tmpvar, 2, true, ffi.typeof('uint64_t'))
	e.LD_IMM_X(2, BPF.PSEUDO_MAP_FD, map.fd, ffi.sizeof('uint64_t'))
	-- Set R1 = ctx
	e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy
	-- Load flags in R2 (immediate value or key)
	local imm = e.V[key].const
	assert(tonumber(imm), 'NYI: stack_id(map, var), var must be constant number')
	e.reg_alloc(e.tmpvar, 3) -- Spill anything in R2 (unnamed tmp variable)
	e.LD_IMM_X(3, 0, imm, 8)
	-- Return R0 as signed integer
	e.vset(ret)
	e.vreg(ret, 0, true, ffi.typeof('int32_t'))
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.get_stackid)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
-- table.insert(table, value) keeps semantics with the exception of BPF maps
-- map `perf_event` -> submit inserted value
builtins[table.insert] = function (e, dst, map_var, value)
	assert(e.V[map_var].const.__map, 'NYI: table.insert() supported only on BPF maps')
	return perf_submit(e, dst, map_var, value)
end
-- bpf_get_current_comm(buffer) - write current process name to byte buffer
local function comm() error('NYI') end
builtins[comm] = function (e, ret, dst)
	-- Set R1 = buffer
	assert(e.V[dst].const.__base, 'NYI: comm(buffer) - buffer variable is not on stack')
	e.reg_alloc(e.tmpvar, 1) -- Spill
	e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0)
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base)
	-- Set R2 = length
	e.reg_alloc(e.tmpvar, 2) -- Spill
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(e.V[dst].type))
	-- Return is integer
	e.vset(ret)
	e.vreg(ret, 0, true, ffi.typeof('int32_t'))
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.get_current_comm)
	e.V[e.tmpvar].reg = nil  -- Free temporary registers
end
-- Math library built-ins
math.log2 = function () error('NYI') end
builtins[math.log2] = function (e, dst, x)
	-- Classic integer bits subdivison algorithm to find the position
	-- of the highest bit set, adapted for BPF bytecode-friendly operations.
	-- https://graphics.stanford.edu/~seander/bithacks.html
	-- r = 0
	local r = e.vreg(dst, nil, true)
	e.emit(BPF.ALU64 + BPF.MOV + BPF.K, r, 0, 0, 0)
	-- v = x
	e.vcopy(e.tmpvar, x)
	local v = e.vreg(e.tmpvar, 2)
	if cdef.isptr(e.V[x].const) then -- No pointer arithmetics, dereference
		e.vderef(v, v, {const = {__dissector=ffi.typeof('uint64_t')}})
	end
	-- Invert value to invert all tests, otherwise we would need and+jnz
	e.emit(BPF.ALU64 + BPF.NEG + BPF.K, v, 0, 0, 0)        -- v = ~v
	-- Unrolled test cases, converted masking to arithmetic as we don't have "if !(a & b)"
	-- As we're testing inverted value, we have to use arithmetic shift to copy MSB
	for i=4,0,-1 do
		local k = bit.lshift(1, i)
		e.emit(BPF.JMP + BPF.JGT + BPF.K, v, 0, 2, bit.bnot(bit.lshift(1, k))) -- if !upper_half(x)
		e.emit(BPF.ALU64 + BPF.ARSH + BPF.K, v, 0, 0, k)                       --     v >>= k
		e.emit(BPF.ALU64 + BPF.OR + BPF.K, r, 0, 0, k)                         --     r |= k
	end
	-- No longer constant, cleanup tmpvars
	e.V[dst].const = nil
	e.V[e.tmpvar].reg = nil
end
builtins[math.log10] = function (e, dst, x)
	-- Compute log2(x) and transform
	builtins[math.log2](e, dst, x)
	-- Relationship: log10(v) = log2(v) / log2(10)
	local r = e.V[dst].reg
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, r, 0, 0, 1)    -- Compensate round-down
	e.emit(BPF.ALU64 + BPF.MUL + BPF.K, r, 0, 0, 1233) -- log2(10) ~ 1233>>12
	e.emit(BPF.ALU64 + BPF.RSH + BPF.K, r, 0, 0, 12)
end
builtins[math.log] = function (e, dst, x)
	-- Compute log2(x) and transform
	builtins[math.log2](e, dst, x)
	-- Relationship: ln(v) = log2(v) / log2(e)
	local r = e.V[dst].reg
	e.emit(BPF.ALU64 + BPF.ADD + BPF.K, r, 0, 0, 1)    -- Compensate round-down
	e.emit(BPF.ALU64 + BPF.MUL + BPF.K, r, 0, 0, 2839) -- log2(e) ~ 2839>>12
	e.emit(BPF.ALU64 + BPF.RSH + BPF.K, r, 0, 0, 12)
end
-- Call-type helpers
local function call_helper(e, dst, h, vtype)
	e.vset(dst)
	e.vreg(dst, 0, true, vtype or ffi.typeof('uint64_t'))
	e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, h)
	e.V[dst].const = nil -- Target is not a function anymore
end
local function cpu() error('NYI') end
local function rand() error('NYI') end
local function time() error('NYI') end
local function pid_tgid() error('NYI') end
local function uid_gid() error('NYI') end
-- Export helpers and builtin variants
builtins.cpu = cpu
builtins.time = time
builtins.pid_tgid = pid_tgid
builtins.uid_gid = uid_gid
builtins.comm = comm
builtins.perf_submit = perf_submit
builtins.stack_id = stack_id
builtins.load_bytes = load_bytes
builtins[cpu] = function (e, dst) return call_helper(e, dst, HELPER.get_smp_processor_id) end
builtins[rand] = function (e, dst) return call_helper(e, dst, HELPER.get_prandom_u32, ffi.typeof('uint32_t')) end
builtins[time] = function (e, dst) return call_helper(e, dst, HELPER.ktime_get_ns) end
builtins[pid_tgid] = function (e, dst) return call_helper(e, dst, HELPER.get_current_pid_tgid) end
builtins[uid_gid] = function (e, dst) return call_helper(e, dst, HELPER.get_current_uid_gid) end
builtins[perf_submit] = function (e, dst, map, value) return perf_submit(e, dst, map, value) end
builtins[stack_id] = function (e, dst, map, key) return stack_id(e, dst, map, key) end
builtins[load_bytes] = function (e, dst, off, var, len) return load_bytes(e, dst, off, var, len) end
return builtins


elf

--[[
]]
-- This is a tiny wrapper over libelf to extract load address
-- and offsets of dynamic symbols
local S = require('syscall')
local ffi = require('ffi')
ffi.cdef [[
/* Type for a 16-bit quantity.  */
typedef uint16_t Elf32_Half;
typedef uint16_t Elf64_Half;
/* Types for signed and unsigned 32-bit quantities.  */
typedef uint32_t Elf32_Word;
typedef int32_t  Elf32_Sword;
typedef uint32_t Elf64_Word;
typedef int32_t  Elf64_Sword;
/* Types for signed and unsigned 64-bit quantities.  */
typedef uint64_t Elf32_Xword;
typedef int64_t  Elf32_Sxword;
typedef uint64_t Elf64_Xword;
typedef int64_t  Elf64_Sxword;
/* Type of addresses.  */
typedef uint32_t Elf32_Addr;
typedef uint64_t Elf64_Addr;
/* Type of file offsets.  */
typedef uint32_t Elf32_Off;
typedef uint64_t Elf64_Off;
/* Type for section indices, which are 16-bit quantities.  */
typedef uint16_t Elf32_Section;
typedef uint16_t Elf64_Section;
/* Constants */
struct Elf_Cmd
{
  static const int READ              = 1;
  static const int RDWR              = 2;
  static const int WRITE             = 3;
  static const int CLR               = 4;
  static const int SET               = 5;
  static const int FDDONE            = 6;
  static const int FDREAD            = 7;
  static const int READ_MMAP         = 8;
  static const int RDWR_MMAP         = 9;
  static const int WRITE_MMAP        =10;
  static const int READ_MMAP_PRIVATE =11;
  static const int EMPTY             =12;
  static const int NUM               =13;
};
/* Descriptor for the ELF file.  */
typedef struct Elf Elf;
/* Descriptor for ELF file section.  */
typedef struct Elf_Scn Elf_Scn;
/* Container type for metatable */
struct Elf_object { int fd; Elf *elf; };
/* Program segment header.  */
typedef struct
{
  Elf64_Word    p_type;                 /* Segment type */
  Elf64_Word    p_flags;                /* Segment flags */
  Elf64_Off     p_offset;               /* Segment file offset */
  Elf64_Addr    p_vaddr;                /* Segment virtual address */
  Elf64_Addr    p_paddr;                /* Segment physical address */
  Elf64_Xword   p_filesz;               /* Segment size in file */
  Elf64_Xword   p_memsz;                /* Segment size in memory */
  Elf64_Xword   p_align;                /* Segment alignment */
} Elf64_Phdr;
typedef Elf64_Phdr GElf_Phdr;
/* Section header.  */
typedef struct
{
  Elf64_Word    sh_name;                /* Section name (string tbl index) */
  Elf64_Word    sh_type;                /* Section type */
  Elf64_Xword   sh_flags;               /* Section flags */
  Elf64_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf64_Off     sh_offset;              /* Section file offset */
  Elf64_Xword   sh_size;                /* Section size in bytes */
  Elf64_Word    sh_link;                /* Link to another section */
  Elf64_Word    sh_info;                /* Additional section information */
  Elf64_Xword   sh_addralign;           /* Section alignment */
  Elf64_Xword   sh_entsize;             /* Entry size if section holds table */
} Elf64_Shdr;
typedef Elf64_Shdr GElf_Shdr;
/* Descriptor for data to be converted to or from memory format.  */
typedef struct
{
  void *d_buf;                  /* Pointer to the actual data.  */
  int d_type;                   /* Type of this piece of data.  */
  unsigned int d_version;       /* ELF version.  */
  size_t d_size;                /* Size in bytes.  */
  uint64_t d_off;               /* Offset into section.  */
  size_t d_align;               /* Alignment in section.  */
} Elf_Data;
/* Symbol table entry.  */
typedef struct
{
  Elf64_Word    st_name;                /* Symbol name (string tbl index) */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf64_Section st_shndx;               /* Section index */
  Elf64_Addr    st_value;               /* Symbol value */
  Elf64_Xword   st_size;                /* Symbol size */
} Elf64_Sym;
typedef Elf64_Sym GElf_Sym;
/* Coordinate ELF library and application versions.  */
unsigned int elf_version (unsigned int __version);
/* Return descriptor for ELF file to work according to CMD.  */
Elf *elf_begin (int __fildes, int __cmd, Elf *__ref);
/* Free resources allocated for ELF.  */
int elf_end (Elf *__elf);
/* Get the number of program headers in the ELF file.  If the file uses
   more headers than can be represented in the e_phnum field of the ELF
   header the information from the sh_info field in the zeroth section
   header is used.  */
int elf_getphdrnum (Elf *__elf, size_t *__dst);
/* Retrieve program header table entry.  */
GElf_Phdr *gelf_getphdr (Elf *__elf, int __ndx, GElf_Phdr *__dst);
/* Retrieve section header.  */
GElf_Shdr *gelf_getshdr (Elf_Scn *__scn, GElf_Shdr *__dst);
/* Retrieve symbol information from the symbol table at the given index.  */
GElf_Sym *gelf_getsym (Elf_Data *__data, int __ndx, GElf_Sym *__dst);
/* Get section with next section index.  */
Elf_Scn *elf_nextscn (Elf *__elf, Elf_Scn *__scn);
/* Get data from section while translating from file representation
   to memory representation.  */
Elf_Data *elf_getdata (Elf_Scn *__scn, Elf_Data *__data);
/* Return pointer to string at OFFSET in section INDEX.  */
char *elf_strptr (Elf *__elf, size_t __index, size_t __offset);
]]
local elf = ffi.load('elf')
local EV = { NONE=0, CURRENT=1, NUM=2 }
local PT = { NULL=0, LOAD=1, DYNAMIC=2, INTERP=3, NOTE=4, SHLIB=5, PHDR=6, TLS=7, NUM=8 }
local SHT = { NULL=0, PROGBITS=1, SYMTAB=2, STRTAB=3, RELA=4, HASH=5, DYNAMIC=6, NOTE=7,
              NOBITS=8, REL=9, SHLIB=10, DYNSYM=11, INIT_ARRAY=14, FINI_ARRAY=15, PREINIT_ARRAY=16,
              GROUP=17, SYMTAB_SHNDX=18, NUM=19 }
local ELF_C = ffi.new('struct Elf_Cmd')
local M = {}
-- Optional poor man's C++ demangler
local cpp_demangler = os.getenv('CPP_DEMANGLER')
if not cpp_demangler then
	for prefix in string.gmatch(os.getenv('PATH'), '[^;:]+') do
		if S.statfs(prefix..'/c++filt') then
			cpp_demangler = prefix..'/c++filt'
			break
		end
	end
end
local cpp_demangle = function (name) return name end
if cpp_demangler then
	cpp_demangle = function (name)
		local cmd = string.format('%s -p %s', cpp_demangler, name)
		local fp = assert(io.popen(cmd, 'r'))
		local output = fp:read('*all')
		fp:close()
		return output:match '^(.-)%s*$'
	end
end
-- Metatable for ELF object
ffi.metatype('struct Elf_object', {
	__gc = function (t) t:close() end,
	__index = {
		close = function (t)
			if t.elf ~= nil then
				elf.elf_end(t.elf)
				S.close(t.fd)
				t.elf = nil
			end
		end,
		-- Load library load address
		loadaddr = function(t)
			local phnum = ffi.new('size_t [1]')
			if elf.elf_getphdrnum(t.elf, phnum) == nil then
				return nil, 'cannot get phdrnum'
			end
			local header = ffi.new('GElf_Phdr [1]')
			for i = 0, tonumber(phnum[0])-1 do
				if elf.gelf_getphdr(t.elf, i, header) ~= nil
				   and header[0].p_type == PT.LOAD then
				   return header[0].p_vaddr
				end
			end
		end,
		-- Resolve symbol address
		resolve = function (t, k, pattern)
			local section = elf.elf_nextscn(t.elf, nil)
			while section ~= nil do
				local header = ffi.new('GElf_Shdr [1]')
				if elf.gelf_getshdr(section, header) ~= nil then
					if header[0].sh_type == SHT.SYMTAB or header[0].sh_type == SHT.DYNSYM then
						local data = elf.elf_getdata(section, nil)
						while data ~= nil do
							if data.d_size % header[0].sh_entsize > 0 then
								return nil, 'bad section header entity size'
							end
							local symcount = tonumber(data.d_size / header[0].sh_entsize)
							local sym = ffi.new('GElf_Sym [1]')
							for i = 0, symcount - 1 do
								if elf.gelf_getsym(data, i, sym) ~= nil then
									local name = elf.elf_strptr(t.elf, header[0].sh_link, sym[0].st_name)
									if name ~= nil then
										-- Demangle C++ symbols if necessary
										name = ffi.string(name)
										if name:sub(1,2) == '_Z' then
											name = cpp_demangle(name)
										end
										-- Match symbol name against pattern
										if pattern and string.match(name, k) or k == name then
											return sym[0]
										end
									end
								end
							end
							data = elf.elf_getdata(section, data)
						end
					end
				end
				section = elf.elf_nextscn(t.elf, section)
			end
		end,
	}
})
-- Open an ELF object
function M.open(path)
	if elf.elf_version(EV.CURRENT) == EV.NONE then
		return nil, 'bad version'
	end
	local fd, err = S.open(path, 'rdonly')
	if not fd then return nil, err end
	local pt = ffi.new('Elf *')
	pt = elf.elf_begin(fd:getfd(), ELF_C.READ, pt)
	if not pt then
		fd:close()
		return nil, 'cannot open elf object'
	end
	return ffi.new('struct Elf_object', fd:nogc():getfd(), pt)
end
return M


bpf

--[[
]]
-- LuaJIT to BPF bytecode compiler.
--
-- The code generation phase is currently one-pass and produces:
-- * Compiled code in BPF bytecode format (https://www.kernel.org/doc/Documentation/networking/filter.txt)
-- * Variables with liveness analysis and other meta (spill information, compile-time value)
--
-- The code generator optimises as much as possible in single pass:
-- * Fold compile-time expressions and constant propagation
-- * Basic control flow analysis with dead code elimination (based on compile-time expressions)
-- * Single-pass optimistic register allocation
--
-- The first pass doesn't have variable lifetime visibility yet, so it relies on rewriter for further
-- optimisations such as:
-- * Dead store elimination (first-pass doesn't know if/when the variable is going to be used)
-- * Common sub-expression elimination (relies on DCE and liveness analysis)
-- * Orphan JMP elimination (removing this in first pass would break previous JMP targets)
-- * Better register allocation (needs to be recomputed after optimisations)
local ffi = require('ffi')
local bit = require('bit')
local S = require('syscall')
local bytecode = require('bpf.ljbytecode')
local cdef = require('bpf.cdef')
local proto = require('bpf.proto')
local builtins = require('bpf.builtins')
-- Constants
local ALWAYS, NEVER = -1, -2
local BPF = ffi.typeof('struct bpf')
local HELPER = ffi.typeof('struct bpf_func_id')
-- Symbolic table of constant expressions over numbers
local const_expr = {
	ADD = function (a, b) return a + b end,
	SUB = function (a, b) return a - b end,
	DIV = function (a, b) return a / b end,
	MOD = function (a, b) return a % b end,
	JEQ = function (a, b) return a == b end,
	JNE = function (a, b) return a ~= b end,
	JGE = function (a, b) return a >= b end,
	JGT = function (a, b) return a > b end,
}
local const_width = {
	[1] = BPF.B, [2] = BPF.H, [4] = BPF.W, [8] = BPF.DW,
}
-- Built-ins that are strict only (never compile-time expandable)
local builtins_strict = {
	[ffi.new] = true,
	[print]   = true,
}
-- Deep copy a table
local function table_copy(t)
	local copy = {}
	for n,v in pairs(t) do
		if type(v) == 'table' then
			v = table_copy(v)
		end
		copy[n] = v
	end
	return copy
end
-- Return true if the constant part is a proxy
local function is_proxy(x)
	return type(x) == 'table' and (x.__dissector or x.__map or x.__base)
end
-- Create compiler closure
local function create_emitter(env, stackslots, params, param_types)
local V = {}   -- Variable tracking / register allocator
local code = { -- Generated code
	pc = 0, bc_pc = 0,
	insn = ffi.new('struct bpf_insn[4096]'),
	fixup = {},
	reachable = true,
	seen_cmp = nil,
}
local Vstate = {} -- Track variable layout at basic block exits
-- Anything below this stack offset is free to use by caller
-- lower it for persistent objects, but such memory will never
-- be reclaimed and the caller is responsible for resetting stack
-- top whenever the memory below is free to be reused
local stack_top = (stackslots + 1) * ffi.sizeof('uint64_t')
local function emit(op, dst, src, off, imm)
	local ins = code.insn[code.pc]
	ins.code = op
	ins.dst_reg = dst
	ins.src_reg = src
	ins.off = off
	ins.imm = imm
	code.pc = code.pc + 1
end
local function reg_spill(var)
	local vinfo = V[var]
	assert(vinfo.reg, 'attempt to spill VAR that doesn\'t have an allocated register')
	vinfo.spill = (var + 1) * ffi.sizeof('uint64_t') -- Index by (variable number) * (register width)
	emit(BPF.MEM + BPF.STX + BPF.DW, 10, vinfo.reg, -vinfo.spill, 0)
	vinfo.reg = nil
end
local function reg_fill(var, reg)
	local vinfo = V[var]
	assert(reg, 'attempt to fill variable to register but not register is allocated')
	assert(vinfo.spill, 'attempt to fill register with a VAR that isn\'t spilled')
	emit(BPF.MEM + BPF.LDX + BPF.DW, reg, 10, -vinfo.spill, 0)
	vinfo.reg = reg
	vinfo.spill = nil
end
-- Allocate a register (lazy simple allocator)
local function reg_alloc(var, reg)
	-- Specific register requested, must spill/move existing variable
	if reg then
		for k,v in pairs(V) do -- Spill any variable that has this register
			if v.reg == reg and not v.shadow then
				reg_spill(k)
				break
			end
		end
		return reg
	end
	-- Find free or least recently used slot
	local last, last_seen, used = nil, 0xffff, 0
	for k,v in pairs(V) do
		if v.reg then
			if not v.live_to or v.live_to < last_seen then
				last, last_seen = k, v.live_to or last_seen
			end
			used = bit.bor(used, bit.lshift(1, v.reg))
		end
	end
	-- Attempt to select a free register from R7-R9 (callee saved)
	local free = bit.bnot(used)
	if     bit.band(free, 0x80) ~= 0 then reg = 7
	elseif bit.band(free,0x100) ~= 0 then reg = 8
	elseif bit.band(free,0x200) ~= 0 then reg = 9
	end
	-- Select another variable to be spilled
	if not reg then
		assert(last)
		reg = V[last].reg
		reg_spill(last)
	end
	assert(reg, 'VAR '..var..'fill/spill failed')
	return reg
end
-- Set new variable
local function vset(var, reg, const, vtype)
	-- Must materialise all variables shadowing this variable slot, as it will be overwritten
	if V[var] and V[var].reg then
		for _, vinfo in pairs(V) do
			-- Shadowing variable MUST share the same type and attributes,
			-- but the register assignment may have changed
			if vinfo.shadow == var then
				vinfo.reg = V[var].reg
				vinfo.shadow = nil
			end
		end
	end
	-- Get precise type for CDATA or attempt to narrow numeric constant
	if not vtype and type(const) == 'cdata' then
		vtype = ffi.typeof(const)
	end
	V[var] = {reg=reg, const=const, type=vtype}
	-- Track variable source
	if V[var].const and type(const) == 'table' then
		V[var].source = V[var].const.source
	end
end
-- Materialize (or register) a variable in a register
-- If the register is nil, then the a new register is assigned (if not already assigned)
local function vreg(var, reg, reserve, vtype)
	local vinfo = V[var]
	assert(vinfo, 'VAR '..var..' not registered')
	vinfo.live_to = code.pc-1
	if (vinfo.reg and not reg) and not vinfo.shadow then return vinfo.reg end
	reg = reg_alloc(var, reg)
	-- Materialize variable shadow copy
	local src = vinfo
	while src.shadow do src = V[src.shadow] end
	if reserve then -- luacheck: ignore
		-- No load to register occurs
	elseif src.reg then
		emit(BPF.ALU64 + BPF.MOV + BPF.X, reg, src.reg, 0, 0)
	elseif src.spill then
		vinfo.spill = src.spill
		reg_fill(var, reg)
	elseif src.const then
		vtype = vtype or src.type
		if type(src.const) == 'table' and src.const.__base then
			-- Load pointer type
			emit(BPF.ALU64 + BPF.MOV + BPF.X, reg, 10, 0, 0)
			emit(BPF.ALU64 + BPF.ADD + BPF.K, reg, 0, 0, -src.const.__base)
		elseif type(src.const) == 'table' and src.const.__dissector then
			-- Load dissector offset (imm32), but keep the constant part (dissector proxy)
			emit(BPF.ALU64 + BPF.MOV + BPF.K, reg, 0, 0, src.const.off or 0)
		elseif vtype and ffi.sizeof(vtype) == 8 then
			-- IMM64 must be done in two instructions with imm64 = (lo(imm32), hi(imm32))
			emit(BPF.LD + BPF.DW, reg, 0, 0, ffi.cast('uint32_t', src.const))
			emit(0, 0, 0, 0, ffi.cast('uint32_t', bit.rshift(bit.rshift(src.const, 16), 16)))
			vinfo.const = nil -- The variable is live
		else
			emit(BPF.ALU64 + BPF.MOV + BPF.K, reg, 0, 0, src.const)
			vinfo.const = nil -- The variable is live
		end
	else assert(false, 'VAR '..var..' has neither register nor constant value') end
	vinfo.reg = reg
	vinfo.shadow = nil
	vinfo.live_from = code.pc-1
	vinfo.type = vtype or vinfo.type
	return reg
end
-- Copy variable
local function vcopy(dst, src)
	if dst == src then return end
	V[dst] = {reg=V[src].reg, const=V[src].const, shadow=src, source=V[src].source, type=V[src].type}
end
-- Dereference variable of pointer type
local function vderef(dst_reg, src_reg, vinfo)
	-- Dereference map pointers for primitive types
	-- BPF doesn't allow pointer arithmetics, so use the entry value
	assert(type(vinfo.const) == 'table' and vinfo.const.__dissector, 'cannot dereference a non-pointer variable')
	local vtype = vinfo.const.__dissector
	local w = ffi.sizeof(vtype)
	assert(const_width[w], 'NYI: sizeof('..tostring(vtype)..') not 1/2/4/8 bytes')
	if dst_reg ~= src_reg then
		emit(BPF.ALU64 + BPF.MOV + BPF.X, dst_reg, src_reg, 0, 0)    -- dst = src
	end
	-- Optimize the NULL check away if provably not NULL
	if not vinfo.source or vinfo.source:find('_or_null', 1, true) then
		emit(BPF.JMP + BPF.JEQ + BPF.K, src_reg, 0, 1, 0)            -- if (src != NULL)
	end
	emit(BPF.MEM + BPF.LDX + const_width[w], dst_reg, src_reg, 0, 0) --     dst = *src;
end
-- Allocate a space for variable
local function valloc(size, blank)
	local base = stack_top
	assert(stack_top + size < 512 * 1024, 'exceeded maximum stack size of 512kB')
	stack_top = stack_top + size
	-- Align to 8 byte boundary
	stack_top = math.ceil(stack_top/8)*8
	-- Current kernel version doesn't support ARG_PTR_TO_RAW_STACK
	-- so we always need to have memory initialized, remove this when supported
	if blank then
		if type(blank) == 'string' then
			local sp = 0
			while sp < size do
				-- TODO: no BPF_ST + BPF_DW instruction yet
				local as_u32 = ffi.new('uint32_t [1]')
				local sub = blank:sub(sp+1, sp+ffi.sizeof(as_u32))
				ffi.copy(as_u32, sub, #sub)
				emit(BPF.MEM + BPF.ST + BPF.W, 10, 0, -(stack_top-sp), as_u32[0])
				sp = sp + ffi.sizeof(as_u32)
			end
		elseif type(blank) == 'boolean' then
			reg_alloc(stackslots, 0)
			emit(BPF.ALU64 + BPF.MOV + BPF.K, 0, 0, 0, 0)
			for sp = base+8,stack_top,8 do
				emit(BPF.MEM + BPF.STX + BPF.DW, 10, 0, -sp, 0)
			end
		else error('NYI: will with unknown type '..type(blank)) end
	end
	return stack_top
end
-- Turn variable into scalar in register (or constant)
local function vscalar(a, w)
	assert(const_width[w], 'sizeof(scalar variable) must be 1/2/4/8')
	local src_reg
	-- If source is a pointer, we must dereference it first
	if cdef.isptr(V[a].type) then
		src_reg = vreg(a)
		local tmp_reg = reg_alloc(stackslots, 1) -- Clone variable in tmp register
		emit(BPF.ALU64 + BPF.MOV + BPF.X, tmp_reg, src_reg, 0, 0)
		vderef(tmp_reg, tmp_reg, V[a])
		src_reg = tmp_reg -- Materialize and dereference it
	-- Source is a value on stack, we must load it first
	elseif type(V[a].const) == 'table' and V[a].const.__base > 0 then
		src_reg = vreg(a)
		emit(BPF.MEM + BPF.LDX + const_width[w], src_reg, 10, -V[a].const.__base, 0)
		V[a].type = V[a].const.__dissector
		V[a].const = nil -- Value is dereferenced
	-- If source is an imm32 number, avoid register load
	elseif type(V[a].const) == 'number' and w < 8 then
		return nil, V[a].const
	-- Load variable from any other source
	else
		src_reg = vreg(a)
	end
	return src_reg, nil
end
-- Emit compensation code at the end of basic block to unify variable set layout on all block exits
-- 1. we need to free registers by spilling
-- 2. fill registers to match other exits from this BB
local function bb_end(Vcomp)
	for i,v in pairs(V) do
		if Vcomp[i] and Vcomp[i].spill and not v.spill then
			-- Materialize constant or shadowing variable to be able to spill
			if not v.reg and (v.shadow or cdef.isimmconst(v)) then
				vreg(i)
			end
			reg_spill(i)
		end
	end
	for i,v in pairs(V) do
		if Vcomp[i] and Vcomp[i].reg and not v.reg then
			vreg(i, Vcomp[i].reg)
		end
		-- Compensate variable metadata change
		if Vcomp[i] and Vcomp[i].source then
			V[i].source = Vcomp[i].source
		end
	end
end
local function CMP_STR(a, b, op)
	assert(op == 'JEQ' or op == 'JNE', 'NYI: only equivallence stack/string only supports == or ~=')
	-- I have no better idea how to implement it than unrolled XOR loop, as we can fixup only one JMP
	-- So: X(a,b) = a[0] ^ b[0] | a[1] ^ b[1] | ...
	--     EQ(a,b) <=> X == 0
	-- This could be optimised by placing early exits by rewriter in second phase for long strings
	local base, size = V[a].const.__base, math.min(#b, ffi.sizeof(V[a].type))
	local acc, tmp = reg_alloc(stackslots, 0), reg_alloc(stackslots+1, 1)
	local sp = 0
	emit(BPF.ALU64 + BPF.MOV + BPF.K, acc, 0, 0, 0)
	while sp < size do
		-- Load string chunk as imm32
		local as_u32 = ffi.new('uint32_t [1]')
		local sub = b:sub(sp+1, sp+ffi.sizeof(as_u32))
		ffi.copy(as_u32, sub, #sub)
		-- TODO: make this faster by interleaved load/compare steps with DW length
		emit(BPF.MEM + BPF.LDX + BPF.W, tmp, 10, -(base-sp), 0)
		emit(BPF.ALU64 + BPF.XOR + BPF.K, tmp, 0, 0, as_u32[0])
		emit(BPF.ALU64 + BPF.OR + BPF.X, acc, tmp, 0, 0)
		sp = sp + ffi.sizeof(as_u32)
	end
	emit(BPF.JMP + BPF[op] + BPF.K, acc, 0, 0xffff, 0)
	code.seen_cmp = code.pc-1
end
local function CMP_REG(a, b, op)
	-- Fold compile-time expressions
	if V[a].const and V[b].const and not (is_proxy(V[a].const) or is_proxy(V[b].const)) then
		code.seen_cmp = const_expr[op](V[a].const, V[b].const) and ALWAYS or NEVER
	else
		-- Comparison against compile-time string or stack memory
		if V[b].const and type(V[b].const) == 'string' then
			return CMP_STR(a, V[b].const, op)
		end
		-- The 0xFFFF target here has no significance, it's just a placeholder for
		-- compiler to replace it's absolute offset to LJ bytecode insn with a relative
		-- offset in BPF program code, verifier will accept only programs with valid JMP targets
		local a_reg, b_reg = vreg(a), vreg(b)
		emit(BPF.JMP + BPF[op] + BPF.X, a_reg, b_reg, 0xffff, 0)
		code.seen_cmp = code.pc-1
	end
end
local function CMP_IMM(a, b, op)
	local c = V[a].const
		code.seen_cmp = const_expr[op](c, b) and ALWAYS or NEVER
	else
		-- Convert imm32 to number
		if type(b) == 'string' then
			if     #b == 1 then b = b:byte()
			elseif cdef.isptr(V[a].type) then
				-- String comparison between stack/constant string
				return CMP_STR(a, b, op)
			elseif #b <= 4 then
				-- Convert to u32 with network byte order
				local imm = ffi.new('uint32_t[1]')
				ffi.copy(imm, b, #b)
				b = builtins.hton(imm[0])
			else error('NYI: compare register with string, where #string > sizeof(u32)') end
		end
		-- The 0xFFFF target here has no significance, it's just a placeholder for
		-- compiler to replace it's absolute offset to LJ bytecode insn with a relative
		-- offset in BPF program code, verifier will accept only programs with valid JMP targets
		local reg = vreg(a)
		emit(BPF.JMP + BPF[op] + BPF.K, reg, 0, 0xffff, b)
		code.seen_cmp = code.pc-1
		-- Remember NULL pointer checks as BPF prohibits pointer comparisons
		-- and repeated checks wouldn't pass the verifier, only comparisons
		-- against constants are checked.
		if op == 'JEQ' and tonumber(b) == 0 and V[a].source then
			local pos = V[a].source:find('_or_null', 1, true)
			if pos then
				code.seen_null_guard = a
			end
		-- Inverse NULL pointer check (if a ~= nil)
		elseif op == 'JNE' and tonumber(b) == 0 and V[a].source then
			local pos = V[a].source:find('_or_null', 1, true)
			if pos then
				code.seen_null_guard = a
				code.seen_null_guard_inverse = true
			end
		end
	end
end
local function ALU_IMM(dst, a, b, op)
	-- Fold compile-time expressions
	if V[a].const and not is_proxy(V[a].const) then
			assert(cdef.isimmconst(V[a]), 'VAR '..a..' must be numeric')
			vset(dst, nil, const_expr[op](V[a].const, b))
	-- Now we need to materialize dissected value at DST, and add it
	else
		vcopy(dst, a)
		local dst_reg = vreg(dst)
		if cdef.isptr(V[a].type) then
			vderef(dst_reg, dst_reg, V[a])
			V[dst].type = V[a].const.__dissector
		else
			V[dst].type = V[a].type
		end
		emit(BPF.ALU64 + BPF[op] + BPF.K, dst_reg, 0, 0, b)
	end
end
local function ALU_REG(dst, a, b, op)
	-- Fold compile-time expressions
	if V[a].const and not (is_proxy(V[a].const) or is_proxy(V[b].const)) then
		assert(cdef.isimmconst(V[a]), 'VAR '..a..' must be numeric')
		assert(cdef.isimmconst(V[b]), 'VAR '..b..' must be numeric')
		if type(op) == 'string' then op = const_expr[op] end
		vcopy(dst, a)
		V[dst].const = op(V[a].const, V[b].const)
	else
		local src_reg = b and vreg(b) or 0 -- SRC is optional for unary operations
		if b and cdef.isptr(V[b].type) then
			-- We have to allocate a temporary register for dereferencing to preserve
			-- pointer in source variable that MUST NOT be altered
			reg_alloc(stackslots, 2)
			vderef(2, src_reg, V[b])
			src_reg = 2
		end
		vcopy(dst, a) -- DST may alias B, so copy must occur after we materialize B
		local dst_reg = vreg(dst)
		if cdef.isptr(V[a].type) then
			vderef(dst_reg, dst_reg, V[a])
			V[dst].type = V[a].const.__dissector
		end
		emit(BPF.ALU64 + BPF[op] + BPF.X, dst_reg, src_reg, 0, 0)
		V[stackslots].reg = nil  -- Free temporary registers
	end
end
local function ALU_IMM_NV(dst, a, b, op)
	-- Do DST = IMM(a) op VAR(b) where we can't invert because
	-- the registers are u64 but immediates are u32, so complement
	-- arithmetics wouldn't work
	vset(stackslots+1, nil, a)
	ALU_REG(dst, stackslots+1, b, op)
end
local function LD_ABS(dst, w, off)
	assert(off, 'LD_ABS called without offset')
	if w < 8 then
		local dst_reg = vreg(dst, 0, true, builtins.width_type(w)) -- Reserve R0
		emit(BPF.LD + BPF.ABS + const_width[w], dst_reg, 0, 0, off)
		if w > 1 and ffi.abi('le') then -- LD_ABS has htonl() semantics, reverse
			emit(BPF.ALU + BPF.END + BPF.TO_BE, dst_reg, 0, 0, w * 8)
		end
	elseif w == 8 then
		-- LD_ABS|IND prohibits DW, we need to do two W loads and combine them
		local tmp_reg = vreg(stackslots, 0, true, builtins.width_type(w)) -- Reserve R0
		emit(BPF.LD + BPF.ABS + const_width[4], tmp_reg, 0, 0, off + 4)
		if ffi.abi('le') then -- LD_ABS has htonl() semantics, reverse
			emit(BPF.ALU + BPF.END + BPF.TO_BE, tmp_reg, 0, 0, 32)
		end
		ALU_IMM(stackslots, stackslots, 32, 'LSH')
		local dst_reg = vreg(dst, 0, true, builtins.width_type(w)) -- Reserve R0, spill tmp variable
		emit(BPF.LD + BPF.ABS + const_width[4], dst_reg, 0, 0, off)
		if ffi.abi('le') then -- LD_ABS has htonl() semantics, reverse
			emit(BPF.ALU + BPF.END + BPF.TO_BE, dst_reg, 0, 0, 32)
		end
		ALU_REG(dst, dst, stackslots, 'OR')
		V[stackslots].reg = nil -- Free temporary registers
	else
		assert(w < 8, 'NYI: only LD_ABS of 1/2/4/8 is supported')
	end
end
local function LD_IND(dst, src, w, off)
	local src_reg = vreg(src) -- Must materialize first in case dst == src
	local dst_reg = vreg(dst, 0, true, builtins.width_type(w)) -- Reserve R0
	emit(BPF.LD + BPF.IND + const_width[w], dst_reg, src_reg, 0, off or 0)
	if w > 1 and ffi.abi('le') then -- LD_ABS has htonl() semantics, reverse
		emit(BPF.ALU + BPF.END + BPF.TO_BE, dst_reg, 0, 0, w * 8)
	end
end
local function LD_MEM(dst, src, w, off)
	local src_reg = vreg(src) -- Must materialize first in case dst == src
	local dst_reg = vreg(dst, nil, true, builtins.width_type(w)) -- Reserve R0
	emit(BPF.MEM + BPF.LDX + const_width[w], dst_reg, src_reg, off or 0, 0)
end
local function LD_IMM_X(dst_reg, src_type, imm, w)
	if w == 8 then -- IMM64 must be done in two instructions with imm64 = (lo(imm32), hi(imm32))
		emit(BPF.LD + const_width[w], dst_reg, src_type, 0, ffi.cast('uint32_t', imm))
		-- Must shift in two steps as bit.lshift supports [0..31]
		emit(0, 0, 0, 0, ffi.cast('uint32_t', bit.lshift(bit.lshift(imm, 16), 16)))
	else
		emit(BPF.LD + const_width[w], dst_reg, src_type, 0, imm)
	end
end
local function BUILTIN(func, ...)
	local builtin_export = {
		-- Compiler primitives (work with variable slots, emit instructions)
		V=V, vreg=vreg, vset=vset, vcopy=vcopy, vderef=vderef, valloc=valloc, emit=emit,
		reg_alloc=reg_alloc, reg_spill=reg_spill, tmpvar=stackslots, const_width=const_width,
		-- Extensions and helpers (use with care)
		LD_IMM_X = LD_IMM_X,
	}
	func(builtin_export, ...)
end
local function LOAD(dst, src, off, vtype)
	local base = V[src].const
	assert(base and base.__dissector, 'NYI: load() on variable that doesn\'t have dissector')
	assert(V[src].source, 'NYI: load() on variable with unknown source')
	-- Cast to different type if requested
	vtype = vtype or base.__dissector
	local w = ffi.sizeof(vtype)
	assert(const_width[w], 'NYI: load() supports 1/2/4/8 bytes at a time only, wanted ' .. tostring(w))
	-- Packet access with a dissector (use BPF_LD)
	if V[src].source:find('ptr_to_pkt', 1, true) then
		if base.off then -- Absolute address to payload
			LD_ABS(dst, w, off + base.off)
		else -- Indirect address to payload
			LD_IND(dst, src, w, off)
		end
	-- Direct access to first argument (skb fields, pt regs, ...)
	elseif V[src].source:find('ptr_to_ctx', 1, true) then
		LD_MEM(dst, src, w, off)
	-- Direct skb access with a dissector (use BPF_MEM)
	elseif V[src].source:find('ptr_to_skb', 1, true) then
		LD_MEM(dst, src, w, off)
	-- Pointer to map-backed memory (use BPF_MEM)
	elseif V[src].source:find('ptr_to_map_value', 1, true) then
		LD_MEM(dst, src, w, off)
	-- Indirect read using probe (uprobe or kprobe, uses helper)
	elseif V[src].source:find('ptr_to_probe', 1, true) then
		BUILTIN(builtins[builtins.probe_read], nil, dst, src, vtype, off)
		V[dst].source = V[src].source -- Builtin handles everything
	else
		error('NYI: load() on variable from ' .. V[src].source)
	end
	V[dst].type = vtype
	V[dst].const = nil -- Dissected value is not constant anymore
end
local function CALL(a, b, d)
	assert(b-1 <= 1, 'NYI: CALL with >1 return values')
	-- Perform either compile-time, helper, or builtin
	local func = V[a].const
	-- Gather all arguments and check if they're constant
	local args, const, nargs = {}, true, d - 1
	for i = a+1, a+d-1 do
		table.insert(args, V[i].const)
		if not V[i].const or is_proxy(V[i].const) then const = false end
	end
	local builtin = builtins[func]
	if not const or nargs == 0 then
		if builtin and type(builtin) == 'function' then
			args = {a}
			for i = a+1, a+nargs do table.insert(args, i) end
			BUILTIN(builtin, unpack(args))
		elseif V[a+2] and V[a+2].const then -- var OP imm
			ALU_IMM(a, a+1, V[a+2].const, builtin)
		elseif nargs <= 2 then              -- var OP var
			ALU_REG(a, a+1, V[a+2] and a+2, builtin)
		else
			error('NYI: CALL non-builtin with 3 or more arguments')
		end
	-- Call on dissector implies slice retrieval
	elseif type(func) == 'table' and func.__dissector then
		assert(nargs >= 2, 'NYI: <dissector>.slice(a, b) must have at least two arguments')
		assert(V[a+1].const and V[a+2].const, 'NYI: slice() arguments must be constant')
		local off = V[a+1].const
		local vtype = builtins.width_type(V[a+2].const - off)
		-- Access to packet via packet (use BPF_LD)
		if V[a].source and V[a].source:find('ptr_to_', 1, true) then
			LOAD(a, a, off, vtype)
		else
			error('NYI: <dissector>.slice(a, b) on non-pointer memory ' .. (V[a].source or 'unknown'))
		end
	-- Strict builtins cannot be expanded on compile-time
	elseif builtins_strict[func] and builtin then
		args = {a}
		for i = a+1, a+nargs do table.insert(args, i) end
		BUILTIN(builtin, unpack(args))
	-- Attempt compile-time call expansion (expects all argument compile-time known)
	else
		assert(const, 'NYI: CALL attempted on constant arguments, but at least one argument is not constant')
		V[a].const = func(unpack(args))
	end
end
local function MAP_INIT(map_var, key, imm)
	local map = V[map_var].const
	vreg(map_var, 1, true, ffi.typeof('uint64_t'))
	-- Reserve R1 and load ptr for process-local map fd
	LD_IMM_X(1, BPF.PSEUDO_MAP_FD, map.fd, ffi.sizeof(V[map_var].type))
	V[map_var].reg = nil -- R1 will be invalidated after CALL, forget register allocation
	-- Reserve R2 and load R2 = key pointer
	local key_size = ffi.sizeof(map.key_type)
	local w = const_width[key_size] or BPF.DW
	local pod_type = const_width[key_size]
	local sp = stack_top + key_size -- Must use stack below spill slots
	-- Store immediate value on stack
	reg_alloc(stackslots, 2) -- Spill anything in R2 (unnamed tmp variable)
	local key_base = key and V[key].const
	imm = imm or key_base
	if imm and (not key or not is_proxy(key_base)) then
		assert(pod_type, 'NYI: map[const K], K width must be 1/2/4/8')
		emit(BPF.MEM + BPF.ST + w, 10, 0, -sp, imm)
	-- Key is in register, spill it
	elseif V[key].reg and pod_type then
		if cdef.isptr(V[key].type) then
			-- There is already pointer in register, dereference before spilling
			emit(BPF.MEM + BPF.LDX + w, 2, V[key].reg, 0, 0)
			emit(BPF.MEM + BPF.STX + w, 10, 2, -sp, 0)
		else -- Variable in register is POD, spill it on the stack
			emit(BPF.MEM + BPF.STX + w, 10, V[key].reg, -sp, 0)
		end
	-- Key is spilled from register to stack
	elseif V[key].spill then
		sp = V[key].spill
	-- Key is already on stack, write to base-relative address
	elseif key_base.__base then
		assert(key_size == ffi.sizeof(V[key].type), 'VAR '..key..' type incompatible with BPF map key type')
		sp = key_base.__base
	else
		error('VAR '..key..' is neither const-expr/register/stack/spilled')
	end
	-- If [FP+K] addressing, emit it
	if sp then
		emit(BPF.ALU64 + BPF.MOV + BPF.X, 2, 10, 0, 0)
		emit(BPF.ALU64 + BPF.ADD + BPF.K, 2, 0, 0, -sp)
	end
end
local function MAP_GET(dst, map_var, key, imm)
	local map = V[map_var].const
	MAP_INIT(map_var, key, imm)
	-- Flag as pointer type and associate dissector for map value type
	vreg(dst, 0, true, ffi.typeof('uint8_t *'))
	V[dst].const = {__dissector=map.val_type}
	V[dst].source = 'ptr_to_map_value_or_null'
	emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.map_lookup_elem)
	V[stackslots].reg = nil -- Free temporary registers
end
local function MAP_DEL(map_var, key, key_imm)
	-- Set R0, R1 (map fd, preempt R0)
	reg_alloc(stackslots, 0) -- Spill anything in R0 (unnamed tmp variable)
	MAP_INIT(map_var, key, key_imm)
	emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.map_delete_elem)
	V[stackslots].reg = nil -- Free temporary registers
end
local function MAP_SET(map_var, key, key_imm, src)
	local map = V[map_var].const
	-- Delete when setting nil
	if V[src].type == ffi.typeof('void') then
		return MAP_DEL(map_var, key, key_imm)
	end
	-- Set R0, R1 (map fd, preempt R0)
	reg_alloc(stackslots, 0) -- Spill anything in R0 (unnamed tmp variable)
	MAP_INIT(map_var, key, key_imm)
	reg_alloc(stackslots, 4) -- Spill anything in R4 (unnamed tmp variable)
	emit(BPF.ALU64 + BPF.MOV + BPF.K, 4, 0, 0, 0) -- BPF_ANY, create new element or update existing
	-- Reserve R3 for value pointer
	reg_alloc(stackslots, 3) -- Spill anything in R3 (unnamed tmp variable)
	local val_size = ffi.sizeof(map.val_type)
	local w = const_width[val_size] or BPF.DW
	local pod_type = const_width[val_size]
	-- Stack pointer must be aligned to both key/value size and have enough headroom for (key, value)
	local sp = stack_top + ffi.sizeof(map.key_type) + val_size
	sp = sp + (sp % val_size)
	local base = V[src].const
	if base and not is_proxy(base) then
		assert(pod_type, 'NYI: MAP[K] = imm V; V width must be 1/2/4/8')
		emit(BPF.MEM + BPF.ST + w, 10, 0, -sp, base)
	-- Value is in register, spill it
	elseif V[src].reg and pod_type then
		-- Value is a pointer, derefernce it and spill it
		if cdef.isptr(V[src].type) then
			vderef(3, V[src].reg, V[src])
			emit(BPF.MEM + BPF.STX + w, 10, 3, -sp, 0)
		else
			emit(BPF.MEM + BPF.STX + w, 10, V[src].reg, -sp, 0)
		end
	-- We get a pointer to spilled register on stack
	elseif V[src].spill then
		-- If variable is a pointer, we can load it to R3 directly (save "LEA")
		if cdef.isptr(V[src].type) then
			reg_fill(src, 3)
			-- If variable is a stack pointer, we don't have to check it
			if base.__base then
				emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.map_update_elem)
				return
			end
			vderef(3, V[src].reg, V[src])
			emit(BPF.MEM + BPF.STX + w, 10, 3, -sp, 0)
		else
			sp = V[src].spill
		end
	-- Value is already on stack, write to base-relative address
	elseif base.__base then
		if val_size ~= ffi.sizeof(V[src].type) then
			local err = string.format('VAR %d type (%s) incompatible with BPF map value type (%s): expected %d, got %d',
				src, V[src].type, map.val_type, val_size, ffi.sizeof(V[src].type))
			error(err)
		end
		sp = base.__base
	-- Value is constant, materialize it on stack
	else
		error('VAR '.. src ..' is neither const-expr/register/stack/spilled')
	end
	emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, 10, 0, 0)
	emit(BPF.ALU64 + BPF.ADD + BPF.K, 3, 0, 0, -sp)
	emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.map_update_elem)
	V[stackslots].reg = nil -- Free temporary registers
end
-- Finally - this table translates LuaJIT bytecode into code emitter actions.
local BC = {
	-- Constants
	KNUM = function(a, _, c, _) -- KNUM
		if c < 2147483648 then
			vset(a, nil, c, ffi.typeof('int32_t'))
		else
			vset(a, nil, c, ffi.typeof('uint64_t'))
		end
	end,
	KSHORT = function(a, _, _, d) -- KSHORT
		vset(a, nil, d, ffi.typeof('int16_t'))
	end,
	KCDATA = function(a, _, c, _) -- KCDATA
		-- Coerce numeric types if possible
		else
			error('NYI: cannot use CDATA constant of type ' .. ct)
		end
	end,
	KPRI = function(a, _, _, d) -- KPRI
		-- KNIL is 0, must create a special type to identify it
		local vtype = (d < 1) and ffi.typeof('void') or ffi.typeof('uint8_t')
		vset(a, nil, (d < 2) and 0 or 1, vtype)
	end,
	KSTR = function(a, _, c, _) -- KSTR
		vset(a, nil, c, ffi.typeof('const char[?]'))
	end,
	MOV = function(a, _, _, d) -- MOV var, var
		vcopy(a, d)
	end,
	-- Comparison ops
	-- Note: comparisons are always followed by JMP opcode, that
	--       will fuse following JMP to JMP+CMP instruction in BPF
	-- Note:  we're narrowed to integers, so operand/operator inversion is legit
	ISLT = function(a, _, _, d) return CMP_REG(d, a, 'JGE') end, -- (a < d) (inverted)
	ISGE = function(a, _, _, d) return CMP_REG(a, d, 'JGE') end, -- (a >= d)
	ISGT = function(a, _, _, d) return CMP_REG(a, d, 'JGT') end, -- (a > d)
	ISEQV = function(a, _, _, d) return CMP_REG(a, d, 'JEQ') end, -- (a == d)
	ISNEV = function(a, _, _, d) return CMP_REG(a, d, 'JNE') end, -- (a ~= d)
	ISEQN = function(a, _, c, _) return CMP_IMM(a, c, 'JEQ') end, -- (a == c)
	ISNEN = function(a, _, c, _) return CMP_IMM(a, c, 'JNE') end, -- (a ~= c)
	IST = function(_, _, _, d) return CMP_IMM(d, 0, 'JNE') end, -- (d)
	ISF = function(_, _, _, d) return CMP_IMM(d, 0, 'JEQ') end, -- (not d)
	ISEQP = function(a, _, c, _) return CMP_IMM(a, c, 'JEQ') end, -- ISEQP (a == c)
	-- Binary operations with RHS constants
	ADDVN = function(a, b, c, _) return ALU_IMM(a, b, c, 'ADD') end,
	SUBVN = function(a, b, c, _) return ALU_IMM(a, b, c, 'SUB') end,
	MULVN = function(a, b, c, _) return ALU_IMM(a, b, c, 'MUL') end,
	DIVVN = function(a, b, c, _) return ALU_IMM(a, b, c, 'DIV') end,
	MODVN = function(a, b, c, _) return ALU_IMM(a, b, c, 'MOD') end,
	-- Binary operations with LHS constants
	-- Cheat code: we're narrowed to integer arithmetic, so MUL+ADD are commutative
	ADDNV = function(a, b, c, _) return ALU_IMM(a, b, c, 'ADD') end, -- ADDNV
	MULNV = function(a, b, c, _) return ALU_IMM(a, b, c, 'MUL') end, -- MULNV
	SUBNV = function(a, b, c, _) return ALU_IMM_NV(a, c, b, 'SUB') end, -- SUBNV
	DIVNV = function(a, b, c, _) return ALU_IMM_NV(a, c, b, 'DIV') end, -- DIVNV
	-- Binary operations between registers
	ADDVV = function(a, b, _, d) return ALU_REG(a, b, d, 'ADD') end,
	SUBVV = function(a, b, _, d) return ALU_REG(a, b, d, 'SUB') end,
	MULVV = function(a, b, _, d) return ALU_REG(a, b, d, 'MUL') end,
	DIVVV = function(a, b, _, d) return ALU_REG(a, b, d, 'DIV') end,
	MODVV = function(a, b, _, d) return ALU_REG(a, b, d, 'MOD') end,
	-- Strings
	CAT = function(a, b, _, d) -- CAT A = B ~ D
		assert(V[b].const and V[d].const, 'NYI: CAT only works on compile-time expressions')
		assert(type(V[b].const) == 'string' and type(V[d].const) == 'string',
			'NYI: CAT only works on compile-time strings')
		vset(a, nil, V[b].const .. V[d].const)
	end,
	-- Tables
	GGET = function (a, _, c, _) -- GGET (A = GLOBAL[c])
		if env[c] ~= nil then
			vset(a, nil, env[c])
		else error(string.format("undefined global '%s'", c)) end
	end,
	UGET = function (a, _, c, _) -- UGET (A = UPVALUE[c])
		if env[c] ~= nil then
			vset(a, nil, env[c])
		else error(string.format("undefined upvalue '%s'", c)) end
	end,
	TSETB = function (a, b, _, d) -- TSETB (B[D] = A)
		assert(V[b] and type(V[b].const) == 'table', 'NYI: B[D] where B is not Lua table, BPF map, or pointer')
		local vinfo = V[b].const
		if vinfo.__map then -- BPF map read (constant)
			return MAP_SET(b, nil, d, a) -- D is literal
		elseif vinfo.__dissector then
			assert(vinfo.__dissector, 'NYI: B[D] where B does not have a known element size')
			local w = ffi.sizeof(vinfo.__dissector)
			-- TODO: support vectorized moves larger than register width
			assert(const_width[w], 'B[C] = A, sizeof(A) must be 1/2/4/8')
			local src_reg, const = vscalar(a, w)
			-- If changing map value, write to absolute address + offset
			if V[b].source and V[b].source:find('ptr_to_map_value', 1, true) then
				local dst_reg = vreg(b)
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' then
					emit(BPF.MEM + BPF.ST + const_width[w], dst_reg, 0, d, const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], dst_reg, src_reg, d, 0)
				end
			-- Table is already on stack, write to vinfo-relative address
			elseif vinfo.__base then
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' then
					emit(BPF.MEM + BPF.ST + const_width[w], 10, 0, -vinfo.__base + (d * w), const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], 10, src_reg, -vinfo.__base + (d * w), 0)
				end
			else
				error('NYI: B[D] where B is not Lua table, BPF map, or pointer')
			end
		elseif vinfo and vinfo and V[a].const then
			vinfo[V[d].const] = V[a].const
		else
			error('NYI: B[D] where B is not Lua table, BPF map, or pointer')
		end
	end,
	TSETV = function (a, b, _, d) -- TSETV (B[D] = A)
		assert(V[b] and type(V[b].const) == 'table', 'NYI: B[D] where B is not Lua table, BPF map, or pointer')
		local vinfo = V[b].const
		if vinfo.__map then -- BPF map read (constant)
			return MAP_SET(b, d, nil, a) -- D is variable
		elseif vinfo.__dissector then
			assert(vinfo.__dissector, 'NYI: B[D] where B does not have a known element size')
			local w = ffi.sizeof(vinfo.__dissector)
			-- TODO: support vectorized moves larger than register width
			assert(const_width[w], 'B[C] = A, sizeof(A) must be 1/2/4/8')
			local src_reg, const = vscalar(a, w)
			-- If changing map value, write to absolute address + offset
			if V[b].source and V[b].source:find('ptr_to_map_value', 1, true) then
				-- Calculate variable address from two registers
				local tmp_var = stackslots + 1
				vset(tmp_var, nil, d)
				ALU_REG(tmp_var, tmp_var, b, 'ADD')
				local dst_reg = vreg(tmp_var)
				V[tmp_var].reg = nil -- Only temporary allocation
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' and w < 8 then
					emit(BPF.MEM + BPF.ST + const_width[w], dst_reg, 0, 0, const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], dst_reg, src_reg, 0, 0)
				end
			-- Table is already on stack, write to vinfo-relative address
			elseif vinfo.__base then
				-- Calculate variable address from two registers
				local tmp_var = stackslots + 1
				vcopy(tmp_var, d)                       -- Element position
				if w > 1 then
					ALU_IMM(tmp_var, tmp_var, w, 'MUL') -- multiply by element size
				end
				local dst_reg = vreg(tmp_var)           -- add R10 (stack pointer)
				emit(BPF.ALU64 + BPF.ADD + BPF.X, dst_reg, 10, 0, 0)
				V[tmp_var].reg = nil -- Only temporary allocation
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' and w < 8 then
					emit(BPF.MEM + BPF.ST + const_width[w], dst_reg, 0, -vinfo.__base, const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], dst_reg, src_reg, -vinfo.__base, 0)
				end
			else
				error('NYI: B[D] where B is not Lua table, BPF map, or pointer')
			end
		elseif vinfo and V[d].const and V[a].const then
			vinfo[V[d].const] = V[a].const
		else
			error('NYI: B[D] where B is not Lua table, BPF map, or pointer')
		end
	end,
	TSETS = function (a, b, c, _) -- TSETS (B[C] = A)
		assert(V[b] and V[b].const, 'NYI: B[D] where B is not Lua table, BPF map, or pointer')
		local base = V[b].const
		if base.__dissector then
			local ofs,bpos = ffi.offsetof(base.__dissector, c)
			assert(not bpos, 'NYI: B[C] = A, where C is a bitfield')
			local w = builtins.sizeofattr(base.__dissector, c)
			-- TODO: support vectorized moves larger than register width
			assert(const_width[w], 'B[C] = A, sizeof(A) must be 1/2/4/8')
			local src_reg, const = vscalar(a, w)
			-- If changing map value, write to absolute address + offset
			if V[b].source and V[b].source:find('ptr_to_map_value', 1, true) then
				local dst_reg = vreg(b)
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' and w < 8 then
					emit(BPF.MEM + BPF.ST + const_width[w], dst_reg, 0, ofs, const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], dst_reg, src_reg, ofs, 0)
				end
			-- Table is already on stack, write to base-relative address
			elseif base.__base then
				-- Optimization: immediate values (imm32) can be stored directly
				if type(const) == 'number' and w < 8 then
					emit(BPF.MEM + BPF.ST + const_width[w], 10, 0, -base.__base + ofs, const)
				else
					emit(BPF.MEM + BPF.STX + const_width[w], 10, src_reg, -base.__base + ofs, 0)
				end
			else
				error('NYI: B[C] where B is not Lua table, BPF map, or pointer')
			end
		elseif V[a].const then
			base[c] = V[a].const
		else
			error('NYI: B[C] where B is not Lua table, BPF map, or pointer')
		end
	end,
	TGETB = function (a, b, _, d) -- TGETB (A = B[D])
		local base = V[b].const
		assert(type(base) == 'table', 'NYI: B[C] where C is string and B not Lua table or BPF map')
		if a ~= b then vset(a) end
		if base.__map then -- BPF map read (constant)
			MAP_GET(a, b, nil, d)
		-- Pointer access with a dissector (traditional uses BPF_LD, direct uses BPF_MEM)
		elseif V[b].source and V[b].source:find('ptr_to_') then
			local vtype = base.__dissector and base.__dissector or ffi.typeof('uint8_t')
			LOAD(a, b, d, vtype)
		-- Specialise PTR[0] as dereference operator
		elseif cdef.isptr(V[b].type) and d == 0 then
			vcopy(a, b)
			local dst_reg = vreg(a)
			vderef(dst_reg, dst_reg, V[a])
			V[a].type = V[a].const.__dissector
		else
			error('NYI: A = B[D], where B is not Lua table or packet dissector or pointer dereference')
		end
	end,
	TGETV = function (a, b, _, d) -- TGETV (A = B[D])
		local base = V[b].const
		assert(type(base) == 'table', 'NYI: B[C] where C is string and B not Lua table or BPF map')
		if a ~= b then vset(a) end
		if base.__map then -- BPF map read
			MAP_GET(a, b, d)
		-- Pointer access with a dissector (traditional uses BPF_LD, direct uses BPF_MEM)
		elseif V[b].source and V[b].source:find('ptr_to_') then
			local vtype = base.__dissector and base.__dissector or ffi.typeof('uint8_t')
			LOAD(a, b, d, vtype)
		-- Constant dereference
		elseif type(V[d].const) == 'number' then
			V[a].const = base[V[d].const]
		else
			error('NYI: A = B[D], where B is not Lua table or packet dissector or pointer dereference')
		end
	end,
	TGETS = function (a, b, c, _) -- TGETS (A = B[C])
		local base = V[b].const
		assert(type(base) == 'table', 'NYI: B[C] where C is string and B not Lua table or BPF map')
		if a ~= b then vset(a) end
		if base.__dissector then
			local ofs,bpos,bsize = ffi.offsetof(base.__dissector, c)
			-- Resolve table key using metatable
			if not ofs and type(base.__dissector[c]) == 'string' then
				c = base.__dissector[c]
				ofs,bpos,bsize = ffi.offsetof(base.__dissector, c)
			end
			if not ofs and proto[c] then -- Load new dissector on given offset
				BUILTIN(proto[c], a, b, c)
			else
				-- Loading register from offset is a little bit tricky as there are
				-- several data sources and value loading modes with different restrictions
				-- such as checking pointer values for NULL compared to using stack.
				assert(ofs, tostring(base.__dissector)..'.'..c..' attribute not exists')
				if a ~= b then vset(a) end
				-- Dissected value is probably not constant anymore
				local new_const = nil
				local w, atype = builtins.sizeofattr(base.__dissector, c)
				-- [SP+K] addressing using R10 (stack pointer)
				-- Doesn't need to be checked for NULL
				if base.__base and base.__base > 0 then
					if cdef.isptr(atype) then -- If the member is pointer type, update base pointer with offset
						new_const = {__base = base.__base-ofs}
					else
						local dst_reg = vreg(a, nil, true)
						emit(BPF.MEM + BPF.LDX + const_width[w], dst_reg, 10, -base.__base+ofs, 0)
					end
				-- Pointer access with a dissector (traditional uses BPF_LD, direct uses BPF_MEM)
				elseif V[b].source and V[b].source:find('ptr_to_') then
					LOAD(a, b, ofs, atype)
				else
					error('NYI: B[C] where B is not Lua table, BPF map, or pointer')
				end
				-- Bitfield, must be further narrowed with a bitmask/shift
				if bpos then
					local mask = 0
					for i=bpos+1,bpos+bsize do
						mask = bit.bor(mask, bit.lshift(1, w*8-i))
					end
					emit(BPF.ALU64 + BPF.AND + BPF.K, vreg(a), 0, 0, mask)
					-- Free optimization: single-bit values need just boolean result
					if bsize > 1 then
						local shift = w*8-bsize-bpos
						if shift > 0 then
							emit(BPF.ALU64 + BPF.RSH + BPF.K, vreg(a), 0, 0, shift)
						end
					end
				end
				V[a].type = atype
				V[a].const = new_const
				V[a].source = V[b].source
				-- Track direct access to skb data
				-- see https://www.kernel.org/doc/Documentation/networking/filter.txt "Direct packet access"
				if ffi.istype(base.__dissector, ffi.typeof('struct sk_buff')) then
					-- Direct access to skb uses skb->data and skb->data_end
					-- which are encoded as u32, but are actually pointers
					if c == 'data' or c == 'data_end' then
						V[a].const = {__dissector = ffi.typeof('uint8_t')}
						V[a].source = 'ptr_to_skb'
					end
				end
			end
		else
			V[a].const = base[c]
		end
	end,
	-- Loops and branches
	CALLM = function (a, b, _, d) -- A = A(A+1, ..., A+D+MULTRES)
		-- NYI: Support single result only
		CALL(a, b, d+2)
	end,
	CALL = function (a, b, _, d) -- A = A(A+1, ..., A+D-1)
		CALL(a, b, d)
	end,
	JMP = function (a, _, c, _) -- JMP
		-- Discard unused slots after jump
		for i, _ in pairs(V) do
			if i >= a and i < stackslots then
				V[i] = nil
			end
		end
		-- Cross basic block boundary if the jump target isn't provably unreachable
		local val = code.fixup[c] or {}
		if code.seen_cmp and code.seen_cmp ~= ALWAYS then
			if code.seen_cmp ~= NEVER then -- Do not emit the jump or fixup
				-- Store previous CMP insn for reemitting after compensation code
				local jmpi = ffi.new('struct bpf_insn', code.insn[code.pc-1])
				code.pc = code.pc - 1
				-- First branch point, emit compensation code
				local Vcomp = Vstate[c]
				if not Vcomp then
					-- Select scratch register (R0-5) that isn't used as operand
					-- in the CMP instruction, as the variable may not be live, after
					-- the JMP, but it may be used in the JMP+CMP instruction itself
					local tmp_reg = 0
					for reg = 0, 5 do
						if reg ~= jmpi.dst_reg and reg ~= jmpi.src_reg then
							tmp_reg = reg
							break
						end
					end
					-- Force materialization of constants at the end of BB
					for i, v in pairs(V) do
						if not v.reg and cdef.isimmconst(v) then
							vreg(i, tmp_reg) -- Load to TMP register (not saved)
							reg_spill(i) -- Spill caller-saved registers
						end
					end
					-- Record variable state
					Vstate[c] = V
					Vcomp = V
					V = table_copy(V)
				-- Variable state already set, emit specific compensation code
				else
					bb_end(Vcomp)
				end
				-- Record pointer NULL check from condition
				-- If the condition checks pointer variable against NULL,
				-- we can assume it will not be NULL in the fall-through block
				if code.seen_null_guard then
					local var = code.seen_null_guard
					-- The null guard can have two forms:
					--   if x == nil then goto
					--   if x ~= nil then goto
					-- First form guarantees that the variable will be non-nil on the following instruction
					-- Second form guarantees that the variable will be non-nil at the jump target
					local vinfo = code.seen_null_guard_inverse and Vcomp[var] or V[var]
					if vinfo.source then
						local pos = vinfo.source:find('_or_null', 1, true)
						if pos then
							vinfo.source = vinfo.source:sub(1, pos - 1)
						end
					end
				end
				-- Reemit CMP insn
				emit(jmpi.code, jmpi.dst_reg, jmpi.src_reg, jmpi.off, jmpi.imm)
				-- Fuse JMP into previous CMP opcode, mark JMP target for fixup
				-- as we don't knot the relative offset in generated code yet
				table.insert(val, code.pc-1)
				code.fixup[c] = val
			end
			code.seen_cmp = nil
			code.seen_null_guard = nil
			code.seen_null_guard_inverse = nil
		elseif c == code.bc_pc + 1 then -- luacheck: ignore 542
			-- Eliminate jumps to next immediate instruction
			-- e.g. 0002    JMP      1 => 0003
		else
			-- We need to synthesise a condition that's always true, however
			-- BPF prohibits pointer arithmetic to prevent pointer leaks
			-- so we have to clear out one register and use it for cmp that's always true
			local dst_reg = reg_alloc(stackslots)
			V[stackslots].reg = nil -- Only temporary allocation
			-- First branch point, emit compensation code
			local Vcomp = Vstate[c]
			if not Vcomp then
				-- Force materialization of constants at the end of BB
				for i, v in pairs(V) do
					if not v.reg and cdef.isimmconst(v) then
						vreg(i, dst_reg) -- Load to TMP register (not saved)
						reg_spill(i) -- Spill caller-saved registers
					end
				end
				-- Record variable state
				Vstate[c] = V
				V = table_copy(V)
			-- Variable state already set, emit specific compensation code
			else
				bb_end(Vcomp)
			end
			emit(BPF.ALU64 + BPF.MOV + BPF.K, dst_reg, 0, 0, 0)
			emit(BPF.JMP + BPF.JEQ + BPF.K, dst_reg, 0, 0xffff, 0)
			table.insert(val, code.pc-1) -- Fixup JMP target
			code.reachable = false -- Code following the JMP is not reachable
			code.fixup[c] = val
		end
	end,
	RET1 = function (a, _, _, _) -- RET1
		-- Free optimisation: spilled variable will not be filled again
		for i, v in pairs(V) do
			if i ~= a then v.reg = nil end
		end
		if V[a].reg ~= 0 then vreg(a, 0) end
		-- Convenience: dereference pointer variables
		-- e.g. 'return map[k]' will return actual map value, not pointer
		if cdef.isptr(V[a].type) then
			vderef(0, 0, V[a])
		end
		emit(BPF.JMP + BPF.EXIT, 0, 0, 0, 0)
		code.reachable = false
	end,
	RET0 = function (_, _, _, _) -- RET0
		emit(BPF.ALU64 + BPF.MOV + BPF.K, 0, 0, 0, 0)
		emit(BPF.JMP + BPF.EXIT, 0, 0, 0, 0)
		code.reachable = false
	end,
	compile = function ()
		return code
	end
}
-- Composite instructions
function BC.CALLT(a, _, _, d) -- Tailcall: return A(A+1, ..., A+D-1)
	CALL(a, 1, d)
	BC.RET1(a)
end
-- Always initialize R6 with R1 context
emit(BPF.ALU64 + BPF.MOV + BPF.X, 6, 1, 0, 0)
-- Register R6 as context variable (first argument)
if params and params > 0 then
	vset(0, 6, param_types[1] or proto.skb)
	assert(V[0].source == V[0].const.source) -- Propagate source annotation from typeinfo
end
-- Register tmpvars
vset(stackslots)
vset(stackslots+1)
return setmetatable(BC, {
	__index = function (_, k, _)
		if type(k) == 'number' then
			local op_str = string.sub(require('jit.vmdef').bcnames, 6*k+1, 6*k+6)
			error(string.format("NYI: opcode '0x%02x' (%-04s)", k, op_str))
		end
	end,
	__call = function (t, op, a, b, c, d)
		code.bc_pc = code.bc_pc + 1
		-- Exitting BB straight through, emit compensation code
		if Vstate[code.bc_pc] then
			if code.reachable then
				-- Instruction is reachable from previous line
				-- so we must make the variable allocation consistent
				-- with the variable allocation at the jump source
				-- e.g. 0001 x:R0 = 5
				--      0002 if rand() then goto 0005
				--      0003 x:R0 -> x:stack
				--      0004 y:R0 = 5
				--      0005 x:? = 10 <-- x was in R0 before jump, and stack after jump
				bb_end(Vstate[code.bc_pc])
			else
				-- Instruction isn't reachable from previous line, restore variable layout
				-- e.g. RET or condition-less JMP on previous line
				V = table_copy(Vstate[code.bc_pc])
			end
		end
		-- Perform fixup of jump targets
		-- We need to do this because the number of consumed and emitted
		-- bytecode instructions is different
		local fixup = code.fixup[code.bc_pc]
		if fixup ~= nil then
			-- Patch JMP source insn with relative offset
			for _,pc in ipairs(fixup) do
				code.insn[pc].off = code.pc - 1 - pc
			end
			code.fixup[code.bc_pc] = nil
			code.reachable = true
		end
		-- Execute
		if code.reachable then
			assert(t[op], string.format('NYI: instruction %s, parameters: %s,%s,%s,%s', op,a,b,c,d))
			return t[op](a, b, c, d)
		end
	end,
})
end
-- Emitted code dump
local function dump_mem(cls, ins, _, fuse)
	-- This is a very dense MEM instruction decoder without much explanation
	-- Refer to https://www.kernel.org/doc/Documentation/networking/filter.txt for instruction format
	local mode = bit.band(ins.code, 0xe0)
	if mode == BPF.XADD then cls = 5 end -- The only mode
	local op_1 = {'LD', 'LDX', 'ST', 'STX', '', 'XADD'}
	local op_2 = {[0]='W', [8]='H', [16]='B', [24]='DW'}
	local name = op_1[cls+1] .. op_2[bit.band(ins.code, 0x18)]
	local off = tonumber(ffi.cast('int16_t', ins.off)) -- Reinterpret as signed
	local dst = cls < 2 and 'R'..ins.dst_reg or string.format('[R%d%+d]', ins.dst_reg, off)
	local src = cls % 2 == 0 and '#'..ins.imm or 'R'..ins.src_reg
	if cls == BPF.LDX then src = string.format('[R%d%+d]', ins.src_reg, off) end
	if mode == BPF.ABS then src = string.format('skb[%d]', ins.imm) end
	if mode == BPF.IND then src = string.format('skb[R%d%+d]', ins.src_reg, ins.imm) end
	return string.format('%s\t%s\t%s', fuse and '' or name, fuse and '' or dst, src)
end
local function dump_alu(cls, ins, pc)
	local alu = {'ADD', 'SUB', 'MUL', 'DIV', 'OR', 'AND', 'LSH', 'RSH', 'NEG', 'MOD', 'XOR', 'MOV', 'ARSH', 'END' }
	local jmp = {'JA', 'JEQ', 'JGT', 'JGE', 'JSET', 'JNE', 'JSGT', 'JSGE', 'CALL', 'EXIT'}
	local helper = {'unspec', 'map_lookup_elem', 'map_update_elem', 'map_delete_elem', 'probe_read', 'ktime_get_ns',
					'trace_printk', 'get_prandom_u32', 'get_smp_processor_id', 'skb_store_bytes',
					'l3_csum_replace', 'l4_csum_replace', 'tail_call', 'clone_redirect', 'get_current_pid_tgid',
					'get_current_uid_gid', 'get_current_comm', 'get_cgroup_classid', 'skb_vlan_push', 'skb_vlan_pop',
					'skb_get_tunnel_key', 'skb_set_tunnel_key', 'perf_event_read', 'redirect', 'get_route_realm',
					'perf_event_output', 'skb_load_bytes'}
	local op = 0
	-- This is a very dense ALU instruction decoder without much explanation
	-- Refer to https://www.kernel.org/doc/Documentation/networking/filter.txt for instruction format
	for i = 0,13 do if 0x10 * i == bit.band(ins.code, 0xf0) then op = i + 1 break end end
	local name = (cls == 5) and jmp[op] or alu[op]
	local src = (bit.band(ins.code, 0x08) == BPF.X) and 'R'..ins.src_reg or '#'..ins.imm
	local target = (cls == 5 and op < 9) and string.format('\t=> %04d', pc + ins.off + 1) or ''
	if cls == 5 and op == 9 then target = string.format('\t; %s', helper[ins.imm + 1] or tostring(ins.imm)) end
	return string.format('%s\t%s\t%s%s', name, 'R'..ins.dst_reg, src, target)
end
local function dump_string(code, off, hide_counter)
	if not code then return end
	local cls_map = {
		[0] = dump_mem, [1] = dump_mem, [2] = dump_mem, [3] = dump_mem,
		[4] = dump_alu, [5] = dump_alu, [7] = dump_alu,
	}
	local result = {}
	local fused = false
	for i = off or 0, code.pc - 1 do
		local ins = code.insn[i]
		local cls = bit.band(ins.code, 0x07)
		local line = cls_map[cls](cls, ins, i, fused)
		if hide_counter then
			table.insert(result, line)
		else
			table.insert(result, string.format('%04u\t%s', i, line))
		end
		fused = string.find(line, 'LDDW', 1)
	end
	return table.concat(result, '\n')
end
local function dump(code)
	if not code then return end
	print(string.format('-- BPF %s:0-%u', code.insn, code.pc))
	print(dump_string(code))
end
local function compile(prog, params)
	-- Create code emitter sandbox, include caller locals
	local env = { pkt=proto.pkt, eth=proto.pkt, BPF=BPF, ffi=ffi }
	-- Include upvalues up to 4 nested scopes back
	-- the narrower scope overrides broader scope
	for k = 5, 2, -1 do
		local i = 1
		while true do
			local ok, n, v = pcall(debug.getlocal, k, i)
			if not ok or not n then break end
			env[n] = v
			i = i + 1
		end
	end
	setmetatable(env, {
		__index = function (_, k)
			return proto[k] or builtins[k] or _G[k]
		end
	})
	-- Create code emitter and compile LuaJIT bytecode
	if type(prog) == 'string' then prog = loadstring(prog) end
	-- Create error handler to print traceback
	local funci, pc = bytecode.funcinfo(prog), 0
	local E = create_emitter(env, funci.stackslots, funci.params, params or {})
	local on_err = function (e)
			funci = bytecode.funcinfo(prog, pc)
			local from, to = 0, 0
			for _ = 1, funci.currentline do
				from = to
				to = string.find(funci.source, '\n', from+1, true) or 0
			end
			print(funci.loc..':'..string.sub(funci.source, from+1, to-1))
			print('error: '..e)
			print(debug.traceback())
	end
	for _,op,a,b,c,d in bytecode.decoder(prog) do
		local ok, _, err = xpcall(E,on_err,op,a,b,c,d)
		if not ok then
			return nil, err
		end
	end
	return E:compile()
end
-- BPF map interface
local bpf_map_mt = {
	__gc = function (map) S.close(map.fd) end,
	__len = function(map) return map.max_entries end,
	__index = function (map, k)
		if type(k) == 'string' then
			-- Return iterator
			if k == 'pairs' then
				return function(t, key)
					-- Get next key
					local next_key = ffi.new(ffi.typeof(t.key))
					local cur_key
					if key then
						cur_key = t.key
						t.key[0] = key
					else
						cur_key = ffi.new(ffi.typeof(t.key))
					end
					local ok, err = S.bpf_map_op(S.c.BPF_CMD.MAP_GET_NEXT_KEY, map.fd, cur_key, next_key)
					if not ok then return nil, err end
					-- Get next value
					assert(S.bpf_map_op(S.c.BPF_CMD.MAP_LOOKUP_ELEM, map.fd, next_key, map.val))
					return next_key[0], map.val[0]
				end, map, nil
			-- Read for perf event map
			elseif k == 'reader' then
				return function (pmap, pid, cpu, event_type)
					-- Caller must either specify PID or CPU
					if not pid or pid < 0 then
						assert((cpu and cpu >= 0), 'NYI: creating composed reader for all CPUs')
						pid = -1
					end
					-- Create BPF output reader
					local pe = S.t.perf_event_attr1()
					pe[0].type = 'software'
					pe[0].config = 'sw_bpf_output'
					pe[0].sample_type = 'raw'
					pe[0].sample_period = 1
					pe[0].wakeup_events = 1
					local reader, err = S.t.perf_reader(S.perf_event_open(pe, pid, cpu or -1))
					if not reader then return nil, tostring(err) end
					-- Register event reader fd in BPF map
					assert(cpu < pmap.max_entries, string.format('BPF map smaller than read CPU %d', cpu))
					pmap[cpu] = reader.fd
					-- Open memory map and start reading
					local ok, err = reader:start()
					assert(ok, tostring(err))
					ok, err = reader:mmap()
					assert(ok, tostring(err))
					return cdef.event_reader(reader, event_type)
				end
			-- Signalise this is a map type
			end
			return k == '__map'
		end
		-- Retrieve key
		map.key[0] = k
		local ok, err = S.bpf_map_op(S.c.BPF_CMD.MAP_LOOKUP_ELEM, map.fd, map.key, map.val)
		if not ok then return nil, err end
		return ffi.new(map.val_type, map.val[0])
	end,
	__newindex = function (map, k, v)
		map.key[0] = k
		if v == nil then
			return S.bpf_map_op(map.fd, S.c.BPF_CMD.MAP_DELETE_ELEM, map.key, nil)
		end
		map.val[0] = v
		return S.bpf_map_op(S.c.BPF_CMD.MAP_UPDATE_ELEM, map.fd, map.key, map.val)
	end,
}
-- Linux tracing interface
local function trace_check_enabled(path)
	path = path or '/sys/kernel/debug/tracing'
	if S.statfs(path) then return true end
	return nil, 'debugfs not accessible: "mount -t debugfs nodev /sys/kernel/debug"? missing sudo?'
end
-- Tracepoint interface
local tracepoint_mt = {
	__index = {
		bpf = function (t, prog)
			if type(prog) ~= 'table' then
				-- Create protocol parser with source probe
				prog = compile(prog, {proto.type(t.type, {source='ptr_to_probe'})})
			end
			-- Load the BPF program
			local prog_fd, err, log = S.bpf_prog_load(S.c.BPF_PROG.TRACEPOINT, prog.insn, prog.pc)
			assert(prog_fd, tostring(err)..': '..tostring(log))
			-- Open tracepoint and attach
			t.reader:setbpf(prog_fd:getfd())
			table.insert(t.progs, prog_fd)
			return prog_fd
		end,
	}
}
-- Open tracepoint
local function tracepoint_open(path, pid, cpu, group_fd)
	-- Open tracepoint and compile tracepoint type
	local tp = assert(S.perf_tracepoint('/sys/kernel/debug/tracing/events/'..path))
	local tp_type = assert(cdef.tracepoint_type(path))
	-- Open tracepoint reader and create interface
	local reader = assert(S.perf_attach_tracepoint(tp, pid, cpu, group_fd))
	return setmetatable({tp=tp,type=tp_type,reader=reader,progs={}}, tracepoint_mt)
end
local function trace_bpf(ptype, pname, pdef, retprobe, prog, pid, cpu, group_fd)
	-- Load BPF program
	if type(prog) ~= 'table' then
		prog = compile(prog, {proto.pt_regs})
	end
	local prog_fd, err, log = S.bpf_prog_load(S.c.BPF_PROG.KPROBE, prog.insn, prog.pc)
	assert(prog_fd, tostring(err)..': '..tostring(log))
	-- Open tracepoint and attach
	local tp, err = S.perf_probe(ptype, pname, pdef, retprobe)
	if not tp then
		prog_fd:close()
		return nil, tostring(err)
	end
	local reader, err = S.perf_attach_tracepoint(tp, pid, cpu, group_fd, {sample_type='raw, callchain'})
	if not reader then
		prog_fd:close()
		S.perf_probe(ptype, pname, false)
		return nil, tostring(err)
	end
	local ok, err = reader:setbpf(prog_fd:getfd())
	if not ok then
		prog_fd:close()
		reader:close()
		S.perf_probe(ptype, pname, false)
		return nil, tostring(err)..' (kernel version should be at least 4.1)'
	end
	-- Create GC closure for reader to close BPF program
	-- and detach probe in correct order
	ffi.gc(reader, function ()
		prog_fd:close()
		reader:close()
		S.perf_probe(ptype, pname, false)
	end)
	return {reader=reader, prog=prog_fd, probe=pname, probe_type=ptype}
end
-- Module interface
return setmetatable({
	new = create_emitter,
	dump = dump,
	dump_string = dump_string,
	maps = {},
	map = function (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
		-- Special case for BPF_MAP_STACK_TRACE
		if S.c.BPF_MAP[type] == S.c.BPF_MAP.STACK_TRACE then
			key_ctype = ffi.typeof('int32_t')
			val_ctype = ffi.typeof('struct bpf_stacktrace')
		end
		local fd, err = S.bpf_map_create(S.c.BPF_MAP[type], ffi.sizeof(key_ctype), ffi.sizeof(val_ctype), max_entries)
		if not fd then return nil, tostring(err) end
		local map = setmetatable({
			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 = fd:nogc():getfd(),
		}, bpf_map_mt)
		return map
	end,
	socket = function (sock, prog)
		-- Expect socket type, if sock is string then assume it's
		-- an interface name (e.g. 'lo'), if it's a number then typecast it as a socket
		local ok, err
		if type(sock) == 'string' then
			local iface = assert(S.nl.getlink())[sock]
			assert(iface, sock..' is not interface name')
			sock, err = S.socket('packet', 'raw')
			assert(sock, tostring(err))
			ok, err = sock:bind(S.t.sockaddr_ll({protocol='all', ifindex=iface.index}))
			assert(ok, tostring(err))
		elseif type(sock) == 'number' then
			sock = S.t.fd(sock):nogc()
		elseif ffi.istype(S.t.fd, sock) then -- luacheck: ignore
			-- No cast required
		else
			return nil, 'socket must either be an fd number, an interface name, or an ljsyscall socket'
		end
		-- Load program and attach it to socket
		if type(prog) ~= 'table' then
			prog = compile(prog, {proto.skb})
		end
		local prog_fd, err, log = S.bpf_prog_load(S.c.BPF_PROG.SOCKET_FILTER, prog.insn, prog.pc)
		assert(prog_fd, tostring(err)..': '..tostring(log))
		assert(sock:setsockopt('socket', 'attach_bpf', prog_fd:getfd()))
		return prog_fd, err
	end,
	tracepoint = function(tp, prog, pid, cpu, group_fd)
		assert(trace_check_enabled())
		-- Return tracepoint instance if no program specified
		-- this allows free specialisation of arg0 to tracepoint type
		local probe = tracepoint_open(tp, pid, cpu, group_fd)
		-- Load the BPF program
		if prog then
			probe:bpf(prog)
		end
		return probe
	end,
	kprobe = function(tp, prog, retprobe, pid, cpu, group_fd)
		assert(trace_check_enabled())
		-- Open tracepoint and attach
		local pname, pdef = tp:match('([^:]+):(.+)')
		return trace_bpf('kprobe', pname, pdef, retprobe, prog, pid, cpu, group_fd)
	end,
	uprobe = function(tp, prog, retprobe, pid, cpu, group_fd)
		assert(trace_check_enabled())
		-- Translate symbol to address
		local obj, sym_want = tp:match('([^:]+):(.+)')
		if not S.statfs(obj) then return nil, S.t.error(S.c.E.NOENT) end
		-- Resolve Elf object (no support for anything else)
		local elf = require('bpf.elf').open(obj)
		local sym = elf:resolve(sym_want)
		if not sym then return nil, 'no such symbol' end
		sym = sym.st_value - elf:loadaddr()
		local sym_addr = string.format('%x%04x', tonumber(bit.rshift(sym, 32)),
		                                         tonumber(ffi.cast('uint32_t', sym)))
		-- Convert it to expected uprobe format
		local pname = string.format('%s_%s', obj:gsub('.*/', ''), sym_addr)
		local pdef = obj..':0x'..sym_addr
		return trace_bpf('uprobe', pname, pdef, retprobe, prog, pid, cpu, group_fd)
	end,
	tracelog = function(path)
		assert(trace_check_enabled())
		path = path or '/sys/kernel/debug/tracing/trace_pipe'
		return io.open(path, 'r')
	end,
	ntoh = builtins.ntoh, hton = builtins.hton,
}, {
	__call = function (_, prog) return compile(prog) end,
})


cdef

--[[
]]
local ffi = require('ffi')
local bit = require('bit')
local has_syscall, S = pcall(require, 'syscall')
local M = {}
ffi.cdef [[
struct bpf {
	/* Instruction classes */
	static const int LD   = 0x00;
	static const int LDX  = 0x01;
	static const int ST   = 0x02;
	static const int STX  = 0x03;
	static const int ALU  = 0x04;
	static const int JMP  = 0x05;
	static const int ALU64 = 0x07;
	/* ld/ldx fields */
	static const int W    = 0x00;
	static const int H    = 0x08;
	static const int B    = 0x10;
	static const int ABS  = 0x20;
	static const int IND  = 0x40;
	static const int MEM  = 0x60;
	static const int LEN  = 0x80;
	static const int MSH  = 0xa0;
	/* alu/jmp fields */
	static const int ADD  = 0x00;
	static const int SUB  = 0x10;
	static const int MUL  = 0x20;
	static const int DIV  = 0x30;
	static const int OR   = 0x40;
	static const int AND  = 0x50;
	static const int LSH  = 0x60;
	static const int RSH  = 0x70;
	static const int NEG  = 0x80;
	static const int MOD  = 0x90;
	static const int XOR  = 0xa0;
	static const int JA   = 0x00;
	static const int JEQ  = 0x10;
	static const int JGT  = 0x20;
	static const int JGE  = 0x30;
	static const int JSET = 0x40;
	static const int K    = 0x00;
	static const int X    = 0x08;
	static const int JNE  = 0x50;	/* jump != */
	static const int JSGT = 0x60;	/* SGT is signed '>', GT in x86 */
	static const int JSGE = 0x70;	/* SGE is signed '>=', GE in x86 */
	static const int CALL = 0x80;	/* function call */
	static const int EXIT = 0x90;	/* function return */
	/* ld/ldx fields */
	static const int DW    = 0x18;	/* double word */
	static const int XADD  = 0xc0;	/* exclusive add */
	/* alu/jmp fields */
	static const int MOV   = 0xb0;	/* mov reg to reg */
	static const int ARSH  = 0xc0;	/* sign extending arithmetic shift right */
	/* change endianness of a register */
	static const int END   = 0xd0;	/* flags for endianness conversion: */
	static const int TO_LE = 0x00;	/* convert to little-endian */
	static const int TO_BE = 0x08;	/* convert to big-endian */
	/* misc */
	static const int PSEUDO_MAP_FD = 0x01;
	/* helper functions */
	static const int F_CURRENT_CPU    = 0xffffffff;
	static const int F_USER_STACK     = 1 << 8;
	static const int F_FAST_STACK_CMP = 1 << 9;
	static const int F_REUSE_STACKID  = 1 << 10;
	/* special offsets for ancillary data */
	static const int NET_OFF          = -0x100000;
	static const int LL_OFF           = -0x200000;
};
/* eBPF commands */
struct bpf_cmd {
	static const int MAP_CREATE       = 0;
	static const int MAP_LOOKUP_ELEM  = 1;
	static const int MAP_UPDATE_ELEM  = 2;
	static const int MAP_DELETE_ELEM  = 3;
	static const int MAP_GET_NEXT_KEY = 4;
	static const int PROG_LOAD        = 5;
	static const int OBJ_PIN          = 6;
	static const int OBJ_GET          = 7;
};
/* eBPF helpers */
struct bpf_func_id {
	static const int unspec               = 0;
	static const int map_lookup_elem      = 1;
	static const int map_update_elem      = 2;
	static const int map_delete_elem      = 3;
	static const int probe_read           = 4;
	static const int ktime_get_ns         = 5;
	static const int trace_printk         = 6;
	static const int get_prandom_u32      = 7;
	static const int get_smp_processor_id = 8;
	static const int skb_store_bytes      = 9;
	static const int l3_csum_replace      = 10;
	static const int l4_csum_replace      = 11;
	static const int tail_call            = 12;
	static const int clone_redirect       = 13;
	static const int get_current_pid_tgid = 14;
	static const int get_current_uid_gid  = 15;
	static const int get_current_comm     = 16;
	static const int get_cgroup_classid   = 17;
	static const int skb_vlan_push        = 18;
	static const int skb_vlan_pop         = 19;
	static const int skb_get_tunnel_key   = 20;
	static const int skb_set_tunnel_key   = 21;
	static const int perf_event_read      = 22;
	static const int redirect             = 23;
	static const int get_route_realm      = 24;
	static const int perf_event_output    = 25;
	static const int skb_load_bytes       = 26;
	static const int get_stackid          = 27;
};
/* BPF_MAP_STACK_TRACE structures and constants */
static const int BPF_MAX_STACK_DEPTH = 127;
struct bpf_stacktrace {
	uint64_t ip[BPF_MAX_STACK_DEPTH];
};
]]
-- Compatibility: ljsyscall doesn't have support for BPF syscall
if not has_syscall or not S.bpf then
	error("ljsyscall doesn't support bpf(), must be updated")
else
	local strflag = require('syscall.helpers').strflag
	-- Compatibility: ljsyscall<=0.12
	if not S.c.BPF_MAP.LRU_HASH then
		S.c.BPF_MAP = strflag {
			UNSPEC           = 0,
			HASH             = 1,
			ARRAY            = 2,
			PROG_ARRAY       = 3,
			PERF_EVENT_ARRAY = 4,
			PERCPU_HASH      = 5,
			PERCPU_ARRAY     = 6,
			STACK_TRACE      = 7,
			CGROUP_ARRAY     = 8,
			LRU_HASH         = 9,
			LRU_PERCPU_HASH  = 10,
			LPM_TRIE         = 11,
			ARRAY_OF_MAPS    = 12,
			HASH_OF_MAPS     = 13,
			DEVMAP           = 14,
			SOCKMAP          = 15,
			CPUMAP           = 16,
		}
	end
	if not S.c.BPF_PROG.TRACEPOINT then
		S.c.BPF_PROG = strflag {
			UNSPEC           = 0,
			SOCKET_FILTER    = 1,
			KPROBE           = 2,
			SCHED_CLS        = 3,
			SCHED_ACT        = 4,
			TRACEPOINT       = 5,
			XDP              = 6,
			PERF_EVENT       = 7,
			CGROUP_SKB       = 8,
			CGROUP_SOCK      = 9,
			LWT_IN           = 10,
			LWT_OUT          = 11,
			SOCK_OPS         = 13,
			SK_SKB           = 14,
			CGROUP_DEVICE    = 15,
			SK_MSG           = 16,
			RAW_TRACEPOINT   = 17,
			CGROUP_SOCK_ADDR = 18,
		}
	end
end
-- Compatibility: metatype for stacktrace
local function stacktrace_iter(t, i)
	i = i + 1
	if i < #t and t.ip[i] > 0 then
		return i, t.ip[i]
	end
end
ffi.metatype('struct bpf_stacktrace', {
	__len = function (t) return ffi.sizeof(t.ip) / ffi.sizeof(t.ip[0]) end,
	__ipairs = function (t) return stacktrace_iter, t, -1 end,
})
-- Reflect cdata type
function M.typename(v)
	if not v or type(v) ~= 'cdata' then return nil end
	return string.match(tostring(ffi.typeof(v)), '<([^>]+)')
end
-- Reflect if cdata type can be pointer (accepts array or pointer)
function M.isptr(v, noarray)
	local ctname = M.typename(v)
	if ctname then
		ctname = string.sub(ctname, -1)
		ctname = ctname == '*' or (not noarray and ctname == ']')
	end
	return ctname
end
-- Return true if variable is a non-nil constant that can be used as immediate value
-- e.g. result of KSHORT and KNUM
function M.isimmconst(v)
	return (type(v.const) == 'number' and not ffi.istype(v.type, ffi.typeof('void')))
		or type(v.const) == 'cdata' and ffi.istype(v.type, ffi.typeof('uint64_t')) -- Lua numbers are at most 52 bits
		or type(v.const) == 'cdata' and ffi.istype(v.type, ffi.typeof('int64_t'))
end
function M.osversion()
	-- We have no better way to extract current kernel hex-string other
	-- than parsing headers, compiling a helper function or reading /proc
	local ver_str, count = S.sysctl('kernel.version'):match('%d+.%d+.%d+'), 2
	if not ver_str then -- kernel.version is freeform, fallback to kernel.osrelease
		ver_str = S.sysctl('kernel.osrelease'):match('%d+.%d+.%d+')
	end
	local version = 0
	for i in ver_str:gmatch('%d+') do -- Convert 'X.Y.Z' to 0xXXYYZZ
		version = bit.bor(version, bit.lshift(tonumber(i), 8*count))
		count = count - 1
	end
	return version
end
function M.event_reader(reader, event_type)
	-- Caller can specify event message binary format
	if event_type then
		assert(type(event_type) == 'string' and ffi.typeof(event_type), 'not a valid type for event reader')
		event_type = ffi.typeof(event_type .. '*') -- Convert type to pointer-to-type
	end
	-- Wrap reader in interface that can interpret read event messages
	return setmetatable({reader=reader,type=event_type}, {__index = {
		block = function(_ --[[self]])
			return S.select { readfds = {reader.fd} }
		end,
		next = function(_ --[[self]], k)
			local len, ev = reader:next(k)
			-- Filter out only sample frames
			while ev and ev.type ~= S.c.PERF_RECORD.SAMPLE do
				len, ev = reader:next(len)
			end
			if ev and event_type then
				-- The perf event reader returns framed data with header and variable length
				-- This is going skip the frame header and cast data to given type
				ev = ffi.cast(event_type, ffi.cast('char *', ev) + ffi.sizeof('struct perf_event_header') + ffi.sizeof('uint32_t'))
			end
			return len, ev
		end,
		read = function(self)
			return self.next, self, nil
		end,
	}})
end
function M.tracepoint_type(tp)
	-- Read tracepoint format string
	local fp = assert(io.open('/sys/kernel/debug/tracing/events/'..tp..'/format', 'r'))
	local fmt = fp:read '*a'
	fp:close()
	-- Parse struct fields
	local fields = {}
	for f in fmt:gmatch 'field:([^;]+;)' do
		table.insert(fields, f)
	end
	return string.format('struct { %s }', table.concat(fields))
end
return M


init

--[[
]]
return require('bpf.bpf')


describe('compile', function()
	local ffi = require('ffi')
	local bpf = require('bpf')

	it('can compile socket filter', function()
		-- Create mock BPF map
		local mock_map = {
			max_entries = 16,
			key_type = ffi.typeof('uint64_t [1]'),
			val_type = ffi.typeof('uint64_t [1]'),
			fd = 1,
			__map = true,
		}
		-- Compile small code example
		local code = bpf(function ()
		   local proto = pkt.ip.proto
		   xadd(mock_map[proto], 1)
		end)
		assert.truthy(code)
		assert.same(type(code), 'table')
		assert.same(code.pc, 15)
	end)
end)


describe('decoder', function()

	-- Decode simple function
	local bytecode = require('bpf.ljbytecode')
	local f = function (x) return x + 1 end

	it('should decode functions', function()
		-- Make sure it calls LJ decoder
		local bc = bytecode.decoder(f)
		assert.truthy(bc)
		-- Decode bytecode bytecode to instructions
		local jutil = require("jit.util")
		spy.on(jutil, 'funcbc')
		local pc, op = bc()
		-- Check bytecode for sanity (starts with ADDVN(x, 1))
		assert.equal(pc, 1)
		assert.equal(op, 'ADDVN')
		for pc, op in bc do
			assert.truthy(pc and op)
		end
		assert.spy(jutil.funcbc).was.called()
	end)
	it('should fail on bad input', function()
		assert.has_error(function() bytecode.decoder(nil)() end)
		assert.has_error(function() bytecode.decoder(5)() end)
		assert.has_error(function() bytecode.decoder('test')() end)
	end)
	it('should dump bytecode', function()
		bytecode.dump(f)
	end)
end)


describe('elf reader', function()

	local ok, elf = pcall(require, 'bpf.elf')
	if not ok then return end

	it('should handle C library', function()
		-- Open libc
		local sh = elf.open('/bin/sh')
		assert.truthy(sh)
		-- Find load address
		local base = sh:loadaddr()
		assert.truthy(base)
		-- Find something from ISO C
		local malloc_addr = sh:resolve('malloc')
		assert.truthy(malloc_addr)
		-- Find something that doesn't exist
		local bad_addr = sh:resolve('thisnotexists')
		assert.falsy(bad_addr)
	end)
	it('should fail on bad input', function()
		assert.falsy(elf.open(nil))
		assert.falsy(elf.open('/tmp'):loadaddr())
	end)
end)