--[=[
 dump   = header proto+ 0U
 header = ESC 'L' 'J' versionB flagsU [namelenU nameB*]
 proto  = lengthU pdata
 pdata  = phead bcinsW* uvdataH* kgc* knum* [debugB*]
 phead  = flagsB numparamsB framesizeB numuvB numkgcU numknU numbcU
          [debuglenU [firstlineU numlineU]]
 kgc    = kgctypeU { ktab | (loU hiU) | (rloU rhiU iloU ihiU) | strB* }
 knum   = intU0 | (loU1 hiU)
 ktab   = narrayU nhashU karray* khash*
 karray = ktabk
 khash  = ktabk ktabk
 ktabk  = ktabtypeU { intU | (loU hiU) | strB* }
 uvdata = register index with high bit set if local to outer
 debug  = lninfoV* uvals vars '\0'
 uvals  = nameB* '\0'
 vars   = nameB* '\0' startU endU

 B = 8 bit,
 H = 16 bit,
 W = 32 bit,
 V = B, H or W,
 U = ULEB128 of W, U0/U1 = ULEB128 of W+1,
]=]

local bit  = require 'bit'
local ffi  = require 'ffi'
local jit  = require 'jit'

local band, bor, shl, shr, bnot = bit.band, bit.bor, bit.lshift, bit.rshift, bit.bnot

local jit_v21 = jit.version_num >= 20100

local typeof = getmetatable

local function enum(t)
    for i=0,#t do t[t[i]] = i end
    return t
end

-- forward declarations
local Buf, Ins, Proto, Dump, KNum, KObj

local MAX_REG = 200
local MAX_UVS = 60

local function enum_mode(t)
    local re, rm = {}, {}
    local nskip = 0
    for i = 1, #t do
        local v = t[i]
        if v[3] == false then
            nskip = nskip + 1
        else
            local idx = i - nskip - 1
            rm[idx ] = v[2]
            re[v[1]] = idx
        end
    end
    return re, rm
end

local BC_ABC = 0
local BC_AD  = 1
local BC_AJ  = 2

local BC, BC_MODE = enum_mode {
    { 'ISLT'  , BC_AD  },
    { 'ISGE'  , BC_AD  },
    { 'ISLE'  , BC_AD  },
    { 'ISGT'  , BC_AD  },
    { 'ISEQV' , BC_AD  },
    { 'ISNEV' , BC_AD  },
    { 'ISEQS' , BC_AD  },
    { 'ISNES' , BC_AD  },
    { 'ISEQN' , BC_AD  },
    { 'ISNEN' , BC_AD  },
    { 'ISEQP' , BC_AD  },
    { 'ISNEP' , BC_AD  },
    { 'ISTC'  , BC_AD  },
    { 'ISFC'  , BC_AD  },
    { 'IST'   , BC_AD  },
    { 'ISF'   , BC_AD  },
    { 'ISTYPE', BC_AD, jit_v21 },
    { 'ISNUM' , BC_AD, jit_v21 },
    { 'MOV'   , BC_AD  },
    { 'NOT'   , BC_AD  },
    { 'UNM'   , BC_AD  },
    { 'LEN'   , BC_AD  },
    { 'ADDVN' , BC_ABC },
    { 'SUBVN' , BC_ABC },
    { 'MULVN' , BC_ABC },
    { 'DIVVN' , BC_ABC },
    { 'MODVN' , BC_ABC },
    { 'ADDNV' , BC_ABC },
    { 'SUBNV' , BC_ABC },
    { 'MULNV' , BC_ABC },
    { 'DIVNV' , BC_ABC },
    { 'MODNV' , BC_ABC },
    { 'ADDVV' , BC_ABC },
    { 'SUBVV' , BC_ABC },
    { 'MULVV' , BC_ABC },
    { 'DIVVV' , BC_ABC },
    { 'MODVV' , BC_ABC },
    { 'POW'   , BC_ABC },
    { 'CAT'   , BC_ABC },
    { 'KSTR'  , BC_AD  },
    { 'KCDATA', BC_AD  },
    { 'KSHORT', BC_AD  },
    { 'KNUM'  , BC_AD  },
    { 'KPRI'  , BC_AD  },
    { 'KNIL'  , BC_AD  },
    { 'UGET'  , BC_AD  },
    { 'USETV' , BC_AD  },
    { 'USETS' , BC_AD  },
    { 'USETN' , BC_AD  },
    { 'USETP' , BC_AD  },
    { 'UCLO'  , BC_AJ  },
    { 'FNEW'  , BC_AD  },
    { 'TNEW'  , BC_AD  },
    { 'TDUP'  , BC_AD  },
    { 'GGET'  , BC_AD  },
    { 'GSET'  , BC_AD  },
    { 'TGETV' , BC_ABC },
    { 'TGETS' , BC_ABC },
    { 'TGETB' , BC_ABC },
    { 'TGETR' , BC_ABC, jit_v21 },
    { 'TSETV' , BC_ABC },
    { 'TSETS' , BC_ABC },
    { 'TSETB' , BC_ABC },
    { 'TSETM' , BC_AD  },
    { 'TSETR' , BC_ABC, jit_v21 },
    { 'CALLM' , BC_ABC },
    { 'CALL'  , BC_ABC },
    { 'CALLMT', BC_AD  },
    { 'CALLT' , BC_AD  },
    { 'ITERC' , BC_ABC },
    { 'ITERN' , BC_ABC },
    { 'VARG'  , BC_ABC },
    { 'ISNEXT', BC_AJ  },
    { 'RETM'  , BC_AD  },
    { 'RET'   , BC_AD  },
    { 'RET0'  , BC_AD  },
    { 'RET1'  , BC_AD  },
    { 'FORI'  , BC_AJ  },
    { 'JFORI' , BC_AJ  },
    { 'FORL'  , BC_AJ  },
    { 'IFORL' , BC_AJ  },
    { 'JFORL' , BC_AD  },
    { 'ITERL' , BC_AJ  },
    { 'IITERL', BC_AJ  },
    { 'JITERL', BC_AD  },
    { 'LOOP'  , BC_AJ  },
    { 'ILOOP' , BC_AJ  },
    { 'JLOOP' , BC_AD  },
    { 'JMP'   , BC_AJ  },
    { 'FUNCF' , BC_AD  },
    { 'IFUNCF', BC_AD  },
    { 'JFUNCF', BC_AD  },
    { 'FUNCV' , BC_AD  },
    { 'IFUNCV', BC_AD  },
    { 'JFUNCV', BC_AD  },
    { 'FUNCC' , BC_AD  },
    { 'FUNCCW', BC_AD  },
}

local VKNIL   = 0
local VKFALSE = 1
local VKTRUE  = 2

local NO_JMP = bnot(0)

-- Type codes for the GC constants of a prototype. Plus length for strings.
local KOBJ = enum {
    [0] = "CHILD", "TAB", "I64", "U64", "COMPLEX", "STR",
}

-- Type codes for the keys/values of a constant table.
local KTAB = enum {
    [0] = "NIL", "FALSE", "TRUE", "INT", "NUM", "STR",
}

local FOR_IDX   = "(for index)";
local FOR_STOP  = "(for limit)";
local FOR_STEP  = "(for step)";
local FOR_GEN   = "(for generator)";
local FOR_STATE = "(for state)";
local FOR_CTL   = "(for control)";

local function num_is_int32(x)
    return x % 1 == 0 and x >= -2^31 and x < 2^31
end

Buf = {}
Buf.new = function(size)
    size = size or 2048
    local self = {
        data = ffi.new('char[?]', size),
        size = size,
        offs = 0,
    }
    return setmetatable(self, Buf)
end

Buf.__index = {}
Buf.__index.need = function(self, size)
    local need_size = self.offs + size
    if self.size <= need_size then
        local prev_size = self.size
        while self.size <= need_size do
            self.size = self.size * 2
        end
        local new_data = ffi.new('char[?]', self.size)
        ffi.copy(new_data, self.data, prev_size)
        self.data = new_data
    end
end
Buf.__index.put = function(self, v)
    self:need(1)
    local offs = self.offs
    self.data[offs] = v
    self.offs = offs + 1
    return offs
end
Buf.__index.put_uint8 = Buf.__index.put

Buf.__index.put_uint16 = function(self, v)
    self:need(2)
    local offs = self.offs
    local dptr = self.data + offs
    dptr[0] = v
    v = shr(v, 8)
    dptr[1] = v
    self.offs = offs + 2
    return offs
end

Buf.__index.put_uint32 = function(self, v)
    self:need(4)
    local offs = self.offs
    local dptr = self.data + offs

    dptr[0] = v
    v = shr(v, 8)
    dptr[1] = v
    v = shr(v, 8)
    dptr[2] = v
    v = shr(v, 8)
    dptr[3] = v

    self.offs = offs + 4
    return offs
end

Buf.__index.put_uleb128 = function(self,  v)
    local offs = self.offs
    local b = band(v, 0x7f)
    v = shr(v, 7)
    if v ~= 0 then b = bor(b, 0x80) end
    self:put(b)
    while v > 0 do
        b = band(v, 0x7f)
        v = shr(v, 7)
        if v ~= 0 then b = bor(b, 0x80) end
        self:put(b)
    end
    return offs
end

-- Write a 32 bit unsigned integer + 1 bit given by "numbit".
-- This last argument should be either 0 or 1.
Buf.__index.put_uleb128_33 = function(self,  v, numbit)
    local offs = self.offs
    local b = bor(shl(band(v, 0x3f), 1), numbit)
    v = shr(v, 6)
    if v ~= 0 then b = bor(b, 0x80) end
    self:put(b)
    while v > 0 do
        b = band(v, 0x7f)
        v = shr(v, 7)
        if v ~= 0 then b = bor(b, 0x80) end
        self:put(b)
    end
    return offs
end

Buf.__index.put_bytes = function(self, v)
    local offs = self.offs
    self:need(#v)
    ffi.copy(self.data + offs, v)
    self.offs = offs + #v
    return offs
end
Buf.__index.pack = function(self)
    return ffi.string(self.data, self.offs)
end

local double_new = ffi.typeof('double[1]')
local uint32_new = ffi.typeof('uint32_t[1]')
local int64_new  = ffi.typeof('int64_t[1]')
local uint64_new = ffi.typeof('uint64_t[1]')

local function dword_get_u32(cdata_new, v)
    local p = cdata_new(v)
    local char = ffi.cast('uint8_t*', p)
    local lo, hi = uint32_new(0), uint32_new(0)
    ffi.copy(lo, char, 4)
    ffi.copy(hi, char + 4, 4)
    return lo[0], hi[0]
end

Buf.__index.put_number = function(self, v)
    local offs = self.offs
    if num_is_int32(v) then
        self:put_uleb128_33(v, 0)
    else
        local lo, hi = dword_get_u32(double_new, v)
        self:put_uleb128_33(lo, 1)
        self:put_uleb128(hi)
    end
    return offs
end

Ins = {}
Ins.__index = {}
function Ins.new(op, a, b, c)
    return setmetatable({
        op;
        a or 0;
        b or 0;
        c or 0;
    }, Ins)
end
function Ins.__index:rewrite(op, a, b, c)
    self[1], self[2], self[3], self[4] = op, a or 0, b or 0, c or 0
end
function Ins.__index:write(buf)
    local op, a = self[1], self[2]
    buf:put(op)
    buf:put(a)
    local mode = BC_MODE[op]
    if mode == BC_ABC then
        local b, c = self[3], self[4]
        buf:put(c)
        buf:put(b)
    elseif mode == BC_AD then
        local d = self[3]
        buf:put_uint16(d)
    elseif mode == BC_AJ then
        local j = self[3]
        buf:put_uint16(j + 0x8000)
    else
        error("bad instruction ["..tostring(op).."] (op mode unknown)")
    end
end
local function hsize2hbits(s)
    if s <= 0 then
        return 0
    elseif s == 1 then
        return 1
    end
    s = s - 1
    local c = 0
    while s > 0 do
        s = shr(s, 1)
        c = c + 1
    end
    return c
end
local function tabsize(narr, nrec)
    return bor(narr, shl(hsize2hbits(nrec), 11))
end
function Ins.__index.tnewsize(narry, nhash)
    if narry then
        if narry < 3 then
            narry = 3
        elseif narry > 0x7ff then
            narry = 0x7ff
        end
    else
        narry = 0
    end
    return tabsize(narry, nhash or 0)
end

KObj = {}
KObj.__index = {}
function KObj.new(v)
    return setmetatable({ v }, KObj)
end
function KObj.__index:write(buf)
    local t, v = type(self[1]), self[1]
    if t == "string" then
        self:write_string(buf, v)
    elseif t == 'table' then
        if typeof(v) == Proto then
            self:write_proto(buf, v)
        else
            self:write_table(buf, v)
        end
    elseif t == 'cdata' then
        self:write_kcdata(buf, v)
    end
end
function KObj.__index:write_string(buf, v)
    buf:put_uleb128(KOBJ.STR + #v)
    buf:put_bytes(v)
end
function KObj.__index:write_kcdata(buf, v)
    if ffi.istype('double complex', v) then
        buf:put_uleb128(KOBJ.COMPLEX)
        local lo, hi = dword_get_u32(double_new, v[0])
        buf:put_uleb128(lo)
        buf:put_uleb128(hi)
        lo, hi = dword_get_u32(double_new, v[1])
        buf:put_uleb128(lo)
        buf:put_uleb128(hi)
    elseif ffi.istype('uint64_t', v) then
        buf:put_uleb128(KOBJ.U64)
        local lo, hi = dword_get_u32(uint64_new, v)
        buf:put_uleb128(lo)
        buf:put_uleb128(hi)
    elseif ffi.istype('int64_t', v) then
        buf:put_uleb128(KOBJ.I64)
        local lo, hi = dword_get_u32(int64_new, v)
        buf:put_uleb128(lo)
        buf:put_uleb128(hi)
    else
        assert(false, 'Unknown KCDATA : ' .. tostring(v))
    end
end

local function write_ktabk(buf, val, narrow)
    local tp = type(val)
    if tp == "string" then
        buf:put_uleb128(KTAB.STR + #val)
        buf:put_bytes(val)
    elseif tp == "number" then
        if narrow and num_is_int32(val) then
            buf:put(KTAB.INT)
            buf:put_uleb128(val)
            return
        end
        local lo, hi = dword_get_u32(double_new, val)
        buf:put(KTAB.NUM)
        buf:put_uleb128(lo)
        buf:put_uleb128(hi)
    elseif tp == "boolean" then
        buf:put(val and KTAB.TRUE or KTAB.FALSE)
    elseif tp == "nil" then
        buf:put(KTAB.NIL)
    else
        assert(false, "error with constant in ktabk")
    end
end

function KObj.__index:write_table(buf, v)
    buf:put_uleb128(KOBJ.TAB)
    buf:put_uleb128(v.narray)
    buf:put_uleb128(v.nhash)
    for i = 0, v.narray - 1 do
        write_ktabk(buf, v.array[i], true)
    end
    for i = 1, v.nhash do
        write_ktabk(buf, v.hash_keys[i], false)
        write_ktabk(buf, v.hash_values[i], true)
    end
end

KNum = {}
KNum.__index = {}
function KNum.new(v)
    return setmetatable({ v }, KNum)
end
function KNum.__index:write(buf)
    buf:put_number(self[1])
end

-- Determine if a backward jump up to "label_name" needs an UCLO instruction.
local function label_need_uclo(scope, label_name)
    local label_list = scope.goto_labels
    local need_uclo = false
    for i = #label_list, 1, -1 do
        local label = label_list[i]
        need_uclo = need_uclo or label.need_uclo
        if label.name == label_name then break end
    end
    return need_uclo
end

local function goto_label_resolve(self, label_name)
    local scope = self.scope
    while scope do
        local ls = scope.goto_labels
        for i = 1, #ls do
            if ls[i].name == label_name then
                return ls[i]
            end
        end
        scope = scope.outer
    end
end

local function goto_label_append(self, label_name)
    local scope = self.scope
    local label = { name = label_name, scope = scope, basereg = self.freereg, need_uclo = false, pc = #self.code }
    scope.goto_labels[#scope.goto_labels + 1] = label
    return label
end

local function goto_fixup_append(self, label_name, source_line, pc)
    local scope = self.scope
    local fixup = { name = label_name, basereg = self.freereg, need_uclo = false, pc = pc, source_line = source_line }
    scope.goto_fixups[#scope.goto_fixups + 1] = fixup
end

-- Follow the scope from the bottom (src_scope) to upper (dest_scope) to
-- tell if an UCLO instruction is needed to perform such a backward jump.
local function goto_uclo_backward(self, label, src_scope, dest_scope)
    local scope = src_scope
    local need_uclo = false
    while scope and scope ~= dest_scope do
        need_uclo = need_uclo or scope.need_uclo
        scope = scope.outer
    end
    if scope then
        need_uclo = need_uclo or label_need_uclo(scope, label.name)
    end
    return need_uclo
end

-- Bind dangling goto instructions ("fixups") to a label.
-- When a dangling goto is bound to a label it is removed from the
-- fixup list.
local function goto_label_bind(self, label_name)
    local fixup_list = self.scope.goto_fixups
    local i = 1
    while fixup_list[i] do
        local fixup = fixup_list[i]
        if fixup.name == label_name then
            if self.freereg > fixup.basereg then return fixup, fixup.basereg end
            self:fix_goto(fixup.pc, self.freereg, fixup.need_uclo, #self.code - fixup.pc)
            table.remove(fixup_list, i)
        else
            i = i + 1
        end
    end
end

Proto = {
    CHILD  = 0x01; -- Has child prototypes.
    VARARG = 0x02; -- Vararg function.
    FFI    = 0x04; -- Uses BC_KCDATA for FFI datatypes.
    NOJIT  = 0x08; -- JIT disabled for this function.
    ILOOP  = 0x10; -- Patched bytecode with ILOOP etc.
}
function Proto.new(flags, firstline, lastline, outer)
    local proto = setmetatable({
        flags  = flags or 0;
        outer  = outer;
        params = {};
        upvals = {};
        code   = {};
        kobj   = {};
        knum   = {};
        debug  = {};
        lninfo = {};
        labels = {};
        tohere = {};
        kcache = {};
        varinfo = {};
        freereg   = 0;
        firstline = firstline;
        lastline  = lastline;
        currline  = firstline;
        numlines  = lastline - firstline;
        framesize = 1;
        explret = false;
    }, Proto)

    proto:enter()
    return proto
end
Proto.__index = {}
function Proto.__index:nextreg(num)
    num = num or 1
    local reg = self.freereg
    self.freereg = self.freereg + num
    if self.freereg >= self.framesize then
        self.framesize = self.freereg
    end
    return reg
end
function Proto.__index:setreg(reg)
    self.freereg = reg
    if self.freereg >= self.framesize then
        self.framesize = self.freereg
    end
end
function Proto.__index:maxframe(reg)
    if reg > self.framesize then
        self.framesize = reg
    end
end

function Proto.__index:enter()
    local outer = self.scope
    self.scope = {
        actvars    = {};
        basereg    = self.freereg;
        goto_labels = {};
        goto_fixups = {};
        outer      = outer;
    }
    goto_label_append(self, nil) -- Add a label without name.
    return self.scope
end
function Proto.__index:is_root_scope()
    return (self.scope.outer == nil)
end
function Proto.__index:leave()
    for i=1, #self.scope.actvars do
        self.scope.actvars[i].endpc = #self.code
    end
    local freereg = self.scope.basereg
    self.scope = self.scope.outer
    self.freereg = freereg
end
function Proto.__index:child(firstline, lastline)
    self.flags = bor(self.flags, Proto.CHILD)
    local child = Proto.new(0, firstline, lastline, self)
    child.idx = #self.kobj
    self.kobj[child] = #self.kobj
    self.kobj[#self.kobj + 1] = child
    return child
end
function Proto.__index:parent()
    local parent = self.outer
    parent.flags = bor(parent.flags, band(self.flags, Proto.FFI))
    return parent
end
function Proto.__index:kpri(val)
    if val == nil then return VKNIL
    elseif val == true then return VKTRUE
    elseif val == false then return VKFALSE
    else error('Invalid primitive value: '..val) end
end
function Proto.__index:const(val)
    if type(val) == 'string' then
        if not self.kcache[val] then
            local item = KObj.new(val)
            item.idx = #self.kobj
            self.kcache[val] = item
            self.kobj[#self.kobj + 1] = item
        end
    elseif type(val) == 'number' then
        if not self.kcache[val] then
            local item = KNum.new(val)
            item.idx = #self.knum
            self.kcache[val] = item
            self.knum[#self.knum + 1] = item
        end
    elseif type(val) == 'cdata' then
        local item = KObj.new(val)
        item.idx = #self.kobj
        self.kobj[#self.kobj + 1] = item
        -- Set the FFI flag for the proto.
        self.flags = bor(self.flags, Proto.FFI)
        return item.idx
    else
        error("not a const: "..tostring(val))
    end
    return self.kcache[val].idx
end
function Proto.__index:new_table_template()
    local t = { array = {}, hash_keys = {}, hash_values = {} }
    local item = KObj.new(t)
    item.idx = #self.kobj
    self.kobj[#self.kobj + 1] = item
    return item.idx, t
end
function Proto.__index:line(ln)
    self.currline = ln
end
-- Set line number for the last generated instruction.
function Proto.__index:setpcline(ln)
    self.lninfo[#self.lninfo] = ln
end
function Proto.__index:emit(op, a, b, c)
    local ins = Ins.new(op, a, b, c)
    self.code[#self.code + 1] = ins
    self.lninfo[#self.lninfo + 1] = self.currline
    return ins
end
function Proto.__index:write(buf)
    local has_child
    if band(self.flags, Proto.CHILD) ~= 0 then
        has_child = true
        for i=1, #self.kobj do
            local o = self.kobj[i]
            if typeof(o) == Proto then
                o:write(buf)
            end
        end
    end

    local body = Buf.new()
    self:write_body(body)

    local offs = body.offs
    self:write_debug(body)

    local head = Buf.new()
    self:write_head(head, body.offs - offs)

    buf:put_uleb128(head.offs + body.offs) -- length of the proto

    local head_pack = ffi.string(head.data, head.offs)
    local body_pack = ffi.string(body.data, body.offs)

    buf:put_bytes(head_pack)
    buf:put_bytes(body_pack)
end
function Proto.__index:write_head(buf, size_debug)
    buf:put(self.flags)
    buf:put(#self.params)
    buf:put(self.framesize)
    buf:put(#self.upvals)
    buf:put_uleb128(#self.kobj)
    buf:put_uleb128(#self.knum)
    buf:put_uleb128(#self.code)
    buf:put_uleb128(size_debug or 0)
    buf:put_uleb128(self.firstline)
    buf:put_uleb128(self.numlines)
end
function Proto.__index:write_body(buf)
    for i=1, #self.code do
        self.code[i]:write(buf)
    end
    for i=1, #self.upvals do
        local uval = self.upvals[i]
        if uval.outer_idx then
            -- the upvalue refer to a local of the enclosing function
            local btag = uval.vinfo.mutable and 0x8000 or 0xc000
            local uv = bor(uval.outer_idx, btag)
            buf:put_uint16(uv)
        else
            -- the upvalue refer to an upvalue of the enclosing function
            local uv = uval.outer_uv
            buf:put_uint16(uv)
        end
    end
    for i=#self.kobj, 1, -1 do
        local o = self.kobj[i]
        if typeof(o) == Proto then
            buf:put_uleb128(KOBJ.CHILD)
        else
            self.kobj[i]:write(buf)
        end
    end
    for i=1, #self.knum do
        self.knum[i]:write(buf)
    end
end
function Proto.__index:write_debug(buf)
    local first = self.firstline
    if self.numlines < 256 then
        for i=1, #self.lninfo do
            local delta = self.lninfo[i] - first
            buf:put_uint8(delta)
        end
    elseif self.numlines < 65536 then
        for i=1, #self.lninfo do
            local delta = self.lninfo[i] - first
            buf:put_uint16(delta)
        end
    else
        for i=1, #self.lninfo do
            local delta = self.lninfo[i] - first
            buf:put_uint32(delta)
        end
    end
    for i=1, #self.upvals do
        local uval = self.upvals[i]
        buf:put_bytes(uval.vinfo.name.."\0")
    end
    local lastpc = 0
    for i=1, #self.varinfo do
        local var = self.varinfo[i]
        local startpc, endpc = (var.startpc or 0), (var.endpc or 0) + 1
        if type(var.name) == "number" then
            for n = var.name, var.name + 2 do
                buf:put(n)
                buf:put_uleb128(startpc - lastpc)
                buf:put_uleb128(endpc - startpc)
                lastpc = startpc
            end
        else
            buf:put_bytes(var.name.."\0")
            buf:put_uleb128(startpc - lastpc)
            buf:put_uleb128(endpc - startpc)
        end
        lastpc = startpc
    end
    buf:put(0)
end
function Proto.__index:newvar(name, dest)
    dest = dest or self:nextreg()
    local vinfo = {
        idx = dest,
        startpc = #self.code + 1,
        endpc = #self.code + 1,
        name = name,
        mutable = false,
    }
    -- scoped variable info
    self.scope.actvars[name] = vinfo
    self.scope.actvars[#self.scope.actvars + 1] = vinfo

    -- for the debug segment only
    self.varinfo[#self.varinfo + 1] = vinfo

    return vinfo
end
-- Add debug variables informations for implicit for variables.
-- The code should be 1 for simple "for" statements and 4 for
-- "for in" statements.
function Proto.__index:forivars(code)
    local vinfo = { name = code, startpc = #self.code + 1 }
    self.varinfo[#self.varinfo + 1] = vinfo
    return vinfo
end
local function scope_var_lookup(scope, name)
    while scope do
        local var = scope.actvars[name]
        if var then return var, scope end
        scope = scope.outer
    end
end
function Proto.__index:lookup(name)
    local var = scope_var_lookup(self.scope, name)
    if var then
        return var, false
    elseif self.outer then
        local v = self.outer:lookup(name)
        if v then return v, true end
    end
    -- Global variable.
    return nil, false
end
function Proto.__index:var_inverse_lookup(var)
    for i = 1, #self.scope.actvars do
        local xvar = self.scope.actvars[i]
        if xvar.idx == var then return xvar end
    end
end
function Proto.__index:param(...)
    local var = self:newvar(...)
    var.startpc = 0
    self.params[#self.params + 1] = var
    return var.idx
end
-- Find into the scope the label that immediately precedes where the given
-- variable is declared and return the label.
function Proto.__index:label_var_lookup(scope, var)
    local label_list = scope.goto_labels
    local label = label_list[1]
    for i = 2, #label_list do
        local next_label = label_list[i]
        if next_label.basereg > var.idx then break end
        label = next_label
    end
    return label
end
function Proto.__index:upval(name)
    if not self.upvals[name] then
        local proto, upval = self.outer, {}
        local var, scope
        while proto do
            var, scope = scope_var_lookup(proto.scope, name)
            if var then
                break
            end
            proto = proto.outer
        end

        upval = { vinfo = var; proto = proto; }

        -- for each upval we set either outer_idx or outer_uv
        if proto == self.outer then
            -- The variable is in the enclosing function's scope.
            -- We store just its register index.
            upval.outer_idx = var.idx
            local label = self:label_var_lookup(scope, var)
            label.need_uclo = true
            scope.need_uclo = true
        else
            -- The variable is in the outer scope of the enclosing
            -- function. We register this variable as an upvalue for
            -- the enclosing function. Then we store the upvale index.
            upval.outer_uv = self.outer:upval(name)
        end

        self.upvals[name] = upval
        upval.idx = #self.upvals
        self.upvals[#self.upvals + 1] = upval
    end
    return self.upvals[name].idx
end
function Proto.__index:here(name)
    if self.tohere[name] then
        -- forward jump
        local back = self.tohere[name]
        for i=1, #back do
            local offs = back[i]
            self.code[offs][3] = #self.code - offs
        end
        self.tohere[name] = nil
    else
        -- backward jump
        self.labels[name] = #self.code - 1
    end
    return name
end
function Proto.__index:enable_jump(name)
    if type(name) == 'number' then
        error("bad label")
    end
    local here = self.tohere[name]
    if not here then
        here = {}
        self.tohere[name] = here
    end
    here[#here + 1] = #self.code + 1
end
local function locate_jump(self, name)
    if self.labels[name] then
        -- backward jump
        return self.labels[name] - #self.code
    else
        -- forward jump
        self:enable_jump(name)
        return NO_JMP
    end
end
-- Emit a jump instruction, JMP or UCLO, to the given "name" location
-- using 'basereg' register.
function Proto.__index:scope_jump(name, basereg, need_uclo)
    local jump = locate_jump(self, name)
    return self:emit(need_uclo and BC.UCLO or BC.JMP, basereg, jump)
end
function Proto.__index:jump(name, basereg)
    local jump = locate_jump(self, name)
    return self:emit(BC.JMP, basereg, jump)
end
-- When closing a scope this function is used to transfer all the dangling
-- gotos (fixups) in the enclosing scope.
-- Before being copied the basereg and need_uclo information is updated.
-- The original list is not modified since it will be dropped with the scope
-- itself.
function Proto.__index:fscope_end()
    local scope = self.scope
    local fixup_list = scope.goto_fixups
    local outer_list = scope.outer.goto_fixups
    for i = 1, #fixup_list do
        local fixup = fixup_list[i]
        fixup.need_uclo = fixup.need_uclo or scope.need_uclo
        fixup.basereg = scope.basereg
        outer_list[#outer_list + 1] = fixup
    end
end
function Proto.__index:fix_goto(pc, basereg, need_uclo, jump)
    local ins = self.code[pc]
    local op = need_uclo and BC.UCLO or BC.JMP
    ins:rewrite(op, basereg, jump)
end
function Proto.__index:goto_jump(label_name, source_line)
    local label = goto_label_resolve(self, label_name)
    if label then -- backward jump
        local dest_scope, basereg = label.scope, label.basereg
        local need_uclo = goto_uclo_backward(self, label, self.scope, dest_scope)
        local op = need_uclo and BC.UCLO or BC.JMP
        self:emit(op, basereg, label.pc - #self.code - 1)
    else -- forward jump
        self:emit(BC.JMP, 0, NO_JMP)
        goto_fixup_append(self, label_name, source_line, #self.code)
    end
end
function Proto.__index:goto_label(label_name)
    if goto_label_resolve(self, label_name) then
        return false, string.format("duplicate label '%s'", label_name)
    end
    local label = goto_label_append(self, label_name)
    local goto_err, var_err = goto_label_bind(self, label_name)
    if goto_err then
        local var = self:var_inverse_lookup(var_err)
        return false, string.format("<goto %s> jumps into the scope of local '%s'", label_name, var.name)
    else
        return true, label
    end
end
-- Add information in the current scope about the loop, its exit location
-- and save register.
function Proto.__index:loop_register(exit, exit_reg)
    self.scope.loop_exit = exit
    self.scope.loop_basereg = exit_reg
end

-- Looks in current and outer scopes for the closer loop and returns:
-- loop's base register and if uclo is needed.
function Proto.__index:current_loop()
    local scope = self.scope
    local need_uclo = false
    while scope do
        need_uclo = need_uclo or scope.need_uclo
        if scope.loop_exit then break end
        scope = scope.outer
    end
    assert(scope, "no loop to break")
    return scope.loop_basereg, scope.loop_exit, need_uclo
end
function Proto.__index:global_uclo()
    local scope = self.scope
    while scope do
        if scope.need_uclo then return true end
        scope = scope.outer
    end
end
function Proto.__index:loop(name)
    if self.labels[name] then
        -- backward jump
        local offs = self.labels[name]
        return self:emit(BC.LOOP, self.freereg, offs - #self.code)
    else
        -- forward jump
        self:enable_jump(name)
        return self:emit(BC.LOOP, self.freereg, NO_JMP)
    end
end
function Proto.__index:op_jump(jump, base)
    return self:emit(BC.JMP, base, jump)
end
function Proto.__index:op_loop(delta)
    return self:emit(BC.LOOP, self.freereg, delta)
end

-- branch if condition
function Proto.__index:op_test(cond, a, here, freereg)
    local inst = self:emit(cond and BC.IST or BC.ISF, 0, a)
    if here then here = self:jump(here, freereg) end
    return inst, here
end
function Proto.__index:op_testmov(cond, dest, var, here, freereg)
    local inst = self:emit(cond and BC.ISTC or BC.ISFC, dest, var)
    if here then here = self:jump(here, freereg) end
    return inst, here
end
function Proto.__index:op_comp(cond, a, btag, b, here, freereg, swap)
    local suffix = (cond == 'EQ' or cond == 'NE') and btag or ''
    local ins = BC['IS'..cond..suffix]
    assert(suffix == '' or not swap, "Cannot swap comparison for VN equality test")
    if swap then self:emit(ins, b, a) else self:emit(ins, a, b) end
    self:jump(here, freereg)
end

function Proto.__index:op_infix(opname, dest, v1tag, var1, v2tag, var2)
    local ins = BC[opname .. v1tag .. v2tag]
    assert(ins, "Invalid operation: " .. opname)
    if v1tag == 'N' then
        var1, var2 = var2, var1
    end
    return self:emit(ins, dest, var1, var2)
end
function Proto.__index:op_add(dest, var1, var2)
    return self:emit(BC.ADDVV, dest, var1, var2)
end
function Proto.__index:op_sub(dest, var1, var2)
    return self:emit(BC.SUBVV, dest, var1, var2)
end
function Proto.__index:op_mul(dest, var1, var2)
    return self:emit(BC.MULVV, dest, var1, var2)
end
function Proto.__index:op_div(dest, var1, var2)
    return self:emit(BC.DIVVV, dest, var1, var2)
end
function Proto.__index:op_mod(dest, var1, var2)
    return self:emit(BC.MODVV, dest, var1, var2)
end
function Proto.__index:op_pow(dest, var1, var2)
    return self:emit(BC.POW, dest, var1, var2)
end
function Proto.__index:op_gget(dest, name)
    return self:emit(BC.GGET, dest, self:const(name))
end
function Proto.__index:op_gset(from, name)
    return self:emit(BC.GSET, from, self:const(name))
end

function Proto.__index:op_not(dest, var1)
    return self:emit(BC.NOT, dest, var1)
end
function Proto.__index:op_unm(dest, var1)
    return self:emit(BC.UNM, dest, var1)
end
function Proto.__index:op_len(dest, var1)
    return self:emit(BC.LEN, dest, var1)
end
function Proto.__index:op_nils(dest, want)
    if want == 1 then
        return self:emit(BC.KPRI, dest, VKNIL)
    elseif want > 1 then
        return self:emit(BC.KNIL, dest, dest + want - 1)
    end
end

function Proto.__index:op_move(dest, from)
    return self:emit(BC.MOV, dest, from)
end
function Proto.__index:op_load(dest, val)
    local tv = type(val)
    if tv == 'nil' then
        return self:emit(BC.KPRI, dest, VKNIL)
    elseif tv == 'boolean' then
        return self:emit(BC.KPRI, dest, val and VKTRUE or VKFALSE)
    elseif tv == 'string' then
        return self:emit(BC.KSTR, dest, self:const(val))
    elseif tv == 'number' then
        if math.floor(val) == val and val < 0x8000 and val >= -0x8000 then
            return self:emit(BC.KSHORT, dest, val)
        else
            return self:emit(BC.KNUM, dest, self:const(val))
        end
    elseif tv == 'cdata' then
        self:emit(BC.KCDATA, dest, self:const(val))
    else
        error("cannot load as constant: "..tostring(val))
    end
end
function Proto.__index:op_tdup(dest, index)
    return self:emit(BC.TDUP, dest, index)
end
function Proto.__index:op_tnew(dest, narr, nrec)
    return self:emit(BC.TNEW, dest, tabsize(narr or 0, nrec or 0))
end
function Proto.__index:op_tget(dest, tab, ktag, key)
    local ins_name = 'TGET' .. ktag
    self:emit(BC[ins_name], dest, tab, key)
end
function Proto.__index:op_tset(tab, ktag, key, val)
    local ins_name = 'TSET' .. ktag
    self:emit(BC[ins_name], val, tab, key)
end
function Proto.__index:op_tsetm(base, vnum)
    local dptr = double_new(0)
    local iptr = ffi.cast('uint32_t*', dptr)
    iptr[0] = vnum
    iptr[1] = 0x43300000 -- Biased integer to avoid denormals.
    local vidx = self:const(dptr[0])
    return self:emit(BC.TSETM, base + 1, vidx)
end
function Proto.__index:op_fnew(dest, pidx)
    return self:emit(BC.FNEW, dest, pidx)
end
function Proto.__index:op_uclo(base, jump)
    return self:emit(BC.UCLO, base, jump)
end
function Proto.__index:op_uset(uv, vtag, val)
    local ins = BC['USET' .. vtag]
    self:emit(ins, uv, val)
end
function Proto.__index:op_uget(dest, uv)
    return self:emit(BC.UGET, dest, uv)
end
-- Generate the UCLO + JMP instruction at the end of a block.
-- The UCLO is generated only if there are open upvalues. The "exit"
-- parameter is optional. If omitted there will be no JMP instruction.
function Proto.__index:close_block(reg, exit)
    if self.scope.need_uclo then
        if exit then
            self:enable_jump(exit)
            self:emit(BC.UCLO, reg, NO_JMP)
        else
            self:emit(BC.UCLO, reg, 0)
        end
    else
        if exit then
            self:enable_jump(exit)
            return self:emit(BC.JMP, reg, NO_JMP)
        end
    end
end
function Proto.__index:close_uvals()
    if self:global_uclo() then
        self:emit(BC.UCLO, 0, 0)
    end
end
function Proto.__index:close_proto()
    if not self.explret then
        self:close_uvals()
        self:op_ret0()
    end
    local fixups = self.scope.goto_fixups
    if #fixups > 0 then
        local label = fixups[1]
        local msg = string.format("undefined label '%s'", label.name)
        return msg, label.source_line
    end
    self:leave()
end
function Proto.__index:op_ret(base, rnum)
    return self:emit(BC.RET, base, rnum + 1)
end
function Proto.__index:op_ret0()
    return self:emit(BC.RET0, 0, 1)
end
function Proto.__index:op_ret1(base)
    return self:emit(BC.RET1, base, 2)
end
function Proto.__index:op_retm(base, rnum)
    return self:emit(BC.RETM, base, rnum)
end
function Proto.__index:op_varg(base, want)
    return self:emit(BC.VARG, base, want + 1, #self.params)
end
function Proto.__index:op_call(base, want, narg)
    return self:emit(BC.CALL, base, want + 1, narg + 1)
end
function Proto.__index:op_callt(base, narg)
    return self:emit(BC.CALLT, base, narg + 1)
end
function Proto.__index:op_callm(base, want, narg)
    return self:emit(BC.CALLM, base, want + 1, narg)
end
function Proto.__index:op_callmt(base, narg)
    return self:emit(BC.CALLMT, base, narg)
end
function Proto.__index:op_fori(base, stop, step)
    local loop = self:emit(BC.FORI, base, NO_JMP)
    self:here(loop)
    return loop
end
function Proto.__index:op_forl(base, loop)
    local offs = self.labels[loop]
    loop[3] = #self.code - offs
    return self:emit(BC.FORL, base, offs - #self.code)
end
function Proto.__index:op_iterc(base, want)
    return self:emit(BC.ITERC, base, want + 1, 3)
end
function Proto.__index:op_iterl(base, loop)
    local offs = self.labels[loop]
    return self:emit(BC.ITERL, base, offs - #self.code)
end
function Proto.__index:op_cat(base, rbot, rtop)
    return self:emit(BC.CAT, base, rbot, rtop)
end

Dump = {
    HEAD_1 = 0x1b;
    HEAD_2 = 0x4c;
    HEAD_3 = 0x4a;
    VERS   = jit_v21 and 0x02 or 0x01;
    BE     = 0x01;
    STRIP  = 0x02;
    FFI    = 0x04;
    DEBUG  = false;
}
Dump.__index = {}
function Dump.new(main, name)
    local self =  setmetatable({
        main  = main;
        name  = name;
        flags = band(main.flags, Dump.FFI);
    }, Dump)
    return self
end
function Dump.__index:write_header(buf)
    buf:put(Dump.HEAD_1)
    buf:put(Dump.HEAD_2)
    buf:put(Dump.HEAD_3)
    buf:put(Dump.VERS)
    buf:put(self.flags)
    local name = self.name and "@" .. self.name or "(binary)"
    if band(self.flags, Dump.STRIP) == 0 then
        buf:put_uleb128(#name)
        buf:put_bytes(name)
    end
end
function Dump.__index:write_footer(buf)
    buf:put(0x00)
end
function Dump.__index:pack()
    local buf = Buf.new()
    self:write_header(buf)
    self.main:write(buf)
    self:write_footer(buf)
    return buf:pack()
end

return {
    Buf   = Buf;
    Ins   = Ins;
    KNum  = KNum;
    KObj  = KObj;
    Proto = Proto;
    Dump  = Dump;
    BC    = BC;
}

