Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Content.Tests/DMProject/SpecialTests/Procs/local_count.dme
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Used in the EnsureLocalCountIsValid test

#define TEST_PROC(name) /proc/Test___##name()
Comment thread
tontyGH marked this conversation as resolved.

TEST_PROC(ProcWithNothing)
// ...

TEST_PROC(ProcWithNoScope)
var/lorem
var/ipsum
var/dolor
var/sit
var/amet
var/consectetur

TEST_PROC(ProcWithOnlyScope)
do
var/lorem
var/ipsum
var/dolor
var/sit
var/amet
var/consectetur
while(FALSE)

TEST_PROC(ProcWithFullOuterScope)
var/lorem
do
var/ipsum
var/dolor
while(FALSE)
var/sit
var/amet
var/consectetur // max

TEST_PROC(ProcWithFullInnerScope)
var/lorem
var/ipsum
do
var/dolor
var/sit
var/amet // max
while(FALSE)
var/consectetur
32 changes: 32 additions & 0 deletions Content.Tests/RuntimeTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using System;
using System.IO;
using System.Linq;
using DMCompiler.Bytecode;
using DMCompiler.Compiler;
using DMCompiler.Json;
using NUnit.Framework;

namespace Content.Tests;

[TestFixture]
public sealed class RuntimeTests {
private const string SpecialTestsDirectory = "SpecialTests";

/// <summary>
/// Validates that the opcodes in DreamProcOpcode are all unique, such that none resolve to the same byte.
/// </summary>
Expand All @@ -29,4 +34,31 @@ public void EnsurePragmaDefaults() {
$"Warning #{(int)code:d4} '{code.ToString()}' is in the range 0-999 and so must be an error.");
}
}

/// <summary>
/// The MaxVariableCount of a proc
/// should reflect the max amount of variables that COULD be allocated at once.
/// </summary>
[Test]
public void EnsureLocalCountIsValid() {
const string testPath = "Procs/local_count.dme";
const string testIdentifier = "Test___";
var compiler = new DMCompiler.DMCompiler();

if(!compiler.Compile(new() {Files = [Path.Join(Directory.GetCurrentDirectory(), SpecialTestsDirectory, testPath)]}, out var compiledDream))
Assert.Fail("Environment failed to compile");
else {
var tests = compiledDream.Procs
.Where((proc) => proc.Name.StartsWith(testIdentifier))
.ToDictionary((proc) => proc.Name[testIdentifier.Length..]); // strip the identifier out

using (Assert.EnterMultipleScope()) {
Assert.That(tests["ProcWithNothing"].MaxVariableId, Is.Zero);
Assert.That(tests["ProcWithNoScope"].MaxVariableId, Is.EqualTo(5));
Assert.That(tests["ProcWithOnlyScope"].MaxVariableId, Is.EqualTo(5));
Assert.That(tests["ProcWithFullOuterScope"].MaxVariableId, Is.EqualTo(3));
Assert.That(tests["ProcWithFullInnerScope"].MaxVariableId, Is.EqualTo(4));
}
}
}
}
10 changes: 9 additions & 1 deletion DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public DMProcScope(DMProcScope? parentScope) {

private readonly List<string> _localVariableNames = new();
private int _localVariableIdCounter;
private int _localVariableHighestId; // 0 is a valid alloc index, which will clip procs with no localvars but that shouldn't matter

private readonly List<SourceInfoJson> _sourceInfo = new();
private string? _lastSourceFile;
Expand Down Expand Up @@ -146,7 +147,13 @@ public DMProc(DMCompiler compiler, int id, DMObject dmObject, DMASTProcDefinitio
private int AllocLocalVariable(string name) {
_localVariableNames.Add(name);
WriteLocalVariable(name);
return _localVariableIdCounter++;

int variableId = _localVariableIdCounter++;
if(variableId > _localVariableHighestId) {
_localVariableHighestId = variableId;
}

return variableId;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}

private void DeallocLocalVariables(int amount) {
Expand Down Expand Up @@ -228,6 +235,7 @@ public ProcDefinitionJson GetJsonRepresentation() {
OwningTypeId = _dmObject.Id,
Name = Name,
Attributes = Attributes,
MaxVariableId = _localVariableHighestId,
MaxStackSize = AnnotatedBytecode.GetMaxStackSize(),
Bytecode = serializer.Serialize(AnnotatedBytecode.GetAnnotatedBytecode()),
Arguments = arguments,
Expand Down
21 changes: 17 additions & 4 deletions DMCompiler/DMCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using DMCompiler.DM.Builders;
using DMCompiler.Json;
using DMCompiler.Optimizer;
using System.Diagnostics.CodeAnalysis;

namespace DMCompiler;

Expand Down Expand Up @@ -44,7 +45,9 @@ public DMCompiler() {
_errorConfig = new Dictionary<WarningCode, ErrorLevel>(CompilerEmission.DefaultErrorConfig);
}

public bool Compile(DMCompilerSettings settings) {
public bool Compile(DMCompilerSettings settings) => Compile(settings, out _);

public bool Compile(DMCompilerSettings settings, [NotNullWhen(true)] out DreamCompiledJson? compiledDream) {
if (_compileStartTime != default)
throw new Exception("Create a new DMCompiler to compile again");

Expand All @@ -71,13 +74,15 @@ public bool Compile(DMCompilerSettings settings) {

var preprocessor = Preprocess(this, settings.Files, settings.MacroDefines);
var successfulCompile = false;
compiledDream = null;
if (preprocessor is not null && Compile(preprocessor)) {
//Output file is the first file with the extension changed to .json
string outputFile = Path.ChangeExtension(settings.Files[0], "json");
List<DreamMapJson> maps = ConvertMaps(this, preprocessor.IncludedMaps);

if (_errorCount == 0) {
var output = SaveJson(maps, preprocessor.IncludedInterface, outputFile);
compiledDream = CompileToJson(maps, preprocessor.IncludedInterface, outputFile);
var output = SaveJson(compiledDream, outputFile);

if (_errorCount == 0) {
successfulCompile = true;
Expand Down Expand Up @@ -291,7 +296,7 @@ private List<DreamMapJson> ConvertMaps(DMCompiler compiler, List<string> mapPath
return maps;
}

private string SaveJson(List<DreamMapJson> maps, string? interfaceFile, string outputFile) {
private DreamCompiledJson? CompileToJson(List<DreamMapJson> maps, string? interfaceFile, string outputFile) {
if (!string.IsNullOrWhiteSpace(interfaceFile) &&
Path.GetDirectoryName(Path.GetFullPath(outputFile)) is { } interfaceDirectory) {
interfaceFile = Path.GetRelativePath(interfaceDirectory, interfaceFile);
Expand Down Expand Up @@ -353,8 +358,16 @@ private string SaveJson(List<DreamMapJson> maps, string? interfaceFile, string o
compiledDream.GlobalProcs = DMObjectTree.GlobalProcs.Values.ToArray();
}

if(_errorCount == 0) {
return compiledDream;
}

return null;
}

private string SaveJson(DreamCompiledJson? compiledDream, string outputFile) {
// Successful serialization
if (_errorCount == 0) {
if (_errorCount == 0 && compiledDream != null) {
using var outputFileHandle = File.Create(outputFile);

try {
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/Json/DreamProcJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public sealed class ProcDefinitionJson {
public required string Name { get; init; }
public ProcAttributes Attributes { get; init; }

public int MaxVariableId { get; init; }
public int MaxStackSize { get; init; }
public List<ProcArgumentJson>? Arguments { get; init; }
public List<LocalVariableJson>? Locals { get; init; }
Expand Down
3 changes: 2 additions & 1 deletion OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed class DMProc : DreamProc {
public readonly bool IsNullProc;
public IReadOnlyList<LocalVariableJson> LocalNames { get; }
public readonly List<SourceInfoJson> SourceInfo;
public int LocalCount => LocalNames.Count;
public readonly int LocalCount;

public readonly AtomManager AtomManager;
public readonly DreamManager DreamManager;
Expand All @@ -42,6 +42,7 @@ public DMProc(int id, TreeEntry owningType, ProcDefinitionJson json, string? nam
Bytecode = json.Bytecode ?? [];
LocalNames = json.Locals ?? [];
SourceInfo = json.SourceInfo;
LocalCount = json.MaxVariableId;
_maxStackSize = json.MaxStackSize;
IsNullProc = CheckIfNullProc();

Expand Down
Loading