/* vim: ts=4 sw=4 sts=4 et tw=78
 * Portions copyright (c) 2015-present, Facebook, Inc. All rights reserved.
 * Portions copyright (c) 2011 James R. McKaskill.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
|.arch arm
|.actionlist build_actionlist
|.globalnames globnames
|.externnames extnames

#define JUMP_SIZE 8
#define MIN_BRANCH ((INT32_MIN) >> 8)
#define MAX_BRANCH ((INT32_MAX) >> 8)
#define BRANCH_OFF 4

static void compile_extern_jump(struct jit* jit, lua_State* L, cfunction func, uint8_t* code)
{
    /* The jump code is the function pointer followed by a stub to call the
     * function pointer. The stub exists so we can jump to functions with an
     * offset greater than 32MB.
     *
     * Note we have to manually set this up since there are commands buffered
     * in the jit state.
     */
    *(cfunction*) code = func;
    /* ldr pc, [pc - 12] */
    *(uint32_t*) &code[4] = 0xE51FF00CU;
}

|.define TOP, r4
|.define L_ARG, r5
|.define DATA, r6
|.define DATA2, r7

|.macro load32, reg, val
| ldr reg, [pc]
| b >5
|.long val
|5:
|.endmacro

|.macro lcall, func
| mov r0, L_ARG
| bl func
|.endmacro

void compile_globals(struct jit* jit, lua_State* L)
{
    (void) jit;
}

cfunction compile_callback(lua_State* L, int fidx, int ct_usr, const struct ctype* ct)
{
    struct jit* Dst = get_jit(L);;
    int i, nargs, num_upvals, ref;
    const struct ctype* mt;

    int top = lua_gettop(L);

    ct_usr = lua_absindex(L, ct_usr);
    fidx = lua_absindex(L, fidx);
    nargs = (int) lua_rawlen(L, ct_usr);

    dasm_setup(Dst, build_actionlist);

    lua_newtable(L);
    lua_pushvalue(L, -1);
    ref = luaL_ref(L, LUA_REGISTRYINDEX);
    num_upvals = 0;

    if (ct->has_var_arg) {
        luaL_error(L, "can't create callbacks with varargs");
    }

    /* prolog and get the upval table */
    | mov r12, sp
    | push {r0, r1, r2, r3} // do this first so that r0-r3 is right before stack bound arguments
    | push {TOP, L_ARG, DATA, DATA2, r12, lr}
    | sub DATA, r12, #16 // points to r0 on stack
    | ldr L_ARG, [pc, #8]
    | ldr r2, [pc, #8]
    | ldr r1, [pc, #8]
    | b >1
    |.long L, ref, LUA_REGISTRYINDEX
    |1:
    | lcall extern lua_rawgeti

    /* get the lua function */
    lua_pushvalue(L, fidx);
    lua_rawseti(L, -2, ++num_upvals);
    | mov r2, #num_upvals
    | mvn r1, #0 // -1
    | lcall extern lua_rawgeti

    for (i = 1; i <= nargs; i++) {
        lua_rawgeti(L, ct_usr, i);
        mt = (const struct ctype*) lua_touserdata(L, -1);

        if (mt->pointers || mt->is_reference) {
            lua_getuservalue(L, -1);
            lua_rawseti(L, -3, ++num_upvals); /* usr value */
            lua_rawseti(L, -2, ++num_upvals); /* mt */

            | mov r2, #num_upvals-1 // usr value
            | mvn r1, #i // -i-1, stack is upval table, func, i-1 args
            | lcall extern lua_rawgeti
            | load32 r2, mt
            | mvn r1, #0 // -1
            | lcall extern push_cdata
            | ldr r2, [DATA], #4
            | str r2, [r0]
            | mvn r1, #1 // -2
            | lcall extern lua_remove // remove the usr value

        } else {
            switch (mt->type) {
            case INT64_TYPE:
                lua_rawseti(L, -2, ++num_upvals); /* mt */
                | lcall extern lua_pushnil
                | load32 r2, mt
                | mvn r1, #0 // -1
                | lcall extern push_cdata
                | ldr r2, [DATA], #4
                | ldr r3, [DATA], #4
                | str r2, [r0]
                | str r3, [r0, #4]
                | mvn r1, #1 // -2
                | lcall extern lua_remove // remove the nil usr
                break;

            case INTPTR_TYPE:
                lua_rawseti(L, -2, ++num_upvals); /* mt */
                | lcall extern lua_pushnil
                | load32 r2, mt
                | mvn r1, #0 // -1
                | lcall extern push_cdata
                | ldr r2, [DATA], #4
                | str r2, [r0]
                | mvn r1, #1 // -2
                | lcall extern lua_remove // remove the nil usr
                break;

            case BOOL_TYPE:
                lua_pop(L, 1);
                | ldr r1, [DATA], #4
                | lcall extern lua_pushboolean
                break;

            case INT8_TYPE:
                lua_pop(L, 1);
                | ldr r1, [DATA], #4
                | mov r1, r1, lsl #24
                if (mt->is_unsigned) {
                    | mov r1, r1, lsr #24
                } else {
                    | mov r1, r1, asr #24
                }
                | lcall extern push_int
                break;

            case INT16_TYPE:
                lua_pop(L, 1);
                | ldr r1, [DATA], #4
                | mov r1, r1, lsl #16
                if (mt->is_unsigned) {
                    | mov r1, r1, lsr #16
                } else {
                    | mov r1, r1, asr #16
                }
                | lcall extern push_int
                break;

            case ENUM_TYPE:
            case INT32_TYPE:
                lua_pop(L, 1);
                | ldr r1, [DATA], #4
                | lcall extern push_int
                break;

            case FLOAT_TYPE:
                lua_pop(L, 1);
                | ldr r1, [DATA], #4
                | lcall extern push_float
                break;

            case DOUBLE_TYPE:
                lua_pop(L, 1);
                | ldmia DATA!, {r1, r2}
                | lcall extern lua_pushnumber
                break;

            default:
                luaL_error(L, "NYI: callback arg type");
            }
        }
    }

    lua_rawgeti(L, ct_usr, 0);
    mt = (const struct ctype*) lua_touserdata(L, -1);

    | mov r3, #0
    | mov r2, #((mt->pointers || mt->is_reference || mt->type != VOID_TYPE) ? 1 : 0)
    | mov r1, #nargs
    | lcall extern lua_callk

    if (mt->pointers || mt->is_reference) {
        lua_getuservalue(L, -1);
        lua_rawseti(L, -3, ++num_upvals); /* usr value */
        lua_rawseti(L, -2, ++num_upvals); /* mt */

        | mov r2, #num_upvals-1 // usr value
        | mvn r1, #1 // -2 stack is (upval table, ret val)
        | lcall extern lua_rawgeti
        | load32 r3, mt
        | mov r2, #0 // -1 - ct_usr
        | mvn r1, #1 // -2 - val
        | lcall extern to_typed_pointer
        | mov DATA, r0
        | mvn r1, #3 // -4 - remove 3 (upval table, ret val, usr value)
        | lcall extern lua_settop
        | mov r0, DATA
    } else {
        switch (mt->type) {
        case ENUM_TYPE:
            lua_getuservalue(L, -1);
            lua_rawseti(L, -3, ++num_upvals); /* usr value */
            lua_rawseti(L, -2, ++num_upvals); /* mt */

            | mov r2, #num_upvals-1 // usr value
            | mvn r1, #1 // -2 stack is (upval table, ret val)
            | lcall extern lua_rawgeti
            | load32 r3, mt
            | mvn r2, #0 // -1 - ct_usr
            | mvn r1, #1 // -2 - val
            | lcall extern to_enum
            | mov DATA, r0
            | mvn r1, #3 // -4 - remove 3 (upval table, ret val, usr value)
            | lcall extern lua_settop
            | mov r0, DATA
            break;

        case VOID_TYPE:
            | mvn r1, #1 // -2
            | lcall extern lua_settop
            lua_pop(L, 1);
            break;

        case BOOL_TYPE:
        case INT8_TYPE:
        case INT16_TYPE:
        case INT32_TYPE:
            | mvn r1, #0 // -1
            if (mt->is_unsigned) {
                | lcall extern check_uint32
            } else {
                | lcall extern check_int32
            }
            goto single;

        case INT64_TYPE:
            | mvn r1, #0 // -1
            if (mt->is_unsigned) {
                | lcall extern check_uint64
            } else {
                | lcall extern check_int64
            }
            goto dual;

        case INTPTR_TYPE:
            | mvn r1, #0 // -1
            | lcall extern check_intptr
            goto single;

        case FLOAT_TYPE:
            | mvn r1, #0 // -1
            | lcall extern check_float
            goto single;

        case DOUBLE_TYPE:
            | mvn r1, #0 // -1
            | lcall extern check_double
            goto dual;

        single:
            | mov DATA, r0
            | mvn r1, #2 // -3
            | lcall extern lua_settop
            | mov r0, DATA
            lua_pop(L, 1);
            break;

        dual:
            | mov DATA, r0
            | mov DATA2, r1
            | mvn r1, #2 // -3
            | lcall extern lua_settop
            | mov r0, DATA
            | mov r1, DATA2
            lua_pop(L, 1);
            break;

        default:
            luaL_error(L, "NYI: callback return type");
        }
    }

    | ldmia sp, {TOP, L_ARG, DATA, DATA2, sp, pc}

    lua_pop(L, 1); /* upval table - already in registry */
    assert(lua_gettop(L) == top);

    {
        void* p;
        struct ctype ft;
        cfunction func;

        func = compile(Dst, L, NULL, ref);

        ft = *ct;
        ft.is_jitted = 1;
        p = push_cdata(L, ct_usr, &ft);
        *(cfunction*) p = func;

        assert(lua_gettop(L) == top + 1);

        return func;
    }
}

void compile_function(lua_State* L, cfunction func, int ct_usr, const struct ctype* ct)
{
    struct jit* Dst = get_jit(L);;
    int i, nargs, num_upvals;
    const struct ctype* mt;
    void* p;

    int top = lua_gettop(L);

    ct_usr = lua_absindex(L, ct_usr);
    nargs = (int) lua_rawlen(L, ct_usr);

    p = push_cdata(L, ct_usr, ct);
    *(cfunction*) p = func;
    num_upvals = 1;

    dasm_setup(Dst, build_actionlist);

    | mov r12, sp
    | push {r0}
    | push {TOP, L_ARG, DATA, DATA2, r11, r12, lr}
    | sub r11, r12, #4
    | mov L_ARG, r0
    | lcall extern lua_gettop
    | mov TOP, r0
    | cmp TOP, #nargs
    | // these should really be in globals - but for some reason dynasm breaks when you do that
    if (ct->has_var_arg) {
        | bge >1
        | load32 r1, "too few arguments"
        | lcall extern luaL_error
        |1:
    } else {
        | beq >1
        | load32 r1, "incorrect number of arguments"
        | lcall extern luaL_error
        |1:
    }

    /* reserve enough stack space for all of the arguments (8 bytes per
     * argument for double and maintains alignment). Add an extra 16 bytes so
     * that the pop {r0, r1, r2, r3} doesn't clean out our stack frame */
    | sub sp, sp, TOP, lsl #3
    | sub sp, sp, #16
    | mov DATA, sp

    for (i = 1; i <= nargs; i++) {
        lua_rawgeti(L, ct_usr, i);
        mt = (const struct ctype*) lua_touserdata(L, -1);

        if (mt->pointers || mt->is_reference || mt->type == FUNCTION_PTR_TYPE || mt->type == ENUM_TYPE) {
            lua_getuservalue(L, -1);
            num_upvals += 2;

            | ldr r3, [pc, #4]
            | ldr r2, [pc, #4]
            | b >1
            |.long mt, lua_upvalueindex(num_upvals)
            |1:
            | mov r1, #i
            | mov r0, L_ARG

            if (mt->pointers || mt->is_reference) {
                | bl extern to_typed_pointer
            } else if (mt->type == FUNCTION_PTR_TYPE) {
                | bl extern to_typed_function
            } else if (mt->type == ENUM_TYPE) {
                | bl extern to_enum
            }

            | str r0, [DATA], #4

        } else {
            lua_pop(L, 1);
            | mov r1, #i

            switch (mt->type) {
            case INT8_TYPE:
                | lcall extern check_int32
                if (mt->is_unsigned) {
                    | and r0, r0, #0xFF
                } else {
                    | mov r0, r0, lsl #24
                    | mov r0, r0, asr #24                
                }
                | str r0, [DATA], #4
                break;

            case INT16_TYPE:
                | lcall extern check_int32
                if (mt->is_unsigned) {
                    | mov r0, r0, lsl #16
                    | mov r0, r0, lsr #16
                } else {
                    | mov r0, r0, lsl #16
                    | mov r0, r0, asr #16                
                }
                | str r0, [DATA], #4
                break;

            case INT32_TYPE:
                if (mt->is_unsigned) {
                    | lcall extern check_uint32
                } else {
                    | lcall extern check_int32
                }
                | str r0, [DATA], #4
                break;

            case INT64_TYPE:
                if (mt->is_unsigned) {
                    | lcall extern check_uint64
                } else {
                    | lcall extern check_int64
                }
                | str r0, [DATA], #4
                | str r1, [DATA], #4
                break;

            case DOUBLE_TYPE:
                | lcall extern check_double
                | str r0, [DATA], #4
                | str r1, [DATA], #4
                break;

            case INTPTR_TYPE:
                | lcall extern check_intptr
                | str r0, [DATA], #4
                break;

            case FLOAT_TYPE:
                | lcall extern check_float
                | str r0, [DATA], #4
                break;

            default:
                luaL_error(L, "NYI: call arg type");
            }
        }
    }

    if (ct->has_var_arg) {
        | mov r3, DATA
        | mov r2, TOP
        | mov r1, #nargs+1
        | lcall extern unpack_varargs_stack
    }

    | load32 r0, &Dst->last_errno
    | ldr r0, [r0]
    | bl extern SetLastError

    | pop {r0, r1, r2, r3} // this pop is balanced with the sub sp, #16
    | bl extern FUNCTION

    |.macro get_errno
    | bl extern GetLastError
    | load32 r1, &Dst->last_errno
    | str r0, [r1]
    |.endmacro

    |.macro return
    | ldmdb r11, {TOP, L_ARG, DATA, r11, sp, pc}
    |.endmacro

    lua_rawgeti(L, ct_usr, 0);
    mt = (const struct ctype*) lua_touserdata(L, -1);

    if (mt->pointers || mt->is_reference) {
        lua_getuservalue(L, -1);
        num_upvals += 2;
        | mov DATA, r0
        | get_errno
        | ldr r2, [pc, #4]
        | ldr r1, [pc, #4]
        | b >1
        |.long mt, lua_upvalueindex(num_upvals)
        |1:
        | lcall extern push_cdata
        | str DATA, [r0]
        | mov r0, #1
        | return

    } else {
        switch (mt->type) {
        case INT64_TYPE:
            num_upvals++;
            | mov DATA, r0
            | mov DATA2, r1
            | get_errno
            | lcall extern lua_pushnil
            | load32 r2, mt
            | mvn r1, #0 // -1
            | lcall extern push_cdata
            | str DATA, [r0]
            | str DATA2, [r0, #4]
            | mov r0, #1
            | return
            break;

        case INTPTR_TYPE:
            num_upvals++;
            | mov DATA, r0
            | get_errno
            | lcall extern lua_pushnil
            | load32 r2, mt
            | mvn r1, #0 // -1
            | lcall extern push_cdata
            | str DATA, [r0]
            | mov r0, #1
            | return
            break;

        case VOID_TYPE:
            lua_pop(L, 1);
            | get_errno
            | mov r0, #0
            | return
            break;

        case BOOL_TYPE:
            lua_pop(L, 1);
            | mov DATA, r0
            | get_errno
            | mov r1, DATA
            | lcall extern lua_pushboolean
            | mov r0, #1
            | return
            break;

        case INT8_TYPE:
        case INT16_TYPE:
        case INT32_TYPE:
        case ENUM_TYPE:
            lua_pop(L, 1);
            | mov DATA, r0
            | get_errno
            | mov r1, DATA
            if (mt->is_unsigned) {
                | lcall extern push_uint
            } else {
                | lcall extern push_int
            }
            | mov r0, #1
            | return
            break;

        case FLOAT_TYPE:
            lua_pop(L, 1);
            | mov DATA, r0
            | get_errno
            | mov r1, DATA
            | lcall extern push_float
            | mov r0, #1
            | return
            break;

        case DOUBLE_TYPE:
            lua_pop(L, 1);
            | mov DATA, r0
            | mov DATA2, r1
            | get_errno
            | mov r2, DATA2
            | mov r1, DATA
            | lcall extern lua_pushnumber
            | mov r0, #1
            | return
            break;

        default:
            luaL_error(L, "NYI: call return type");
        }
    }

    assert(lua_gettop(L) == top + num_upvals);
    lua_pushcclosure(L, (lua_CFunction) compile(Dst, L, func, LUA_NOREF), num_upvals);
}

