add a little readme, factor things properly

This commit is contained in:
profan 2026-06-07 18:32:43 +01:00
parent b8c9d55258
commit 335a9cc99a
2 changed files with 112 additions and 73 deletions

View File

@ -330,7 +330,23 @@ internal static class Parsing
public static EvaluationInstructions Parse(string filename)
{
var identifiers = new Dictionary<string, Operand>();
// parse all identifiers first, so we know which are directly associated with constant values
Dictionary<string, Operand> identifiers = ParseIdentifiers(filename);
// now parse instructions, correctly tagging operands that operate directly on constants
List<Instruction> 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<string, Operand> ParseIdentifiers(string filename)
{
Dictionary<string, Operand> identifiers = [];
foreach (string line in File.ReadAllLines(filename))
{
if (line.StartsWith('#'))
@ -358,7 +374,13 @@ internal static class Parsing
}
}
return identifiers;
}
private static List<Instruction> ParseInstructions(string filename, Dictionary<string, Operand> identifiers)
{
List<Instruction> instructions = [];
foreach (string line in File.ReadAllLines(filename))
{
if (line.StartsWith('#'))
@ -370,7 +392,7 @@ internal static class Parsing
{
[{ } @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,80 +410,77 @@ internal static class Parsing
}
}
bool shouldEliminateConstants = true;
return instructions;
}
if (shouldEliminateConstants)
private static (int TotalInstructions, int NumberOfRemovedInstructions) EliminateConstants(List<Instruction> 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);
}
}

20
README.md Normal file
View File

@ -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 |