Генерация и выполнение IL-кода — различия между версиями
Материал из Вики ИТ мехмата ЮФУ
Admin (обсуждение | вклад) (→Генерация IL-кода в .NET) |
Admin (обсуждение | вклад) (→Дополнительные задания (5 баллов)) |
||
(не показано 26 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
+ | __NOTOC__ | ||
+ | [[Страница_курса_%22Методы_построения_компиляторов%22| К основной странице курса]] | ||
+ | |||
+ | ===Код проекта=== | ||
+ | Комплект для практического занятия [http://pascalabc.net/downloads/CompilerConstruction/SimpleLanguage3.zip скачиваем отсюда]. | ||
+ | |||
===Генерация IL-кода в .NET=== | ===Генерация IL-кода в .NET=== | ||
====Пример 1==== | ====Пример 1==== | ||
− | + | Рассмотрим следующий код: | |
<source lang="Csharp"> | <source lang="Csharp"> | ||
{ | { | ||
Строка 38: | Строка 44: | ||
} | } | ||
</source> | </source> | ||
+ | |||
====Комментарии к примеру 1==== | ====Комментарии к примеру 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 - выполняет возврат из текущего метода | ||
+ | **OpCodes.Br - обеспечивает безусловный переход | ||
+ | **OpCodes.Blt - обеспечивает переход если первый операнд меньше второго | ||
===Визитор генерации кода=== | ===Визитор генерации кода=== | ||
Строка 265: | Строка 289: | ||
} | } | ||
</source> | </source> | ||
+ | |||
+ | ===Основные задания (8 баллов)=== | ||
+ | *Реализовать операции div и mod целочисленного деления | ||
+ | |||
+ | ===Дополнительные задания (8 баллов)=== | ||
+ | *Реализовать оператор if (полную и неполную формы): if expr then statement и if expr then statement else statement (3 балла) | ||
+ | |||
+ | *Реализовать оператор while expr do statement (3 балла) | ||
+ | *Реализовать оператор repeat stlist until expr (2 балла) | ||
+ | |||
+ | expr - целого типа, 0 означает False, не 0 - True |
Текущая версия на 10:09, 18 декабря 2015
Код проекта
Комплект для практического занятия скачиваем отсюда.
Генерация 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 - выполняет возврат из текущего метода
- OpCodes.Br - обеспечивает безусловный переход
- OpCodes.Blt - обеспечивает переход если первый операнд меньше второго
Визитор генерации кода
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;
}
}
}
Основные задания (8 баллов)
- Реализовать операции div и mod целочисленного деления
Дополнительные задания (8 баллов)
- Реализовать оператор if (полную и неполную формы): if expr then statement и if expr then statement else statement (3 балла)
- Реализовать оператор while expr do statement (3 балла)
- Реализовать оператор repeat stlist until expr (2 балла)
expr - целого типа, 0 означает False, не 0 - True