diff --git a/API/fleece/InstanceCounted.hh b/API/fleece/InstanceCounted.hh index 56c04a5a..8d879b97 100644 --- a/API/fleece/InstanceCounted.hh +++ b/API/fleece/InstanceCounted.hh @@ -47,9 +47,9 @@ namespace fleece { protected: InstanceCounted(size_t offset) {track(offset);} + void untrack() const; private: void track(size_t offset =0) const; - void untrack() const; static void dumpInstances(function_ref*); #else diff --git a/CMakeLists.txt b/CMakeLists.txt index b7497740..94b008e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,13 @@ elseif(APPLE) include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_apple.cmake") elseif(ANDROID OR "${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_linux.cmake") +elseif(EMSCRIPTEN) + # Emscripten does not actually support shared libraries and instead + # just builds a static library for compatibility with existing + # build setups. We just set this property to suppress a CMake warning. + set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) + + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_emscripten.cmake") else() message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}!") endif() @@ -54,6 +61,8 @@ add_library(FleeceBase STATIC ${FLEECE_BASE_SRC}) add_executable(fleeceTool Tool/fleece_tool.cc) target_link_libraries(fleeceTool FleeceObjects) +setup_build() + # Fleece Tests set_test_source_files(RESULT FLEECE_TEST_SRC) add_executable(FleeceTests EXCLUDE_FROM_ALL ${FLEECE_TEST_SRC}) diff --git a/Fleece.xcodeproj/project.pbxproj b/Fleece.xcodeproj/project.pbxproj index 0e70e85f..8f19efaa 100644 --- a/Fleece.xcodeproj/project.pbxproj +++ b/Fleece.xcodeproj/project.pbxproj @@ -424,6 +424,7 @@ 279AC5311C096872002C80DB /* fleece */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fleece; sourceTree = BUILT_PRODUCTS_DIR; }; 279AC5331C096872002C80DB /* fleece_tool.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fleece_tool.cc; sourceTree = ""; }; 279AC53B1C097941002C80DB /* Value+Dump.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "Value+Dump.cc"; sourceTree = ""; }; + 27A0B3652C1B89120053B941 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; tabWidth = 4; usesTabs = 1; }; 27A0E3DD24DCD86900380563 /* ConcurrentArena.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConcurrentArena.hh; sourceTree = ""; }; 27A0E3DE24DCD86900380563 /* ConcurrentArena.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ConcurrentArena.cc; sourceTree = ""; }; 27A2F73A21248DA40081927B /* FLSlice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLSlice.h; sourceTree = ""; }; @@ -562,6 +563,7 @@ 2776AA43208A6C5A004ACE85 /* xcconfigs */, 27298E3E1C00F8A9000CFBA8 /* vendor */, 270FA25D1BF53CAD005DCB13 /* Products */, + 27A0B3652C1B89120053B941 /* Makefile */, 2750735D1F4B5F0F003D2CCE /* CMakeLists.txt */, 275B35A2234E4D3C00FE9CF0 /* cmake */, 27DE2EE22125FAC600123597 /* Frameworks */, diff --git a/Fleece/Integration/ObjC/MTests.mm b/Fleece/Integration/ObjC/MTests.mm index 2c9f2f5e..eb8cccea 100644 --- a/Fleece/Integration/ObjC/MTests.mm +++ b/Fleece/Integration/ObjC/MTests.mm @@ -174,7 +174,7 @@ static void verifyDictIterator(NSDictionary *dict) { NSUInteger i = 0; for (id o in array) { NSLog(@"item #%zu: %@", i, o); - CHECK([o isEqualTo: orig[i]]); + CHECK([o isEqual: orig[i]]); ++i; } } diff --git a/Fleece/Support/Backtrace.cc b/Fleece/Support/Backtrace.cc index ad897b6c..63de386f 100644 --- a/Fleece/Support/Backtrace.cc +++ b/Fleece/Support/Backtrace.cc @@ -26,7 +26,7 @@ #include // dladdr() -#ifndef __ANDROID__ +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) #define HAVE_EXECINFO #include // backtrace(), backtrace_symbols() #else @@ -39,12 +39,16 @@ #define HAVE_UNMANGLE #endif +#ifdef __EMSCRIPTEN__ + #include +#endif + namespace fleece { using namespace std; -#ifndef HAVE_EXECINFO +#if !defined(HAVE_EXECINFO) && !defined(__EMSCRIPTEN__) // Use libunwind to emulate backtrace(). This is limited in that it won't traverse any stack // frames before a signal handler, so it's not useful for logging the stack upon a crash. // Adapted from https://stackoverflow.com/a/28858941 @@ -91,8 +95,11 @@ namespace fleece { Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { - precondition(i < _addrs.size()); frameInfo frame = { }; +#ifdef __EMSCRIPTEN__ + precondition(i < _frames.size()); +#else + precondition(i < _addrs.size()); Dl_info info; if (dladdr(_addrs[i], &info)) { frame.pc = _addrs[i]; @@ -103,11 +110,10 @@ namespace fleece { if (slash) frame.library = slash + 1; } - +#endif return frame; } - // If any of these strings occur in a backtrace, suppress further frames. static constexpr const char* kTerminalFunctions[] = { "_C_A_T_C_H____T_E_S_T_", @@ -132,8 +138,34 @@ namespace fleece { } } - bool Backtrace::writeTo(ostream &out) const { +#ifdef __EMSCRIPTEN__ + int i = 0; + for (auto &frameRef : _frames) { + if (i++ > 0) + out << "\n"; + out << '\t'; + + string frame(frameRef); + + bool stop = false; + for (auto fn : kTerminalFunctions) { + if (frame.find(fn) != string::npos) + stop = true; + } + + for (auto &abbrev : kAbbreviations) + replace(frame, abbrev.old, abbrev.nuu); + + out << frame; + + if (stop) { + out << "\n\t... (more suppressed) ..."; + break; + } + } + return true; +#else for (decltype(_addrs.size()) i = 0; i < _addrs.size(); ++i) { if (i > 0) out << '\n'; @@ -179,6 +211,7 @@ namespace fleece { } } return true; +#endif } @@ -318,7 +351,9 @@ namespace fleece { namespace fleece { Backtrace::~Backtrace() { +#ifndef __EMSCRIPTEN__ free(_symbols); +#endif } std::string Unmangle(const char *name NONNULL) { @@ -344,23 +379,45 @@ namespace fleece { Backtrace::Backtrace(unsigned skipFrames, unsigned maxFrames) { if (maxFrames > 0) - capture(skipFrames + 1, maxFrames); + _capture(skipFrames + 1, maxFrames); } void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames) { +#ifdef __EMSCRIPTEN__ + auto callstackSize = emscripten_get_callstack(EM_LOG_JS_STACK | EM_LOG_C_STACK, nullptr, 0); + if (callstackSize > 0) { + auto buffer = (char*)malloc(callstackSize + 128); + emscripten_get_callstack(EM_LOG_JS_STACK | EM_LOG_C_STACK, buffer, callstackSize);; + istringstream frames(buffer); + string frame; + int i = 0; + while (i < maxFrames && getline(frames, frame, '\n')) { + i++; + // Remove the " at " prefix before each frame. + replace(frame, " at ", ""); + _frames.push_back(frame); + } + skip(skipFrames); + } +#else _addrs.resize(++skipFrames + maxFrames); // skip this frame auto n = backtrace(&_addrs[0], skipFrames + maxFrames); _addrs.resize(n); skip(skipFrames); -#if !defined(_MSC_VER) && !defined(__ANDROID__) +#if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) _symbols = backtrace_symbols(_addrs.data(), int(_addrs.size())); +#endif #endif } void Backtrace::skip(unsigned nFrames) { +#ifdef __EMSCRIPTEN__ + _frames.erase(_frames.begin(),_frames.begin() + min(size_t(nFrames), _frames.size())); +#else _addrs.erase(_addrs.begin(), _addrs.begin() + min(size_t(nFrames), _addrs.size())); +#endif } @@ -388,7 +445,7 @@ namespace fleece { out << "unknown exception type\n"; } } - out << "Backtrace:"; + out << "Backtrace:" << endl; bt.writeTo(out); } diff --git a/Fleece/Support/Backtrace.hh b/Fleece/Support/Backtrace.hh index 958cca21..b689d688 100644 --- a/Fleece/Support/Backtrace.hh +++ b/Fleece/Support/Backtrace.hh @@ -53,7 +53,13 @@ namespace fleece { }; /// The number of stack frames captured. - unsigned size() const {return (unsigned)_addrs.size();} + unsigned size() const { +#ifdef __EMSCRIPTEN__ + return (unsigned)_frames.size(); +#else + return (unsigned)_addrs.size(); +#endif + } /// Returns info about a stack frame. 0 is the top. frameInfo getFrame(unsigned) const; @@ -74,8 +80,12 @@ namespace fleece { char* printFrame(unsigned i) const; static void writeCrashLog(std::ostream&); +#ifdef __EMSCRIPTEN__ + std::vector _frames; +#else std::vector _addrs; // Array of PCs in backtrace, top first char** _symbols { nullptr }; +#endif }; diff --git a/Fleece/Support/NumConversion.cc b/Fleece/Support/NumConversion.cc index ad1f363f..5f0bf91e 100644 --- a/Fleece/Support/NumConversion.cc +++ b/Fleece/Support/NumConversion.cc @@ -15,7 +15,7 @@ #include #include #include -#if !defined(_MSC_VER) && !defined(__GLIBC__) +#if !defined(_MSC_VER) && !defined(__GLIBC__) && !defined(__EMSCRIPTEN__) #include #endif diff --git a/Fleece/Support/ParseDate.cc b/Fleece/Support/ParseDate.cc index 02070791..ff6ed632 100644 --- a/Fleece/Support/ParseDate.cc +++ b/Fleece/Support/ParseDate.cc @@ -558,6 +558,8 @@ namespace fleece { auto offset = seconds(-s); #elif defined(__DARWIN_UNIX03) || defined(__ANDROID__) || defined(_XOPEN_SOURCE) || defined(_SVID_SOURCE) auto offset = seconds(-timezone); +#elif defined(__EMSCRIPTEN__) + auto offset = seconds(timezone); #else # error Unimplemented GetLocalTZOffset #endif diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..868fa975 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +# Fleece makefile ... just to kick off CMake builds + +native: OUT = build_cmake/native +native: + mkdir -p $(OUT) + cmake -S . -B $(OUT) -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake --build $(OUT) -j + +test: OUT = build_cmake/test +test: + mkdir -p $(OUT) + cmake -S . -B $(OUT) -DCMAKE_BUILD_TYPE=Debug + cmake --build $(OUT) -j --target FleeceTests + $(OUT)/FleeceTests -r list + +tests: test + + +# WASM build requires Emscripten +wasm: OUT = build_cmake/wasm +wasm: + mkdir -p $(OUT) + emcmake cmake -S . -B $(OUT) -DCMAKE_BUILD_TYPE=RelWithDebInfo + cd $(OUT) && emmake make -j + +test_wasm: OUT = build_cmake/test_wasm +test_wasm: + mkdir -p $(OUT) + emcmake cmake -S . -B $(OUT) -DCMAKE_BUILD_TYPE=Debug + cd $(OUT) && emmake make -j FleeceTests + node $(OUT)/FleeceTests.js -r list + +wasm_test: test_wasm +wasm_tests: test_wasm + + +docs: + doxygen Documentation/Doxyfile + doxygen Documentation/Doxyfile_C++ + + +clean: + rm -rf build_cmake/{native,test,wasm,test_wasm} diff --git a/Tests/EncoderTests.cc b/Tests/EncoderTests.cc index 8096cd3c..87cbc20a 100644 --- a/Tests/EncoderTests.cc +++ b/Tests/EncoderTests.cc @@ -1067,6 +1067,8 @@ class EncoderTests { CHECK(ParseDouble(floatBuf, recovered)); CHECK(FloatEquals(float(recovered), 2.71828f)); + // Emscripten does not come with locales for different languages. +#ifndef __EMSCRIPTEN__ #ifdef _MSC_VER setlocale(LC_ALL, "fr-FR"); #else @@ -1094,6 +1096,7 @@ class EncoderTests { CHECK(FloatEquals(recovered_f, 2.71828f)); setlocale(LC_ALL, "C"); +#endif } diff --git a/Tests/PerfTests.cc b/Tests/PerfTests.cc index ae9f96c3..1ed9edc2 100644 --- a/Tests/PerfTests.cc +++ b/Tests/PerfTests.cc @@ -51,7 +51,7 @@ TEST_CASE("GetUVarint performance", "[.Perf]") { uint8_t buf[100]; fprintf(stderr, "buf = %p\n", &buf); double d = 1.0; - while (d <= UINT64_MAX) { + while (d <= (double)UINT64_MAX) { auto n = (uint64_t)d; size_t nBytes = PutUVarInt(buf, n); uint64_t result = 0; diff --git a/Tests/ValueTests.cc b/Tests/ValueTests.cc index f7ec136a..ae64357e 100644 --- a/Tests/ValueTests.cc +++ b/Tests/ValueTests.cc @@ -56,7 +56,7 @@ namespace fleece { TEST_CASE("VarInt read") { uint8_t buf[100]; uint64_t result; - for (double d = 0.0; d <= UINT64_MAX; d = std::max(d, 1.0) * 1.5) { + for (double d = 0.0; d <= (double)UINT64_MAX; d = std::max(d, 1.0) * 1.5) { auto n = (uint64_t)d; std::cerr << std::hex << n << std::dec << ", "; size_t nBytes = PutUVarInt(buf, n); @@ -75,7 +75,7 @@ namespace fleece { TEST_CASE("VarInt32 read") { uint8_t buf[100]; - for (double d = 0.0; d <= UINT64_MAX; d = std::max(d, 1.0) * 1.5) { + for (double d = 0.0; d <= (double)UINT64_MAX; d = std::max(d, 1.0) * 1.5) { auto n = (uint64_t)d; std::cerr << std::hex << n << std::dec << ", "; size_t nBytes = PutUVarInt(buf, n); diff --git a/Tool/fleece_tool.cc b/Tool/fleece_tool.cc index 7504593e..2f10c36d 100644 --- a/Tool/fleece_tool.cc +++ b/Tool/fleece_tool.cc @@ -37,7 +37,7 @@ static void usage(void) { } -#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) +#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) || defined(__EMSCRIPTEN__) // digittoint is a BSD function, not available on Android, Linux, etc. static int digittoint(char ch) { int d = ch - '0'; diff --git a/build_cmake/build.sh b/build_cmake/build.sh deleted file mode 100755 index 17886ca2..00000000 --- a/build_cmake/build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/bash -e - -# Copyright 2016-Present Couchbase, Inc. -# -# Use of this software is governed by the Business Source License included in -# the file licenses/BSL-Couchbase.txt. As of the Change Date specified in that -# file, in accordance with the Business Source License, use of this software -# will be governed by the Apache License, Version 2.0, included in the file -# licenses/APL2.txt. - -SCRIPT_DIR=`dirname $0` -cd $SCRIPT_DIR - -cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. - -core_count=`getconf _NPROCESSORS_ONLN` -make -j `expr $core_count + 1` -make FleeceTests -j `expr $core_count + 1` - diff --git a/cmake/platform_emscripten.cmake b/cmake/platform_emscripten.cmake new file mode 100644 index 00000000..5f6bf510 --- /dev/null +++ b/cmake/platform_emscripten.cmake @@ -0,0 +1,75 @@ +include("${CMAKE_CURRENT_LIST_DIR}/platform_base.cmake") + +set(EMSCRIPTEN_COMPILE_FLAGS + "-pthread" + "-fwasm-exceptions" + "-flto" +) +set(EMSCRIPTEN_LINK_FLAGS + "-pthread" + "-fwasm-exceptions" + "-flto" + "-lembind" + "SHELL:-s ALLOW_MEMORY_GROWTH=1" + "SHELL:-s EXIT_RUNTIME=1" + "SHELL:-s WASM_BIGINT=1" +) + +function(set_source_files) + set(oneValueArgs RESULT) + cmake_parse_arguments(EMSCRIPTEN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED EMSCRIPTEN_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set_source_files_base(RESULT BASE_SRC_FILES) + set(${EMSCRIPTEN_SSS_RESULT} ${BASE_SRC_FILES} PARENT_SCOPE) +endfunction() + +function(set_base_platform_files) + # No-op +endfunction() + +function(set_test_source_files) + set(oneValueArgs RESULT) + cmake_parse_arguments(EMSCRIPTEN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED EMSCRIPTEN_SSS_RESULT) + message(FATAL_ERROR "set_source_files_base needs to be called with RESULT") + endif() + + set_test_source_files_base(RESULT BASE_SRC_FILES) + set(${EMSCRIPTEN_SSS_RESULT} ${BASE_SRC_FILES} PARENT_SCOPE) +endfunction() + +function(setup_build) + foreach(platform Fleece FleeceStatic FleeceBase FleeceObjects fleeceTool) + target_compile_options( + ${platform} + PRIVATE + "-Wformat=2" + ${EMSCRIPTEN_COMPILE_FLAGS} + ) + endforeach() + + target_link_options( + fleeceTool + PRIVATE + ${EMSCRIPTEN_LINK_FLAGS} + ) +endfunction() + +function(setup_test_build) + target_compile_options( + FleeceTests + PRIVATE + ${EMSCRIPTEN_COMPILE_FLAGS} + ) + target_link_options( + FleeceTests + PRIVATE + ${EMSCRIPTEN_LINK_FLAGS} + "-lnodefs.js" + "-lnoderawfs.js" + "SHELL:-s PTHREAD_POOL_SIZE=8" + ) +endfunction()