Как стать автором
Обновить

Комментарии 22

А я пересел на Expression.Lambda<>().Compile() и забыл про IL как страшный сон )

> Разница в скорости разработки – на порядок

Попробуйте и вы, скорость разработки возрастёт на ещё один порядок
Expression.Compile не дружит со штуками типа Mono.Cecil и dnlib. Штуки типа FluentIL умеют с ними интегрироваться.
Вопрос, как именно динамически генерировать код — отдельный разговор. Конечно, можно почти всегда и Expression-ы сделать, или, например, C#. Но не факт, что это будет лучше и быстрее, чем на IL. С Expression'ами у меня тоже большой опыт работы, но у них есть свои проблемы, например, как-то раз я попытался скомпилировать Expression, DebugView которого содержал примерно 900 000 строк. Так стандартный компилятор выделил 24ГБ оперативки, и еще и результат работал очень медленно. В итоге мне пришлось написать свой компилятор, кстати, с использованием GroboIL.
Под какую необходимость требуется делать большие монолитные методы на 10-ки тысяч инструкции и более? Почему не получается разбить на генерацию отдельных методов?
От сгенерированного кода требуются две вещи: чтобы он работал и, если нужно, чтобы он работал быстро. Просто было проще и элегантнее сгенерировать один большой метод, в другом месте, например, пришлось все же разбивать, но там другая история — уперлись в производительность JIT-компилятора.
Отличная штука, спасибо!
Здорово, что эта библиотека умеет через Mono.Cecil работать.
Очень существенное преимущество перед аналогами.
Я наверное очень ленивый или просто глупый, эмиттить руками у меня не хватает способностей.
Поэтому поступаю просто по-студенчески, в лоб — пишу нужный код руками, декомпилирую, смотрю на полученный IL, и дорабатываю его напильником до нужного состояния. Ошибок в сгенерированном коде гарантированно нет, остается не привнести своих, но это намного проще. Это не откровение, многие думаю так делают, но имхо довольно приятный способ. Хотя вот по ссылке товарища kekekeks всё еще приятнее, может и этот способ отжил свое.
Расскажите, пожалуйста, зачем нужна кодогенерация с использованием IL?
Если для оптимизации, почему не удобнее наиоблее критичные вещи реализовать с помощью c/c++ и делать unmanaged вызовы? Ведь это должно работать быстрее.
Какие задачи решаются кодогенерацией?
Другими словами — почему мне нужно тратить время на это? :)
Кодогенерация нужна когда нужно кодогенерировать и лениво писать руками. Каноничный пример — материализаторы сущностей из бд; мапперы, сериализаторы.
В C# есть аж три варианта получения универсального кода: Reflection, Expressions, IL. Рефлексия самая медленная, генерация IL самая быстрая.
Unmanaged не помогает, потому что есть затраты на то, чтобы делать unmanaged-вызовы. Т.е. сгенерированный IL будет быстрее в случае когда у вас есть очень много коротких по длительности методов.
Ну например я писал простенькое расширение для преобразования любой структуры в массив байт и обратно со скоростью сравнимой с плюсовой. Для структуры одного типа сделать это легко, а для дженерика — язык запрещает (но не среда). А раз так, то легко её обмануть, написав на чистом IL:

github.com/Pzixel/StructInterop/blob/master/StructInterop/StructInterop/StructInterop.cs
Все-таки гораздо удобнее использовать высокоуровневые обертки вместо генерации IL.
Чтобы сразу запустить код хватает Expression.Compile(), а чтобы сохранить на диск — что-то типа RunSharp:

AssemblyGen ag = new AssemblyGen("hello.exe");
TypeGen Test = ag.Public.Class("Test");
{
   CodeGen g = Test.Public.Static.Method(typeof(void), "Main", typeof(string[]));
   {
      Operand args = g.Param(0, "args");
      g.Invoke(typeof(Console), "WriteLine", "Hello " + args[0] + "!");
   }
}
ag.Save();

Более сложный пример с генерацией метода с локальными переменными, с циклом и пр.
Взято отсюда: github.com/yallie/RunSharp/blob/master/Examples/08_Indexers.cs

// Demonstrate the FileByteArray class.
// Reverses the bytes in a file.
TypeGen Reverse = ag.Public.Class("Reverse");
{
	CodeGen g = Reverse.Public.Static.Void("Main").Parameter<string[]>("args");
	{
		Operand args = g.Arg("args");

		// Check for arguments.
		g.If(args.ArrayLength() != 1);
		{
			g.WriteLine("Usage : Indexer <filename>");
			g.Return();
		}
		g.End();

		// Check for file existence
		g.If(!Static.Invoke(typeof(File), "Exists", args[0]));
		{
			g.WriteLine("File " + args[0] + " not found.");
			g.Return();
		}
		g.End();

		Operand file = g.Local(Exp.New(FileByteArray, args[0]));
		Operand len = g.Local(file.Property("Length"));

		// Swap bytes in the file to reverse it.
		Operand i = g.Local<long>();
		g.For(i.Assign(0), i < len / 2, i.Increment());
		{
			Operand t = g.Local();

			// Note that indexing the "file" variable invokes the
			// indexer on the FileByteStream class, which reads
			// and writes the bytes in the file.
			g.Assign(t, file[i]);
			g.Assign(file[i], file[len - i - 1]);
			g.Assign(file[len - i - 1], t);
		}
		g.End();

		g.Invoke(file, "Close");
	}
}

Чтобы сохранить на диск можно использовать извращения вроде CSharpCodeProvider
CSharpCodeProvider вызывает внешний компилятор csc.exe, чтобы скомпилировать файл.
А у нас тут вроде обсуждается генерация кода на лету без использования компилятора.
В таком случае есть параметр `AssemblyBuilderAccess.DynamicAssembly`, который думаю внутри этой штуки и вызывается :)

Только сегодня дочитал до этой статьи. У вас очень классная библиотека, просто замечательная. Пишем свой компилятор, и нам ваша либа уже пригодилась.


Жалко только, что вы название сменили. GroboIL, по-моему, намного круче, чем GrEmit ;)

Рад, что библиотека помогла) А название GrEmit — потому что по замыслу там не только GroboIL, но и еще что-то будет. Например, сейчас я пытаюсь придумать, как сделать проще ассемблерные вставки в C#, уж очень муторно это руками делать)

Охохо, вот это очень интересно! А, вроде, Microsoft же грозился сам запилить IL-вставки в виде атрибутов? Или у вас там будет настоящий ассемблер?

Да, настоящий ассемблер. Иногда приходится так глубоко опускаться, в основном, конечно, просто for fun, но бывали задачи, где ассемблер за счет SSE инструкций дал большой прирост. И писать dll на асме, а потом ее вызывать через PInvoke — не очень удобно, плюс затраты на переключение контесктов managed/unmanaged.

Ага, а вы планируете вставлять туда настоящий машинный код, наподобие того, как это делает компилятор C++/CLI? Слушайте, это так круто! Это будет в открытом доступе?

Смешивать в одном методе asm и C# — это было бы супер, но вряд ли получится, слишком сложно, а вот сделать весь метод на ассемблере — это возможно. Да, я планирую это сделать частью GrEmit, когда сделаю.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий