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 @@ internal void Compile()
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() as JObject, optimizationType: Options.Optimize);
#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
21 changes: 20 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,23 @@ 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)
{
if (!optimizationType.HasFlag(CompilationOptions.OptimizationType.Experimental))
return (nef, manifest, debugInfo); // do nothing
// Define the optimization type inside the manifest
manifest.Extra ??= new JObject();
manifest.Extra["nef"] = new JObject();
manifest.Extra["nef"]!["optimization"] = optimizationType.ToString();
// 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);
}
}
}
41 changes: 40 additions & 1 deletion src/Neo.Compiler.CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,46 @@ 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")
{
if (options.Optimize != CompilationOptions.OptimizationType.Experimental)
{
Console.Error.WriteLine($"Required {nameof(options.Optimize).ToLower()}={options.Optimize}, " +
$"but the .nef optimizer supports only {CompilationOptions.OptimizationType.Experimental} level of optimization. ");
Console.Error.WriteLine($"Still using {nameof(options.Optimize).ToLower()}={CompilationOptions.OptimizationType.Experimental}");
options.Optimize = CompilationOptions.OptimizationType.Experimental;
}
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: options.Optimize);
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;
}
else if (extension != ".cs")
{
Console.Error.WriteLine("The files must have a .cs extension.");
context.ExitCode = 1;
Expand Down
Loading