You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
A new VTask object tracks a chain of the VFrames, from the point where the task was spawned to the point where it suspended. This task boundary is recorded by a pair of BeginTask/EndTask instructions. Individual <suspends> function calls in the new VM do not have distinct tasks, and <suspends> does not change how bytecode is generated. Native functions may suspend the current task by returning a new FOpResult of kind Yield, at which point the current PC is saved to the VTask, its VFrames are detached from the stack, and execution continues in the parent task. Native code may resume a task with a new VTask::ResumeInTransaction API, which re-attaches these VFrames to the top of the stack and continues execution from the saved PC. VNI functions continue to use the verse::continuation API, which now wraps a VTask pointer and thus must be accounted for during GC tracing. #rb Markus.Breyer, saam.barati #okforversepublic [CL 30978163 by russell johnston in ue5-main branch]
911 lines
24 KiB
C#
911 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("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("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)
|
|
.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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|