Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/System/VerseVMBytecodeGenerator.cs
jared cotton e4644f0e4f SOL-4828 - "Split container types between being mutable and immutable"
Split VMap into mutable and immutable versions

(preflight failure looked spurious... ie timeout on cook worker. Ready to backout if this is a real issue somehow caused by my CL but thought this unlikely)

[CL 29994701 by jared cotton in ue5-main branch]
2023-11-29 14:05:35 -05:00

905 lines
24 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,
}
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";
}
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)
{
string OperandString = bIsSuspensionCapture ? "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");
}
// 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(" // Variadic arguments.\n");
}
for (int CurrentIndex = 0; CurrentIndex < NumVariadicArgs; ++CurrentIndex)
{
Argument Arg = Inst.Args[VariadicArgsIndices[CurrentIndex]];
// 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)
{
throw new ArgumentException("Variadic immediate arguments are not currently supported!");
}
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)
{
// TODO: Support variadic immediate arguments.
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);
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]];
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.Arity == Arity.Variadic)
{
S.Append($" TArray<TWriteBarrier<VValue>> {Arg.Name}; // Captured variadic arguments.\n");
}
else if (Arg.Role == Role.Immediate)
{
S.Append($" TWriteBarrier<{Arg.DefCppType()}> {Arg.Name}; \n");
}
else
{
S.Append($" TWriteBarrier<VValue> {Arg.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.Arity == Arity.Variadic)
{
S.Append($", TArray<TWriteBarrier<VValue>>&& In{Arg.Name}");
}
else if (Arg.Role == Role.Immediate)
{
S.Append($", const TWriteBarrier<{Arg.DefCppType()}>& In{Arg.Name}");
}
else
{
S.Append($", VValue In{Arg.Name}");
}
}
if (Inst._CapturesEffectToken)
{
S.Append(", VValue EffectToken");
}
if (Inst._CreatesNewReturnEffectToken)
{
S.Append(", VValue ReturnEffectToken");
}
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 = ",";
}
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 = ", ";
}
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)
{
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");
}
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("Call")
.Arg("Dest", Role.UnifyDef)
.Arg("Callee", Role.Use)
.Arg("Arguments", Role.Use, Arity.Variadic)
.CapturesEffectToken()
.CreatesNewReturnEffectToken()
.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("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)
.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("NewMutableMap")
.Arg("Dest", Role.UnifyDef)
.Arg("Keys", Role.Use, Arity.Variadic)
.Arg("Values", Role.Use, Arity.Variadic)
.Suspends();
Inst("NewMutableMapWithCapacity")
.Arg("Dest", Role.UnifyDef)
.Arg("Size", Role.Use)
.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)
.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();
}
}
}
}