-
Notifications
You must be signed in to change notification settings - Fork 136
CFG Part 0: Max Stack Size & Peephole Optimizer Fixes/Improvements #2561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d564dab
5208f46
4ecd882
f3afae2
c9501a2
8c46b07
744b5a0
cb3fc78
ff5664d
0155307
939d0b2
a08e4fc
99b6020
c8b0116
f4571d1
f13e7d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -207,7 +207,9 @@ public void ValidateReturnType(DMExpression expr) { | |
| public ProcDefinitionJson GetJsonRepresentation() { | ||
| var serializer = new AnnotatedBytecodeSerializer(_compiler); | ||
|
|
||
| _compiler.BytecodeOptimizer.Optimize(AnnotatedBytecode.GetAnnotatedBytecode()); | ||
| List<IAnnotatedBytecode> annotatedBytecode = AnnotatedBytecode.GetAnnotatedBytecode(); | ||
| _compiler.BytecodeOptimizer.Optimize(annotatedBytecode); | ||
| int maxStackSize = AnnotatedBytecode.RecalculateMaxStackSize(); | ||
|
|
||
| List<ProcArgumentJson>? 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<IAnnotatedBytecode> bytecode = AnnotatedBytecode.GetAnnotatedBytecode(); | ||
| HashSet<string>? 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also save the last opcode written by |
||
| 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)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't this not work if the label is only referenced later on in the code? |
||
| return false; | ||
|
|
||
| continue; | ||
| case AnnotatedBytecodeInstruction instruction: | ||
| return InstructionTransfersControl(instruction.Opcode); | ||
| default: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private HashSet<string>? GetReferencedLabels(List<IAnnotatedBytecode> bytecode) { | ||
| HashSet<string>? 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<string>(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); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ private readonly List<IAnnotatedBytecode> | |
| private Location _location; | ||
| private int _maxStackSize; | ||
| private bool _negativeStackSizeError; | ||
| private int _pendingInstructionStackDelta; | ||
| private int _requiredArgIdx; | ||
| private OpcodeMetadata? _currentMetadata; | ||
| private Dictionary<string, long> _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<DMProc.CodeLabelReference> pendingL | |
| /// </summary> | ||
| /// <param name="sizeDelta">The net change in stack size caused by an operation</param> | ||
| 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; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Recomputes the maximum possible stack size from the current annotated bytecode. | ||
| /// Used after optimization because peephole rewrites can change the max stack size. | ||
| /// </summary> | ||
| public int RecalculateMaxStackSize() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why bother tracking stack size while generating opcodes if we just recalculate it at the end? |
||
| _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: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could
LastInstructionTransfersControl()be put intoJump()instead of littering these checks everywhere?