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 optimize-nef

# Conflicts:
#	src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs
  • Loading branch information
Hecate2 committed Sep 9, 2024
2 parents 688f0de + 25278fc commit 326db42
Show file tree
Hide file tree
Showing 50 changed files with 359 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public static (NefFile, ContractManifest, JObject?) BuildOptimizedAssets(
System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress,
Dictionary<Instruction, Instruction> jumpSourceToTargets,
Dictionary<Instruction, (Instruction, Instruction)> trySourceToTargets,
Dictionary<int, Instruction> oldAddressToInstruction)
Dictionary<int, Instruction> oldAddressToInstruction,
Dictionary<int, int>? oldSequencePointAddressToNew = null)
{
nef.Script = OptimizedScriptBuilder.BuildScriptWithJumpTargets(
simplifiedInstructionsToAddress,
Expand All @@ -36,7 +37,9 @@ public static (NefFile, ContractManifest, JObject?) BuildOptimizedAssets(
nef.CheckSum = NefFile.ComputeChecksum(nef);
foreach (ContractMethodDescriptor method in manifest.Abi.Methods)
method.Offset = (int)simplifiedInstructionsToAddress[oldAddressToInstruction[method.Offset]]!;
debugInfo = DebugInfoBuilder.ModifyDebugInfo(debugInfo, simplifiedInstructionsToAddress, oldAddressToInstruction);
debugInfo = DebugInfoBuilder.ModifyDebugInfo(
debugInfo, simplifiedInstructionsToAddress, oldAddressToInstruction,
oldSequencePointAddressToNew: oldSequencePointAddressToNew);
return (nef, manifest, debugInfo);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ static class DebugInfoBuilder
/// <returns></returns>
public static JObject? ModifyDebugInfo(JObject? debugInfo,
System.Collections.Specialized.OrderedDictionary simplifiedInstructionsToAddress,
Dictionary<int, Instruction> oldAddressToInstruction)
Dictionary<int, Instruction> oldAddressToInstruction,
Dictionary<int, int>? oldSequencePointAddressToNew = null)
{
if (debugInfo == null) return null;
//Dictionary<int, (int docId, int startLine, int startCol, int endLine, int endCol)> newAddrToSequencePoint = new();
Expand Down Expand Up @@ -58,6 +59,12 @@ static class DebugInfoBuilder
newSequencePoints.Add(new JString($"{startInstructionAddress}{sequencePointGroups[2]}"));
previousSequencePoint = startInstructionAddress;
}
else if (oldSequencePointAddressToNew != null
&& oldSequencePointAddressToNew.TryGetValue(startInstructionAddress, out int newStartInstructionAddress))
{
newSequencePoints.Add(new JString($"{newStartInstructionAddress}{sequencePointGroups[2]}"));
previousSequencePoint = startInstructionAddress;
}
else
newSequencePoints.Add(new JString($"{previousSequencePoint}{sequencePointGroups[2]}"));
}
Expand Down
7 changes: 5 additions & 2 deletions src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ public static (NefFile, ContractManifest, JObject?) Optimize(NefFile nef, Contra
if (!optimizationType.HasFlag(CompilationOptions.OptimizationType.Experimental))
return (nef, manifest, debugInfo); // do nothing
// TODO in the future: optimize by StrategyAttribute in a loop
(nef, manifest, debugInfo) = Reachability.RemoveUncoveredInstructions(nef, manifest, debugInfo?.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo?.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo!.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.ReplaceJumpWithRet(nef, manifest, debugInfo!.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.RemoveUncoveredInstructions(nef, manifest, debugInfo!.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo!.Clone() as JObject);
(nef, manifest, debugInfo) = Reachability.ReplaceJumpWithRet(nef, manifest, debugInfo!.Clone() as JObject);
return (nef, manifest, debugInfo);
}
}
Expand Down
78 changes: 76 additions & 2 deletions src/Neo.Compiler.CSharp/Optimizer/Strategies/Reachability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Neo.Optimizer
{
static class Reachability
{
[Strategy(Priority = int.MaxValue)]
[Strategy(Priority = int.MaxValue - 16)]
public static (NefFile, ContractManifest, JObject?) RemoveUncoveredInstructions(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
InstructionCoverage oldContractCoverage = new InstructionCoverage(nef, manifest);
Expand Down Expand Up @@ -63,7 +63,7 @@ public static Dictionary<int, BranchType>
/// <param name="manifest"></param>
/// <param name="debugInfo"></param>
/// <returns></returns>
[Strategy(Priority = int.MaxValue - 16)]
[Strategy(Priority = int.MaxValue)]
public static (NefFile, ContractManifest, JObject?) RemoveUnnecessaryJumps(NefFile nef, ContractManifest manifest, JObject? debugInfo = null)
{
Script script = nef.Script;
Expand Down Expand Up @@ -118,5 +118,79 @@ public static (NefFile, ContractManifest, JObject?) RemoveUnnecessaryJumps(NefFi
jumpSourceToTargets, trySourceToTargets,
oldAddressToInstruction);
}

/// <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 = new();
foreach ((int a, Instruction i) in oldAddressAndInstructionsList)
oldAddressToInstruction.Add(a, 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);
if (jumpTargetToSources[dstRet].Count == 0)
jumpTargetToSources.Remove(dstRet);
// 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)
if (jumpTargetToSources.TryGetValue(i, out HashSet<Instruction>? othersJumpingToCurrentJmp))
{
foreach (Instruction iJumpingToCurrentRet in othersJumpingToCurrentJmp)
{
if (SingleJumpInOperand(iJumpingToCurrentRet))
jumpSourceToTargets[iJumpingToCurrentRet] = newRet;
if (iJumpingToCurrentRet.OpCode == OpCode.TRY || iJumpingToCurrentRet.OpCode == OpCode.TRY_L)
{
(Instruction t1, Instruction t2) = trySourceToTargets[iJumpingToCurrentRet];
if (t1 == i) t1 = newRet;
if (t2 == i) t2 = newRet;
trySourceToTargets[iJumpingToCurrentRet] = (t1, t2);
}
}
jumpTargetToSources.Remove(i);
jumpTargetToSources[newRet] = othersJumpingToCurrentJmp;
}
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ NC4020 | Naming | Warning | SmartContractMethodNamingAnalyzerUnderline
NC4021 | Usage | Warning | SupportedStandardsAnalyzer
NC4022 | Usage | Warning | BigIntegerUsingUsageAnalyzer
NC4023 | Usage | Error | StaticFieldInitializationAnalyzer
NC4024 | Usage | Error | MultipleCatchBlockAnalyzer
48 changes: 48 additions & 0 deletions src/Neo.SmartContract.Analyzer/MultipleCatchBlockAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MultipleCatchBlockAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "NC4024";

private static readonly LocalizableString Title = "Multiple catch blocks are not allowed in Neo smart contracts";
private static readonly LocalizableString MessageFormat = "Neo smart contracts only support a single catch block: {0}";
private static readonly LocalizableString Description = "Neo smart contracts are limited to one catch block per try statement.";
private const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeTryStatement, SyntaxKind.TryStatement);
}

private static void AnalyzeTryStatement(SyntaxNodeAnalysisContext context)
{
var tryStatement = (TryStatementSyntax)context.Node;

if (tryStatement.Catches.Count > 1)
{
var diagnostic = Diagnostic.Create(Rule, tryStatement.GetLocation(), tryStatement.Catches.Count);
context.ReportDiagnostic(diagnostic);
}
}
}
}
3 changes: 2 additions & 1 deletion src/Neo.SmartContract.Analyzer/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This repository contains a set of Roslyn analyzers and code fix providers for Ne
- [SupportedStandardsAnalyzer.cs](NeoContractAnalyzer/SupportedStandardsAnalyzer.cs): This analyzer checks for correct implementation of supported standards in smart contracts.
- [BigIntegerUsingUsageAnalyzer.cs](NeoContractAnalyzer/BigIntegerUsingUsageAnalyzer.cs): This analyzer warns about incorrect usage of BigInteger in using statements.
- [StaticFieldInitializationAnalyzer.cs](NeoContractAnalyzer/StaticFieldInitializationAnalyzer.cs): This analyzer checks for proper initialization of static fields in smart contracts.
- [MultipleCatchBlockAnalyzer.cs](NeoContractAnalyzer/MultipleCatchBlockAnalyzer.cs): This analyzer checks for multiple catch blocks in try statements.

## How to Use

Expand All @@ -41,4 +42,4 @@ Contributions to improve existing analyzers or add new ones are welcome. Please

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Loading

0 comments on commit 326db42

Please sign in to comment.