Beispiel #1
0
        private StringId AddStringToBuffer <T>(T seg) where T : struct, ICharSpan <T>
        {
            Contract.Requires(IsValid());
            Contract.Ensures(Contract.Result <StringId>().IsValid);

            // Not in the set, so get some space to hold the string
            //
            // Note that this call is invoked in a racy setting. It's possible for multiple calls to execute concurrently and
            // try to insert the same string N times. We allow this and end up just wasting space in the table when this happens.
            // In this context, m_count thus accounts for the total number of strings inserted into the table, and not the potential
            // smaller of logical strings known to the table.
            bool longString = seg.Length >= 255;

            int space = 0;

            // see if the string is pure ASCII
            bool isAscii = seg.OnlyContains8BitChars;

            if (isAscii)
            {
                // stored as bytes
                space += seg.Length;
            }
            else
            {
                // stored as UTF-16
                space += (seg.Length * 2) + 1; // *2 for UTF-16, +1 for the 'switch to unicode' marker byte
            }

            // count how many strings are in the buffer
            Interlocked.Increment(ref m_count);

            if (space > s_largeStringBufferThreshold && m_largeStringBuffer.TryReserveSlot(out var largeStringIndex))
            {
                var buffer = new byte[space];
                WriteBytes(seg, isAscii, buffer, 0);
                m_largeStringBuffer[largeStringIndex] = buffer;
                return(ComputeStringId(LargeStringBufferNum, offset: largeStringIndex));
            }

            // if the length >= 255 then we store it as a marker byte followed by a 4-byte length value
            space += longString ? 5 : 1;

            int byteIndex;
            int bufferNum;

            // loop until we find a suitable location to copy the string to
            while (true)
            {
                // get the next possible location for the string
                int current = Volatile.Read(ref m_nextId);
                bufferNum = (current >> BytesPerBufferBits) & NumByteBuffersMask;
                byteIndex = current & BytesPerBufferMask;

                // is the available space big enough?
                if (space < BytesPerBuffer - byteIndex)
                {
                    // there's room in the buffer so try to claim it
                    int next = (bufferNum << BytesPerBufferBits) | (byteIndex + space);
                    if (Interlocked.CompareExchange(ref m_nextId, next, current) == current)
                    {
                        // got some room, now go fill it in
                        break;
                    }

                    // go try again...
                    continue;
                }

                // the string doesn't fit in the current buffer, we need a new buffer
                int newBufferSize = BytesPerBuffer;
                if (space > BytesPerBuffer)
                {
                    newBufferSize = space;
                }

                bufferNum++;

                // Make sure we don't overflow the buffer we're indexing into
                if (bufferNum >= NumByteBuffers)
                {
                    Contract.Assert(false, $"Exceeded the number of ByteBuffers allowed in this StringTable: {bufferNum} >= {NumByteBuffers}");
                }

                lock (m_byteBuffers)
                {
                    if (m_byteBuffers[bufferNum] != null)
                    {
                        // somebody racily beat us and allocated this buffer, so just retry the whole thing from scratch
                        continue;
                    }

                    // allocate a new buffer
                    m_byteBuffers[bufferNum] = new byte[newBufferSize];

                    if (space >= BytesPerBuffer)
                    {
                        // force writing into a fresh buffer
                        Volatile.Write(ref m_nextId, (bufferNum << BytesPerBufferBits) | BytesPerBufferMask);
                    }
                    else
                    {
                        // force writing into this new buffer
                        Volatile.Write(ref m_nextId, (bufferNum << BytesPerBufferBits) | space);
                    }

                    // go write the string at the base of the new buffer
                    byteIndex = 0;
                    break;
                }
            }

            var stringId = ComputeStringId(bufferNum, offset: byteIndex);

            // now copy the string data into the buffer
            byte[] currentBuffer = m_byteBuffers[bufferNum];
            if (longString)
            {
                currentBuffer[byteIndex++] = LongStringMarker;
                Bits.WriteInt32(currentBuffer, ref byteIndex, seg.Length);
            }
            else
            {
                currentBuffer[byteIndex++] = (byte)seg.Length;
            }

            WriteBytes(seg, isAscii, currentBuffer, byteIndex);
            return(stringId);
        }