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