# Handle WASI lack of support for and . # # WASI issue: https://github.com/WebAssembly/wasi-sdk/issues/180 diff --git a/intl/icu/source/common/putilimp.h b/intl/icu/source/common/putilimp.h --- a/intl/icu/source/common/putilimp.h +++ b/intl/icu/source/common/putilimp.h @@ -105,10 +105,12 @@ typedef size_t uintptr_t; #endif #elif U_PLATFORM == U_PF_OS400 /* not defined */ #elif U_PLATFORM == U_PF_HAIKU /* not defined */ +#elif defined(__wasi__) + /* not defined */ #else # define U_TZSET tzset #endif #if defined(U_TIMEZONE) || defined(U_HAVE_TIMEZONE) @@ -130,10 +132,12 @@ typedef size_t uintptr_t; /* not defined */ #elif U_PLATFORM == U_PF_OS400 /* not defined */ #elif U_PLATFORM == U_PF_IPHONE /* not defined */ +#elif defined(__wasi__) + /* not defined */ #else # define U_TIMEZONE timezone #endif #if defined(U_TZNAME) || defined(U_HAVE_TZNAME) @@ -145,10 +149,12 @@ typedef size_t uintptr_t; #endif #elif U_PLATFORM == U_PF_OS400 /* not defined */ #elif U_PLATFORM == U_PF_HAIKU /* not defined, (well it is but a loop back to icu) */ +#elif defined(__wasi__) + /* not defined */ #else # define U_TZNAME tzname #endif #ifdef U_HAVE_MMAP diff --git a/intl/icu/source/common/umapfile.h b/intl/icu/source/common/umapfile.h --- a/intl/icu/source/common/umapfile.h +++ b/intl/icu/source/common/umapfile.h @@ -38,10 +38,12 @@ U_CFUNC void uprv_unmapFile(UDataMemory #define MAP_POSIX 2 #define MAP_STDIO 3 #if UCONFIG_NO_FILE_IO # define MAP_IMPLEMENTATION MAP_NONE +#elif defined(__wasi__) +# define MAP_IMPLEMENTATION MAP_STDIO #elif U_PLATFORM_USES_ONLY_WIN32_API # define MAP_IMPLEMENTATION MAP_WIN32 #elif U_HAVE_MMAP || U_PLATFORM == U_PF_OS390 # define MAP_IMPLEMENTATION MAP_POSIX #else /* unknown platform, no memory map implementation: use stdio.h and uprv_malloc() instead */ diff --git a/intl/icu/source/common/umutex.cpp b/intl/icu/source/common/umutex.cpp --- a/intl/icu/source/common/umutex.cpp +++ b/intl/icu/source/common/umutex.cpp @@ -41,10 +41,11 @@ U_NAMESPACE_BEGIN * * ICU Mutex wrappers. * *************************************************************************************************/ +#ifndef __wasi__ namespace { std::mutex *initMutex; std::condition_variable *initCondition; // The ICU global mutex. @@ -53,32 +54,38 @@ UMutex globalMutex; std::once_flag initFlag; std::once_flag *pInitFlag = &initFlag; } // Anonymous namespace +#endif U_CDECL_BEGIN static UBool U_CALLCONV umtx_cleanup() { +#ifndef __wasi__ initMutex->~mutex(); initCondition->~condition_variable(); UMutex::cleanup(); // Reset the once_flag, by destructing it and creating a fresh one in its place. // Do not use this trick anywhere else in ICU; use umtx_initOnce, not std::call_once(). pInitFlag->~once_flag(); pInitFlag = new(&initFlag) std::once_flag(); +#endif return true; } static void U_CALLCONV umtx_init() { +#ifndef __wasi__ initMutex = STATIC_NEW(std::mutex); initCondition = STATIC_NEW(std::condition_variable); ucln_common_registerCleanup(UCLN_COMMON_MUTEX, umtx_cleanup); +#endif } U_CDECL_END +#ifndef __wasi__ std::mutex *UMutex::getMutex() { std::mutex *retPtr = fMutex.load(std::memory_order_acquire); if (retPtr == nullptr) { std::call_once(*pInitFlag, umtx_init); std::lock_guard guard(*initMutex); @@ -91,41 +98,48 @@ std::mutex *UMutex::getMutex() { } } U_ASSERT(retPtr != nullptr); return retPtr; } +#endif UMutex *UMutex::gListHead = nullptr; void UMutex::cleanup() { UMutex *next = nullptr; for (UMutex *m = gListHead; m != nullptr; m = next) { +#ifndef __wasi__ (*m->fMutex).~mutex(); m->fMutex = nullptr; +#endif next = m->fListLink; m->fListLink = nullptr; } gListHead = nullptr; } U_CAPI void U_EXPORT2 umtx_lock(UMutex *mutex) { +#ifndef __wasi__ if (mutex == nullptr) { mutex = &globalMutex; } mutex->lock(); +#endif } U_CAPI void U_EXPORT2 umtx_unlock(UMutex* mutex) { +#ifndef __wasi__ if (mutex == nullptr) { mutex = &globalMutex; } mutex->unlock(); +#endif } /************************************************************************************************* * @@ -141,22 +155,26 @@ umtx_unlock(UMutex* mutex) // that knows the C++ types involved. This function returns true if // the caller needs to call the Init function. // U_COMMON_API UBool U_EXPORT2 umtx_initImplPreInit(UInitOnce &uio) { +#ifndef __wasi__ std::call_once(*pInitFlag, umtx_init); std::unique_lock lock(*initMutex); +#endif if (umtx_loadAcquire(uio.fState) == 0) { umtx_storeRelease(uio.fState, 1); return true; // Caller will next call the init function. } else { +#ifndef __wasi__ while (umtx_loadAcquire(uio.fState) == 1) { // Another thread is currently running the initialization. // Wait until it completes. initCondition->wait(lock); } U_ASSERT(uio.fState == 2); +#endif return false; } } @@ -166,15 +184,17 @@ umtx_initImplPreInit(UInitOnce &uio) { // Some threads may be racing to test the fState variable outside of the mutex, // requiring the use of store/release when changing its value. U_COMMON_API void U_EXPORT2 umtx_initImplPostInit(UInitOnce &uio) { +#ifndef __wasi__ { std::unique_lock lock(*initMutex); umtx_storeRelease(uio.fState, 2); } initCondition->notify_all(); +#endif } U_NAMESPACE_END /************************************************************************************************* diff --git a/intl/icu/source/common/umutex.h b/intl/icu/source/common/umutex.h --- a/intl/icu/source/common/umutex.h +++ b/intl/icu/source/common/umutex.h @@ -18,13 +18,16 @@ */ #ifndef UMUTEX_H #define UMUTEX_H +#ifndef __wasi__ #include #include #include +#endif + #include #include "unicode/utypes.h" #include "unicode/uclean.h" #include "unicode/uobject.h" @@ -43,10 +46,12 @@ U_NAMESPACE_BEGIN * * Low Level Atomic Operations, ICU wrappers for. * ****************************************************************************/ +#ifndef __wasi__ + typedef std::atomic u_atomic_int32_t; inline int32_t umtx_loadAcquire(u_atomic_int32_t &var) { return var.load(std::memory_order_acquire); } @@ -61,10 +66,31 @@ inline int32_t umtx_atomic_inc(u_atomic_ inline int32_t umtx_atomic_dec(u_atomic_int32_t *var) { return var->fetch_sub(1) - 1; } +#else + +typedef int32_t u_atomic_int32_t; + +inline int32_t umtx_loadAcquire(u_atomic_int32_t &var) { + return var; +} + +inline void umtx_storeRelease(u_atomic_int32_t &var, int32_t val) { + var = val; +} + +inline int32_t umtx_atomic_inc(u_atomic_int32_t *var) { + return ++(*var); +} + +inline int32_t umtx_atomic_dec(u_atomic_int32_t *var) { + return --(*var); +} + +#endif /************************************************************************************************* * * UInitOnce Definitions. * @@ -211,21 +237,29 @@ public: U_COMMON_API UMutex& operator=(const UMutex& other) = delete; U_COMMON_API void* operator new(size_t) = delete; // requirements for C++ BasicLockable, allows UMutex to work with std::lock_guard U_COMMON_API void lock() { +#ifndef __wasi__ std::mutex *m = fMutex.load(std::memory_order_acquire); if (m == nullptr) { m = getMutex(); } m->lock(); +#endif } - U_COMMON_API void unlock() { fMutex.load(std::memory_order_relaxed)->unlock(); } + U_COMMON_API void unlock() { +#ifndef __wasi__ + fMutex.load(std::memory_order_relaxed)->unlock(); +#endif + } U_COMMON_API static void cleanup(); private: +#ifndef __wasi__ alignas(std::mutex) char fStorage[sizeof(std::mutex)] {}; std::atomic fMutex { nullptr }; +#endif /** All initialized UMutexes are kept in a linked list, so that they can be found, * and the underlying std::mutex destructed, by u_cleanup(). */ UMutex *fListLink { nullptr }; @@ -233,11 +267,13 @@ private: /** Out-of-line function to lazily initialize a UMutex on first use. * Initial fast check is inline, in lock(). The returned value may never * be nullptr. */ +#ifndef __wasi__ std::mutex *getMutex(); +#endif }; /* Lock a mutex. * @param mutex The given mutex to be locked. Pass NULL to specify diff --git a/intl/icu/source/common/unifiedcache.cpp b/intl/icu/source/common/unifiedcache.cpp --- a/intl/icu/source/common/unifiedcache.cpp +++ b/intl/icu/source/common/unifiedcache.cpp @@ -11,19 +11,23 @@ */ #include "unifiedcache.h" #include // For std::max() +#ifndef __wasi__ #include +#endif #include "uassert.h" #include "uhash.h" #include "ucln_cmn.h" static icu::UnifiedCache *gCache = nullptr; +#ifndef __wasi__ static std::mutex *gCacheMutex = nullptr; static std::condition_variable *gInProgressValueAddedCond; +#endif static icu::UInitOnce gCacheInitOnce {}; static const int32_t MAX_EVICT_ITERATIONS = 10; static const int32_t DEFAULT_MAX_UNUSED = 1000; static const int32_t DEFAULT_PERCENTAGE_OF_IN_USE = 100; @@ -32,14 +36,16 @@ static const int32_t DEFAULT_PERCENTAGE_ U_CDECL_BEGIN static UBool U_CALLCONV unifiedcache_cleanup() { gCacheInitOnce.reset(); delete gCache; gCache = nullptr; +#ifndef __wasi__ gCacheMutex->~mutex(); gCacheMutex = nullptr; gInProgressValueAddedCond->~condition_variable(); gInProgressValueAddedCond = nullptr; +#endif return true; } U_CDECL_END @@ -70,12 +76,14 @@ CacheKeyBase::~CacheKeyBase() { static void U_CALLCONV cacheInit(UErrorCode &status) { U_ASSERT(gCache == nullptr); ucln_common_registerCleanup( UCLN_COMMON_UNIFIED_CACHE, unifiedcache_cleanup); +#ifndef __wasi__ gCacheMutex = STATIC_NEW(std::mutex); gInProgressValueAddedCond = STATIC_NEW(std::condition_variable); +#endif gCache = new UnifiedCache(status); if (gCache == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; } if (U_FAILURE(status)) { @@ -133,41 +141,53 @@ void UnifiedCache::setEvictionPolicy( } if (count < 0 || percentageOfInUseItems < 0) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif fMaxUnused = count; fMaxPercentageOfInUse = percentageOfInUseItems; } int32_t UnifiedCache::unusedCount() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif return uhash_count(fHashtable) - fNumValuesInUse; } int64_t UnifiedCache::autoEvictedCount() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif return fAutoEvictedCount; } int32_t UnifiedCache::keyCount() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif return uhash_count(fHashtable); } void UnifiedCache::flush() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif // Use a loop in case cache items that are flushed held hard references to // other cache items making those additional cache items eligible for // flushing. while (_flush(false)); } void UnifiedCache::handleUnreferencedObject() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif --fNumValuesInUse; _runEvictionSlice(); } #ifdef UNIFIED_CACHE_DEBUG @@ -182,11 +202,13 @@ void UnifiedCache::dump() { } cache->dumpContents(); } void UnifiedCache::dumpContents() const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif _dumpContents(); } // Dumps content of cache. // On entry, gCacheMutex must be held. @@ -222,11 +244,13 @@ UnifiedCache::~UnifiedCache() { flush(); { // Now all that should be left in the cache are entries that refer to // each other and entries with hard references from outside the cache. // Nothing we can do about these so proceed to wipe out the cache. +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif _flush(true); } uhash_close(fHashtable); fHashtable = nullptr; delete fNoValue; @@ -323,11 +347,13 @@ void UnifiedCache::_putNew( void UnifiedCache::_putIfAbsentAndGet( const CacheKeyBase &key, const SharedObject *&value, UErrorCode &status) const { +#ifndef __wasi__ std::lock_guard lock(*gCacheMutex); +#endif const UHashElement *element = uhash_find(fHashtable, &key); if (element != nullptr && !_inProgress(element)) { _fetch(element, value, status); return; } @@ -348,18 +374,22 @@ UBool UnifiedCache::_poll( const CacheKeyBase &key, const SharedObject *&value, UErrorCode &status) const { U_ASSERT(value == nullptr); U_ASSERT(status == U_ZERO_ERROR); +#ifndef __wasi__ std::unique_lock lock(*gCacheMutex); +#endif const UHashElement *element = uhash_find(fHashtable, &key); // If the hash table contains an inProgress placeholder entry for this key, // this means that another thread is currently constructing the value object. // Loop, waiting for that construction to complete. while (element != nullptr && _inProgress(element)) { +#ifndef __wasi__ gInProgressValueAddedCond->wait(lock); +#endif element = uhash_find(fHashtable, &key); } // If the hash table contains an entry for the key, // fetch out the contents and return them. @@ -426,13 +456,15 @@ void UnifiedCache::_put( UHashElement *ptr = const_cast(element); ptr->value.pointer = (void *) value; U_ASSERT(oldValue == fNoValue); removeSoftRef(oldValue); +#ifndef __wasi__ // Tell waiting threads that we replace in-progress status with // an error. gInProgressValueAddedCond->notify_all(); +#endif } void UnifiedCache::_fetch( const UHashElement *element, const SharedObject *&value, diff --git a/intl/icu/source/i18n/decContext.h b/intl/icu/source/i18n/decContext.h --- a/intl/icu/source/i18n/decContext.h +++ b/intl/icu/source/i18n/decContext.h @@ -59,11 +59,13 @@ #if !defined(int32_t) /* #include */ /* C99 standard integers */ #endif #include /* for printf, etc. */ +#ifndef __wasi__ #include /* for traps */ +#endif /* Extended flags setting -- set this to 0 to use only IEEE flags */ #if !defined(DECEXTFLAG) #define DECEXTFLAG 1 /* 1=enable extended flags */ #endif diff --git a/intl/icu/source/i18n/decimfmt.cpp b/intl/icu/source/i18n/decimfmt.cpp --- a/intl/icu/source/i18n/decimfmt.cpp +++ b/intl/icu/source/i18n/decimfmt.cpp @@ -478,12 +478,17 @@ DecimalFormat& DecimalFormat::operator=( } DecimalFormat::~DecimalFormat() { if (fields == nullptr) { return; } +#ifndef __wasi__ delete fields->atomicParser.exchange(nullptr); delete fields->atomicCurrencyParser.exchange(nullptr); +#else + delete fields->atomicParser; + delete fields->atomicCurrencyParser; +#endif delete fields; } DecimalFormat* DecimalFormat::clone() const { // can only clone valid objects. @@ -1635,12 +1640,17 @@ void DecimalFormat::touch(UErrorCode& st // Do this after fields->exportedProperties are set up setupFastFormat(); // Delete the parsers if they were made previously +#ifndef __wasi__ delete fields->atomicParser.exchange(nullptr); delete fields->atomicCurrencyParser.exchange(nullptr); +#else + delete fields->atomicParser; + delete fields->atomicCurrencyParser; +#endif // In order for the getters to work, we need to populate some fields in NumberFormat. NumberFormat::setCurrency(fields->exportedProperties.currency.get(status).getISOCurrency(), status); NumberFormat::setMaximumIntegerDigits(fields->exportedProperties.maximumIntegerDigits); NumberFormat::setMinimumIntegerDigits(fields->exportedProperties.minimumIntegerDigits); @@ -1671,11 +1681,15 @@ const numparse::impl::NumberParserImpl* if (U_FAILURE(status)) { return nullptr; } // First try to get the pre-computed parser +#ifndef __wasi__ auto* ptr = fields->atomicParser.load(); +#else + auto* ptr = fields->atomicParser; +#endif if (ptr != nullptr) { return ptr; } // Try computing the parser on our own @@ -1690,25 +1704,34 @@ const numparse::impl::NumberParserImpl* // Note: ptr starts as nullptr; during compare_exchange, // it is set to what is actually stored in the atomic // if another thread beat us to computing the parser object. auto* nonConstThis = const_cast(this); +#ifndef __wasi__ if (!nonConstThis->fields->atomicParser.compare_exchange_strong(ptr, temp)) { // Another thread beat us to computing the parser delete temp; return ptr; } else { // Our copy of the parser got stored in the atomic return temp; } +#else + nonConstThis->fields->atomicParser = temp; + return temp; +#endif } const numparse::impl::NumberParserImpl* DecimalFormat::getCurrencyParser(UErrorCode& status) const { if (U_FAILURE(status)) { return nullptr; } // First try to get the pre-computed parser +#ifndef __wasi__ auto* ptr = fields->atomicCurrencyParser.load(); +#else + auto* ptr = fields->atomicCurrencyParser; +#endif if (ptr != nullptr) { return ptr; } // Try computing the parser on our own @@ -1719,18 +1742,23 @@ const numparse::impl::NumberParserImpl* } // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the // atomic if another thread beat us to computing the parser object. auto* nonConstThis = const_cast(this); +#ifndef __wasi__ if (!nonConstThis->fields->atomicCurrencyParser.compare_exchange_strong(ptr, temp)) { // Another thread beat us to computing the parser delete temp; return ptr; } else { // Our copy of the parser got stored in the atomic return temp; } +#else + nonConstThis->fields->atomicCurrencyParser = temp; + return temp; +#endif } void DecimalFormat::fieldPositionHelper( const UFormattedNumberData& formatted, diff --git a/intl/icu/source/i18n/number_mapper.h b/intl/icu/source/i18n/number_mapper.h --- a/intl/icu/source/i18n/number_mapper.h +++ b/intl/icu/source/i18n/number_mapper.h @@ -5,18 +5,21 @@ #if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_MAPPER_H__ #define __NUMBER_MAPPER_H__ -#include #include "number_types.h" #include "unicode/currpinf.h" #include "standardplural.h" #include "number_patternstring.h" #include "number_currencysymbols.h" #include "numparse_impl.h" +#ifndef __wasi__ +#include +#endif + U_NAMESPACE_BEGIN namespace number::impl { class AutoAffixPatternProvider; class CurrencyPluralInfoAffixProvider; @@ -194,14 +197,22 @@ struct DecimalFormatFields : public UMem * #format} method uses the formatter directly without needing to synchronize. */ LocalizedNumberFormatter formatter; /** The lazy-computed parser for .parse() */ +#ifndef __wasi__ std::atomic<::icu::numparse::impl::NumberParserImpl*> atomicParser = {}; +#else + ::icu::numparse::impl::NumberParserImpl* atomicParser = nullptr; +#endif /** The lazy-computed parser for .parseCurrency() */ +#ifndef __wasi__ std::atomic<::icu::numparse::impl::NumberParserImpl*> atomicCurrencyParser = {}; +#else + ::icu::numparse::impl::NumberParserImpl* atomicCurrencyParser = {}; +#endif /** Small object ownership warehouse for the formatter and parser */ DecimalFormatWarehouse warehouse; /** The effective properties as exported from the formatter object. Used by some getters. */ diff --git a/intl/icu/source/i18n/numrange_fluent.cpp b/intl/icu/source/i18n/numrange_fluent.cpp --- a/intl/icu/source/i18n/numrange_fluent.cpp +++ b/intl/icu/source/i18n/numrange_fluent.cpp @@ -246,33 +246,53 @@ LocalizedNumberRangeFormatter::Localized LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(NFS&& src) noexcept : NFS(std::move(src)) { // Steal the compiled formatter LNF&& _src = static_cast(src); +#ifndef __wasi__ auto* stolen = _src.fAtomicFormatter.exchange(nullptr); delete fAtomicFormatter.exchange(stolen); +#else + delete fAtomicFormatter; + fAtomicFormatter = _src.fAtomicFormatter; + _src.fAtomicFormatter = nullptr; +#endif } LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(const LNF& other) { if (this == &other) { return *this; } // self-assignment: no-op NFS::operator=(static_cast&>(other)); // Do not steal; just clear +#ifndef __wasi__ delete fAtomicFormatter.exchange(nullptr); +#else + delete fAtomicFormatter; +#endif return *this; } LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(LNF&& src) noexcept { NFS::operator=(static_cast&&>(src)); // Steal the compiled formatter +#ifndef __wasi__ auto* stolen = src.fAtomicFormatter.exchange(nullptr); delete fAtomicFormatter.exchange(stolen); +#else + delete fAtomicFormatter; + fAtomicFormatter = src.fAtomicFormatter; + src.fAtomicFormatter = nullptr; +#endif return *this; } LocalizedNumberRangeFormatter::~LocalizedNumberRangeFormatter() { +#ifndef __wasi__ delete fAtomicFormatter.exchange(nullptr); +#else + delete fAtomicFormatter; +#endif } LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const RangeMacroProps& macros, const Locale& locale) { fMacros = macros; fMacros.locale = locale; @@ -363,11 +383,15 @@ LocalizedNumberRangeFormatter::getFormat if (U_FAILURE(status)) { return nullptr; } // First try to get the pre-computed formatter +#ifndef __wasi__ auto* ptr = fAtomicFormatter.load(); +#else + auto* ptr = fAtomicFormatter; +#endif if (ptr != nullptr) { return ptr; } // Try computing the formatter on our own @@ -378,17 +402,22 @@ LocalizedNumberRangeFormatter::getFormat // Note: ptr starts as nullptr; during compare_exchange, // it is set to what is actually stored in the atomic // if another thread beat us to computing the formatter object. auto* nonConstThis = const_cast(this); +#ifndef __wasi__ if (!nonConstThis->fAtomicFormatter.compare_exchange_strong(ptr, temp.getAlias())) { // Another thread beat us to computing the formatter return ptr; } else { // Our copy of the formatter got stored in the atomic return temp.orphan(); } +#else + nonConstThis->fAtomicFormatter = temp.getAlias(); + return temp.orphan(); +#endif } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/intl/icu/source/i18n/unicode/numberrangeformatter.h b/intl/icu/source/i18n/unicode/numberrangeformatter.h --- a/intl/icu/source/i18n/unicode/numberrangeformatter.h +++ b/intl/icu/source/i18n/unicode/numberrangeformatter.h @@ -8,18 +8,21 @@ #if U_SHOW_CPLUSPLUS_API #if !UCONFIG_NO_FORMATTING -#include #include "unicode/appendable.h" #include "unicode/fieldpos.h" #include "unicode/formattedvalue.h" #include "unicode/fpositer.h" #include "unicode/numberformatter.h" #include "unicode/unumberrangeformatter.h" +#ifndef __wasi__ +#include +#endif + /** * \file * \brief C++ API: Library for localized formatting of number, currency, and unit ranges. * * The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement. @@ -559,11 +562,15 @@ class U_I18N_API_CLASS LocalizedNumberRa * @stable ICU 63 */ U_I18N_API ~LocalizedNumberRangeFormatter(); private: +#ifndef __wasi__ std::atomic fAtomicFormatter = {}; +#else + impl::NumberRangeFormatterImpl* fAtomicFormatter = nullptr; +#endif const impl::NumberRangeFormatterImpl* getFormatter(UErrorCode& stauts) const; explicit LocalizedNumberRangeFormatter( const NumberRangeFormatterSettings& other);