Skip to content

Commit c9d8479

Browse files
committed
1.0.12
Simplify jump instructions pointing to more jumps Allow TCO to apply to Call + Jump to return
1 parent 2c6c61e commit c9d8479

10 files changed

Lines changed: 134 additions & 31 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using UdonSharp.Compiler.Assembly;
3+
using UdonSharp.Compiler.Assembly.Instructions;
4+
using UnityEditor;
5+
6+
namespace UdonSharpOptimizer.Optimizations
7+
{
8+
internal class OPTDirectJump : IBaseOptimization
9+
{
10+
private int patchedInstructions;
11+
12+
public bool Enabled => OptimizerSettings.Instance.DirectJump;
13+
14+
public void ResetStats()
15+
{
16+
patchedInstructions = 0;
17+
}
18+
19+
public void OnGUI()
20+
{
21+
OptimizerEditorWindow.AlignedText("Direct Jump", patchedInstructions.ToString(), EditorStyles.label);
22+
}
23+
24+
public void ProcessInstruction(Optimizer optimizer, List<AssemblyInstruction> instrs, int i)
25+
{
26+
// Simplify jump chains
27+
if (instrs[i] is JumpInstruction jInst)
28+
{
29+
JumpLabel innerJump = jInst.JumpTarget;
30+
int chain = 0;
31+
while (optimizer.GetJumpTarget(innerJump.Address) is JumpInstruction nextJump)
32+
{
33+
innerJump = nextJump.JumpTarget;
34+
chain++;
35+
}
36+
if (innerJump.Address != jInst.JumpTarget.Address)
37+
{
38+
instrs[i] = optimizer.TransferInstr(new JumpInstruction(innerJump), jInst);
39+
Comment comment = new Comment($"OPTDirectJump: Skipped {chain} jumps");
40+
comment.InstructionAddress = instrs[i].InstructionAddress;
41+
instrs.Insert(i, comment);
42+
patchedInstructions += chain;
43+
}
44+
}
45+
else if (instrs[i] is JumpIfFalseInstruction jifInst)
46+
{
47+
JumpLabel innerJump = jifInst.JumpTarget;
48+
int chain = 0;
49+
while (optimizer.GetJumpTarget(innerJump.Address) is JumpInstruction nextJump)
50+
{
51+
innerJump = nextJump.JumpTarget;
52+
chain++;
53+
}
54+
if (innerJump.Address != jifInst.JumpTarget.Address)
55+
{
56+
instrs[i] = optimizer.TransferInstr(new JumpIfFalseInstruction(innerJump, jifInst.ConditionValue), jifInst);
57+
Comment comment = new Comment($"OPTDirectJump: Skipped {chain} jumps");
58+
comment.InstructionAddress = instrs[i].InstructionAddress;
59+
instrs.Insert(i, comment);
60+
patchedInstructions += chain;
61+
}
62+
}
63+
}
64+
}
65+
}

Packages/blueamulet.udonsharpoptimizer/Editor/Optimizations/OPTDirectJump.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/blueamulet.udonsharpoptimizer/Editor/Optimizations/OPTTailCall.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,31 @@ public override void ProcessInstruction(Optimizer optimizer, List<AssemblyInstru
1515
{
1616
// Tail call optimization
1717
// TODO: Properly verify this jump is to a method
18-
if (instrs[i] is JumpInstruction && instrs[i + 1] is RetInstruction rInst && instrs[i - 1] is Comment cInst && cInst.Comment.StartsWith("Calling "))
18+
if (instrs[i] is JumpInstruction && instrs[i - 1] is Comment cInst && cInst.Comment.StartsWith("Calling "))
1919
{
20+
// Try to locate a return after the call
21+
RetInstruction rInst = null;
22+
JumpInstruction jInst = null;
23+
if (instrs[i + 1] is RetInstruction inst)
24+
{
25+
rInst = inst;
26+
}
27+
else if (instrs[i + 1] is JumpInstruction inst2 && optimizer.GetJumpTarget(inst2.JumpTarget.Address) is RetInstruction inst3)
28+
{
29+
rInst = inst3;
30+
jInst = inst2;
31+
}
32+
33+
if (rInst == null)
34+
{
35+
return;
36+
}
2037
// Locate the corresponding push above
2138
int pushIdx = -1;
39+
uint afterCall = instrs[i + 1].InstructionAddress;
2240
for (int j = i - 1; j >= 0; j--)
2341
{
24-
if (instrs[j] is PushInstruction pInst && pInst.PushValue.Flags == Value.ValueFlags.InternalGlobal && pInst.PushValue.DefaultValue is uint val && pInst.PushValue.UniqueID.StartsWith("__gintnl_RetAddress_") && val == rInst.InstructionAddress)
42+
if (instrs[j] is PushInstruction pInst && pInst.PushValue.Flags == Value.ValueFlags.InternalGlobal && pInst.PushValue.DefaultValue is uint val && pInst.PushValue.UniqueID.StartsWith("__gintnl_RetAddress_") && val == afterCall)
2543
{
2644
pushIdx = j;
2745
break;
@@ -35,14 +53,19 @@ public override void ProcessInstruction(Optimizer optimizer, List<AssemblyInstru
3553
{
3654
PushInstruction pInst = (PushInstruction)instrs[pushIdx];
3755
instrs[pushIdx] = optimizer.TransferInstr(new Comment($"OPTTailCall: Tail call optimization, removed PUSH {pInst.PushValue.UniqueID}"), instrs[pushIdx]);
38-
if (!optimizer.HasJump(rInst))
56+
CountRemoved(optimizer, 1); // PUSH
57+
if (jInst != null)
3958
{
40-
instrs[i + 1] = optimizer.TransferInstr(new Comment($"OPTTailCall: Tail call optimization, removed RET {rInst.RetValRef.UniqueID}"), rInst);
41-
CountRemoved(optimizer, 4); // PUSH & PUSH + COPY + JUMP_INDIRECT
59+
if (!optimizer.HasJump(jInst))
60+
{
61+
instrs[i + 1] = optimizer.TransferInstr(new Comment($"OPTTailCall: Tail call optimization, removed JUMP to RET"), jInst);
62+
CountRemoved(optimizer, 1); // JUMP
63+
}
4264
}
43-
else
65+
else if (!optimizer.HasJump(rInst))
4466
{
45-
CountRemoved(optimizer, 1); // PUSH
67+
instrs[i + 1] = optimizer.TransferInstr(new Comment($"OPTTailCall: Tail call optimization, removed RET {rInst.RetValRef.UniqueID}"), rInst);
68+
CountRemoved(optimizer, 3); // RET (PUSH + COPY + JUMP_INDIRECT)
4669
}
4770
}
4871
}

Packages/blueamulet.udonsharpoptimizer/Editor/Optimizer.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Unofficial UdonSharp Optimizer
33
* The Optimizer.
4-
* Version 1.0.11
4+
* Version 1.0.12
55
* Written by BlueAmulet
66
*/
77

@@ -50,13 +50,15 @@ internal class Optimizer
5050
new OPTStoreCopy(),
5151
new OPTDoubleCopy(),
5252
new OPTUnreadCopy(),
53+
new OPTDirectJump(),
5354
new OPTTailCall(),
5455
};
5556

5657
// Per program state
5758
private readonly EmitContext _moduleEmitContext;
5859
private List<AssemblyInstruction> _instrs;
5960
private HashSet<AssemblyInstruction> _hasJump;
61+
private Dictionary<uint, AssemblyInstruction> _instrMap;
6062
private Dictionary<string, Value> _tempTable = new Dictionary<string, Value>();
6163
internal int removedInsts = 0;
6264

@@ -136,6 +138,11 @@ internal bool HasJump(int min, int max)
136138
return false;
137139
}
138140

141+
internal AssemblyInstruction GetJumpTarget(uint address)
142+
{
143+
return _instrMap[address];
144+
}
145+
139146
// Full code scan, except optimizable patterns are ignored
140147
internal bool ReadScan(Func<int, bool> ignore, Value value)
141148
{
@@ -182,6 +189,7 @@ internal void OptimizeProgram()
182189

183190
_instrs = new List<AssemblyInstruction>();
184191
_hasJump = new HashSet<AssemblyInstruction>();
192+
_instrMap = new Dictionary<uint, AssemblyInstruction>();
185193
AssemblyModule assemblyModule = _moduleEmitContext.Module;
186194
HashSet<JumpLabel> jumpLabels = new HashSet<JumpLabel>();
187195
List<Value> addrValues = new List<Value>();
@@ -232,6 +240,7 @@ internal void OptimizeProgram()
232240
if (inst.Size != 0 && inst.InstructionAddress == jumpEnumerator.Current)
233241
{
234242
_hasJump.Add(inst);
243+
_instrMap.Add(inst.InstructionAddress, inst);
235244
while (inst.InstructionAddress == jumpEnumerator.Current)
236245
{
237246
if (!jumpEnumerator.MoveNext())
@@ -597,17 +606,6 @@ internal void OptimizeProgram()
597606
addressMap.Add(assemblyModule.CurrentAddress, currentAddress);
598607
_instructions(assemblyModule) = _instrs;
599608

600-
// Add comments to instructions that can be jumped to
601-
/*
602-
for (int i = instrs.Count - 1; i >= 0; i--)
603-
{
604-
if (hasJump.Contains(instrs[i]))
605-
{
606-
instrs.Insert(i, new Comment($"Jump Target: 0x{instrs[i].InstructionAddress:X8}"));
607-
}
608-
}
609-
//*/
610-
611609
// Update jump addresses
612610
foreach (JumpLabel jumpLabel in jumpLabels)
613611
{
@@ -757,6 +755,7 @@ internal AssemblyInstruction TransferInstr(AssemblyInstruction instr, AssemblyIn
757755
{
758756
_hasJump.Remove(original);
759757
_hasJump.Add(instr);
758+
_instrMap[instr.InstructionAddress] = instr;
760759
}
761760
return instr;
762761
}

Packages/blueamulet.udonsharpoptimizer/Editor/OptimizerEditorWindow.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal class OptimizerEditorWindow : EditorWindow
2323
SerializedProperty _optStoreAndCopy;
2424
SerializedProperty _optDoubleCopy;
2525
SerializedProperty _optCleanUnreadCopy;
26+
SerializedProperty _optDirectJump;
2627
SerializedProperty _optTCO;
2728
SerializedProperty _optVariables;
2829
SerializedProperty _optBlockReduction;
@@ -51,6 +52,7 @@ public void OnEnable()
5152
_optStoreAndCopy = _settingsSO.FindProperty(nameof(OptimizerSettings.StoreAndCopy));
5253
_optDoubleCopy = _settingsSO.FindProperty(nameof(OptimizerSettings.DoubleCopy));
5354
_optCleanUnreadCopy = _settingsSO.FindProperty(nameof(OptimizerSettings.CleanUnreadCopy));
55+
_optDirectJump = _settingsSO.FindProperty(nameof(OptimizerSettings.DirectJump));
5456
_optTCO = _settingsSO.FindProperty(nameof(OptimizerSettings.EnableTCO));
5557
_optVariables = _settingsSO.FindProperty(nameof(OptimizerSettings.EnableVariableReduction));
5658
_optBlockReduction = _settingsSO.FindProperty(nameof(OptimizerSettings.EnableBlockReduction));
@@ -112,7 +114,8 @@ public void OnGUI()
112114
EditorGUILayout.PropertyField(_optStoreAndCopy, false);
113115
EditorGUILayout.PropertyField(_optDoubleCopy, false);
114116
EditorGUILayout.PropertyField(_optCleanUnreadCopy, false);
115-
EditorGUILayout.PropertyField(_optTCO, false);
117+
EditorGUILayout.PropertyField(_optDirectJump, false);
118+
EditorGUILayout.PropertyField(_optTCO, new GUIContent("Optimize Tail Calls"), false);
116119
EditorGUILayout.Space();
117120
EditorGUILayout.PropertyField(_optVariables, new GUIContent("Reduce Variables"), false);
118121
using (new EditorGUI.DisabledScope(!_settings.EnableVariableReduction))

Packages/blueamulet.udonsharpoptimizer/Editor/OptimizerInject.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ internal static class OptimizerInject
3030

3131
private static readonly MethodInfo optimizerInject = AccessTools.Method(typeof(OptimizerInject), nameof(OptimizeHook));
3232

33-
private static bool _patchSuccess;
34-
public static bool PatchSuccess => _patchSuccess;
33+
public static bool PatchSuccess { get; private set; }
3534

36-
private static int _patchFailures;
37-
public static int PatchFailures => _patchFailures;
35+
public static int PatchFailures { get; private set; }
3836

3937
static OptimizerInject()
4038
{
@@ -49,7 +47,7 @@ private static void RunPostAssemblyBuildRefresh()
4947
using (new UdonSharpUtils.UdonSharpAssemblyLoadStripScope())
5048
{
5149
Harmony.UnpatchAll(HARMONY_ID);
52-
_patchFailures = 0;
50+
PatchFailures = 0;
5351

5452
// Inject Optimizer into UdonSharp
5553
MethodInfo target = AccessTools.Method(typeof(UdonSharpCompilerV1), "EmitAllPrograms");
@@ -151,7 +149,7 @@ private static IEnumerable<CodeInstruction> TranspilerDump(IEnumerable<CodeInstr
151149
}
152150
}
153151

154-
_patchSuccess = patched || already;
152+
PatchSuccess = patched || already;
155153
if (patched)
156154
{
157155
Debug.Log("[Optimizer] Activated");
@@ -210,7 +208,7 @@ private static IEnumerable<CodeInstruction> ReturnValueTranspiler(IEnumerable<Co
210208
if (!patched)
211209
{
212210
Debug.LogWarning("[Optimizer] Failed to add debug string to return value");
213-
_patchFailures++;
211+
PatchFailures++;
214212
return instrEnumerator;
215213
}
216214

@@ -259,7 +257,7 @@ private static IEnumerable<CodeInstruction> SwitchTableTranspiler(IEnumerable<Co
259257

260258
if (!patched)
261259
{
262-
_patchFailures++;
260+
PatchFailures++;
263261
Debug.LogWarning("[Optimizer] Failed to add debug string to switch jump table");
264262
return instrEnumerator;
265263
}

Packages/blueamulet.udonsharpoptimizer/Editor/OptimizerSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ internal class OptimizerSettings : ScriptableObject
3030
public bool DoubleCopy = true;
3131
[Tooltip("Targets Unread COPY (Cow dirty)")]
3232
public bool CleanUnreadCopy = true;
33+
[Tooltip("Simplify JUMP => JUMP")]
34+
public bool DirectJump = true;
3335
[Tooltip("Performs Tail Call Optimization")]
3436
public bool EnableTCO = true;
3537
[Tooltip("Reduce amount of temporary variables")]

Packages/blueamulet.udonsharpoptimizer/README.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ Changelog:
2323
1.0.9 - Moved TCO into first pass, added block based variable reduction
2424
1.0.9b - Fixed udon functions destroying variables in other functions
2525
1.0.10 - Code refactor, added additional instruction and variable optimizations
26-
1.0.11 - Per optimization statistics, Unity 2019 fix, expanded TCO optimization
26+
1.0.11 - Per optimization statistics, Unity 2019 fix, expanded TCO optimization
27+
1.0.12 - Simplify jump chains, further expaned TCO optimization

Packages/blueamulet.udonsharpoptimizer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "blueamulet.udonsharpoptimizer",
33
"displayName": "UdonSharpOptimizer",
4-
"version": "1.0.11",
4+
"version": "1.0.12",
55
"description": "UdonSharp postprocessor to reduce instruction and variable counts",
66
"gitDependencies": {},
77
"vpmDependencies": {

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ UdonSharp makes a *LOT* of temporary variables. We detect places where we can re
3131
1.0.9 - Moved TCO into first pass, added block based variable reduction
3232
1.0.9b - Fixed udon functions destroying variables in other functions
3333
1.0.10 - Code refactor, added additional instruction and variable optimizations
34-
1.0.11 - Per optimization statistics, Unity 2019 fix, expanded TCO optimization
34+
1.0.11 - Per optimization statistics, Unity 2019 fix, expanded TCO optimization
35+
1.0.12 - Simplify jump chains, further expaned TCO optimization

0 commit comments

Comments
 (0)