
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


typedef struct {
    njs_vmcode_t               operation;
    size_t                     size;
    njs_str_t                  name;
} njs_code_name_t;


static njs_code_name_t  code_names[] = {

    { NJS_VMCODE_PUT_ARG, sizeof(njs_vmcode_1addr_t),
          njs_str("PUT ARG         ") },
    { NJS_VMCODE_OBJECT, sizeof(njs_vmcode_object_t),
          njs_str("OBJECT          ") },
    { NJS_VMCODE_FUNCTION, sizeof(njs_vmcode_function_t),
          njs_str("FUNCTION        ") },
    { NJS_VMCODE_ARGUMENTS, sizeof(njs_vmcode_arguments_t),
          njs_str("ARGUMENTS       ") },
    { NJS_VMCODE_REGEXP, sizeof(njs_vmcode_regexp_t),
          njs_str("REGEXP          ") },
    { NJS_VMCODE_TEMPLATE_LITERAL, sizeof(njs_vmcode_template_literal_t),
          njs_str("TEMPLATE LITERAL") },

    { NJS_VMCODE_PROPERTY_GET, sizeof(njs_vmcode_prop_get_t),
          njs_str("PROP GET        ") },
    { NJS_VMCODE_PROPERTY_ATOM_GET, sizeof(njs_vmcode_prop_get_t),
          njs_str("PROP ATOM GET   ") },
    { NJS_VMCODE_GLOBAL_GET, sizeof(njs_vmcode_prop_get_t),
          njs_str("GLOBAL GET      ") },
    { NJS_VMCODE_PROPERTY_INIT, sizeof(njs_vmcode_prop_set_t),
          njs_str("PROP INIT       ") },
    { NJS_VMCODE_PROTO_INIT, sizeof(njs_vmcode_prop_set_t),
          njs_str("PROTO INIT      ") },
    { NJS_VMCODE_PROPERTY_SET, sizeof(njs_vmcode_prop_set_t),
          njs_str("PROP SET        ") },
    { NJS_VMCODE_PROPERTY_ATOM_SET, sizeof(njs_vmcode_prop_set_t),
          njs_str("PROP ATOM SET   ") },
    { NJS_VMCODE_PROPERTY_IN, sizeof(njs_vmcode_3addr_t),
          njs_str("PROP IN         ") },
    { NJS_VMCODE_PROPERTY_DELETE, sizeof(njs_vmcode_3addr_t),
          njs_str("PROP DELETE     ") },
    { NJS_VMCODE_INSTANCE_OF, sizeof(njs_vmcode_instance_of_t),
          njs_str("INSTANCE OF     ") },

    { NJS_VMCODE_FUNCTION_CALL, sizeof(njs_vmcode_function_call_t),
          njs_str("FUNCTION CALL   ") },
    { NJS_VMCODE_METHOD_FRAME, sizeof(njs_vmcode_method_frame_t),
          njs_str("METHOD FRAME    ") },
    { NJS_VMCODE_RETURN, sizeof(njs_vmcode_return_t),
          njs_str("RETURN          ") },
    { NJS_VMCODE_STOP, sizeof(njs_vmcode_stop_t),
          njs_str("STOP            ") },

    { NJS_VMCODE_INCREMENT, sizeof(njs_vmcode_3addr_t),
          njs_str("INC             ") },
    { NJS_VMCODE_DECREMENT, sizeof(njs_vmcode_3addr_t),
          njs_str("DEC             ") },
    { NJS_VMCODE_POST_INCREMENT, sizeof(njs_vmcode_3addr_t),
          njs_str("POST INC        ") },
    { NJS_VMCODE_POST_DECREMENT, sizeof(njs_vmcode_3addr_t),
          njs_str("POST DEC        ") },

    { NJS_VMCODE_DELETE, sizeof(njs_vmcode_2addr_t),
          njs_str("DELETE          ") },
    { NJS_VMCODE_VOID, sizeof(njs_vmcode_2addr_t),
          njs_str("VOID            ") },
    { NJS_VMCODE_TYPEOF, sizeof(njs_vmcode_2addr_t),
          njs_str("TYPEOF          ") },
    { NJS_VMCODE_TO_PROPERTY_KEY, sizeof(njs_vmcode_2addr_t),
          njs_str("TO PROP KEY     ") },
    { NJS_VMCODE_TO_PROPERTY_KEY_CHK, sizeof(njs_vmcode_3addr_t),
          njs_str("TO PROP KEY CHK ") },
    { NJS_VMCODE_SET_FUNCTION_NAME, sizeof(njs_vmcode_2addr_t),
          njs_str("SET FUNC NAME   ") },

    { NJS_VMCODE_UNARY_PLUS, sizeof(njs_vmcode_2addr_t),
          njs_str("PLUS            ") },
    { NJS_VMCODE_UNARY_NEGATION, sizeof(njs_vmcode_2addr_t),
          njs_str("NEGATION        ") },

    { NJS_VMCODE_ADDITION, sizeof(njs_vmcode_3addr_t),
          njs_str("ADD             ") },
    { NJS_VMCODE_SUBTRACTION, sizeof(njs_vmcode_3addr_t),
          njs_str("SUBTRACT        ") },
    { NJS_VMCODE_MULTIPLICATION, sizeof(njs_vmcode_3addr_t),
          njs_str("MULTIPLY        ") },
    { NJS_VMCODE_EXPONENTIATION, sizeof(njs_vmcode_3addr_t),
          njs_str("POWER           ") },
    { NJS_VMCODE_DIVISION, sizeof(njs_vmcode_3addr_t),
          njs_str("DIVIDE          ") },
    { NJS_VMCODE_REMAINDER, sizeof(njs_vmcode_3addr_t),
          njs_str("REMAINDER       ") },

    { NJS_VMCODE_LEFT_SHIFT, sizeof(njs_vmcode_3addr_t),
          njs_str("LEFT SHIFT      ") },
    { NJS_VMCODE_RIGHT_SHIFT, sizeof(njs_vmcode_3addr_t),
          njs_str("RIGHT SHIFT     ") },
    { NJS_VMCODE_UNSIGNED_RIGHT_SHIFT, sizeof(njs_vmcode_3addr_t),
          njs_str("USGN RIGHT SHIFT") },

    { NJS_VMCODE_LOGICAL_NOT, sizeof(njs_vmcode_2addr_t),
          njs_str("LOGICAL NOT     ") },

    { NJS_VMCODE_BITWISE_NOT, sizeof(njs_vmcode_2addr_t),
          njs_str("BINARY NOT      ") },
    { NJS_VMCODE_BITWISE_AND, sizeof(njs_vmcode_3addr_t),
          njs_str("BINARY AND      ") },
    { NJS_VMCODE_BITWISE_XOR, sizeof(njs_vmcode_3addr_t),
          njs_str("BINARY XOR      ") },
    { NJS_VMCODE_BITWISE_OR, sizeof(njs_vmcode_3addr_t),
          njs_str("BINARY OR       ") },

    { NJS_VMCODE_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("EQUAL           ") },
    { NJS_VMCODE_NOT_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("NOT EQUAL       ") },
    { NJS_VMCODE_LESS, sizeof(njs_vmcode_3addr_t),
          njs_str("LESS            ") },
    { NJS_VMCODE_LESS_OR_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("LESS OR EQUAL   ") },
    { NJS_VMCODE_GREATER, sizeof(njs_vmcode_3addr_t),
          njs_str("GREATER         ") },
    { NJS_VMCODE_GREATER_OR_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("GREATER OR EQUAL") },

    { NJS_VMCODE_STRICT_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("STRICT EQUAL    ") },
    { NJS_VMCODE_STRICT_NOT_EQUAL, sizeof(njs_vmcode_3addr_t),
          njs_str("STRICT NOT EQUAL") },

    { NJS_VMCODE_MOVE, sizeof(njs_vmcode_move_t),
          njs_str("MOVE            ") },

    { NJS_VMCODE_THROW, sizeof(njs_vmcode_throw_t),
          njs_str("THROW           ") },

    { NJS_VMCODE_LET, sizeof(njs_vmcode_variable_t),
          njs_str("LET             ") },

    { NJS_VMCODE_LET_UPDATE, sizeof(njs_vmcode_variable_t),
          njs_str("LET UPDATE      ") },

    { NJS_VMCODE_INITIALIZATION_TEST, sizeof(njs_vmcode_variable_t),
          njs_str("INIT TEST       ") },

    { NJS_VMCODE_NOT_INITIALIZED, sizeof(njs_vmcode_variable_t),
          njs_str("NOT INIT        ") },

    { NJS_VMCODE_ASSIGNMENT_ERROR, sizeof(njs_vmcode_variable_t),
          njs_str("ASSIGNMENT ERROR") },

    { NJS_VMCODE_DEBUGGER, sizeof(njs_vmcode_debugger_t),
          njs_str("DEBUGGER        ") },

    { NJS_VMCODE_AWAIT, sizeof(njs_vmcode_await_t),
          njs_str("AWAIT           ") },
};


void
njs_disassembler(njs_vm_t *vm)
{
    njs_uint_t     n;
    njs_vm_code_t  *code;

    code = vm->codes->start;
    n = vm->codes->items;

    while (n != 0) {
        njs_printf("%V:%V\n", &code->file, &code->name);
        njs_disassemble(code->start, code->end, -1, code->lines);
        code++;
        n--;
    }

    njs_printf("\n");
}


void
njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines)
{
    u_char                       *p;
    uint32_t                     line;
    njs_str_t                    *name;
    njs_uint_t                   n;
    const char                   *type;
    njs_vmcode_t                 operation;
    njs_code_name_t              *code_name;
    njs_vmcode_jump_t            *jump;
    njs_vmcode_error_t           *error;
    njs_vmcode_1addr_t           *code1;
    njs_vmcode_2addr_t           *code2;
    njs_vmcode_3addr_t           *code3;
    njs_vmcode_array_t           *array;
    njs_vmcode_catch_t           *catch;
    njs_vmcode_import_t          *import;
    njs_vmcode_finally_t         *finally;
    njs_vmcode_try_end_t         *try_end;
    njs_vmcode_try_start_t       *try_start;
    njs_vmcode_cond_jump_t       *cond_jump;
    njs_vmcode_test_jump_t       *test_jump;
    njs_vmcode_prop_next_t       *prop_next;
    njs_vmcode_try_return_t      *try_return;
    njs_vmcode_equal_jump_t      *equal;
    njs_vmcode_prop_foreach_t    *prop_foreach;
    njs_vmcode_method_frame_t    *method;
    njs_vmcode_prop_accessor_t   *prop_accessor;
    njs_vmcode_try_trampoline_t  *try_tramp;
    njs_vmcode_function_frame_t  *function;

    /*
     * On some 32-bit platform uintptr_t is int and compilers warn
     * about %l format modifier.  size_t has the size as pointer so
     * there is no run-time overhead.
     */

    p = start;

    while (((p < end) && (count == -1)) || (count-- > 0)) {
        operation = *(njs_vmcode_t *) p;
        line = njs_lookup_line(lines, p - start);

        if (operation == NJS_VMCODE_ARRAY) {
            array = (njs_vmcode_array_t *) p;

            njs_printf("%5uD | %05uz ARRAY             %04Xz %uz%s\n",
                       line, p - start, (size_t) array->retval,
                       (size_t) array->length, array->ctor ? " INIT" : "");

            p += sizeof(njs_vmcode_array_t);

            continue;
        }

        if (operation == NJS_VMCODE_IF_TRUE_JUMP) {
            cond_jump = (njs_vmcode_cond_jump_t *) p;

            njs_printf("%5uD | %05uz JUMP IF TRUE      %04Xz %z\n",
                       line, p - start, (size_t) cond_jump->cond,
                       (size_t) cond_jump->offset);

            p += sizeof(njs_vmcode_cond_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_IF_FALSE_JUMP) {
            cond_jump = (njs_vmcode_cond_jump_t *) p;

            njs_printf("%5uD | %05uz JUMP IF FALSE     %04Xz %z\n",
                       line, p - start, (size_t) cond_jump->cond,
                       (size_t) cond_jump->offset);

            p += sizeof(njs_vmcode_cond_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_JUMP) {
            jump = (njs_vmcode_jump_t *) p;

            njs_printf("%5uD | %05uz JUMP              %z\n",
                       line, p - start, (size_t) jump->offset);

            p += sizeof(njs_vmcode_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_IF_EQUAL_JUMP) {
            equal = (njs_vmcode_equal_jump_t *) p;

            njs_printf("%5uD | %05uz JUMP IF EQUAL     %04Xz %04Xz %z\n",
                       line, p - start, (size_t) equal->value1,
                       (size_t) equal->value2, (size_t) equal->offset);

            p += sizeof(njs_vmcode_equal_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_TEST_IF_TRUE) {
            test_jump = (njs_vmcode_test_jump_t *) p;

            njs_printf("%5uD | %05uz TEST IF TRUE      %04Xz %04Xz %z\n",
                       line, p - start, (size_t) test_jump->retval,
                       (size_t) test_jump->value, (size_t) test_jump->offset);

            p += sizeof(njs_vmcode_test_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_TEST_IF_FALSE) {
            test_jump = (njs_vmcode_test_jump_t *) p;

            njs_printf("%5uD | %05uz TEST IF FALSE     %04Xz %04Xz %z\n",
                       line, p - start, (size_t) test_jump->retval,
                       (size_t) test_jump->value, (size_t) test_jump->offset);

            p += sizeof(njs_vmcode_test_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_COALESCE) {
            test_jump = (njs_vmcode_test_jump_t *) p;

            njs_printf("%5uD | %05uz COALESCE          %04Xz %04Xz %z\n",
                       line, p - start, (size_t) test_jump->retval,
                       (size_t) test_jump->value, (size_t) test_jump->offset);

            p += sizeof(njs_vmcode_test_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_OPTIONAL_CHAIN) {
            test_jump = (njs_vmcode_test_jump_t *) p;

            njs_printf("%5uD | %05uz OPTIONAL CHAIN    "
                       "%04Xz %04Xz %z\n",
                       line, p - start,
                       (size_t) test_jump->retval,
                       (size_t) test_jump->value,
                       (size_t) test_jump->offset);

            p += sizeof(njs_vmcode_test_jump_t);

            continue;
        }

        if (operation == NJS_VMCODE_FUNCTION_FRAME) {
            function = (njs_vmcode_function_frame_t *) p;

            njs_printf("%5uD | %05uz FUNCTION FRAME    %04Xz %uz%s\n",
                       line, p - start, (size_t) function->name,
                       function->nargs, function->ctor ? " CTOR" : "");

            p += sizeof(njs_vmcode_function_frame_t);

            continue;
        }

        if (operation == NJS_VMCODE_METHOD_FRAME) {
            method = (njs_vmcode_method_frame_t *) p;

            njs_printf("%5uD | %05uz METHOD FRAME      %04Xz %04Xz %uz%s\n",
                       line, p - start, (size_t) method->function,
                       (size_t) method->this_object, method->nargs,
                       method->ctor ? " CTOR" : "");

            p += sizeof(njs_vmcode_method_frame_t);
            continue;
        }

        if (operation == NJS_VMCODE_PROPERTY_FOREACH) {
            prop_foreach = (njs_vmcode_prop_foreach_t *) p;

            njs_printf("%5uD | %05uz PROP FOREACH      %04Xz %04Xz %z\n",
                       line, p - start, (size_t) prop_foreach->next,
                       (size_t) prop_foreach->object,
                       (size_t) prop_foreach->offset);

            p += sizeof(njs_vmcode_prop_foreach_t);
            continue;
        }

        if (operation == NJS_VMCODE_PROPERTY_NEXT) {
            prop_next = (njs_vmcode_prop_next_t *) p;

            njs_printf("%5uD | %05uz PROP NEXT         %04Xz %04Xz %04Xz %z\n",
                       line, p - start, (size_t) prop_next->retval,
                       (size_t) prop_next->object, (size_t) prop_next->next,
                       (size_t) prop_next->offset);

            p += sizeof(njs_vmcode_prop_next_t);

            continue;
        }

        if (operation == NJS_VMCODE_PROPERTY_ACCESSOR) {
            prop_accessor = (njs_vmcode_prop_accessor_t *) p;

            njs_printf("%5uD | %05uz PROP %s ACCESSOR %04Xz %04Xz %04Xz\n",
                       line, p - start,
                       (prop_accessor->type == NJS_OBJECT_PROP_GETTER)
                           ? "GET" : "SET",
                       (size_t) prop_accessor->value,
                       (size_t) prop_accessor->object,
                       (size_t) prop_accessor->property);

            p += sizeof(njs_vmcode_prop_accessor_t);

            continue;
        }

        if (operation == NJS_VMCODE_IMPORT) {
            import = (njs_vmcode_import_t *) p;

            njs_printf("%5uD | %05uz IMPORT            %04Xz %V\n",
                       line, p - start, (size_t) import->retval,
                       &import->module->name);

            p += sizeof(njs_vmcode_import_t);

            continue;
        }

        if (operation == NJS_VMCODE_TRY_START) {
            try_start = (njs_vmcode_try_start_t *) p;

            njs_printf("%5uD | %05uz TRY START         %04Xz %04Xz %z\n",
                       line, p - start, (size_t) try_start->exception_value,
                       (size_t) try_start->exit_value,
                       (size_t) try_start->offset);

            p += sizeof(njs_vmcode_try_start_t);

            continue;
        }

        if (operation == NJS_VMCODE_TRY_BREAK) {
            try_tramp = (njs_vmcode_try_trampoline_t *) p;

            njs_printf("%5uD | %05uz TRY BREAK         %04Xz %z\n",
                       line, p - start, (size_t) try_tramp->exit_value,
                       (size_t) try_tramp->offset);

            p += sizeof(njs_vmcode_try_trampoline_t);

            continue;
        }

        if (operation == NJS_VMCODE_TRY_CONTINUE) {
            try_tramp = (njs_vmcode_try_trampoline_t *) p;

            njs_printf("%5uD | %05uz TRY CONTINUE      %04Xz %z\n",
                       line, p - start, (size_t) try_tramp->exit_value,
                       (size_t) try_tramp->offset);

            p += sizeof(njs_vmcode_try_trampoline_t);

            continue;
        }

        if (operation == NJS_VMCODE_TRY_RETURN) {
            try_return = (njs_vmcode_try_return_t *) p;

            njs_printf("%5uD | %05uz TRY RETURN        %04Xz %04Xz %z\n",
                       line, p - start, (size_t) try_return->save,
                       (size_t) try_return->retval,
                       (size_t) try_return->offset);

            p += sizeof(njs_vmcode_try_return_t);

            continue;
        }

        if (operation == NJS_VMCODE_CATCH) {
            catch = (njs_vmcode_catch_t *) p;

            njs_printf("%5uD | %05uz CATCH             %04Xz %z\n",
                       line, p - start, (size_t) catch->exception,
                       (size_t) catch->offset);

            p += sizeof(njs_vmcode_catch_t);

            continue;
        }

        if (operation == NJS_VMCODE_TRY_END) {
            try_end = (njs_vmcode_try_end_t *) p;

            njs_printf("%5uD | %05uz TRY END           %z\n",
                       line, p - start, (size_t) try_end->offset);

            p += sizeof(njs_vmcode_try_end_t);

            continue;
        }

        if (operation == NJS_VMCODE_FINALLY) {
            finally = (njs_vmcode_finally_t *) p;

            njs_printf("%5uD | %05uz TRY FINALLY       %04Xz %04Xz %z %z\n",
                       line, p - start, (size_t) finally->retval,
                       (size_t) finally->exit_value,
                       (size_t) finally->continue_offset,
                       (size_t) finally->break_offset);

            p += sizeof(njs_vmcode_finally_t);

            continue;
        }

        if (operation == NJS_VMCODE_ERROR) {
            error = (njs_vmcode_error_t *) p;

            switch (error->type) {
            case NJS_OBJ_TYPE_REF_ERROR:
                type = "REFERENCE";
                break;

            case NJS_OBJ_TYPE_TYPE_ERROR:
                type = "TYPE";
                break;

            case NJS_OBJ_TYPE_ERROR:
            default:
                type = "";
            }

            njs_printf("%5uD | %05uz %s ERROR\n", line, p - start, type);

            p += sizeof(njs_vmcode_error_t);

            continue;
        }

        code_name = code_names;
        n = njs_nitems(code_names);

        do {
            if (operation == code_name->operation) {
                name = &code_name->name;

                if (code_name->size == sizeof(njs_vmcode_3addr_t)) {
                    code3 = (njs_vmcode_3addr_t *) p;

                    njs_printf("%5uD | %05uz %*s  %04Xz %04Xz %04Xz\n",
                               line, p - start, name->length, name->start,
                               (size_t) code3->dst, (size_t) code3->src1,
                               (size_t) code3->src2);

                } else if (code_name->size == sizeof(njs_vmcode_2addr_t)) {
                    code2 = (njs_vmcode_2addr_t *) p;

                    njs_printf("%5uD | %05uz %*s  %04Xz %04Xz\n",
                               line, p - start, name->length, name->start,
                               (size_t) code2->dst, (size_t) code2->src);

                } else if (code_name->size == sizeof(njs_vmcode_1addr_t)) {
                    code1 = (njs_vmcode_1addr_t *) p;

                    njs_printf("%5uD | %05uz %*s  %04Xz\n",
                               line, p - start, name->length, name->start,
                               (size_t) code1->index);
                }

                p += code_name->size;

                goto next;
            }

            code_name++;
            n--;

        } while (n != 0);

        njs_printf("%5uD | %05uz UNKNOWN           %04Xz\n", line,
                   p - start, (size_t) (uintptr_t) operation);

        p += sizeof(njs_vmcode_t);

    next:

        continue;
    }
}
