From d564dababb733e0772ba617ac84fb97262eb3d37 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 9 May 2026 10:23:41 -0500 Subject: [PATCH 01/17] steal opts from CFG PR --- DMCompiler/Optimizer/PeepholeOptimizations.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs index 5e5ec5b4f8..aaef4e7623 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizations.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs @@ -232,6 +232,42 @@ 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); + } +} + // PushFloat [float] // SwitchCase [label] // -> SwitchOnFloat [float] [label] From 5208f46225a938618d9e37b67fe00029c895e985 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 9 May 2026 10:40:05 -0500 Subject: [PATCH 02/17] fix a ton of NullRef opts being left on the table --- DMCompiler/Optimizer/PeepholeOptimizations.cs | 2 +- DMCompiler/Optimizer/PeepholeOptimizer.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs index aaef4e7623..3ee9b7e8f9 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; diff --git a/DMCompiler/Optimizer/PeepholeOptimizer.cs b/DMCompiler/Optimizer/PeepholeOptimizer.cs index de4d4791ba..6dd1abfa14 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizer.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizer.cs @@ -137,7 +137,13 @@ int AttemptCurrentOpt(int i) { 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) { i -= AttemptCurrentOpt(i); @@ -162,7 +168,5 @@ int AttemptCurrentOpt(int i) { i -= AttemptCurrentOpt(i); i = Math.Max(i, -1); // i++ brings -1 back to 0 } - - AttemptCurrentOpt(input.Count); } } From 4ecd882967964fcdee1aabd1a9c8a823049b66f9 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 9 May 2026 10:53:41 -0500 Subject: [PATCH 03/17] keep rerunning the peephole optimizer until it's done changing things --- DMCompiler/Optimizer/PeepholeOptimizer.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/DMCompiler/Optimizer/PeepholeOptimizer.cs b/DMCompiler/Optimizer/PeepholeOptimizer.cs index 6dd1abfa14..221db38dec 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizer.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizer.cs @@ -109,14 +109,21 @@ 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 optSize = 0; + bool changed = false; int AttemptCurrentOpt(int i) { if (currentOpt == null) @@ -126,6 +133,7 @@ int AttemptCurrentOpt(int i) { if (currentOpt.Optimization?.CheckPreconditions(input, i - optSize) is true) { currentOpt.Optimization.Apply(_compiler, input, i - optSize); + changed = true; offset = (optSize + 2); // Run over the new opcodes for potential further optimization } else { // This chain of opcodes did not lead to a valid optimization. @@ -139,7 +147,7 @@ int AttemptCurrentOpt(int i) { for (int i = 0; i <= input.Count; i++) { if (i == input.Count) { - i -= AttemptCurrentOpt(i); + i -= AttemptCurrentOpt(i); i = Math.Max(i, -1); // i++ brings -1 back to 0 continue; } @@ -168,5 +176,7 @@ int AttemptCurrentOpt(int i) { i -= AttemptCurrentOpt(i); i = Math.Max(i, -1); // i++ brings -1 back to 0 } + + return changed; } } From f3afae29fcadc35b2f47de497b288e63210eb161 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 9 May 2026 12:11:25 -0500 Subject: [PATCH 04/17] fix max stack size ignoring opts and using incorrect metadata --- DMCompiler/Bytecode/DreamProcOpcode.cs | 4 +- DMCompiler/DM/DMProc.cs | 17 +++---- DMCompiler/DM/Expressions/Builtins.cs | 2 +- .../Optimizer/AnnotatedByteCodeWriter.cs | 44 +++++++++++++++++-- DMCompiler/Optimizer/AnnotatedBytecode.cs | 25 ++++++++++- .../Optimizer/CompactorOptimizations.cs | 4 +- DMCompiler/Optimizer/PeepholeOptimizations.cs | 2 +- 7 files changed, 77 insertions(+), 21 deletions(-) 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/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index ceb83ac094..4e76ded9b9 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -200,7 +200,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) { @@ -228,8 +230,8 @@ public ProcDefinitionJson GetJsonRepresentation() { OwningTypeId = _dmObject.Id, Name = Name, Attributes = Attributes, - MaxStackSize = AnnotatedBytecode.GetMaxStackSize(), - Bytecode = serializer.Serialize(AnnotatedBytecode.GetAnnotatedBytecode()), + MaxStackSize = maxStackSize, + Bytecode = serializer.Serialize(annotatedBytecode), Arguments = arguments, SourceInfo = serializer.SourceInfo, Locals = (_localVariableNames.Count > 0) ? serializer.GetLocalVariablesJson() : null, @@ -901,9 +903,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); @@ -937,7 +938,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); @@ -1267,7 +1268,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 84beac7b80..7da8e7ab14 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 3bb38adb09..276f9b491c 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 static 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 static 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/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 3ee9b7e8f9..fb7ec5b943 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizations.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs @@ -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])); } } From c9501a2cd279d131115087f9e5ea3513c339c290 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sat, 9 May 2026 23:59:17 -0500 Subject: [PATCH 05/17] skip AnnotatedBytecodeVariables during peephole opts --- DMCompiler/Optimizer/PeepholeOptimizer.cs | 87 ++++++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/DMCompiler/Optimizer/PeepholeOptimizer.cs b/DMCompiler/Optimizer/PeepholeOptimizer.cs index 221db38dec..82f17bfe34 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizer.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizer.cs @@ -122,26 +122,35 @@ public void RunPeephole(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; } @@ -154,6 +163,10 @@ int AttemptCurrentOpt(int i) { 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; @@ -164,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; } @@ -179,4 +193,71 @@ int AttemptCurrentOpt(int i) { 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); + } + } } From 8c46b078e8f87e1d0101cfd07087254a62c60583 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 00:08:00 -0500 Subject: [PATCH 06/17] pre-emptively nuke any jumps after throw --- DMCompiler/Optimizer/PeepholeOptimizations.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs index fb7ec5b943..62cf8d9473 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizations.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs @@ -268,6 +268,24 @@ public void Apply(DMCompiler compiler, List input, int index } } +// 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] From 744b5a0eadb7dc280f98eb538bec463535f172c2 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 00:17:40 -0500 Subject: [PATCH 07/17] fix similar issues with labels --- DMCompiler/Optimizer/BytecodeOptimizer.cs | 54 ++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index bef207a634..3bb378121e 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; @@ -98,6 +99,8 @@ private void JoinAndForwardLabels(List input) { } } + ForwardLabelsAfterTerminalJump(input, labelAliases); + for (int i = 0; i < input.Count; i++) { if (input[i] is AnnotatedBytecodeInstruction instruction) { if (TryGetLabelName(instruction, out string? labelName)) { @@ -105,7 +108,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 +120,55 @@ private void JoinAndForwardLabels(List input) { } } + private void ForwardLabelsAfterTerminalJump(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 { 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[labelName] = targetLabel; + } + + break; + } + } + + break; + } + } + } + + // 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.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) From cb3fc78fb1b95dbd9c98635e9d4953eeb58923a2 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 00:28:51 -0500 Subject: [PATCH 08/17] fix switch() inserting dead jumps --- DMCompiler/DM/Builders/DMProcBuilder.cs | 9 +++++++-- DMCompiler/DM/DMProc.cs | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index d1564ccda7..32a1c76eb9 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -758,7 +758,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 +769,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 4e76ded9b9..90dd0fca86 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -850,6 +850,28 @@ public void Jump(string label) { WriteLabel(label); } + // 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 + public bool LastInstructionTransfersControl() { + List bytecode = AnnotatedBytecode.GetAnnotatedBytecode(); + // 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 AnnotatedBytecodeInstruction instruction: + return instruction.Opcode is DreamProcOpcode.Jump or + DreamProcOpcode.Return or + DreamProcOpcode.ReturnReferenceValue or + DreamProcOpcode.ReturnFloat or + DreamProcOpcode.Throw; + default: + return false; + } + } + + return false; + } + public void JumpIfFalse(string label) { WriteOpcode(DreamProcOpcode.JumpIfFalse); WriteLabel(label); From ff5664dc6bb3b125e9c5306f66731a522c32e86f Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 01:27:34 -0500 Subject: [PATCH 09/17] deal with more dead jumps --- DMCompiler/Optimizer/BytecodeOptimizer.cs | 28 ++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index 3bb378121e..446d9a7020 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -27,6 +27,8 @@ internal void Optimize(List input) { RemoveUnreferencedLabels(input); RemoveImmediateJumps(input); RemoveUnreferencedLabels(input); + RemoveJumpsAfterTerminalInstructions(input); + RemoveUnreferencedLabels(input); } private void RemoveUnreferencedLabels(List input) { @@ -99,7 +101,12 @@ private void JoinAndForwardLabels(List input) { } } - ForwardLabelsAfterTerminalJump(input, labelAliases); + 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) { @@ -120,7 +127,12 @@ private void JoinAndForwardLabels(List input) { } } - private void ForwardLabelsAfterTerminalJump(List input, Dictionary labelAliases) { + 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; @@ -139,21 +151,31 @@ private void ForwardLabelsAfterTerminalJump(List input, Dict break; foreach (string labelName in labels) { + labelAliases ??= new Dictionary(); labelAliases[labelName] = targetLabel; } break; } + case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump }: { + input.RemoveAt(j); + i--; + 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.Return or DreamProcOpcode.ReturnReferenceValue or DreamProcOpcode.ReturnFloat or DreamProcOpcode.Throw; + return opcode is + DreamProcOpcode.Jump or DreamProcOpcode.Return or DreamProcOpcode.ReturnReferenceValue or + DreamProcOpcode.ReturnFloat or DreamProcOpcode.Throw; } private string ResolveLabelAlias(Dictionary labelAliases, string labelName) { From 01553077cb231582d32fd01a4bdeb4553c7df173 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 01:40:42 -0500 Subject: [PATCH 10/17] beat EndTry into submission --- DMCompiler/Optimizer/BytecodeOptimizer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index 446d9a7020..3dd2dd1842 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -145,6 +145,12 @@ private void RemoveJumpsAfterTerminalInstructions(List input case AnnotatedBytecodeLabel label: labels.Add(label.LabelName); continue; + // Edge case in which we have Return, EndTry, Jump + case AnnotatedBytecodeInstruction cleanup when labels.Count == 0 && cleanup.Opcode is DreamProcOpcode.EndTry: + input.RemoveAt(j); + i -= 1; + j -= 1; + continue; case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump } jump when labels.Count > 0: { string targetLabel = jump.GetArg(0).LabelName; if (labels.Contains(targetLabel)) @@ -159,7 +165,7 @@ private void RemoveJumpsAfterTerminalInstructions(List input } case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump }: { input.RemoveAt(j); - i--; + i -= 1; break; } } From 939d0b21040535ca34c410291fe514ab4c9abc40 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 01:51:26 -0500 Subject: [PATCH 11/17] nuke redundant codegen from spawn() too --- DMCompiler/DM/Builders/DMProcBuilder.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index 32a1c76eb9..31869d009d 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(); From a08e4fced83ce17367e4fcc8d173091514bcbdaf Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 04:11:15 -0500 Subject: [PATCH 12/17] nuke jumps from loops --- DMCompiler/DM/Builders/DMProcBuilder.cs | 13 ++++--- DMCompiler/DM/DMProc.cs | 50 ++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index 31869d009d..f75b2d8d90 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -244,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(); @@ -548,7 +549,7 @@ void CheckType(DMValueType type, DreamPath path, ref bool doOr) { } ProcessBlockInner(body); - proc.LoopJumpToStart(loopLabel); + proc.LoopJumpToStartIfReachable(loopLabel); } proc.LoopEnd(); } @@ -590,7 +591,7 @@ private void ProcessStatementForType(Location location, DMExpression? initialize } ProcessBlockInner(body); - proc.LoopJumpToStart(loopLabel); + proc.LoopJumpToStartIfReachable(loopLabel); } proc.LoopEnd(); } @@ -627,7 +628,7 @@ private void ProcessStatementForRange(DMExpression? initializer, DMExpression ou } ProcessBlockInner(body); - proc.LoopJumpToStart(loopLabel); + proc.LoopJumpToStartIfReachable(loopLabel); } proc.LoopEnd(); } @@ -644,7 +645,7 @@ private void ProcessStatementInfLoop(DMASTProcStatementInfLoop statementInfLoop) { proc.MarkLoopContinue(loopLabel); ProcessBlockInner(statementInfLoop.Body); - proc.LoopJumpToStart(loopLabel); + proc.LoopJumpToStartIfReachable(loopLabel); } proc.LoopEnd(); } @@ -663,7 +664,7 @@ private void ProcessStatementWhile(DMASTProcStatementWhile statementWhile) { proc.StartScope(); { ProcessBlockInner(statementWhile.Body); - proc.LoopJumpToStart(loopLabel); + proc.LoopJumpToStartIfReachable(loopLabel); } proc.EndScope(); } diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 90dd0fca86..dc92f10470 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -694,6 +694,15 @@ public void LoopJumpToStart(string loopLabel) { Jump($"{loopLabel}_start"); } + /// + /// Jump to start but only if the last instruction doesn't supersede the Jump (e.g. a return) + /// + public void LoopJumpToStartIfReachable(string loopLabel) { + if (!LastInstructionTransfersControl()) { + LoopJumpToStart(loopLabel); + } + } + public void LoopEnd() { if (_loopStack?.TryPop(out var pop) ?? false) { AddLabel(pop + "_end"); @@ -850,20 +859,22 @@ public void Jump(string label) { WriteLabel(label); } - // 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 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 instruction.Opcode is DreamProcOpcode.Jump or - DreamProcOpcode.Return or - DreamProcOpcode.ReturnReferenceValue or - DreamProcOpcode.ReturnFloat or - DreamProcOpcode.Throw; + return InstructionTransfersControl(instruction.Opcode); default: return false; } @@ -872,6 +883,33 @@ DreamProcOpcode.ReturnFloat or 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); From 99b6020406e8d544e83a2a97ef72a022c36d7e40 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 05:29:57 -0500 Subject: [PATCH 13/17] revert a regression in label handling that led to bad bytecode --- DMCompiler/DM/Builders/DMProcBuilder.cs | 10 +++++----- DMCompiler/DM/DMProc.cs | 9 --------- DMCompiler/Optimizer/BytecodeOptimizer.cs | 20 +++++++++----------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index f75b2d8d90..a4181dde76 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -549,7 +549,7 @@ void CheckType(DMValueType type, DreamPath path, ref bool doOr) { } ProcessBlockInner(body); - proc.LoopJumpToStartIfReachable(loopLabel); + proc.LoopJumpToStart(loopLabel); } proc.LoopEnd(); } @@ -591,7 +591,7 @@ private void ProcessStatementForType(Location location, DMExpression? initialize } ProcessBlockInner(body); - proc.LoopJumpToStartIfReachable(loopLabel); + proc.LoopJumpToStart(loopLabel); } proc.LoopEnd(); } @@ -628,7 +628,7 @@ private void ProcessStatementForRange(DMExpression? initializer, DMExpression ou } ProcessBlockInner(body); - proc.LoopJumpToStartIfReachable(loopLabel); + proc.LoopJumpToStart(loopLabel); } proc.LoopEnd(); } @@ -645,7 +645,7 @@ private void ProcessStatementInfLoop(DMASTProcStatementInfLoop statementInfLoop) { proc.MarkLoopContinue(loopLabel); ProcessBlockInner(statementInfLoop.Body); - proc.LoopJumpToStartIfReachable(loopLabel); + proc.LoopJumpToStart(loopLabel); } proc.LoopEnd(); } @@ -664,7 +664,7 @@ private void ProcessStatementWhile(DMASTProcStatementWhile statementWhile) { proc.StartScope(); { ProcessBlockInner(statementWhile.Body); - proc.LoopJumpToStartIfReachable(loopLabel); + proc.LoopJumpToStart(loopLabel); } proc.EndScope(); } diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index dc92f10470..a7362aa83c 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -694,15 +694,6 @@ public void LoopJumpToStart(string loopLabel) { Jump($"{loopLabel}_start"); } - /// - /// Jump to start but only if the last instruction doesn't supersede the Jump (e.g. a return) - /// - public void LoopJumpToStartIfReachable(string loopLabel) { - if (!LastInstructionTransfersControl()) { - LoopJumpToStart(loopLabel); - } - } - public void LoopEnd() { if (_loopStack?.TryPop(out var pop) ?? false) { AddLabel(pop + "_end"); diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index 3dd2dd1842..728938243b 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -106,7 +106,8 @@ private void JoinAndForwardLabels(List input) { } private void RewriteLabelAliases(List input, Dictionary? labelAliases) { - if (labelAliases is null || labelAliases.Count == 0) return; + if (labelAliases is null || labelAliases.Count == 0) + return; for (int i = 0; i < input.Count; i++) { if (input[i] is AnnotatedBytecodeInstruction instruction) { @@ -145,10 +146,8 @@ private void RemoveJumpsAfterTerminalInstructions(List input case AnnotatedBytecodeLabel label: labels.Add(label.LabelName); continue; - // Edge case in which we have Return, EndTry, Jump - case AnnotatedBytecodeInstruction cleanup when labels.Count == 0 && cleanup.Opcode is DreamProcOpcode.EndTry: + case AnnotatedBytecodeInstruction when labels.Count == 0: input.RemoveAt(j); - i -= 1; j -= 1; continue; case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump } jump when labels.Count > 0: { @@ -157,13 +156,10 @@ private void RemoveJumpsAfterTerminalInstructions(List input break; foreach (string labelName in labels) { - labelAliases ??= new Dictionary(); + labelAliases ??= new Dictionary(1); labelAliases[labelName] = targetLabel; } - break; - } - case AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.Jump }: { input.RemoveAt(j); i -= 1; break; @@ -179,9 +175,11 @@ private void RemoveJumpsAfterTerminalInstructions(List input // 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; + return opcode is DreamProcOpcode.Jump or + DreamProcOpcode.Return or + DreamProcOpcode.ReturnReferenceValue or + DreamProcOpcode.ReturnFloat or + DreamProcOpcode.Throw; } private string ResolveLabelAlias(Dictionary labelAliases, string labelName) { From c8b01167ec0b192b0d1886e3cf0ea856e7d34457 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 10 May 2026 05:59:55 -0500 Subject: [PATCH 14/17] I should quit while I'm ahead --- DMCompiler/Optimizer/BytecodeOptimizer.cs | 3 ++ OpenDreamRuntime/Procs/ProcDecoder.cs | 42 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index 728938243b..2bcd0b70ac 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -150,6 +150,9 @@ private void RemoveJumpsAfterTerminalInstructions(List input 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)) diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs index b01a50c2a5..b6a984f82e 100644 --- a/OpenDreamRuntime/Procs/ProcDecoder.cs +++ b/OpenDreamRuntime/Procs/ProcDecoder.cs @@ -61,6 +61,7 @@ public DMReference ReadReference() { 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}"); } } @@ -287,7 +288,10 @@ or DreamProcOpcode.BooleanAnd or DreamProcOpcode.SwitchCase or DreamProcOpcode.SwitchCaseRange or DreamProcOpcode.Jump - or DreamProcOpcode.JumpIfFalse, int jumpPosition): + or DreamProcOpcode.JumpIfFalse + or DreamProcOpcode.JumpIfNull + or DreamProcOpcode.JumpIfNullNoPop + or DreamProcOpcode.TryNoValue, int jumpPosition): text.AppendFormat("0x{0:x}", jumpPosition); break; @@ -310,6 +314,34 @@ or DreamProcOpcode.JumpIfTrueReference text.AppendFormat(" 0x{0:x}", jumpPosition); break; + case (DreamProcOpcode.Try, int jumpPosition, DMReference reference): + text.AppendFormat("0x{0:x}", jumpPosition); + text.Append(' '); + text.Append(reference.ToString()); + break; + + case (DreamProcOpcode.Enumerate, int enumeratorId, DMReference reference, int jumpPosition): + text.Append(enumeratorId); + text.Append(' '); + text.Append(reference.ToString()); + text.AppendFormat(" 0x{0:x}", jumpPosition); + break; + + case (DreamProcOpcode.EnumerateAssoc, int enumeratorId, DMReference assocReference, + DMReference outputReference, int jumpPosition): + text.Append(enumeratorId); + text.Append(' '); + text.Append(assocReference.ToString()); + text.Append(' '); + text.Append(outputReference.ToString()); + text.AppendFormat(" 0x{0:x}", jumpPosition); + break; + + case (DreamProcOpcode.EnumerateNoAssign, int enumeratorId, int jumpPosition): + text.Append(enumeratorId); + text.AppendFormat(" 0x{0:x}", jumpPosition); + break; + case (DreamProcOpcode.PushType or DreamProcOpcode.IsTypeDirect, int type): text.Append(getTypePath(type)); @@ -426,6 +458,8 @@ or DreamProcOpcode.SwitchCase or DreamProcOpcode.SwitchCaseRange or DreamProcOpcode.Jump or DreamProcOpcode.JumpIfFalse + or DreamProcOpcode.JumpIfNull + or DreamProcOpcode.JumpIfNullNoPop or DreamProcOpcode.TryNoValue, int jumpPosition): return jumpPosition; case (DreamProcOpcode.JumpIfFalseReference @@ -437,9 +471,11 @@ or DreamProcOpcode.JumpIfTrueReference return jumpPosition; case (DreamProcOpcode.Try, int jumpPosition, DMReference): return jumpPosition; - case (DreamProcOpcode.Enumerate, DMReference, int jumpPosition): + case (DreamProcOpcode.Enumerate, int, DMReference, int jumpPosition): + return jumpPosition; + case (DreamProcOpcode.EnumerateAssoc, int, DMReference, DMReference, int jumpPosition): return jumpPosition; - case (DreamProcOpcode.EnumerateAssoc, DMReference, DMReference, DMReference, int jumpPosition): + case (DreamProcOpcode.EnumerateNoAssign, int, int jumpPosition): return jumpPosition; default: return null; From f4571d1492ac633a03f333f0f427b8cd48df47a9 Mon Sep 17 00:00:00 2001 From: ike709 Date: Thu, 14 May 2026 10:32:37 -0500 Subject: [PATCH 15/17] I forgot to commit the part where I made those methods not static --- DMCompiler/Optimizer/AnnotatedBytecode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DMCompiler/Optimizer/AnnotatedBytecode.cs b/DMCompiler/Optimizer/AnnotatedBytecode.cs index 276f9b491c..4a7fad9b46 100644 --- a/DMCompiler/Optimizer/AnnotatedBytecode.cs +++ b/DMCompiler/Optimizer/AnnotatedBytecode.cs @@ -51,7 +51,7 @@ public AnnotatedBytecodeInstruction(DreamProcOpcode opcode, int stackSizeDelta, _args = args; } - private static int GetArgsStackSizeDelta(List args) { + private int GetArgsStackSizeDelta(List args) { int delta = 0; foreach (IAnnotatedBytecode arg in args) { @@ -64,7 +64,7 @@ private static int GetArgsStackSizeDelta(List args) { return delta; } - private static int GetReferenceStackSizeDelta(DMReference.Type refType) { + private int GetReferenceStackSizeDelta(DMReference.Type refType) { return refType switch { DMReference.Type.Field => -1, DMReference.Type.ListIndex => -2, From 39c508296d3f3ed01c85d64fe1b82b8dfb49e9f4 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 26 May 2026 07:06:29 -0500 Subject: [PATCH 16/17] CFG Part 1: ProcDecoder Refactor --- DMCompiler/Bytecode/BytecodeProcDecoder.cs | 328 ++++++++++++ DMCompiler/Bytecode/ProcInstruction.cs | 389 ++++++++++++++ DMDisassembler/DMProc.cs | 18 +- .../Procs/DebugAdapter/DreamDebugManager.cs | 4 +- OpenDreamRuntime/Procs/ProcDecoder.cs | 484 ------------------ 5 files changed, 728 insertions(+), 495 deletions(-) create mode 100644 DMCompiler/Bytecode/BytecodeProcDecoder.cs create mode 100644 DMCompiler/Bytecode/ProcInstruction.cs delete mode 100644 OpenDreamRuntime/Procs/ProcDecoder.cs 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/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/DMDisassembler/DMProc.cs b/DMDisassembler/DMProc.cs index c7d69b0b34..5a1273145d 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,7 +25,7 @@ public string Decompile() { StringBuilder result = new StringBuilder(); foreach (DecompiledOpcode decompiledOpcode in decompiled) { if (labeledPositions.Contains(decompiledOpcode.Position)) { - result.AppendFormat("0x{0:x}", decompiledOpcode.Position); + result.AppendFormat("0x{0:x}", decompiledOpcode.Position.ToString()); result.AppendLine(); } @@ -36,7 +35,7 @@ public string Decompile() { if (labeledPositions.Contains(Bytecode.Length)) { // In case of a Jump off the end of the proc. - result.AppendFormat("0x{0:x}", Bytecode.Length); + result.AppendFormat("0x{0:x}", Bytecode.Length.ToString()); result.AppendLine(); } @@ -48,13 +47,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 b6a984f82e..0000000000 --- a/OpenDreamRuntime/Procs/ProcDecoder.cs +++ /dev/null @@ -1,484 +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; - case DMReference.Type.NoRef: return new DMReference { RefType = DMReference.Type.NoRef }; - 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 - or DreamProcOpcode.JumpIfNull - or DreamProcOpcode.JumpIfNullNoPop - or DreamProcOpcode.TryNoValue, 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.Try, int jumpPosition, DMReference reference): - text.AppendFormat("0x{0:x}", jumpPosition); - text.Append(' '); - text.Append(reference.ToString()); - break; - - case (DreamProcOpcode.Enumerate, int enumeratorId, DMReference reference, int jumpPosition): - text.Append(enumeratorId); - text.Append(' '); - text.Append(reference.ToString()); - text.AppendFormat(" 0x{0:x}", jumpPosition); - break; - - case (DreamProcOpcode.EnumerateAssoc, int enumeratorId, DMReference assocReference, - DMReference outputReference, int jumpPosition): - text.Append(enumeratorId); - text.Append(' '); - text.Append(assocReference.ToString()); - text.Append(' '); - text.Append(outputReference.ToString()); - text.AppendFormat(" 0x{0:x}", jumpPosition); - break; - - case (DreamProcOpcode.EnumerateNoAssign, int enumeratorId, int jumpPosition): - text.Append(enumeratorId); - 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.JumpIfNull - or DreamProcOpcode.JumpIfNullNoPop - 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, int, DMReference, int jumpPosition): - return jumpPosition; - case (DreamProcOpcode.EnumerateAssoc, int, DMReference, DMReference, int jumpPosition): - return jumpPosition; - case (DreamProcOpcode.EnumerateNoAssign, int, int jumpPosition): - return jumpPosition; - default: - return null; - } - } -} From 4da52a43db144d2f628900993896fbbabfa75b81 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 26 May 2026 07:36:24 -0500 Subject: [PATCH 17/17] fix me breaking hexadecimal conversion --- DMDisassembler/DMProc.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DMDisassembler/DMProc.cs b/DMDisassembler/DMProc.cs index 5a1273145d..10a5bbfd76 100644 --- a/DMDisassembler/DMProc.cs +++ b/DMDisassembler/DMProc.cs @@ -25,7 +25,8 @@ public string Decompile() { StringBuilder result = new StringBuilder(); foreach (DecompiledOpcode decompiledOpcode in decompiled) { if (labeledPositions.Contains(decompiledOpcode.Position)) { - result.AppendFormat("0x{0:x}", decompiledOpcode.Position.ToString()); + // Calling ToString() on Position will mess up the format conversion to hexadecimal + result.AppendFormat("0x{0:x}", decompiledOpcode.Position); result.AppendLine(); } @@ -35,7 +36,7 @@ public string Decompile() { if (labeledPositions.Contains(Bytecode.Length)) { // In case of a Jump off the end of the proc. - result.AppendFormat("0x{0:x}", Bytecode.Length.ToString()); + result.AppendFormat("0x{0:x}", Bytecode.Length); result.AppendLine(); }