Здравствуйте, Lloyd, Вы писали:
MC>>У меня такой вопрос: а как JIT узнает адрес объекта "type object" для базового типа, в котором реально определен метод? L>Кто такой "type object"?
См. выше в теме, я уже писал.
L>И зачем он нужен?
Чтобы jit мог обратиться к соответствуешей таблице методов, повторю еще раз цитату:
(If the Employee type didn’t define the method being called, the JIT compiler walks down the class hierarchy toward Object looking for this method. It can do this because each type object has a field in it that refers to its base type; this information is not shown in the figures.) Then, the JIT compiler locates the entry in the type object’s method table that refers to the method being called, JITs the method (if necessary), and then calls the JITted code.
L>JIT-у достаточно знать метод, который надо вызвать, а этот метод в явном виде присутствует в IL-е.
При вызове невиртуальных методов jit явно прописывает адрес метода в сгенерированном коде. А этот адрес нужно получить через таблицу методов. А таблица методов получается через type object.
MC>>В рантайме же у JIT'а будет иметься только объект дочернего типа, и ссылка на его type object. L>У JIT-а нет никакого объекта, он не знает с каким именно объектом будет вызван метод. На вход он получает только IL.
Здравствуйте, MozgC, Вы писали:
MC>>>У меня такой вопрос: а как JIT узнает адрес объекта "type object" для базового типа, в котором реально определен метод? L>>Кто такой "type object"?
MC>См. выше в теме, я уже писал.
L>>И зачем он нужен?
MC>Чтобы jit мог обратиться к соответствуешей таблице методов, повторю еще раз цитату:
Тогда я не понимаю, почему тебя вообще мог заинтересовать вопрос "как". Очевидно, рантайм знает, как по типу, указаному в IL-е найти соответствующий type object.
MC>
(If the Employee type didn’t define the method being called, the JIT compiler walks down the class hierarchy toward Object looking for this method. It can do this because each type object has a field in it that refers to its base type; this information is not shown in the figures.) Then, the JIT compiler locates the entry in the type object’s method table that refers to the method being called, JITs the method (if necessary), and then calls the JITted code.
Какая-то мутная формулировка. Тезис про "type didn’t define the method" вообще не воспроизвадится. Код
Т.е. этот "walks down" по факту делается не JIT-ом, а компилятором C#/
L>>JIT-у достаточно знать метод, который надо вызвать, а этот метод в явном виде присутствует в IL-е.
MC>При вызове невиртуальных методов jit явно прописывает адрес метода в сгенерированном коде. А этот адрес нужно получить через таблицу методов. А таблица методов получается через type object.
Здравствуйте, Lloyd, Вы писали:
L>Тогда я не понимаю, почему тебя вообще мог заинтересовать вопрос "как". Очевидно, рантайм знает, как по типу, указаному в IL-е найти соответствующий type object.
Видимо да.
MC>>
(If the Employee type didn’t define the method being called, the JIT compiler walks down the class hierarchy toward Object looking for this method. It can do this because each type object has a field in it that refers to its base type; this information is not shown in the figures.) Then, the JIT compiler locates the entry in the type object’s method table that refers to the method being called, JITs the method (if necessary), and then calls the JITted code.
L>Какая-то мутная формулировка. Тезис про "type didn’t define the method" вообще не воспроизвадится. L>Т.е. этот "walks down" по факту делается не JIT-ом, а компилятором C#/
Не факт. Возможно jit не доверяет полностью компилятору, а проверяет, что метод точно существует у указанного типа.
Здравствуйте, MozgC, Вы писали:
MC>У меня такой вопрос: а как JIT узнает адрес объекта "type object" для базового типа, в котором реально определен метод? В рантайме же у JIT'а будет иметься только объект дочернего типа, и ссылка на его type object. Поэтому JIT'у придется идти по иерархии, чтобы найти type object для базового типа, чтобы уже обратиться к method table базового типа и вызвать этот метод. Тогда может Рихтер прав? MC>Или JIT может более коротким путем выйти на type object для базового типа в котором реализован метод?
Я думаю, Рихтер просто постарался упростить описание. В рантайме JIT требуется адрес места, где находится IL нужных методов, а этот адрес отлично известен на этапе компиляции. И он непосредственно задаётся компилятором в IL, как и показывает приведённый мною листинг. То есть IL сразу получает указание — "здесь вызывать такой-то метод такого-то базового класса". Где лежит IL этого метода тоже уже известно С#-компилятору, зачем ещё что-то искать? Вообще, для невиртуальных методов нет нужды в каких-либо таблицах, их код может располагаться в любом месте исполняемого образа, хранить указатель на них в рантайме нет необходимости, так как этот адрес подставляется в место вызова на этапе компиляции. Вряд-ли JIT что-то меняет здесь кардинально, ибо бессмысленно. Таблицы методов имеют смысл для виртуальных и интерфейсных методов, так как позволяют создать необходимый для них дополнительный уровень косвенности. Для невиртуальных-же методов этот уровень не нужен, и даже вреден.
Иначе говоря, как-то в рантайм физически связывать тот самый "type object" (по сути — метаданные класса) с невиртуальными методами этого класса нет нужды, а значит и нет смысла перекладывать поиск точек входа таких методов на рантайм, на JIT в том числе.
Впрочем, может быть были какие-то причины сводить в dotnet невиртуальные методы в таблицы, но я их пока не вижу
Здравствуйте, samius, Вы писали:
S>Выглядит нелепо, т.к. я еще застал те времена, когда вызов невиртуального метода у null прокатывал. А общение с дотнетом я начал году в 2003-м. Когда конкретно застал такое поведение — точно не помню, но сейчас уже не канает.
Эти времена никуда не ушли, в неуправляемых языках и сейчас так-же Вызов невиртуального метода — это простой call на некоторый код, лежащий в некотором точно известном месте исполняемого образа, с передачей одного "скрытого" параметра — this.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, samius, Вы писали:
S>>Выглядит нелепо, т.к. я еще застал те времена, когда вызов невиртуального метода у null прокатывал. А общение с дотнетом я начал году в 2003-м. Когда конкретно застал такое поведение — точно не помню, но сейчас уже не канает.
JR>Эти времена никуда не ушли, в неуправляемых языках и сейчас так-же Вызов невиртуального метода — это простой call на некоторый код, лежащий в некотором точно известном месте исполняемого образа, с передачей одного "скрытого" параметра — this.
Вообще я говорил конкретно о дотнете.
И кстати, за все неуправляемые говорить не буду, но в C++ для такой ситуации будет UB.
using System;
namespace JitTest
{
class Program
{
static void Main(string[] args)
{
Employee e;
e = new Manager();
int years = e.GetYearsEmployed();
Console.ReadKey();
}
public class EmployeeBase
{
public int GetYearsEmployed()
{
Console.WriteLine("Method in EmployeeBase called.");
return 1;
}
}
public class Employee : EmployeeBase
{
public void SomeOtherMethod() { }
}
public class Manager : Employee
{
public void GenProgressReport() { }
}
}
}
Здравствуйте, Jolly Roger, Вы писали:
JR>Впрочем, может быть были какие-то причины сводить в dotnet невиртуальные методы в таблицы, но я их пока не вижу
Таблица нужна хотя бы для того, что бы не JIT-ить вызываемые методы при каждом обращении из JIT-компилируемого в текущий момент метода.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, samius, Вы писали:
S>>И кстати, за все неуправляемые говорить не буду, но в C++ для такой ситуации будет UB.
JR>Оно будет, если попытаться использовать this, например, попытаться к полям класса. Если не обращаться, то нормально пройдёт.
Так написано в стандарте?
Здравствуйте, MozgC, Вы писали:
MC>В общем, походу, зря подняли бучу, и Рихтер прав.
MC>Т.е. JIT все-таки в рантайме прошелся по иерархии и нашел класс в котором определен метод и сгенерировал правильный вызов.
С тем-же успехом необходимую правку мог сделать ILAsm
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, samius, Вы писали:
S>>Так написано в стандарте?
JR>Нет, а что? Мы разве стандарт обсуждаем? Я как-то не заметил
Когда я написал что вызов метода экземпляра у null-а приведет к UB, я имел в виду именно то, что поведение в таком случае неопределено стандартом (или я ошибаюсь?).
Обсуждать сам стандарт C++ я не собирался, т.к. не открывал его.
MissingMethodException is thrown if a non-static method with the indicated name and signature could not be found in the class associated with obj or any of its base classes. This is typically detected when Microsoft Intermediate Language (MSIL) instructions are converted to native code, rather than at runtime.
Здравствуйте, Jolly Roger, Вы писали:
JR>С тем-же успехом необходимую правку мог сделать ILAsm
А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?
Здравствуйте, MozgC, Вы писали:
MC>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?
Может быть. Я, признаться, не вникал в тонкости механики взаимодействия сборок. Можно предположить, что в сборке создаётся нечто вроде таблицы экспорта, через которую такое взаимодействие и происходит. Тогда внутренние изменения не скажутся на внешнем коде, и подобные вопросы можно разрешить на этапе загрузки исполняемого образа, при подгрузке необходимых либ в АП процесса.
С другой стороны, тут есть ещё один момент, который я прошлой ночью упустил — рефлексия. (Ну очень спать хотелось ) Для неё необходимо, чтобы информация о всех членах класса, в том числе невиртуальных методах, была доступна в рантайм. В принципе, JIT может ей воспользоваться при необходимости. Но с другой стороны, ей-же может воспользоваться и C# компилятор, то есть такие вопросы можно вроде как разрешить на этапе компиляции в IL. Иначе у меня не получается непротиворечиво объяснить тот листинг, что я приводил здесь
. Ведь в методе Test нет никаких упоминаний BaseObj, однако в IL-листинге он присутствует. Это, по-моему, может означать только одно — C#-компилятор произвёл поиск метода вверх по иерархии и подставил в результирующий IL код его адрес, этот адрес мы и видим в виде метки BaseObj::Sum. Иначе у меня как-то не срастается
А вот тот пример с дизассемблированием, правкой и повторной компиляцией с помощью ILasm — а что покажет ILDasm, если ему скормить результат работы ILAsm? Там сохранится внесённое вручную Employee::GetYearsEmployed, или опять оказывается исходная EmployeeBase::GetYearsEmployed?
Здравствуйте, samius, Вы писали:
S>Когда я написал что вызов метода экземпляра у null-а приведет к UB, я имел в виду именно то, что поведение в таком случае неопределено стандартом (или я ошибаюсь?). S>Обсуждать сам стандарт C++ я не собирался, т.к. не открывал его.
Нет конечно, не ошибаетесь, просто я, видимо, неверно Вас понял Просто смотрел "под углом" обсуждаемой темы, а понимание таких недокументированных тонкостей как раз помогает понять и поведение компилятора.
Здравствуйте, MozgC, Вы писали:
MC>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?
А что будет в противоположном случае, когда в другой сборке метод переопределили в наследнике.
Без перекомпиляции приложения, до этого компилятор сам нашел базовый класс и вызывает Base::Method, а сейчас должен вызвать Derived::Method. Без перекомпиляции вызовет не тот метод?!
Здравствуйте, Jolly Roger, Вы писали:
JR>Ведь в методе Test нет никаких упоминаний BaseObj, однако в IL-листинге он присутствует.
Судя повсему тот класс который указан в IL предназначен только для пользователя, для компилятора он не нужен и не несет никакой информации.
That is, the method is chosen based on the runtime type of obj rather than the compile-time class visible in the method pointer.
То есть тип объекта, JIT-компилятору (как параметр) не нужен, он его смотрит по obj.
callvirt method
Calls a specific method associated with obj.
The stack transitional behavior, in sequential order, is:
1. An object reference obj is pushed onto the stack.
2. Method arguments arg1 through argN are pushed onto the stack.
3. Method arguments arg1 through argN and the object reference obj are popped from the stack; the method call is performed with these arguments and control is transferred to the method in obj referred to by the method metadata token. When complete, a return value is generated by the callee method and sent to the caller.
4. The return value is pushed onto the stack.
Здравствуйте, vf, Вы писали:
MC>>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?
vf>А что будет в противоположном случае, когда в другой сборке метод переопределили в наследнике. vf>Без перекомпиляции приложения, до этого компилятор сам нашел базовый класс и вызывает Base::Method, а сейчас должен вызвать Derived::Method. Без перекомпиляции вызовет не тот метод?!
В таком случае без перекомпиляции вызовется метод базового класса. Видимо jit просто берет метод указанный в IL и, если он не найден в указанном классе, то идет вверх и ищет где он. А в данном примере в IL останется вызов базового класса, jit увидит, что метод есть у указанного класса и не проверит, что метод уже переопределен в дочернем.