diff options
Diffstat (limited to 'src/lcode.js')
| -rw-r--r-- | src/lcode.js | 223 | 
1 files changed, 223 insertions, 0 deletions
| diff --git a/src/lcode.js b/src/lcode.js new file mode 100644 index 0000000..695c7dc --- /dev/null +++ b/src/lcode.js @@ -0,0 +1,223 @@ +/* jshint esversion: 6 */ +"use strict"; + +const assert   = require('assert'); + +const lopcode  = require('./lopcode.js'); +const llex     = require('./llex.js'); +const lcode    = require('./lcode.js'); +const OpCodesI = lopcode.OpCodesI; + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +const NO_JUMP = -1; + +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +const getjump = function(fs, pc) { +    let offset = fs.f.code[pc].sBx; +    if (offset === NO_JUMP)  /* point to itself represents end of list */ +        return NO_JUMP;  /* end of list */ +    else +        return pc + 1 + offset;  /* turn offset into absolute position */ +}; + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +const fixjump = function(fs, pc, dest) { +    let jmp = fs.f.code[pc]; +    let offset = dest - (pc + 1); +    assert(dest !== NO_JUMP); +    if (Math.abs(offset) > lopcode.MAXARG_sBx) +        llex.luaX_syntaxerror(fs.ls, "control structure too long"); +    lopcode.SETARG_sBx(jmp, offset); +}; + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +const luaK_concat = function(fs, l1, l2) { +    if (l2 === NO_JUMP) return;  /* nothing to concatenate? */ +    else if (l1 === NO_JUMP)  /* no original list? */ +        l1[0] = l2; +    else { +        let list = l1[0]; +        let next = getjump(fs, list); +        while (next !== NO_JUMP) {  /* find last element */ +            list = next; +            next = getjump(fs, list); +        } +        fixjump(fs, list, l2); +    } +}; + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ +const luaK_jump = function (fs) { +    let jpc = fs.jpc;  /* save list of jumps to here */ +    fs.jpc = NO_JUMP;  /* no more jumps to here */ +    let j = luaK_codeAsBx(fs, OpCodesI.OP_JMP, 0, NO_JUMP); +    luaK_concat(fs, j, jpc);  /* keep them on hold */ +    return j; +}; + +/* +** Code a 'return' instruction +*/ +const luaK_ret = function(fs, first, nret) { +    luaK_codeABC(fs, OpCodesI.OP_RETURN, first, nret + 1, 0); +}; + +/* +** returns current 'pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +const luaK_getlabel = function(fs) { +    fs.lasttarget = fs.pc; +    return fs.pc; +}; + +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ +const getjumpcontrol = function(fs, pc) { +    if (pc >= 1 && lopcode.testTMode(fs.f.code[pc - 1].opcode)) +        return fs.f.code[pc - 1]; +    else +        return fs.f.code[pc]; +}; + +/* +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) +*/ +const patchtestreg = function(fs, node, reg) { +    let i = getjumpcontrol(fs, node); +    if (i.opcode !== OpCodesI.OP_TESTSET) +        return false;  /* cannot patch other instructions */ +    if (reg !== lopcode.NO_REG && reg !== i.B) +        lopcode.SETARG_A(i, reg); +    else { +        /* no register to put value or register already has the value; +           change instruction to simple test */ +        i = lopcode.CREATE_ABC(OpCodesI.OP_TEST, i.B, 0, i.C); +    } +    return true; +}; + +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ +const patchlistaux = function(fs, list, vtarget, reg, dtarget) { +    while (list !== NO_JUMP) { +        let next = getjump(fs, list); +        if (patchtestreg(fs, list, reg)) +            fixjump(fs, list, vtarget); +        else +            fixjump(fs, list, dtarget);  /* jump to default target */ +        list = next; +    } +}; + +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +const luaK_patchtohere = function(fs, list) { +    luaK_getlabel(fs);  /* mark "here" as a jump target */ +    luaK_concat(fs, fs.jpc, list); +}; + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +const luaK_patchlist = function(fs, list, target) { +    if (target === fs.pc)  /* 'target' is current position? */ +        luaK_patchtohere(fs, list);  /* add list to pending jumps */ +    else { +        assert(target < fs.pc); +        patchlistaux(fs, list, target, lopcode.NO_REG, target); +    } +}; + +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ +const luaK_patchclose = function(fs, list, level) { +    level++;  /* argument is +1 to reserve 0 as non-op */ +    for (; list !== NO_JUMP; list = getjump(fs, list)) { +        let ins = fs.f.code[list]; +        assert(ins.opcode === OpCodesI.OP_JMP && (ins.A === 0 || i.A >= level); +        lopcode.SETARG_A(ins, level); +    } +}; + +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +const luaK_code = function(fs, i) { +    let f = fs.f; +    lcode.dischargejpc(fs);  /* 'pc' will change */ +    /* put new instruction in code array */ +    f.code[fs.pc] = i; +    f.lineinfo[fs.pc] = fs.ls.lastline; +    return fs.pc++; +}; + +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +const luaK_codeABC = function(fs, o, a, b, c) { +    assert(lopcode.getOpMode(o) == lopcode.iABC); +    assert(lopcode.getBMode(o) != lopcode.OpArgN || b === 0); +    assert(lopcode.getCMode(o) != lopcode.OpArgN || c === 0); +    assert(a <= lopcode.MAXARG_A && b <= lopcode.MAXARG_B && c <= lopcode.MAXARG_C); +    return luaK_code(fs, lopcode.CREATE_ABC(o, a, b, c)); +}; + +/* +** Format and emit an 'iABx' instruction. +*/ +const luaK_codeABx = function(fs, o, a, bc) { +    lua_assert(lopcode.getOpMode(o) == lopcode.iABx || getOpMode(o) == lopcode.iAsBx); +    lua_assert(lopcode.getCMode(o) == lopcode.OpArgN); +    lua_assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); +    return luaK_code(fs, lopcode.CREATE_ABx(o, a, bc)); +}; + +const luaK_codeAsBx = function(fs,o,A,sBx) { +    return luaK_codeABx(fs, o, A, (sBx) + lopcode.MAXARG_sBx); +}; + +module.exports.NO_JUMP          = NO_JUMP; +module.exports.luaK_codeABx     = luaK_codeABx; +module.exports.luaK_codeAsBx    = luaK_codeAsBx; +module.exports.luaK_getlabel    = luaK_getlabel; +module.exports.luaK_jump        = luaK_jump; +module.exports.luaK_patchclose  = luaK_patchclose; +module.exports.luaK_patchlist   = luaK_patchlist; +module.exports.luaK_patchtohere = luaK_patchtohere; +module.exports.luaK_ret         = luaK_ret;
\ No newline at end of file | 
