Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/System/VerseVMBytecodeGenerator.cs
saam barati 0b729bc858 Generate bytecode for instantiating top-level module code
#rb russell.johnston
#okforversepublic

Top-level code is allowed to have mostly arbitrary expressions in it. For example, you could have `X:int = 1+2` at the top level. Prior to this patch, we had a janky AST interpreter that pattern matched code at the top level. It only worked for things that were constant to the degree it understood. However, it's cleaner to just generate bytecode for top-level code that uses the normal interperter and binds evaluation results to variables.

[CL 31833870 by saam barati in ue5-main branch]
2024-02-27 03:10:19 -05:00

1009 lines
29 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnrealBuildTool
{
namespace VerseVMBytecode
{
internal enum CppType
{
LabelOffset,
ClassKind,
}
internal enum Role
{
Use,
Immediate, // This means that the operand will be embedded in the bytecode itself.
UnifyDef,
ClobberDef,
}
internal enum Arity
{
Fixed,
Variadic,
}
static class Extensions
{
public static string ToCpp(this Role TheRole)
{
switch (TheRole)
{
case Role.Use:
return "EOperandRole::Use";
case Role.Immediate:
return "EOperandRole::Immediate";
case Role.UnifyDef:
return "EOperandRole::UnifyDef";
case Role.ClobberDef:
return "EOperandRole::ClobberDef";
default:
break;
}
return "#error \"Unknown role.\"";
}
public static string DefCppType(this Argument TheArg)
{
switch (TheArg.Role)
{
case Role.Use:
return "FValueOperand";
case Role.Immediate:
return $"{TheArg.CppTypeName}";
case Role.UnifyDef: // fallthrough
case Role.ClobberDef:
return "FRegisterIndex";
default:
throw new ArgumentException("Unknown role.");
}
}
public static string ToCpp(this CppType Type)
{
switch (Type)
{
case CppType.LabelOffset:
return "FLabelOffset";
case CppType.ClassKind:
return "VClass::EKind";
}
return "#error";
}
public static string ToCpp(this bool Bool)
{
return Bool ? "true" : "false";
}
}
internal class Argument
{
public string Name;
/// <summary>
/// If this is an immediate operand, the value will be embedded in the opcode itself.
/// This should be set to the the string name of the underlying operand native type.
/// </summary>
public string CppTypeName;
public Role Role;
public Arity Arity;
public Argument(in string InName, in Role InRole, in Arity InArity, in string InCppTypeName)
{
Name = InName;
Role = InRole;
Arity = InArity;
CppTypeName = InCppTypeName;
}
public Argument(in string InName, in Role InRole, in Arity InArity) : this(InName, InRole, InArity, "")
{}
public Argument(in string InName, in Role InRole) : this(InName, InRole, Arity.Fixed)
{}
}
internal class Constant
{
public string Name;
public CppType Type;
public Constant(string _Name, CppType _Type)
{
Name = _Name;
Type = _Type;
}
public bool IsJump()
{
return Type == CppType.LabelOffset;
}
}
internal class Instruction
{
public string Name;
public List<Argument> Args = new List<Argument>();
public List<Constant> Consts = new List<Constant>();
public bool _CapturesEffectToken = false;
public bool _CreatesNewReturnEffectToken = false;
public bool _Suspends = false;
public bool _Jumps = false;
public Instruction(string _Name)
{
Name = _Name;
}
public string CppName => $"FOp{Name}";
public string CppCapturesName => $"F{Name}SuspensionCaptures";
public Instruction Arg(in string InName, in Role InRole, in Arity InArity, in string InCppTypeName)
{
Args.Add(new Argument(InName, InRole, InArity, InCppTypeName));
return this;
}
public Instruction Arg(in string InName, in Role InRole, in Arity InArity)
{
return Arg(InName, InRole, InArity, "");
}
public Instruction Arg(in string InName, in Role InRole)
{
return Arg(InName, InRole, Arity.Fixed);
}
public Instruction Const(string Name, CppType Type)
{
Consts.Add(new Constant(Name, Type));
return this;
}
public Instruction Jump(string Name)
{
_Jumps = true;
return Const(Name, CppType.LabelOffset);
}
public Instruction CapturesEffectToken()
{
_CapturesEffectToken = true;
return this;
}
public Instruction CreatesNewReturnEffectToken()
{
_CreatesNewReturnEffectToken = true;
return this;
}
public Instruction Suspends()
{
_Suspends = true;
return this;
}
}
}
}
namespace UnrealBuildTool
{
using VerseVMBytecode;
/// <summary>
/// Generates bytecode and bytecode helpers for the VerseVM.
/// </summary>
public class VerseVMBytecodeGenerator
{
readonly List<Instruction> Instructions = new List<Instruction>();
Instruction Inst(string Name)
{
Instruction I = new Instruction(Name);
Instructions.Add(I);
return I;
}
static string Preamble()
{
StringBuilder S = new StringBuilder();
S.Append("// Copyright Epic Games, Inc. All Rights Reserved.\n\n");
S.Append("// WARNING: This code is autogenerated by VerseVMBytecodeGenerator.cs. Do not edit directly\n\n");
S.Append("#pragma once\n\n");
return S.ToString();
}
string EmitBytecodeMacroList()
{
StringBuilder S = new StringBuilder();
S.Append(Preamble());
S.Append("// IWYU pragma: private, include \"VVMBytecodeOps.h\"\n\n");
S.Append("#define VERSE_ENUM_OPS(v) \\\n");
foreach (Instruction Inst in Instructions)
{
S.Append($" v({Inst.Name}) \\\n");
}
S.Append("\n");
return S.ToString();
}
string EmitReflectionMethods(in Instruction Inst, in bool bIsSuspensionCapture)
{
StringBuilder S = new StringBuilder();
void EmitForEachOperand(in StringBuilder S, in Instruction Inst, in bool bIsSuspensionCapture, in bool bIsConst)
{
string ConstString = bIsConst ? " const" : "";
S.Append(" template <typename FunctionType>\n");
S.Append($" void ForEachOperand(FunctionType Function){ConstString}\n");
S.Append(" {\n");
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
if (Arg.Role == Role.Immediate)
{
S.Append($" for (auto& Operand : {Arg.Name})\n");
S.Append( " {\n");
S.Append($" Function({Arg.Role.ToCpp()}, Operand);\n");
S.Append( " }\n");
}
else
{
string OperandString = bIsSuspensionCapture && Arg.Role != Role.Immediate ? "Operand.Get()" : "Operand";
S.Append($" for (const auto& Operand : {Arg.Name})\n");
S.Append( " {\n");
S.Append($" Function({Arg.Role.ToCpp()}, {OperandString});\n");
S.Append( " }\n");
}
}
else
{
// If the argument is immediate, `ForEachOperand`/`ForEachOperandWithName` will operate on
// the `TWriteBarrier<T>` itself instead of `T`. This is to allow for better control over marking the
// values encapsulated within the write barriers.
string ArgString = (bIsSuspensionCapture && Arg.Role != Role.Immediate) ? $"{Arg.Name}.Get()" : $"{Arg.Name}";
S.Append($" Function({Arg.Role.ToCpp()}, {ArgString});\n");
}
}
S.Append(" }\n\n");
S.Append(" template <typename FunctionType>\n");
S.Append($" void ForEachOperandWithName(FunctionType Function){ConstString}\n");
S.Append(" {\n");
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
string OperandString = bIsSuspensionCapture ? "Operand.Get()" : "Operand";
S.Append($" for (int32 Index = 0; Index < {Arg.Name}.Num(); ++Index)\n");
S.Append(" {\n");
S.Append($" auto& Operand = {Arg.Name}[Index];\n");
S.Append($" const FString ArgName = FString::Format(TEXT(\"{{0}}{{1}}\"), {{\"{Arg.Name}\", Index}});\n");
S.Append($" Function({Arg.Role.ToCpp()}, {OperandString}, TCHAR_TO_ANSI(*ArgName));\n");
S.Append(" }\n");
}
else
{
string ArgString = (bIsSuspensionCapture && Arg.Role != Role.Immediate) ? $"{Arg.Name}.Get()" : $"{Arg.Name}";
S.Append($" Function({Arg.Role.ToCpp()}, {ArgString}, \"{Arg.Name}\");\n");
}
}
S.Append(" }\n\n");
if (!bIsSuspensionCapture)
{
S.Append(" template <typename FunctionType>\n");
S.Append($" void ForEachOperandNoUnroll(FunctionType Function){ConstString}\n");
S.Append(" {\n");
foreach (Argument Arg in Inst.Args)
{
S.Append($" Function({Arg.Role.ToCpp()}, {Arg.Name});\n");
}
S.Append(" }\n\n");
S.Append(" template <typename FunctionType>\n");
S.Append($" void ForEachOperandWithNameNoUnroll(FunctionType Function){ConstString}\n");
S.Append(" {\n");
foreach (Argument Arg in Inst.Args)
{
S.Append($" Function({Arg.Role.ToCpp()}, {Arg.Name}, \"{Arg.Name}\");\n");
}
S.Append(" }\n\n");
}
}
// Generate const and non-const versions since it is useful for being able to mutate operands (i.e. marking).
EmitForEachOperand(S, Inst, bIsSuspensionCapture, true);
EmitForEachOperand(S, Inst, bIsSuspensionCapture, false);
return S.ToString();
}
String EmitBytecodeAndCaptureDefs()
{
StringBuilder S = new StringBuilder();
S.Append(Preamble());
S.Append("// IWYU pragma: private, include \"VVMBytecodesAndCaptures.h\"\n\n");
S.Append("namespace Verse {\n");
// Emit bytecode structs.
foreach (Instruction Inst in Instructions)
{
S.Append($"struct {Inst.CppName} : public FOp");
S.Append("\n{\n");
int[] ImmediateArgsIndices = new int[Inst.Args.Count];
int[] VariadicArgsIndices = new int[Inst.Args.Count];
int NumImmediateArgs = 0;
int NumVariadicArgs = 0;
int Index = 0;
// Define fields
foreach (Argument Arg in Inst.Args)
{
if (Arg.Role == Role.Immediate)
{
ImmediateArgsIndices[NumImmediateArgs++] = Index;
}
if (Arg.Arity == Arity.Variadic)
{
VariadicArgsIndices[NumVariadicArgs++] = Index;
}
else if (Arg.Arity == Arity.Fixed && Arg.Role != Role.Immediate)
{
S.Append($" {Arg.DefCppType()} {Arg.Name};\n");
}
++Index;
}
foreach (Constant Const in Inst.Consts)
{
S.Append($" {Const.Type.ToCpp()} {Const.Name};\n");
}
if (NumVariadicArgs > 0)
{
S.Append(" // Non-Immedate Variadic arguments.\n");
}
for (int CurrentIndex = 0; CurrentIndex < NumVariadicArgs; ++CurrentIndex)
{
Argument Arg = Inst.Args[VariadicArgsIndices[CurrentIndex]];
if (Arg.Role != Role.Immediate)
{
// NOTE: (yiliang.siew) This could be raised to a location such as in `VProgram` when that
// exists and each opcode store only the index + size to index into that array, so that we don't
// need to store a separate `TArray` of operand values per-opcode struct.
S.Append($" TArray<{Arg.DefCppType()}> {Arg.Name};\n");
}
}
// We wrap these in a `TWriteBarrier` so that we can have an easy way to mark these values for GC purposes.
if (NumImmediateArgs > 0)
{
S.Append(" // Immediate arguments.\n");
}
for (int CurrentIndex = 0; CurrentIndex < NumImmediateArgs; ++CurrentIndex)
{
Argument Arg = Inst.Args[ImmediateArgsIndices[CurrentIndex]];
if (Arg.Arity == Arity.Variadic)
S.Append($" TArray<TWriteBarrier<{Arg.DefCppType()}>> {Arg.Name};\n");
else
S.Append($" TWriteBarrier<{Arg.DefCppType()}> {Arg.Name};\n");
}
S.Append("\n"); // For readability.
S.Append($" static constexpr EOpcode StaticOpcode = EOpcode::{Inst.Name};\n");
S.Append($" static constexpr bool bHasJumps = {Inst._Jumps.ToCpp()};\n\n");
// Constructor
S.Append($" {Inst.CppName}(");
string ArgumentsList = string.Join(", ", Inst.Args.Select(Arg =>
{
// Prefix with `In` to avoid any shadowing issues.
if (Arg.Role == Role.Immediate)
{
if (Arg.Arity == Arity.Variadic)
{
return $"TArray<TWriteBarrier<{Arg.DefCppType()}>>&& In{Arg.Name}";
}
else
{
return $"TWriteBarrier<{Arg.DefCppType()}>&& In{Arg.Name}";
}
}
else if (Arg.Arity == Arity.Variadic)
{
return $"TArray<{Arg.DefCppType()}>&& In{Arg.Name}";
}
else
{
return $"const {Arg.DefCppType()} In{Arg.Name}";
}
}));
S.Append(ArgumentsList);
if (Inst.Args.Count > 0 && Inst.Consts.Count > 0)
{
S.Append(", ");
}
string ConstantsList = string.Join(", ", Inst.Consts.Select(Const => $"{Const.Type.ToCpp()} {Const.Name}"));
S.Append(ConstantsList);
S.Append(")\n");
S.Append(" : FOp(StaticOpcode)\n");
foreach (Argument Arg in Inst.Args)
{
// The order here has to match because C++ initializer order must match the order of the fields.
if (Arg.Arity == Arity.Variadic || Arg.Role == Role.Immediate)
{
continue;
}
S.Append($" , {Arg.Name}(In{Arg.Name})\n");
}
foreach (Constant Const in Inst.Consts)
{
S.Append($" , {Const.Name}({Const.Name})\n");
}
for (int CurrentIndex = 0; CurrentIndex < NumVariadicArgs; ++CurrentIndex)
{
Argument Arg = Inst.Args[VariadicArgsIndices[CurrentIndex]];
if (Arg.Role != Role.Immediate)
S.Append($" , {Arg.Name}(In{Arg.Name})\n");
}
for (int CurrentIndex = 0; CurrentIndex < NumImmediateArgs; ++CurrentIndex)
{
Argument Arg = Inst.Args[ImmediateArgsIndices[CurrentIndex]];
S.Append($" , {Arg.Name}(In{Arg.Name})\n");
}
S.Append(" {}\n\n");
// Reflection methods
S.Append(EmitReflectionMethods(Inst, false));
S.Append(" template <typename FunctionType>\n");
S.Append(" FORCEINLINE void ForEachJump(FunctionType Function) const\n");
S.Append(" {\n");
foreach (Constant Const in Inst.Consts.Where(C => C.IsJump()))
{
S.Append($" Function({Const.Name});\n");
}
S.Append(" }\n");
S.Append(" template <typename FunctionType>\n");
S.Append(" FORCEINLINE void ForEachJumpWithName(FunctionType Function) const\n");
S.Append(" {\n");
foreach (Constant Const in Inst.Consts.Where(C => C.IsJump()))
{
S.Append($" Function({Const.Name}, \"{Const.Name}\");\n");
}
S.Append(" }\n");
S.Append("\n};\n");
S.Append($"static_assert(alignof({Inst.CppName}) >= 8);\n\n");
}
// Emit captures structs.
foreach (Instruction Inst in Instructions.Where(I => I._Suspends))
{
string Name = Inst.CppCapturesName;
S.Append($"struct {Name}");
S.Append("\n{\n");
// Generate the fields.
foreach (Argument Arg in Inst.Args)
{
if (Arg.Role == Role.Immediate)
{
if (Arg.Arity == Arity.Variadic)
S.Append($" TArray<TWriteBarrier<{Arg.DefCppType()}>> {Arg.Name}; // variadic immediate arguments.\n");
else
S.Append($" TWriteBarrier<{Arg.DefCppType()}> {Arg.Name}; \n");
}
else
{
if (Arg.Arity == Arity.Variadic)
S.Append($" TArray<TWriteBarrier<VValue>> {Arg.Name}; // Captured variadic arguments.\n");
else
S.Append($" TWriteBarrier<VValue> {Arg.Name}; \n");
}
}
foreach (Constant Const in Inst.Consts)
{
S.Append($" {Const.Type.ToCpp()} {Const.Name};\n");
}
if (Inst._CapturesEffectToken)
{
S.Append($" TWriteBarrier<VValue> EffectToken;\n");
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append($" TWriteBarrier<VValue> ReturnEffectToken;\n");
}
S.Append("\n");
// Generate the constructor.
{
S.Append($" {Name}(FAccessContext Context");
foreach (Argument Arg in Inst.Args)
{
if (Arg.Role == Role.Immediate)
{
if (Arg.Arity == Arity.Variadic)
S.Append($", TArray<TWriteBarrier<{Arg.DefCppType()}>>&& In{Arg.Name}");
else
S.Append($", const TWriteBarrier<{Arg.DefCppType()}>& In{Arg.Name}");
}
else
{
if (Arg.Arity == Arity.Variadic)
S.Append($", TArray<TWriteBarrier<VValue>>&& In{Arg.Name}");
else
S.Append($", VValue In{Arg.Name}");
}
}
if (Inst._CapturesEffectToken)
{
S.Append(", VValue EffectToken");
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append(", VValue ReturnEffectToken");
}
foreach (Constant Const in Inst.Consts)
{
S.Append($", {Const.Type.ToCpp()} In{Const.Name}");
}
S.Append(")\n");
string Prefix = ":";
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
S.Append($" {Prefix} {Arg.Name}(MoveTemp(In{Arg.Name}))\n");
}
else
{
if (Arg.Role == Role.Immediate)
{
S.Append($" {Prefix} {Arg.Name}(In{Arg.Name})\n");
}
else
{
S.Append($" {Prefix} {Arg.Name}(Context, In{Arg.Name})\n");
}
}
Prefix = ",";
}
if (Inst._CapturesEffectToken)
{
S.Append($" {Prefix} EffectToken(Context, EffectToken)\n");
Prefix = ",";
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append($" {Prefix} ReturnEffectToken(Context, ReturnEffectToken)\n");
Prefix = ",";
}
foreach (Constant Const in Inst.Consts)
{
S.Append($" {Prefix} {Const.Name}(In{Const.Name})\n");
}
S.Append(" {}\n");
}
S.Append("\n");
// Generate the copy constructor.
{
S.Append($" {Name}(FAccessContext Context, const {Name}& Other)\n");
string Prefix = ":";
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
S.Append($" {Prefix} {Arg.Name}(Other.{Arg.Name})\n");
}
else
{
if (Arg.Role == Role.Immediate)
{
S.Append($" {Prefix} {Arg.Name}(Other.{Arg.Name})\n");
}
else
{
S.Append($" {Prefix} {Arg.Name}(Context, Other.{Arg.Name}.Get())\n");
}
}
Prefix = ",";
}
if (Inst._CapturesEffectToken)
{
S.Append($" {Prefix} EffectToken(Context, Other.EffectToken.Get())\n");
Prefix = ", ";
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append($" {Prefix} ReturnEffectToken(Context, Other.ReturnEffectToken.Get())\n");
Prefix = ", ";
}
foreach (Constant Const in Inst.Consts)
{
S.Append($" {Prefix} {Const.Name}(Other.{Const.Name})\n");
}
S.Append(" {\n }\n");
}
S.Append("\n");
S.Append(EmitReflectionMethods(Inst, true));
S.Append("};\n\n");
}
S.Append("} // namespace Verse\n");
return S.ToString();
}
string EmitMakeCapturesFunctions()
{
StringBuilder S = new StringBuilder();
S.Append(Preamble());
foreach (Instruction Inst in Instructions.Where(I => I._Suspends))
{
S.Append($"FORCEINLINE {Inst.CppCapturesName} MakeCaptures(const {Inst.CppName}& Op)\n{{\n");
if (Inst._CapturesEffectToken)
{
S.Append(" const VValue IncomingEffectToken = EffectToken.Get(Context);\n");
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append(" const VValue ReturnEffectToken = VValue::Placeholder(VPlaceholder::New(Context, 0));\n");
S.Append(" EffectToken.Set(Context, ReturnEffectToken);\n");
}
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
if (Arg.Role == Role.Immediate)
{
S.Append($" TArray<TWriteBarrier<{Arg.DefCppType()}>> Array{Arg.Name};\n");
S.Append($" for (auto& Arg : Op.{Arg.Name})\n");
S.Append(" {\n");
S.Append($" Array{Arg.Name}.Add(Arg);\n");
S.Append(" }\n");
}
else
{
S.Append($" TArray<TWriteBarrier<VValue>> Array{Arg.Name};\n");
S.Append($" for (auto& CurrentValue : Op.{Arg.Name})\n");
S.Append(" {\n");
S.Append($" Array{Arg.Name}.Add({{Context, GetOperand(CurrentValue)}});\n");
S.Append(" }\n");
}
}
}
S.Append($" return {Inst.CppCapturesName}(Context");
foreach (Argument Arg in Inst.Args)
{
if (Arg.Arity == Arity.Variadic)
{
S.Append($", MoveTemp(Array{Arg.Name})");
}
else if (Arg.Role == Role.Immediate)
{
S.Append($", Op.{Arg.Name}");
}
else
{
S.Append($", GetOperand(Op.{Arg.Name})");
}
}
if (Inst._CapturesEffectToken)
{
S.Append(", IncomingEffectToken");
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append(", ReturnEffectToken");
}
foreach (Constant Const in Inst.Consts)
{
S.Append($", Op.{Const.Name}");
}
S.Append(");\n");
S.Append("}\n\n");
}
return S.ToString();
}
string EmitCaptureSwitch()
{
StringBuilder S = new StringBuilder();
S.Append(Preamble());
S.Append("// IWYU pragma: private, include \"VVMCaptureSwitch.h\"\n\n");
S.Append("namespace Verse {\n");
S.Append("template <typename TFunc>\n");
S.Append("void VBytecodeSuspension::CaptureSwitch(const TFunc& Func)\n");
S.Append("{\n");
S.Append(" switch (PC->Opcode)\n");
S.Append(" {\n");
foreach (Instruction Inst in Instructions.Where(I => I._Suspends))
{
S.Append($" case EOpcode::{Inst.Name}:\n");
S.Append(" {\n");
S.Append($" Func(GetCaptures<{Inst.CppCapturesName}>());\n");
S.Append(" break;\n");
S.Append(" }\n");
}
S.Append(" default:\n");
S.Append(" {\n");
S.Append(" V_DIE(\"Opcode doesn't have a captures\");\n");
S.Append(" break;\n");
S.Append(" }\n");
S.Append(" }\n");
S.Append("}\n");
S.Append("} // namespace Verse\n");
return S.ToString();
}
VerseVMBytecodeGenerator(ILogger Logger, DirectoryReference GenDirectory)
{
DefineOps();
Action<string, Func<string>> GenFile = (FileName, Method) =>
{
FileReference File = FileReference.Combine(GenDirectory, FileName);
bool bWritten = FileReference.WriteAllTextIfDifferent(File, Method());
Logger.LogDebug($"\tWriting out generated header file. Changed:{bWritten} Path:'{File}'");
};
GenFile("VVMBytecodeOps.gen.h", EmitBytecodeMacroList);
GenFile("VVMBytecodesAndCaptures.gen.h", EmitBytecodeAndCaptureDefs);
GenFile("VVMMakeCapturesFuncs.gen.h", EmitMakeCapturesFunctions);
GenFile("VVMCaptureSwitch.gen.h", EmitCaptureSwitch);
}
/// <summary>
/// Entrypoint to generate the bytecode. Generated code will go in Directory.
/// </summary>
public static void Generate(ILogger Logger, DirectoryReference Directory)
{
Logger.LogDebug($"VerseVMBytecodeGenerator.Generate, generating CPP headers in: '{Directory}'");
new VerseVMBytecodeGenerator(Logger, Directory);
}
void DefineOps()
{
string[] BinOps =
{
"Add", "Sub", "Mul", "Div", "Mod"
};
foreach (string Op in BinOps)
{
Inst(Op)
.Arg("Dest", Role.UnifyDef)
.Arg("LeftSource", Role.Use)
.Arg("RightSource", Role.Use)
.Suspends();
}
Inst("MutableAdd")
.Arg("Dest", Role.UnifyDef)
.Arg("LeftSource", Role.Use)
.Arg("RightSource", Role.Use)
.Suspends();
string[] UnaryOps =
{
"Neg",
"Query"
};
foreach (string Op in UnaryOps)
{
Inst(Op)
.Arg("Dest", Role.UnifyDef)
.Arg("Source", Role.Use)
.Suspends();
}
Inst("Err");
Inst("Move")
.Arg("Dest", Role.UnifyDef)
.Arg("Source", Role.Use);
Inst("Reset")
.Arg("Dest", Role.ClobberDef);
Inst("Jump")
.Jump("JumpOffset");
// These labels are needed for lenient execution.
// On success, EndFailureContext falls through.
// On failure, it jumps to OnFailure.
// When there are still unresolved suspensions in this failure context,
// we jump to "Done" to continue lenient execution.
Inst("BeginFailureContext")
.Jump("OnFailure");
Inst("EndFailureContext")
.Jump("Done");
Inst("BeginTask")
.Jump("OnYield");
Inst("EndTask");
Inst("Call")
.Arg("Dest", Role.UnifyDef)
.Arg("Callee", Role.Use)
.Arg("Arguments", Role.Use, Arity.Variadic)
.CapturesEffectToken()
.CreatesNewReturnEffectToken()
.Suspends();
Inst("CallNamed")
.Arg("Dest", Role.UnifyDef)
.Arg("Callee", Role.Use)
.Arg("Arguments", Role.Use, Arity.Variadic)
.Arg("NamedArguments", Role.Immediate, Arity.Variadic, "VUniqueString")
.CapturesEffectToken()
.CreatesNewReturnEffectToken()
.Suspends();
Inst("JumpIfInitialized")
.Arg("RegIdx", Role.Use)
.Jump("JumpOffset")
.Suspends();
Inst("Return")
.Arg("Value", Role.Use);
Inst("NewVar")
.Arg("Dest", Role.UnifyDef);
Inst("VarGet")
.Arg("Dest", Role.UnifyDef)
.Arg("Var", Role.Use)
.CapturesEffectToken()
.Suspends();
Inst("VarSet")
.Arg("Var", Role.Use)
.Arg("Value", Role.Use)
.CapturesEffectToken()
.Suspends();
Inst("Freeze")
.Arg("Dest", Role.UnifyDef)
.Arg("Value", Role.Use)
.CapturesEffectToken()
.Suspends();
Inst("Melt")
.Arg("Dest", Role.UnifyDef)
.Arg("Value", Role.Use)
.CapturesEffectToken()
.Suspends();
Inst("Length")
.Arg("Dest", Role.UnifyDef)
.Arg("Container", Role.Use)
.Suspends();
Inst("IndexSet")
.Arg("Container", Role.Use)
.Arg("Index", Role.Use)
.Arg("ValueToSet", Role.Use)
.CapturesEffectToken()
.Suspends();
Inst("NewArray")
.Arg("Dest", Role.UnifyDef)
.Arg("Values", Role.Use, Arity.Variadic)
.Suspends();
Inst("NewMutableArray")
.Arg("Dest", Role.UnifyDef)
.Arg("Values", Role.Use, Arity.Variadic)
.Suspends();
Inst("NewMutableArrayWithCapacity")
.Arg("Dest", Role.UnifyDef)
.Arg("Size", Role.Use)
.Suspends();
Inst("ArrayAdd")
.Arg("Container", Role.Use)
.Arg("ValueToAdd", Role.Use)
.CapturesEffectToken()
.Suspends();
// This in place converts a VMutableArray into a VArray.
// This can get away with being non-transactional because we
// call it on data structures before they become observable
// to user code.
Inst("InPlaceMakeImmutable")
.Arg("Container", Role.Use)
.Suspends();
Inst("NewOption")
.Arg("Dest", Role.UnifyDef)
.Arg("Value", Role.Use)
.Suspends();
Inst("NewMap")
.Arg("Dest", Role.UnifyDef)
.Arg("Keys", Role.Use, Arity.Variadic)
.Arg("Values", Role.Use, Arity.Variadic)
.Suspends();
Inst("MapKey")
.Arg("Dest", Role.UnifyDef)
.Arg("Map", Role.Use)
.Arg("Index", Role.Use)
.Suspends();
Inst("MapValue")
.Arg("Dest", Role.UnifyDef)
.Arg("Map", Role.Use)
.Arg("Index", Role.Use)
.Suspends();
Inst("NewClass")
.Arg("Dest", Role.UnifyDef)
.Arg("Constructor", Role.Immediate, Arity.Fixed, "VConstructor")
.Arg("Inherited", Role.Use, Arity.Variadic)
.Arg("Name", Role.Immediate, Arity.Fixed, "VUTF8String")
.Arg("Package", Role.Immediate, Arity.Fixed, "VPackage")
.Const("ClassKind", CppType.ClassKind)
.Suspends();
Inst("NewObject")
.Arg("Dest", Role.UnifyDef)
.Arg("Class", Role.Use)
.Arg("Fields", Role.Immediate, Arity.Fixed, "VUniqueStringSet")
.Arg("Values", Role.Use, Arity.Variadic)
.Suspends();
Inst("LoadField")
.Arg("Dest", Role.UnifyDef)
.Arg("Object", Role.Use)
.Arg("Name", Role.Immediate, Arity.Fixed, "VUniqueString")
.Suspends();
Inst("UnifyField")
.Arg("Object", Role.Use)
.Arg("Name", Role.Immediate, Arity.Fixed, "VUniqueString")
.Arg("Value", Role.Use)
.Suspends();
string[] ComparisonOps =
{
"Neq", "Lt", "Lte", "Gt", "Gte"
};
foreach (string Op in ComparisonOps)
{
Inst(Op)
.Arg("Dest", Role.UnifyDef)
.Arg("LeftSource", Role.Use)
.Arg("RightSource", Role.Use)
.Suspends();
}
}
}
}