Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add feature as a standalone .nef file optimizer #1162

Merged
merged 8 commits into from
Sep 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,7 @@
try
{
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
(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);
(nef, manifest, debugInfo) = Neo.Optimizer.Optimizer.Optimize(nef, manifest, debugInfo: debugInfo!.Clone(), optimizationType: Options.Optimize);

Check failure on line 162 in src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs

View workflow job for this annotation

GitHub Actions / Test

Argument 3: cannot convert from 'Neo.Json.JToken' to 'Neo.Json.JObject?'

Check failure on line 162 in src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs

View workflow job for this annotation

GitHub Actions / Test

Argument 3: cannot convert from 'Neo.Json.JToken' to 'Neo.Json.JObject?'
Hecate2 marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
}
catch (Exception ex)
Expand Down
37 changes: 37 additions & 0 deletions src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;

namespace Neo.Optimizer
Expand All @@ -32,6 +34,41 @@ static class DumpNef
static readonly Lazy<IReadOnlyDictionary<uint, string>> sysCallNames = new(
() => ApplicationEngine.Services.ToImmutableDictionary(kvp => kvp.Value.Hash, kvp => kvp.Value.Name));

public static byte[] ZipDebugInfo(byte[] content, string innerFilename)
{
using (var compressedFileStream = new MemoryStream())
{
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Update, false))
{
var zipEntry = zipArchive.CreateEntry(innerFilename);
using (var originalFileStream = new MemoryStream(content))
{
using (var zipEntryStream = zipEntry.Open())
{
originalFileStream.CopyTo(zipEntryStream);
}
}
}
return compressedFileStream.ToArray();
}
}

public static string UnzipDebugInfo(byte[] zippedBuffer)
{
using var zippedStream = new MemoryStream(zippedBuffer);
using var archive = new ZipArchive(zippedStream, ZipArchiveMode.Read, false, Encoding.UTF8);
var entry = archive.Entries.FirstOrDefault();
if (entry != null)
{
using var unzippedEntryStream = entry.Open();
using var ms = new MemoryStream();
unzippedEntryStream.CopyTo(ms);
var unzippedArray = ms.ToArray();
return Encoding.UTF8.GetString(unzippedArray);
}
throw new ArgumentException("No file found in zip archive");
}

public static string GetInstructionAddressPadding(this Script script)
{
var digitCount = EnumerateInstructions(script).Last().address switch
Expand Down
24 changes: 23 additions & 1 deletion src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Compiler;
using Neo.Json;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
Expand All @@ -19,7 +20,7 @@

namespace Neo.Optimizer
{
class Optimizer
public static class Optimizer
{
public static readonly int[] OperandSizePrefixTable = new int[256];
public static readonly int[] OperandSizeTable = new int[256];
Expand Down Expand Up @@ -50,5 +51,26 @@ public static void RegisterStrategies(Type type)
strategies[name] = method.CreateDelegate<Func<NefFile, ContractManifest, JObject, (NefFile nef, ContractManifest manifest, JObject debugInfo)>>();
}
}

public static (NefFile, ContractManifest, JObject?) Optimize(NefFile nef, ContractManifest manifest, JObject? debugInfo = null, CompilationOptions.OptimizationType optimizationType = CompilationOptions.OptimizationType.All)
{
// Define the optimization type inside the manifest
if (optimizationType != CompilationOptions.OptimizationType.None)
{
manifest.Extra ??= new JObject();
manifest.Extra["nef"] = new JObject();
manifest.Extra["nef"]!["optimization"] = optimizationType.ToString();
}
if (!optimizationType.HasFlag(CompilationOptions.OptimizationType.Experimental))
return (nef, manifest, debugInfo); // do nothing
// TODO in the future: optimize by StrategyAttribute in a loop
debugInfo = debugInfo?.Clone() as JObject; // do not pollute the input when optimization fails
(nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo);
(nef, manifest, debugInfo) = Reachability.ReplaceJumpWithRet(nef, manifest, debugInfo);
(nef, manifest, debugInfo) = Reachability.RemoveUncoveredInstructions(nef, manifest, debugInfo);
(nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo);
(nef, manifest, debugInfo) = Reachability.ReplaceJumpWithRet(nef, manifest, debugInfo);
return (nef, manifest, debugInfo);
}
}
}
38 changes: 37 additions & 1 deletion src/Neo.Compiler.CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,43 @@ private static void Handle(RootCommand command, Options options, string[]? paths
}
foreach (string path in paths)
{
if (Path.GetExtension(path).ToLowerInvariant() != ".cs")
string extension = Path.GetExtension(path).ToLowerInvariant();
if (extension == ".nef")
{
string directory = Path.GetDirectoryName(path)!;
string filename = Path.GetFileNameWithoutExtension(path)!;
Console.WriteLine($"Optimizing {filename}.nef to {filename}.optimized.nef...");
NefFile nef = NefFile.Parse(File.ReadAllBytes(path));
string manifestPath = Path.Join(directory, filename + ".manifest.json");
if (!File.Exists(manifestPath))
throw new FileNotFoundException($"{filename}.manifest.json required for optimization");
ContractManifest manifest = ContractManifest.Parse(File.ReadAllText(manifestPath));
string debugInfoPath = Path.Join(directory, filename + ".nefdbgnfo");
JObject? debugInfo;
if (File.Exists(debugInfoPath))
debugInfo = (JObject?)JObject.Parse(DumpNef.UnzipDebugInfo(File.ReadAllBytes(debugInfoPath)));
else
debugInfo = null;
(nef, manifest, debugInfo) = Neo.Optimizer.Optimizer.Optimize(nef, manifest, debugInfo, optimizationType: CompilationOptions.OptimizationType.All);
// Define the optimization type inside the manifest
manifest.Extra ??= new JObject();
manifest.Extra["nef"] = new JObject();
manifest.Extra["nef"]!["optimization"] = CompilationOptions.OptimizationType.All.ToString();
shargon marked this conversation as resolved.
Show resolved Hide resolved
File.WriteAllBytes(Path.Combine(directory, filename + ".optimized.nef"), nef.ToArray());
File.WriteAllBytes(Path.Combine(directory, filename + ".optimized.manifest.json"), manifest.ToJson().ToByteArray(true));
if (options.Assembly)
{
string dumpnef = DumpNef.GenerateDumpNef(nef, debugInfo);
File.WriteAllText(Path.Combine(directory, filename + ".optimized.nef.txt"), dumpnef);
}
if (debugInfo != null)
File.WriteAllBytes(Path.Combine(directory, filename + ".optimized.nefdbgnfo"), DumpNef.ZipDebugInfo(debugInfo.ToByteArray(true), filename + ".optimized.debug.json"));
Console.WriteLine($"Optimization finished.");
if (options.SecurityAnalysis)
ReEntrancyAnalyzer.AnalyzeSingleContractReEntrancy(nef, manifest, debugInfo).GetWarningInfo(print: true);
return;
}
if (extension != ".cs")
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
Console.Error.WriteLine("The files must have a .cs extension.");
context.ExitCode = 1;
Expand Down
Loading