using System; using System.Collections; using System.IO; using System.Reflection; using System.Reflection.Emit; /// /// The code generator for the Z# compiler. /// public class Code { //----- shortcuts to the relevant IL instruction codes of System.Reflection.Emit.OpCodes public static readonly OpCode // loading method arguments LDARG0 = OpCodes.Ldarg_0, LDARG1 = OpCodes.Ldarg_1, LDARG2 = OpCodes.Ldarg_2, LDARG3 = OpCodes.Ldarg_3, LDARG = OpCodes.Ldarg_S, // storing in method argument slots STARG = OpCodes.Starg_S, // loading local variables LDLOC0 = OpCodes.Ldloc_0, LDLOC1 = OpCodes.Ldloc_1, LDLOC2 = OpCodes.Ldloc_2, LDLOC3 = OpCodes.Ldloc_3, LDLOC = OpCodes.Ldloc_S, // storing local variables STLOC0 = OpCodes.Stloc_0, STLOC1 = OpCodes.Stloc_1, STLOC2 = OpCodes.Stloc_2, STLOC3 = OpCodes.Stloc_3, STLOC = OpCodes.Stloc_S, // loading constant values LDNULL = OpCodes.Ldnull, LDCM1 = OpCodes.Ldc_I4_M1, LDC0 = OpCodes.Ldc_I4_0, LDC1 = OpCodes.Ldc_I4_1, LDC2 = OpCodes.Ldc_I4_2, LDC3 = OpCodes.Ldc_I4_3, LDC4 = OpCodes.Ldc_I4_4, LDC5 = OpCodes.Ldc_I4_5, LDC6 = OpCodes.Ldc_I4_6, LDC7 = OpCodes.Ldc_I4_7, LDC8 = OpCodes.Ldc_I4_8, LDC = OpCodes.Ldc_I4, // stack manipulation DUP = OpCodes.Dup, POP = OpCodes.Pop, // method invocation CALL = OpCodes.Call, RET = OpCodes.Ret, // branching BR = OpCodes.Br, BEQ = OpCodes.Beq, BGE = OpCodes.Bge, BGT = OpCodes.Bgt, BLE = OpCodes.Ble, BLT = OpCodes.Blt, BNE = OpCodes.Bne_Un, // arithmetics ADD = OpCodes.Add, SUB = OpCodes.Sub, MUL = OpCodes.Mul, DIV = OpCodes.Div, REM = OpCodes.Rem, NEG = OpCodes.Neg, // field access LDFLD = OpCodes.Ldfld, STFLD = OpCodes.Stfld, LDSFLD = OpCodes.Ldsfld, STSFLD = OpCodes.Stsfld, // object creation NEWOBJ = OpCodes.Newobj, NEWARR = OpCodes.Newarr, // array handling LDLEN = OpCodes.Ldlen, LDELEMCHR = OpCodes.Ldelem_U2, LDELEMINT = OpCodes.Ldelem_I4, LDELEMREF = OpCodes.Ldelem_Ref, STELEMCHR = OpCodes.Stelem_I2, STELEMINT = OpCodes.Stelem_I4, STELEMREF = OpCodes.Stelem_Ref, // exception handling THROW = OpCodes.Throw; const FieldAttributes GLOBALATTR = FieldAttributes.Assembly | FieldAttributes.Static; const FieldAttributes FIELDATTR = FieldAttributes.Assembly; const MethodAttributes METHATTR = MethodAttributes.Assembly | MethodAttributes.Static; const TypeAttributes INNERATTR = TypeAttributes.Class | TypeAttributes.NotPublic; const TypeAttributes PROGATTR = TypeAttributes.Class | TypeAttributes.Public; // quick access to conditional branch instructions static readonly OpCode[] brtrue = { BEQ, BGE, BGT, BLE, BLT, BNE }; static readonly OpCode[] brfalse = { BNE, BLT, BLE, BGT, BGE, BEQ }; // No-arg contructor of class System.Object. static readonly ConstructorInfo objCtor = typeof(object).GetConstructor(new Type[0]); // No-arg constructor of class System.ExecutionEngineException, for functions without return public static readonly ConstructorInfo eeexCtor = typeof(System.ExecutionEngineException).GetConstructor(new Type[0]); //----- System.Reflection.Emit objects for metadata management static AssemblyBuilder assembly; // metadata builder for the program assembly static ModuleBuilder module; // metadata builder for the program module static TypeBuilder program; // metadata builder for the main class static TypeBuilder inner; // metadata builder for the currently compiled inner class public static MethodBuilder // builders for the basic I/O operations provided by the Z# keywords read and write readChar, readInt, writeChar, writeInt; public static ILGenerator il; // IL stream of currently compiled method // needed for the readi method (important to preserve the character successing a number, // because System.Console.In.Peek does not work properly). static FieldBuilder __LastRecognizedCharacter; //----- metadata generation // Creates the required metadata builder objects for the given Symbol. // Call this after you inserted you Symbol into the symbol table. public static void CreateMetadata (Symbol sym) { switch (sym.kind) { case Symbol.Kind.Global: if (sym.type != Tab.noType) sym.fld = program.DefineField(sym.name, sym.type.sysType, GLOBALATTR); break; case Symbol.Kind.Field: if (sym.type != Tab.noType) sym.fld = inner.DefineField(sym.name, sym.type.sysType, FIELDATTR); break; case Symbol.Kind.Local: il.DeclareLocal(sym.type.sysType); break; case Symbol.Kind.Type: inner = module.DefineType(sym.name, INNERATTR); sym.type.sysType = inner; // define default contructor (calls base constructor) sym.ctor = inner.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]); il = sym.ctor.GetILGenerator(); il.Emit(LDARG0); il.Emit(CALL, objCtor); il.Emit(RET); break; case Symbol.Kind.Meth: // build argument list Type[] args = new Type[sym.nArgs]; Symbol arg = sym.locals; while (arg != null && arg.kind == Symbol.Kind.Arg) { args[arg.adr] = arg.type.sysType; arg = arg.next; } sym.meth = program.DefineMethod(sym.name, METHATTR, sym.type.sysType, args); il = sym.meth.GetILGenerator(); if (sym.name == "Main") assembly.SetEntryPoint(sym.meth); break; case Symbol.Kind.Prog: AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = sym.name; assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save); module = assembly.DefineDynamicModule(sym.name + "Module", sym.name + ".exe"); program = module.DefineType(sym.name, PROGATTR); // needed for the readi method (important to preserve the character successing a number, // because System.Console.In.Peek does not work properly). __LastRecognizedCharacter = program.DefineField("__LastRecognizedCharacter", typeof(int), GLOBALATTR); // initialize globals inner = null; // build methods for I/O keywords (read, write) BuildReadChar(); BuildReadInt(); BuildWriteChar(); BuildWriteInt(); break; } } // Completes the system type of an inner class. // Call this at end of class declaration. public static void CompleteClass () { inner.CreateType(); inner = null; } // ---------- instruction generation // Load the operand x onto the expression stack. public static void Load (Item x) { switch (x.kind) { case Item.Kind.Const: if (x.type == Tab.nullType) { il.Emit(LDNULL); } else { LoadConst(x.val); } break; case Item.Kind.Arg: switch (x.adr) { case 0: il.Emit(LDARG0); break; case 1: il.Emit(LDARG1); break; case 2: il.Emit(LDARG2); break; case 3: il.Emit(LDARG3); break; default: il.Emit(LDARG, x.adr); break; } break; case Item.Kind.Local: switch (x.adr) { case 0: il.Emit(LDLOC0); break; case 1: il.Emit(LDLOC1); break; case 2: il.Emit(LDLOC2); break; case 3: il.Emit(LDLOC3); break; default: il.Emit(LDLOC, x.adr); break; } break; case Item.Kind.Static: if (x.sym.fld != null) { il.Emit(LDSFLD, x.sym.fld); } break; case Item.Kind.Field: // assert: object reference is on stack if (x.sym.fld != null) { il.Emit(LDFLD, x.sym.fld); } break; case Item.Kind.Stack: // nothing to do (already loaded) break; case Item.Kind.Elem: // assert: array base address & index are on stack if (x.type == Tab.charType) { il.Emit(LDELEMCHR); } else if (x.type == Tab.intType) { il.Emit(LDELEMINT); } else if (x.type.kind == Struct.Kind.Class) { il.Emit(LDELEMREF); } else { Parser.Error("Invalid array element type"); } break; default: Parser.Error("Compiler error in Code.load, unexpected item kind."); break; } x.kind = Item.Kind.Stack; } // Load an integer constant onto the expression stack. public static void LoadConst (int n) { switch (n) { case -1: il.Emit(LDCM1); break; case 0: il.Emit(LDC0); break; case 1: il.Emit(LDC1); break; case 2: il.Emit(LDC2); break; case 3: il.Emit(LDC3); break; case 4: il.Emit(LDC4); break; case 5: il.Emit(LDC5); break; case 6: il.Emit(LDC6); break; case 7: il.Emit(LDC7); break; case 8: il.Emit(LDC8); break; default: il.Emit(LDC, n); break; } } // Generate an assignment x = y. public static void Assign (Item x, Item y) { // make sure y is loaded (if already on stack, nothing will happen) Load(y); switch (x.kind) { case Item.Kind.Arg: il.Emit(STARG, x.adr); break; case Item.Kind.Local: switch (x.adr) { case 0: il.Emit(STLOC0); break; case 1: il.Emit(STLOC1); break; case 2: il.Emit(STLOC2); break; case 3: il.Emit(STLOC3); break; default: il.Emit(STLOC, x.adr); break; } break; case Item.Kind.Static: if (x.sym.fld != null) { il.Emit(STSFLD, x.sym.fld); } break; case Item.Kind.Field: if (x.sym.fld != null) { il.Emit(STFLD, x.sym.fld); } break; case Item.Kind.Elem: if (x.type == Tab.charType) { il.Emit(STELEMCHR); } else if (x.type == Tab.intType) { il.Emit(STELEMINT); } else if (x.type.kind == Struct.Kind.Class) { il.Emit(STELEMREF); } else { Parser.Error("invalid array element type"); } break; default: Parser.Error("Left-hand side of an assignment must be a variable."); break; } } // Generate an increment instruction that increments x by n. public static void Inc (Item x, int n) { // prepare the stack switch (x.kind) { case Item.Kind.Local: case Item.Kind.Arg: case Item.Kind.Static: // nothing to do, but no error break; case Item.Kind.Field: // duplicate heap-address il.Emit(DUP); break; case Item.Kind.Elem: // In order to duplicate the heap-address and the index on the top of the stack, // we have to safe them in local variables, because there is no instruction like dup2 in CIL. // OK for Z#, because operator ++ may only be applied to int LocalBuilder heapAdr = il.DeclareLocal(typeof(int[])); LocalBuilder offset = il.DeclareLocal(typeof(int)); il.Emit(STLOC, offset); il.Emit(STLOC, heapAdr); il.Emit(LDLOC, heapAdr); il.Emit(LDLOC, offset); il.Emit(LDLOC, heapAdr); il.Emit(LDLOC, offset); break; default: Parser.Error("Incremented object must be a variable."); return; } // save item kind of x to restore after Load and before Assign Item.Kind before = x.kind; Load(x); LoadConst(n); il.Emit(ADD); x.kind = before; Assign(x, new Item(x.type)); } // Unconditional jump. public static void Jump (Label lab) { il.Emit(BR, lab); } // True Jump. Generates conditional branch instruction. public static void TJump (Item x) { il.Emit(brtrue[x.relop - Token.EQ], x.tLabel); // switch (x.relop) { // case Token.EQ: il.Emit(BEQ, x.tLabel); break; // case Token.GE: il.Emit(BGE, x.tLabel); break; // case Token.GT: il.Emit(BGT, x.tLabel); break; // case Token.LE: il.Emit(BLE, x.tLabel); break; // case Token.LT: il.Emit(BLT, x.tLabel); break; // case Token.NE: il.Emit(BNE, x.tLabel); break; // } } // False Jump. Generates conditional branch instruction. public static void FJump (Item x) { il.Emit(brfalse[x.relop - Token.EQ], x.fLabel); // switch (x.relop) { // case Token.EQ: il.Emit(BNE, x.fLabel); break; // case Token.GE: il.Emit(BLT, x.fLabel); break; // case Token.GT: il.Emit(BLE, x.fLabel); break; // case Token.LE: il.Emit(BGT, x.fLabel); break; // case Token.LT: il.Emit(BGE, x.fLabel); break; // case Token.NE: il.Emit(BEQ, x.fLabel); break; // } } // Generate an executable .NET-PE-File. public static void WritePEFile () { program.CreateType(); if (inner != null) { inner.CreateType(); } assembly.Save(assembly.GetName().Name + ".exe"); } static void BuildReadChar () { // char read () { readChar = program.DefineMethod("readc", MethodAttributes.Static, typeof(char), new Type[0]); il = readChar.GetILGenerator(); Label ifEnd = il.DefineLabel(); Label ifElse = il.DefineLabel(); // if (__LastRecognizedCharacter < 0) { il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC0); il.Emit(BGE, ifElse); // result = System.Console.Read(); il.EmitCall(CALL, typeof(Console).GetMethod("Read", new Type[0]), null); // } il.Emit(BR, ifEnd); // else { il.MarkLabel(ifElse); // result = __LastRecognizedCharacter; il.Emit(LDSFLD, __LastRecognizedCharacter); // __LastRecognizedCharacter = -1; il.Emit(LDCM1); il.Emit(STSFLD, __LastRecognizedCharacter); // } il.MarkLabel(ifEnd); il.Emit(OpCodes.Conv_U2); // return (char) result; // } il.Emit(RET); } static void BuildReadInt () { // int readi () readInt = program.DefineMethod("readi", MethodAttributes.Static, typeof(int), new Type[0]); il = readInt.GetILGenerator(); // bool neg = false; LocalBuilder neg = il.DeclareLocal(typeof(bool)); il.Emit(LDC0); il.Emit(STLOC0); // int x = 0; LocalBuilder x = il.DeclareLocal(typeof(int)); il.Emit(LDC0); il.Emit(STLOC1); // if (__LastRecognizedCharacter < 0) { Label ifEnd = il.DefineLabel(); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC0); il.Emit(BGE, ifEnd); // __LastRecognizedCharacter = Console.Read(); il.EmitCall(CALL, typeof(Console).GetMethod("Read", new Type[0]), null); il.Emit(STSFLD, __LastRecognizedCharacter); // } il.MarkLabel(ifEnd); // while (__LastRecognizedCharacter >= 0 && __LastRecognizedCharacter <= 32) Label whileStart = il.DefineLabel(); Label whileEnd = il.DefineLabel(); il.MarkLabel(whileStart); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC0); il.Emit(BLT, whileEnd); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC, 32); il.Emit(BGT, whileEnd); // { // __LastRecognizedCharacter = Console.Read(); il.EmitCall(CALL, typeof(Console).GetMethod("Read", new Type[0]), null); il.Emit(STSFLD, __LastRecognizedCharacter); // } il.Emit(BR, whileStart); il.MarkLabel(whileEnd); // if (__LastRecognizedCharacter == '-') { ifEnd = il.DefineLabel(); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC, (int) '-'); il.Emit(BNE, ifEnd); // neg = true; il.Emit(LDC1); il.Emit(STLOC0); // __LastRecognizedCharacter = Console.Read(); il.EmitCall(CALL, typeof(Console).GetMethod("Read", new Type[0]), null); il.Emit(STSFLD, __LastRecognizedCharacter); // } il.MarkLabel(ifEnd); // while ('0' <= __LastRecognizedCharacter && __LastRecognizedCharacter <= '9') { whileStart = il.DefineLabel(); whileEnd = il.DefineLabel(); il.MarkLabel(whileStart); il.Emit(LDC, (int) '0'); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(BGT, whileEnd); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC, (int) '9'); il.Emit(BGT, whileEnd); // x = 10 * x + (int) (__LastRecognizedCharacter-'0'); il.Emit(LDC, 10); il.Emit(LDLOC1); il.Emit(MUL); il.Emit(LDSFLD, __LastRecognizedCharacter); il.Emit(LDC, (int) '0'); il.Emit(SUB); il.Emit(ADD); il.Emit(STLOC1); // __LastRecognizedCharacter = Console.Read(); il.EmitCall(CALL, typeof(Console).GetMethod("Read", new Type[0]), null); il.Emit(STSFLD, __LastRecognizedCharacter); // } il.Emit(BR, whileStart); il.MarkLabel(whileEnd); // return neg ? -x : x; ifEnd = il.DefineLabel(); Label elseBranch = il.DefineLabel(); il.Emit(LDLOC0); il.Emit(OpCodes.Brfalse, elseBranch); il.Emit(LDLOC1); il.Emit(NEG); il.Emit(BR, ifEnd); il.MarkLabel(elseBranch); il.Emit(LDLOC1); il.MarkLabel(ifEnd); // } il.Emit(RET); } static void BuildWriteChar () { // void Write (char c, int width) writeChar = program.DefineMethod("write", MethodAttributes.Static, typeof(void), new Type[] { typeof(char), typeof(int) }); il = writeChar.GetILGenerator(); // System.Console.Write(System.String.Format("{{0,{0}}}", width), c) il.Emit(OpCodes.Ldstr, "{{0,{0}}}"); il.Emit(LDARG1); il.Emit(OpCodes.Box, typeof(int)); il.EmitCall(CALL, typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }), null); il.Emit(LDARG0); il.Emit(OpCodes.Box, typeof(char)); il.EmitCall(CALL, typeof(Console).GetMethod("Write", new Type[] { typeof(string), typeof(object) }), null); // } il.Emit(RET); } static void BuildWriteInt () { // void write (int x, int width) writeInt = program.DefineMethod("write", MethodAttributes.Static, typeof(void), new Type[] { typeof(int), typeof(int) }); il = writeInt.GetILGenerator(); // System.Console.Write(System.String.Format("{{0,{0}}}", width), x) il.Emit(OpCodes.Ldstr, "{{0,{0}}}"); il.Emit(LDARG1); il.Emit(OpCodes.Box, typeof(int)); il.EmitCall(CALL, typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }), null); il.Emit(LDARG0); il.Emit(OpCodes.Box, typeof(int)); il.EmitCall(CALL, typeof(Console).GetMethod("Write", new Type[] { typeof(string), typeof(object) }), null); // } il.Emit(RET); } }