Files
UnrealEngineUWP/Engine/Source/Programs/Shared/EpicGames.BuildGraph/BgCompiler.cs
Ben Marsh 38f3bc55ef BuildGraph: Various VM improvements.
* Added support for deserializing BgObject types directly into native classes.
* Removed opcodes for creating graph structures. These are now created in user code from BgObject types.
* Removed BgNodeSpecBuilder. BgNode objects can now be modified directly (returning a modified copy).
* Added concrete types for option parameters. The VM now keeps track of any parameters for evaluated options, allowing them to be added into the graph definition.
* Order dependencies now take nodes rather than outputs.
* Added explicit support for native thunks, whose bindings are saved to a sideband channel during compilation and referenced in bytecode as an index. This generalizes code that was previously specific to node definitions.
* Added a name table to bytecode, to optimize situations where we reference the same string mulitple times.

#preflight 62bf3c583f0d6beee2e8f4a6

[CL 20918762 by Ben Marsh in ue5-main branch]
2022-07-01 14:47:54 -04:00

243 lines
6.6 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using EpicGames.BuildGraph.Expressions;
using EpicGames.Core;
using EpicGames.Serialization;
using Microsoft.Extensions.Logging;
namespace EpicGames.BuildGraph
{
/// <summary>
/// Version numbers for bytecode streams
/// </summary>
enum BgBytecodeVersion
{
Current = 0,
}
/// <summary>
/// Helper class for writing BuildGraph bytecode to a buffer.
/// </summary>
public static class BgCompiler
{
class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
public new bool Equals([AllowNull] object x, [AllowNull] object y) => ReferenceEquals(x, y);
public int GetHashCode([DisallowNull] object obj) => RuntimeHelpers.GetHashCode(obj);
}
class FragmentCollector : BgBytecodeWriter
{
readonly HashSet<BgExpr> _uniqueExprs = new HashSet<BgExpr>(ReferenceEqualityComparer.Instance);
readonly List<BgExpr> _fragments;
readonly Dictionary<BgExpr, int> _exprToFragmentIndex;
public FragmentCollector(List<BgExpr> fragments, Dictionary<BgExpr, int> exprToFragmentIdx)
{
_fragments = fragments;
_exprToFragmentIndex = exprToFragmentIdx;
}
/// <inheritdoc/>
public override void WriteExpr(BgExpr expr)
{
if ((expr.Flags & BgExprFlags.ForceFragment) != 0 && false)
{
RegisterFragment(expr);
}
else if (_uniqueExprs.Add(expr) || (expr.Flags & BgExprFlags.NotInterned) != 0)
{
expr.Write(this);
}
else
{
RegisterFragment(expr);
}
}
/// <inheritdoc/>
public override void WriteExprAsFragment(BgExpr expr)
{
RegisterFragment(expr);
if (_uniqueExprs.Add(expr))
{
expr.Write(this);
}
}
/// <summary>
/// Registers an expression for compilation into a new fragment
/// </summary>
/// <param name="expr"></param>
void RegisterFragment(BgExpr expr)
{
if (!_exprToFragmentIndex.ContainsKey(expr))
{
int index = _exprToFragmentIndex.Count;
_fragments.Add(expr);
_exprToFragmentIndex[expr] = index;
}
}
/// <inheritdoc/>
public override void WriteOpcode(BgOpcode opcode) { }
/// <inheritdoc/>
public override void WriteName(string str) { }
/// <inheritdoc/>
public override void WriteString(string str) { }
/// <inheritdoc/>
public override void WriteSignedInteger(long value) { }
/// <inheritdoc/>
public override void WriteUnsignedInteger(ulong value) { }
/// <inheritdoc/>
public override void WriteThunk(BgThunkDef handler) { }
}
class ForwardWriter : BgBytecodeWriter
{
readonly ByteArrayBuilder _builder;
readonly Dictionary<BgExpr, int> _exprToFragmentIdx;
readonly List<string> _names;
readonly List<BgThunkDef> _thunks;
readonly Dictionary<string, int> _nameToIndex = new Dictionary<string, int>();
public ForwardWriter(ByteArrayBuilder builder, Dictionary<BgExpr, int> exprToFragmentIdx, List<string> names, List<BgThunkDef> thunks)
{
_builder = builder;
_exprToFragmentIdx = exprToFragmentIdx;
_names = names;
_thunks = thunks;
for (int idx = 0; idx < _names.Count; idx++)
{
_nameToIndex[_names[idx]] = idx;
}
}
/// <inheritdoc/>
public override void WriteExpr(BgExpr expr)
{
int index;
if (_exprToFragmentIdx.TryGetValue(expr, out index))
{
WriteOpcode(BgOpcode.Jump);
WriteUnsignedInteger((ulong)index);
}
else
{
expr.Write(this);
}
}
/// <inheritdoc/>
public override void WriteExprAsFragment(BgExpr expr)
{
WriteUnsignedInteger(_exprToFragmentIdx[expr]);
}
/// <inheritdoc/>
public override void WriteOpcode(BgOpcode opcode)
{
_builder.WriteByte((byte)opcode);
}
/// <inheritdoc/>
public override void WriteName(string name)
{
int index;
if (!_nameToIndex.TryGetValue(name, out index))
{
index = _names.Count;
_names.Add(name);
_nameToIndex.Add(name, index);
}
WriteUnsignedInteger((ulong)index);
}
/// <inheritdoc/>
public override void WriteString(string str)
{
int textLength = Encoding.UTF8.GetByteCount(str);
int lengthLength = VarInt.MeasureUnsigned(textLength);
Span<byte> buffer = _builder.GetWritableSpan(lengthLength + textLength);
VarInt.WriteUnsigned(buffer, textLength);
Encoding.UTF8.GetBytes(str, buffer.Slice(lengthLength));
}
/// <inheritdoc/>
public override void WriteSignedInteger(long value) => _builder.WriteSignedVarInt(value);
/// <inheritdoc/>
public override void WriteUnsignedInteger(ulong value) => _builder.WriteUnsignedVarInt(value);
/// <inheritdoc/>
public override void WriteThunk(BgThunkDef thunk)
{
_builder.WriteUnsignedVarInt(_thunks.Count);
_thunks.Add(thunk);
}
}
/// <summary>
/// Compiles the given expression into bytecode
/// </summary>
/// <param name="expr">Expression to compile</param>
/// <returns>Compiled bytecode for the expression, suitable for passing to <see cref="BgInterpreter"/></returns>
public static (byte[], BgThunkDef[]) Compile(BgExpr expr)
{
List<BgExpr> fragments = new List<BgExpr>();
Dictionary<BgExpr, int> exprToFragmentIndex = new Dictionary<BgExpr, int>(ReferenceEqualityComparer.Instance);
// Figure out which expressions need to be compiled into separate fragments
FragmentCollector collector = new FragmentCollector(fragments, exprToFragmentIndex);
collector.WriteExprAsFragment(expr);
// Write all the fragments
ByteArrayBuilder code = new ByteArrayBuilder();
List<int> fragmentLengths = new List<int>(fragments.Count);
List<BgThunkDef> thunks = new List<BgThunkDef>();
List<string> names = new List<string>();
ForwardWriter writer = new ForwardWriter(code, exprToFragmentIndex, names, thunks);
for (int idx = 0; idx < fragments.Count; idx++)
{
int fragmentOffset = code.Length;
fragments[idx].Write(writer);
fragmentLengths.Add(code.Length - fragmentOffset);
}
// Create the header
ByteArrayBuilder header = new ByteArrayBuilder();
header.WriteUnsignedVarInt((int)BgBytecodeVersion.Current);
header.WriteVariableLengthArray(names, x => header.WriteString(x));
header.WriteVariableLengthArray(fragmentLengths, x => header.WriteUnsignedVarInt(x));
// Append them together
byte[] output = new byte[header.Length + code.Length];
header.CopyTo(output);
code.CopyTo(output.AsSpan(header.Length));
return (output, thunks.ToArray());
}
}
}