implement basic constant folding, eliminating around 17 % of the instructions from the hot loop and folding it into the instructions
This commit is contained in:
parent
5f00120b97
commit
3625fce0c9
|
|
@ -1 +0,0 @@
|
|||
Sharpero
|
||||
214
Program.cs
214
Program.cs
|
|
@ -8,6 +8,7 @@ using System.Numerics;
|
|||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using SkiaSharp;
|
||||
|
||||
|
|
@ -55,7 +56,48 @@ void CreateOutputImage(int imageSize, float[] imageData, string imageOutputPath)
|
|||
|
||||
internal enum OpCode { VarX, VarY, Const, Add, Sub, Mul, Max, Min, Neg, Square, Sqrt }
|
||||
|
||||
internal readonly record struct Instruction(int Out, OpCode OpCode, int A = -1, int B = -1, float V = float.MaxValue);
|
||||
internal readonly record struct Operand
|
||||
{
|
||||
private readonly int _value = -1;
|
||||
|
||||
public bool IsConstant => ((_value >> 31) & 1) != 0;
|
||||
public int Value => _value & 0x7FFFFFFF;
|
||||
|
||||
public Operand(int value, bool isConstant = false)
|
||||
{
|
||||
_value = (isConstant ? 1 : 0) << 31 | value & 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
public static implicit operator int(Operand o) => o.Value;
|
||||
public override string ToString() => $"{Value} (constant: {IsConstant.ToString().ToLower()})";
|
||||
}
|
||||
|
||||
internal readonly record struct Instruction(
|
||||
Operand Out,
|
||||
OpCode OpCode,
|
||||
Operand A = default,
|
||||
Operand B = default,
|
||||
float C = 0.0f)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return OpCode switch
|
||||
{
|
||||
OpCode.VarX => $"_{Out} var-x",
|
||||
OpCode.VarY => $"_{Out} var-y",
|
||||
OpCode.Const => $"_{Out} const",
|
||||
OpCode.Add => $"_{Out} add {A} {B}",
|
||||
OpCode.Sub => $"_{Out} sub {A} {B}",
|
||||
OpCode.Mul => $"_{Out} mul {A} {B}",
|
||||
OpCode.Max => $"_{Out} max {A} {B}",
|
||||
OpCode.Min => $"_{Out} min {A} {B}",
|
||||
OpCode.Neg => $"_{Out} neg {A}",
|
||||
OpCode.Square => $"_{Out} square {A}",
|
||||
OpCode.Sqrt => $"_{Out} sqrt {A}",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Parsing
|
||||
{
|
||||
|
|
@ -64,28 +106,64 @@ internal static class Parsing
|
|||
public static (int x, int y) IndexToCoord(int idx, int width) => (idx % width, idx / width);
|
||||
public static int CoordToIndex(int x, int y, int width) => x + (y * width);
|
||||
|
||||
// the identifiers are hexadecimal, stripping off the leading _ is enough :)
|
||||
public static int ParseIdentifier(string id) => Convert.ToInt32(id[1..], 16);
|
||||
|
||||
// the identifiers are hexadecimal, stripping off the leading _ is enough :),
|
||||
public static Operand ParseIdentifier(Dictionary<string, Operand> identifiers, string id, bool isConstant = false)
|
||||
{
|
||||
if (identifiers.TryGetValue(id, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return identifiers[id] = new Operand(Convert.ToInt32(id[1..], 16), isConstant);
|
||||
}
|
||||
|
||||
public static Instruction[] Parse(string filename)
|
||||
{
|
||||
var identifiers = new Dictionary<string, Operand>();
|
||||
foreach (string line in File.ReadAllLines(filename))
|
||||
{
|
||||
switch (line.Split(" "))
|
||||
{
|
||||
case [{ } @out, "const", not null]:
|
||||
ParseIdentifier(identifiers, @out, isConstant: true);
|
||||
break;
|
||||
case [{ } @out, not null, { } a, { } b]:
|
||||
ParseIdentifier(identifiers, @out);
|
||||
ParseIdentifier(identifiers, a);
|
||||
ParseIdentifier(identifiers, b);
|
||||
break;
|
||||
case [{ } @out, not null, { } a]:
|
||||
ParseIdentifier(identifiers, @out);
|
||||
ParseIdentifier(identifiers, a);
|
||||
break;
|
||||
case [{ } @out, not null]:
|
||||
ParseIdentifier(identifiers, @out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<Instruction> instructions = [];
|
||||
|
||||
foreach (string line in File.ReadAllLines(filename))
|
||||
{
|
||||
Instruction? parsedInstruction = line.Split(" ") switch
|
||||
{
|
||||
[{ } @out, "var-x"] => new Instruction(ParseIdentifier(@out), OpCode.VarX),
|
||||
[{ } @out, "var-y"] => new Instruction(ParseIdentifier(@out), OpCode.VarY),
|
||||
[{ } @out, "const", {} v] => new Instruction(ParseIdentifier(@out), OpCode.Const, V: float.Parse(v, CultureInfo.InvariantCulture)),
|
||||
[{ } @out, "add", { } a, { } b] => new Instruction(ParseIdentifier(@out), OpCode.Add, ParseIdentifier(a), ParseIdentifier(b)),
|
||||
[{ } @out, "sub", { } a, { } b] => new Instruction(ParseIdentifier(@out), OpCode.Sub, ParseIdentifier(a), ParseIdentifier(b)),
|
||||
[{ } @out, "mul", { } a, { } b] => new Instruction(ParseIdentifier(@out), OpCode.Mul, ParseIdentifier(a), ParseIdentifier(b)),
|
||||
[{ } @out, "max", { } a, { } b] => new Instruction(ParseIdentifier(@out), OpCode.Max, ParseIdentifier(a), ParseIdentifier(b)),
|
||||
[{ } @out, "min", { } a, { } b] => new Instruction(ParseIdentifier(@out), OpCode.Min, ParseIdentifier(a), ParseIdentifier(b)),
|
||||
[{ } @out, "neg", { } a] => new Instruction(ParseIdentifier(@out), OpCode.Neg, ParseIdentifier(a)),
|
||||
[{ } @out, "square", { } a] => new Instruction(ParseIdentifier(@out), OpCode.Square, ParseIdentifier(a)),
|
||||
[{ } @out, "sqrt", { } a] => new Instruction(ParseIdentifier(@out), OpCode.Sqrt, ParseIdentifier(a)),
|
||||
[{ } @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, "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]),
|
||||
[{ } @out, "max", { } a, { } b] => new Instruction(identifiers[@out], OpCode.Max, identifiers[a],
|
||||
identifiers[b]),
|
||||
[{ } @out, "min", { } a, { } b] => new Instruction(identifiers[@out], OpCode.Min, identifiers[a],
|
||||
identifiers[b]),
|
||||
[{ } @out, "neg", { } a] => new Instruction(identifiers[@out], OpCode.Neg, identifiers[a]),
|
||||
[{ } @out, "square", { } a] => new Instruction(identifiers[@out], OpCode.Square, identifiers[a]),
|
||||
[{ } @out, "sqrt", { } a] => new Instruction(identifiers[@out], OpCode.Sqrt, identifiers[a]),
|
||||
_ => null
|
||||
};
|
||||
|
||||
|
|
@ -95,6 +173,91 @@ 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 = false;
|
||||
|
||||
if (shouldEliminateConstants)
|
||||
{
|
||||
// handle constant propagation, eliminating all constants from the instruction stream and merging them
|
||||
foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions))
|
||||
{
|
||||
switch (instruction.OpCode)
|
||||
{
|
||||
case (OpCode.Add or OpCode.Sub or OpCode.Mul) when instruction.A.IsConstant || instruction.B.IsConstant:
|
||||
{
|
||||
instruction = instruction switch
|
||||
{
|
||||
{ A.IsConstant: true, B.IsConstant: false } => instruction with
|
||||
{
|
||||
C = instructions[instruction.A].C
|
||||
},
|
||||
{ A.IsConstant: false, B.IsConstant: true } => instruction with
|
||||
{
|
||||
C = instructions[instruction.B].C
|
||||
},
|
||||
{ A.IsConstant: true, B.IsConstant: true } => instruction with
|
||||
{
|
||||
C = EvaluateExpression(instruction.OpCode, instructions[instruction.A].C,
|
||||
instructions[instruction.B].C)
|
||||
},
|
||||
{ A.IsConstant: false, B.IsConstant: false } => instruction
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
if (otherInstruction.Out >= offset)
|
||||
{
|
||||
otherInstruction = otherInstruction with { Out = new Operand(otherInstruction.Out - 1) };
|
||||
}
|
||||
|
||||
if (otherInstruction.A >= offset)
|
||||
{
|
||||
otherInstruction = otherInstruction with
|
||||
{
|
||||
A = new Operand(otherInstruction.A - 1, isConstant: otherInstruction.A.IsConstant)
|
||||
};
|
||||
}
|
||||
|
||||
if (otherInstruction.B >= offset)
|
||||
{
|
||||
otherInstruction = otherInstruction with
|
||||
{
|
||||
B = new Operand(otherInstruction.B - 1, 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 instructions.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -187,15 +350,26 @@ internal static class Interpreter
|
|||
{
|
||||
{ OpCode: OpCode.VarX } => xs,
|
||||
{ OpCode: OpCode.VarY } => ys,
|
||||
{ OpCode: OpCode.Add, A: var a, B: var b } => variables[a] + variables[b],
|
||||
{ OpCode: OpCode.Sub, A: var a, B: var b } => variables[a] - variables[b],
|
||||
{ OpCode: OpCode.Mul, A: var a, B: var b } => variables[a] * variables[b],
|
||||
|
||||
{ OpCode: OpCode.Add, A: { IsConstant: false } a, B: { IsConstant: false } b } => variables[a] + variables[b],
|
||||
{ OpCode: OpCode.Add, A.IsConstant: true, B: { IsConstant: false } b } => new Vector<float>(instruction.C) + variables[b],
|
||||
{ OpCode: OpCode.Add, A: { IsConstant: false } a, B.IsConstant: true } => variables[a] + new Vector<float>(instruction.C),
|
||||
|
||||
{ OpCode: OpCode.Sub, A: { IsConstant: false } a, B: { IsConstant: false } b } => variables[a] - variables[b],
|
||||
{ OpCode: OpCode.Sub, A.IsConstant: true, B: { IsConstant: false } b } => new Vector<float>(instruction.C) - variables[b],
|
||||
{ OpCode: OpCode.Sub, A: { IsConstant: false } a, B.IsConstant: true } => variables[a] - new Vector<float>(instruction.C),
|
||||
|
||||
{ OpCode: OpCode.Mul, A: { IsConstant: false } a, B: { IsConstant: false } b } => variables[a] * variables[b],
|
||||
{ OpCode: OpCode.Mul, A.IsConstant: true, B: { IsConstant: false } b } => new Vector<float>(instruction.C) * variables[b],
|
||||
{ OpCode: OpCode.Mul, A: { IsConstant: false } a, B.IsConstant: true } => variables[a] * new Vector<float>(instruction.C),
|
||||
|
||||
{ OpCode: OpCode.Max, A: var a, B: var b } => Vector.Max(variables[a], variables[b]),
|
||||
{ OpCode: OpCode.Min, A: var a, B: var b } => Vector.Min(variables[a], variables[b]),
|
||||
{ OpCode: OpCode.Neg, A: var a } => -variables[a],
|
||||
{ OpCode: OpCode.Sqrt, A: var a } => Vector.SquareRoot(variables[a]),
|
||||
{ OpCode: OpCode.Square, A: var a } => variables[a] * variables[a],
|
||||
{ OpCode: OpCode.Const, V: var v } => Vector<float>.One * v,
|
||||
|
||||
{ OpCode: OpCode.Const, C: var v } => Vector<float>.One * v,
|
||||
_ => variables[instruction.Out]
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APersistedAssemblyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F479daf628d169411ac8526d497865727d69e6e7f4832a1ba6e65e18abf4f90_003FPersistedAssemblyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARuntimeAssemblyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89f78f55baa267d67190b18dceab4e687d397e0573226d8f9d32ecc2271863e_003FRuntimeAssemblyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AScalar_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1a6f5a6d45979b9db8954e4e92811b8d7b853439d7953d79151da4586e57b3_003FScalar_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASKBitmap_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F86313366d68bcb85ab793bdfd1df8614c6d587059aa7347837ac54be1c28_003FSKBitmap_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASKCanvas_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F40c61ebf4349bc39224b986147986231e7ffd8b3b69ac47f370bd607e7a2_003FSKCanvas_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASKPaint_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fb4d6b3f01617f13d8e24965ceb91a3b4bff76e3d61e49788aa782f68597af6a_003FSKPaint_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVector_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7b94acd1ee29851cd828afb28baacf1440add77e5870783fa3e5965f7536aaf_003FVector_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASpan_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc24d475d83442e0a97ce97fdd4d43d6b5a3977b2a474c7996b895330c6b0_003FSpan_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F228bff957d5b9f32cfbedd8bf82a5d3e862ec3e051593a67913b95ae288287_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVector_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7b94acd1ee29851cd828afb28baacf1440add77e5870783fa3e5965f7536aaf_003FVector_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVector_005F1_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F512c07e71314e149661d5edf86fb11fa7d0737832496882904c3c464b6ead_003FVector_005F1_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
Loading…
Reference in New Issue