Skip to content

Commit

Permalink
Refactor the PeepholeOptimizer into arbitrary passes (#2069)
Browse files Browse the repository at this point in the history
Co-authored-by: ike709 <ike709@github.com>
ike709 and ike709 authored Nov 21, 2024
1 parent 1f067c8 commit 12b9f6e
Showing 4 changed files with 537 additions and 428 deletions.
1 change: 1 addition & 0 deletions DMCompiler/Optimizer/BytecodeOptimizer.cs
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ internal void Optimize(DMCompiler compiler, List<IAnnotatedBytecode> input) {
RemoveUnreferencedLabels(input);
JoinAndForwardLabels(input);
RemoveUnreferencedLabels(input);

PeepholeOptimizer.RunPeephole(compiler, input);
}

378 changes: 378 additions & 0 deletions DMCompiler/Optimizer/CompactorOptimizations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
using DMCompiler.Bytecode;

// ReSharper disable UnusedType.Global

namespace DMCompiler.Optimizer;

#region BytecodeCompactors

// PushString [string]
// ...
// PushString [string]
// -> PushNStrings [count] [string] ... [string]
internal sealed class PushNStrings : IOptimization {
public OptPass OptimizationPass => OptPass.BytecodeCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushString,
DreamProcOpcode.PushString
];
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
int count = 0;
int stackDelta = 0;

while (index + count < input.Count &&
input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString }) {
count++;
}

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) };

for (int i = 0; i < count; i++) {
AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]);
args.Add(instruction.GetArg(0));
stackDelta++;
}

input.RemoveRange(index, count);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNStrings, stackDelta, args));
}
}

// PushFloat [float]
// ...
// PushFloat [float]
// -> PushNFloats [count] [float] ... [float]
internal sealed class PushNFloats : IOptimization {
public OptPass OptimizationPass => OptPass.BytecodeCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushFloat,
DreamProcOpcode.PushFloat
];
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
int count = 0;
int stackDelta = 0;

while (index + count < input.Count &&
input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) {
count++;
}

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) };

for (int i = 0; i < count; i++) {
AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]);
args.Add(instruction.GetArg(0));
stackDelta++;
}

input.RemoveRange(index, count);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNFloats, stackDelta, args));
}
}

// PushReferenceValue [ref]
// ...
// PushReferenceValue [ref]
// -> PushNRef [count] [ref] ... [ref]
internal sealed class PushNRef : IOptimization {
public OptPass OptimizationPass => OptPass.BytecodeCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushReferenceValue,
DreamProcOpcode.PushReferenceValue
];
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
int count = 0;
int stackDelta = 0;

while (index + count < input.Count &&
input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushReferenceValue }) {
count++;
}

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) };

for (int i = 0; i < count; i++) {
AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]);
args.Add(instruction.GetArg(0));
stackDelta++;
}

input.RemoveRange(index, count);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNRefs, stackDelta, args));
}
}

// PushString [string]
// PushFloat [float]
// -> PushStringFloat [string] [float]
// or if there's multiple
// -> PushNOfStringFloat [count] [string] [float] ... [string] [float]
internal sealed class PushStringFloat : IOptimization {
public OptPass OptimizationPass => OptPass.BytecodeCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushString,
DreamProcOpcode.PushFloat
];
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

int count = 0;
while (index + count*2 + 1 < input.Count &&
input[index + count * 2] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString } && input[index + count * 2 + 1] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) {
count++;
}

// If the pattern only occurs once, replace with PushStringFloat and return
if (count == 1) {
AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]);
AnnotatedBytecodeString pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeString>(0);
AnnotatedBytecodeFloat pushVal2 = secondInstruction.GetArg<AnnotatedBytecodeFloat>(0);

input.RemoveRange(index, 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushStringFloat, [pushVal1, pushVal2]));
return;
}

// Otherwise, replace with PushNOfStringFloat

int stackDelta = 0;
List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(2 * count + 1) { new AnnotatedBytecodeInteger(count, input[index].GetLocation()) };

for (int i = 0; i < count; i++) {
AnnotatedBytecodeInstruction stringInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2]);
AnnotatedBytecodeInstruction floatInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2 + 1]);
args.Add(stringInstruction.GetArg<AnnotatedBytecodeString>(0));
args.Add(floatInstruction.GetArg<AnnotatedBytecodeFloat>(0));
stackDelta += 2;
}

input.RemoveRange(index, count * 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNOfStringFloats, stackDelta, args));
}
}

// PushResource [resource]
// ...
// PushResource [resource]
// -> PushNResources [count] [resource] ... [resource]
internal sealed class PushNResources : IOptimization {
public OptPass OptimizationPass => OptPass.BytecodeCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushResource,
DreamProcOpcode.PushResource
];
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
int count = 0;
int stackDelta = 0;
while (index + count < input.Count &&
input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushResource }) {
count++;
}

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) };

for (int i = 0; i < count; i++) {
AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]);
args.Add(instruction.GetArg(0));
stackDelta++;
}

input.RemoveRange(index, count);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNResources, stackDelta, args));
}
}

#endregion

#region ListCompactors

// PushNFloats [count] [float] ... [float]
// CreateList [count]
// -> CreateListNFloats [count] [float] ... [float]
internal sealed class CreateListNFloats : IOptimization {
public OptPass OptimizationPass => OptPass.ListCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushNFloats,
DreamProcOpcode.CreateList
];
}

public bool CheckPreconditions(List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;
int pushVal2 = secondInstruction.GetArg<AnnotatedBytecodeListSize>(0).Size;

return pushVal1 == pushVal2;
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) };
args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]);

input.RemoveRange(index, 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNFloats, 1, args));
}
}

// PushNStrings [count] [string] ... [string]
// CreateList [count]
// -> CreateListNStrings [count] [string] ... [string]
internal sealed class CreateListNStrings : IOptimization {
public OptPass OptimizationPass => OptPass.ListCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushNStrings,
DreamProcOpcode.CreateList
];
}

public bool CheckPreconditions(List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;
int pushVal2 = secondInstruction.GetArg<AnnotatedBytecodeListSize>(0).Size;

return pushVal1 == pushVal2;
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) };
args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]);

input.RemoveRange(index, 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNStrings, 1, args));
}
}

// PushNResources [count] [resource] ... [resource]
// CreateList [count]
// -> CreateListNResources [count] [resource] ... [resource]
internal sealed class CreateListNResources : IOptimization {
public OptPass OptimizationPass => OptPass.ListCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushNResources,
DreamProcOpcode.CreateList
];
}

public bool CheckPreconditions(List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;
int pushVal2 = secondInstruction.GetArg<AnnotatedBytecodeListSize>(0).Size;

return pushVal1 == pushVal2;
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list.");
}

AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) };
args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]);

input.RemoveRange(index, 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNResources, 1, args));
}
}

// PushNRefs [count] [ref] ... [ref]
// CreateList [count]
// -> CreateListNRefs [count] [ref] ... [ref]
internal sealed class CreateListNRefs : IOptimization {
public OptPass OptimizationPass => OptPass.ListCompactor;

public ReadOnlySpan<DreamProcOpcode> GetOpcodes() {
return [
DreamProcOpcode.PushNRefs,
DreamProcOpcode.CreateList
];
}

public bool CheckPreconditions(List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index),"Bytecode index is outside the bounds of the input list.");
}

int pushVal1 = ((AnnotatedBytecodeInstruction)input[index]).GetArg<AnnotatedBytecodeInteger>(0).Value;
int pushVal2 = ((AnnotatedBytecodeInstruction)input[index + 1]).GetArg<AnnotatedBytecodeListSize>(0).Size;

return pushVal1 == pushVal2;
}

public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index) {
if (index + 1 >= input.Count) {
throw new ArgumentOutOfRangeException(nameof(index), "Bytecode index is outside the bounds of the input list.");
}

var firstInstruction = (AnnotatedBytecodeInstruction)(input[index]);
int pushVal1 = firstInstruction.GetArg<AnnotatedBytecodeInteger>(0).Value;

List<IAnnotatedBytecode> args = new List<IAnnotatedBytecode>(1 + pushVal1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) };
args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]);

input.RemoveRange(index, 2);
input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNRefs, 1, args));
}
}

#endregion
527 changes: 112 additions & 415 deletions DMCompiler/Optimizer/PeepholeOptimizations.cs

Large diffs are not rendered by default.

59 changes: 46 additions & 13 deletions DMCompiler/Optimizer/PeepholeOptimizer.cs
Original file line number Diff line number Diff line change
@@ -3,7 +3,11 @@

namespace DMCompiler.Optimizer;

internal interface IPeepholeOptimization {
/// <summary>
/// A single peephole optimization (e.g. const fold an operator)
/// </summary>
internal interface IOptimization {
public OptPass OptimizationPass { get; }
public ReadOnlySpan<DreamProcOpcode> GetOpcodes();
public void Apply(DMCompiler compiler, List<IAnnotatedBytecode> input, int index);

@@ -25,44 +29,66 @@ public static void ReplaceInstructions(List<IAnnotatedBytecode> input, int index
}
}

/// <summary>
/// The list of peephole optimizer passes in the order that they should run
/// </summary>
internal enum OptPass : byte {
PeepholeOptimization = 0, // First-pass peephole optimizations (e.g. const folding)
BytecodeCompactor = 1, // Next-pass bytecode compacting (e.g. PushNFloats and other PushN opcodes)
ListCompactor = 2 // Final-pass list compacting (e.g. PushNFloats & CreateList -> CreateListNFloats)
}

// ReSharper disable once ClassNeverInstantiated.Global
internal sealed class PeepholeOptimizer {
private class OptimizationTreeEntry {
public IPeepholeOptimization? Optimization;
public IOptimization? Optimization;
public Dictionary<DreamProcOpcode, OptimizationTreeEntry>? Children;
}

/// <summary>
/// The optimization passes in the order that they run
/// </summary>
private static readonly OptPass[] Passes;

/// <summary>
/// Trees matching chains of opcodes to peephole optimizations
/// </summary>
private static readonly Dictionary<DreamProcOpcode, OptimizationTreeEntry> OptimizationTrees = new();
private static readonly Dictionary<DreamProcOpcode, OptimizationTreeEntry>[] OptimizationTrees;

/// Setup <see cref="OptimizationTrees"/>
private PeepholeOptimizer(DMCompiler compiler) {
var possibleTypes = typeof(PeepholeOptimizer).Assembly.GetTypes();
static PeepholeOptimizer() {
Passes = (OptPass[])Enum.GetValues(typeof(OptPass));
OptimizationTrees = new Dictionary<DreamProcOpcode, OptimizationTreeEntry>[Passes.Length];
for (int i = 0; i < OptimizationTrees.Length; i++) {
OptimizationTrees[i] = new Dictionary<DreamProcOpcode, OptimizationTreeEntry>();
}
}

/// Setup <see cref="OptimizationTrees"/> for each <see cref="OptPass"/>
private static void GetOptimizations(DMCompiler compiler) {
var possibleTypes = typeof(IOptimization).Assembly.GetTypes();
var optimizationTypes = new List<Type>(possibleTypes.Length);

foreach (var type in possibleTypes) {
if (typeof(IPeepholeOptimization).IsAssignableFrom(type)) {
if (typeof(IOptimization).IsAssignableFrom(type) && type is { IsClass: true, IsAbstract: false }) {
optimizationTypes.Add(type);
}
}

foreach (var optType in optimizationTypes) {
if (optType.IsInterface || optType.IsAbstract)
continue;
var opt = (IOptimization)(Activator.CreateInstance(optType)!);

var opt = (IPeepholeOptimization)(Activator.CreateInstance(optType))!;
var opcodes = opt.GetOpcodes();
if (opcodes.Length < 2) {
compiler.ForcedError(Location.Internal, $"Peephole optimization {optType} must have at least 2 opcodes");
continue;
}

if (!OptimizationTrees.TryGetValue(opcodes[0], out var treeEntry)) {
if (!OptimizationTrees[(byte)opt.OptimizationPass].TryGetValue(opcodes[0], out var treeEntry)) {
treeEntry = new() {
Children = new()
};

OptimizationTrees.Add(opcodes[0], treeEntry);
OptimizationTrees[(byte)opt.OptimizationPass].Add(opcodes[0], treeEntry);
}

for (int i = 1; i < opcodes.Length; i++) {
@@ -82,6 +108,13 @@ private PeepholeOptimizer(DMCompiler compiler) {
}

public static void RunPeephole(DMCompiler compiler, List<IAnnotatedBytecode> input) {
GetOptimizations(compiler);
foreach (var optPass in Passes) {
RunPass(compiler, (byte)optPass, input);
}
}

private static void RunPass(DMCompiler compiler, byte pass, List<IAnnotatedBytecode> input) {
OptimizationTreeEntry? currentOpt = null;
int optSize = 0;

@@ -116,7 +149,7 @@ int AttemptCurrentOpt(int i) {

if (currentOpt == null) {
optSize = 1;
OptimizationTrees.TryGetValue(opcode, out currentOpt);
OptimizationTrees[pass].TryGetValue(opcode, out currentOpt);
continue;
}

0 comments on commit 12b9f6e

Please sign in to comment.