diff --git a/DMCompiler/Bytecode/BytecodeProcDecoder.cs b/DMCompiler/Bytecode/BytecodeProcDecoder.cs
new file mode 100644
index 0000000000..265ea8f222
--- /dev/null
+++ b/DMCompiler/Bytecode/BytecodeProcDecoder.cs
@@ -0,0 +1,328 @@
+namespace DMCompiler.Bytecode;
+
+///
+/// Decodes OpenDream proc bytecode into typed instruction records
+///
+public sealed class BytecodeProcDecoder(IReadOnlyList strings, byte[] bytecode) {
+ public readonly IReadOnlyList Strings = strings;
+ public readonly byte[] Bytecode = bytecode;
+ public int Offset = 0;
+
+ public bool Remaining => Offset < Bytecode.Length;
+
+ public int ReadByte() {
+ return Bytecode[Offset++];
+ }
+
+ public DreamProcOpcode ReadOpcode() {
+ return (DreamProcOpcode)ReadByte();
+ }
+
+ public int ReadInt() {
+ int value = BitConverter.ToInt32(Bytecode, Offset);
+ Offset += 4;
+ return value;
+ }
+
+ public float ReadFloat() {
+ float value = BitConverter.ToSingle(Bytecode, Offset);
+ Offset += 4;
+ return value;
+ }
+
+ public string ReadString() {
+ int stringId = ReadInt();
+ return Strings[stringId];
+ }
+
+ public DMReference ReadReference() {
+ DMReference.Type refType = (DMReference.Type)ReadByte();
+
+ switch (refType) {
+ case DMReference.Type.Argument: return DMReference.CreateArgument(ReadByte());
+ case DMReference.Type.Local: return DMReference.CreateLocal(ReadByte());
+ case DMReference.Type.Global: return DMReference.CreateGlobal(ReadInt());
+ case DMReference.Type.GlobalProc: return DMReference.CreateGlobalProc(ReadInt());
+ case DMReference.Type.Field: return DMReference.CreateField(ReadString());
+ case DMReference.Type.SrcField: return DMReference.CreateSrcField(ReadString());
+ case DMReference.Type.SrcProc: return DMReference.CreateSrcProc(ReadString());
+ case DMReference.Type.Src: return DMReference.Src;
+ case DMReference.Type.Self: return DMReference.Self;
+ case DMReference.Type.Usr: return DMReference.Usr;
+ case DMReference.Type.Args: return DMReference.Args;
+ case DMReference.Type.World: return DMReference.World;
+ case DMReference.Type.SuperProc: return DMReference.SuperProc;
+ case DMReference.Type.ListIndex: return DMReference.ListIndex;
+ case DMReference.Type.Caller: return DMReference.Caller;
+ case DMReference.Type.Callee: return DMReference.Callee;
+ case DMReference.Type.NoRef: return new DMReference { RefType = DMReference.Type.NoRef };
+ default: throw new Exception($"Invalid reference type {refType}");
+ }
+ }
+
+ public int? GetBranchTarget(ProcInstruction instruction) {
+ switch (instruction.Opcode) {
+ case DreamProcOpcode.Spawn:
+ case DreamProcOpcode.BooleanOr:
+ case DreamProcOpcode.BooleanAnd:
+ case DreamProcOpcode.SwitchCase:
+ case DreamProcOpcode.SwitchCaseRange:
+ case DreamProcOpcode.Jump:
+ case DreamProcOpcode.JumpIfFalse:
+ case DreamProcOpcode.JumpIfNull:
+ case DreamProcOpcode.JumpIfNullNoPop:
+ case DreamProcOpcode.TryNoValue:
+ return instruction.GetInt(0);
+ case DreamProcOpcode.SwitchOnFloat:
+ case DreamProcOpcode.SwitchOnString:
+ return instruction.GetInt(1);
+ case DreamProcOpcode.JumpIfFalseReference:
+ case DreamProcOpcode.JumpIfTrueReference:
+ case DreamProcOpcode.JumpIfReferenceFalse:
+ return instruction.GetInt(1);
+ case DreamProcOpcode.Try:
+ return instruction.GetInt(0);
+ case DreamProcOpcode.Enumerate:
+ return instruction.GetInt(2);
+ case DreamProcOpcode.EnumerateAssoc:
+ return instruction.GetInt(3);
+ case DreamProcOpcode.EnumerateNoAssign:
+ return instruction.GetInt(1);
+ default:
+ return null;
+ }
+ }
+
+ public ProcInstruction DecodeInstruction() {
+ int startOffset = Offset;
+ DreamProcOpcode opcode = ReadOpcode();
+
+ switch (opcode) {
+ case DreamProcOpcode.FormatString:
+ return Instruction(startOffset, opcode, ProcOperand.FromString(ReadString()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.PushStringFloat:
+ return Instruction(startOffset, opcode, ProcOperand.FromString(ReadString()), ProcOperand.FromFloat(ReadFloat()));
+ case DreamProcOpcode.PushFloatAssign:
+ return Instruction(startOffset, opcode, ProcOperand.FromFloat(ReadFloat()), ProcOperand.FromReference(ReadReference()));
+ case DreamProcOpcode.NPushFloatAssign: {
+ var count = ReadInt();
+ var floats = new float[count];
+ var refs = new DMReference[count];
+
+ for (int i = 0; i < count; i++) {
+ floats[i] = ReadFloat();
+ refs[i] = ReadReference();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromFloats(floats), ProcOperand.FromReferences(refs));
+ }
+ case DreamProcOpcode.PushString:
+ case DreamProcOpcode.PushResource:
+ case DreamProcOpcode.DereferenceField:
+ return Instruction(startOffset, opcode, ProcOperand.FromString(ReadString()));
+
+ case DreamProcOpcode.DereferenceCall:
+ return Instruction(startOffset, opcode, ProcOperand.FromString(ReadString()),
+ ProcOperand.FromCallArgumentsType((DMCallArgumentsType)ReadByte()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.Prompt:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.PushFloat:
+ case DreamProcOpcode.ReturnFloat:
+ return Instruction(startOffset, opcode, ProcOperand.FromFloat(ReadFloat()));
+
+ case DreamProcOpcode.SwitchOnFloat:
+ return Instruction(startOffset, opcode, ProcOperand.FromFloat(ReadFloat()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.SwitchOnString:
+ return Instruction(startOffset, opcode, ProcOperand.FromString(ReadString()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.Assign:
+ case DreamProcOpcode.Append:
+ case DreamProcOpcode.Remove:
+ case DreamProcOpcode.Combine:
+ case DreamProcOpcode.Increment:
+ case DreamProcOpcode.Decrement:
+ case DreamProcOpcode.Mask:
+ case DreamProcOpcode.MultiplyReference:
+ case DreamProcOpcode.DivideReference:
+ case DreamProcOpcode.BitXorReference:
+ case DreamProcOpcode.ModulusReference:
+ case DreamProcOpcode.BitShiftLeftReference:
+ case DreamProcOpcode.BitShiftRightReference:
+ case DreamProcOpcode.OutputReference:
+ case DreamProcOpcode.PushReferenceValue:
+ case DreamProcOpcode.PopReference:
+ case DreamProcOpcode.AppendNoPush:
+ case DreamProcOpcode.NullRef:
+ case DreamProcOpcode.AssignNoPush:
+ case DreamProcOpcode.ReturnReferenceValue:
+ return Instruction(startOffset, opcode, ProcOperand.FromReference(ReadReference()));
+
+ case DreamProcOpcode.Input:
+ return Instruction(startOffset, opcode, ProcOperand.FromReference(ReadReference()),
+ ProcOperand.FromReference(ReadReference()));
+
+ case DreamProcOpcode.PushRefAndDereferenceField:
+ case DreamProcOpcode.IndexRefWithString:
+ return Instruction(startOffset, opcode, ProcOperand.FromReference(ReadReference()),
+ ProcOperand.FromString(ReadString()));
+
+ case DreamProcOpcode.CallStatement:
+ case DreamProcOpcode.CreateObject:
+ case DreamProcOpcode.Gradient:
+ case DreamProcOpcode.Rgb:
+ case DreamProcOpcode.Animate:
+ return Instruction(startOffset, opcode, ProcOperand.FromCallArgumentsType((DMCallArgumentsType)ReadByte()),
+ ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.Call:
+ return Instruction(startOffset, opcode, ProcOperand.FromReference(ReadReference()),
+ ProcOperand.FromCallArgumentsType((DMCallArgumentsType)ReadByte()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.CreateList:
+ case DreamProcOpcode.CreateAssociativeList:
+ case DreamProcOpcode.CreateStrictAssociativeList:
+ case DreamProcOpcode.PickWeighted:
+ case DreamProcOpcode.PickUnweighted:
+ case DreamProcOpcode.Spawn:
+ case DreamProcOpcode.BooleanOr:
+ case DreamProcOpcode.BooleanAnd:
+ case DreamProcOpcode.SwitchCase:
+ case DreamProcOpcode.SwitchCaseRange:
+ case DreamProcOpcode.Jump:
+ case DreamProcOpcode.JumpIfFalse:
+ case DreamProcOpcode.PushType:
+ case DreamProcOpcode.PushProc:
+ case DreamProcOpcode.MassConcatenation:
+ case DreamProcOpcode.JumpIfNull:
+ case DreamProcOpcode.JumpIfNullNoPop:
+ case DreamProcOpcode.TryNoValue:
+ case DreamProcOpcode.CreateListEnumerator:
+ case DreamProcOpcode.CreateRangeEnumerator:
+ case DreamProcOpcode.CreateTypeEnumerator:
+ case DreamProcOpcode.DestroyEnumerator:
+ case DreamProcOpcode.IsTypeDirect:
+ case DreamProcOpcode.CreateMultidimensionalList:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.JumpIfTrueReference:
+ case DreamProcOpcode.JumpIfFalseReference:
+ case DreamProcOpcode.JumpIfReferenceFalse:
+ return Instruction(startOffset, opcode, ProcOperand.FromReference(ReadReference()),
+ ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.Try:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()),
+ ProcOperand.FromReference(ReadReference()));
+
+ case DreamProcOpcode.Enumerate:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()),
+ ProcOperand.FromReference(ReadReference()), ProcOperand.FromInt(ReadInt()));
+ case DreamProcOpcode.EnumerateAssoc:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()),
+ ProcOperand.FromReference(ReadReference()), ProcOperand.FromReference(ReadReference()),
+ ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.CreateFilteredListEnumerator:
+ case DreamProcOpcode.EnumerateNoAssign:
+ return Instruction(startOffset, opcode, ProcOperand.FromInt(ReadInt()), ProcOperand.FromInt(ReadInt()));
+
+ case DreamProcOpcode.CreateListNRefs:
+ case DreamProcOpcode.PushNRefs: {
+ var count = ReadInt();
+ var values = new DMReference[count];
+
+ for (int i = 0; i < count; i++) {
+ values[i] = ReadReference();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromReferences(values));
+ }
+
+ case DreamProcOpcode.CreateListNStrings:
+ case DreamProcOpcode.PushNStrings: {
+ var count = ReadInt();
+ var values = new string[count];
+
+ for (int i = 0; i < count; i++) {
+ values[i] = ReadString();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromStrings(values));
+ }
+
+ case DreamProcOpcode.CreateListNFloats:
+ case DreamProcOpcode.PushNFloats: {
+ var count = ReadInt();
+ var values = new float[count];
+
+ for (int i = 0; i < count; i++) {
+ values[i] = ReadFloat();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromFloats(values));
+ }
+
+ case DreamProcOpcode.PushNOfStringFloats: {
+ var count = ReadInt();
+ var strings = new string[count];
+ var floats = new float[count];
+
+ for (int i = 0; i < count; i++) {
+ strings[i] = ReadString();
+ floats[i] = ReadFloat();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromStrings(strings), ProcOperand.FromFloats(floats));
+ }
+
+ case DreamProcOpcode.CreateListNResources:
+ case DreamProcOpcode.PushNResources: {
+ var count = ReadInt();
+ var values = new string[count];
+
+ for (int i = 0; i < count; i++) {
+ values[i] = ReadString();
+ }
+
+ return Instruction(startOffset, opcode, ProcOperand.FromStrings(values));
+ }
+
+ default:
+ return Instruction(startOffset, opcode);
+ }
+ }
+
+ private ProcInstruction Instruction(int startOffset, DreamProcOpcode opcode) {
+ return new ProcInstruction(startOffset, Offset, opcode);
+ }
+
+ private ProcInstruction Instruction(int startOffset, DreamProcOpcode opcode, ProcOperand operand0) {
+ return new ProcInstruction(startOffset, Offset, opcode, operand0);
+ }
+
+ private ProcInstruction Instruction(int startOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1) {
+ return new ProcInstruction(startOffset, Offset, opcode, operand0, operand1);
+ }
+
+ private ProcInstruction Instruction(int startOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1, ProcOperand operand2) {
+ return new ProcInstruction(startOffset, Offset, opcode, operand0, operand1, operand2);
+ }
+
+ private ProcInstruction Instruction(int startOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1, ProcOperand operand2, ProcOperand operand3) {
+ return new ProcInstruction(startOffset, Offset, opcode, operand0, operand1, operand2, operand3);
+ }
+
+ public IEnumerable<(int Offset, ProcInstruction Instruction)> Disassemble() {
+ while (Remaining) {
+ ProcInstruction instruction = DecodeInstruction();
+ yield return (instruction.Offset, instruction);
+ }
+ }
+}
diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs
index d47c7b98ae..ad53e337b6 100644
--- a/DMCompiler/Bytecode/DreamProcOpcode.cs
+++ b/DMCompiler/Bytecode/DreamProcOpcode.cs
@@ -136,7 +136,7 @@ public enum DreamProcOpcode : byte {
EnumerateAssoc = 0x43,
[OpcodeMetadata(-2)]
Link = 0x44,
- [OpcodeMetadata(-3, OpcodeArgType.TypeId)]
+ [OpcodeMetadata(-4, OpcodeArgType.TypeId)]
Prompt = 0x45,
[OpcodeMetadata(-3)]
Ftp = 0x46,
@@ -301,7 +301,7 @@ public enum DreamProcOpcode : byte {
ReturnFloat = 0x98,
[OpcodeMetadata(1, OpcodeArgType.Reference, OpcodeArgType.String)]
IndexRefWithString = 0x99,
- [OpcodeMetadata(2, OpcodeArgType.Float, OpcodeArgType.Reference)]
+ [OpcodeMetadata(0, OpcodeArgType.Float, OpcodeArgType.Reference)]
PushFloatAssign = 0x9A,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
NPushFloatAssign = 0x9B,
diff --git a/DMCompiler/Bytecode/ProcInstruction.cs b/DMCompiler/Bytecode/ProcInstruction.cs
new file mode 100644
index 0000000000..4d029c20df
--- /dev/null
+++ b/DMCompiler/Bytecode/ProcInstruction.cs
@@ -0,0 +1,389 @@
+using System.Globalization;
+using System.Text;
+using DMCompiler.DM;
+
+namespace DMCompiler.Bytecode;
+
+public enum ProcOperandKind : byte {
+ None,
+ Int,
+ Float,
+ String,
+ Reference,
+ CallArgumentsType,
+ References,
+ Floats,
+ Strings
+}
+
+///
+/// A typed bytecode operand. Array operands are used only by compact variable-argument opcodes
+/// TODO: When .NET 11 is out, revisit this with C# 15 unions
+///
+public readonly struct ProcOperand {
+ public readonly ProcOperandKind Kind;
+ public readonly int Int;
+ public readonly float Float;
+ public readonly string? String;
+ public readonly DMReference Reference;
+ public readonly DMCallArgumentsType CallArgumentsType;
+ public readonly DMReference[]? References;
+ public readonly float[]? Floats;
+ public readonly string[]? Strings;
+
+ private ProcOperand(ProcOperandKind kind, int intValue = default, float floatValue = default,
+ string? stringValue = default, DMReference reference = default,
+ DMCallArgumentsType callArgumentsType = default, DMReference[]? references = default,
+ float[]? floats = default, string[]? strings = default) {
+ Kind = kind;
+ Int = intValue;
+ Float = floatValue;
+ String = stringValue;
+ Reference = reference;
+ CallArgumentsType = callArgumentsType;
+ References = references;
+ Floats = floats;
+ Strings = strings;
+ }
+
+ public static ProcOperand FromInt(int value) {
+ return new ProcOperand(ProcOperandKind.Int, intValue: value);
+ }
+
+ public static ProcOperand FromFloat(float value) {
+ return new ProcOperand(ProcOperandKind.Float, floatValue: value);
+ }
+
+ public static ProcOperand FromString(string value) {
+ return new ProcOperand(ProcOperandKind.String, stringValue: value);
+ }
+
+ public static ProcOperand FromReference(DMReference value) {
+ return new ProcOperand(ProcOperandKind.Reference, reference: value);
+ }
+
+ public static ProcOperand FromCallArgumentsType(DMCallArgumentsType value) {
+ return new ProcOperand(ProcOperandKind.CallArgumentsType, callArgumentsType: value);
+ }
+
+ public static ProcOperand FromReferences(DMReference[] values) {
+ return new ProcOperand(ProcOperandKind.References, references: values);
+ }
+
+ public static ProcOperand FromFloats(float[] values) {
+ return new ProcOperand(ProcOperandKind.Floats, floats: values);
+ }
+
+ public static ProcOperand FromStrings(string[] values) {
+ return new ProcOperand(ProcOperandKind.Strings, strings: values);
+ }
+
+ public override string ToString() {
+ return Kind switch {
+ ProcOperandKind.Int => Int.ToString(),
+ ProcOperandKind.Float => Float.ToString(CultureInfo.InvariantCulture),
+ ProcOperandKind.String => String ?? string.Empty,
+ ProcOperandKind.Reference => Reference.ToString(),
+ ProcOperandKind.CallArgumentsType => CallArgumentsType.ToString(),
+ ProcOperandKind.References => References is null ? string.Empty : string.Join(' ', References),
+ ProcOperandKind.Floats => Floats is null ? string.Empty : string.Join(' ', Floats),
+ ProcOperandKind.Strings => Strings is null ? string.Empty : string.Join(' ', Strings),
+ _ => string.Empty
+ };
+ }
+}
+
+///
+/// A decoded instruction from a proc bytecode stream
+///
+public readonly struct ProcInstruction(int offset, int endOffset, DreamProcOpcode opcode) {
+ public readonly int Offset = offset;
+
+ // ReSharper disable once UnusedMember.Global
+ public readonly int EndOffset = endOffset; // TODO: CFG exporter will use this
+ public readonly DreamProcOpcode Opcode = opcode;
+ public readonly int OperandCount = 0;
+ private readonly ProcOperand _operand0;
+ private readonly ProcOperand _operand1;
+ private readonly ProcOperand _operand2;
+ private readonly ProcOperand _operand3;
+
+ public ProcInstruction(int offset, int endOffset, DreamProcOpcode opcode, ProcOperand operand0) : this(offset,
+ endOffset, opcode) {
+ OperandCount = 1;
+ _operand0 = operand0;
+ }
+
+ public ProcInstruction(int offset, int endOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1) : this(offset, endOffset, opcode, operand0) {
+ OperandCount = 2;
+ _operand1 = operand1;
+ }
+
+ public ProcInstruction(int offset, int endOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1, ProcOperand operand2) : this(offset, endOffset, opcode, operand0, operand1) {
+ OperandCount = 3;
+ _operand2 = operand2;
+ }
+
+ public ProcInstruction(int offset, int endOffset, DreamProcOpcode opcode, ProcOperand operand0,
+ ProcOperand operand1, ProcOperand operand2, ProcOperand operand3) : this(offset, endOffset, opcode, operand0,
+ operand1, operand2) {
+ OperandCount = 4;
+ _operand3 = operand3;
+ }
+
+ public ProcOperand GetOperand(int index) {
+ if ((uint)index >= (uint)OperandCount)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ return index switch {
+ 0 => _operand0,
+ 1 => _operand1,
+ 2 => _operand2,
+ 3 => _operand3,
+ _ => throw new ArgumentOutOfRangeException(nameof(index))
+ };
+ }
+
+ public int GetInt(int index) {
+ return GetOperand(index).Int;
+ }
+
+ public float GetFloat(int index) {
+ return GetOperand(index).Float;
+ }
+
+ public string GetString(int index) {
+ return GetOperand(index).String!;
+ }
+
+ public DMReference GetReference(int index) {
+ return GetOperand(index).Reference;
+ }
+
+ public DMCallArgumentsType GetCallArgumentsType(int index) {
+ return GetOperand(index).CallArgumentsType;
+ }
+
+ public DMReference[] GetReferences(int index) {
+ return GetOperand(index).References!;
+ }
+
+ public float[] GetFloats(int index) {
+ return GetOperand(index).Floats!;
+ }
+
+ public string[] GetStrings(int index) {
+ return GetOperand(index).Strings!;
+ }
+
+ public string Format(Func getTypePath) {
+ StringBuilder text = new();
+ text.Append(Opcode.ToString());
+ text.Append(' ');
+
+ switch (Opcode) {
+ case DreamProcOpcode.FormatString:
+ text.Append(GetInt(1));
+ text.Append(' ');
+ AppendQuoted(text, GetString(0));
+ break;
+
+ case DreamProcOpcode.PushResource:
+ AppendResource(text, GetString(0));
+ break;
+
+ case DreamProcOpcode.Spawn:
+ case DreamProcOpcode.BooleanOr:
+ case DreamProcOpcode.BooleanAnd:
+ case DreamProcOpcode.SwitchCase:
+ case DreamProcOpcode.SwitchCaseRange:
+ case DreamProcOpcode.Jump:
+ case DreamProcOpcode.JumpIfFalse:
+ case DreamProcOpcode.JumpIfNull:
+ case DreamProcOpcode.JumpIfNullNoPop:
+ case DreamProcOpcode.TryNoValue:
+ AppendOffset(text, GetInt(0));
+ break;
+
+ case DreamProcOpcode.SwitchOnFloat:
+ text.Append(GetFloat(0));
+ text.Append(' ');
+ AppendOffset(text, GetInt(1));
+ break;
+
+ case DreamProcOpcode.SwitchOnString:
+ AppendQuoted(text, GetString(0));
+ text.Append(' ');
+ AppendOffset(text, GetInt(1));
+ break;
+
+ case DreamProcOpcode.JumpIfFalseReference:
+ case DreamProcOpcode.JumpIfTrueReference:
+ case DreamProcOpcode.JumpIfReferenceFalse:
+ text.Append(GetReference(0).ToString());
+ text.Append(' ');
+ AppendOffset(text, GetInt(1));
+ break;
+
+ case DreamProcOpcode.Try:
+ AppendOffset(text, GetInt(0));
+ text.Append(' ');
+ text.Append(GetReference(1).ToString());
+ break;
+
+ case DreamProcOpcode.Enumerate:
+ text.Append(GetInt(0));
+ text.Append(' ');
+ text.Append(GetReference(1).ToString());
+ text.Append(' ');
+ AppendOffset(text, GetInt(2));
+ break;
+
+ case DreamProcOpcode.EnumerateAssoc:
+ text.Append(GetInt(0));
+ text.Append(' ');
+ text.Append(GetReference(1).ToString());
+ text.Append(' ');
+ text.Append(GetReference(2).ToString());
+ text.Append(' ');
+ AppendOffset(text, GetInt(3));
+ break;
+
+ case DreamProcOpcode.EnumerateNoAssign:
+ text.Append(GetInt(0));
+ text.Append(' ');
+ AppendOffset(text, GetInt(1));
+ break;
+
+ case DreamProcOpcode.PushType:
+ case DreamProcOpcode.IsTypeDirect:
+ text.Append(getTypePath(GetInt(0)));
+ break;
+
+ case DreamProcOpcode.Prompt:
+ text.Append((((DMValueType)GetInt(0))).ToString());
+ break;
+
+ case DreamProcOpcode.CreateFilteredListEnumerator:
+ text.Append(GetInt(0));
+ text.Append(' ');
+ text.Append(getTypePath(GetInt(1)));
+ break;
+
+ case DreamProcOpcode.CreateListNRefs:
+ case DreamProcOpcode.PushNRefs:
+ AppendMany(text, GetReferences(0));
+ break;
+
+ case DreamProcOpcode.CreateListNStrings:
+ case DreamProcOpcode.PushNStrings:
+ AppendManyQuoted(text, GetStrings(0));
+ break;
+
+ case DreamProcOpcode.CreateListNFloats:
+ case DreamProcOpcode.PushNFloats:
+ AppendMany(text, GetFloats(0));
+ break;
+
+ case DreamProcOpcode.CreateListNResources:
+ case DreamProcOpcode.PushNResources:
+ AppendManyResources(text, GetStrings(0));
+ break;
+
+ case DreamProcOpcode.PushNOfStringFloats: {
+ string[] strings = GetStrings(0);
+ float[] floats = GetFloats(1);
+ for (int i = 0; i < strings.Length; i++) {
+ AppendQuoted(text, strings[i]);
+ text.Append(' ');
+ text.Append(floats[i]);
+ if (i + 1 < strings.Length)
+ text.Append(' ');
+ }
+
+ break;
+ }
+
+ case DreamProcOpcode.PushFloatAssign:
+ text.Append(GetFloat(0));
+ text.Append(' ');
+ text.Append(GetReference(1).ToString());
+ break;
+
+ case DreamProcOpcode.NPushFloatAssign: {
+ float[] floats = GetFloats(0);
+ DMReference[] refs = GetReferences(1);
+ for (int i = 0; i < refs.Length; i++) {
+ text.Append(refs[i].ToString());
+ text.Append('=');
+ text.Append(floats[i]);
+ if (i + 1 < refs.Length)
+ text.Append(' ');
+ }
+
+ break;
+ }
+
+ default:
+ AppendDefaultOperands(text);
+ break;
+ }
+
+ return text.ToString();
+ }
+
+ private void AppendDefaultOperands(StringBuilder text) {
+ for (int i = 0; i < OperandCount; i++) {
+ ProcOperand operand = GetOperand(i);
+ if (operand.Kind == ProcOperandKind.String) {
+ AppendQuoted(text, operand.String!);
+ } else {
+ text.Append(operand.ToString());
+ }
+
+ text.Append(' ');
+ }
+ }
+
+ private void AppendOffset(StringBuilder text, int offset) {
+ text.AppendFormat("0x{0:x}", offset);
+ }
+
+ private void AppendQuoted(StringBuilder text, string value) {
+ text.Append('"');
+ text.Append(value);
+ text.Append('"');
+ }
+
+ private void AppendResource(StringBuilder text, string value) {
+ text.Append('\'');
+ text.Append(value);
+ text.Append('\'');
+ }
+
+ private void AppendMany(StringBuilder text, IReadOnlyList values) {
+ for (int i = 0; i < values.Count; i++) {
+ text.Append(values[i]);
+ if (i + 1 < values.Count)
+ text.Append(' ');
+ }
+ }
+
+ private void AppendManyQuoted(StringBuilder text, IReadOnlyList values) {
+ for (int i = 0; i < values.Count; i++) {
+ AppendQuoted(text, values[i]);
+ if (i + 1 < values.Count)
+ text.Append(' ');
+ }
+ }
+
+ private void AppendManyResources(StringBuilder text, IReadOnlyList values) {
+ for (int i = 0; i < values.Count; i++) {
+ AppendResource(text, values[i]);
+ if (i + 1 < values.Count)
+ text.Append(' ');
+ }
+ }
+}
diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs
index d1564ccda7..a4181dde76 100644
--- a/DMCompiler/DM/Builders/DMProcBuilder.cs
+++ b/DMCompiler/DM/Builders/DMProcBuilder.cs
@@ -153,8 +153,10 @@ private void ProcessStatementSpawn(DMASTProcStatementSpawn statementSpawn) {
ProcessBlockInner(statementSpawn.Body);
//Prevent the new thread from executing outside its own code
- proc.PushNull();
- proc.Return();
+ if (!proc.LastInstructionTransfersControl()) {
+ proc.PushNull();
+ proc.Return();
+ }
}
proc.EndScope();
@@ -242,7 +244,8 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) {
proc.StartScope();
ProcessBlockInner(statement.Body);
proc.EndScope();
- proc.Jump(endLabel);
+ if (!proc.LastInstructionTransfersControl())
+ proc.Jump(endLabel);
proc.AddLabel(elseLabel);
proc.StartScope();
@@ -758,7 +761,9 @@ Constant CoerceBound(Constant bound, bool upperRange) {
proc.EndScope();
}
- proc.Jump(endLabel);
+ // don't Jump after a Return (or similar)
+ if (!proc.LastInstructionTransfersControl())
+ proc.Jump(endLabel);
foreach ((string CaseLabel, DMASTProcBlockInner CaseBody) valueCase in valueCases) {
proc.AddLabel(valueCase.CaseLabel);
@@ -767,7 +772,10 @@ Constant CoerceBound(Constant bound, bool upperRange) {
ProcessBlockInner(valueCase.CaseBody);
}
proc.EndScope();
- proc.Jump(endLabel);
+
+ // don't Jump after a Return (or similar)
+ if (!proc.LastInstructionTransfersControl())
+ proc.Jump(endLabel);
}
proc.AddLabel(endLabel);
diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs
index 13fc3e80f9..a64796f00e 100644
--- a/DMCompiler/DM/DMProc.cs
+++ b/DMCompiler/DM/DMProc.cs
@@ -207,7 +207,9 @@ public void ValidateReturnType(DMExpression expr) {
public ProcDefinitionJson GetJsonRepresentation() {
var serializer = new AnnotatedBytecodeSerializer(_compiler);
- _compiler.BytecodeOptimizer.Optimize(AnnotatedBytecode.GetAnnotatedBytecode());
+ List annotatedBytecode = AnnotatedBytecode.GetAnnotatedBytecode();
+ _compiler.BytecodeOptimizer.Optimize(annotatedBytecode);
+ int maxStackSize = AnnotatedBytecode.RecalculateMaxStackSize();
List? arguments = null;
if (_parameters.Count > 0) {
@@ -235,9 +237,9 @@ public ProcDefinitionJson GetJsonRepresentation() {
OwningTypeId = _dmObject.Id,
Name = Name,
Attributes = Attributes,
+ MaxStackSize = maxStackSize,
+ Bytecode = serializer.Serialize(annotatedBytecode),
MaxVariableId = _localVariableHighestId,
- MaxStackSize = AnnotatedBytecode.GetMaxStackSize(),
- Bytecode = serializer.Serialize(AnnotatedBytecode.GetAnnotatedBytecode()),
Arguments = arguments,
SourceInfo = serializer.SourceInfo,
Locals = (_localVariableNames.Count > 0) ? serializer.GetLocalVariablesJson() : null,
@@ -856,6 +858,57 @@ public void Jump(string label) {
WriteLabel(label);
}
+ public bool LastInstructionTransfersControl() {
+ List bytecode = AnnotatedBytecode.GetAnnotatedBytecode();
+ HashSet? referencedLabels = null;
+ // Man sometimes it'd be nice if we had a list of just instructions and didn't need loops like this just to grab the last actual instruction
+ for (int i = bytecode.Count - 1; i >= 0; i--) {
+ switch (bytecode[i]) {
+ case AnnotatedBytecodeVariable:
+ continue;
+ case AnnotatedBytecodeLabel label:
+ referencedLabels ??= GetReferencedLabels(bytecode);
+ if (referencedLabels is not null && referencedLabels.Contains(label.LabelName))
+ return false;
+
+ continue;
+ case AnnotatedBytecodeInstruction instruction:
+ return InstructionTransfersControl(instruction.Opcode);
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private HashSet? GetReferencedLabels(List bytecode) {
+ HashSet? referencedLabels = null;
+
+ foreach (IAnnotatedBytecode item in bytecode) {
+ if (item is not AnnotatedBytecodeInstruction instruction)
+ continue;
+
+ foreach (IAnnotatedBytecode arg in instruction.GetArgs()) {
+ if (arg is AnnotatedBytecodeLabel label) {
+ referencedLabels ??= new HashSet(1);
+ referencedLabels.Add(label.LabelName);
+ }
+ }
+ }
+
+ return referencedLabels;
+ }
+
+ // TODO: Once we have a CFG we'll likely be storing this info in opcode metadata and this method's hardcoded list can be removed
+ private bool InstructionTransfersControl(DreamProcOpcode opcode) {
+ return opcode is DreamProcOpcode.Jump or
+ DreamProcOpcode.Return or
+ DreamProcOpcode.ReturnReferenceValue or
+ DreamProcOpcode.ReturnFloat or
+ DreamProcOpcode.Throw;
+ }
+
public void JumpIfFalse(string label) {
WriteOpcode(DreamProcOpcode.JumpIfFalse);
WriteLabel(label);
@@ -909,9 +962,8 @@ public void Call(DMReference reference, DMCallArgumentsType argumentsType, int a
WriteStackDelta(argumentStackSize);
}
- public void CallStatement(DMCallArgumentsType argumentsType, int argumentStackSize) {
- //Shrinks the stack by argumentStackSize. Could also shrink it by argumentStackSize+1, but assume not.
- ResizeStack(-argumentStackSize);
+ public void CallStatement(DMCallArgumentsType argumentsType, int argumentStackSize, bool hasProcName) {
+ ResizeStack(-(argumentStackSize + (hasProcName ? 1 : 0)));
WriteOpcode(DreamProcOpcode.CallStatement);
WriteArgumentType(argumentsType);
WriteStackDelta(argumentStackSize);
@@ -945,7 +997,7 @@ public void AssignInto(DMReference reference) {
}
public void CreateObject(DMCallArgumentsType argumentsType, int argumentStackSize) {
- ResizeStack(-argumentStackSize); // Pops type and arguments, pushes new object
+ ResizeStack(-(argumentStackSize + 1)); // Pops overrides, type, and arguments, pushes new object
WriteOpcode(DreamProcOpcode.CreateObject);
WriteArgumentType(argumentsType);
WriteStackDelta(argumentStackSize);
@@ -1275,7 +1327,7 @@ public void Animate(DMCallArgumentsType argumentsType, int argumentStackSize) {
}
public void PickWeighted(int count) {
- ResizeStack(-(count - 1));
+ ResizeStack(-(count * 2 - 1));
WriteOpcode(DreamProcOpcode.PickWeighted);
WritePickCount(count);
}
diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs
index 5737cb822b..76675400df 100644
--- a/DMCompiler/DM/Expressions/Builtins.cs
+++ b/DMCompiler/DM/Expressions/Builtins.cs
@@ -674,7 +674,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
_b?.EmitPushValue(ctx);
_a.EmitPushValue(ctx);
- ctx.Proc.CallStatement(argumentInfo.Type, argumentInfo.StackSize);
+ ctx.Proc.CallStatement(argumentInfo.Type, argumentInfo.StackSize, _b is not null);
}
}
diff --git a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
index 3e9a29b4e5..afda9cc770 100644
--- a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
+++ b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
@@ -18,6 +18,7 @@ private readonly List
private Location _location;
private int _maxStackSize;
private bool _negativeStackSizeError;
+ private int _pendingInstructionStackDelta;
private int _requiredArgIdx;
private OpcodeMetadata? _currentMetadata;
private Dictionary _labels = new();
@@ -49,9 +50,10 @@ public void WriteOpcode(DreamProcOpcode opcode, Location location) {
// Goal here is to maintain correspondence between the raw bytecode and the annotated bytecode such that
// the annotated bytecode can be used to generate the raw bytecode again.
- _annotatedBytecode.Add(new AnnotatedBytecodeInstruction(opcode, metadata.StackDelta, location));
+ _annotatedBytecode.Add(new AnnotatedBytecodeInstruction(opcode, metadata.StackDelta + _pendingInstructionStackDelta, location));
+ _pendingInstructionStackDelta = 0;
- ResizeStack(metadata.StackDelta);
+ ResizeStackOnly(metadata.StackDelta);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -215,6 +217,19 @@ public void ResolveCodeLabelReferences(Stack pendingL
///
/// The net change in stack size caused by an operation
public void ResizeStack(int sizeDelta) {
+ _pendingInstructionStackDelta += sizeDelta;
+ ResizeStackOnly(sizeDelta);
+ }
+
+ private void ResizeCurrentInstructionStack(int sizeDelta) {
+ if (_annotatedBytecode.Count > 0 && _annotatedBytecode[^1] is AnnotatedBytecodeInstruction instruction) {
+ instruction.StackSizeDelta += sizeDelta;
+ }
+
+ ResizeStackOnly(sizeDelta);
+ }
+
+ private void ResizeStackOnly(int sizeDelta) {
_currentStackSize += sizeDelta;
_maxStackSize = Math.Max(_currentStackSize, _maxStackSize);
if (_currentStackSize < 0 && !_negativeStackSizeError) {
@@ -230,6 +245,27 @@ public int GetMaxStackSize() {
return _maxStackSize;
}
+ ///
+ /// Recomputes the maximum possible stack size from the current annotated bytecode.
+ /// Used after optimization because peephole rewrites can change the max stack size.
+ ///
+ public int RecalculateMaxStackSize() {
+ _currentStackSize = 0;
+ _maxStackSize = 0;
+ _negativeStackSizeError = false;
+ _pendingInstructionStackDelta = 0;
+
+ foreach (IAnnotatedBytecode bytecode in _annotatedBytecode) {
+ if (bytecode is not AnnotatedBytecodeInstruction instruction)
+ continue;
+
+ _location = instruction.GetLocation();
+ ResizeStackOnly(instruction.StackSizeDelta);
+ }
+
+ return _maxStackSize;
+ }
+
public void WriteResource(string value, Location location) {
_location = location;
ValidateArgument(location, OpcodeArgType.Resource);
@@ -294,7 +330,7 @@ public void WriteReference(DMReference reference, Location location, bool affect
int fieldId = compiler.DMObjectTree.AddString(reference.Name);
_annotatedBytecode[^1]
.AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, fieldId, location));
- ResizeStack(affectStack ? -1 : 0);
+ ResizeCurrentInstructionStack(affectStack ? -1 : 0);
break;
case DMReference.Type.SrcProc:
@@ -306,7 +342,7 @@ public void WriteReference(DMReference reference, Location location, bool affect
case DMReference.Type.ListIndex:
_annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, location));
- ResizeStack(affectStack ? -2 : 0);
+ ResizeCurrentInstructionStack(affectStack ? -2 : 0);
break;
case DMReference.Type.SuperProc:
diff --git a/DMCompiler/Optimizer/AnnotatedBytecode.cs b/DMCompiler/Optimizer/AnnotatedBytecode.cs
index 3e31ed2f98..3dde357bfc 100644
--- a/DMCompiler/Optimizer/AnnotatedBytecode.cs
+++ b/DMCompiler/Optimizer/AnnotatedBytecode.cs
@@ -37,7 +37,7 @@ public AnnotatedBytecodeInstruction(AnnotatedBytecodeInstruction instruction, Li
public AnnotatedBytecodeInstruction(DreamProcOpcode op, List args) {
Opcode = op;
OpcodeMetadata metadata = OpcodeMetadataCache.GetMetadata(op);
- StackSizeDelta = metadata.StackDelta;
+ StackSizeDelta = metadata.StackDelta + GetArgsStackSizeDelta(args);
Location = new Location("Internal", null, null);
ValidateArgs(metadata, args);
_args = args;
@@ -45,12 +45,33 @@ public AnnotatedBytecodeInstruction(DreamProcOpcode op, List
public AnnotatedBytecodeInstruction(DreamProcOpcode opcode, int stackSizeDelta, List args) {
Opcode = opcode;
- StackSizeDelta = stackSizeDelta;
+ StackSizeDelta = stackSizeDelta + GetArgsStackSizeDelta(args);
Location = new Location("Internal", null, null);
ValidateArgs(OpcodeMetadataCache.GetMetadata(opcode), args);
_args = args;
}
+ private int GetArgsStackSizeDelta(List args) {
+ int delta = 0;
+
+ foreach (IAnnotatedBytecode arg in args) {
+ if (arg is not AnnotatedBytecodeReference reference)
+ continue;
+
+ delta += GetReferenceStackSizeDelta(reference.RefType);
+ }
+
+ return delta;
+ }
+
+ private int GetReferenceStackSizeDelta(DMReference.Type refType) {
+ return refType switch {
+ DMReference.Type.Field => -1,
+ DMReference.Type.ListIndex => -2,
+ _ => 0
+ };
+ }
+
private void ValidateArgs(OpcodeMetadata metadata, List args) {
if (metadata.VariableArgs) {
if (args[0] is not AnnotatedBytecodeInteger) {
diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs
index bef207a634..2bcd0b70ac 100644
--- a/DMCompiler/Optimizer/BytecodeOptimizer.cs
+++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
+using DMCompiler.Bytecode;
namespace DMCompiler.Optimizer;
@@ -26,6 +27,8 @@ internal void Optimize(List input) {
RemoveUnreferencedLabels(input);
RemoveImmediateJumps(input);
RemoveUnreferencedLabels(input);
+ RemoveJumpsAfterTerminalInstructions(input);
+ RemoveUnreferencedLabels(input);
}
private void RemoveUnreferencedLabels(List input) {
@@ -98,6 +101,14 @@ private void JoinAndForwardLabels(List input) {
}
}
+ RemoveJumpsAfterTerminalInstructions(input, labelAliases);
+ RewriteLabelAliases(input, labelAliases);
+ }
+
+ private void RewriteLabelAliases(List input, Dictionary? labelAliases) {
+ if (labelAliases is null || labelAliases.Count == 0)
+ return;
+
for (int i = 0; i < input.Count; i++) {
if (input[i] is AnnotatedBytecodeInstruction instruction) {
if (TryGetLabelName(instruction, out string? labelName)) {
@@ -105,7 +116,7 @@ private void JoinAndForwardLabels(List input) {
List args = instruction.GetArgs();
for (int j = 0; j < args.Count; j++) {
if (args[j] is AnnotatedBytecodeLabel argLabel) {
- args[j] = new AnnotatedBytecodeLabel(labelAliases[argLabel.LabelName],
+ args[j] = new AnnotatedBytecodeLabel(ResolveLabelAlias(labelAliases, argLabel.LabelName),
argLabel.Location);
}
}
@@ -117,6 +128,76 @@ private void JoinAndForwardLabels(List input) {
}
}
+ private void RemoveJumpsAfterTerminalInstructions(List input) {
+ var labelAliases = RemoveJumpsAfterTerminalInstructions(input, null);
+ RewriteLabelAliases(input, labelAliases);
+ }
+
+ private Dictionary? RemoveJumpsAfterTerminalInstructions(List input, Dictionary? labelAliases) {
+ for (int i = 0; i < input.Count; i++) {
+ if (input[i] is not AnnotatedBytecodeInstruction instruction || !IsTerminalInstruction(instruction.Opcode))
+ continue;
+
+ var labels = new List();
+ for (int j = i + 1; j < input.Count; j++) {
+ switch (input[j]) {
+ case AnnotatedBytecodeVariable:
+ continue;
+ case AnnotatedBytecodeLabel label:
+ labels.Add(label.LabelName);
+ continue;
+ case AnnotatedBytecodeInstruction when labels.Count == 0:
+ input.RemoveAt(j);
+ j -= 1;
+ continue;
+ case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump } when labels.Count > 0 &&
+ instruction.Opcode == DreamProcOpcode.Jump:
+ break;
+ case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump } jump when labels.Count > 0: {
+ string targetLabel = jump.GetArg(0).LabelName;
+ if (labels.Contains(targetLabel))
+ break;
+
+ foreach (string labelName in labels) {
+ labelAliases ??= new Dictionary(1);
+ labelAliases[labelName] = targetLabel;
+ }
+
+ input.RemoveAt(j);
+ i -= 1;
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return labelAliases;
+ }
+
+ // TODO: Once we have a CFG we'll likely be storing this info in opcode metadata and this hardcoded list can be removed
+ private bool IsTerminalInstruction(DreamProcOpcode opcode) {
+ return opcode is DreamProcOpcode.Jump or
+ DreamProcOpcode.Return or
+ DreamProcOpcode.ReturnReferenceValue or
+ DreamProcOpcode.ReturnFloat or
+ DreamProcOpcode.Throw;
+ }
+
+ private string ResolveLabelAlias(Dictionary labelAliases, string labelName) {
+ HashSet? visited = null;
+ while (labelAliases.TryGetValue(labelName, out string? alias) && alias != labelName) {
+ visited ??= new();
+ if (!visited.Add(labelName))
+ break;
+
+ labelName = alias;
+ }
+
+ return labelName;
+ }
+
private bool TryGetLabelName(AnnotatedBytecodeInstruction instruction, [NotNullWhen(true)] out string? labelName) {
foreach (var arg in instruction.GetArgs()) {
if (arg is not AnnotatedBytecodeLabel label)
diff --git a/DMCompiler/Optimizer/CompactorOptimizations.cs b/DMCompiler/Optimizer/CompactorOptimizations.cs
index 19f4aa3652..ed361fe86b 100644
--- a/DMCompiler/Optimizer/CompactorOptimizations.cs
+++ b/DMCompiler/Optimizer/CompactorOptimizations.cs
@@ -46,7 +46,6 @@ public void Apply(DMCompiler compiler, List input, int index
// Otherwise, replace with NPushFloatAssign
- int stackDelta = 0;
List args = new List(2 * count + 1) { new AnnotatedBytecodeInteger(count, input[index].GetLocation()) };
for (int i = 0; i < count; i++) {
@@ -54,11 +53,10 @@ public void Apply(DMCompiler compiler, List input, int index
AnnotatedBytecodeInstruction assignInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2 + 1]);
args.Add(floatInstruction.GetArg(0));
args.Add(assignInstruction.GetArg(0));
- stackDelta += 2;
}
input.RemoveRange(index, count * 2);
- input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.NPushFloatAssign, stackDelta, args));
+ input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.NPushFloatAssign, 0, args));
}
}
diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs
index 5e5ec5b4f8..62cf8d9473 100644
--- a/DMCompiler/Optimizer/PeepholeOptimizations.cs
+++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs
@@ -58,7 +58,7 @@ public void Apply(DMCompiler compiler, List input, int index
// PushNull
// AssignNoPush [ref]
-// -> AssignNull [ref]
+// -> NullRef [ref]
internal sealed class AssignNull : IOptimization {
public OptPass OptimizationPass => OptPass.PeepholeOptimization;
@@ -139,7 +139,7 @@ public void Apply(DMCompiler compiler, List input, int index
AnnotatedBytecodeString strIndex = secondInstruction.GetArg(0);
input.RemoveRange(index, 3);
- input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.IndexRefWithString, -1,
+ input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.IndexRefWithString,
[pushVal, strIndex]));
}
}
@@ -232,6 +232,60 @@ public void Apply(DMCompiler compiler, List input, int index
}
}
+// ReturnFloat
+// Jump [label]
+// -> ReturnFloat
+internal sealed class RemoveJumpAfterReturnFloat : IOptimization {
+ public OptPass OptimizationPass => OptPass.ListCompactor;
+
+ public ReadOnlySpan GetOpcodes() {
+ return [
+ DreamProcOpcode.ReturnFloat,
+ DreamProcOpcode.Jump
+ ];
+ }
+
+ public void Apply(DMCompiler compiler, List input, int index) {
+ input.RemoveRange(index + 1, 1);
+ }
+}
+
+// ReturnReferenceValue
+// Jump [label]
+// -> ReturnReferenceValue
+internal sealed class RemoveJumpAfterReturnRefValue : IOptimization {
+ public OptPass OptimizationPass => OptPass.ListCompactor;
+
+ public ReadOnlySpan GetOpcodes() {
+ return [
+ DreamProcOpcode.ReturnReferenceValue,
+ DreamProcOpcode.Jump
+ ];
+ }
+
+ public void Apply(DMCompiler compiler, List input, int index) {
+ input.RemoveRange(index + 1, 1);
+ }
+}
+
+// Throw
+// Jump [label]
+// -> Throw
+internal sealed class RemoveJumpAfterThrow : IOptimization {
+ public OptPass OptimizationPass => OptPass.PeepholeOptimization;
+
+ public ReadOnlySpan GetOpcodes() {
+ return [
+ DreamProcOpcode.Throw,
+ DreamProcOpcode.Jump
+ ];
+ }
+
+ public void Apply(DMCompiler compiler, List input, int index) {
+ input.RemoveRange(index + 1, 1);
+ }
+}
+
// PushFloat [float]
// SwitchCase [label]
// -> SwitchOnFloat [float] [label]
diff --git a/DMCompiler/Optimizer/PeepholeOptimizer.cs b/DMCompiler/Optimizer/PeepholeOptimizer.cs
index de4d4791ba..82f17bfe34 100644
--- a/DMCompiler/Optimizer/PeepholeOptimizer.cs
+++ b/DMCompiler/Optimizer/PeepholeOptimizer.cs
@@ -109,37 +109,64 @@ private void GetOptimizations() {
}
public void RunPeephole(List input) {
- foreach (var optPass in _passes) {
- RunPass((byte)optPass, input);
- }
+ bool changed;
+
+ do {
+ changed = false;
+
+ foreach (var optPass in _passes) {
+ changed |= RunPass((byte)optPass, input);
+ }
+ } while (changed);
}
- private void RunPass(byte pass, List input) {
+ private bool RunPass(byte pass, List input) {
OptimizationTreeEntry? currentOpt = null;
+ int currentOptStartIndex = -1;
int optSize = 0;
+ bool changed = false;
int AttemptCurrentOpt(int i) {
- if (currentOpt == null)
+ if (currentOpt == null || currentOptStartIndex == -1)
return 0;
int offset;
- if (currentOpt.Optimization?.CheckPreconditions(input, i - optSize) is true) {
- currentOpt.Optimization.Apply(_compiler, input, i - optSize);
+ int startIndex = currentOptStartIndex;
+ var skippedVariables = RemoveTransparentVariables(input, startIndex);
+ if (currentOpt.Optimization?.CheckPreconditions(input, startIndex) is true) {
+ IAnnotatedBytecode[] inputBeforeApply = input.ToArray();
+ currentOpt.Optimization.Apply(_compiler, input, startIndex);
+ ReinsertSkippedVariables(input, skippedVariables, inputBeforeApply, startIndex);
+
+ changed = true;
offset = (optSize + 2); // Run over the new opcodes for potential further optimization
} else {
+ RestoreSkippedVariables(input, skippedVariables);
+
// This chain of opcodes did not lead to a valid optimization.
// Start again from the opcode after the first.
offset = optSize;
}
currentOpt = null;
+ currentOptStartIndex = -1;
return offset;
}
- for (int i = 0; i < input.Count; i++) {
+ for (int i = 0; i <= input.Count; i++) {
+ if (i == input.Count) {
+ i -= AttemptCurrentOpt(i);
+ i = Math.Max(i, -1); // i++ brings -1 back to 0
+ continue;
+ }
+
var bytecode = input[i];
if (bytecode is not AnnotatedBytecodeInstruction instruction) {
+ if (bytecode is AnnotatedBytecodeVariable && currentOpt is not null) {
+ continue;
+ }
+
i -= AttemptCurrentOpt(i);
i = Math.Max(i, -1); // i++ brings -1 back to 0
continue;
@@ -150,6 +177,7 @@ int AttemptCurrentOpt(int i) {
if (currentOpt == null) {
optSize = 1;
_optimizationTrees[pass].TryGetValue(opcode, out currentOpt);
+ currentOptStartIndex = currentOpt is null ? -1 : i;
continue;
}
@@ -163,6 +191,73 @@ int AttemptCurrentOpt(int i) {
i = Math.Max(i, -1); // i++ brings -1 back to 0
}
- AttemptCurrentOpt(input.Count);
+ return changed;
+ }
+
+ private sealed record RemovedVariable(int Index, int InstructionOffset, IAnnotatedBytecode Variable);
+
+ private static List RemoveTransparentVariables(List input, int startIndex) {
+ var variables = new List();
+ int instructionOffset = 0;
+
+ for (int i = startIndex; i < input.Count; i++) {
+ switch (input[i]) {
+ case AnnotatedBytecodeInstruction:
+ instructionOffset++;
+ break;
+ case AnnotatedBytecodeVariable variable:
+ variables.Add(new RemovedVariable(i, instructionOffset, variable));
+ break;
+ default:
+ return RemoveVariables(input, variables);
+ }
+ }
+
+ return RemoveVariables(input, variables);
+ }
+
+ private static List RemoveVariables(List input, List variables) {
+ for (int i = variables.Count - 1; i >= 0; i--) {
+ input.RemoveAt(variables[i].Index);
+ }
+
+ return variables;
+ }
+
+ private static void ReinsertSkippedVariables(List input, List variables, IAnnotatedBytecode[] inputBeforeApply, int startIndex) {
+ if (variables.Count == 0)
+ return;
+
+ // Find how much of the normalized instruction run Apply() rewrote.
+ // Some compactors consume more opcodes than the matched tree path.
+ int beforeSuffixStart = inputBeforeApply.Length;
+ int afterSuffixStart = input.Count;
+
+ while (beforeSuffixStart > startIndex &&
+ afterSuffixStart > startIndex &&
+ ReferenceEquals(inputBeforeApply[beforeSuffixStart - 1], input[afterSuffixStart - 1])) {
+ beforeSuffixStart--;
+ afterSuffixStart--;
+ }
+
+ int removedInstructionCount = beforeSuffixStart - startIndex;
+ int insertedItemCount = afterSuffixStart - startIndex;
+
+ for (int i = 0; i < variables.Count; i++) {
+ var variable = variables[i];
+ int insertIndex = startIndex + insertedItemCount;
+ if (variable.InstructionOffset > removedInstructionCount) {
+ insertIndex += variable.InstructionOffset - removedInstructionCount;
+ }
+
+ insertIndex = Math.Min(insertIndex + i, input.Count);
+ input.Insert(insertIndex, variable.Variable);
+ }
+ }
+
+ private static void RestoreSkippedVariables(List input, List variables) {
+ foreach (var variable in variables) {
+ input.Insert(variable.Index, variable.Variable);
+ }
}
}
diff --git a/DMDisassembler/DMProc.cs b/DMDisassembler/DMProc.cs
index c7d69b0b34..10a5bbfd76 100644
--- a/DMDisassembler/DMProc.cs
+++ b/DMDisassembler/DMProc.cs
@@ -1,10 +1,9 @@
-using OpenDreamRuntime.Procs;
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
+using DMCompiler.Bytecode;
using DMCompiler.DM;
using DMCompiler.Json;
-using JetBrains.Annotations;
namespace DMDisassembler;
@@ -26,6 +25,7 @@ public string Decompile() {
StringBuilder result = new StringBuilder();
foreach (DecompiledOpcode decompiledOpcode in decompiled) {
if (labeledPositions.Contains(decompiledOpcode.Position)) {
+ // Calling ToString() on Position will mess up the format conversion to hexadecimal
result.AppendFormat("0x{0:x}", decompiledOpcode.Position);
result.AppendLine();
}
@@ -48,13 +48,14 @@ public string Decompile() {
}
public List GetDecompiledOpcodes(out HashSet labeledPositions) {
- List decompiled = new();
+ List decompiled = new(Bytecode.Length);
labeledPositions = new();
try {
- foreach (var (position, instruction) in new ProcDecoder(Program.CompiledJson.Strings, Bytecode).Disassemble()) {
- decompiled.Add(new DecompiledOpcode(position, ProcDecoder.Format(instruction, type => Program.CompiledJson.Types[type].Path)));
- if (ProcDecoder.GetJumpDestination(instruction) is int jumpPosition) {
+ var decoder = new BytecodeProcDecoder(Program.CompiledJson.Strings, Bytecode);
+ foreach (var (position, instruction) in decoder.Disassemble()) {
+ decompiled.Add(new DecompiledOpcode(position, instruction.Format(type => Program.CompiledJson.Types[type].Path)));
+ if (decoder.GetBranchTarget(instruction) is { } jumpPosition) {
labeledPositions.Add(jumpPosition);
}
}
diff --git a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs
index 1d1a5c79f9..41ce79c989 100644
--- a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs
+++ b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs
@@ -807,7 +807,7 @@ private void HandleRequestDisassemble(DebugAdapterClient client, RequestDisassem
List output = new();
DisassembledInstruction? previousInstruction = null;
int previousOffset = 0;
- foreach (var (offset, instruction) in new ProcDecoder(_objectTree.Strings, proc.Bytecode).Disassemble()) {
+ foreach (var (offset, instruction) in new BytecodeProcDecoder(_objectTree.Strings, proc.Bytecode).Disassemble()) {
/*if (previousInstruction != null) {
previousInstruction.InstructionBytes = BitConverter.ToString(proc.Bytecode, previousOffset, offset - previousOffset).Replace("-", " ").ToLowerInvariant();
}*/
@@ -815,7 +815,7 @@ private void HandleRequestDisassemble(DebugAdapterClient client, RequestDisassem
previousOffset = offset;
previousInstruction = new DisassembledInstruction {
Address = EncodeInstructionPointer(proc, offset),
- Instruction = ProcDecoder.Format(instruction, type => _objectTree.Types[type].Path.ToString()),
+ Instruction = instruction.Format(type => _objectTree.Types[type].Path.ToString()),
};
var sourceInfo = proc.GetSourceAtOffset(previousOffset);
diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs
deleted file mode 100644
index b01a50c2a5..0000000000
--- a/OpenDreamRuntime/Procs/ProcDecoder.cs
+++ /dev/null
@@ -1,448 +0,0 @@
-using System.Runtime.CompilerServices;
-using System.Text;
-using DMCompiler.Bytecode;
-using OpenDreamShared.Dream;
-
-namespace OpenDreamRuntime.Procs;
-
-public struct ProcDecoder(IReadOnlyList strings, byte[] bytecode) {
- public readonly IReadOnlyList Strings = strings;
- public readonly byte[] Bytecode = bytecode;
- public int Offset = 0;
-
- public bool Remaining => Offset < Bytecode.Length;
-
- public int ReadByte() {
- return Bytecode[Offset++];
- }
-
- public DreamProcOpcode ReadOpcode() {
- return (DreamProcOpcode) ReadByte();
- }
-
- public int ReadInt() {
- int value = BitConverter.ToInt32(Bytecode, Offset);
- Offset += 4;
- return value;
- }
-
- public DreamValueType ReadValueType() {
- return (DreamValueType) ReadInt();
- }
-
- public float ReadFloat() {
- float value = BitConverter.ToSingle(Bytecode, Offset);
- Offset += 4;
- return value;
- }
-
- public string ReadString() {
- int stringId = ReadInt();
- return Strings[stringId];
- }
-
- public DMReference ReadReference() {
- DMReference.Type refType = (DMReference.Type)ReadByte();
-
- switch (refType) {
- case DMReference.Type.Argument: return DMReference.CreateArgument(ReadByte());
- case DMReference.Type.Local: return DMReference.CreateLocal(ReadByte());
- case DMReference.Type.Global: return DMReference.CreateGlobal(ReadInt());
- case DMReference.Type.GlobalProc: return DMReference.CreateGlobalProc(ReadInt());
- case DMReference.Type.Field: return DMReference.CreateField(ReadString());
- case DMReference.Type.SrcField: return DMReference.CreateSrcField(ReadString());
- case DMReference.Type.SrcProc: return DMReference.CreateSrcProc(ReadString());
- case DMReference.Type.Src: return DMReference.Src;
- case DMReference.Type.Self: return DMReference.Self;
- case DMReference.Type.Usr: return DMReference.Usr;
- case DMReference.Type.Args: return DMReference.Args;
- case DMReference.Type.World: return DMReference.World;
- case DMReference.Type.SuperProc: return DMReference.SuperProc;
- case DMReference.Type.ListIndex: return DMReference.ListIndex;
- case DMReference.Type.Caller: return DMReference.Caller;
- case DMReference.Type.Callee: return DMReference.Callee;
- default: throw new Exception($"Invalid reference type {refType}");
- }
- }
-
- public ITuple DecodeInstruction() {
- var opcode = ReadOpcode();
- switch (opcode) {
- case DreamProcOpcode.FormatString:
- return (opcode, ReadString(), ReadInt());
-
- case DreamProcOpcode.PushStringFloat:
- return (opcode, ReadString(), ReadFloat());
- case DreamProcOpcode.PushFloatAssign:
- return (opcode, ReadFloat(), ReadReference());
- case DreamProcOpcode.NPushFloatAssign: {
- var count = ReadInt();
- var floats = new float[count];
- var refs = new DMReference[count];
-
- for (int i = 0; i < count; i++) {
- floats[i] = ReadFloat();
- refs[i] = ReadReference();
- }
-
- return (opcode, floats, refs);
- }
- case DreamProcOpcode.PushString:
- case DreamProcOpcode.PushResource:
- case DreamProcOpcode.DereferenceField:
- return (opcode, ReadString());
-
- case DreamProcOpcode.DereferenceCall:
- return (opcode, ReadString(), (DMCallArgumentsType)ReadByte(), ReadInt());
-
- case DreamProcOpcode.Prompt:
- return (opcode, ReadValueType());
-
- case DreamProcOpcode.PushFloat:
- case DreamProcOpcode.ReturnFloat:
- return (opcode, ReadFloat());
-
- case DreamProcOpcode.SwitchOnFloat:
- return (opcode, ReadFloat(), ReadInt());
-
- case DreamProcOpcode.SwitchOnString:
- return (opcode, ReadString(), ReadInt());
-
- case DreamProcOpcode.Assign:
- case DreamProcOpcode.Append:
- case DreamProcOpcode.Remove:
- case DreamProcOpcode.Combine:
- case DreamProcOpcode.Increment:
- case DreamProcOpcode.Decrement:
- case DreamProcOpcode.Mask:
- case DreamProcOpcode.MultiplyReference:
- case DreamProcOpcode.DivideReference:
- case DreamProcOpcode.BitXorReference:
- case DreamProcOpcode.ModulusReference:
- case DreamProcOpcode.BitShiftLeftReference:
- case DreamProcOpcode.BitShiftRightReference:
- case DreamProcOpcode.OutputReference:
- case DreamProcOpcode.PushReferenceValue:
- case DreamProcOpcode.PopReference:
- case DreamProcOpcode.AppendNoPush:
- case DreamProcOpcode.NullRef:
- case DreamProcOpcode.AssignNoPush:
- case DreamProcOpcode.ReturnReferenceValue:
- return (opcode, ReadReference());
-
- case DreamProcOpcode.Input:
- return (opcode, ReadReference(), ReadReference());
-
- case DreamProcOpcode.PushRefAndDereferenceField:
- case DreamProcOpcode.IndexRefWithString:
- return (opcode, ReadReference(), ReadString());
-
- case DreamProcOpcode.CallStatement:
- case DreamProcOpcode.CreateObject:
- case DreamProcOpcode.Gradient:
- case DreamProcOpcode.Rgb:
- case DreamProcOpcode.Animate:
- return (opcode, (DMCallArgumentsType)ReadByte(), ReadInt());
-
- case DreamProcOpcode.Call:
- return (opcode, ReadReference(), (DMCallArgumentsType)ReadByte(), ReadInt());
-
- case DreamProcOpcode.CreateList:
- case DreamProcOpcode.CreateAssociativeList:
- case DreamProcOpcode.CreateStrictAssociativeList:
- case DreamProcOpcode.PickWeighted:
- case DreamProcOpcode.PickUnweighted:
- case DreamProcOpcode.Spawn:
- case DreamProcOpcode.BooleanOr:
- case DreamProcOpcode.BooleanAnd:
- case DreamProcOpcode.SwitchCase:
- case DreamProcOpcode.SwitchCaseRange:
- case DreamProcOpcode.Jump:
- case DreamProcOpcode.JumpIfFalse:
- case DreamProcOpcode.PushType:
- case DreamProcOpcode.PushProc:
- case DreamProcOpcode.MassConcatenation:
- case DreamProcOpcode.JumpIfNull:
- case DreamProcOpcode.JumpIfNullNoPop:
- case DreamProcOpcode.TryNoValue:
- case DreamProcOpcode.CreateListEnumerator:
- case DreamProcOpcode.CreateRangeEnumerator:
- case DreamProcOpcode.CreateTypeEnumerator:
- case DreamProcOpcode.DestroyEnumerator:
- case DreamProcOpcode.IsTypeDirect:
- case DreamProcOpcode.CreateMultidimensionalList:
- return (opcode, ReadInt());
-
- case DreamProcOpcode.JumpIfTrueReference:
- case DreamProcOpcode.JumpIfFalseReference:
- case DreamProcOpcode.JumpIfReferenceFalse:
- return (opcode, ReadReference(), ReadInt());
-
- case DreamProcOpcode.Try:
- return (opcode, ReadInt(), ReadReference());
-
- case DreamProcOpcode.Enumerate:
- return (opcode, ReadInt(), ReadReference(), ReadInt());
- case DreamProcOpcode.EnumerateAssoc:
- return (opcode, ReadInt(), ReadReference(), ReadReference(), ReadInt());
-
- case DreamProcOpcode.CreateFilteredListEnumerator:
- case DreamProcOpcode.EnumerateNoAssign:
- return (opcode, ReadInt(), ReadInt());
-
- case DreamProcOpcode.CreateListNRefs:
- case DreamProcOpcode.PushNRefs: {
- var count = ReadInt();
- var values = new DMReference[count];
-
- for (int i = 0; i < count; i++) {
- values[i] = ReadReference();
- }
-
- return (opcode, values);
- }
-
- case DreamProcOpcode.CreateListNStrings:
- case DreamProcOpcode.PushNStrings: {
- var count = ReadInt();
- var values = new string[count];
-
- for (int i = 0; i < count; i++) {
- values[i] = ReadString();
- }
-
- return (opcode, values);
- }
-
- case DreamProcOpcode.CreateListNFloats:
- case DreamProcOpcode.PushNFloats: {
- var count = ReadInt();
- var values = new float[count];
-
- for (int i = 0; i < count; i++) {
- values[i] = ReadFloat();
- }
-
- return (opcode, values);
- }
-
- case DreamProcOpcode.PushNOfStringFloats: {
- var count = ReadInt();
- var strings = new string[count];
- var floats = new float[count];
-
- for (int i = 0; i < count; i++) {
- strings[i] = ReadString();
- floats[i] = ReadFloat();
- }
-
- return (opcode, strings, floats);
- }
-
- case DreamProcOpcode.CreateListNResources:
- case DreamProcOpcode.PushNResources: {
- var count = ReadInt();
- var values = new string[count];
-
- for (int i = 0; i < count; i++) {
- values[i] = ReadString();
- }
-
- return (opcode, values);
- }
-
- default:
- return ValueTuple.Create(opcode);
- }
- }
-
- public IEnumerable<(int Offset, ITuple Instruction)> Disassemble() {
- while (Remaining) {
- yield return (Offset, DecodeInstruction());
- }
- }
-
- public static string Format(ITuple instruction, Func getTypePath) {
- StringBuilder text = new StringBuilder();
- text.Append(instruction[0]);
- text.Append(' ');
- switch (instruction) {
- case (DreamProcOpcode.FormatString, string str, int numReplacements):
- text.Append(numReplacements);
- text.Append(' ');
- text.Append('"');
- text.Append(str);
- text.Append('"');
- break;
-
- case (DreamProcOpcode.PushResource, string str):
- text.Append('\'');
- text.Append(str);
- text.Append('\'');
- break;
-
- case (DreamProcOpcode.Spawn
- or DreamProcOpcode.BooleanOr
- or DreamProcOpcode.BooleanAnd
- or DreamProcOpcode.SwitchCase
- or DreamProcOpcode.SwitchCaseRange
- or DreamProcOpcode.Jump
- or DreamProcOpcode.JumpIfFalse, int jumpPosition):
- text.AppendFormat("0x{0:x}", jumpPosition);
- break;
-
- case (DreamProcOpcode.SwitchOnFloat, float value, int jumpPosition):
- text.Append(value);
- text.AppendFormat(" 0x{0:x}", jumpPosition);
- break;
-
- case (DreamProcOpcode.SwitchOnString, string value, int jumpPosition):
- text.Append('"');
- text.Append(value);
- text.Append("\" ");
- text.AppendFormat(" 0x{0:x}", jumpPosition);
- break;
-
- case (DreamProcOpcode.JumpIfFalseReference
- or DreamProcOpcode.JumpIfTrueReference
- or DreamProcOpcode.JumpIfReferenceFalse, DMReference reference, int jumpPosition):
- text.Append(reference.ToString());
- text.AppendFormat(" 0x{0:x}", jumpPosition);
- break;
-
- case (DreamProcOpcode.PushType
- or DreamProcOpcode.IsTypeDirect, int type):
- text.Append(getTypePath(type));
- break;
-
- case (DreamProcOpcode.CreateFilteredListEnumerator, int enumeratorId, int type):
- text.Append(enumeratorId);
- text.Append(' ');
- text.Append(getTypePath(type));
- break;
-
- case (DreamProcOpcode.CreateListNRefs
- or DreamProcOpcode.PushNRefs, DMReference[] refs): {
- foreach (var reference in refs) {
- text.Append(reference.ToString());
- text.Append(' ');
- }
-
- break;
- }
-
- case (DreamProcOpcode.CreateListNStrings
- or DreamProcOpcode.PushNStrings, string[] strings): {
- foreach (var value in strings) {
- text.Append('"');
- text.Append(value);
- text.Append("\" ");
- }
-
- break;
- }
-
- case (DreamProcOpcode.CreateListNFloats
- or DreamProcOpcode.PushNFloats, float[] floats): {
- foreach (var value in floats) {
- text.Append(value);
- text.Append(' ');
- }
-
- break;
- }
-
- case (DreamProcOpcode.CreateListNResources
- or DreamProcOpcode.PushNResources, string[] resources): {
- foreach (var value in resources) {
- text.Append('\'');
- text.Append(value);
- text.Append("' ");
- }
-
- break;
- }
-
- case (DreamProcOpcode.PushNOfStringFloats, string[] strings, float[] floats): {
- // The length of both arrays are equal
- for (var index = 0; index < strings.Length; index++) {
- text.Append($"\"{strings[index]}\"");
- text.Append(' ');
- text.Append(floats[index]);
- if(index + 1 < strings.Length) // Don't leave a trailing space
- text.Append(' ');
- }
-
- break;
- }
-
- case (DreamProcOpcode.PushFloatAssign, float value, DMReference reference): {
- text.Append(value);
- text.Append(' ');
- text.Append(reference.ToString());
- break;
- }
-
- case (DreamProcOpcode.NPushFloatAssign, float[] floats, DMReference[] refs): {
- // The length of both arrays are equal
- for (var index = 0; index < refs.Length; index++) {
- text.Append(refs[index]);
- text.Append('=');
- text.Append(floats[index]);
-
- if(index + 1 < refs.Length) // Don't leave a trailing space
- text.Append(' ');
- }
-
- break;
- }
-
- default:
- for (int i = 1; i < instruction.Length; ++i) {
- var arg = instruction[i];
-
- if (arg is string) {
- text.Append('"');
- text.Append(arg);
- text.Append("\" ");
- } else {
- text.Append(instruction[i]);
- text.Append(' ');
- }
- }
-
- break;
- }
-
- return text.ToString();
- }
-
- public static int? GetJumpDestination(ITuple instruction) {
- switch (instruction) {
- case (DreamProcOpcode.Spawn
- or DreamProcOpcode.BooleanOr
- or DreamProcOpcode.BooleanAnd
- or DreamProcOpcode.SwitchCase
- or DreamProcOpcode.SwitchCaseRange
- or DreamProcOpcode.Jump
- or DreamProcOpcode.JumpIfFalse
- or DreamProcOpcode.TryNoValue, int jumpPosition):
- return jumpPosition;
- case (DreamProcOpcode.JumpIfFalseReference
- or DreamProcOpcode.JumpIfTrueReference
- or DreamProcOpcode.JumpIfReferenceFalse, DMReference, int jumpPosition):
- return jumpPosition;
- case (DreamProcOpcode.SwitchOnFloat
- or DreamProcOpcode.SwitchOnString, float or string, int jumpPosition):
- return jumpPosition;
- case (DreamProcOpcode.Try, int jumpPosition, DMReference):
- return jumpPosition;
- case (DreamProcOpcode.Enumerate, DMReference, int jumpPosition):
- return jumpPosition;
- case (DreamProcOpcode.EnumerateAssoc, DMReference, DMReference, DMReference, int jumpPosition):
- return jumpPosition;
- default:
- return null;
- }
- }
-}