diff --git a/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs b/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs index d6a4eb257..a52e8fad2 100644 --- a/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs +++ b/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs @@ -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) diff --git a/src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs b/src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs index cacb3b2eb..12243b703 100644 --- a/src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs +++ b/src/Neo.Compiler.CSharp/Optimizer/DumpNef.cs @@ -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 @@ -32,6 +34,41 @@ static class DumpNef static readonly Lazy> 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 diff --git a/src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs b/src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs index fd9000090..e46f04293 100644 --- a/src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs +++ b/src/Neo.Compiler.CSharp/Optimizer/Strategies/Optimizer.cs @@ -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; @@ -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]; @@ -50,5 +51,23 @@ public static void RegisterStrategies(Type type) strategies[name] = method.CreateDelegate>(); } } + + 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); + } } } diff --git a/src/Neo.Compiler.CSharp/Program.cs b/src/Neo.Compiler.CSharp/Program.cs index ce26c29e1..b4ab7f26a 100644 --- a/src/Neo.Compiler.CSharp/Program.cs +++ b/src/Neo.Compiler.CSharp/Program.cs @@ -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;