Skip to content
Open
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
24 changes: 12 additions & 12 deletions DMCompiler/Bytecode/DreamProcOpcode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum DreamProcOpcode : byte {
PushType = 0x2,
[OpcodeMetadata(1, OpcodeArgType.String)]
PushString = 0x3,
[OpcodeMetadata(0, OpcodeArgType.String, OpcodeArgType.FormatCount)]
[OpcodeMetadata(0, OpcodeArgType.FormatCount, OpcodeArgType.String)]
FormatString = 0x4,
[OpcodeMetadata(-2, OpcodeArgType.Label)]
SwitchCaseRange = 0x5, //This could either shrink the stack by 2 or 3. Assume 2.
Expand Down Expand Up @@ -136,7 +136,7 @@ public enum DreamProcOpcode : byte {
EnumerateAssoc = 0x43,
[OpcodeMetadata(-2)]
Link = 0x44,
[OpcodeMetadata(-3, OpcodeArgType.TypeId)]
[OpcodeMetadata(-3, OpcodeArgType.ValueType)]
Prompt = 0x45,
[OpcodeMetadata(-3)]
Ftp = 0x46,
Expand Down Expand Up @@ -264,29 +264,29 @@ public enum DreamProcOpcode : byte {
AssignNoPush = 0x85,
[OpcodeMetadata(1, OpcodeArgType.Reference, OpcodeArgType.String)]
PushRefAndDereferenceField = 0x86,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Reference })]
PushNRefs = 0x87,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Float })]
PushNFloats = 0x88,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Resource })]
PushNResources = 0x89,
[OpcodeMetadata(2, OpcodeArgType.String, OpcodeArgType.Float)]
PushStringFloat = 0x8A,
[OpcodeMetadata(0, OpcodeArgType.Reference, OpcodeArgType.Label)]
JumpIfReferenceFalse = 0x8B,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.String })]
PushNStrings = 0x8C,
[OpcodeMetadata(0, OpcodeArgType.Float, OpcodeArgType.Label)]
SwitchOnFloat = 0x8D,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.String, OpcodeArgType.Float })]
PushNOfStringFloats = 0x8E,
[OpcodeMetadata(true, 1, OpcodeArgType.Int)]
[OpcodeMetadata(1, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Float })]
CreateListNFloats = 0x8F,
[OpcodeMetadata(true, 1, OpcodeArgType.Int)]
[OpcodeMetadata(1, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.String })]
CreateListNStrings = 0x90,
[OpcodeMetadata(true, 1, OpcodeArgType.Int)]
[OpcodeMetadata(1, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Reference })]
CreateListNRefs = 0x91,
[OpcodeMetadata(true, 1, OpcodeArgType.Int)]
[OpcodeMetadata(1, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Resource })]
CreateListNResources = 0x92,
[OpcodeMetadata(0, OpcodeArgType.String, OpcodeArgType.Label)]
SwitchOnString = 0x93,
Expand All @@ -303,7 +303,7 @@ public enum DreamProcOpcode : byte {
IndexRefWithString = 0x99,
[OpcodeMetadata(2, OpcodeArgType.Float, OpcodeArgType.Reference)]
PushFloatAssign = 0x9A,
[OpcodeMetadata(true, 0, OpcodeArgType.Int)]
[OpcodeMetadata(0, OpcodeArgType.Int, RepeatedArgs = new[] { OpcodeArgType.Float, OpcodeArgType.Reference })]
NPushFloatAssign = 0x9B,
[OpcodeMetadata(0, OpcodeArgType.ArgType, OpcodeArgType.StackDelta)]
Animate = 0x9C,
Expand Down
87 changes: 73 additions & 14 deletions DMCompiler/Bytecode/OpcodeMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
using System.Runtime.InteropServices;

namespace DMCompiler.Bytecode;

/// <summary>
/// Custom attribute for declaring <see cref="OpcodeMetadata"/> metadata for individual opcodes
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
internal sealed class OpcodeMetadataAttribute : Attribute {
public OpcodeMetadata Metadata;

public OpcodeMetadataAttribute(int stackDelta = 0, params OpcodeArgType[] requiredArgs) {
Metadata = new OpcodeMetadata(stackDelta, false, requiredArgs);
}
public sealed class OpcodeMetadataAttribute(int stackDelta = 0, params OpcodeArgType[] requiredArgs) : Attribute {
private int StackDelta { get; } = stackDelta;
private OpcodeArgType[] RequiredArgs { get; } = requiredArgs;
public OpcodeArgType[] RepeatedArgs { get; set; } = [];

public OpcodeMetadataAttribute(bool variableArgs, int stackDelta,
params OpcodeArgType[] requiredArgs) {
Metadata = new OpcodeMetadata(stackDelta, variableArgs, requiredArgs);
}
public OpcodeMetadata Metadata => new(StackDelta, RequiredArgs, RepeatedArgs);
}

/// <summary>
/// Miscellaneous metadata associated with individual <see cref="DreamProcOpcode"/> opcodes using the <see cref="OpcodeMetadataAttribute"/> attribute
/// </summary>
public struct OpcodeMetadata(int stackDelta = 0, bool variableArgs = false, params OpcodeArgType[] requiredArgs) {
[StructLayout(LayoutKind.Auto)]
public readonly struct OpcodeMetadata(
int stackDelta = 0,
OpcodeArgType[]? requiredArgs = null,
OpcodeArgType[]? repeatedArgs = null) {
public readonly OpcodeArgType[] RequiredArgs = requiredArgs ?? []; // The types of arguments this opcode requires
public readonly OpcodeArgType[] RepeatedArgs = repeatedArgs ?? []; // For variable-arg opcodes, the repeated argument pattern after the count operand
public readonly int StackDelta = stackDelta; // Net change in stack size caused by this opcode
public readonly OpcodeArgType[] RequiredArgs = requiredArgs; // The types of arguments this opcode requires
public readonly bool VariableArgs = variableArgs; // Whether this opcode takes a variable number of arguments
public readonly ProcOperandShape OperandShape = GetOperandShape(repeatedArgs);
public readonly int JumpDestinationOperandIndex = Array.IndexOf(requiredArgs ?? [], OpcodeArgType.Label); // Cache the index of the jump label arg
public readonly bool VariableArgs = repeatedArgs is { Length: > 0 }; // Whether this opcode takes a variable number of arguments

/// <summary>
/// All opcodes with a variable argument length have a pattern to their argument types, which this method deduces and stores in metadata
/// </summary>
/// <remarks>The purpose of this is to optimize formatting and decoding by avoiding a bunch of argument typechecking during enumeration</remarks>
private static ProcOperandShape GetOperandShape(OpcodeArgType[]? repeatedArgs) {
if (repeatedArgs is null)
return ProcOperandShape.Fixed;

return repeatedArgs switch {
[OpcodeArgType.Float] => ProcOperandShape.RepeatedFloat,
[OpcodeArgType.String] => ProcOperandShape.RepeatedString,
[OpcodeArgType.Resource] => ProcOperandShape.RepeatedResource,
[OpcodeArgType.Reference] => ProcOperandShape.RepeatedReference,
[OpcodeArgType.String, OpcodeArgType.Float] => ProcOperandShape.RepeatedStringFloat,
[OpcodeArgType.Float, OpcodeArgType.Reference] => ProcOperandShape.RepeatedFloatReference,
_ => ProcOperandShape.Unsupported
};
}
}

/// <summary>
Expand All @@ -37,15 +60,50 @@ static OpcodeMetadataCache() {
var field = typeof(DreamProcOpcode).GetField(opcode.ToString());
var attribute = Attribute.GetCustomAttribute(field!, typeof(OpcodeMetadataAttribute));
var metadataAttribute = (OpcodeMetadataAttribute?)attribute;
MetadataCache[(byte)opcode] = metadataAttribute?.Metadata ?? new OpcodeMetadata();
OpcodeMetadata metadata = metadataAttribute?.Metadata ?? new OpcodeMetadata();
ValidateMetadata(opcode, metadata);
MetadataCache[(byte)opcode] = metadata;
}
}

public static OpcodeMetadata GetMetadata(DreamProcOpcode opcode) {
return MetadataCache[(byte)opcode];
}

private static void ValidateMetadata(DreamProcOpcode opcode, OpcodeMetadata metadata) {
if (!metadata.VariableArgs) {
if (metadata.RepeatedArgs.Length != 0)
throw new InvalidOperationException($"{opcode} has repeated args but is not variable-arg");

return;
}

if (metadata.RequiredArgs is not [OpcodeArgType.Int])
throw new InvalidOperationException($"{opcode} variable-arg metadata must use an int count operand");

if (metadata.OperandShape == ProcOperandShape.Unsupported)
throw new InvalidOperationException($"{opcode} has unsupported repeated args");
}
}

///<summary>
/// Pattern of arguments for variable-arg <see cref="DreamProcOpcode"/> opcodes
/// </summary>
/// <remarks>Fixed indicates a fixed non-variable arg length</remarks>
public enum ProcOperandShape {
Fixed,
RepeatedFloat,
RepeatedString,
RepeatedResource,
RepeatedReference,
RepeatedStringFloat,
RepeatedFloatReference,
Unsupported,
}

/// <summary>
/// Every type of argument a <see cref="DreamProcOpcode"/> can have
/// </summary>
public enum OpcodeArgType {
ArgType,
StackDelta,
Expand All @@ -63,4 +121,5 @@ public enum OpcodeArgType {
FormatCount,
PickCount,
ConcatCount,
ValueType,
}
16 changes: 15 additions & 1 deletion DMCompiler/Bytecode/OpcodeVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace DMCompiler.Bytecode;
// Dummy class-as-namespace because C# just kinda be like this
public static class OpcodeVerifier {
/// <summary>
/// Calculates a hash of all the <c>DreamProcOpcode</c>s for warning on incompatibilities.
/// Calculates a hash of all the <c>DreamProcOpcode</c>s and their bytecode metadata for warning on incompatibilities.
/// </summary>
/// <returns>A MD5 hash string</returns>
public static string GetOpcodesHash() {
Expand All @@ -17,9 +17,23 @@ public static string GetOpcodesHash() {
byte[] nameBytes = Encoding.ASCII.GetBytes(value.ToString()!);
opcodesBytes.AddRange(nameBytes);
opcodesBytes.Add((byte)value);

var metadata = OpcodeMetadataCache.GetMetadata((DreamProcOpcode)value);
opcodesBytes.AddRange(BitConverter.GetBytes(metadata.StackDelta));
opcodesBytes.Add((byte)(metadata.VariableArgs ? 1 : 0));
AddArgTypes(opcodesBytes, metadata.RequiredArgs);
AddArgTypes(opcodesBytes, metadata.RepeatedArgs);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather override OpcodeMetadata.GetHashCode() and use that here, so that it's clearer that this needs updated if the metadata's fields change.

}

byte[] hashBytes = MD5.HashData(opcodesBytes.ToArray());
return BitConverter.ToString(hashBytes).Replace("-", "");
}

private static void AddArgTypes(List<byte> opcodesBytes, OpcodeArgType[] argTypes) {
opcodesBytes.AddRange(BitConverter.GetBytes(argTypes.Length));

foreach (OpcodeArgType argType in argTypes) {
opcodesBytes.Add((byte)argType);
}
}
}
Loading
Loading