/*
 * Copyright (C) 2018-2020 Western Digital Corporation or its affiliates
 * Copyright (C) 2017-2018 Wearable, Inc.
 * Copyright (C) 2000-2012 leJOS Contributors
 * Copyright (C) 2000 Jose H. Solorzano and TinyVM Contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is “Incompatible With Secondary Licenses”,
 * as defined by the Mozilla Public License, v. 2.0.
 */

/**
 * Runtime data structures for loaded program.
 */

#include "types.h"
#include "trace.h"
#include "constants.h"
#include "specialsignatures.h"
#include "specialclasses.h"
#include "memory.h"
#include "threads.h"
#include "classes.h"
#include "language.h"
#include "configure.h"
#include "interpreter.h"
#include "exceptions.h"
#include "stack.h"
#include "platform_hooks.h"
#include "opcodes.h"

/*
 *
 *The following arrays all contain short sequences of Java byte code.
 * These are used to allow the VM to call out to the user program.
 * These sequences are only used when the stack is empty,
 * They basically consist of a call to the java method, followed by
 * a call to a native method that will end the thread. So if the
 * java method returns the thread will end.
 *
 * We do things this way to allow the call to the java method to be
 * restarted. This may be required if we need to call the static
 * initializer of the containing class, or if we need to retry the
 * operation to claim a mointor, or because of memory allocation
 * failure.
 */

#define PROGRAM0_CLASS_NO NUM_SPECIAL_CLASSES

#define CALL_RUN_OFFSET 0
#define CALL_DEH_OFFSET 8
#define CALL_PROGRAM_OFFSET 16

static const byte specialBytecode[SPECIAL_BYTECODE_SIZE] = {
    // callRun
    OP_INVOKEVIRTUAL, 0, run_4_5V,
    OP_INVOKEJAVA, JAVA_LANG_THREAD, exitThread_4_5V,
    OP_NOP, OP_NOP,
    // callDEH
    OP_INVOKEJAVA, JAVA_LANG_THREAD, systemUncaughtExceptionHandler_4Ljava_3lang_3Throwable_2II_5V,
    OP_INVOKEJAVA, JAVA_LANG_THREAD, exitThread_4_5V,
    OP_NOP, OP_NOP,
    // callProgram
    OP_INVOKEJAVA, PROGRAM0_CLASS_NO, main_4_1Ljava_3lang_3String_2_5V,
    OP_INVOKEJAVA, ORG_NANITEPROJECT_SHUTDOWN, shutdownWait_4_5V,
    OP_INVOKEJAVA, JAVA_LANG_THREAD, exitThread_4_5V,
    OP_NOP, OP_NOP, OP_NOP
};

#if RELATIVE_REFERENCES
#define SPECIAL_BYTECODE_AT(pState, addr) (&((pState)->specialBytecode[(addr)]))
#else
#define SPECIAL_BYTECODE_AT(pState, addr) (&specialBytecode[(addr)])
#endif

// Methods:

void install_binary(nvm_state_t* pState, const byte* ptr)
{
    pState->installed_binary = (byte *)ptr;
    
    pState->constant_table_base = __get_constant_base(pState);
    pState->static_fields_base = __get_static_fields_base(pState);
    pState->class_base = __get_class_base(pState);
    pState->constant_values_base = __get_constant_values_base(pState);
    pState->vm_options = get_master_record(pState)->runtimeOptions;

#if RELATIVE_REFERENCES
    memcpy(pState->specialBytecode, specialBytecode, SPECIAL_BYTECODE_SIZE);
#endif
}

/**
 * @return Method record or null.
 */
MethodRecord *find_method (nvm_state_t* pState, ClassRecord *classRecord, int methodSignature)
{
    MethodRecord* mr0 = get_method_table(pState, classRecord);
    MethodRecord* mr = mr0 + get_method_cnt(classRecord);
    while(-- mr >= mr0) {
        if(mr->signatureId == methodSignature) {
            return mr;
        }
    }
    return NULL;
}

/**
 * Exceute the static initializer if required. Note that the ret address used
 * here is set such that the current instruction will be re-started when the
 * initialization completes.
 * @return An indication of how the VM should proceed
 */
int dispatch_static_initializer (nvm_state_t* pState, ClassRecord *aRec, const byte *retAddr)
{
    int state = get_init_state(pState, aRec);
    ClassRecord *init = aRec;
    ClassRecord *super = get_class_record(pState, init->parentClass);
    MethodRecord *method;
    // Are we needed?
    if (state & C_INITIALIZED) {
        return EXEC_CONTINUE;
    }
    // We need to initialize all of the super classes first. So we find the
    // highest one that has not been initialized and deal with that. This code
    // will then be called again and we will init the next highest and so on
    // until all of the classes in the chain are done.
    for(;;) {
        // find first super class that has not been initialized
        while(init != super && (get_init_state(pState, super) & C_INITIALIZED) == 0) {
            init = super;
            super = get_class_record(pState, init->parentClass);
        }
        // Do we have an initilizer if so we have found our class
        if(has_clinit (init)) {
            break;
        }
        // no initializer so mark as now initialized
        set_init_state(pState, init, C_INITIALIZED);
        // If we are at the start of the list we are done
        if (init == aRec) {
            return EXEC_CONTINUE;
        }
        // Otherwise go do it all again
        init = aRec;
    }
    state = get_init_state(pState, init);
    // are we already initializing ?
    if (state & C_INITIALIZING) {
        // Is it this thread that is doing the init?
        if (get_sync(pState, init)->threadId == pState->current_thread->threadId) {
            return EXEC_CONTINUE;
        }
        // No so we must retry the current instruction
        pState->program_counter = retAddr;
        sleep_thread(pState, 1, 0);
        schedule_request(pState, REQUEST_SWITCH_THREAD);
        return EXEC_RETRY;
    }
#if NVM_DEBUG_METHODS
    printf ("dispatch_static_initializer: has clinit: %d, %d\n", (int) aRec, (int) retAddr);
#endif
    // Static initializer is always the first method
    method = get_method_table(pState, init);
    if ((byte *)method == get_binary_base(pState) || method->signatureId != _6clinit_7_4_5V) {
        nvm_throw_new_exception(pState, JAVA_LANG_NOSUCHMETHODERROR);
        return EXEC_EXCEPTION;
    }
    
    // Can we run it?
    if(!dispatch_special(pState, method, retAddr)) {
        return EXEC_RETRY;
    }
    // Mark for next time
    set_init_state(pState, init, C_INITIALIZING);
    // and claim the monitor
    current_stackframe(pState)->monitor = obj2ref(init);
    enter_monitor (pState, pState->current_thread, (Object *)init);
    return EXEC_RUN;
}

void dispatch_virtual (nvm_state_t* pState, Object *ref, int signature, const byte *retAddr)
{
#if NVM_DEBUG_METHODS
    printf("dispatch_virtual %d\n", signature);
#endif
    if (ref == NULL) {
        nvm_throw_new_exception(pState, JAVA_LANG_NULLPOINTEREXCEPTION);
        return;
    }
    ClassRecord *classRecord;
    MethodRecord *methodRecord;
    // When calling methods on arrays, we use the methods for the Object class...
    nvm_class_index_t class_index = nvm_object_class_index(ref);
LABEL_METHODLOOKUP:
    classRecord = get_class_record (pState, class_index);
    methodRecord = find_method (pState, classRecord, signature);
    if (methodRecord == NULL) {
#if SAFE
        if (class_index == JAVA_LANG_OBJECT) {
            nvm_throw_new_exception(pState, JAVA_LANG_NOSUCHMETHODERROR);
            return;
        }
#endif
        class_index = classRecord->parentClass;
        goto LABEL_METHODLOOKUP;
    }
    
    if (dispatch_special (pState, methodRecord, retAddr)) {
        if (is_synchronized(methodRecord)) {
            current_stackframe(pState)->monitor = ptr2ref(ref);
            enter_monitor (pState, pState->current_thread, ref);
        }
    }
}

/**
 * Calls static initializer if necessary before
 * dispatching with dispatch_special().
 * @param retAddr Return bytecode address.
 * @param btAddr Backtrack bytecode address (in case
 *               static initializer is executed).
 */
void dispatch_special_checked (nvm_state_t* pState, nvm_class_index_t class_index, nvm_method_index_t method_index, const byte *retAddr, const byte *btAddr)
{
    ClassRecord *classRecord;
    MethodRecord *methodRecord;
#if NVM_DEBUG_METHODS
    printf("dispatch_special_checked: %u, %u, %p, %p\n", class_index, method_index, (void*)retAddr, (void*)btAddr);
#endif
    
    // If we need to run the initializer then the real method will get called
    // later, when we re-run the current instruction.
    classRecord = get_class_record (pState, class_index);
    if (!is_initialized_idx (pState, class_index)) {
        if (dispatch_static_initializer (pState, classRecord, btAddr) != EXEC_CONTINUE) {
            return;
        }
    }
    methodRecord = get_method_record (pState, classRecord, method_index);
    if(dispatch_special (pState, methodRecord, retAddr)) {
        if (is_synchronized(methodRecord)) {
            if (!is_static(methodRecord)){
                Object *ref = word2obj(pState->program_locals_base[0]);
                current_stackframe(pState)->monitor = ptr2ref(ref);
                enter_monitor (pState, pState->current_thread, ref);
            } else {
                Object *ref = (Object *)classRecord;
                current_stackframe(pState)->monitor = ptr2ref(ref);
                enter_monitor (pState, pState->current_thread, ref);
            }
        }
    }
}

void dispatch_special_checked_indirect(nvm_state_t* pState, nvm_class_index_t index, const byte *retAddr, const byte *btAddr)
{
    IndirectIndexRecord* record = get_indirect_index_record(pState, index);
    dispatch_special_checked(pState, record->class_index, record->method_index, retAddr, btAddr);
}

/**
 * Call a java static method from the VM
 * NOTE: This can only be used to call methods that have a signature
 * value that is less than 255. Normally it will be used to call
 * one of the specialsignature entries (which are all less than 255).
 */
void dispatch_java(nvm_state_t* pState, nvm_class_index_t class_index, byte sig, const byte *retAddr, const byte *btAddr)
{
    ClassRecord *classRecord = get_class_record (pState, class_index);
    MethodRecord *mRec = find_method (pState, classRecord, sig);
    dispatch_special_checked(pState, class_index, (nvm_method_index_t)(mRec - get_method_table(pState, classRecord)), retAddr, btAddr);
}


/**
 * @param methodRecord Calle's method record.
 * @param retAddr What the PC should be upon return.
 * @return true iff the stack frame was pushed.
 */
boolean dispatch_special (nvm_state_t* pState, MethodRecord *methodRecord, const byte *retAddr)
{
    /**
     * Note: This code is a little tricky, particularly when used with
     * a garbage collector. It manipulates the stack frame and in some cases
     * may need to perform memory allocation. In all cases we must take care
     * to ensure that if an allocation can be made then any live objects
     * on the stack must be below the current stack pointer.
     * In addition to the above we take great care so that this function can
     * be restarted (to allow us to wait for available memory). To enable this
     * we avoid making any commitments to changes to global state until both
     * stacks have been commited.
     */
    Object *stackFrameArray;
    StackFrame *stackFrame;
    StackFrame *stackBase;
    int newStackFrameIndex;
    STACKWORD *newStackTop;
#if NVM_DEBUG_BYTECODE
    printf("call method %d ret %s\n", methodRecord - get_method_table(pState, get_class_record(pState, 0)), retAddr);
    printf ("\n------ dispatch special - %d ------------------\n\n",
            methodRecord->signatureId);
#endif
    
#if NVM_DEBUG_METHODS
    printf ("dispatch_special: %d, %d\n", (int) methodRecord, (int) retAddr);
    printf ("-- signature id = %d\n", methodRecord->signatureId);
    printf ("-- code offset  = %d\n", methodRecord->codeOffset);
    printf ("-- flags        = %d\n", methodRecord->mflags);
    printf ("-- num params   = %d\n", methodRecord->numParameters);
#if 0
    printf ("-- stack ptr    = %d\n", (int) get_stack_ptr());
    printf ("-- max stack ptr= %d\n", (int) (pState->current_thread->stackArray + (get_array_size(pState->current_thread->stackArray))*2));
#endif
#endif
    
    
    // First deal with the easy case of a native call...
    if (is_native (methodRecord)) {
#if NVM_DEBUG_METHODS
        printf ("-- native\n");
#endif
        // WARNING: Once the instruction below has been executed we may have
        // references on the stack that are above the stack pointer. If a GC
        // gets run when in this state the reference may get collected as
        // grabage. This means that any native functions that take a reference
        // parameter and that may end up allocating memory *MUST* protect that
        // reference before calling the allocator...
        pop_words_cur (pState, methodRecord->numParameters);
        switch(dispatch_native (pState, methodRecord->signatureId, get_stack_ptr_cur(pState) + 1)) {
            case EXEC_RETRY:
                // Need to re-start the instruction, so reset the state of the stack
                pState->program_stack_top += methodRecord->numParameters;
                break;
            case EXEC_CONTINUE:
                // Normal completion return to the requested point.
                pState->program_counter = retAddr;
                break;
            case EXEC_RUN:
                // We are running new code, program_counter will be set. Nothing to do.
                break;
            case EXEC_EXCEPTION:
                // An exception has been thrown. The PC will be set correctly and
                // the stack may have been adjusted...
                break;
        }
        // Stack frame not pushed
        return false;
    }
    // Now start to build the new stack frames. We start by placing the
    // the new stack pointer below any params. The params will become locals
    // in the new frame.
    newStackTop = get_stack_ptr_cur(pState) - methodRecord->numParameters;
    
    newStackFrameIndex = (int)(byte)pState->current_thread->stackFrameIndex;
    if (newStackFrameIndex >=  255) {
        nvm_throw_new_exception(pState, JAVA_LANG_STACKOVERFLOWERROR);
        return false;
    }
#if NVM_DEBUG_METHODS
#if 0
    for (int debug_ctr = 0; debug_ctr < methodRecord->numParameters; debug_ctr++) {
        printf ("-- param[%d]    = %ld\n", debug_ctr, (long) get_stack_ptr_cur()[debug_ctr+1]);
    }
#endif
#endif
    stackFrameArray = ref2obj(pState->current_thread->stackFrameArray);
    stackBase = (StackFrame*)(void*)array_start(stackFrameArray);
    // Setup OLD stackframe ready for return
    stackFrame = stackBase + (newStackFrameIndex);
    stackFrame->stackTop = ptr2ref(newStackTop);
    stackFrame->pc = ptr2ref(retAddr);
    // Push NEW stack frame
    // Increment size of stack frame array but do not commit to it until we have
    // completely built both new stacks.
    newStackFrameIndex++;
    stackFrame++;
    if (((byte *)stackFrame - (byte *)stackBase) >= nvm_array_size(stackFrameArray)) {
#if FIXED_STACK_SIZE
        nvm_throw_new_exception (pState, JAVA_LANG_STACKOVERFLOWERROR);
        return false;
#else
        if (expand_call_stack(pState, pState->current_thread) < 0) {
            return false;
        }
        stackFrame = (StackFrame*)(void*)array_start(ref2obj(pState->current_thread->stackFrameArray)) + newStackFrameIndex;
#endif
    }
    
    // Initialize rest of new stack frame
    stackFrame->methodRecord = ptr2ref(methodRecord);
    stackFrame->monitor = ptr2ref(NULL);
    stackFrame->localsBase = ptr2ref(newStackTop + 1);
    // Allocate space for locals etc.
    newStackTop = init_sp(pState, stackFrame, methodRecord);
    stackFrame->stackTop = ptr2ref(newStackTop);
    pState->current_thread->stackFrameIndex = (byte)newStackFrameIndex;
    
    // Check for stack overflow
    if (is_stack_overflow (pState, newStackTop, methodRecord)) {
#if FIXED_STACK_SIZE
        nvm_throw_new_exception (pState, JAVA_LANG_STACKOVERFLOWERROR);
        return false;
#else
        
        if (expand_value_stack(pState, pState->current_thread, methodRecord->maxOperands+methodRecord->numLocals) < 0) {
            pState->current_thread->stackFrameIndex--;
            return false;
        }
        // NOTE at this point newStackTop is no longer valid!
        newStackTop = (STACKWORD*) ref2ptr(stackFrame->stackTop);
#endif
    }
    // All set. So now we can finally commit to the new stack frames
    update_constant_registers (pState, stackFrame);
    pState->program_stack_top = newStackTop;
    // and jump to the start of the new code
    pState->program_counter = get_code_ptr(pState, methodRecord);
    return true;
}

/**
 * Perform a return from a method call. Clean up the stack, setup
 * the return of any results, release any monitor and finally set the
 * PC for the return address.
 */
void do_return (nvm_state_t* pState, int numWords)
{
    StackFrame *stackFrame;
    STACKWORD *fromStackPtr;
    
    stackFrame = current_stackframe(pState);
#if VERIFY
    assert (stackFrame != NULL, LANGUAGE3);
#endif
    
    if(!stackFrame) {
        return;
    }
    
#if NVM_DEBUG_BYTECODE
    printf ("\n------ return ----- %d ------------------\n\n",
            stackFrame->methodRecord);
#endif
    
#if NVM_DEBUG_METHODS
    printf ("do_return: method: %d  #  num. words: %d\n",
            stackFrame->methodRecord, numWords);
#endif
    
    if (!ref_null_p(stackFrame->monitor)) {
        // if this was a class init then mark the class as now complete
        if (((MethodRecord*) ref2ptr(stackFrame->methodRecord))->signatureId == _6clinit_7_4_5V) {
            set_init_state(pState, (ClassRecord*)ref2ptr(stackFrame->monitor), C_INITIALIZED);
        }
        exit_monitor (pState, pState->current_thread, ref2obj(stackFrame->monitor));
    }
    
#if NVM_DEBUG_THREADS || NVM_DEBUG_METHODS
    printf ("do_return: stack frame array size: %d\n", pState->current_thread->stackFrameIndex);
#endif
    
    // Place source ptr below data to be copied up the stack
    fromStackPtr = get_stack_ptr_at_cur(pState, numWords);
    // Pop stack frame
    pState->current_thread->stackFrameIndex--;
    stackFrame--;
    // Assign registers
    update_registers (pState, stackFrame);
    
#if NVM_DEBUG_METHODS
    printf ("do_return: stack reset to:\n");
    printf ("-- stack ptr = %d\n", (int) stackFrame);
#endif
    
    while (numWords--) {
        push_word_cur (pState, *(++fromStackPtr));
    }
}

/**
 * Create a compact form of the specified  call stack.
 * One int per frame containing
 * the method number and current offset. If the ignore parameter is non null
 * then frames that have a matching this field will not be included in the
 * trace. This allow the frames for the creation of an exception object to
 * be ignored.
 */
Object* create_stack_trace(nvm_state_t* pState, Thread *thread, Object *ignore)
{
    int frameCnt = thread->stackFrameIndex;
    Object *stackArray;
    JINT *data;
    int i;
    StackFrame *topFrame = ((StackFrame *)(void*)array_start(ref2obj(thread->stackFrameArray))) + frameCnt;
    StackFrame *stackFrame = topFrame;
    MethodRecord *methodBase = get_method_table(pState, get_class_record(pState, 0));
    byte *pcBase = get_binary_base(pState) + 2;
    
    // Ignore frames if required.
    if (ignore) {
        while ((STACKWORD)ignore == *((STACKWORD*) ref2ptr(stackFrame->localsBase))) {
            stackFrame--;
            frameCnt--;
        }
    }
    // Try and allocate the space for the trace.
    stackArray = new_single_array(pState, AI, frameCnt, false);
    if (stackArray == NULL) {
        return NULL;
    }
    if (thread == pState->current_thread) {
        topFrame->pc = ptr2ref(getPc(pState));
    }
    // adjust top most pc to allow for return address hack
    topFrame->pc = stackword2ref(ref2stackword(topFrame->pc) + 2);
    // Fill in the trace.
    data = jint_array(stackArray);
    for(i = 0; i < frameCnt; i++) {
        nvm_method_index_t method_index = (nvm_method_index_t)((MethodRecord*) ref2ptr(stackFrame->methodRecord) - methodBase);
        nvm_binary_size_t code_offset  = (nvm_binary_size_t)((byte*) ref2ptr(stackFrame->pc) - pcBase);
        code_offset -= ((MethodRecord*) ref2ptr(stackFrame->methodRecord))->codeOffset;
        data[i] = (int)method_index << 16 | (int)code_offset;
        stackFrame--;
    }
    // restore correct pc
    topFrame->pc = stackword2ref(ref2stackword(topFrame->pc) - 2);
    return stackArray;
}

/**
 * Empty the call and value stacks, ready to call java from the firmware
 */
void empty_stacks(nvm_state_t* pState)
{
    // smash the stacks back to the initial state
    pState->current_thread->stackFrameIndex = 0;
    init_sp_pv(pState);
    update_stack_frame(pState, current_stackframe(pState));
}

/**
 * Exceute a "program" on the current thread.
 * NOTE: This call, resets both stacks and so the called method
 * will never return. The thread will exit.
 * @return An indication of how the VM should proceed.
 */
int execute_program(nvm_state_t* pState)
{
    // smash the stacks back to the initial state
    empty_stacks(pState);
    // Push the param (we save a word by setting the top param)
    set_top_ref_cur(pState, ptr2ref(NULL));
    pState->program_counter = SPECIAL_BYTECODE_AT(pState, CALL_PROGRAM_OFFSET);
    return EXEC_RUN;
}

/**
 * Set things up so that we call out to the exception handler in Java.
 * When this function is called we will empty the stack, so the thread
 * will exit if the method ever returns.
 */
int call_exception_handler(nvm_state_t* pState, Throwable *exception, STACKWORD method, STACKWORD pc)
{
    empty_stacks(pState);
    // Push the params
    set_top_ref_cur(pState, obj2ref(exception));
    push_word_cur(pState, method);
    push_word_cur(pState, pc);
    pState->program_counter = SPECIAL_BYTECODE_AT(pState, CALL_DEH_OFFSET);
    return EXEC_RUN;
}

/**
 * Call the run method of the specifed thread. The thread will
 * exit if the run method ever returns.
 */
void call_run(nvm_state_t* pState, Thread *thread)
{
    empty_stacks(pState);
    set_top_ref_cur(pState, obj2ref(thread));
    pState->program_counter = SPECIAL_BYTECODE_AT(pState, CALL_RUN_OFFSET);
}


/**
 * Check to see if obj is a sub type of the type described by
 * cls.
 */
static boolean sub_type_of(nvm_state_t* pState, nvm_class_index_t obj, const nvm_class_index_t cls)
{
    if (cls == JAVA_LANG_OBJECT) {
        return true;
    }
    while (obj != cls) {
        obj = get_class_record(pState, obj)->parentClass;
        if (obj == JAVA_LANG_OBJECT) {
            return false;
        }
    }
    return true;
}


/**
 * Check to see if obj is an instance of the type described by sig.
 * @return true or false
 */
boolean instance_of (nvm_state_t* pState, Object *obj, const nvm_class_index_t class_index)
{
    nvm_class_index_t rtCls;
    
    if (obj == NULL) {
        return false;
    }
    
    // Check for common cases
    if (class_index == JAVA_LANG_OBJECT) {
        return true;
    }
    rtCls = nvm_object_class_index(obj);
    if (rtCls == class_index) {
        return true;
    }
    return is_assignable(pState, rtCls, class_index);
}

/**
 * Check to see if it is allowed to assign the an object of type srcCls
 * to an object of type dstCls.
 */
boolean is_assignable(nvm_state_t* pState, const nvm_class_index_t srcCls, const nvm_class_index_t dstCls)
{
    ClassRecord *dstRec;
    // Check common cases
    if (srcCls == dstCls || dstCls == JAVA_LANG_OBJECT) {
        return true;
    }
    dstRec = get_class_record(pState, dstCls);
    if (is_interface(dstRec)) {
        // we are testing against an interface. So we use the associated interface
        // map to test if the src implements it...
        int base = get_interface_map_base(dstRec);
        // Special case all arrays implement cloneable
        if (dstCls == JAVA_LANG_CLONEABLE && is_array_class(get_class_record(pState, srcCls))) {
            return true;
        }
        if (srcCls < base) {
            return false;
        }
        if (srcCls - base >= get_interface_map_len(dstRec)) {
            return false;
        }
        base = srcCls - base;
        return ((get_interface_map(pState, dstRec)[base/8]) & (1 << (base%8))) != 0;
    } else if (is_array_class(dstRec)) {
        ClassRecord* srcRec = get_class_record(pState, srcCls);
        if (!is_array_class(srcRec)) {
            return false;
        }

        return is_assignable(pState, get_element_class(srcRec), get_element_class(dstRec));
    }
    return sub_type_of(pState, srcCls, dstCls);
}


