Генерация и выполнение 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 - выполняет возврат из текущего метода
    • 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