Files
swift-mirror/stdlib/public/core/ThreadLocalStorage.swift
Michael Ilseman a37a823e6e [stdlib] Update non-contiguous NSStrings to Unicode 9
This adds Unicode 9 grapheme breaking support for non-contiguous
NSStrings. Non-contiguous NSStrings that don't hit our fast paths are
very rare, but should still behave identically to contiguous
strings.

We first copy a fixed number of code units into a fixed size buffer
(currently 16 in size) and try to grapheme break inside of that
buffer. This is sufficient storage for all known non-pathological
graphemes. Any graphemes larger than the buffer are handled by copying
larger portions of the string into an Array.

Test cases added, including pathological "zalgo" text that stresses
extremely long graphemes.
2017-06-28 15:35:25 -07:00

154 lines
5.6 KiB
Swift

//===--- ThreadLocalStorage.swift -----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftShims
// For testing purposes, a thread-safe counter to guarantee that destructors get
// called by pthread.
#if INTERNAL_CHECKS_ENABLED
public // @testable
let _destroyTLSCounter = _stdlib_AtomicInt()
#endif
// Thread local storage for all of the Swift standard library
//
// @moveonly/@pointeronly: shouldn't be used as a value, only through its
// pointer. Similarly, shouldn't be created, except by
// _initializeThreadLocalStorage.
//
internal struct _ThreadLocalStorage {
// TODO: might be best to absract uBreakIterator handling and caching into
// separate struct. That would also make it easier to maintain multiple ones
// and other TLS entries side-by-side.
// Save a pre-allocated UBreakIterator, as they are very expensive to set up.
// Each thread can reuse their unique break iterator, being careful to reset
// the text when it has changed (see below). Even with a naive always-reset
// policy, grapheme breaking is 30x faster when using a pre-allocated
// UBreakIterator than recreating one.
//
// private
var uBreakIterator: OpaquePointer
// TODO: Consider saving two, e.g. for character-by-character comparison
// The below cache key tries to avoid resetting uBreakIterator's text when
// operating on the same String as before. Avoiding the reset gives a 50%
// speedup on grapheme breaking.
//
// As a invalidation check, save the base address from the last used
// StringCore. We can skip resetting the uBreakIterator's text when operating
// on a given StringCore when both of these associated references/pointers are
// equal to the StringCore's. Note that the owner is weak, to force it to
// compare unequal if a new StringCore happens to be created in the same
// memory.
//
// TODO: unowned reference to string owner, base address, and _countAndFlags
// private: Should only be called by _initializeThreadLocalStorage
init(_uBreakIterator: OpaquePointer) {
self.uBreakIterator = _uBreakIterator
}
// Get the current thread's TLS pointer. On first call for a given thread,
// creates and initializes a new one.
static internal func getPointer()
-> UnsafeMutablePointer<_ThreadLocalStorage>
{
let tlsRawPtr = _swift_stdlib_pthread_getspecific(_tlsKey)
if _fastPath(tlsRawPtr != nil) {
return tlsRawPtr._unsafelyUnwrappedUnchecked.assumingMemoryBound(
to: _ThreadLocalStorage.self)
}
return _initializeThreadLocalStorage()
}
// Retrieve our thread's local uBreakIterator and set it up for the given
// StringCore.
static internal func getUBreakIterator(
for core: _StringCore
) -> OpaquePointer {
_sanityCheck(core._owner != nil || core._baseAddress != nil,
"invalid StringCore")
let corePtr: UnsafeMutablePointer<UTF16.CodeUnit> = core.startUTF16
return getUBreakIterator(
for: UnsafeBufferPointer(start: corePtr, count: core.count))
}
static internal func getUBreakIterator(
for bufPtr: UnsafeBufferPointer<UTF16.CodeUnit>
) -> OpaquePointer {
let tlsPtr = getPointer()
let brkIter = tlsPtr[0].uBreakIterator
var err = __swift_stdlib_U_ZERO_ERROR
__swift_stdlib_ubrk_setText(
brkIter, bufPtr.baseAddress!, Int32(bufPtr.count), &err)
_precondition(err.isSuccess, "unexpected ubrk_setUText failure")
return brkIter
}
}
// Destructor to register with pthreads. Responsible for deallocating any memory
// owned.
internal func _destroyTLS(_ ptr: UnsafeMutableRawPointer?) {
_sanityCheck(ptr != nil,
"_destroyTLS was called, but with nil...")
let tlsPtr = ptr!.assumingMemoryBound(to: _ThreadLocalStorage.self)
__swift_stdlib_ubrk_close(tlsPtr[0].uBreakIterator)
tlsPtr.deinitialize(count: 1)
tlsPtr.deallocate(capacity: 1)
#if INTERNAL_CHECKS_ENABLED
// Log the fact we've destroyed our storage
_destroyTLSCounter.fetchAndAdd(1)
#endif
}
// Lazily created global key for use with pthread TLS
internal let _tlsKey: __swift_pthread_key_t = {
let sentinelValue = __swift_pthread_key_t.max
var key: __swift_pthread_key_t = sentinelValue
let success = _swift_stdlib_pthread_key_create(&key, _destroyTLS)
_sanityCheck(success == 0, "somehow failed to create TLS key")
_sanityCheck(key != sentinelValue, "Didn't make a new key")
return key
}()
@inline(never)
internal func _initializeThreadLocalStorage()
-> UnsafeMutablePointer<_ThreadLocalStorage>
{
_sanityCheck(_swift_stdlib_pthread_getspecific(_tlsKey) == nil,
"already initialized")
// Create and initialize one.
var err = __swift_stdlib_U_ZERO_ERROR
let newUBreakIterator = __swift_stdlib_ubrk_open(
/*type:*/ __swift_stdlib_UBRK_CHARACTER, /*locale:*/ nil,
/*text:*/ nil, /*textLength:*/ 0, /*status:*/ &err)
_precondition(err.isSuccess, "unexpected ubrk_open failure")
let tlsPtr: UnsafeMutablePointer<_ThreadLocalStorage>
= UnsafeMutablePointer<_ThreadLocalStorage>.allocate(
capacity: 1
)
tlsPtr.initialize(
to: _ThreadLocalStorage(_uBreakIterator: newUBreakIterator)
)
let success = _swift_stdlib_pthread_setspecific(_tlsKey, tlsPtr)
_sanityCheck(success == 0, "setspecific failed")
return tlsPtr
}