diff --git a/Program.cs b/Program.cs index 0217e8c..fca0f22 100644 --- a/Program.cs +++ b/Program.cs @@ -1,10 +1,13 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Numerics; +using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -62,6 +65,7 @@ void Main() } // options?? + bool shouldUseCompiler = false; bool shouldUseParallelism = true; bool shouldUseSimd = true; @@ -88,12 +92,15 @@ void Main() isEvaluating = true; shouldUpdateTexture = true; InterpreterOptions interpreterOptions = (shouldUseParallelism ? InterpreterOptions.Parallelism : default) - | (shouldUseSimd ? InterpreterOptions.Simd : default); + | (shouldUseSimd ? InterpreterOptions.Simd : default) + | (shouldUseCompiler + ? InterpreterOptions.CompileInnerLoop + : default); Task.Run(() => { Stopwatch sw = Stopwatch.StartNew(); - Instruction[] instructions = Parsing.Parse(programsProsperoVm); + EvaluationInstructions instructions = Parsing.Parse(programsProsperoVm); currentOutputImageData.AsSpan()[..currentOutputImageData.Length].Clear(); if ((interpreterOptions & InterpreterOptions.Simd) != 0) @@ -129,7 +136,7 @@ void Main() Raylib.EndShaderMode(); Raylib.DrawText("Sharpero (press R to evaluate, O to output to file)", 12, 12, 20, Color.White); - Raylib.DrawText($" - parallelism {(shouldUseParallelism ? "enabled" : "disabled")} (P to toggle), simd {(shouldUseSimd ? "enabled" : "disabled")} (S to toggle)", 12, 32, 20, Color.White); + Raylib.DrawText($" - parallelism {(shouldUseParallelism ? "enabled" : "disabled")} (P to toggle), simd {(shouldUseSimd ? "enabled" : "disabled")} (S to toggle), compile {(shouldUseCompiler? "enabled" : "disabled")} (C to toggle)", 12, 32, 20, Color.White); if (lastEvaluationTimeTook != 0.0f) { @@ -160,6 +167,11 @@ void Main() { shouldUseSimd = !shouldUseSimd; } + + if (Raylib.IsKeyPressed(KeyboardKey.C)) + { + shouldUseCompiler = !shouldUseCompiler; + } Raylib.EndDrawing(); } @@ -177,7 +189,7 @@ float[] GenerateOutputImage(int currentImageSize, bool shouldWriteOutputImage = { (float[] result, double timeTakenSecondsEvaluate) = BenchmarkFunction(() => { - Instruction[] instructions = Parsing.Parse(programsProsperoVm); + EvaluationInstructions instructions = Parsing.Parse(programsProsperoVm); InterpreterOptions interpreterOptions = InterpreterOptions.Parallelism | InterpreterOptions.Simd; return Interpreter.Evaluate>(instructions, imageSize: currentImageSize, interpreterOptions); }); @@ -264,6 +276,8 @@ internal readonly record struct Instruction( } } +internal record EvaluationInstructions(Instruction[] Instructions); + internal static class Parsing { @@ -282,7 +296,18 @@ internal static class Parsing return identifiers[id] = new Operand(Convert.ToInt32(id[1..], 16), isConstant); } - public static Instruction[] Parse(string filename) + private static float EvaluateExpression(OpCode opCode, float a, float b) + { + return opCode switch + { + OpCode.Add => a + b, + OpCode.Sub => a - b, + OpCode.Mul => a * b, + _ => 0.0f + }; + } + + public static EvaluationInstructions Parse(string filename) { var identifiers = new Dictionary(); foreach (string line in File.ReadAllLines(filename)) @@ -332,17 +357,6 @@ internal static class Parsing } } - float EvaluateExpression(OpCode opCode, float a, float b) - { - return opCode switch - { - OpCode.Add => a + b, - OpCode.Sub => a - b, - OpCode.Mul => a * b, - _ => 0.0f - }; - } - bool shouldEliminateConstants = true; if (shouldEliminateConstants) @@ -416,15 +430,507 @@ internal static class Parsing Console.WriteLine($"Sharpero eliminated {numberOfRemovedInstructions} constants from tape, {(float)numberOfRemovedInstructions / totalNumberOfInstructions * 100.0f:0.0} % of total"); } - return instructions.ToArray(); + return new EvaluationInstructions(instructions.ToArray()); } } [Flags] internal enum InterpreterOptions { - Parallelism = 0x1, - Simd = 0x2 + CompileInnerLoop = 0x1, + Parallelism = 0x2, + Simd = 0x4 +} + +internal static class Evaluation +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(Span variables, T value, int offset) + where T : unmanaged + { + Unsafe.Add(ref MemoryMarshal.GetReference(variables), offset) = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read(Span variables, int offset) + where T : unmanaged + { + return Unsafe.Add(ref MemoryMarshal.GetReference(variables), offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Add(T a, T b) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(Unsafe.As(ref a) + Unsafe.As(ref b)); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Unsafe.As>(ref a) + Unsafe.As>(ref b)); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Sub(T a, T b) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(Unsafe.As(ref a) - Unsafe.As(ref b)); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Unsafe.As>(ref a) - Unsafe.As>(ref b)); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Mul(T a, T b) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(Unsafe.As(ref a) * Unsafe.As(ref b)); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Unsafe.As>(ref a) * Unsafe.As>(ref b)); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Neg(T v) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(-Unsafe.As(ref v)); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(-Unsafe.As>(ref v)); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Max(T a, T b) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(MathF.Max(Unsafe.As(ref a), Unsafe.As(ref b))); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Vector.Max(Unsafe.As>(ref a), Unsafe.As>(ref b))); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Min(T a, T b) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(MathF.Min(Unsafe.As(ref a), Unsafe.As(ref b))); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Vector.Min(Unsafe.As>(ref a), Unsafe.As>(ref b))); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T SquareRoot(T v) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(MathF.Sqrt(Unsafe.As(ref v))); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Vector.SquareRoot(Unsafe.As>(ref v))); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Square(T v) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(Unsafe.As(ref v) * Unsafe.As(ref v)); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Vector.Multiply(Unsafe.As>(ref v), Unsafe.As>(ref v))); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T EvaluateConstant(float c) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(c); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(new Vector(c)); + } + else + { + throw new InvalidOperationException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T One() + where T : unmanaged + { + if (typeof(T) == typeof(float)) + { + return Unsafe.BitCast(1.0f); + } + else if (typeof(T) == typeof(Vector)) + { + return Unsafe.BitCast, T>(Vector.One); + } + else + { + throw new InvalidOperationException(); + } + } +} + +internal static class Delegates + where T : unmanaged +{ + public delegate void EvaluateDelegate(Span variables, T xs, T ys); +} + +internal static class Compiler +{ + internal static class CompilerCache + where T : unmanaged + { + internal static readonly ConcurrentDictionary.EvaluateDelegate> CachedPrograms = + new ConcurrentDictionary.EvaluateDelegate>(); + } + + private static (AssemblyBuilder, MethodBuilder, TypeBuilder) CreateDynamicAssemblyWithMethodBuilder(string assemblyName, string moduleName, string typeName, string methodName) + where T : unmanaged + { + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName); + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); + var methodBuilder = + typeBuilder.DefineMethod( + methodName, + MethodAttributes.Public | MethodAttributes.Static, + typeof(void), + [typeof(Span), typeof(T), typeof(T)]); + + return (assemblyBuilder, methodBuilder, typeBuilder); + } + + public static Delegates.EvaluateDelegate Compile(EvaluationInstructions evaluationInstructions) + where T : unmanaged + { + if (CompilerCache.CachedPrograms.TryGetValue(evaluationInstructions, out var program)) + { + return program; + } + + lock (CompilerCache.CachedPrograms) + { + int maximumInstructionsPerChunk = 32; + + Stopwatch totalSw = Stopwatch.StartNew(); + var chunkDelegates = evaluationInstructions.Instructions + .Chunk(maximumInstructionsPerChunk) + .AsParallel() + .Select((instructionsChunk, instructionChunkIndex) => + { + Stopwatch subTotalSw = Stopwatch.StartNew(); + var evaluateDynamicMethod = new DynamicMethod( + $"EvaluateChunk_{instructionChunkIndex}", + typeof(void), + [typeof(Span), typeof(T), typeof(T)], + typeof(Compiler).Module, + skipVisibility: true + ); + + var methodGenerator = evaluateDynamicMethod.GetILGenerator(); + methodGenerator.DeclareLocal(typeof(T)); + + foreach (Instruction instruction in instructionsChunk) + { + CompileInstruction(methodGenerator, in instruction); + } + + // finalize method, we need a return as our final bit + methodGenerator.Emit(OpCodes.Ret); + + // create the delegate, but also force the JIT to compile our function :D + var newChunkDelegate = evaluateDynamicMethod.CreateDelegate.EvaluateDelegate>(); + RuntimeHelpers.PrepareDelegate(newChunkDelegate); + + Console.WriteLine($" - took: {subTotalSw.Elapsed.Milliseconds:0.0} ms to compile sub program ({instructionChunkIndex}) ({methodGenerator.ILOffset} bytes of instructions)!"); + return newChunkDelegate; + }).ToList(); + + Console.WriteLine($"Sharpero took: {totalSw.Elapsed.Milliseconds:0.0} ms to compile program with {chunkDelegates.Count} parts (max instructions per chunk = {maximumInstructionsPerChunk})!"); + CompilerCache.CachedPrograms[evaluationInstructions] = (variables, xs, ys) => + { + foreach (Delegates.EvaluateDelegate chunkDelegate in chunkDelegates) + { + chunkDelegate(variables, xs, ys); + } + }; + + // force the JIT to compile our function :D + RuntimeHelpers.PrepareDelegate(CompilerCache.CachedPrograms[evaluationInstructions]); + return CompilerCache.CachedPrograms[evaluationInstructions]; + } + } + + private static void CompileInstruction(ILGenerator methodGenerator, in Instruction instruction) + where T : unmanaged + { + void EmitReadConstant(float constant) + { + methodGenerator.Emit(OpCodes.Ldc_R4, constant); // constant + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.EvaluateConstant))!.MakeGenericMethod(typeof(T))); + } + + void EmitWriteConstant(int offset, float constant) + { + // write, but with constant value + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldc_R4, constant); // result of call + methodGenerator.Emit(OpCodes.Ldc_I4, offset); // offset + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Write))!.MakeGenericMethod(typeof(T))); + } + + void EmitWrite(int offset) + { + // write, previous result will be at loc 0 if all is well + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldloc_0); // result of call + methodGenerator.Emit(OpCodes.Ldc_I4, offset); // offset + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Write))!.MakeGenericMethod(typeof(T))); + } + + void EmitWriteXs(int offset) + { + // write, but xs specifically + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldarg_1); // xs + methodGenerator.Emit(OpCodes.Ldc_I4, offset); // offset + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Write))!.MakeGenericMethod(typeof(T))); + } + + void EmitWriteYs(int offset) + { + // write, but ys specifically + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldarg_2); // ys + methodGenerator.Emit(OpCodes.Ldc_I4, offset); // offset + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Write))!.MakeGenericMethod(typeof(T))); + } + + void EmitInvokeUnaryOperation(string methodName) + { + // call with Span, pushed results of variables[a] and variables[b] reads + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(methodName)!.MakeGenericMethod(typeof(T))); + methodGenerator.Emit(OpCodes.Stloc_0); + } + + void EmitInvokeBinaryOperation(string methodName) + { + // call with Span, pushed results of variables[a] and variables[b] reads + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(methodName)!.MakeGenericMethod(typeof(T))); + methodGenerator.Emit(OpCodes.Stloc_0); + } + + void EmitReadVariablesB(Operand b) + { + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldc_I4, b); // b + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Read))!.MakeGenericMethod(typeof(T))); + } + + void EmitReadVariablesA(Operand a) + { + methodGenerator.Emit(OpCodes.Ldarg_0); // Span + methodGenerator.Emit(OpCodes.Ldc_I4, a); // a + methodGenerator.Emit(OpCodes.Call, typeof(Evaluation).GetMethod(nameof(Evaluation.Read))!.MakeGenericMethod(typeof(T))); + } + + switch (instruction) + { + case { OpCode: OpCode.VarX }: // => xs, + + EmitWriteXs(instruction.Out); + break; + + case { OpCode: OpCode.VarY }: // => ys, + + EmitWriteYs(instruction.Out); + break; + + case { OpCode: OpCode.Add, A: { IsConstant: false } a, B: { IsConstant: false } b }: // => Add(variables[a], variables[b]), + + EmitReadVariablesA(a); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Add)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Add, A.IsConstant: true, B: { IsConstant: false } b }: // => Add(EvaluateConstant(instruction.C), variables[b]), + + EmitReadConstant(instruction.C); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Add)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Add, A: { IsConstant: false } a, B.IsConstant: true }: // => Add(variables[a], EvaluateConstant(instruction.C)), + + EmitReadVariablesA(a); + EmitReadConstant(instruction.C); + EmitInvokeBinaryOperation(nameof(Evaluation.Add)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Sub, A: { IsConstant: false } a, B: { IsConstant: false } b }: // => Sub(variables[a], variables[b]), + + EmitReadVariablesA(a); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Sub)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Sub, A.IsConstant: true, B: { IsConstant: false } b }: // => Sub(EvaluateConstant(instruction.C), variables[b]), + + EmitReadConstant(instruction.C); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Sub)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Sub, A: { IsConstant: false } a, B.IsConstant: true }: // => Sub(variables[a], EvaluateConstant(instruction.C)), + + EmitReadVariablesA(a); + EmitReadConstant(instruction.C); + EmitInvokeBinaryOperation(nameof(Evaluation.Sub)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Mul, A: { IsConstant: false } a, B: { IsConstant: false } b }: // => Mul(variables[a], variables[b]), + + EmitReadVariablesA(a); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Mul)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Mul, A.IsConstant: true, B: { IsConstant: false } b }: // => Mul(EvaluateConstant(instruction.C), variables[b]), + + EmitReadConstant(instruction.C); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Mul)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Mul, A: { IsConstant: false } a, B.IsConstant: true }: // => Mul(variables[a], EvaluateConstant(instruction.C)), + + EmitReadVariablesA(a); + EmitReadConstant(instruction.C); + EmitInvokeBinaryOperation(nameof(Evaluation.Mul)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Max, A: var a, B: var b }: // => Max(variables[a], variables[b]), + + EmitReadVariablesA(a); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Max)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Min, A: var a, B: var b }: // => Min(variables[a], variables[b]), + + EmitReadVariablesA(a); + EmitReadVariablesB(b); + EmitInvokeBinaryOperation(nameof(Evaluation.Min)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Neg, A: var a }: // => Neg(variables[a]), + + EmitReadVariablesA(a); + EmitInvokeUnaryOperation(nameof(Evaluation.Neg)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Sqrt, A: var a }: // => SquareRoot(variables[a]), + + EmitReadVariablesA(a); + EmitInvokeUnaryOperation(nameof(Evaluation.SquareRoot)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Square, A: var a }: // => Square(variables[a]), + + EmitReadVariablesA(a); + EmitInvokeUnaryOperation(nameof(Evaluation.Square)); + EmitWrite(instruction.Out); + break; + + case { OpCode: OpCode.Const, C: var v }: // => EvaluateConstant(v), + + EmitWriteConstant(instruction.Out, v); + break; + } + } } internal static class Interpreter @@ -434,11 +940,11 @@ internal static class Interpreter { if (typeof(T) == typeof(float)) { - return (T)(object)values[0]; + return Unsafe.BitCast(values[0]); } else if (typeof(T) == typeof(Vector)) { - return (T)(object)new Vector(values); + return Unsafe.BitCast, T>(new Vector(values)); } else { @@ -447,7 +953,7 @@ internal static class Interpreter } [SkipLocalsInit] - public static float[] Evaluate(Instruction[] instructions, int imageSize, InterpreterOptions options = default, float[]? result = null) + public static float[] Evaluate(EvaluationInstructions evaluationInstructions, int imageSize, InterpreterOptions options = default, float[]? result = null) where T : unmanaged { result ??= new float[imageSize * imageSize]; @@ -456,6 +962,13 @@ internal static class Interpreter { MaxDegreeOfParallelism = (options & InterpreterOptions.Parallelism) != 0 ? -1 : 1 }; + + bool shouldCompileInnerLoop = (options & InterpreterOptions.CompileInnerLoop) != 0; + + if (shouldCompileInnerLoop) + { + Compiler.Compile(evaluationInstructions); + } int chunkSize = typeof(T) == typeof(Vector) ? Vector.Count : 1; Parallel.For(0, (imageSize * imageSize) / chunkSize, parallelOptions, chunkIdx => @@ -475,236 +988,71 @@ internal static class Interpreter (xs[idx], ys[idx]) = (vx, vy); } - T results = Evaluate(instructions, GetValues(xs), GetValues(ys)); + T results = shouldCompileInnerLoop + ? EvaluateCompiled(evaluationInstructions,GetValues(xs), GetValues(ys)) + : EvaluateInterpreted(evaluationInstructions, GetValues(xs), GetValues(ys)); + for (int idx = 0; idx < chunkSize; ++idx) { int currentIdx = chunkIdx * chunkSize + idx; (int x, int y) = Parsing.IndexToCoord(currentIdx, width: imageSize); - result[Parsing.CoordToIndex(x, y, width: imageSize)] = Unsafe.Add(ref Unsafe.As(ref results), idx); } }); return result; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Add(T a, T b) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)(Unsafe.As(ref a) + Unsafe.As(ref b)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)(Unsafe.As>(ref a) + Unsafe.As>(ref b)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Sub(T a, T b) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)(Unsafe.As(ref a) - Unsafe.As(ref b)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)(Unsafe.As>(ref a) - Unsafe.As>(ref b)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Mul(T a, T b) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)(Unsafe.As(ref a) * Unsafe.As(ref b)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)(Unsafe.As>(ref a) * Unsafe.As>(ref b)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Mul(T a, float b) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)(Unsafe.As(ref a) * b); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)(Unsafe.As>(ref a) * b); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Neg(T v) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)(-Unsafe.As(ref v)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)(-Unsafe.As>(ref v)); - } - else - { - throw new InvalidOperationException(); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Max(T a, T b) + [SkipLocalsInit] + public static T EvaluateCompiled(EvaluationInstructions evaluationInstructions, T xs, T ys) where T : unmanaged { - if (typeof(T) == typeof(float)) - { - return (T)(object)MathF.Max(Unsafe.As(ref a), Unsafe.As(ref b)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)Vector.Max(Unsafe.As>(ref a), Unsafe.As>(ref b)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Min(T a, T b) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)MathF.Min(Unsafe.As(ref a), Unsafe.As(ref b)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)Vector.Min(Unsafe.As>(ref a), Unsafe.As>(ref b)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T SquareRoot(T v) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)MathF.Sqrt(Unsafe.As(ref v)); - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)Vector.SquareRoot(Unsafe.As>(ref v)); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T EvaluateConstant(float c) - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)c; - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)new Vector(c); - } - else - { - throw new InvalidOperationException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T One() - where T : unmanaged - { - if (typeof(T) == typeof(float)) - { - return (T)(object)1.0f; - } - else if (typeof(T) == typeof(Vector)) - { - return (T)(object)Vector.One; - } - else - { - throw new InvalidOperationException(); - } + // #TODO: this construction is just a little bit unhinged lol + Span variables = stackalloc T[evaluationInstructions.Instructions.Length]; + + // compile if we've not already cached this instruction set, and evaluate! + Compiler.Compile(evaluationInstructions)?.Invoke(variables, xs, ys); + + return variables[evaluationInstructions.Instructions.Length - 1]; } [SkipLocalsInit] - public static T Evaluate(Instruction[] instructions, T xs, T ys) + public static T EvaluateInterpreted(EvaluationInstructions evaluationInstructions, T xs, T ys) where T : unmanaged { // #TODO: this construction is just a little bit unhinged lol - Span variables = stackalloc T[instructions.Length]; + Span variables = stackalloc T[evaluationInstructions.Instructions.Length]; - foreach (ref Instruction instruction in instructions.AsSpan()) + foreach (ref Instruction instruction in evaluationInstructions.Instructions.AsSpan()) { variables[instruction.Out] = instruction switch { { OpCode: OpCode.VarX } => xs, { OpCode: OpCode.VarY } => ys, - { OpCode: OpCode.Add, A: { IsConstant: false } a, B: { IsConstant: false } b } => Add(variables[a], variables[b]), - { OpCode: OpCode.Add, A.IsConstant: true, B: { IsConstant: false } b } => Add(EvaluateConstant(instruction.C), variables[b]), - { OpCode: OpCode.Add, A: { IsConstant: false } a, B.IsConstant: true } => Add(variables[a], EvaluateConstant(instruction.C)), + { OpCode: OpCode.Add, A: { IsConstant: false } a, B: { IsConstant: false } b } => Evaluation.Add(variables[a], variables[b]), + { OpCode: OpCode.Add, A.IsConstant: true, B: { IsConstant: false } b } => Evaluation.Add(Evaluation.EvaluateConstant(instruction.C), variables[b]), + { OpCode: OpCode.Add, A: { IsConstant: false } a, B.IsConstant: true } => Evaluation.Add(variables[a], Evaluation.EvaluateConstant(instruction.C)), - { OpCode: OpCode.Sub, A: { IsConstant: false } a, B: { IsConstant: false } b } => Sub(variables[a], variables[b]), - { OpCode: OpCode.Sub, A.IsConstant: true, B: { IsConstant: false } b } => Sub(EvaluateConstant(instruction.C), variables[b]), - { OpCode: OpCode.Sub, A: { IsConstant: false } a, B.IsConstant: true } => Sub(variables[a], EvaluateConstant(instruction.C)), + { OpCode: OpCode.Sub, A: { IsConstant: false } a, B: { IsConstant: false } b } => Evaluation.Sub(variables[a], variables[b]), + { OpCode: OpCode.Sub, A.IsConstant: true, B: { IsConstant: false } b } => Evaluation.Sub(Evaluation.EvaluateConstant(instruction.C), variables[b]), + { OpCode: OpCode.Sub, A: { IsConstant: false } a, B.IsConstant: true } => Evaluation.Sub(variables[a], Evaluation.EvaluateConstant(instruction.C)), - { OpCode: OpCode.Mul, A: { IsConstant: false } a, B: { IsConstant: false } b } => Mul(variables[a], variables[b]), - { OpCode: OpCode.Mul, A.IsConstant: true, B: { IsConstant: false } b } => Mul(EvaluateConstant(instruction.C), variables[b]), - { OpCode: OpCode.Mul, A: { IsConstant: false } a, B.IsConstant: true } => Mul(variables[a], EvaluateConstant(instruction.C)), + { OpCode: OpCode.Mul, A: { IsConstant: false } a, B: { IsConstant: false } b } => Evaluation.Mul(variables[a], variables[b]), + { OpCode: OpCode.Mul, A.IsConstant: true, B: { IsConstant: false } b } => Evaluation.Mul(Evaluation.EvaluateConstant(instruction.C), variables[b]), + { OpCode: OpCode.Mul, A: { IsConstant: false } a, B.IsConstant: true } => Evaluation.Mul(variables[a], Evaluation.EvaluateConstant(instruction.C)), - { OpCode: OpCode.Max, A: var a, B: var b } => Max(variables[a], variables[b]), - { OpCode: OpCode.Min, A: var a, B: var b } => Min(variables[a], variables[b]), - { OpCode: OpCode.Neg, A: var a } => Neg(variables[a]), - { OpCode: OpCode.Sqrt, A: var a } => SquareRoot(variables[a]), - { OpCode: OpCode.Square, A: var a } => Mul(variables[a], variables[a]), + { OpCode: OpCode.Max, A: var a, B: var b } => Evaluation.Max(variables[a], variables[b]), + { OpCode: OpCode.Min, A: var a, B: var b } => Evaluation.Min(variables[a], variables[b]), + { OpCode: OpCode.Neg, A: var a } => Evaluation.Neg(variables[a]), + { OpCode: OpCode.Sqrt, A: var a } => Evaluation.SquareRoot(variables[a]), + { OpCode: OpCode.Square, A: var a } => Evaluation.Square(variables[a]), - { OpCode: OpCode.Const, C: var v } => Mul(One(), v), + { OpCode: OpCode.Const, C: var v } => Evaluation.EvaluateConstant(v), _ => variables[instruction.Out] }; } - return variables[instructions.Length - 1]; + return variables[evaluationInstructions.Instructions.Length - 1]; } } \ No newline at end of file diff --git a/Sharpero.sln.DotSettings.user b/Sharpero.sln.DotSettings.user index b24b261..a681c1f 100644 --- a/Sharpero.sln.DotSettings.user +++ b/Sharpero.sln.DotSettings.user @@ -1,18 +1,24 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded \ No newline at end of file