Генерация и выполнение IL-кода
Материал из Вики ИТ мехмата ЮФУ
Содержание
Код проекта
Комплект для практического занятия скачиваем отсюда.
Генерация IL-кода в .NET
Пример 1
Рассмотрим следующий код:
{
int x,y;
x = 123;
y = x;
Console.WriteLine(y);
}
Сгенерируем для него IL-код в виде динамического метода и выполним его.
using System.Reflection.Emit;
class Program
{
static void Main(string[] args)
{
DynamicMethod dyn = new DynamicMethod("My", null, null, typeof(void));
ILGenerator gen = dyn.GetILGenerator();
LocalBuilder x = gen.DeclareLocal(typeof(int));
LocalBuilder y = gen.DeclareLocal(typeof(int));
gen.Emit(OpCodes.Ldc_I4,123); // x = 123
gen.Emit(OpCodes.Stloc,x);
gen.Emit(OpCodes.Ldloc,x); // y = x + 1
gen.Emit(OpCodes.Ldc_I4,1);
gen.Emit(OpCodes.Add);
gen.Emit(OpCodes.Stloc,y);
gen.EmitWriteLine(y);
gen.Emit(OpCodes.Ret);
dyn.Invoke(null, null);
}
}
Комментарии к примеру 1
- Вначале создается динамический метод, по нему - генератор IL-кода.
- Основная команда для создания команды IL-кода:
ILGenerator.Emit(OpCodes,параметры)
- IL-код основан на стековой модели, регистры отсутствуют. Чтобы произвести вычисления. значения вначале кладутся на стек, а потом над верхними значениями совершается операция, при этом они снимаются со стека и на стек кладется результат.
- OpCodes содержит около сотни кодов команд кода IL (Intermediate Language). Основные:
- OpCodes.Ldc_I4 - загружает в стек целое значение
- OpCodes.Ldc_R8 - загружает в стек вещественное значение
- OpCodes.Ldloc - загружает в стек локальную переменную
- OpCodes.Stloc - извлекает из стека верхнее значение и помещает его в локальную переменную
- OpCodes.Ldarg - загружает в стек параметр функции
- OpCodes.StArg - извлекает из стека верхнее значение и помещает его в параметр функции
- OpCodes.Add - складывает два значения на вершине стека и помещает результат в стек вычислений
- OpCodes.Sub - вычитает два значения на вершине стека и помещает результат в стек вычислений
- OpCodes.Mult - умножает два значения на вершине стека и помещает результат в стек вычислений
- OpCodes.Div - делит два значения на вершине стека и помещает результат в стек вычислений
- OpCodes.Ret - выполняет возврат из текущего метода
Визитор генерации кода
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProgramTree;
using System.Reflection.Emit;
namespace SimpleLang.Visitors
{
class GenCodeVisitor: Visitor
{
private Dictionary<string, LocalBuilder> vars = new Dictionary<string, LocalBuilder>();
private GenCodeCreator genc;
public GenCodeVisitor()
{
genc = new GenCodeCreator();
}
public override void VisitIdNode(IdNode id)
{
// Этот Visit не вызывается если переменная стоит слева от оператора присваивания !
// Т.е. он вызывается только если id находится в выражении, а значит, мы просто кладем его значение на стек!
genc.Emit(OpCodes.Ldloc, vars[id.Name]);
}
public override void VisitIntNumNode(IntNumNode num)
{
genc.Emit(OpCodes.Ldc_I4, num.Num);
}
public override void VisitBinOpNode(BinOpNode binop)
{
binop.Left.Visit(this);
binop.Right.Visit(this);
switch (binop.Op)
{
case '+':
genc.Emit(OpCodes.Add);
break;
case '-':
genc.Emit(OpCodes.Sub);
break;
case '*':
genc.Emit(OpCodes.Mul);
break;
case '/':
genc.Emit(OpCodes.Div);
break;
}
}
public override void VisitAssignNode(AssignNode a)
{
a.Expr.Visit(this);
genc.Emit(OpCodes.Stloc, vars[a.Id.Name]);
}
public override void VisitCycleNode(CycleNode c)
{
var i = genc.DeclareLocal(typeof(int)); // переменная цикла cycle
c.Expr.Visit(this); // сгенерировать команды, связанные с вычислением количества итераций цикла
genc.Emit(OpCodes.Stloc, i); // i := кво итераций
Label startLoop = genc.DefineLabel();
Label endLoop = genc.DefineLabel();
genc.MarkLabel(startLoop);
genc.Emit(OpCodes.Ldloc, i);
genc.Emit(OpCodes.Ldc_I4_0);
genc.Emit(OpCodes.Ble, endLoop); // if i<=0 then goto endLoop
c.Stat.Visit(this); // выполнить тело цикла
genc.Emit(OpCodes.Ldloc, i); // положить i на стек
genc.Emit(OpCodes.Ldc_I4_1); // положить 1 на стек
genc.Emit(OpCodes.Sub);
genc.Emit(OpCodes.Stloc, i); // i := i - 1;
genc.Emit(OpCodes.Br, startLoop);
genc.MarkLabel(endLoop);
}
public override void VisitBlockNode(BlockNode bl)
{
foreach (var st in bl.StList)
st.Visit(this);
}
public override void VisitWriteNode(WriteNode w)
{
w.Expr.Visit(this);
genc.EmitWriteLine();
}
public override void VisitVarDefNode(VarDefNode w)
{
foreach (var v in w.vars)
vars[v.Name] = genc.DeclareLocal(typeof(int));
}
public void EndProgram()
{
genc.EndProgram();
}
public void RunProgram()
{
genc.RunProgram();
}
public void PrintCommands()
{
foreach (var s in genc.commands)
Console.WriteLine(s);
}
}
}
Генератор кода
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
namespace SimpleLang.Visitors
{
class GenCodeCreator
{
private DynamicMethod dyn;
private ILGenerator gen;
private bool write_commands = true;
private static MethodInfo writeLineInt = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) });
public List<string> commands = new List<string>();
public GenCodeCreator()
{
dyn = new DynamicMethod("My", null, null, typeof(void));
gen = dyn.GetILGenerator();
}
public void Emit(OpCode op)
{
gen.Emit(op);
if (write_commands)
commands.Add(op.ToString());
}
public void Emit(OpCode op, int num)
{
gen.Emit(op,num);
if (write_commands)
commands.Add(op.ToString() + " " + num);
}
public void Emit(OpCode op, LocalBuilder lb)
{
gen.Emit(op, lb);
if (write_commands)
commands.Add(op.ToString() + " var" + lb.LocalIndex);
}
public void Emit(OpCode op, Label l)
{
gen.Emit(op, l);
if (write_commands)
commands.Add(op.ToString() + " Label" + l.GetHashCode());
}
public LocalBuilder DeclareLocal(Type t)
{
var lb = gen.DeclareLocal(t);
if (write_commands)
commands.Add("DeclareLocal " + "var" + lb.LocalIndex + ": " + t);
return lb;
}
public Label DefineLabel()
{
var l = gen.DefineLabel();
if (write_commands)
commands.Add("DefineLabel" + " Label" + l.GetHashCode());
return l;
}
public void MarkLabel(Label l)
{
gen.MarkLabel(l);
if (write_commands)
commands.Add("MarkLabel" + " Label" + l.GetHashCode());
}
public void EmitWriteLine()
{
gen.Emit(OpCodes.Call, writeLineInt);
if (write_commands)
commands.Add("WriteLine");
}
public void EndProgram()
{
gen.Emit(OpCodes.Ret);
}
public void RunProgram()
{
dyn.Invoke(null, null);
}
public void WriteCommandsOn()
{
write_commands = true;
}
public void WriteCommandsOff()
{
write_commands = false;
}
}
}