// 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()); } } }