Рассматривая, как работает asm.js, я считаю, что лучше всего использовать метод, используемый исходным компилятором CFront: скомпилируйте виртуальные методы до функций, которые берут этот указатель, и используйте thunks для исправления этого указателя перед тем как передать его. Я буду пройти через это шаг за шагом:
Нет Наследование
Сокращение методов к специальным функциям:
void ExampleObject::foo(void);
не будет преобразован в
void exampleobject_foo(ExampleObject* this);
Это прекрасно работает для объекты, не основанные на наследовании.
Single Inheritance
Мы можем легко добавлять поддержку для сколь угодно большого количества одиночного наследования через простой трюк: всегда хранить объект в базе памяти первого:
class A : public B
стало бы, в памяти :
[[ B ] A ]
Подходите ближе!
Множественное наследование Теперь, множественное наследование делает это гораздо труднее работать с
class A : public B, public C
Это невозможно для обоих B и C, чтобы быть в начале A; они просто не могут сосуществовать. Существует два варианта:
- Храните явное смещение (известное как delta) для каждого вызова на базу.
- Не разрешать звонки через к B или C
Второй вариант намного предпочтительнее по ряду причин; если вы вызываете функцию члена базового класса, редко вы хотите сделать это через производный класс. Вместо этого вы можете просто перейти в C :: conlyfunc, который затем может выполнить настройку на ваш указатель для вас бесплатно. Разрешение A :: conlyfunc удаляет важную информацию, которую мог бы использовать компилятор, при очень небольшой пользе.
Первый выбор используется в C++; все объекты множественного наследования вызовут thunk перед каждым вызовом базовому классу, который настраивает этот указатель так, чтобы он указывал на подобъект внутри него. В простом примере:
class ExampleBaseClass
{
void foo(void);
}
class ExampleDerivedClass : public ExampleBaseClass, private IrrelevantBaseClass
{
void bar(void);
}
тогда станет
void examplebaseclass_foo(ExampleBaseClass* this);
void examplederivedclass_bar(ExampleDerivedClass* this);
void examplederivedclass_thunk_foo(ExampleDerivedClass* this)
{
examplebaseclass_foo(this + delta);
}
Это может быть встраиваемыми во многих ситуациях, так что это не слишком большая накладных расходов. Однако, если вы никогда не сможете ссылаться на ExampleBaseClass :: foo как ExampleDerivedClass :: foo, эти thunks не нужны, так как дельта будет легко распознаваться из самого вызова.
Виртуальные функции
Виртуальные функции добавляет целый новый уровень сложности. В примере с множественным наследованием у thunks были фиксированные адреса для вызова; мы просто корректировали это, прежде чем передавать его в уже известную функцию. С виртуальными функциями функция, которую мы вызываем, неизвестна; мы могли бы быть переопределены производным объектом, о котором у нас нет возможности знать во время компиляции, из-за того, что он находится в другой единицы перевода или в библиотеке и т. д.
Это означает, что нам нужна какая-то форма динамической отправки для каждого объекта, имеет практически передовую функцию; многие методы возможны, но реализации C++ имеют тенденцию использовать простой массив указателей на функции или таблицу vtable.Для каждого объекта, который имеет виртуальные функции, мы добавим точку в массив как скрытый член, как правило, на фронте:
class A
{
hidden:
void* vtable;
public:
virtual void foo(void);
}
Добавит стук функцию, которые перенаправляют на виртуальные таблицы
void a_foo(A* this)
{
int vindex = 0;
this->vtable[vindex](this);
}
виртуальных таблицы затем заполняется указателями на функции, которые мы на самом деле хотим назвать:
vtable [0] = & A :: foo_default; // наша базовая эксплоатация foo
В производном классе, если мы хотим переопределить эту виртуальную функцию, все, что нам нужно сделать, это изменить vtable в нашем собственном объекте, указать на новую функцию и переопределить ее в базовом классе, а также:
class B: public A
{
virtual void foo(void);
}
будет делать это в конструкторе:
((A*)this)->vtable[0] = &B::foo;
Наконец, у нас есть поддержка всех форм наследования! Почти.
Virtual Наследования Существует один окончательный нюанс с этой реализацией: если вы по-прежнему позволяют Derived :: Foo будет использоваться, когда то, что вы на самом деле имеете в виду, Base :: Foo, вы получите проблемы с бриллиантом:
class A : public B, public C;
class B : public D;
class C : public D;
A::DFunc(); // Which D?
Эта проблема также может возникать, когда вы используете базовые классы как классы с сохранением состояния или когда вы помещаете функцию, которая должна быть has-a, а не is-a; как правило, это признак необходимости в реструктуризации. Но не всегда.
В C++ это имеет решение, которое не очень элегантно, но работает:
class A : public B, public C;
class B : virtual D;
class C : virtual D;
Это требует тех, кто реализует такие классы и иерархий думать заранее и преднамеренно делают их классы немного медленнее, чтобы поддержать возможное будущее использование. Но это решает проблему.
Как мы можем реализовать это решение?
[ [ D ] [ B ] [ Dptr ] [ C ] [ Dptr ] A ]
Вместо того, чтобы использовать базовый класс непосредственно, как и в обычном наследовании, с виртуальным наследованием мы помещаем все вхождения D через указатель, добавление окольного, в то время как топают многочисленные экземпляры в один. Обратите внимание, что оба B и C имеют свой собственный указатель, что оба указывают на тот же D; это потому, что B и C не знают, являются ли они свободными плавающими копиями или связаны в производных объектах. Те же вызовы должны использоваться для обоих, или виртуальные функции не будут работать должным образом.
Резюме
- Transform метод вызывает в вызовах функций с помощью специального этого параметра в базовых классах
- объекты структуры в памяти, так одиночном наследовании ничем не отличаются ни от наследования
- Добавить санки отрегулировать эти указатели затем вызывают базовые классы для множественного наследования
- Добавьте vtables в классы с виртуальными методами и сделайте все вызовы методов пройденными через vtable в метод (thunk -> vt состоянии -> метод)
- Сделка с виртуальным наследованием через BaseObject указатель на, а не выведем объект вызывает
Все это просто в js.asm.
Вы имеете в виду, как это реализует asmjs? –
Нет, я на самом деле имел в виду, как это должно быть реализовано ** в ** asmjs. – Tim
Вы должны написать свою программу на C/C++, чтобы получить asm.js. –