/// <summary> /// Converts given user code into a IL program which can then be compiled. /// </summary> /// <param name="settings">The settings.</param> /// <returns></returns> public static (ErrorCodes errorCode, List <Instruction> Il) CompileAst(CompilerSettings settings) { AbstractSyntaxTree tree = Lexer.LexAst(settings.InputCode); Optimizer.Optimize(tree, settings); List <Instruction> Il = tree.ToIl(); Optimizer.Optimize(Il, settings); return(ErrorCodes.Successful, Il); }
private static (ErrorCodes errorCode, List <Instruction> Il) CompileCommon(CompilerSettings settings) { if (settings == null) { throw new ArgumentNullException(nameof(settings)); } if (settings.InputCode == null) { throw new ArgumentException(nameof(settings.InputCode)); } return(CompileAst(settings)); }
/// <summary> /// Writes the compiled exe along with the input code (brainf*ck), CSharp source and IL to files. /// </summary> /// <param name="IlString">The IL as a string. (gets written to IL.txt)</param> /// <param name="outputSrc">The CSharp source code of the output. (gets written to output-src.cs)</param> /// <param name="settings">The settings.</param> private static void WriteToFiles(string IlString, string outputSrc, CompilerSettings settings) { if (!string.IsNullOrEmpty(settings.FileNameUserCode)) { File.WriteAllText(Path.Combine(appdir, settings.FileNameUserCode + ".txt"), settings.InputCode); } if (!string.IsNullOrEmpty(settings.FileNameIL)) { File.WriteAllText(Path.Combine(appdir, settings.FileNameIL + ".txt"), IlString); } if (!string.IsNullOrEmpty(settings.FileNameCSharpSrc)) { File.WriteAllText(Path.Combine(appdir, settings.FileNameCSharpSrc + ".cs"), outputSrc); } }
/// <summary> /// Attempts to compile the code specified in <paramref name="settings"/> outputs code in output.exe /// </summary> /// <param name="settings"></param> /// <returns>The success of the compilation.</returns> public static ErrorCodes Compile(CompilerSettings settings) { (ErrorCodes errorCode, List <Instruction> Il)preCompResults = CompileCommon(settings); if (preCompResults.errorCode != ErrorCodes.Successful) { return(preCompResults.errorCode); } // Why is this using a constant string? because a better way to do this hasn't been // found. The reason why this isn't a $ string is because of the brackets. string compiled = "using System;public class Program{public static void Main(){byte[] ram=new byte[256];" + GetInjectString(preCompResults.Il) + "Console.ReadKey();}}"; using (CSharpCodeProvider provider = new CSharpCodeProvider()) { CompilerParameters paramaters = new CompilerParameters { CompilerOptions = "/optimize", // This currently does nothing? GenerateExecutable = true, OutputAssembly = Path.Combine( appdir, ((!string.IsNullOrEmpty(settings.FileNameOutputExe)) ? settings.FileNameOutputExe : "output") + ".exe") }; if (provider.CompileAssemblyFromSource(paramaters, compiled).Errors.Count > 0) { // This *shouldn't* ever happen because all errors *should* be caught by the // validator. The only known instance of this occuring is when the application // can't write to the file. return(ErrorCodes.UnknownCompilationFailure); } } // create a string which contains all the IL on new lines & pass the other args. WriteToFiles(string.Join(Environment.NewLine, preCompResults.Il), compiled, settings); return(ErrorCodes.Successful); // Made it. }
/// <summary> /// Uses <paramref name="settings"/> to optimize <paramref name="IL"/>. /// </summary> /// <param name="IL">The IL that is to be optimized.</param> /// <param name="settings"> /// The compiler settings that tell which optimizations should be used. /// </param> internal static void Optimize(List <Instruction> IL, CompilerSettings settings) { int CodeLength = 0; IL.RemoveNops(); // Just in case. // It feels evil to use a do while loop but this seems to be the best way to not loop // more than required. do { CodeLength = IL.Count; if (settings.CombineMatchingInstructions) { CombineMatchingInstructions(IL); } if (settings.EliminateEmptyLoops) { EliminateEmptyLoops(IL); } } while (IL.Count < CodeLength); // This one gets it's own loop because it doesn't help the other ones, less time spent here. if (settings.EliminateDeadStores) { do { CodeLength = IL.Count; EliminateDeadStores(IL); } while (IL.Count < CodeLength); } // Doesn't need a loop. if (settings.MergeAssignThenModifyInstructions) { MergeAssignThenModifyInstructions(IL); } }
internal static void Optimize(AbstractSyntaxTree ast, CompilerSettings settings) { bool Continue = true; while (Continue) { Continue = false; if (settings.SimplifyAssignZeroLoops) { Continue |= SimplifyAssignZeroLoops(ast); ast.ColapseNops(); } if (settings.EliminateDeadStores) { Continue |= EliminateDeadStores(ast, true); ast.ColapseNops(); } if (settings.EliminateUnreachableLoops) { Continue |= EliminateUnreachableLoops(ast, true); ast.ColapseNops(); } if (settings.EliminateConflictingInstructions) { Continue |= EliminateConflictingInstructions(ast); ast.ColapseNops(); } if (true) { Continue |= SimplifyScans(ast); ast.ColapseNops(); } } }
public static ErrorCodes CompileDmg(CompilerSettings settings) { (ErrorCodes errorCode, List <Instruction> Il)preCompResults = CompileCommon(settings); if (preCompResults.errorCode != ErrorCodes.Successful) { return(preCompResults.errorCode); } Stack <int> jumpOffsets = new Stack <int>(); List <byte> rom = new byte[0x100].Concat(DmgHeader).ToList(); rom.Add(0xAF); // xor A rom.Add(0x47); // ld B,A (set B to 0) rom.Add(0x6F); // ld L,A (set L to 0) // ld H,C0 rom.Add(0x26); rom.Add(0xC0); foreach (Instruction instr in preCompResults.Il) { int instrOffset = rom.Count; switch (instr.OpCode) { case OpCode.Nop: rom.Add(0x00); // Maybe emit a compiler warning here? break; case OpCode.AddVal: case OpCode.SubVal: rom.Add(instr.OpCode == OpCode.AddVal ? (byte)0xC6 : (byte)0xD6); rom.Add(instr.Value); break; case OpCode.AddPtr: case OpCode.SubPtr: rom.Add(0x77); // Store A into the pointer. // Load a with the address rom.Add(0x3E); rom.Add(instr.Value); rom.Add(instr.OpCode == OpCode.AddPtr ? (byte)0x85 : (byte)0x95); // Modify the address. rom.Add(0x6F); // Store the result into L rom.Add(0x7E); // Read at the new pointer. rom.Add(0xBB); break; case OpCode.StartLoop: rom.Add(0xCA); rom.Add(0); rom.Add(0); jumpOffsets.Push(instrOffset); break; case OpCode.EndLoop: int baseAddr = jumpOffsets.Pop(); // Setup jump to the instruction after this. rom[baseAddr + 1] = (byte)(instrOffset + 3); rom[baseAddr + 2] = (byte)((instrOffset + 3) >> 8); // Setup this jump. rom.Add(0xC2); rom.Add((byte)(baseAddr + 3)); rom.Add((byte)((baseAddr + 3) >> 8)); break; case OpCode.AssignVal: rom.Add(0x3E); rom.Add(instr.Value); break; case OpCode.AssignZero: rom.Add(0xAF); break; case OpCode.ScanLeft: case OpCode.ScanRight: rom.Add(0x28); rom.Add(0x05); // this is a fixed address based on the start address of this. rom.Add(0xAF); rom.Add(instr.OpCode == OpCode.ScanRight ? (byte)0x2C : (byte)0x2D); // INC/DEC L rom.Add(0xBE); rom.Add(0x20); // JR NZ rom.Add(0xFC); // addr: INC/DEC L. break; case OpCode.GetInput: // TODO: load from serial. case OpCode.SetOutput: // TODO: write to serial. default: return(ErrorCodes.UnexpectedIlInstruction); } } rom.Add(0x10); rom.Add(0x00); File.WriteAllBytes(((!string.IsNullOrEmpty(settings.FileNameOutputExe)) ? settings.FileNameOutputExe : "output") + ".gb", rom.ToArray()); if (!string.IsNullOrEmpty(settings.FileNameIL)) { File.WriteAllText(Path.Combine(appdir, settings.FileNameIL + ".txt"), string.Join(Environment.NewLine, preCompResults.Il)); } return(ErrorCodes.Successful); }