/// <summary> /// Emit IL for the given expression /// </summary> /// <typeparam name="TInLeft"></typeparam> /// <typeparam name="TInRight"></typeparam> /// <typeparam name="TOut"></typeparam> /// <typeparam name="TEmit"></typeparam> /// <param name="expr"></param> /// <param name="emitter"></param> /// <param name="errorLabel"></param> /// <param name="leftConst">Indicates if the left parameter is a constant</param> /// <param name="rightConst">Indicates if the right parameter is a constant</param> /// <returns>A tuple, indicating the type left on the stack by this expression and if the expression **is** potentially fallible (i.e. errorLabel may be jumped to)</returns> public static ConvertResult ConvertBinary <TInLeft, TInRight, TOut, TEmit>( this Expression <Func <TInLeft, TInRight, TOut> > expr, OptimisingEmitter <TEmit> emitter, ExceptionBlock errorLabel, Value?leftConst, Value?rightConst ) { if (expr.ReturnType == typeof(StaticError)) { return(new ConvertResult(typeof(StaticError), null, null)); } // Try to convert expression without putting things into locals. Only works with certain expressions. var fast = TryConvertBinaryFastPath(expr, emitter, errorLabel, leftConst, rightConst); if (fast != null) { return(fast.Value); } // Put the parameters into local, ready to be used later using var localRight = emitter.DeclareLocal(typeof(TInRight), "ConvertBinary_Right", false); emitter.StoreLocal(localRight); using var localLeft = emitter.DeclareLocal(typeof(TInLeft), "ConvertBinary_Left", false); emitter.StoreLocal(localLeft); var parameters = new Dictionary <string, Parameter> { { expr.Parameters[0].Name !, new Parameter(localLeft, leftConst) },
/// <summary> /// Call an instance method on a given type, taking zero arguments /// </summary> /// <typeparam name="TEmit"></typeparam> /// <typeparam name="TCallee"></typeparam> /// <param name="emitter"></param> /// <param name="methodName"></param> public static void CallRuntimeThis0 <TEmit, TCallee>(this OptimisingEmitter <TEmit> emitter, string methodName) { using (var local = emitter.DeclareLocal(typeof(TCallee), "CallRuntimeThis0_Callee", false)) { emitter.StoreLocal(local); emitter.LoadLocalAddress(local, false); emitter.CallRuntimeN <TEmit, TCallee>(methodName); } }
/// <summary> /// Get the value of a field (even a private one) on a type /// </summary> /// <typeparam name="TEmit"></typeparam> /// <typeparam name="TType"></typeparam> /// <param name="emitter"></param> /// <param name="fieldName"></param> public static void GetRuntimeFieldValue <TEmit, TType>(this OptimisingEmitter <TEmit> emitter, string fieldName) { var field = typeof(TType).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static) !; using (var local = emitter.DeclareLocal(typeof(TType), "GetRuntimeFieldValue_Callee", false)) { emitter.StoreLocal(local); emitter.LoadLocalAddress(local, true); emitter.LoadField(field); } }
/// <summary> /// Get the value of a property on a type /// </summary> /// <typeparam name="TEmit"></typeparam> /// <typeparam name="TType"></typeparam> /// <param name="emitter"></param> /// <param name="propertyName"></param> public static void GetRuntimePropertyValue <TEmit, TType>(this OptimisingEmitter <TEmit> emitter, string propertyName) { // ReSharper disable once PossibleNullReferenceException var method = typeof(TType).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static) !.GetMethod !; using (var local = emitter.DeclareLocal(typeof(TType), "GetRuntimePropertyValue_Callee", false)) { emitter.StoreLocal(local); emitter.LoadLocalAddress(local, true); emitter.Call(method); } }
private void IdentityConversionTest <T>() { var type = typeof(T).ToStackType(); var emitter = Emit <Func <T, T> > .NewDynamicMethod(strictBranchVerification : true); var optim = new OptimisingEmitter <Func <T, T> >(emitter); optim.EmitCoerce(type, type); Assert.AreEqual(0, optim.InstructionCount); }
private Func <TA, TB> CreateFunc <TA, TB>(Action <OptimisingEmitter <Func <TA, TB> > > act) { var emitter = Emit <Func <TA, TB> > .NewDynamicMethod(strictBranchVerification : true); using (var optim = new OptimisingEmitter <Func <TA, TB> >(emitter)) { optim.LoadArgument(0); act(optim); optim.Return(); } return(emitter.CreateDelegate()); }
public ArraySegmentMemoryAccessor( OptimisingEmitter <TEmit> emitter, ushort externalArraySegmentArg, ushort internalArraySegmentArg, IReadonlyInternalsMap internals, IReadonlyExternalsMap externals, IStaticTypeTracker types, Local?changeSet) { _emitter = emitter; _externalArraySegmentArg = externalArraySegmentArg; _internalArraySegmentArg = internalArraySegmentArg; _internals = internals; _externals = externals; _types = types; _changeSet = changeSet; _cache = new Dictionary <VariableName, TypedLocal>(); _cacheDirty = new Dictionary <VariableName, Local>(); _mutated = new HashSet <VariableName>(); }
/// <summary> /// Emit code to dynamically check for errors. If an error would occur clear out the stack and jump away to the given label /// </summary> /// <typeparam name="TEmit"></typeparam> /// <param name="emitter"></param> /// <param name="errorLabel"></param> /// <param name="parameters"></param> public void EmitDynamicWillThrow <TEmit>(OptimisingEmitter <TEmit> emitter, Compiler.Emitter.Instructions.ExceptionBlock errorLabel, IReadOnlyList <Local> parameters) { // Load parameters back onto stack for (var i = parameters.Count - 1; i >= 0; i--) { emitter.LoadLocal(parameters[i]); } // Invoke the `will throw` method to discover if this invocation would trigger a runtime error emitter.Call(WillThrow); // Create a label to jump past the error handling for the normal case var noThrowLabel = emitter.DefineLabel(); // Jump past error handling if this is ok emitter.BranchIfFalse(noThrowLabel); // If execution reaches here it means an error would occur in this operation emitter.Leave(errorLabel); emitter.MarkLabel(noThrowLabel); }
/// <summary> /// Call a method on the `Runtime` class, taking a given set of arguments /// </summary> /// <typeparam name="TEmit"></typeparam> /// <param name="emitter"></param> /// <param name="methodName"></param> /// <param name="args"></param> public static void CallRuntimeN <TEmit>(this OptimisingEmitter <TEmit> emitter, string methodName, params Type[] args) { var method = typeof(Runtime).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static, null, args, null) !; emitter.Call(method); }
/// <summary> /// Coerce an object of the given type into another type /// </summary> /// <typeparam name="TEmit"></typeparam> /// <param name="emitter"></param> /// <param name="input"></param> /// <param name="output"></param> public static void EmitCoerce <TEmit>(this OptimisingEmitter <TEmit> emitter, StackType input, StackType output) { switch (input, output) {
/// <summary> /// Compile a line of Yolol into the given IL emitter /// </summary> /// <param name="line"></param> /// <param name="emit"></param> /// <param name="lineNumber"></param> /// <param name="maxLines"></param> /// <param name="maxStringLength"></param> /// <param name="internalVariableMap"></param> /// <param name="externalVariableMap"></param> /// <param name="staticTypes"></param> /// <param name="changeDetection"></param> public static void Compile( this Line line, Emit <Func <ArraySegment <Value>, ArraySegment <Value>, LineResult> > emit, int lineNumber, int maxLines, int maxStringLength, IReadonlyInternalsMap internalVariableMap, IReadonlyExternalsMap externalVariableMap, IReadOnlyDictionary <VariableName, Type>?staticTypes = null, bool changeDetection = false ) { using (var emitter = new OptimisingEmitter <Func <ArraySegment <Value>, ArraySegment <Value>, LineResult> >(emit)) { void EmitFallthroughCalc() { if (lineNumber == maxLines) { emitter.LoadConstant(1); } else { emitter.LoadConstant(lineNumber + 1); } } // Special case for totally empty lines if (line.Statements.Statements.Count == 0) { EmitFallthroughCalc(); emitter.LoadConstant(0ul); emitter.NewObject <ChangeSet, ulong>(); emitter.NewObject(typeof(LineResult).GetConstructor(new[] { typeof(int), typeof(ChangeSet) }) !); emitter.Return(); return; } // Begin an exception block to catch Yolol runtime errors var exBlock = emitter.BeginExceptionBlock(); // Create a local to store the return address from inside the try/catch block var retAddr = emitter.DeclareLocal <int>("ret_addr", initializeReused: false); // Create a local for the change bit set var changeSet = changeDetection ? emitter.DeclareLocal <ulong>("change_set") : null; // Store the default return address to go to EmitFallthroughCalc(); emitter.StoreLocal(retAddr); // Create a label which any `goto` statements can use. They drop their destination PC on the stack and then jump to this label var gotoLabel = emitter.DefineLabel2("encountered_goto"); var types = new StaticTypeTracker(staticTypes); // Create a memory accessor which manages reading and writing the memory arrays using (var accessor = new ArraySegmentMemoryAccessor <Func <ArraySegment <Value>, ArraySegment <Value>, LineResult> >( emitter, 1, 0, internalVariableMap, externalVariableMap, types, changeSet )) { accessor.EmitLoad(line); // Convert the entire line into IL var converter = new ConvertLineVisitor <Func <ArraySegment <Value>, ArraySegment <Value>, LineResult> >(emitter, maxLines, accessor, exBlock, gotoLabel, types, maxStringLength); converter.Visit(line); // When a line finishes (with no gotos in the line) call flow eventually reaches here. Go to the next line. emitter.Leave(exBlock); // Create a block to handle gotos. The destination will already be on the stack, so just return if (gotoLabel.IsUsed) { emitter.MarkLabel(gotoLabel); emitter.StoreLocal(retAddr); emitter.Leave(exBlock); } // Catch all execution exceptions and return the appropriate next line number to fall through to var catchBlock = emitter.BeginCatchBlock <ExecutionException>(exBlock); #if DEBUG using (var ex = emitter.DeclareLocal(typeof(ExecutionException), initializeReused: false)) { emitter.StoreLocal(ex); emitter.WriteLine("execution exception: {0}", ex); } #else emitter.Pop(); #endif // Close the exception block which was wrapping the entire method emitter.EndCatchBlock(catchBlock); emitter.EndExceptionBlock(exBlock); } // Load the return address from inside the catch block emitter.LoadLocal(retAddr); // Create the change set, either from the computer value or the default value (which indicates that everything has changed) if (changeSet != null) { emitter.LoadLocal(changeSet); } else { emitter.LoadConstant(ulong.MaxValue); } emitter.NewObject <ChangeSet, ulong>(); emitter.NewObject(typeof(LineResult).GetConstructor(new[] { typeof(int), typeof(ChangeSet) }) !); emitter.Return(); emitter.Optimise(); #if DEBUG Console.WriteLine($"// {line}"); Console.WriteLine(emitter.ToString()); Console.WriteLine($"------------------------------"); #endif } }