diff --git a/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs b/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs index dc34d77c7..97af6ec15 100644 --- a/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs +++ b/src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs @@ -159,8 +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.RemoveUncoveredInstructions(nef, manifest, debugInfo.Clone() as JObject); - (nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo!.Clone() as JObject); + (nef, manifest, debugInfo) = Neo.Optimizer.Optimizer.Optimize(nef, manifest, debugInfo: debugInfo!, 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..e467842da 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,22 @@ 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) + { + // 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 + (nef, manifest, debugInfo) = Reachability.RemoveUncoveredInstructions(nef, manifest, debugInfo?.Clone() as JObject); + (nef, manifest, debugInfo) = Reachability.RemoveUnnecessaryJumps(nef, manifest, debugInfo?.Clone() as JObject); + return (nef, manifest, debugInfo); + } } } diff --git a/src/Neo.Compiler.CSharp/Program.cs b/src/Neo.Compiler.CSharp/Program.cs index ce26c29e1..39cc1ee8e 100644 --- a/src/Neo.Compiler.CSharp/Program.cs +++ b/src/Neo.Compiler.CSharp/Program.cs @@ -92,7 +92,41 @@ 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(); + 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."); + return; + } + if (extension != ".cs") { Console.Error.WriteLine("The files must have a .cs extension."); context.ExitCode = 1;