Re[9]: Базовый вопрос по наследованию
От: MozgC США http://nightcoder.livejournal.com
Дата: 05.02.11 21:20
Оценка:
Здравствуйте, 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.

Ага, тут я затупил, ерунду написал
Re[10]: Базовый вопрос по наследованию
От: Lloyd Россия  
Дата: 05.02.11 21:34
Оценка: +1
Здравствуйте, 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" вообще не воспроизвадится. Код
void Test(Derived obj)
{
    obj.Test();
}

все равно на уровне IL-а транслируется в
callvirt   instance void ConsoleApplication5.Base::Test()

Т.е. этот "walks down" по факту делается не JIT-ом, а компилятором C#/

L>>JIT-у достаточно знать метод, который надо вызвать, а этот метод в явном виде присутствует в IL-е.


MC>При вызове невиртуальных методов jit явно прописывает адрес метода в сгенерированном коде. А этот адрес нужно получить через таблицу методов. А таблица методов получается через type object.
Re[11]: Базовый вопрос по наследованию
От: MozgC США http://nightcoder.livejournal.com
Дата: 05.02.11 21:47
Оценка:
Здравствуйте, 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 не доверяет полностью компилятору, а проверяет, что метод точно существует у указанного типа.
Re[8]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 05.02.11 21:55
Оценка:
Здравствуйте, 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 невиртуальные методы в таблицы, но я их пока не вижу
"Нормальные герои всегда идут в обход!"
Re[10]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 05.02.11 22:04
Оценка:
Здравствуйте, samius, Вы писали:

S>Выглядит нелепо, т.к. я еще застал те времена, когда вызов невиртуального метода у null прокатывал. А общение с дотнетом я начал году в 2003-м. Когда конкретно застал такое поведение — точно не помню, но сейчас уже не канает.


Эти времена никуда не ушли, в неуправляемых языках и сейчас так-же Вызов невиртуального метода — это простой call на некоторый код, лежащий в некотором точно известном месте исполняемого образа, с передачей одного "скрытого" параметра — this.
"Нормальные герои всегда идут в обход!"
Re[11]: Базовый вопрос по наследованию
От: samius Япония http://sams-tricks.blogspot.com
Дата: 05.02.11 22:06
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, samius, Вы писали:


S>>Выглядит нелепо, т.к. я еще застал те времена, когда вызов невиртуального метода у null прокатывал. А общение с дотнетом я начал году в 2003-м. Когда конкретно застал такое поведение — точно не помню, но сейчас уже не канает.


JR>Эти времена никуда не ушли, в неуправляемых языках и сейчас так-же Вызов невиртуального метода — это простой call на некоторый код, лежащий в некотором точно известном месте исполняемого образа, с передачей одного "скрытого" параметра — this.

Вообще я говорил конкретно о дотнете.
И кстати, за все неуправляемые говорить не буду, но в C++ для такой ситуации будет UB.
Re[9]: Базовый вопрос по наследованию
От: MozgC США http://nightcoder.livejournal.com
Дата: 05.02.11 22:08
Оценка:
В общем, походу, зря подняли бучу, и Рихтер прав.

Делаем такой проектик:

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() { }
        }
    }
}

Компилируем, проверяем в ILDasm или Reflector'е:

    L_0000: nop 
    L_0001: newobj instance void JitTest.Program/Manager::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: callvirt instance int32 JitTest.Program/EmployeeBase::GetYearsEmployed()

Берем ILDasm и делаем dump в IL.
В открываем исходник в IL и меняем

callvirt instance int32 JitTest.Program/EmployeeBase::GetYearsEmployed()
на
callvirt instance int32 JitTest.Program/Employee::GetYearsEmployed()

Компилируем с помощью ILAsm.

Проверяем в ILDasm или Reflector'е:

    L_0000: nop 
    L_0001: newobj instance void JitTest.Program/Manager::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: callvirt instance int32 JitTest.Program/Employee::GetYearsEmployed()

Запускаем, видим что нормально вызывается метод класса EmployeeBase.

Т.е. JIT все-таки в рантайме прошелся по иерархии и нашел класс в котором определен метод и сгенерировал правильный вызов.
Re[9]: Базовый вопрос по наследованию
От: samius Япония http://sams-tricks.blogspot.com
Дата: 05.02.11 22:09
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Впрочем, может быть были какие-то причины сводить в dotnet невиртуальные методы в таблицы, но я их пока не вижу


Таблица нужна хотя бы для того, что бы не JIT-ить вызываемые методы при каждом обращении из JIT-компилируемого в текущий момент метода.
Re[12]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 05.02.11 22:11
Оценка:
Здравствуйте, samius, Вы писали:

S>И кстати, за все неуправляемые говорить не буду, но в C++ для такой ситуации будет UB.


Оно будет, если попытаться использовать this, например, попытаться к полям класса. Если не обращаться, то нормально пройдёт.
"Нормальные герои всегда идут в обход!"
Re[13]: Базовый вопрос по наследованию
От: samius Япония http://sams-tricks.blogspot.com
Дата: 05.02.11 22:12
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, samius, Вы писали:


S>>И кстати, за все неуправляемые говорить не буду, но в C++ для такой ситуации будет UB.


JR>Оно будет, если попытаться использовать this, например, попытаться к полям класса. Если не обращаться, то нормально пройдёт.

Так написано в стандарте?
Re[10]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 05.02.11 22:17
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>В общем, походу, зря подняли бучу, и Рихтер прав.


MC>Т.е. JIT все-таки в рантайме прошелся по иерархии и нашел класс в котором определен метод и сгенерировал правильный вызов.


С тем-же успехом необходимую правку мог сделать ILAsm
"Нормальные герои всегда идут в обход!"
Re[14]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 05.02.11 22:18
Оценка:
Здравствуйте, samius, Вы писали:

S>Так написано в стандарте?


Нет, а что? Мы разве стандарт обсуждаем? Я как-то не заметил
"Нормальные герои всегда идут в обход!"
Re[15]: Базовый вопрос по наследованию
От: samius Япония http://sams-tricks.blogspot.com
Дата: 05.02.11 22:26
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, samius, Вы писали:


S>>Так написано в стандарте?


JR>Нет, а что? Мы разве стандарт обсуждаем? Я как-то не заметил


Когда я написал что вызов метода экземпляра у null-а приведет к UB, я имел в виду именно то, что поведение в таком случае неопределено стандартом (или я ошибаюсь?).
Обсуждать сам стандарт C++ я не собирался, т.к. не открывал его.
Re[11]: Базовый вопрос по наследованию
От: vf  
Дата: 05.02.11 22:28
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

Вот здесь есть косвеная информация...

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.

Re[11]: Базовый вопрос по наследованию
От: MozgC США http://nightcoder.livejournal.com
Дата: 05.02.11 22:30
Оценка: +1
Здравствуйте, Jolly Roger, Вы писали:

JR>С тем-же успехом необходимую правку мог сделать ILAsm


А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?
Re[12]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 06.02.11 04:02
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?


Может быть. Я, признаться, не вникал в тонкости механики взаимодействия сборок. Можно предположить, что в сборке создаётся нечто вроде таблицы экспорта, через которую такое взаимодействие и происходит. Тогда внутренние изменения не скажутся на внешнем коде, и подобные вопросы можно разрешить на этапе загрузки исполняемого образа, при подгрузке необходимых либ в АП процесса.

С другой стороны, тут есть ещё один момент, который я прошлой ночью упустил — рефлексия. (Ну очень спать хотелось ) Для неё необходимо, чтобы информация о всех членах класса, в том числе невиртуальных методах, была доступна в рантайм. В принципе, JIT может ей воспользоваться при необходимости. Но с другой стороны, ей-же может воспользоваться и C# компилятор, то есть такие вопросы можно вроде как разрешить на этапе компиляции в IL. Иначе у меня не получается непротиворечиво объяснить тот листинг, что я приводил здесь
Автор: Jolly Roger
Дата: 05.02.11
. Ведь в методе Test нет никаких упоминаний BaseObj, однако в IL-листинге он присутствует. Это, по-моему, может означать только одно — C#-компилятор произвёл поиск метода вверх по иерархии и подставил в результирующий IL код его адрес, этот адрес мы и видим в виде метки BaseObj::Sum. Иначе у меня как-то не срастается

А вот тот пример с дизассемблированием, правкой и повторной компиляцией с помощью ILasm — а что покажет ILDasm, если ему скормить результат работы ILAsm? Там сохранится внесённое вручную Employee::GetYearsEmployed, или опять оказывается исходная EmployeeBase::GetYearsEmployed?
"Нормальные герои всегда идут в обход!"
Re[16]: Базовый вопрос по наследованию
От: Jolly Roger  
Дата: 06.02.11 04:21
Оценка:
Здравствуйте, samius, Вы писали:

S>Когда я написал что вызов метода экземпляра у null-а приведет к UB, я имел в виду именно то, что поведение в таком случае неопределено стандартом (или я ошибаюсь?).

S>Обсуждать сам стандарт C++ я не собирался, т.к. не открывал его.

Нет конечно, не ошибаетесь, просто я, видимо, неверно Вас понял Просто смотрел "под углом" обсуждаемой темы, а понимание таких недокументированных тонкостей как раз помогает понять и поведение компилятора.
"Нормальные герои всегда идут в обход!"
Re[12]: Базовый вопрос по наследованию
От: vf  
Дата: 06.02.11 10:40
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?


А что будет в противоположном случае, когда в другой сборке метод переопределили в наследнике.
Без перекомпиляции приложения, до этого компилятор сам нашел базовый класс и вызывает Base::Method, а сейчас должен вызвать Derived::Method. Без перекомпиляции вызовет не тот метод?!
Re[13]: Базовый вопрос по наследованию
От: vf  
Дата: 06.02.11 11:01
Оценка:
Здравствуйте, 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.

Re[13]: Базовый вопрос по наследованию
От: MozgC США http://nightcoder.livejournal.com
Дата: 06.02.11 11:18
Оценка:
Здравствуйте, vf, Вы писали:

MC>>А что если бы эта иерархия находилась в другой сборке, и потом ее заменили на новую (без перекомпиляции клиента), в которой метод перенесен в суперкласс? Т.е. возможно джит выполняет функцию дополнительной проверки и поиска на такие случаи?


vf>А что будет в противоположном случае, когда в другой сборке метод переопределили в наследнике.

vf>Без перекомпиляции приложения, до этого компилятор сам нашел базовый класс и вызывает Base::Method, а сейчас должен вызвать Derived::Method. Без перекомпиляции вызовет не тот метод?!

В таком случае без перекомпиляции вызовется метод базового класса. Видимо jit просто берет метод указанный в IL и, если он не найден в указанном классе, то идет вверх и ищет где он. А в данном примере в IL останется вызов базового класса, jit увидит, что метод есть у указанного класса и не проверит, что метод уже переопределен в дочернем.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.