Ejemplo n.º 1
0
    /**
     * Compile and finish executing a function.
     * We don't actually replace the interpreter frame.  Instead, this
     * routine JIT-compiles the current method and then calls the result
     * of that.  The JIT-ted version returns to here and the interpreter
     * then immediately exits, returning the result of the JIT-ted version,
     * which is returned here.
     */
    bool OSR::execute(MethodEnv *env, Atom* interp_frame,
                      MethodSignaturep ms, const uint8_t* osr_pc, Atom* result)
    {
        BaseExecMgr* exec = BaseExecMgr::exec(env);
        OSR osr(osr_pc, interp_frame);

#ifdef AVMPLUS_VERBOSE
        if (env->method->pool()->isVerbose(VB_execpolicy)) {
            env->core()->console << "execpolicy jit hot-loop " <<
                    env->method << " osr_pc=" <<
                    int(osr_pc - ms->abc_code_start()) <<
                    "\n";
        }
#endif
        // compile the method with an OSR entry point
        AvmAssert(!env->method->hasFailedJit());
        exec->verifyJit(env->method, ms, env->toplevel(), env->abcEnv(), &osr);
        env->_implGPR = env->method->_implGPR;

        if (env->method->hasFailedJit()) {
            // Clean up OSR object explicitly, as there is no destructor.
            mmfx_delete(osr.jit_frame_state);
            return false;
        }

        // Save current_osr.  It will be set to NULL in adjust_frame(),
        // once we have initialized the JIT frame from it.
        exec->current_osr = &osr;
        int fakeArgc = ms->requiredParamCount();
        *result = exec->endCoerce(env, fakeArgc, (uint32_t*)interp_frame, ms);
        return true;
    }
Ejemplo n.º 2
0
    void MethodInfo::setInterpImpl() 
	{
		MethodSignaturep ms = getMethodSignature();
		if (ms->returnTraitsBT() == BUILTIN_number)
			_implFPR = avmplus::interpFPR;
		else
			_implGPR = avmplus::interpGPR;
		AvmAssert(isInterpreted());
		_invoker = hasTypedArgs(ms) ? MethodEnv::coerceEnter_interp : MethodEnv::coerceEnter_interp_nocoerce;
    }
Ejemplo n.º 3
0
void JitWriter::startBlock(const FrameState* frame) {
  newBlock(frame->abc_pc, frame);
  MethodSignaturep signature = method_->getMethodSignature();
  const Type** types = new (abc_->alloc0()) const Type*[signature->frame_size()];
  FrameRange<const FrameValue> from = range(&frame->value(0), frame, signature);
  FrameRange<const Type*> t = range(types, frame, signature);
  for (; !from.empty(); from.popFront(), t.popFront())
    t.front() = jit_mgr_->lattice()->makeType(from.front());
  current_block_->start_types = types;
}
Ejemplo n.º 4
0
	static bool hasTypedArgs(MethodSignaturep ms)
	{
		int32_t param_count = ms->param_count();
		for (int32_t i = 1; i <= param_count; i++) {
			if (ms->paramTraits(i) != NULL) {
				// at least one parameter is typed; need full coerceEnter
				return true;
			}
		}
		return false;
	}
Ejemplo n.º 5
0
	Atom FunctionObject::get_coerced_receiver(Atom a)
	{
		if (AvmCore::isNullOrUndefined(a))
		{
			// use callee's global object as this.
			// see E3 15.3.4.4
			a = _call->scope()->getScope(0);
		}
		MethodSignaturep ms = _call->method->getMethodSignature();
		return toplevel()->coerce(a, ms->paramTraits(0));
	}
Ejemplo n.º 6
0
 int DebugStackFrame::indexOfFirstLocal()
 {
     // 'trace->argc' is the number of arguments that were actually passed in
     // to this function, but that is not what we want -- we want
     // 'info->param_count', because that is the number of arguments we were
     // *expecting* to get.  There are two reasons we want 'info->param_count':
     //
     // (1) if the caller passed in too many args, we want to ignore the
     //     trailing ones; and
     // (2) if the caller passed in too few args to a function that has some
     //     default parameters, we want to display the args with their default
     //     values.
     if (!trace->info())
         return 0;
     MethodSignaturep ms = trace->info()->getMethodSignature();
     return 1 + ms->param_count();
 }
Ejemplo n.º 7
0
	void CallStackNode::enumerateScopeChainAtoms(IScopeChainEnumerator& scb)
	{
		// First, get the "dynamic" portion of the scope chain, that is, the
		// part that is modified on-the-fly within the function.  This includes
		// activation objects for try/catch blocks and "with" clauses.

		if (m_info)
		{
            MethodSignaturep const ms = m_info->getMethodSignature();
            for (int i = (ms->max_scope() + ms->local_count() - 1), n = ms->local_count(); i >= n; --i)
            {
                Atom const scope = m_info->boxOneLocal(m_framep, i, m_traits);
                AvmAssert(atomKind(scope) != kUnusedAtomTag);
                // go ahead and call addScope, even if null or undefined.
                scb.addScope(scope);
            }
		}

		// Next, get the "static" portion of the scope chain, that is, the
		// part that is defined as part of the definition of the function.  This
		// includes the locals of any functions that enclose this one, and the "this"
		// object, if any.

		ScopeChain* scopeChain = m_env ? m_env->scope() : NULL;
		if (scopeChain) 
		{
			int scopeChainLength = scopeChain->getSize();
			for (int i = scopeChainLength - 1; i >= 0; --i)
			{
				Atom scope = scopeChain->getScope(i);
				if (AvmCore::isObject(scope))
				{
                    scb.addScope(scope);
				}
			}
		}
	}
Ejemplo n.º 8
0
	void MethodInfo::verify(Toplevel *toplevel, AbcEnv* abc_env)
	{
		AvmAssert(declaringTraits()->isResolved());
		resolveSignature(toplevel);
		AvmCore* core = this->pool()->core;
		if (isNative())
		{
			union {
				GprMethodProc implGPR;
				AvmThunkNativeThunker thunker;
				AvmThunkNativeThunkerN thunkerN;
			} u;
	#ifdef DEBUGGER
			if (core->debugger())
			{
				MethodSignaturep ms = getMethodSignature();
				if (ms->returnTraitsBT() == BUILTIN_number)
					u.thunkerN = MethodInfo::debugEnterExitWrapperN;
				else
					u.thunker = MethodInfo::debugEnterExitWrapper32;
			}
			else
	#endif
			{
				u.thunker = this->thunker();
			}
			this->setNativeImpl(u.implGPR);
		}
		else
		{
			#ifdef DEBUGGER
			// just a fake CallStackNode here, so that if we throw a verify error, 
			// we get a stack trace with the method being verified as its top entry.
			CallStackNode callStackNode(this);
			#endif /* DEBUGGER */

			PERFM_NTPROF("verify-ticks");

			CodeWriter* coder = NULL;
			Verifier verifier(this, toplevel, abc_env);

			/*
				These "buf" declarations are an unfortunate but expedient hack:
				the existing CodeWriter classes (eg CodegenLIR, etc) have historically
				always been stack-allocated, thus they have no WB protection on member
				fields. Recent changes were made to allow for explicit cleanup() of them
				in the event of exception, but there was a latent bug waiting to happen:
				the actual automatic var was going out of scope, but still being referenced
				(via the 'coder' pointer) in the exception handler, but the area being 
				pointed to may or may not still be valid. The ideal fix for this would 
				simply be to heap-allocate the CodeWriters, but that would require going
				thru the existing code carefully and inserting WB where appropriate.
				Instead, this "expedient" hack uses placement new to ensure the memory
				stays valid into the exception handler. 
				
				Note: the lack of a call to the dtor of the CodeWriter(s) is not an oversight.
				Calling cleanup() on coder is equivalent to running the dtor for all
				of the CodeWriters here.
				
				Note: allocated using arrays of intptr_t (rather than char) to ensure alignment is acceptable.
			*/
		#define MAKE_BUF(name, type) \
			intptr_t name[(sizeof(type)+sizeof(intptr_t)-1)/sizeof(intptr_t)]

		#if defined FEATURE_NANOJIT
			MAKE_BUF(jit_buf, CodegenLIR);
			#if defined AVMPLUS_WORD_CODE
			MAKE_BUF(teeWriter_buf, TeeWriter);
			#endif
			#ifdef FEATURE_CFGWRITER
			MAKE_BUF(cfg_buf, CFGWriter);
			#endif
		#endif
			#if defined AVMPLUS_WORD_CODE
			MAKE_BUF(translator_buf, WordcodeEmitter);
			#else
			MAKE_BUF(stubWriter_buf, CodeWriter);
			#endif

			TRY(core, kCatchAction_Rethrow)
			{
#if defined FEATURE_NANOJIT
				if ((core->IsJITEnabled()) && !suggestInterp())
				{
					PERFM_NTPROF("verify & IR gen");
					
					// note placement-new usage!
					CodegenLIR* jit = new(jit_buf) CodegenLIR(this);
					#if defined AVMPLUS_WORD_CODE
					WordcodeEmitter* translator = new(translator_buf) WordcodeEmitter(this, toplevel);
					TeeWriter* teeWriter = new(teeWriter_buf) TeeWriter(translator, jit);
					coder = teeWriter;
					#else
					coder = jit;
					#endif

				#ifdef FEATURE_CFGWRITER
					// analyze code and generate LIR
					CFGWriter* cfg = new(cfg_buf) CFGWriter(this, coder); 
					coder = cfg;
				#endif

				    verifier.verify(coder);
					PERFM_TPROF_END();
			
					if (!jit->overflow) {
						// assembler LIR into native code
						jit->emitMD();
					}

					// the MD buffer can overflow so we need to re-iterate
					// over the whole thing, since we aren't yet robust enough
					// to just rebuild the MD code.

					// mark it as interpreted and try to limp along
					if (jit->overflow) {
						if (core->JITMustSucceed()) {
							Exception* e = new (core->GetGC()) Exception(core, core->newStringLatin1("JIT failed")->atom());
							e->flags |= Exception::EXIT_EXCEPTION;
							core->throwException(e);
						}
						setInterpImpl();
					}
	                #ifdef AVMPLUS_WORD_CODE
					else {
						if (_abc.word_code.translated_code) 
						{
							set_word_code(core->GetGC(), NULL);
						}
						if (_abc.word_code.exceptions) 
						{
							_abc.word_code.exceptions = NULL;
						}
					}
                    #endif
				}
				else
				{
					// NOTE copied below
					#if defined AVMPLUS_WORD_CODE
					WordcodeEmitter* translator = new(translator_buf) WordcodeEmitter(this, toplevel);
					coder = translator;
					#else
					CodeWriter* stubWriter = new(stubWriter_buf) CodeWriter();
					coder = stubWriter;
					#endif
					verifier.verify(coder); // pass2 dataflow
					setInterpImpl();
					// NOTE end copy
				}
#else // FEATURE_NANOJIT

				// NOTE copied from above
				#if defined AVMPLUS_WORD_CODE
				WordcodeEmitter* translator = new(translator_buf) WordcodeEmitter(this, toplevel);
				coder = translator;
				#else
				CodeWriter* stubWriter = new(stubWriter_buf) CodeWriter();
				coder = stubWriter;
				#endif
				verifier.verify(coder); // pass2 dataflow
				setInterpImpl();
				// NOTE end copy

#endif // FEATURE_NANOJIT
				
				if (coder)
				{
					coder->cleanup();
					coder = NULL;
				}
			}
			CATCH (Exception *exception) 
			{
				// clean up verifier
				verifier.~Verifier();

				// call cleanup all the way down the chain
				// each stage calls cleanup on the next one
				if (coder)
					coder->cleanup();

				// re-throw exception
				core->throwException(exception);
			}
			END_CATCH
			END_TRY
			PERFM_TPROF_END();
		}
Ejemplo n.º 9
0
    /**
     * This function is called by JIT code if OSR has been requested
     * if (exec->current_osr != 0).  This function fills the JIT frame locals
     * to match the interpreter frame.  The JIT code looks like this:
     *
     *   if (*(&exec->current_osr)) {
     *     adjustFrame(...);
     *     goto loop_entry
     *   }
     */
    void OSR::adjustFrame(MethodFrame* jitMethodFrame, CallStackNode *callStack,
                             FramePtr jitFramePointer,  uint8_t *jitFrameTags)
    {
        MethodEnv* env = jitMethodFrame->env();
        BaseExecMgr* exec = BaseExecMgr::exec(env);
        OSR *osr = exec->current_osr;
        AvmAssert(osr && "should not have gotten here");
        Atom* interpFramePointer = osr->interp_frame;

        MethodSignaturep ms = env->method->getMethodSignature();
        int nLocals = ms->local_count();
        int stackBase = nLocals + ms->max_scope();
        FrameState* frameState = osr->jit_frame_state;
        int scopeTop = nLocals + frameState->scopeDepth;
        int stackTop = stackBase + frameState->stackDepth;

        // OSR has been requested.

#ifdef AVMPLUS_VERBOSE
        if (env->method->pool()->isVerbose(VB_interp)) {
            env->core()->console <<
                    "osr-adjust_frame " << env->method->method_id() <<
                    " " << env->method <<
                    " scopeTop=" << scopeTop <<
                    " stackTop=" << stackTop <<
                    "\n";
        }
#endif

        // Patch the JIT frame local variable slots and the scope slots in use to match the interpreter state.
        for (int i = 0; i < scopeTop; i++)
            unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);

        // zero out stack area for unused scopes:
        if (scopeTop < stackBase) {
            void* p = ((char*) jitFramePointer + (scopeTop << VARSHIFT(env->method)));
            size_t nbytes = (stackBase - scopeTop) << VARSHIFT(env->method);
            VMPI_memset(p, 0, nbytes);
        }

        // Patch operand stack slots:
        for (int i = stackBase; i < stackTop; i++)
            unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);

        MethodFrame *interpreterMethodFrame = jitMethodFrame->next;
        jitMethodFrame->dxns = interpreterMethodFrame->dxns;
        jitMethodFrame->next = interpreterMethodFrame->next;

        // Clean up non-local OSR parameterization data:
        mmfx_delete(frameState);
        exec->current_osr = NULL;

#ifdef DEBUGGER
        // Call debugEnter if necessary, since the jit code won't.
        // We can safely pass NULL for &eip here, because OSR is disabled
        // for methods with catch blocks.
        if (callStack)
            env->debugEnter(jitFrameTags, callStack, jitFramePointer, NULL);
#else
        (void) callStack;
#endif
    }
Ejemplo n.º 10
0
    /**
     * This function detects if OSR has been requested or if this is a
     * subsequent normal call.  In the latter case it has no side effect and
     * returns false and control flow is supposed to proceed normally.
     * If OSR is in the works, then it fills the JIT frame locals to match the
     * interpreter frame.  Then it returns true, which indicates to the
     * generated code to jump to the OSR entry point.
     *
     * The JIT code looks like this:
     *
     *   if (adjust_frame(...))
     *     goto loop_entry
     */
    int32_t OSR::adjustFrame(MethodFrame* jitMethodFrame,
                             CallStackNode *callStack,
                             FramePtr jitFramePointer,
                             uint8_t *jitFrameTags)
    {
        MethodEnv* env = jitMethodFrame->env();
        BaseExecMgr* exec = BaseExecMgr::exec(env);
        OSR *osr = exec->current_osr;
        if (!osr) {
            // No OSR has been requested.
            // We are at the beginning of a normal AS method call.
            // Indicate proceeding with normal control flow.
            return 0;
        }
        Atom* interpFramePointer = osr->interp_frame;

        MethodSignaturep ms = env->method->getMethodSignature();
        int nLocals = ms->local_count();
        int stackBase = nLocals + ms->max_scope();
        FrameState* frameState = osr->jit_frame_state;
        int scopeTop = nLocals + frameState->scopeDepth;
        int stackTop = stackBase + frameState->stackDepth;

        // OSR has been requested.

#ifdef AVMPLUS_VERBOSE
        if (env->method->pool()->isVerbose(VB_interp)) {
            env->core()->console <<
                    "osr-adjust_frame " << env->method->method_id() <<
                    " " << env->method <<
                    " scopeTop=" << scopeTop <<
                    " stackTop=" << stackTop <<
                    "\n";
        }
#endif

        // Patch the JIT frame local variable slots and the scope slots in use to match the interpreter state.
        for (int i = 0; i < scopeTop; i++)
            unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);

        // zero out stack area for unused scopes:
        if (scopeTop < stackBase) {
            void* p = ((char*) jitFramePointer + scopeTop * VARSIZE);
            size_t nbytes = (stackBase - scopeTop) * VARSIZE;
            VMPI_memset(p, 0, nbytes);
        }

        // Patch operand stack slots:
        for (int i = stackBase; i < stackTop; i++)
            unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);

        MethodFrame *interpreterMethodFrame = jitMethodFrame->next;
        jitMethodFrame->dxns = interpreterMethodFrame->dxns;
        jitMethodFrame->next = interpreterMethodFrame->next;

        // Clean up non-local OSR parameterization data:
        mmfx_delete(frameState);
        exec->current_osr = NULL;

#ifdef DEBUGGER
        // Call debugEnter if necessary, since the jit code won't.
        // We can safely pass NULL for &eip here, because OSR is disabled
        // for methods with catch blocks.
        if (callStack)
            env->debugEnter(jitFrameTags, callStack, jitFramePointer, NULL);
#else
        (void) callStack;
#endif

        // Indicate taking a shortcut from the call site
        // to the OSR entry point instead of normal control flow:
        return 1;
    }
Ejemplo n.º 11
0
	int FunctionObject::get_length()
	{
		MethodSignaturep ms = _call->method->getMethodSignature();
		return ms->param_count();
	}
Ejemplo n.º 12
0
    ScriptObject* TypeDescriber::describeTraits(Traitsp traits, uint32_t flags, Toplevel* toplevel)
    {
        if (!(flags & INCLUDE_TRAITS))
            return NULL;

        AvmCore* core = m_toplevel->core();
        GC* gc = core->GetGC();
        TraitsBindingsp tb = traits->getTraitsBindings();
        TraitsMetadatap tm = traits->getTraitsMetadata();

        ScriptObject* o = new_object();

        ArrayObject* bases = NULL;
        ArrayObject* metadata = NULL;
        ArrayObject* interfaces = NULL;
        ArrayObject* methods = NULL;
        ArrayObject* accessors = NULL;
        ArrayObject* variables = NULL;
        ScriptObject* constructor = NULL;

        if (flags & INCLUDE_METADATA)
        {
            metadata = new_array();
            PoolObject* class_mdpool;
            const uint8_t* class_md = tm->getMetadataPos(class_mdpool);
            if (class_md)
                addDescribeMetadata(metadata, class_mdpool, class_md);
        }

        if (flags & INCLUDE_BASES)
        {
            bases = new_array();
            for (Traitsp b = traits->base; b; b = b->base)
                pushstr(bases, describeClassName(b));
        }

        if (flags & INCLUDE_INTERFACES)
        {
            interfaces = new_array();
            for (InterfaceIterator iter(traits); iter.hasNext();)
            {
                Traits* ti = iter.next();
                pushstr(interfaces, describeClassName(ti));
            }
        }

        // constructor
        if (flags & INCLUDE_CONSTRUCTOR)
        {
            MethodInfo* initMethod = traits->init;
            if (initMethod)
            {
                initMethod->resolveSignature(toplevel);
                MethodSignaturep ms = initMethod->getMethodSignature();
                if (ms->param_count() > 0)
                {
                    constructor = describeParams(initMethod, ms);
                }
            }
        }

        if (flags & (INCLUDE_ACCESSORS | INCLUDE_METHODS | INCLUDE_VARIABLES))
        {
            // recover slot/method metadata and method-declarer information.

            // make a flattened set of bindings so we don't have to check for overrides as we go.
            // This is not terribly efficient, but doesn't need to be.
            MultinameBindingHashtable* mybind = MultinameBindingHashtable::create(gc);
            addBindings(m_toplevel->core(), mybind, tb, flags);

            // Don't want interface methods, so post-process and wipe out any
            // bindings that were added.
            for (InterfaceIterator ifc_iter(traits); ifc_iter.hasNext();)
            {
                Traitsp ti = ifc_iter.next();
                TraitsBindingsp tbi = ti->getTraitsBindings();
                StTraitsBindingsIterator iter(tbi);
                while (iter.next())
                {
                    if (!iter.key()) continue;
                    mybind->add(iter.key(), iter.ns(), BIND_NONE);
                }
            }

            // yuck, replicate buggy behavior in FP9/10
            RCList<Namespace> nsremoval(gc, kListInitialCapacity);
            if (flags & HIDE_NSURI_METHODS)
            {
                for (TraitsBindingsp tbi = tb->base; tbi; tbi = tbi->base)
                {
                    StTraitsBindingsIterator iter(tbi);
                    while (iter.next())
                    {
                        if (!iter.key()) continue;
                        Namespacep ns = iter.ns();
                        if (ns->getURI()->length() > 0 && nsremoval.indexOf(ns) < 0)
                        {
                            nsremoval.add(ns);
                        }
                    }
                }
            }

            StMNHTBindingIterator iter(mybind);
            while (iter.next())
            {
                if (!iter.key()) continue;
                Stringp name = iter.key();
                Namespacep ns = iter.ns();
                Binding binding = iter.value();
                Stringp nsuri = ns->getURI();
                TraitsMetadata::MetadataPtr md1 = NULL;
                TraitsMetadata::MetadataPtr md2 = NULL;
                PoolObject* md1pool = NULL;
                PoolObject* md2pool = NULL;

                // We only display public members -- exposing private namespaces could compromise security.
                if (ns->getType() != Namespace::NS_Public) {
                    continue;
                }

                if ((flags & HIDE_NSURI_METHODS) && nsremoval.indexOf(ns) >= 0) {
                    continue;
                }
                ScriptObject* v = new_object();

                const BindingKind bk = AvmCore::bindingKind(binding);
                switch (bk)
                {
                    case BKIND_CONST:
                    case BKIND_VAR:
                    {
                        if (!(flags & INCLUDE_VARIABLES))
                            continue;

                        const uint32_t slotID = AvmCore::bindingToSlotId(binding);
                        const KVPair props[] = {
                            { kstrid_access, strAtom(str(bk == BKIND_CONST ? kstrid_readonly : kstrid_readwrite)) },
                            { kstrid_type, strAtom(describeClassName(tb->getSlotTraits(slotID))) },
                        };
                        setpropmulti(v, props, elem_count(props));
                        if (!variables) variables = new_array();
                        pushobj(variables, v);
                        md1 = tm->getSlotMetadataPos(slotID, md1pool);
                        break;
                    }

                    case BKIND_METHOD:
                    {
                        if (!(flags & INCLUDE_METHODS))
                            continue;

                        const uint32_t methodID = AvmCore::bindingToMethodId(binding);
                        MethodInfo* mi = tb->getMethod(methodID);
                        mi->resolveSignature(toplevel);
                        MethodSignaturep ms = mi->getMethodSignature();

                        Traitsp declaringTraits = mi->declaringTraits();

                        const KVPair props[] = {
                            { kstrid_declaredBy, strAtom(describeClassName(declaringTraits)) },
                            { kstrid_returnType, strAtom(describeClassName(ms->returnTraits())) },
                            { kstrid_parameters, objAtom(describeParams(mi, ms)) },
                        };
                        setpropmulti(v, props, elem_count(props));
                        if (!methods) methods = new_array();
                        pushobj(methods, v);
                        md1 = tm->getMethodMetadataPos(methodID, md1pool);
                        break;
                    }

                    case BKIND_GET:
                    case BKIND_SET:
                    case BKIND_GETSET:
                    {
                        if (!(flags & INCLUDE_ACCESSORS))
                            continue;

                        const uint32_t methodID = AvmCore::hasGetterBinding(binding) ?
                                                    AvmCore::bindingToGetterId(binding) :
                                                    AvmCore::bindingToSetterId(binding);

                        MethodInfo* mi = tb->getMethod(methodID);
                        mi->resolveSignature(toplevel);
                        MethodSignaturep ms = mi->getMethodSignature();

                        Traitsp declaringTraits = mi->declaringTraits();

                        // The verifier does not check the signature of a setter
                        // except when it is invoked.  We must be prepared for the
                        // case in which it has no arguments.
                        Traitsp accessorType;

                        if (AvmCore::hasGetterBinding(binding)) {
                            accessorType = ms->returnTraits();
                        } else {
                            // If setter is malformed, just use '*' as a placeholder.
                            accessorType = (ms->param_count() < 1) ? NULL : ms->paramTraits(1);
                        }

                        static const uint8_t bk2str[8] =
                        {
                            uint8_t(kstrid_emptyString),    // BKIND_NONE
                            uint8_t(kstrid_emptyString),    // BKIND_METHOD
                            uint8_t(kstrid_emptyString),    // BKIND_VAR
                            uint8_t(kstrid_emptyString),    // BKIND_CONST
                            uint8_t(kstrid_emptyString),    // unused
                            uint8_t(kstrid_readonly),       // BKIND_GET
                            uint8_t(kstrid_writeonly),      // BKIND_SET
                            uint8_t(kstrid_readwrite)       // BKIND_GETSET
                        };
                        const KVPair props[] = {
                            { kstrid_declaredBy, strAtom(describeClassName(declaringTraits)) },
                            { kstrid_access, strAtom(str(StringId(bk2str[bk]))) },
                            { kstrid_type, strAtom(describeClassName(accessorType)) },
                        };
                        setpropmulti(v, props, elem_count(props));
                        if (AvmCore::hasGetterBinding(binding))
                            md1 = tm->getMethodMetadataPos(AvmCore::bindingToGetterId(binding), md1pool);
                        if (AvmCore::hasSetterBinding(binding))
                            md2 = tm->getMethodMetadataPos(AvmCore::bindingToSetterId(binding), md2pool);
                        if (!accessors) accessors = new_array();
                        pushobj(accessors, v);
                        break;
                    }
                    case BKIND_NONE:
                        break;
                    default:
                        break;
                }

                ArrayObject* vm = NULL;
                if ((flags & INCLUDE_METADATA) && (md1 || md2))
                {
                    vm = new_array();
                    addDescribeMetadata(vm, md1pool, md1);
                    addDescribeMetadata(vm, md2pool, md2);
                }
                const KVPair props[] = {
                    { kstrid_name, strAtom(name) },
                    { kstrid_uri, strAtom(nsuri->length() == 0 ? NULL : nsuri) },
                    { kstrid_metadata, objAtom(vm) },
                };
                setpropmulti(v, props, elem_count(props));
            }
        }

        const KVPair props[] = {
            { kstrid_bases, objAtom(bases) },
            { kstrid_interfaces, objAtom(interfaces) },
            { kstrid_metadata, objAtom(metadata) },
            { kstrid_accessors, objAtom(accessors) },
            { kstrid_methods, objAtom(methods) },
            { kstrid_variables, objAtom(variables) },
            { kstrid_constructor, objAtom(constructor) },
        };
        setpropmulti(o, props, elem_count(props));

        return o;
    }