From 335a9cc99a74b9e94455b6b418a09d4678e66044 Mon Sep 17 00:00:00 2001 From: profan Date: Sun, 7 Jun 2026 18:32:43 +0100 Subject: [PATCH] add a little readme, factor things properly --- Program.cs | 165 +++++++++++++++++++++++++++++------------------------ README.md | 20 +++++++ 2 files changed, 112 insertions(+), 73 deletions(-) create mode 100644 README.md diff --git a/Program.cs b/Program.cs index fbc6863..af6bcd6 100644 --- a/Program.cs +++ b/Program.cs @@ -330,14 +330,30 @@ internal static class Parsing public static EvaluationInstructions Parse(string filename) { - var identifiers = new Dictionary(); + // parse all identifiers first, so we know which are directly associated with constant values + Dictionary identifiers = ParseIdentifiers(filename); + + // now parse instructions, correctly tagging operands that operate directly on constants + List instructions = ParseInstructions(filename, identifiers); + + // eliminate any constants from the tape, folding them into the instruction operands instead + (int totalNumberOfInstructions, int numberOfRemovedInstructions) = EliminateConstants(instructions); + Console.WriteLine($"Sharpero eliminated {numberOfRemovedInstructions} constants from tape, {(float)numberOfRemovedInstructions / totalNumberOfInstructions * 100.0f:0.0} % of total"); + + return new EvaluationInstructions(instructions.ToArray()); + } + + private static Dictionary ParseIdentifiers(string filename) + { + Dictionary identifiers = []; + foreach (string line in File.ReadAllLines(filename)) { if (line.StartsWith('#')) { continue; } - + switch (line.Split(" ")) { case [{ } @out, "const", not null]: @@ -358,19 +374,25 @@ internal static class Parsing } } + return identifiers; + } + + private static List ParseInstructions(string filename, Dictionary identifiers) + { List instructions = []; + foreach (string line in File.ReadAllLines(filename)) { if (line.StartsWith('#')) { continue; } - + Instruction? parsedInstruction = line.Split(" ") switch { [{ } @out, "var-x"] => new Instruction(identifiers[@out], OpCode.VarX), [{ } @out, "var-y"] => new Instruction(identifiers[@out], OpCode.VarY), - [{ } @out, "const", { } v]=> new Instruction(identifiers[@out], OpCode.Const, C: float.Parse(v, CultureInfo.InvariantCulture)), + [{ } @out, "const", { } v] => new Instruction(identifiers[@out], OpCode.Const, C: float.Parse(v, CultureInfo.InvariantCulture)), [{ } @out, "add", { } a, { } b] => new Instruction(identifiers[@out], OpCode.Add, identifiers[a], identifiers[b]), [{ } @out, "sub", { } a, { } b] => new Instruction(identifiers[@out], OpCode.Sub, identifiers[a], identifiers[b]), [{ } @out, "mul", { } a, { } b] => new Instruction(identifiers[@out], OpCode.Mul, identifiers[a], identifiers[b]), @@ -388,81 +410,78 @@ internal static class Parsing } } - bool shouldEliminateConstants = true; + return instructions; + } - if (shouldEliminateConstants) + private static (int TotalInstructions, int NumberOfRemovedInstructions) EliminateConstants(List instructions) + { + // handle constant propagation, eliminating all constants from the instruction stream and merging them + foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions)) { - // handle constant propagation, eliminating all constants from the instruction stream and merging them - foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions)) + switch (instruction.OpCode) { - switch (instruction.OpCode) - { - case (OpCode.Add or OpCode.Sub or OpCode.Mul) when instruction.A.IsConstant || instruction.B.IsConstant: - switch (instruction) - { - case { A: { IsConstant: true } a, B.IsConstant: false }: - instruction = instruction with { C = instructions[a].C }; - break; - case { A.IsConstant: false, B: { IsConstant: true } b }: - instruction = instruction with { C = instructions[b].C }; - break; - case { OpCode: var opCode, A: { IsConstant: true } a, B: { IsConstant: true } b }: - instruction = instruction with { C = EvaluateExpression(opCode, instructions[a].C, instructions[b].C) }; - break; - case { A.IsConstant: false, B.IsConstant: false }: - break; - } - break; - } + case OpCode.Add or OpCode.Sub or OpCode.Mul when instruction.A.IsConstant || instruction.B.IsConstant: + switch (instruction) + { + case { A: { IsConstant: true } a, B.IsConstant: false }: + instruction = instruction with { C = instructions[a].C }; + break; + case { A.IsConstant: false, B: { IsConstant: true } b }: + instruction = instruction with { C = instructions[b].C }; + break; + case { OpCode: var opCode, A: { IsConstant: true } a, B: { IsConstant: true } b }: + instruction = instruction with { C = EvaluateExpression(opCode, instructions[a].C, instructions[b].C) }; + break; + case { A.IsConstant: false, B.IsConstant: false }: + break; + } + break; } - - // relocate all offsets, now that we've nuked all the constants - foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions)) - { - switch (instruction.OpCode) - { - case OpCode.Const: - int offset = instruction.Out; - foreach (ref Instruction otherInstruction in CollectionsMarshal.AsSpan(instructions)) - { - int newOperandOut = otherInstruction.Out >= offset - ? otherInstruction.Out - 1 - : otherInstruction.Out; - - int newOperandA = otherInstruction.A >= offset - ? otherInstruction.A - 1 - : otherInstruction.A; - - int newOperandB = otherInstruction.B >= offset - ? otherInstruction.B - 1 - : otherInstruction.B; - - if (newOperandOut == otherInstruction.Out - && newOperandA == otherInstruction.A - && newOperandB == otherInstruction.B) - { - continue; - } - - otherInstruction = otherInstruction with - { - Out = new Operand(newOperandOut), - A = new Operand(newOperandA, isConstant: otherInstruction.A.IsConstant), - B = new Operand(newOperandB, isConstant: otherInstruction.B.IsConstant), - }; - } - break; - } - } - - // eliminate all constants now :) - int totalNumberOfInstructions = instructions.Count; - int numberOfRemovedInstructions = instructions.RemoveAll(i => i.OpCode == OpCode.Const); - Console.WriteLine($"Sharpero eliminated {numberOfRemovedInstructions} constants from tape, {(float)numberOfRemovedInstructions / totalNumberOfInstructions * 100.0f:0.0} % of total"); } - return new EvaluationInstructions(instructions.ToArray()); - } + // relocate all offsets, now that we've nuked all the constants + foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions)) + { + switch (instruction.OpCode) + { + case OpCode.Const: + int offset = instruction.Out; + foreach (ref Instruction otherInstruction in CollectionsMarshal.AsSpan(instructions)) + { + int newOperandOut = otherInstruction.Out >= offset + ? otherInstruction.Out - 1 + : otherInstruction.Out; + + int newOperandA = otherInstruction.A >= offset + ? otherInstruction.A - 1 + : otherInstruction.A; + + int newOperandB = otherInstruction.B >= offset + ? otherInstruction.B - 1 + : otherInstruction.B; + + if (newOperandOut == otherInstruction.Out + && newOperandA == otherInstruction.A + && newOperandB == otherInstruction.B) + { + continue; + } + + otherInstruction = otherInstruction with + { + Out = new Operand(newOperandOut), + A = new Operand(newOperandA, isConstant: otherInstruction.A.IsConstant), + B = new Operand(newOperandB, isConstant: otherInstruction.B.IsConstant), + }; + } + break; + } + } + + int totalNumberOfInstructions = instructions.Count; + int numberOfRemovedInstructions = instructions.RemoveAll(i => i.OpCode == OpCode.Const); + return (TotalInstructions: totalNumberOfInstructions, NumberOfRemovedInstructions: numberOfRemovedInstructions); + } } [Flags] diff --git a/README.md b/README.md new file mode 100644 index 0000000..72456d7 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +Sharpero +---------- +This is an implementation of an Interpreter/Compiler for the [Prospero](https://www.mattkeeter.com/projects/prospero/) challenge, implementing some basic optimizations including removing constants from the instruction tape, vectorization, parallelization but also optionally compiling the loop of the evaluator to [CIL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) prior to invocation. + +It doesn't perform any sophisticated interval-arithmetic based optimizations (... yet), it's essentially a brute-force approach. + +This program is also an interactive visualizer of the rendering process, allowing you to see exactly how it's writing the image data out, and toggle vectorization, parallel execution, compilation on/off and observe the effects on runtime. + +On my own machine (CPU: Ryzen 7 4800HS), the results tabulate roughly as follows. + +# (Crude) Benchmark Results +| Compilation | Parallelism | Vectorization | Evaluation Time | Compilation Time | +|-------------|-------------|---------------|-----------------|------------------| +| enabled | enabled | enabled | 0.2s | 0.2s | +| enabled | enabled | disabled | 1.3s | 0.2s | +| enabled | disabled | enabled | 1.7s | 0.2s | +| enabled | disabled | disabled | 10s | 0.2s | +| disabled | enabled | enabled | 0.7s | N/A | +| disabled | enabled | disabled | 5.0s | N/A | +| disabled | disabled | disabled | 48s | N/A |