Skip to content

Commit

Permalink
Merge branch 'master' of github.com:neo-project/neo-devpack-dotnet in…
Browse files Browse the repository at this point in the history
…to unchecked

# Conflicts:
#	tests/Neo.Compiler.CSharp.UnitTests/TestingArtifacts/Contract_GoTo.cs
#	tests/Neo.Compiler.CSharp.UnitTests/TestingArtifacts/Contract_Initializer.cs
#	tests/Neo.Compiler.CSharp.UnitTests/TestingArtifacts/Contract_TryCatch.cs
#	tests/Neo.Compiler.CSharp.UnitTests/UnitTest_GoTo.cs
  • Loading branch information
Hecate2 committed Nov 5, 2024
2 parents 56a5745 + 00b678f commit 261016c
Show file tree
Hide file tree
Showing 27 changed files with 383 additions and 136 deletions.
18 changes: 18 additions & 0 deletions src/Neo.Compiler.CSharp/Optimizer/Analysers/OpCodeTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ static OpCodeTypes()
.ToHashSet();
}

public static OpCode ToLongVersion(OpCode opCode)
{
if (longInstructions.Contains(opCode))
return opCode;
if (!shortInstructions.Contains(opCode))
throw new BadScriptException($"No long version for OpCode {opCode}");
return opCode + 1;
}

public static OpCode ToShortVersion(OpCode opCode)
{
if (shortInstructions.Contains(opCode))
return opCode;
if (!longInstructions.Contains(opCode))
throw new BadScriptException($"No short version for OpCode {opCode}");
return opCode - 1;
}

public static byte SlotIndex(Instruction i)
{
OpCode o = i.OpCode;
Expand Down
16 changes: 10 additions & 6 deletions src/Neo.Compiler.CSharp/Optimizer/AssetBuilder/DebugInfoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ static class DebugInfoBuilder
}
// The instruction at the end of the method may have been deleted.
// We need to find the last instruction that is not deleted.
//int methodEnd = (int)simplifiedInstructionsToAddress[oldAddressToInstruction[oldMethodEnd]]!;
int oldMethodEndNotDeleted = oldAddressToInstruction.Where(kv =>
kv.Key >= oldMethodStart && kv.Key <= oldMethodEnd &&
simplifiedInstructionsToAddress.Contains(kv.Value)
).Max(kv => kv.Key);
int methodEnd = (int)simplifiedInstructionsToAddress[oldAddressToInstruction[oldMethodEndNotDeleted]]!;
int methodEnd;
if (oldSequencePointAddressToNew?.TryGetValue(oldMethodEnd, out methodEnd) != true)
{
//int methodEnd = (int)simplifiedInstructionsToAddress[oldAddressToInstruction[oldMethodEnd]]!;
int oldMethodEndNotDeleted = oldAddressToInstruction.Where(kv =>
kv.Key >= oldMethodStart && kv.Key <= oldMethodEnd &&
simplifiedInstructionsToAddress.Contains(kv.Value)
).Max(kv => kv.Key);
methodEnd = (int)simplifiedInstructionsToAddress[oldAddressToInstruction[oldMethodEndNotDeleted]]!;
}
method["range"] = $"{methodStart}-{methodEnd}";

int previousSequencePoint = methodStart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public static void RetargetJump(Instruction oldTarget, Instruction newTarget,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<Instruction, HashSet<Instruction>> jumpTargetToSources)
{
if (jumpTargetToSources.Remove(oldTarget, out HashSet<Instruction>? sources))
if (jumpTargetToSources.Remove(oldTarget, out HashSet<Instruction>? sources) && sources.Count > 0)
{
foreach (Instruction s in sources)
{
Expand All @@ -108,9 +108,8 @@ public static void RetargetJump(Instruction oldTarget, Instruction newTarget,
}
}
if (jumpTargetToSources.TryGetValue(newTarget, out HashSet<Instruction>? newTargetSources))
newTargetSources.Union(sources);
else
jumpTargetToSources[newTarget] = sources;
sources = newTargetSources.Union(sources).ToHashSet();
jumpTargetToSources[newTarget] = sources;
}
}
}
Expand Down
194 changes: 194 additions & 0 deletions src/Neo.Compiler.CSharp/Optimizer/Strategies/JumpCompresser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,199 @@ public static (NefFile, ContractManifest, JObject?) CompressJump(NefFile nef, Co
while (modified);
return (nef, manifest, debugInfo);
}

/// <summary>
/// Removes JMP and JMP_L that targets the next instruction after the JMP or JMP_L.
/// Replace JMPIF/JMPIFNOT with DROP if it jumps to the next instruction
/// If the removed JMP or JMP_L itself is a jump target,
/// re-target to the instruction after the JMP or JMP_L
/// </summary>
/// <param name="nef"></param>
/// <param name="manifest"></param>
/// <param name="debugInfo"></param>
/// <returns></returns>
[Strategy(Priority = int.MaxValue)]
public static (NefFile, ContractManifest, JObject?) RemoveUnnecessaryJumps(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
Script script = nef.Script;
List<(int a, Instruction i)> oldAddressAndInstructionsList = script.EnumerateInstructions().ToList();
Dictionary<int, Instruction> oldAddressToInstruction = oldAddressAndInstructionsList.ToDictionary(e => e.a, e => e.i);
(Dictionary<Instruction, Instruction> jumpSourceToTargets,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<Instruction, HashSet<Instruction>> jumpTargetToSources) =
FindAllJumpAndTrySourceToTargets(oldAddressAndInstructionsList);
Dictionary<int, int> oldSequencePointAddressToNew = new();

System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress = new();
int currentAddress = 0;
foreach ((int a, Instruction i) in oldAddressAndInstructionsList)
{
if (unconditionalJump.Contains(i.OpCode))
{
int target = ComputeJumpTarget(a, i);
if (target - a == i.Size)
{
// Just jumping to the instruction after the jump itself
// This is unnecessary jump. The jump should be deleted.
// And, if this JMP is the target of other jump instructions,
// re-target to the next instruction after this JMP.
Instruction nextInstruction = oldAddressToInstruction[a + i.Size];
// handle the reference of the deleted JMP
jumpSourceToTargets.Remove(i);
jumpTargetToSources[nextInstruction].Remove(i);
OptimizedScriptBuilder.RetargetJump(i, nextInstruction, jumpSourceToTargets, trySourceToTargets, jumpTargetToSources);
continue; // do not add this JMP into simplified instructions
}
}
if (i.OpCode == OpCode.JMPIF || i.OpCode == OpCode.JMPIFNOT
|| i.OpCode == OpCode.JMPIF_L || i.OpCode == OpCode.JMPIFNOT_L)
{
int target = ComputeJumpTarget(a, i);
if (target - a == i.Size)
{
Instruction newDrop = new Script(new byte[] { (byte)OpCode.DROP }).GetInstruction(0);
simplifiedInstructionsToAddress.Add(newDrop, currentAddress);
oldSequencePointAddressToNew.Add(a, currentAddress);
currentAddress += newDrop.Size;

Instruction nextInstruction = oldAddressToInstruction[a + i.Size];
// handle the reference of the deleted JMP
jumpSourceToTargets.Remove(i);
jumpTargetToSources[nextInstruction].Remove(i);
OptimizedScriptBuilder.RetargetJump(i, newDrop, jumpSourceToTargets, trySourceToTargets, jumpTargetToSources);
continue;
}
}
simplifiedInstructionsToAddress.Add(i, currentAddress);
currentAddress += i.Size;
}

return AssetBuilder.BuildOptimizedAssets(nef, manifest, debugInfo,
simplifiedInstructionsToAddress,
jumpSourceToTargets, trySourceToTargets,
oldAddressToInstruction, oldSequencePointAddressToNew: oldSequencePointAddressToNew);
}

/// <summary>
/// If a JMP or JMP_L jumps to a RET, replace the JMP with RET
/// </summary>
/// <param name="nef"></param>
/// <param name="manifest"></param>
/// <param name="debugInfo"></param>
/// <returns></returns>
[Strategy(Priority = int.MaxValue - 4)]
public static (NefFile, ContractManifest, JObject?) ReplaceJumpWithRet(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
Script script = nef.Script;
List<(int a, Instruction i)> oldAddressAndInstructionsList = script.EnumerateInstructions().ToList();
Dictionary<int, Instruction> oldAddressToInstruction = oldAddressAndInstructionsList.ToDictionary(e => e.a, e => e.i);
(Dictionary<Instruction, Instruction> jumpSourceToTargets,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<Instruction, HashSet<Instruction>> jumpTargetToSources) =
FindAllJumpAndTrySourceToTargets(oldAddressAndInstructionsList);
Dictionary<int, int> oldSequencePointAddressToNew = new();

System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress = new();
int currentAddress = 0;
foreach ((int a, Instruction i) in oldAddressAndInstructionsList)
{
if (unconditionalJump.Contains(i.OpCode))
{
int target = ComputeJumpTarget(a, i);
if (!oldAddressToInstruction.TryGetValue(target, out Instruction? dstRet))
throw new BadScriptException($"Bad {nameof(oldAddressToInstruction)}. No target found for {i} jumping from {a} to {target}");
if (dstRet.OpCode == OpCode.RET)
{
oldSequencePointAddressToNew[a] = currentAddress;
// handle the reference of the deleted JMP
jumpSourceToTargets.Remove(i);
jumpTargetToSources[dstRet].Remove(i);
// handle the reference of the added RET
Instruction newRet = new Script(new byte[] { (byte)OpCode.RET }).GetInstruction(0);
// above is a workaround of new Instruction(OpCode.RET)
OptimizedScriptBuilder.RetargetJump(i, newRet,
jumpSourceToTargets, trySourceToTargets, jumpTargetToSources);
simplifiedInstructionsToAddress.Add(newRet, currentAddress);
currentAddress += newRet.Size;
continue;
}
}
simplifiedInstructionsToAddress.Add(i, currentAddress);
currentAddress += i.Size;
}

return AssetBuilder.BuildOptimizedAssets(nef, manifest, debugInfo,
simplifiedInstructionsToAddress,
jumpSourceToTargets, trySourceToTargets,
oldAddressToInstruction,
oldSequencePointAddressToNew: oldSequencePointAddressToNew);
}

/// <summary>
/// If an unconditional jump targets an unconditional jump, re-target the first unconditional jump to its final destination
/// If a conditional jump targets an unconditional jump, re-target the conditional jump to its final destination
/// If an unconditional jump targets a conditional jump, DO NOT replace the unconditional jump with a conditional jump to its final destination. THIS IS WRONG.
/// This should be executed very early, before <see cref="Reachability.RemoveUncoveredInstructions"/>
/// </summary>
/// <param name="nef"></param>
/// <param name="manifest"></param>
/// <param name="debugInfo"></param>
/// <returns></returns>
[Strategy(Priority = int.MaxValue - 8)]
public static (NefFile, ContractManifest, JObject?) FoldJump(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
(nef, manifest, debugInfo) = JumpCompresser.UncompressJump(nef, manifest, debugInfo);
bool modified;
do
{
modified = false;
Script script = nef.Script;
List<(int a, Instruction i)> oldAddressAndInstructionsList = script.EnumerateInstructions().ToList();
Dictionary<int, Instruction> oldAddressToInstruction = oldAddressAndInstructionsList.ToDictionary(e => e.a, e => e.i);
Dictionary<Instruction, int> oldInstructionToAddress = oldAddressAndInstructionsList.ToDictionary(e => e.i, e => e.a);
(Dictionary<Instruction, Instruction> jumpSourceToTargets,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<Instruction, HashSet<Instruction>> jumpTargetToSources) =
FindAllJumpAndTrySourceToTargets(oldAddressAndInstructionsList);
Dictionary<int, int> oldSequencePointAddressToNew = new();

System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress = new();
int newAddr = 0;
foreach ((int a, Instruction i) in oldAddressAndInstructionsList)
{
if (shortInstructions.Contains(i.OpCode))
throw new BadScriptException($"Long version of OpCodes are required in {nameof(FoldJump)} optimization");
if (unconditionalJump.Contains(i.OpCode) || conditionalJump_L.Contains(i.OpCode))
{
Instruction target = jumpSourceToTargets[i];
if (unconditionalJump.Contains(target.OpCode) || unconditionalJump.Contains(target.OpCode))
{
modified = true;
Instruction finalTarget = jumpSourceToTargets[target];
// No need to change opcode. Use the old instruction without a new one.
// No need to reset operand. BuildOptimizedAssets does it.
simplifiedInstructionsToAddress.Add(i, newAddr);
oldSequencePointAddressToNew.Add(a, newAddr);
newAddr += i.Size;

jumpSourceToTargets[i] = finalTarget;
jumpTargetToSources[target].Remove(i);
jumpTargetToSources[finalTarget].Add(i);
continue;
}
}
simplifiedInstructionsToAddress.Add(i, newAddr);
newAddr += i.Size;
}

(nef, manifest, debugInfo) = AssetBuilder.BuildOptimizedAssets(
nef, manifest, debugInfo,
simplifiedInstructionsToAddress,
jumpSourceToTargets, trySourceToTargets,
oldAddressToInstruction, oldSequencePointAddressToNew);
}
while (modified);
return JumpCompresser.CompressJump(nef, manifest, debugInfo);
}
}
}
80 changes: 80 additions & 0 deletions src/Neo.Compiler.CSharp/Optimizer/Strategies/Miscellaneous.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Neo.Json;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.VM;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Neo.Optimizer
{
public static class Miscellaneous
{
/// <summary>
/// If any method token in nef is not utilized by CALLT, remove the method token.
/// </summary>
/// <param name="nef"></param>
/// <param name="manifest"></param>
/// <param name="debugInfo"></param>
/// <returns></returns>
[Strategy(Priority = int.MinValue)]
public static (NefFile, ContractManifest, JObject?) RemoveMethodToken(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
if (nef.Tokens.Length == 0)
return (nef, manifest, debugInfo);
List<bool> oldTokenNeeded = Enumerable.Repeat(false, nef.Tokens.Length).ToList();
Script script = nef.Script;
List<(int a, Instruction i)> oldAddressAndInstructionsList = script.EnumerateInstructions().ToList();
foreach ((_, Instruction i) in oldAddressAndInstructionsList)
if (i.OpCode == OpCode.CALLT)
// Possibly i.TokenU16 >= result.Count
// In this case the CALLT is invalid. We just let it throw exceptions.
oldTokenNeeded[i.TokenU16] = true;
Dictionary<int, int> oldTokenIdToNew = new();
List<MethodToken> newTokens = new();
for (int i = 0; i < oldTokenNeeded.Count; ++i)
{
if (oldTokenNeeded[i])
newTokens.Add(nef.Tokens[i]);
oldTokenIdToNew.Add(i, newTokens.Count - 1);
}
nef.Tokens = newTokens.ToArray();
if (newTokens.Count == 0 || newTokens.Count == oldTokenNeeded.Count)
{// all tokens deleted, or no token deleted
nef.CheckSum = NefFile.ComputeChecksum(nef);
return (nef, manifest, debugInfo);
}
// else: some operand of CALLT should be changed
Dictionary<int, Instruction> oldAddressToInstruction = oldAddressAndInstructionsList.ToDictionary(e => e.a, e => e.i);
(Dictionary<Instruction, Instruction> jumpSourceToTargets,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<Instruction, HashSet<Instruction>> jumpTargetToSources) =
JumpTarget.FindAllJumpAndTrySourceToTargets(oldAddressAndInstructionsList);
Dictionary<int, int> oldSequencePointAddressToNew = new();
System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress = new();
int newAddr = 0;
foreach ((int a, Instruction i) in oldAddressAndInstructionsList)
{
if (i.OpCode == OpCode.CALLT && oldTokenIdToNew[i.TokenU16] != i.TokenU16)
{
IEnumerable<byte> newInstructionInBytes = [(byte)OpCode.CALLT];
newInstructionInBytes = newInstructionInBytes.Concat(BitConverter.GetBytes(oldTokenIdToNew[i.TokenU16])[0..2]);
Instruction newInstruction = new Script(newInstructionInBytes.ToArray()).GetInstruction(0);
simplifiedInstructionsToAddress.Add(newInstruction, newAddr);
oldSequencePointAddressToNew.Add(a, newAddr);
newAddr += i.Size;
OptimizedScriptBuilder.RetargetJump(i, newInstruction,
jumpSourceToTargets, trySourceToTargets, jumpTargetToSources);
continue;
}
simplifiedInstructionsToAddress.Add(i, newAddr);
newAddr += i.Size;
}
return AssetBuilder.BuildOptimizedAssets(nef, manifest, debugInfo,
simplifiedInstructionsToAddress,
jumpSourceToTargets, trySourceToTargets,
oldAddressToInstruction,
oldSequencePointAddressToNew: oldSequencePointAddressToNew);
}
}
}
Loading

0 comments on commit 261016c

Please sign in to comment.