// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using EpicGames.Core;
using EpicGames.Serialization;
namespace EpicGames.BuildGraph
{
///
/// Version numbers for bytecode streams
///
enum BgBytecodeVersion
{
Current = 0,
}
///
/// Helper class for writing BuildGraph bytecode to a buffer.
///
public static class BgCompiler
{
class ReferenceEqualityComparer : IEqualityComparer
{
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
public new bool Equals([AllowNull] object x, [AllowNull] object y) => ReferenceEquals(x, y);
[SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1024:Compare symbols correctly", Justification = "")]
public int GetHashCode([DisallowNull] object obj) => RuntimeHelpers.GetHashCode(obj);
}
class FragmentCollector : BgBytecodeWriter
{
readonly HashSet _uniqueExprs = new HashSet(ReferenceEqualityComparer.Instance);
readonly List _fragments;
readonly Dictionary _exprToFragmentIndex;
public FragmentCollector(List fragments, Dictionary exprToFragmentIdx)
{
_fragments = fragments;
_exprToFragmentIndex = exprToFragmentIdx;
}
///
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);
}
}
///
public override void WriteExprAsFragment(BgExpr expr)
{
RegisterFragment(expr);
if (_uniqueExprs.Add(expr))
{
expr.Write(this);
}
}
///
/// Registers an expression for compilation into a new fragment
///
///
void RegisterFragment(BgExpr expr)
{
if (!_exprToFragmentIndex.ContainsKey(expr))
{
int index = _exprToFragmentIndex.Count;
_fragments.Add(expr);
_exprToFragmentIndex[expr] = index;
}
}
///
public override void WriteOpcode(BgOpcode opcode) { }
///
public override void WriteName(string str) { }
///
public override void WriteString(string str) { }
///
public override void WriteSignedInteger(long value) { }
///
public override void WriteUnsignedInteger(ulong value) { }
///
public override void WriteThunk(BgThunkDef handler) { }
}
class ForwardWriter : BgBytecodeWriter
{
readonly ByteArrayBuilder _builder;
readonly Dictionary _exprToFragmentIdx;
readonly List _names;
readonly List _thunks;
readonly Dictionary _nameToIndex = new Dictionary();
public ForwardWriter(ByteArrayBuilder builder, Dictionary exprToFragmentIdx, List names, List thunks)
{
_builder = builder;
_exprToFragmentIdx = exprToFragmentIdx;
_names = names;
_thunks = thunks;
for (int idx = 0; idx < _names.Count; idx++)
{
_nameToIndex[_names[idx]] = idx;
}
}
///
public override void WriteExpr(BgExpr expr)
{
int index;
if (_exprToFragmentIdx.TryGetValue(expr, out index))
{
WriteOpcode(BgOpcode.Jump);
WriteUnsignedInteger((ulong)index);
}
else
{
expr.Write(this);
}
}
///
public override void WriteExprAsFragment(BgExpr expr)
{
WriteUnsignedInteger(_exprToFragmentIdx[expr]);
}
///
public override void WriteOpcode(BgOpcode opcode)
{
_builder.WriteUInt8((byte)opcode);
}
///
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);
}
///
public override void WriteString(string str)
{
int textLength = Encoding.UTF8.GetByteCount(str);
int lengthLength = VarInt.MeasureUnsigned(textLength);
Span buffer = _builder.GetSpanAndAdvance(lengthLength + textLength);
VarInt.WriteUnsigned(buffer, textLength);
Encoding.UTF8.GetBytes(str, buffer.Slice(lengthLength));
}
///
public override void WriteSignedInteger(long value) => _builder.WriteSignedVarInt(value);
///
public override void WriteUnsignedInteger(ulong value) => _builder.WriteUnsignedVarInt(value);
///
public override void WriteThunk(BgThunkDef thunk)
{
_builder.WriteUnsignedVarInt(_thunks.Count);
_thunks.Add(thunk);
}
}
///
/// Compiles the given expression into bytecode
///
/// Expression to compile
/// Compiled bytecode for the expression, suitable for passing to
public static (byte[], BgThunkDef[]) Compile(BgExpr expr)
{
List fragments = new List();
Dictionary exprToFragmentIndex = new Dictionary(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 fragmentLengths = new List(fragments.Count);
List thunks = new List();
List names = new List();
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());
}
}
}