private void PushOperand(DebuggableEmitter il, ulong index, UrclInstruction inst) { bool isImm; ulong value; switch (index) { case 0: isImm = inst.AType switch { OperandType.Register => false, OperandType.Immediate => true, _ => throw new InvalidOperationException(), }; value = inst.A; break; case 1: isImm = inst.BType switch { OperandType.Register => false, OperandType.Immediate => true, _ => throw new InvalidOperationException(), }; value = inst.B; break; case 2: isImm = inst.CType switch { OperandType.Register => false, OperandType.Immediate => true, _ => throw new InvalidOperationException(), }; value = inst.C; break; default: throw new InvalidOperationException(); } if (isImm) { il.Emit(OpCodes.Ldc_I8, value); } else { if (value == 0) { il.Emit(OpCodes.Ldc_I8, 0UL); } else { il.Emit(OpCodes.Ldloc, Registers[value - 1]); } } }
private static Operand GetA(UrclInstruction instruction) { return(instruction.AType switch { OperandType.None => Operand.Imm("0"), OperandType.Register => instruction.A == 0 ? Operand.Imm("0") : Operand.Abs(((instruction.A - 1) * 2) + RegistersOffset), OperandType.Immediate => Operand.Imm(instruction.A.ToString()), OperandType.Label => Operand.Imm(LabelToString(instruction.ALabel)), _ => throw new ArgumentException($"Unsupported operand type {instruction.AType}.", "A"), });
private void PopOperand(DebuggableEmitter il, ulong index, UrclInstruction inst) { ulong reg; switch (index) { case 0: if (inst.AType == OperandType.Register) { reg = inst.A; } else { throw new InvalidOperationException(); } break; case 1: if (inst.AType == OperandType.Register) { reg = inst.B; } else { throw new InvalidOperationException(); } break; case 2: if (inst.AType == OperandType.Register) { reg = inst.C; } else { throw new InvalidOperationException(); } break; default: throw new InvalidOperationException(); } if (reg == 0) { il.Emit(OpCodes.Pop); } else { il.Emit(OpCodes.Stloc, Registers[reg - 1]); } }
private ResolvedInstruction Decode(UrclInstruction inst) { var argCount = 0; if (inst.AType != OperandType.None) { argCount++; } if (inst.BType != OperandType.None) { argCount++; } if (inst.CType != OperandType.None) { argCount++; } var args = new object[argCount]; if (argCount >= 1) { args[0] = inst.AType switch { OperandType.Register => new Register(inst.A), OperandType.Immediate => inst.A, _ => ResolveValue(inst.ALabel), }; } if (argCount >= 2) { args[1] = inst.BType switch { OperandType.Register => new Register(inst.B), OperandType.Immediate => inst.B, _ => ResolveValue(inst.BLabel), }; } if (argCount >= 3) { args[2] = inst.CType switch { OperandType.Register => new Register(inst.C), OperandType.Immediate => inst.C, _ => ResolveValue(inst.CLabel), }; } return(new ResolvedInstruction(inst.Operation, args)); }
public static IEnumerable <UrclInstruction> Parse(IEnumerable <string> lines) { var labels = new Dictionary <string, Label>(); ulong ram = 1024; ulong maxReg = 0; for (int parseIteration = 0; parseIteration < 3; parseIteration++) { int index = 0; foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.Length != 0 && !line.StartsWith("//")) { UrclInstruction result = null; try { result = ParseInstruction(trimmed, parseIteration == 0, parseIteration > 0, labels); if (parseIteration == 1) { if (result.Operation == Operation.MINRAM && result.A > ram) { ram = result.A; } else { if (result.AType == OperandType.Register && result.A > maxReg) { maxReg = result.A; } if (result.BType == OperandType.Register && result.B > maxReg) { maxReg = result.B; } if (result.CType == OperandType.Register && result.C > maxReg) { maxReg = result.C; } } result = null; } else if (result != null && result.Operation == Operation.MINRAM) { result = null; } } catch (ParserError ex) { throw new ParserError($"Error on line {index + 1}: \"{line}\" {ex.Message}"); } if (result != null) { yield return(result); } } index++; } if (parseIteration == 1) { yield return(new UrclInstruction(Operation.COMPILER_MAXREG, OperandType.Immediate, maxReg)); yield return(new UrclInstruction(Operation.MINRAM, OperandType.Immediate, ram)); } } }
private void Emit(Action <X86Inst> emit, UrclInstruction instruction) { switch (instruction.Operation) { case Operation.NOP: emit(new X86Inst("nop")); break; case Operation.BRK: case Operation.HLT: emit(new X86Inst("hlt")); break; case Operation.ADD: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("add", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.INC: Set(emit, AX, GetB(instruction)); emit(new X86Inst("inc", AX)); Set(emit, GetA(instruction), AX); break; case Operation.SUB: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("sub", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.DEC: Set(emit, AX, GetB(instruction)); emit(new X86Inst("dec", AX)); Set(emit, GetA(instruction), AX); break; case Operation.MLT: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("mul", BX)); Set(emit, GetA(instruction), AX); break; case Operation.DIV: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("xor", DX, DX)); emit(new X86Inst("div", BX)); Set(emit, GetA(instruction), AX); break; case Operation.MOD: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("xor", DX, DX)); emit(new X86Inst("div", BX)); Set(emit, GetA(instruction), DX); break; case Operation.AND: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("and", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.OR: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("or", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.XOR: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("xor", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.NAND: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("and", AX, BX)); emit(new X86Inst("not", AX)); Set(emit, GetA(instruction), AX); break; case Operation.NOR: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("or", AX, BX)); emit(new X86Inst("not", AX)); Set(emit, GetA(instruction), AX); break; case Operation.XNOR: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("xor", AX, BX)); emit(new X86Inst("not", AX)); Set(emit, GetA(instruction), AX); break; case Operation.NOT: Set(emit, AX, GetB(instruction)); emit(new X86Inst("not", AX)); Set(emit, GetA(instruction), AX); break; case Operation.LSH: Set(emit, AX, GetB(instruction)); Set(emit, BX, Operand.Imm("1")); emit(new X86Inst("shl", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.BSL: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("shl", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.RSH: Set(emit, AX, GetB(instruction)); Set(emit, BX, Operand.Imm("1")); emit(new X86Inst("shr", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.BSR: Set(emit, AX, GetB(instruction)); Set(emit, BX, GetC(instruction)); emit(new X86Inst("shr", AX, BX)); Set(emit, GetA(instruction), AX); break; case Operation.MOV: if (instruction.BType == OperandType.Register && instruction.B == 0) { Set(emit, GetA(instruction), Operand.Imm("0")); } else { Set(emit, AX, GetB(instruction)); Set(emit, GetA(instruction), AX); } break; case Operation.IMM: Set(emit, GetA(instruction), GetB(instruction)); break; case Operation.LOAD: { Set(emit, BX, GetB(instruction)); emit(new X86Inst("shl", BX, Operand.Imm("1"))); Set(emit, AX, Operand.Rel(BX, 0)); Set(emit, GetA(instruction), AX); } break; case Operation.STORE: { Set(emit, AX, GetB(instruction)); Set(emit, BX, GetA(instruction)); emit(new X86Inst("shl", BX, Operand.Imm("1"))); Set(emit, Operand.Rel(BX, 0), AX); } break; case Operation.IN: { var port = GetB(instruction).ToString(); switch (port) { case "3": case "78": case "79": emit(new X86Inst("xor", AX, AX)); emit(new X86Inst("int", Operand.Imm("0x16"))); emit(new X86Inst("and", AX, Operand.Imm("0xFF"))); Set(emit, GetA(instruction), AX); break; default: throw new ArgumentException($"Unsupported IO port \"{port}\"", "B"); } } break; case Operation.OUT: { var port = GetA(instruction).ToString(); switch (port) { case "3": case "78": case "79": Set(emit, AX, Operand.Imm("0x0A00")); Set(emit, BX, GetB(instruction)); Set(emit, CX, Operand.Imm("1")); emit(new X86Inst("and", BX, Operand.Imm("0xFF"))); emit(new X86Inst("or", AX, BX)); emit(new X86Inst("xor", BX, BX)); emit(new X86Inst("int", Operand.Imm("0x10"))); break; default: throw new ArgumentException($"Unsupported IO port \"{port}\"", "A"); } } break; case Operation.PSH: { emit(new X86Inst("push", GetA(instruction))); } break; case Operation.POP: { if (instruction.AType == OperandType.Register && instruction.A != 0) { emit(new X86Inst("pop", GetA(instruction))); } else { emit(new X86Inst("pop", AX)); } } break; case Operation.BRA: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jmp", target)); } break; case Operation.BRZ: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jz", target)); } break; case Operation.BNZ: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jnz", target)); } break; case Operation.BRC: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jc", target)); } break; case Operation.BNC: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jnc", target)); } break; case Operation.BRP: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("jns", target)); } break; case Operation.BRN: { var target = GetA(instruction); if (instruction.AType == OperandType.Register) { Set(emit, AX, target); target = AX; } emit(new X86Inst("js", target)); } break; case Operation.CAL: { emit(new X86Inst("call", GetA(instruction))); } break; case Operation.RET: emit(new X86Inst("ret")); break; case Operation.COMPILER_MARKLABEL: emit(new X86Inst($"{LabelToString(instruction.ALabel)}:")); break; case Operation.MINRAM: case Operation.BENCHMARK: case Operation.COMPILER_CREATELABEL: case Operation.COMPILER_PRAGMA: case Operation.COMPILER_MAXREG: break; case Operation.COMPILER_COMMENT: if (CommentPrefix != null) { foreach (var line in instruction.Arguments) { emit(new X86Inst($"{CommentPrefix}{line}")); } } break; default: break; } }
public IEnumerable <UrclInstruction> Parse(IEnumerable <string> lines, Func <string, IEnumerable <string> > import, string sourceName = null) { var lineLabels = new Label[lines.Count()]; var used = new bool[lineLabels.Length]; for (int parseIteration = 0; parseIteration < 3; parseIteration++) { string macroName = null; var macroBuffer = new List <UrclInstruction>(); int index = 0; foreach (var line in lines) { var trimmed = line.Trim(); if (!ParserIgnore(trimmed)) { if (parseIteration == 0) { lineLabels[index] = new Label(); } UrclInstruction result = null; IEnumerable <UrclInstruction> block = null; try { result = ParseInstruction(trimmed, parseIteration == 0, parseIteration > 0, Labels, ValueMacros, CodeMacros, (rel) => { var target = rel + index; if (target < 0 || target >= lineLabels.Length || lineLabels[target] is null) { throw new ParserError("Relative address is out of bounds."); } used[target] = true; return(lineLabels[target]); }); if (parseIteration == 1 && result != null) { if (result.Operation == Operation.COMPILER_CODEMACRO_BEGIN) { if (macroName != null) { throw new ParserError("Macro was not finished before starting another macro."); } macroName = result.Arguments[0]; macroBuffer.Clear(); result = null; } else if (result.Operation == Operation.COMPILER_CODEMACRO_END) { if (macroName == null) { throw new ParserError("Missing beginning of macro."); } CodeMacros[macroName] = macroBuffer.ToArray(); macroName = null; macroBuffer.Clear(); result = null; } else if (result.Operation == Operation.COMPILER_CODEMACRO_USE) { if (macroName != null) { throw new ParserError("Nested macros are not supported."); } if (CodeMacros.TryGetValue(result.Arguments[0], out IEnumerable <UrclInstruction> insts)) { block = insts; result = null; } else { throw new ParserError($"Undefined macro \"{result.Arguments[0]}\""); } } else if (result.Operation == Operation.IMPORT) { var name = result.Arguments[0]; block = Parse(import(string.Join(' ', result.Arguments)), import, sourceName).ToArray(); result = null; } else if (macroName != null) { macroBuffer.Add(result); result = null; } result = null; } else if (result != null) { if (result.Operation == Operation.MINRAM || result.Operation == Operation.IMPORT) { result = null; } } } catch (TargetInvocationException ex) { throw new ParserError($"Error on line {index + 1}: \"{line}\" {ex.InnerException.Message}"); } catch (ParserError ex) { throw new ParserError($"Error on line {index + 1}: \"{line}\" {ex.Message}"); } if ((result != null || block != null) && used[index]) { yield return(new UrclInstruction(Operation.COMPILER_MARKLABEL, lineLabels[Array.IndexOf(lineLabels, lineLabels[index])])); } if (result != null) { yield return(result); } if (block != null) { foreach (var inst in block) { yield return(inst); } } } else { if (parseIteration == 0) { if (index > 0) { lineLabels[index] = lineLabels[index - 1]; } else { lineLabels[index] = null; } } } index++; } } }
public void Emit(Instruction inst) { var start = Urcl.Count; var result = new UrclInstruction(Operation.NOP) { A = inst.Destination.Value, B = inst.Source.Value, C = inst.Target.Value, AType = inst.Destination.Immediate ? OperandType.Immediate : OperandType.Register, BType = inst.Source.Immediate ? OperandType.Immediate : OperandType.Register, CType = inst.Target.Immediate ? OperandType.Immediate : OperandType.Register }; if (result.AType == OperandType.Register) { result.A++; } if (result.BType == OperandType.Register) { result.B++; } if (result.CType == OperandType.Register) { result.C++; } switch (inst.Operation) { case global::SpeedAsm.Operation.Set: result.Operation = inst.Source.Immediate ? Operation.IMM : Operation.MOV; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.Add: result.Operation = Operation.ADD; break; case global::SpeedAsm.Operation.Inc: result.Operation = Operation.INC; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.Sub: result.Operation = Operation.SUB; break; case global::SpeedAsm.Operation.Dec: result.Operation = Operation.DEC; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.Mul: result.Operation = Operation.MLT; break; case global::SpeedAsm.Operation.Div: result.Operation = Operation.DIV; break; case global::SpeedAsm.Operation.Mod: result.Operation = Operation.MOD; break; case global::SpeedAsm.Operation.And: result.Operation = Operation.AND; break; case global::SpeedAsm.Operation.Or: result.Operation = Operation.OR; break; case global::SpeedAsm.Operation.Xor: result.Operation = Operation.XOR; break; case global::SpeedAsm.Operation.Not: result.Operation = Operation.NOT; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BAnd: { var skipB = new Label(); var skipC = new Label(); Urcl.AddRange(new[] { new UrclInstruction(Operation.OR, OperandType.Register, result.B, OperandType.Register, result.B, OperandType.Register, result.B), new UrclInstruction(Operation.BRZ, skipB), new UrclInstruction(Operation.IMM, OperandType.Register, result.B, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipB), new UrclInstruction(Operation.OR, OperandType.Register, result.C, OperandType.Register, result.C, OperandType.Register, result.C), new UrclInstruction(Operation.BRZ, skipC), new UrclInstruction(Operation.IMM, OperandType.Register, result.C, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipC), new UrclInstruction(Operation.AND, OperandType.Register, result.A, OperandType.Register, result.B, OperandType.Register, result.C) }); } break; case global::SpeedAsm.Operation.BOr: { var skipB = new Label(); var skipC = new Label(); Urcl.AddRange(new[] { new UrclInstruction(Operation.OR, OperandType.Register, result.B, OperandType.Register, result.B, OperandType.Register, result.B), new UrclInstruction(Operation.BRZ, skipB), new UrclInstruction(Operation.IMM, OperandType.Register, result.B, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipB), new UrclInstruction(Operation.OR, OperandType.Register, result.C, OperandType.Register, result.C, OperandType.Register, result.C), new UrclInstruction(Operation.BRZ, skipC), new UrclInstruction(Operation.IMM, OperandType.Register, result.C, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipC), new UrclInstruction(Operation.OR, OperandType.Register, result.A, OperandType.Register, result.B, OperandType.Register, result.C) }); } break; case global::SpeedAsm.Operation.BXor: { var skipB = new Label(); var skipC = new Label(); Urcl.AddRange(new[] { new UrclInstruction(Operation.OR, OperandType.Register, result.B, OperandType.Register, result.B, OperandType.Register, result.B), new UrclInstruction(Operation.BRZ, skipB), new UrclInstruction(Operation.IMM, OperandType.Register, result.B, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipB), new UrclInstruction(Operation.OR, OperandType.Register, result.C, OperandType.Register, result.C, OperandType.Register, result.C), new UrclInstruction(Operation.BRZ, skipC), new UrclInstruction(Operation.IMM, OperandType.Register, result.C, OperandType.Immediate, 1), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipC), new UrclInstruction(Operation.XOR, OperandType.Register, result.A, OperandType.Register, result.B, OperandType.Register, result.C) }); } break; case global::SpeedAsm.Operation.BNot: { var skipB = new Label(); var end = new Label(); Urcl.AddRange(new[] { new UrclInstruction(Operation.OR, OperandType.Register, result.B, OperandType.Register, result.B, OperandType.Register, result.B), new UrclInstruction(Operation.BNZ, skipB), new UrclInstruction(Operation.IMM, OperandType.Register, result.A, OperandType.Immediate, 1), new UrclInstruction(Operation.BRA, end), new UrclInstruction(Operation.COMPILER_MARKLABEL, skipB), new UrclInstruction(Operation.MOV, OperandType.Register, result.A, OperandType.Register, 0), new UrclInstruction(Operation.COMPILER_MARKLABEL, end), new UrclInstruction(Operation.OR, OperandType.Register, result.A, OperandType.Register, result.A, OperandType.Register, result.A) }); } break; case global::SpeedAsm.Operation.Label: result.Operation = Operation.COMPILER_MARKLABEL; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.Branch: result.Operation = Operation.BRA; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfNotZero: result.Operation = Operation.BNZ; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfZero: result.Operation = Operation.BRZ; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfCarry: result.Operation = Operation.BRC; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfSign: result.Operation = Operation.BRN; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfNotCarry: result.Operation = Operation.BNC; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; case global::SpeedAsm.Operation.BranchIfNotSign: result.Operation = Operation.BRN; result.AType = OperandType.Label; result.ALabel = GetLabel(result.A); result.BType = OperandType.None; result.CType = OperandType.None; break; default: throw new CompileError($"Operation is not supported for URCL: {inst.Operation}"); } if (result.Operation != Operation.NOP) { Urcl.Add(result); } if (Urcl.Count > start) { foreach (var urcl in Urcl.GetRange(start, Urcl.Count - start)) { Instructions.Enqueue(urcl); } } }
private void Emit(UrclInstruction instruction) { Instructions.Add(instruction); }