From b4da2a46d732a3ca91d2ceea977a711c8217a488 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Tue, 6 Feb 2024 12:55:46 -0600 Subject: [PATCH] Add instruction schema and some type information --- libs/UIX.Asm/Disassembler.cs | 29 ++--- libs/UIX.Asm/InstructionSet.cs | 171 ++++++++++++++++++++++++++++ libs/UIX.Asm/Lexer.Body.cs | 24 +++- libs/UIX.Asm/Models/Instructions.cs | 67 +++++++++++ 4 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 libs/UIX.Asm/InstructionSet.cs create mode 100644 libs/UIX.Asm/Models/Instructions.cs diff --git a/libs/UIX.Asm/Disassembler.cs b/libs/UIX.Asm/Disassembler.cs index 2a9d606..201800d 100644 --- a/libs/UIX.Asm/Disassembler.cs +++ b/libs/UIX.Asm/Disassembler.cs @@ -62,7 +62,7 @@ public class Disassembler // Insert a label to mark the start of the object section. // In the future, this might use a special directive like `.section object` - yield return new Label("code"); + yield return new SectionDirective("object"); while (reader.CurrentOffset < reader.Size) { @@ -77,7 +77,7 @@ public class Disassembler case OpCode.DiscardValue: // DIS case OpCode.ReturnValue: // RET case OpCode.ReturnVoid: // RETV - yield return new Instruction(opCode, []); + yield return Instruction.CreateWithSchema(opCode); break; // CMD @@ -107,7 +107,7 @@ public class Disassembler case OpCode.TypeOf: // TYP case OpCode.PushConstant: // PSHC case OpCode.ConstructListenerStorage: // CLIS - yield return new Instruction(opCode, [new(reader.ReadUInt16())]); + yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16()); break; // CMD @@ -116,12 +116,12 @@ public class Disassembler case OpCode.JumpIfTruePeek: // JMPT case OpCode.JumpIfNullPeek: // JMPNP case OpCode.Jump: // JMP - yield return new Instruction(opCode, [new(reader.ReadUInt32())]); + yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt32()); break; // CMD case OpCode.EnterDebugState: // DBG - yield return new Instruction(opCode, [new(reader.ReadInt32())]); + yield return Instruction.CreateWithSchema(opCode, reader.ReadInt32()); break; // CMD @@ -129,11 +129,11 @@ public class Disassembler case OpCode.ConstructFromString: // CSTR case OpCode.PropertyDictionaryAdd: // PDAD case OpCode.ConvertType: // CON - yield return new Instruction(opCode, [new(reader.ReadUInt16()), new(reader.ReadUInt16())]); + yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16(), reader.ReadUInt16()); break; case OpCode.JumpIfDictionaryContains: // JMPD - yield return new Instruction(opCode, [new(reader.ReadUInt16()), new(reader.ReadUInt16()), new(reader.ReadUInt32())]); + yield return Instruction.CreateWithSchema(opCode, reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt32()); break; case OpCode.ConstructFromBinary: // CBIN @@ -141,25 +141,28 @@ public class Disassembler TypeSchema cbinTypeSchema = _loadResult.ImportTables.TypeImports[cbinTypeIndex]; object cbinObject = cbinTypeSchema.DecodeBinary(reader); - yield return new Instruction(opCode, [new(cbinTypeIndex), new(cbinObject)]); + yield return Instruction.CreateWithSchema(opCode, cbinTypeIndex, cbinObject); break; case OpCode.Operation: // OPR var opHostIndex = reader.ReadUInt16(); var op = (OperationType)reader.ReadByte(); - yield return new Instruction(opCode, op, [new(opHostIndex)]); + yield return Instruction.CreateWithSchema(opCode, opHostIndex, op); break; case OpCode.Listen: // LIS case OpCode.DestructiveListen: // LISD - List lisOperands = [new(reader.ReadUInt16()), new(reader.ReadByte()), - new(reader.ReadUInt16()), new(reader.ReadUInt32())]; + var listenerIndex = reader.ReadUInt16(); + var listenerType = reader.ReadByte(); + var watchIndex = reader.ReadUInt16(); + var handlerOffset = reader.ReadUInt32(); if (opCode == OpCode.DestructiveListen) - lisOperands.Add(new(reader.ReadUInt32())); + yield return Instruction.CreateWithSchema(opCode, listenerIndex, listenerType, watchIndex, handlerOffset, reader.ReadUInt32()); + else + yield return Instruction.CreateWithSchema(opCode, listenerIndex, listenerType, watchIndex, handlerOffset); - yield return new Instruction(opCode, lisOperands); break; default: diff --git a/libs/UIX.Asm/InstructionSet.cs b/libs/UIX.Asm/InstructionSet.cs new file mode 100644 index 0000000..1caca91 --- /dev/null +++ b/libs/UIX.Asm/InstructionSet.cs @@ -0,0 +1,171 @@ +using Microsoft.Iris.Asm.Models; +using Microsoft.Iris.Markup; +using System.Collections.Generic; + +namespace Microsoft.Iris.Asm; + +internal static class InstructionSet +{ + public static OperationType OperationMnemonicToType(string mnemonic) => OperationMnemonicMap[mnemonic.ToUpperInvariant()]; + public static OperationType? TryOperationMnemonicToType(string mnemonic) + { + return OperationMnemonicMap.TryGetRight(mnemonic.ToUpperInvariant(), out var type) + ? type : null; + } + + public static OpCode MnemonicToOpCode(string mnemonic) + { + if (MnemonicMap.TryGetRight(mnemonic, out var opCode)) + return opCode; + else if (OperationMnemonicMap.TryGetRight(mnemonic, out _)) + return OpCode.Operation; + + throw new System.ArgumentException($"'{mnemonic}' is not a known UIXA instruction.", nameof(mnemonic)); + } + + public static string GetMnemonic(OpCode opCode, OperationType? opType = null) + { + if (MnemonicMap.TryGetLeft(opCode, out var mnemonic)) + return mnemonic; + else if (opType.HasValue && OperationMnemonicMap.TryGetLeft(opType.Value, out var operationMnemonic)) + return operationMnemonic; + + throw new System.ArgumentException($"'{opCode}' is not a known UIXA instruction.", nameof(opCode)); + } + + internal static readonly DoubleDictionary OperationMnemonicMap = + [ + ("ADD", OperationType.MathAdd), + ("SUB", OperationType.MathSubtract), + ("MUL", OperationType.MathMultiply), + ("DIV", OperationType.MathDivide), + ("MOD", OperationType.MathModulus), + ("NEG", OperationType.MathModulus), + ("AND", OperationType.LogicalAnd), + ("ORR", OperationType.LogicalOr), + ("NOT", OperationType.LogicalNot), + ("REQ", OperationType.RelationalEquals), + ("RNE", OperationType.RelationalNotEquals), + ("RLT", OperationType.RelationalLessThan), + ("RGT", OperationType.RelationalGreaterThan), + ("RLE", OperationType.RelationalLessThanEquals), + ("RGE", OperationType.RelationalGreaterThanEquals), + ("RIS", OperationType.RelationalIs), + ("INC", OperationType.PostIncrement), + ("DEC", OperationType.PostDecrement), + ]; + + internal static readonly DoubleDictionary MnemonicMap = + [ + ("COBJ", OpCode.ConstructObject), + ("COBJI", OpCode.ConstructObjectIndirect), + ("COBP", OpCode.ConstructObjectParam), + ("CSTR", OpCode.ConstructFromString), + ("CBIN", OpCode.ConstructFromBinary), + ("INIT", OpCode.InitializeInstance), + ("INITI", OpCode.InitializeInstanceIndirect), + ("LSYM", OpCode.LookupSymbol), + ("WSYM", OpCode.WriteSymbol), + ("WSYMP", OpCode.WriteSymbolPeek), + ("CSYM", OpCode.ClearSymbol), + ("PINI", OpCode.PropertyInitialize), + ("PINII", OpCode.PropertyInitializeIndirect), + ("PLAD", OpCode.PropertyListAdd), + ("PDAD", OpCode.PropertyDictionaryAdd), + ("PASS", OpCode.PropertyAssign), + ("PASST", OpCode.PropertyAssignStatic), + ("PGET", OpCode.PropertyGet), + ("PGETP", OpCode.PropertyGetPeek), + ("PGETT", OpCode.PropertyGetStatic), + ("MINV", OpCode.MethodInvoke), + ("MINVP", OpCode.MethodInvokePeek), + ("MINVT", OpCode.MethodInvokeStatic), + ("MINVA", OpCode.MethodInvokePushLastParam), + ("MINVAT", OpCode.MethodInvokeStaticPushLastParam), // Avoid using "LT" as suffix + ("VTC", OpCode.VerifyTypeCast), + ("CON", OpCode.ConvertType), + ("OPR", OpCode.Operation), // Generic operation, allow dynamic invocations of operators + ("ISC", OpCode.IsCheck), + ("ASC", OpCode.As), + ("TYP", OpCode.TypeOf), + ("PSHN", OpCode.PushNull), + ("PSHC", OpCode.PushConstant), + ("PSHT", OpCode.PushThis), + ("DIS", OpCode.DiscardValue), + ("RET", OpCode.ReturnValue), + ("RETV", OpCode.ReturnVoid), + ("JMPF", OpCode.JumpIfFalse), + ("JMPFP", OpCode.JumpIfFalsePeek), + ("JMPTP", OpCode.JumpIfTruePeek), + ("JMPD", OpCode.JumpIfDictionaryContains), + ("JMPNP", OpCode.JumpIfNullPeek), + ("JMP", OpCode.Jump), + ("CLIS", OpCode.ConstructListenerStorage), + ("LIS", OpCode.Listen), + ("LISD", OpCode.DestructiveListen), + ("DBG", OpCode.EnterDebugState), + ]; + + private static readonly OperandDataType[] Inst_UInt16 = [OperandDataType.UInt16]; + private static readonly OperandDataType[] Inst_UInt32 = [OperandDataType.UInt32]; + private static readonly OperandDataType[] Inst_UInt16x2 = [OperandDataType.UInt16, OperandDataType.UInt16]; + + public static readonly Dictionary InstructionSchema = new() + { + [OpCode.InitializeInstanceIndirect] = [], + [OpCode.PushNull] = [], + [OpCode.PushThis] = [], + [OpCode.DiscardValue] = [], + [OpCode.ReturnValue] = [], + [OpCode.ReturnVoid] = [], + + [OpCode.ConstructObject] = Inst_UInt16, + [OpCode.ConstructObjectIndirect] = Inst_UInt16, + [OpCode.InitializeInstance] = Inst_UInt16, + [OpCode.LookupSymbol] = Inst_UInt16, + [OpCode.WriteSymbol] = Inst_UInt16, + [OpCode.WriteSymbolPeek] = Inst_UInt16, + [OpCode.ClearSymbol] = Inst_UInt16, + [OpCode.PropertyInitialize] = Inst_UInt16, + [OpCode.PropertyInitializeIndirect] = Inst_UInt16, + [OpCode.PropertyListAdd] = Inst_UInt16, + [OpCode.PropertyAssign] = Inst_UInt16, + [OpCode.PropertyAssignStatic] = Inst_UInt16, + [OpCode.PropertyGet] = Inst_UInt16, + [OpCode.PropertyGetPeek] = Inst_UInt16, + [OpCode.PropertyGetStatic] = Inst_UInt16, + [OpCode.MethodInvoke] = Inst_UInt16, + [OpCode.MethodInvokePeek] = Inst_UInt16, + [OpCode.MethodInvokeStatic] = Inst_UInt16, + [OpCode.MethodInvokePushLastParam] = Inst_UInt16, + [OpCode.MethodInvokeStaticPushLastParam] = Inst_UInt16, + [OpCode.VerifyTypeCast] = Inst_UInt16, + [OpCode.IsCheck] = Inst_UInt16, + [OpCode.As] = Inst_UInt16, + [OpCode.TypeOf] = Inst_UInt16, + [OpCode.PushConstant] = Inst_UInt16, + [OpCode.ConstructListenerStorage] = Inst_UInt16, + + [OpCode.JumpIfFalse] = Inst_UInt32, + [OpCode.JumpIfFalsePeek] = Inst_UInt32, + [OpCode.JumpIfTruePeek] = Inst_UInt32, + [OpCode.JumpIfNullPeek] = Inst_UInt32, + [OpCode.Jump] = Inst_UInt32, + + [OpCode.EnterDebugState] = [OperandDataType.Int32], + + [OpCode.ConstructObjectParam] = Inst_UInt16x2, + [OpCode.ConstructFromString] = Inst_UInt16x2, + [OpCode.PropertyDictionaryAdd] = Inst_UInt16x2, + [OpCode.ConvertType] = Inst_UInt16x2, + + [OpCode.JumpIfDictionaryContains] = [OperandDataType.UInt16, OperandDataType.UInt16, OperandDataType.UInt32], + + [OpCode.ConstructFromBinary] = [OperandDataType.UInt16, OperandDataType.Bytes], + + [OpCode.Operation] = [OperandDataType.UInt16, OperandDataType.Byte], + + [OpCode.Listen] = [OperandDataType.UInt16, OperandDataType.Byte, OperandDataType.UInt16, OperandDataType.UInt32], + [OpCode.DestructiveListen] = [OperandDataType.UInt16, OperandDataType.Byte, OperandDataType.UInt16, OperandDataType.UInt32, OperandDataType.UInt32], + }; +} \ No newline at end of file diff --git a/libs/UIX.Asm/Lexer.Body.cs b/libs/UIX.Asm/Lexer.Body.cs index 7c996fa..5df8a5f 100644 --- a/libs/UIX.Asm/Lexer.Body.cs +++ b/libs/UIX.Asm/Lexer.Body.cs @@ -54,8 +54,30 @@ partial class Lexer input = ConsumeWhitespace(input); var operandsResult = Parse.Ref(() => AlphanumericText).DelimitedBy(Parse.Char(',').Token())(input); - operands.AddRange(operandsResult.Value.Select(s => new Operand(s) { Line = line })); input = operandsResult.Remainder; + + var opCode = InstructionSet.MnemonicToOpCode(identifier); + var schema = InstructionSet.InstructionSchema[opCode]; + + int schemaIndex = 0; + foreach (var operandContent in operandsResult.Value) + { + var operandType = schema[schemaIndex++]; + + object operandValue = operandType switch + { + OperandDataType.Byte => byte.Parse(operandContent), + OperandDataType.UInt16 => ushort.Parse(operandContent), + OperandDataType.UInt32 => uint.Parse(operandContent), + OperandDataType.Int32 => int.Parse(operandContent), + OperandDataType.Bytes or _ => operandContent, + }; + + operands.Add(new(operandValue, operandType, operandContent) + { + Line = line, + }); + } } bodyItem = new Instruction(identifier, operands) diff --git a/libs/UIX.Asm/Models/Instructions.cs b/libs/UIX.Asm/Models/Instructions.cs new file mode 100644 index 0000000..dd32e47 --- /dev/null +++ b/libs/UIX.Asm/Models/Instructions.cs @@ -0,0 +1,67 @@ +using Microsoft.Iris.Markup; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.Iris.Asm.Models; + +[DebuggerDisplay("{ToString()} " + DebuggerDisplay)] +public record Instruction(string Mnemonic, IEnumerable Operands) : BodyItem +{ + public Instruction(OpCode opCode, OperationType? operationType, IEnumerable Operands) + : this(InstructionSet.GetMnemonic(opCode, operationType), Operands) + { + } + public Instruction(OpCode opCode, IEnumerable Operands) + : this(opCode, null, Operands) + { + } + + public OpCode OpCode => InstructionSet.MnemonicToOpCode(Mnemonic); + public OperationType? OperationType => InstructionSet.TryOperationMnemonicToType(Mnemonic); + + public override string ToString() => ToString(true); + + public string ToString(bool uppercase) + { + var mnemonic = uppercase ? Mnemonic.ToUpperInvariant() : Mnemonic.ToLowerInvariant(); + return Operands.Any() + ? $"{mnemonic} {string.Join(", ", Operands)}" + : mnemonic; + } + + public static Instruction CreateParamless(OpCode opCode) => new(opCode, Array.Empty()); + + public static Instruction CreateUInt16(OpCode opCode, ushort operand1) + => new(opCode, [new(operand1, OperandDataType.UInt16)]); + + public static Instruction CreateUInt32(OpCode opCode, uint operand1) + => new(opCode, [new(operand1, OperandDataType.UInt32)]); + + public static Instruction CreateInt32(OpCode opCode, int operand1) + => new(opCode, [new(operand1, OperandDataType.Int32)]); + + public static Instruction CreateUInt16UInt16(OpCode opCode, ushort operand1, ushort operand2) + => new(opCode, [new(operand1, OperandDataType.UInt16), new(operand2, OperandDataType.UInt16)]); + + public static Instruction CreateWithSchema(OpCode opCode, params object[] operands) + { + var schema = InstructionSet.InstructionSchema[opCode]; + if (operands.Length != schema.Length) + throw new ArgumentException($"{opCode} requires {schema.Length} operands, got {operands.Length}"); + + var operandModels = new Operand[operands.Length]; + for (int i = 0; i < schema.Length; i++) + { + var operandValue = operands[i]; + // Should we verify types? + + operandModels[i] = new(operandValue, schema[i]); + } + + var operationType = operands.Length > 0 ? operands[0] as OperationType? : null; + + return new Instruction(opCode, operationType, operandModels); + } +}