using IFPSLib.Types; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; using System.Collections; namespace IFPSLib.Emit { public sealed class Instruction : IEnumerable { /// /// The opcode /// public OpCode OpCode; /// /// The operands /// public IReadOnlyList Operands => m_Operands.AsReadOnly(); /// /// Offset of the instruction in the function body /// public uint Offset; /// /// Referenced by an operand of another instruction /// public bool Referenced; private List m_Operands = new List(); /// /// Gets or sets the operand at the specified index. /// /// The zero-based index of the operand to get or set. /// The operand at the specified index. /// No operand exists at the given index; or the operand to set is incompatible with the existing operand. public Operand this[int index] { get => m_Operands[index]; set { if (!m_Operands[index].SimilarType(value)) throw new ArgumentOutOfRangeException(nameof(value)); m_Operands[index] = value; } } /// /// Returns an enumerator that iterates through the operands. /// /// An enumerator for the operands list. public IEnumerator GetEnumerator() { return m_Operands.GetEnumerator(); } /// /// Returns an enumerator that iterates through the operands. /// /// An enumerator for the operands list. IEnumerator IEnumerable.GetEnumerator() { return m_Operands.GetEnumerator(); } /// /// Default constructor /// internal Instruction() { } /// /// Constructor /// /// Opcode internal Instruction(OpCode opcode) { OpCode = opcode; m_Operands = new List(0); } /// /// Constructor /// /// Opcode /// Operands internal Instruction(OpCode opcode, List operands) : this(opcode) { m_Operands = operands; } /// /// Creates a new instruction with no operand /// /// Opcode /// New instruction public static Instruction Create(OpCode opcode) { if (opcode.OperandType != OperandType.InlineNone) throw new ArgumentOutOfRangeException(nameof(opcode), "Must be a no-operand opcode"); return new Instruction(opcode); } /// /// Creates a new instruction with one operand /// /// Opcode /// Operand /// New instruction public static Instruction Create(OpCode opcode, Operand operand) { if (opcode.OperandType != OperandType.InlineValue && opcode.OperandType != OperandType.InlineValueSF) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a value operand"); return new Instruction(opcode, new List(1) { operand }); } /// /// Creates a new instruction with an immediate operand /// /// Opcode /// Immediate operand /// Type of operand, must be a PascalScript primitive /// New instruction public static Instruction Create(OpCode opcode, TType val) => Create(opcode, Operand.Create(val)); /// /// Creates a new instruction with a variable operand /// /// Opcode /// Variable operand /// New instruction public static Instruction Create(OpCode opcode, IVariable var) => Create(opcode, Operand.Create(var)); /// /// Creates a new branch instruction /// /// Opcode /// Branch target /// New instruction public static Instruction Create(OpCode opcode, Instruction target) { if (opcode.OperandType != OperandType.InlineBrTarget) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have an instruction operand"); target.Referenced = true; return new Instruction(opcode, new List(1) { Operand.Create(target) }); } /// /// Creates a new instruction with two operands /// /// Opcode /// First operand /// Second operand /// New instruction public static Instruction Create(OpCode opcode, Operand op0, Operand op1) { if (opcode.OperandType != OperandType.InlineValueValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have two value operands"); return new Instruction(opcode, new List(2) { op0, op1 }); } /// /// Creates a new instruction with two variable operands /// /// Opcode /// First operand /// Second operand /// New instruction public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1) => Create(opcode, Operand.Create(op0), Operand.Create(op1)); /// /// Creates a new instruction with a variable operand and an immediate operand /// /// Opcode /// First operand /// Second operand /// Type of second operand, must be a PascalScript primitive /// New instruction public static Instruction Create(OpCode opcode, IVariable op0, TType val) => Create(opcode, Operand.Create(op0), Operand.Create(val)); /// /// Creates a new branch instruction with an operand /// /// Opcode /// Branch target /// Operand /// New instruction public static Instruction Create(OpCode opcode, Instruction target, Operand operand) { if (opcode.OperandType != OperandType.InlineBrTargetValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have an instruction and a value operand"); return new Instruction(opcode, new List(2) { Operand.Create(target), operand }); } /// /// Creates a new branch instruction with a variable operand /// /// Opcode /// Branch target /// Operand /// New instruction public static Instruction Create(OpCode opcode, Instruction target, IVariable var) => Create(opcode, target, Operand.Create(var)); /// /// Creates a new call instruction /// /// Opcode /// Function /// New instruction public static Instruction Create(OpCode opcode, IFunction func) { if (opcode.OperandType != OperandType.InlineFunction) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a function operand"); return new Instruction(opcode, new List(1) { Operand.Create(func) }); } /// /// Creates a new instruction with a type operand /// /// Opcode /// Type /// New instruction public static Instruction Create(OpCode opcode, Types.IType type) { if (opcode.OperandType != OperandType.InlineType) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a function operand"); return new Instruction(opcode, new List(1) { Operand.Create(type) }); } /// /// Creates a new compare instruction /// /// Opcode /// Destination /// Left hand side /// Right hand side /// New instruction public static Instruction Create(OpCode opcode, Operand op0, Operand op1, Operand op2) { if (opcode.OperandType != OperandType.InlineCmpValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have three value operands"); return new Instruction(opcode, new List(3) { op0, op1, op2 }); } /// /// Creates a new compare instruction with two variables and an immediate /// /// Opcode /// Destination /// Left hand side /// Right hand side /// Type of right hand side, must be a PascalScript primitive /// New instruction public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1, TType op2) => Create(opcode, Operand.Create(op0), Operand.Create(op1), Operand.Create(op2)); /// /// Creates a new compare instruction with three variables /// /// Opcode /// Destination /// Left hand side /// Right hand side /// New instruction public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1, IVariable op2) => Create(opcode, Operand.Create(op0), Operand.Create(op1), Operand.Create(op2)); /// /// Creates a new compare-type instruction /// /// Opcode /// Destination /// Left hand side /// Right hand side (type) /// New instruction public static Instruction Create(OpCode opcode, Operand op0, Operand op1, Types.IType type) { if (opcode.OperandType != OperandType.InlineCmpValueType) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have two value operands and a type operand"); return new Instruction(opcode, new List(3) { op0, op1, Operand.Create(type) }); } /// /// Creates a new compare-type instruction with two variables /// /// Opcode /// Destination /// Left hand side /// Right hand side (type) /// New instruction public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1, Types.IType type) => Create(opcode, Operand.Create(op0), Operand.Create(op1), type); /// /// Creates a new StartEH instruction /// /// Start of first finally block /// Start of catch block /// Start of second finally block /// End of last exception handler block /// New instruction public static Instruction CreateStartEH(Instruction Finally, Instruction Catch, Instruction CatchFinally, Instruction End) { return new Instruction(OpCodes.StartEH, new List(4) { Operand.Create(Finally), Operand.Create(Catch), Operand.Create(CatchFinally), Operand.Create(End) }); } /// /// Creates a new StartEH instruction for a try-finally block /// /// Start of finally block /// End of finally block /// New instruction public static Instruction CreateTryFinally(Instruction Finally, Instruction End) => CreateStartEH(Finally, null, null, End); /// /// Create a new StartEH instruction for a try-catch block /// /// Start of catch block /// End of catch block /// New instruction public static Instruction CreateTryCatch(Instruction Catch, Instruction End) => CreateStartEH(null, Catch, null, End); /// /// Create a new StartEH instruction for a try-catch-finally block /// /// Start of catch block /// Start of finally block /// End of finally block /// New instruction public static Instruction CreateTryCatchFinally(Instruction Catch, Instruction Finally, Instruction End) => CreateStartEH(null, Catch, Finally, End); /// /// Creates a SetStackType instruction (removed after version 21 or 22) /// /// Type to set variable to /// Variable to set /// New instruction public static Instruction CreateSetStackType(Types.IType type, IVariable variable) { return new Instruction(OpCodes.SetStackType, new List(2) { Operand.Create(type), Operand.Create(variable) }); } /// /// Replaces this instruction with the content of another. Use instead of replacing an instruction in an array in case it is referenced by another instruction. /// /// New instruction public void Replace(Instruction insn) { OpCode = insn.OpCode; m_Operands = insn.m_Operands; Offset = insn.Offset; Referenced = insn.Referenced; } private static uint FixBranchOffset(BinaryReader br, uint data) { return data + (uint)br.BaseStream.Position; } private static uint FixBranchOffsetEH(BinaryReader br, uint data) { if (data == uint.MaxValue) return uint.MaxValue; return FixBranchOffset(br, data); } private static uint ReadBranchOffset(BinaryReader br) { return FixBranchOffset(br, br.Read()); } internal static Instruction Load(BinaryReader br, Script script, ScriptFunction function) { var ret = new Instruction(); ret.Offset = (uint)br.BaseStream.Position; OpCode opcode = null; var opFirstByte = (Code)br.ReadByte(); switch (opFirstByte) { case Code.Calculate: var calcSecond = (AluOpCode)br.ReadByte(); if (calcSecond > AluOpCode.Xor) opcode = OpCodes.UNKNOWN_ALU; else opcode = OpCodes.AluOpCodes[(int)calcSecond]; break; case Code.Compare: var cmpSecond = (CmpOpCode)br.ReadByte(); if (cmpSecond > CmpOpCode.Is) opcode = OpCodes.UNKNOWN_CMP; else opcode = OpCodes.CmpOpCodes[(int)cmpSecond]; break; case Code.PopEH: var popEhSecond = (PopEHOpCode)br.ReadByte(); if (popEhSecond > PopEHOpCode.EndHandler) opcode = OpCodes.UNKNOWN_POPEH; else opcode = OpCodes.PopEHOpCodes[(int)popEhSecond]; break; case Code.SetFlag: // placeholder opcode = OpCodes.UNKNOWN_SF; break; case Code.SetStackType: if (script.FileVersion >= Script.VERSION_MAX_SETSTACKTYPE) opcode = OpCodes.UNKNOWN1; else opcode = OpCodes.SetStackType; break; default: opcode = OpCodes.OneByteOpCodes[(int)opFirstByte]; break; } ret.OpCode = opcode; switch (opcode.OperandType) { case OperandType.InlineNone: ret.m_Operands = new List(0); break; case OperandType.InlineValue: ret.m_Operands = new List(1) { Operand.LoadValue(br, script, function) }; break; case OperandType.InlineBrTarget: ret.m_Operands = new List(1) { new Operand(new TypedData(InstructionType.Instance, ReadBranchOffset(br))) }; break; case OperandType.InlineValueValue: ret.m_Operands = new List(2) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function) }; break; case OperandType.InlineBrTargetValue: var brOffset = br.Read(); var valOp = Operand.LoadValue(br, script, function); ret.m_Operands = new List(2) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffset(br, brOffset))), valOp }; break; case OperandType.InlineFunction: ret.m_Operands = new List(1) { Operand.Create(script.Functions[(int)br.Read()]) }; break; case OperandType.InlineType: ret.m_Operands = new List(1) { Operand.Create(script.Types[(int)br.Read()]) }; break; case OperandType.InlineCmpValue: ret.m_Operands = new List(3) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function) }; break; case OperandType.InlineCmpValueType: ret.m_Operands = new List(3) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function), Operand.Create(script.Types[(int)br.Read()]) }; break; case OperandType.InlineEH: var br0 = br.Read(); var br1 = br.Read(); var br2 = br.Read(); var br3 = br.Read(); ret.m_Operands = new List(4) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br0))), new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br1))), new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br2))), new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br3))) }; break; case OperandType.InlineTypeVariable: ret.m_Operands = new List(2) { Operand.Create(script.Types[(int)br.Read()]), new Operand(VariableBase.Load(br, script, function)) }; return ret; case OperandType.InlineValueSF: ret.m_Operands = new List(1) { Operand.LoadValue(br, script, function) }; var sfSecond = (SetFlagOpCode)br.ReadByte(); if (sfSecond > SetFlagOpCode.Zero) opcode = OpCodes.UNKNOWN_SF; else opcode = OpCodes.SetFlagOpCodes[(int)sfSecond]; ret.OpCode = opcode; break; default: throw new ArgumentOutOfRangeException(string.Format("Unknown OperandType {0}", opcode.OperandType)); } return ret; } internal void FixUpBranchTargets(Dictionary table) { for (int i = 0; i < m_Operands.Count; i++) { var operand = m_Operands[i]; if (operand.m_Type != BytecodeOperandType.Immediate) continue; if (operand.ImmediateTyped.Type.BaseType != PascalTypeCode.Instruction) continue; if (!(operand.Immediate is uint)) continue; var target = operand.ImmediateAs(); if (OpCode.Code == Code.StartEH && target == uint.MaxValue) m_Operands[i] = Operand.Create((Instruction)null); else { m_Operands[i] = Operand.Create(table[target]); table[target].Referenced = true; } } } /// /// Disassembles the instruction without labelling it /// /// Instruction text public override string ToString() { return ToString(false); } /// /// Disassembles the instruction /// /// If true, labels the instruction if it's referenced by another instruction /// Instruction text public string ToString(bool forDisasm) { var sb = new StringBuilder(); if (Referenced) sb.AppendLine(string.Format("loc_{0}:", Offset.ToString("x"))); if (forDisasm) sb.Append('\t'); sb.Append(OpCode); for (int i = 0; i < m_Operands.Count; i++) { sb.Append(i == 0 ? " " : ", "); sb.Append(m_Operands[i]); } return sb.ToString(); } /// /// Gets the size of the instruction /// public int Size { get { var ret = OpCode.Size; switch (OpCode.OperandType) { case OperandType.InlineNone: break; case OperandType.InlineValue: case OperandType.InlineValueSF: ret += m_Operands[0].Size; break; case OperandType.InlineBrTarget: ret += sizeof(uint); break; case OperandType.InlineValueValue: ret += m_Operands[0].Size + m_Operands[1].Size; break; case OperandType.InlineBrTargetValue: ret += sizeof(uint) + m_Operands[1].Size; break; case OperandType.InlineFunction: case OperandType.InlineType: ret += sizeof(int); break; case OperandType.InlineCmpValue: ret += m_Operands[0].Size + m_Operands[1].Size + m_Operands[2].Size; break; case OperandType.InlineCmpValueType: ret += m_Operands[0].Size + m_Operands[1].Size + sizeof(int); break; case OperandType.InlineEH: ret += sizeof(uint) * 4; break; case OperandType.InlineTypeVariable: ret += sizeof(int) + m_Operands[1].Variable.Size; break; } return ret; } } private void FixUpReference(int index, Dictionary table, bool allowedNull = false) { var insn = m_Operands[index].ImmediateAs(); if (insn == null) { if (!allowedNull) throw new InvalidOperationException("Instruction is null"); return; } if (!table.ContainsKey(insn)) throw new InvalidOperationException("Referenced instruction is not in the same function as the referencing instruction"); insn.Referenced = true; } internal void FixUpReferences(Dictionary table) { switch (OpCode.OperandType) { case OperandType.InlineBrTarget: case OperandType.InlineBrTargetValue: FixUpReference(0, table); break; case OperandType.InlineEH: FixUpReference(0, table, true); FixUpReference(1, table, true); FixUpReference(2, table, true); FixUpReference(3, table, true); break; } } private uint GetEHOffset(Operand operand, Dictionary table) { var insn = operand.ImmediateAs(); if (insn == null) return uint.MaxValue; return table[insn] - Offset - (uint)Size; } internal void Save(BinaryWriter bw, Script.SaveContext ctx, Dictionary table) { if (OpCode.Size == 2) { var firstByte = (byte)((int)OpCode.Code >> 8); bw.Write(firstByte); if (OpCode.OperandType != OperandType.InlineValueSF) { // not setflag, so second byte follows the first bw.Write((byte)OpCode.Code); } } else if (OpCode.Size == 1) { bw.Write((byte)OpCode.Code); } else throw new InvalidOperationException(string.Format("Invalid opcode size of {0} bytes", OpCode.Size)); switch (OpCode.OperandType) { case OperandType.InlineNone: break; case OperandType.InlineValue: m_Operands[0].Save(bw, ctx); break; case OperandType.InlineBrTarget: bw.Write(table[m_Operands[0].ImmediateAs()] - Offset - (uint)Size); break; case OperandType.InlineValueValue: m_Operands[0].Save(bw, ctx); m_Operands[1].Save(bw, ctx); break; case OperandType.InlineBrTargetValue: bw.Write(table[m_Operands[0].ImmediateAs()] - Offset - (uint)Size); m_Operands[1].Save(bw, ctx); break; case OperandType.InlineFunction: bw.Write(ctx.GetFunctionIndex(m_Operands[0].ImmediateAs())); break; case OperandType.InlineType: bw.Write(ctx.GetTypeIndex(m_Operands[0].ImmediateAs())); break; case OperandType.InlineCmpValue: m_Operands[0].Save(bw, ctx); m_Operands[1].Save(bw, ctx); m_Operands[2].Save(bw, ctx); break; case OperandType.InlineCmpValueType: m_Operands[0].Save(bw, ctx); m_Operands[1].Save(bw, ctx); bw.Write(ctx.GetTypeIndex(m_Operands[2].ImmediateAs())); break; case OperandType.InlineEH: bw.Write(GetEHOffset(m_Operands[0], table)); bw.Write(GetEHOffset(m_Operands[1], table)); bw.Write(GetEHOffset(m_Operands[2], table)); bw.Write(GetEHOffset(m_Operands[3], table)); break; case OperandType.InlineTypeVariable: bw.Write(ctx.GetTypeIndex(m_Operands[0].ImmediateAs())); ((VariableBase)m_Operands[1].Variable).Save(bw, ctx); break; case OperandType.InlineValueSF: m_Operands[0].Save(bw, ctx); // setflag; second opcode byte is after the operand for some reason bw.Write((byte)OpCode.Code); break; default: throw new ArgumentOutOfRangeException(string.Format("Unknown OperandType {0}", OpCode.OperandType)); } } } }