// Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; using System.Text; #pragma warning disable SA1509 // Opening braces should not be preceded by blank line namespace MessagePack.Internal { // Key = long, Value = int for UTF8String Dictionary /// /// This code is used by dynamically generated code as well as AOT generated code, /// and thus must be public for the "C# generated and compiled into saved assembly" scenario. /// public class AutomataDictionary : IEnumerable> { private readonly AutomataNode root; public AutomataDictionary() { this.root = new AutomataNode(0); } public void Add(string str, int value) { ReadOnlySpan bytes = Encoding.UTF8.GetBytes(str); AutomataNode node = this.root; while (bytes.Length > 0) { var key = AutomataKeyGen.GetKey(ref bytes); if (bytes.Length == 0) { node = node.Add(key, value, str); } else { node = node.Add(key); } } } public bool TryGetValue(in ReadOnlySequence bytes, out int value) => this.TryGetValue(bytes.ToArray(), out value); public bool TryGetValue(ReadOnlySpan bytes, out int value) { AutomataNode node = this.root; while (bytes.Length > 0 && node != null) { node = node.SearchNext(ref bytes); } if (node == null) { value = -1; return false; } else { value = node.Value; return true; } } // for debugging public override string ToString() { var sb = new StringBuilder(); ToStringCore(this.root.YieldChildren(), sb, 0); return sb.ToString(); } private static void ToStringCore(IEnumerable nexts, StringBuilder sb, int depth) { foreach (AutomataNode item in nexts) { if (depth != 0) { sb.Append(' ', depth * 2); } sb.Append("[" + item.Key + "]"); if (item.Value != -1) { sb.Append("(" + item.OriginalKey + ")"); sb.Append(" = "); sb.Append(item.Value); } sb.AppendLine(); ToStringCore(item.YieldChildren(), sb, depth + 1); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public IEnumerator> GetEnumerator() { return YieldCore(this.root.YieldChildren()).GetEnumerator(); } private static IEnumerable> YieldCore(IEnumerable nexts) { foreach (AutomataNode item in nexts) { if (item.Value != -1) { yield return new KeyValuePair(item.OriginalKey, item.Value); } foreach (KeyValuePair x in YieldCore(item.YieldChildren())) { yield return x; } } } /* IL Emit */ #if !NET_STANDARD_2_0 public void EmitMatch(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action> onFound, Action onNotFound) { this.root.EmitSearchNext(il, bytesSpan, key, onFound, onNotFound); } #endif private class AutomataNode : IComparable { #pragma warning disable SA1401 // Fields should be private internal ulong Key; internal int Value; internal string OriginalKey; #pragma warning restore SA1401 // Fields should be private private AutomataNode[] nexts; private ulong[] nextKeys; private int count; public bool HasChildren { get { return this.count != 0; } } public AutomataNode(ulong key) { this.Key = key; this.Value = -1; this.nexts = Array.Empty(); this.nextKeys = Array.Empty(); this.count = 0; this.OriginalKey = null; } public AutomataNode Add(ulong key) { var index = Array.BinarySearch(this.nextKeys, 0, this.count, key); if (index < 0) { if (this.nexts.Length == this.count) { Array.Resize(ref this.nexts, (this.count == 0) ? 4 : (this.count * 2)); Array.Resize(ref this.nextKeys, (this.count == 0) ? 4 : (this.count * 2)); } this.count++; var nextNode = new AutomataNode(key); this.nexts[this.count - 1] = nextNode; this.nextKeys[this.count - 1] = key; Array.Sort(this.nexts, 0, this.count); Array.Sort(this.nextKeys, 0, this.count); return nextNode; } else { return this.nexts[index]; } } public AutomataNode Add(ulong key, int value, string originalKey) { AutomataNode v = this.Add(key); v.Value = value; v.OriginalKey = originalKey; return v; } public AutomataNode SearchNext(ref ReadOnlySpan value) { var key = AutomataKeyGen.GetKey(ref value); if (this.count < 4) { // linear search for (int i = 0; i < this.count; i++) { if (this.nextKeys[i] == key) { return this.nexts[i]; } } } else { // binary search var index = BinarySearch(this.nextKeys, 0, this.count, key); if (index >= 0) { return this.nexts[index]; } } return null; } internal static int BinarySearch(ulong[] array, int index, int length, ulong value) { int lo = index; int hi = index + length - 1; while (lo <= hi) { int i = lo + ((hi - lo) >> 1); var arrayValue = array[i]; int order; if (arrayValue < value) { order = -1; } else if (arrayValue > value) { order = 1; } else { order = 0; } if (order == 0) { return i; } if (order < 0) { lo = i + 1; } else { hi = i - 1; } } return ~lo; } public int CompareTo(AutomataNode other) { return this.Key.CompareTo(other.Key); } public IEnumerable YieldChildren() { for (int i = 0; i < this.count; i++) { yield return this.nexts[i]; } } #if !NET_STANDARD_2_0 // SearchNext(ref ReadOnlySpan bytes) public void EmitSearchNext(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action> onFound, Action onNotFound) { // key = AutomataKeyGen.GetKey(ref bytesSpan); il.EmitLdloca(bytesSpan); il.EmitCall(AutomataKeyGen.GetKeyMethod); il.EmitStloc(key); // match children. EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, this.nexts, this.count); } private static void EmitSearchNextCore(ILGenerator il, LocalBuilder bytesSpan, LocalBuilder key, Action> onFound, Action onNotFound, AutomataNode[] nexts, int count) { if (count < 4) { // linear-search AutomataNode[] valueExists = nexts.Take(count).Where(x => x.Value != -1).ToArray(); AutomataNode[] childrenExists = nexts.Take(count).Where(x => x.HasChildren).ToArray(); Label gotoSearchNext = il.DefineLabel(); Label gotoNotFound = il.DefineLabel(); { // bytesSpan.Length il.EmitLdloca(bytesSpan); il.EmitCall(typeof(ReadOnlySpan).GetRuntimeProperty(nameof(ReadOnlySpan.Length)).GetMethod); if (childrenExists.Length != 0 && valueExists.Length == 0) { il.Emit(OpCodes.Brfalse, gotoNotFound); // if(bytesSpan.Length == 0) } else { il.Emit(OpCodes.Brtrue, gotoSearchNext); // if(bytesSpan.Length != 0) } } { Label[] ifValueNexts = Enumerable.Range(0, Math.Max(valueExists.Length - 1, 0)).Select(_ => il.DefineLabel()).ToArray(); for (int i = 0; i < valueExists.Length; i++) { Label notFoundLabel = il.DefineLabel(); if (i != 0) { il.MarkLabel(ifValueNexts[i - 1]); } il.EmitLdloc(key); il.EmitULong(valueExists[i].Key); il.Emit(OpCodes.Bne_Un, notFoundLabel); // found onFound(new KeyValuePair(valueExists[i].OriginalKey, valueExists[i].Value)); // notfound il.MarkLabel(notFoundLabel); if (i != valueExists.Length - 1) { il.Emit(OpCodes.Br, ifValueNexts[i]); } else { onNotFound(); } } } il.MarkLabel(gotoSearchNext); Label[] ifRecNext = Enumerable.Range(0, Math.Max(childrenExists.Length - 1, 0)).Select(_ => il.DefineLabel()).ToArray(); for (int i = 0; i < childrenExists.Length; i++) { Label notFoundLabel = il.DefineLabel(); if (i != 0) { il.MarkLabel(ifRecNext[i - 1]); } il.EmitLdloc(key); il.EmitULong(childrenExists[i].Key); il.Emit(OpCodes.Bne_Un, notFoundLabel); // found childrenExists[i].EmitSearchNext(il, bytesSpan, key, onFound, onNotFound); // notfound il.MarkLabel(notFoundLabel); if (i != childrenExists.Length - 1) { il.Emit(OpCodes.Br, ifRecNext[i]); } else { onNotFound(); } } il.MarkLabel(gotoNotFound); onNotFound(); } else { // binary-search var midline = count / 2; var mid = nexts[midline].Key; AutomataNode[] l = nexts.Take(count).Take(midline).ToArray(); AutomataNode[] r = nexts.Take(count).Skip(midline).ToArray(); Label gotoRight = il.DefineLabel(); // if(key < mid) il.EmitLdloc(key); il.EmitULong(mid); il.Emit(OpCodes.Bge_Un, gotoRight); EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, l, l.Length); // else il.MarkLabel(gotoRight); EmitSearchNextCore(il, bytesSpan, key, onFound, onNotFound, r, r.Length); } } #endif } } /// /// This is used by dynamically generated code. It can be made internal after we enable our dynamic assemblies to access internals. /// But that trick may require net46, so maybe we should leave this as public. /// public static class AutomataKeyGen { public static readonly MethodInfo GetKeyMethod = typeof(AutomataKeyGen).GetRuntimeMethod(nameof(GetKey), new[] { typeof(ReadOnlySpan).MakeByRefType() }); public static ulong GetKey(ref ReadOnlySpan span) { ulong key; unchecked { if (span.Length >= 8) { key = SafeBitConverter.ToUInt64(span); span = span.Slice(8); } else { switch (span.Length) { case 1: { key = span[0]; span = span.Slice(1); break; } case 2: { key = SafeBitConverter.ToUInt16(span); span = span.Slice(2); break; } case 3: { var a = span[0]; var b = SafeBitConverter.ToUInt16(span.Slice(1)); key = a | (ulong)b << 8; span = span.Slice(3); break; } case 4: { key = SafeBitConverter.ToUInt32(span); span = span.Slice(4); break; } case 5: { var a = span[0]; var b = SafeBitConverter.ToUInt32(span.Slice(1)); key = a | (ulong)b << 8; span = span.Slice(5); break; } case 6: { ulong a = SafeBitConverter.ToUInt16(span); ulong b = SafeBitConverter.ToUInt32(span.Slice(2)); key = a | (b << 16); span = span.Slice(6); break; } case 7: { var a = span[0]; var b = SafeBitConverter.ToUInt16(span.Slice(1)); var c = SafeBitConverter.ToUInt32(span.Slice(3)); key = a | (ulong)b << 8 | (ulong)c << 24; span = span.Slice(7); break; } default: throw new MessagePackSerializationException("Not Supported Length"); } } return key; } } } }