wsharp%adobe.com 039e5a464d bug 375561. stejohns+ review. Various fixes from Flash player codebase
git-svn-id: svn://10.0.0.236/trunk@222472 18797224-902f-48f8-a5cc-f745e15eee43
2007-03-27 18:37:45 +00:00

886 lines
24 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is [Open Source Virtual Machine.].
*
* The Initial Developer of the Original Code is
* Adobe System Incorporated.
* Portions created by the Initial Developer are Copyright (C) 1993-2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adobe AS3 Team
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "avmplus.h"
namespace avmplus
{
using namespace MMgc;
/**
* traits with base traits (inheritance)
*/
Traits::Traits(AvmCore *core,
Traits *base,
int nameCount,
int interfaceCount,
int interfaceCapacity,
size_t sizeofInstance)
: core(core),
MultinameHashtable(0)
{
// delay initialization to when vtable is setup so memory can be reported appropriately
Init(1+5*nameCount/4);
this->needsHashtable = false;
this->sizeofInstance = sizeofInstance;
this->isInterface = false;
this->hasInterfaces = false;
this->interfaceCount = interfaceCount;
this->interfaceCapacity = interfaceCapacity;
this->hashTableOffset = -1;
this->metadata_pos = NULL;
this->slot_metadata_pos = NULL;
// We are being sneaky and storing the slots immediately
// at the end of this object. The methods table follows
// the slots table.
// replaced with getInterfaces()
//interfaces = (Traitsp*)((char*)this+sizeof(Traits));
if (base != NULL)
{
this->base = base;
// clear and copy interfaces
addInterface(base);
// don't copy down the symbol table, we'll walk up the tree
// when we need to. This saves a lot of memory for negligable slowdown.
}
}
Traits::~Traits()
{
}
void Traits::addInterface(Traits* ifc)
{
AvmAssert(ifc != this && ifc != NULL);
//*(findInterface(ifc)) = ifc;
WB(core->GetGC(), this, findInterface(ifc), ifc);
if(ifc->isInterface)
hasInterfaces = true;
for (int i=0, n=ifc->interfaceCapacity; i < n; i++)
{
Traits* t = ifc->getInterface(i);
if (t != NULL)
{
AvmAssert(ifc != this);
if(t->isInterface)
hasInterfaces = true;
//*(findInterface(t)) = t;
WB(core->GetGC(), this, findInterface(t), t);
}
}
}
Binding Traits::findBinding(Stringp name, Namespace* ns) const
{
const Traits* t = this;
Binding b;
do
{
b = t->get(name, ns);
}
while (b == BIND_NONE && (t=t->base) != NULL);
return b;
}
Binding Traits::findBinding(Stringp name, NamespaceSet* nsset) const
{
const Traits* t = this;
Binding b;
do
{
b = t->get(name, nsset);
}
while (b == BIND_NONE && (t=t->base) != NULL);
return b;
}
void Traits::initTables(const Toplevel *toplevel)
{
// copy down info from base class
AvmAssert(!linked);
MMgc::GC* gc = core->GetGC();
if (slotCount > 0 || methodCount > 0 || hasInterfaces)
{
MMGC_MEM_TYPE(this);
size_t size = slotCount*sizeof(Traits*) + // slotTypes
slotCount*sizeof(int) + // slot offsets
methodCount*sizeof(AbstractFunction*);
if(hasInterfaces)
size += IMT_SIZE * sizeof(Binding);
void *idata = gc->Alloc(size, GC::kZero | GC::kContainsPointers);
// Can happen with corrupt SWFs
if (!idata)
toplevel->throwError(kOutOfMemoryError);
WB(gc, this, &instanceData, idata);
}
AbstractFunction **methods = getMethods();
int *offsets = getOffsets();
Traits **slotTypes = getSlotTypes();
if (base)
{
AvmAssert(base->linked);
//
// process the slots: types, offsets, default values
//
int firstSlot;
if ((firstSlot=base->slotCount) > 0)
{
// copy base offsets and adjust for sizeofInstance
AvmAssert(sizeofInstance >= base->sizeofInstance);
int delta = sizeofInstance - base->sizeofInstance;
for (int i=0; i < firstSlot; i++)
{
WB(gc, instanceData, &slotTypes[i], base->getSlotTypes()[i]);
offsets[i] = base->getOffsets()[i] + delta;
}
}
int firstMethod;
if ((firstMethod = base->methodCount) > 0)
{
// copy base methods
for (int i=0; i < firstMethod; i++)
{
WB(gc, slotTypes, &methods[i], base->getMethods()[i]);
}
}
}
}
void Traits::initMetadataTable()
{
if( !slot_metadata_pos )
{
MMgc::GC* gc = core->GetGC();
// Lots of things won't have any metadata, so only alloc the space if we really need it
MMGC_MEM_TYPE(this);
const byte** idata = (const byte**)gc->Calloc(slotCount+methodCount, sizeof(const byte*), GC::kZero | GC::kContainsPointers);
WB(gc, this, &slot_metadata_pos, idata);
}
}
void Traits::setIndexedMetadataPos(uint32 i, const byte* pos)
{
AvmAssert(i < (slotCount+methodCount) );
if (!slot_metadata_pos)
initMetadataTable();
slot_metadata_pos[i] = pos;
}
const byte* Traits::getSlotMetadataPos(uint32 i, PoolObject*& residingPool) const
{
AvmAssert( i < slotCount );
Traits* t = (Traits*)this;
const byte* pos = 0;
while(t && !pos && i<t->slotCount)
{
if (t->slot_metadata_pos)
{
pos = t->slot_metadata_pos[i];
residingPool = t->pool;
}
t = t->base;
}
return pos;
}
const byte* Traits::getMethodMetadataPos(uint32 i, PoolObject*& residingPool) const
{
AvmAssert( i < methodCount );
Traits* t = (Traits*)this;
const byte* pos = 0;
while(t && !pos && i<t->methodCount)
{
if (t->slot_metadata_pos)
{
pos = t->slot_metadata_pos[t->slotCount+i];
residingPool = t->pool;
}
t = t->base;
}
return pos;
}
/**
* This must be called before any method is verified or any
* instances are created. It is not done eagerly in AbcParser
* because doing so would prevent circular type references between
* slots of cooperating classes.
*
* Resolve the type and position/width of each slot.
*/
void Traits::resolveSignatures(const Toplevel* toplevel)
{
if (!linked)
{
int firstSlot = 0;
int firstMethod = 0;
Traitsp* interfaces = getInterfaces();
// make sure base class and all interfaces are resolved
for (int i=0, n=interfaceCapacity; i < n; i++)
{
Traits* ifc = interfaces[i];
if (ifc && !ifc->linked)
{
ifc->resolveSignatures(toplevel);
}
}
if (base)
{
firstSlot = base->slotCount;
firstMethod = base->methodCount;
}
initTables(toplevel);
pool->resolveTraits(this, firstSlot, toplevel);
// make sure all the methods have resolved types
for (int i=0, n=methodCount; i < n; i++)
{
AbstractFunction *f = getMethod(i);
if (f != NULL)
f->resolveSignature(toplevel);
// else, sparse method array, why?
}
bool legal = true;
if (base && base->methodCount > 0)
{
// check concrete overrides
AbstractFunction *virt=NULL;
AbstractFunction *over=NULL;
for (int i=0, n=base->methodCount; i < n; i++)
{
virt = base->getMethod(i);
over = getMethod(i);
if (virt != NULL && virt != over)
legal &= checkOverride(virt, over);
}
}
if (hasInterfaces && legal && !this->isInterface)
{
ImtBuilder imtBuilder(core->GetGC());
// make sure every interface method is implemented
for (int i=0, n=interfaceCapacity; i < n; i++)
{
Traits* ifc = interfaces[i];
if (ifc && ifc->isInterface)
{
AbstractFunction *virt=NULL;
AbstractFunction *over=NULL;
for (int j=ifc->next(0); j != 0; j = ifc->next(j))
{
Stringp name = ifc->keyAt(j);
Namespace* ns = ifc->nsAt(j);
Binding iBinding = ifc->valueAt(j);
if (AvmCore::isMethodBinding(iBinding))
{
virt = ifc->getMethod(AvmCore::bindingToMethodId(iBinding));
Binding cBinding = findBinding(name, ns);
if (!AvmCore::isMethodBinding(cBinding))
{
// Try again with public namespace
Binding pBinding = findBinding(name, core->publicNamespace);
// If that worked, add a trait.
if (AvmCore::isMethodBinding(pBinding))
{
add(name, ns, pBinding);
cBinding = pBinding;
}
}
if (!AvmCore::isMethodBinding(cBinding))
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "method not implemented " << virt << "\n";
core->console << " over-binding " << (int)cBinding << " in " << this << "\n";
#endif
over = NULL;
legal = false;
}
else
{
int disp_id = AvmCore::bindingToMethodId(cBinding);
over = getMethod(disp_id);
if (over != virt)
legal &= checkOverride(virt, over);
imtBuilder.addEntry(virt, disp_id);
}
}
else if (AvmCore::isAccessorBinding(iBinding))
{
//virtAcc = bindingToAccessor(iBinding)
Binding cBinding = findBinding(name, ns);
if (!AvmCore::isAccessorBinding(cBinding))
{
// Try again with public namespace
Binding pBinding = findBinding(name, core->publicNamespace);
// If that worked, add a trait.
if (AvmCore::isAccessorBinding(pBinding))
{
add(name, ns, pBinding);
cBinding = pBinding;
}
}
if (!AvmCore::isAccessorBinding(cBinding))
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "accessor not implemented " << ns << "::" << name << "\n";
core->console << " over-binding " << (int)cBinding << " in " << this << "\n";
#endif
over = NULL;
legal = false;
}
else
{
// check getter & setter overrides
if (AvmCore::hasGetterBinding(iBinding))
{
virt = ifc->getMethod(AvmCore::bindingToGetterId(iBinding));
if (!AvmCore::hasGetterBinding(cBinding))
{
// missing getter
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "getter not implemented " << ns << "::" << name << "\n";
core->console << " over-binding " << (int)cBinding << " in " << this << "\n";
#endif
over = NULL;
legal = false;
}
else
{
int disp_id = AvmCore::bindingToGetterId(cBinding);
over = getMethod(disp_id);
if (over != virt)
legal &= checkOverride(virt,over);
imtBuilder.addEntry(virt, disp_id);
}
}
if (AvmCore::hasSetterBinding(iBinding))
{
virt = ifc->getMethod(AvmCore::bindingToSetterId(iBinding));
if (!AvmCore::hasSetterBinding(cBinding))
{
// missing setter
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "setter not implemented " << ns << "::" << name << "\n";
core->console << " over-binding " << (int)cBinding << " in " << this << "\n";
#endif
over = NULL;
legal = false;
}
else
{
int disp_id = AvmCore::bindingToSetterId(cBinding);
over = getMethod(disp_id);
if (over != virt)
legal &= checkOverride(virt,over);
imtBuilder.addEntry(virt, disp_id);
}
}
}
}
}
}
}
imtBuilder.finish(getIMT(), pool);
}
if (!legal)
{
Multiname qname(ns, name);
toplevel->throwVerifyError(kIllegalOverrideError, core->toErrorString(&qname), core->toErrorString(this));
}
linked = true;
}
}
bool Traits::checkOverride(AbstractFunction* virt, AbstractFunction* over) const
{
Traits* overTraits = over->returnTraits();
Traits* virtTraits = virt->returnTraits();
if (overTraits != virtTraits)
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "return types dont match\n";
core->console << " virt " << virtTraits << " " << virt << "\n";
core->console << " over " << overTraits << " " << over << "\n";
#endif
return false;
}
if (over->param_count != virt->param_count ||
over->optional_count != virt->optional_count)
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "param count mismatch\n";
core->console << " virt params=" << virt->param_count << " optional=" << virt->optional_count << " " << virt << "\n";
core->console << " over params=" << over->param_count << " optional=" << over->optional_count << " " << virt << "\n";
#endif
return false;
}
// allow subclass param 0 to implement or extend base param 0
virtTraits = virt->paramTraits(0);
if (!containsInterface(virtTraits) ||
!isMachineCompatible(this,virtTraits))
{
if (!this->isMachineType && virtTraits == core->traits.object_itraits)
{
over->flags |= AbstractFunction::UNBOX_THIS;
}
else
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "param 0 incompatible\n";
core->console << " virt " << virtTraits << " " << virt << "\n";
core->console << " over " << this << " " << over << "\n";
#endif
return false;
}
}
for (int k=1, p=over->param_count; k <= p; k++)
{
overTraits = over->paramTraits(k);
virtTraits = virt->paramTraits(k);
if (overTraits != virtTraits)
{
#ifdef AVMPLUS_VERBOSE
core->console << "\n";
core->console << "param " << k << " incompatible\n";
core->console << " virt " << virtTraits << " " << virt << "\n";
core->console << " over " << overTraits << " " << over << "\n";
#endif
return false;
}
}
if (virt->flags & AbstractFunction::UNBOX_THIS)
{
// the UNBOX_THIS flag is sticky, all the way down the inheritance tree
over->flags |= AbstractFunction::UNBOX_THIS;
}
return true;
}
// static
bool Traits::isMachineCompatible(const Traits* a, const Traits* b)
{
return a == b ||
// *, Object, and Void are each represented as Atom
(!a || a == a->core->traits.object_itraits || a == a->core->traits.void_itraits) &&
(!b || b == b->core->traits.object_itraits || b == b->core->traits.void_itraits) ||
// all other non-pointer types have unique representations
(a && b && !a->isMachineType && !b->isMachineType);
}
#ifdef AVMPLUS_VERBOSE
Stringp Traits::format(AvmCore* core) const
{
if (name != NULL)
return Multiname::format(core, ns, name);
else
return core->concatStrings(core->newString("Traits@"),
core->formatAtomPtr((uintptr)this));
}
#endif
//
// implemented traits, organized as a hash-set
//
Traitsp* Traits::findInterface(Traits* t) const
{
Traitsp* set = this->getInterfaces();
// this is a quadratic probe
int n = 7;
unsigned bitMask = this->interfaceCapacity - 1;
// Note: Mask off MSB to avoid negative indices. Mask off bottom
// 3 bits because it doesn't contribute to hash.
// [ed] math assumes t is 8-aligned!
AvmAssert((((uintptr)t) & 7) == 0);
unsigned i = (((uintptr)t)>>3) & bitMask;
Traitsp k;
while ((k=set[i]) != t && k != NULL)
{
i = (i + (n++)) & bitMask; // quadratic probe
}
return &set[i];
}
void Traits::setSlotInfo(int index, uint32 slot_id, const Toplevel* toplevel, Traits* t, int offset, CPoolKind kind, AbcGen& gen) const
{
AvmAssert(t != VOID_TYPE);
AvmAssert(slot_id < slotCount);
Traits **slotTypes = getSlotTypes();
int *offsets = getOffsets();
// slotTypes[slot_id] = t;
WB(core->GetGC(), instanceData, &slotTypes[slot_id], t);
offsets[slot_id] = offset;
if (!t)
{
Atom value = !index ? undefinedAtom : pool->getDefaultValue(toplevel, index, kind, t);
if(value != 0)
{
gen.getlocalN(0);
if(value == undefinedAtom)
gen.pushundefined();
else if (AvmCore::isNull(value))
gen.pushnull();
else
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == OBJECT_TYPE)
{
Atom value = !index ? nullObjectAtom : pool->getDefaultValue(toplevel, index, kind, t);
if (value == undefinedAtom)
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(value != 0)
{
gen.getlocalN(0);
if(AvmCore::isNull(value))
gen.pushnull();
else
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == NUMBER_TYPE)
{
Atom value = !index ? core->kNaN : pool->getDefaultValue(toplevel, index, kind, t);
if (!(AvmCore::isInteger(value)||AvmCore::isDouble(value)))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(AvmCore::number_d(value) != 0)
{
gen.getlocalN(0);
if(value == core->kNaN)
gen.pushnan();
else
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == BOOLEAN_TYPE)
{
Atom value = !index ? falseAtom : pool->getDefaultValue(toplevel, index, kind, t);
if (!AvmCore::isBoolean(value))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
AvmAssert(urshift(falseAtom,3) == 0);
if(value != falseAtom)
{
gen.getlocalN(0);
gen.pushtrue();
gen.setslot(slot_id);
}
}
else if (t == UINT_TYPE)
{
Atom value = !index ? (0|kIntegerType) : pool->getDefaultValue(toplevel, index, kind, t);
if (!AvmCore::isInteger(value) && !AvmCore::isDouble(value))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
double d = AvmCore::number_d(value);
if (d != (uint32)d)
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(value != (0|kIntegerType))
{
gen.getlocalN(0);
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == INT_TYPE)
{
Atom value = !index ? (0|kIntegerType) : pool->getDefaultValue(toplevel, index, kind, t);
if (!AvmCore::isInteger(value) && !AvmCore::isDouble(value))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
double d = AvmCore::number_d(value);
if (d != (int)d)
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(value != (0|kIntegerType))
{
gen.getlocalN(0);
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == STRING_TYPE)
{
Atom value = !index ? nullStringAtom : pool->getDefaultValue(toplevel, index, kind, t);
if (!(AvmCore::isNull(value) || AvmCore::isString(value)))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(!AvmCore::isNull(value))
{
gen.getlocalN(0);
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else if (t == NAMESPACE_TYPE)
{
Atom value = !index ? nullNsAtom : pool->getDefaultValue(toplevel, index, kind, t);
if (!(AvmCore::isNull(value) || AvmCore::isNamespace(value)))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
if(!AvmCore::isNull(value))
{
gen.getlocalN(0);
gen.pushconstant(kind, index);
gen.setslot(slot_id);
}
}
else
{
// any other type: only allow null default value
Atom value = !index ? nullObjectAtom : pool->getDefaultValue(toplevel, index, kind, t);
if (!AvmCore::isNull(value))
{
Multiname qname(t->ns, t->name);
toplevel->throwVerifyError(kIllegalDefaultValue, core->toErrorString(&qname));
}
}
}
#ifdef _DEBUG
int Traits::interfaceSlop()
{
Traitsp *interfaces = getInterfaces();
int count=0;
for(int i=0, n=interfaceCapacity; i<n; i++)
{
if(interfaces[i] != NULL)
count++;
}
return interfaceCount - count;
}
#endif
#ifdef MMGC_DRC
void Traits::destroyInstance(ScriptObject *obj)
{
//TODO: code gen this function
Hashtable *ht = NULL;
if(needsHashtable) {
ht = obj->getTable();
}
// memset native space to zero except baseclasses
memset((char*)obj+sizeof(AvmPlusScriptableObject), 0, sizeofInstance-sizeof(AvmPlusScriptableObject));
// NOTE: this code can't use type macros or core b/c it can run
// after AvmCore has been destructed
int *offsets = getOffsets();
Traitsp *traits = getSlotTypes();
for(int i=0, n=slotCount; i<n; i++) {
Traits *t = traits[i];
// offset is pointed off the end of our object
AvmAssert(offsets[i] < int(MMgc::GC::Size (obj)));
Atom *slot = (Atom*)((char*)obj+offsets[i]);
if(Traits::canContainPointer(t)) {
RCObject *rc = NULL;
if(!t || t->isObjectType) { // atom
Atom a = *slot;
int type = a&7;
//if(type == kObjectType || type == kStringType || type == kNamespaceType)
AvmAssert(kObjectType == 1 && kStringType == 2 && kNamespaceType == 3);
if(type <= kNamespaceType)
rc = (RCObject*)(a&~7);
} else { // raw pointer
rc = *(RCObject**)slot;
}
if(rc)
rc->DecrementRef();
*slot = 0;
} else { // machineType
AvmAssert(t->getTotalSize() == 1 || t->getTotalSize() == 4 || t->getTotalSize() == 8);
if (t->getTotalSize() == 8)
{
#ifdef AVMPLUS_64BIT
*slot = 0;
#else
*slot = 0;
*(slot + 1) = 0;
#endif
}
else // 4 byte slot
{
// The Boolean size is 1 but takes up 4 bytes. (Important on Mac with reverse endian)
*(uint32 *)slot = 0;
}
}
}
// do this last, idea is to zero the object in order
if(ht) {
ht->destroy();
}
}
#endif
ImtBuilder::ImtBuilder(MMgc::GC *gc)
{
this->gc = gc;
memset(entries, 0, sizeof(ImtEntry*)*Traits::IMT_SIZE);
}
void ImtBuilder::addEntry(AbstractFunction* virt, int disp_id)
{
int i = virt->iid() % Traits::IMT_SIZE;
#ifdef AVMPLUS_VERBOSE
if (entries[i] && virt->pool->verbose)
virt->core()->console << "conflict " << virt->iid() << " " << i << "\n";
#endif
entries[i] = new (gc) ImtEntry(virt, entries[i], disp_id);
}
void ImtBuilder::finish(Binding imt[], PoolObject* pool)
{
for (int i=0; i < Traits::IMT_SIZE; i++)
{
ImtEntry *e = entries[i];
if (e == NULL)
{
imt[i] = BIND_NONE;
}
else if (e->next == NULL)
{
// single entry, no conflict
imt[i] = e->disp_id<<3 | BIND_METHOD;
gc->Free(e);
}
else
{
// build conflict stub
CodegenMIR mir(pool);
imt[i] = BIND_ITRAMP | (uintptr)mir.emitImtThunk(e);
AvmAssert((imt[i]&7)==BIND_ITRAMP); // addr must be 8-aligned
}
}
}
}