diff --git a/CMakeLists.txt b/CMakeLists.txt index c1d9f1a6..4fa8f511 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,24 @@ option(SERIALIZE_CHIPDB "Never build chipdb in parallel to reduce peak memory us set(Boost_NO_BOOST_CMAKE ON) +if(WASI) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lwasi-emulated-mman") + set(USE_THREADS OFF) + add_definitions( + -DBOOST_EXCEPTION_DISABLE + -DBOOST_NO_EXCEPTIONS + -DBOOST_SP_NO_ATOMIC_ACCESS + -DBOOST_AC_DISABLE_THREADS + -DBOOST_NO_CXX11_HDR_MUTEX + ) +else() + set(USE_THREADS ON) +endif() + +if (NOT USE_THREADS) + add_definitions(-DNPNR_DISABLE_THREADS) +endif() + set(link_param "") if (STATIC_BUILD) set(Boost_USE_STATIC_LIBS ON) @@ -84,7 +102,6 @@ else() set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe") endif() endif() -set(CMAKE_DEFIN) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/3rdparty/sanitizers-cmake/cmake;." ${CMAKE_MODULE_PATH}) @@ -99,7 +116,10 @@ endif() find_package(Sanitizers) # List of Boost libraries to include -set(boost_libs filesystem thread program_options iostreams system) +set(boost_libs filesystem program_options iostreams system) +if (USE_THREADS) + list(APPEND boost_libs thread) +endif() if (BUILD_GUI AND NOT BUILD_PYTHON) message(FATAL_ERROR "GUI requires Python to build") @@ -229,6 +249,10 @@ foreach (family ${ARCH}) # Add the CLI binary target add_executable(${PROGRAM_PREFIX}nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES}) + if (WASI) + # set(CMAKE_EXECUTABLE_SUFFIX) breaks CMake tests for some reason + set_property(TARGET ${PROGRAM_PREFIX}nextpnr-${family} PROPERTY SUFFIX ".wasm") + endif() install(TARGETS ${PROGRAM_PREFIX}nextpnr-${family} RUNTIME DESTINATION bin) target_compile_definitions(${PROGRAM_PREFIX}nextpnr-${family} PRIVATE MAIN_EXECUTABLE) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 1156490c..c16a601c 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -23,6 +23,25 @@ #include "log.h" #include "util.h" +#if defined(__wasm) +extern "C" { + // FIXME: WASI does not currently support exceptions. + void* __cxa_allocate_exception(size_t thrown_size) throw() { + return malloc(thrown_size); + } + bool __cxa_uncaught_exception() throw(); + void __cxa_throw(void* thrown_exception, struct std::type_info * tinfo, void (*dest)(void*)) { + std::terminate(); + } +} + +namespace boost { + void throw_exception( std::exception const & e ) { + NEXTPNR_NAMESPACE::log_error("boost::exception(): %s\n", e.what()); + } +} +#endif + NEXTPNR_NAMESPACE_BEGIN assertion_failure::assertion_failure(std::string msg, std::string expr_str, std::string filename, int line) diff --git a/common/nextpnr.h b/common/nextpnr.h index 66bba997..4d481d06 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -33,7 +33,9 @@ #include #include #include +#ifndef NPNR_DISABLE_THREADS #include +#endif #ifndef NEXTPNR_H #define NEXTPNR_H @@ -647,6 +649,7 @@ struct DeterministicRNG struct BaseCtx { +#ifndef NPNR_DISABLE_THREADS // Lock to perform mutating actions on the Context. std::mutex mutex; boost::thread::id mutex_owner; @@ -655,6 +658,7 @@ struct BaseCtx // method will lock/unlock it when its' released the main mutex to make // sure the UI is not starved. std::mutex ui_mutex; +#endif // ID String database. mutable std::unordered_map *idstring_str_to_idx; @@ -706,28 +710,36 @@ struct BaseCtx // Must be called before performing any mutating changes on the Ctx/Arch. void lock(void) { +#ifndef NPNR_DISABLE_THREADS mutex.lock(); mutex_owner = boost::this_thread::get_id(); +#endif } void unlock(void) { +#ifndef NPNR_DISABLE_THREADS NPNR_ASSERT(boost::this_thread::get_id() == mutex_owner); mutex.unlock(); +#endif } // Must be called by the UI before rendering data. This lock will be // prioritized when processing code calls yield(). void lock_ui(void) { +#ifndef NPNR_DISABLE_THREADS ui_mutex.lock(); mutex.lock(); +#endif } void unlock_ui(void) { +#ifndef NPNR_DISABLE_THREADS mutex.unlock(); ui_mutex.unlock(); +#endif } // Yield to UI by unlocking the main mutex, flashing the UI mutex and @@ -737,10 +749,12 @@ struct BaseCtx // Must be called with the main lock taken. void yield(void) { +#ifndef NPNR_DISABLE_THREADS unlock(); ui_mutex.lock(); ui_mutex.unlock(); lock(); +#endif } IdString id(const std::string &s) const { return IdString(this, s); } diff --git a/common/placer_heap.cc b/common/placer_heap.cc index c04e7091..8c43c433 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -154,9 +153,14 @@ class HeAPPlacer for (int i = 0; i < 4; i++) { setup_solve_cells(); auto solve_startt = std::chrono::high_resolution_clock::now(); +#ifdef NPNR_DISABLE_THREADS + build_solve_direction(false, -1); + build_solve_direction(true, -1); +#else boost::thread xaxis([&]() { build_solve_direction(false, -1); }); build_solve_direction(true, -1); xaxis.join(); +#endif auto solve_endt = std::chrono::high_resolution_clock::now(); solve_time += std::chrono::duration(solve_endt - solve_startt).count(); @@ -211,13 +215,16 @@ class HeAPPlacer // Heuristic: don't bother with threading below a certain size auto solve_startt = std::chrono::high_resolution_clock::now(); - if (solve_cells.size() < 500) { - build_solve_direction(false, (iter == 0) ? -1 : iter); - build_solve_direction(true, (iter == 0) ? -1 : iter); - } else { +#ifndef NPNR_DISABLE_THREADS + if (solve_cells.size() >= 500) { boost::thread xaxis([&]() { build_solve_direction(false, (iter == 0) ? -1 : iter); }); build_solve_direction(true, (iter == 0) ? -1 : iter); xaxis.join(); + } else +#endif + { + build_solve_direction(false, (iter == 0) ? -1 : iter); + build_solve_direction(true, (iter == 0) ? -1 : iter); } auto solve_endt = std::chrono::high_resolution_clock::now(); solve_time += std::chrono::duration(solve_endt - solve_startt).count(); diff --git a/common/router2.cc b/common/router2.cc index 26e78eaa..4dfd868b 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -33,7 +33,6 @@ #include #include #include -#include #include "log.h" #include "nextpnr.h" #include "router1.h" @@ -985,8 +984,22 @@ struct Router2 } if (ctx->verbose) log_info("%d/%d nets not multi-threadable\n", int(tcs.at(N).route_nets.size()), int(route_queue.size())); +#ifdef NPNR_DISABLE_THREADS + // Singlethreaded routing - quadrants + for (int i = 0; i < Nq; i++) { + router_thread(tcs.at(i)); + } + // Vertical splits + for (int i = Nq; i < Nq + Nv; i++) { + router_thread(tcs.at(i)); + } + // Horizontal splits + for (int i = Nq + Nv; i < Nq + Nv + Nh; i++) { + router_thread(tcs.at(i)); + } +#else // Multithreaded part of routing - quadrants - std::vector threads; + std::vector threads; for (int i = 0; i < Nq; i++) { threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i)); }); } @@ -1007,6 +1020,7 @@ struct Router2 for (auto &t : threads) t.join(); threads.clear(); +#endif // Singlethreaded part of routing - nets that cross partitions // or don't fit within bounding box for (auto st_net : tcs.at(N).route_nets) @@ -1130,4 +1144,4 @@ Router2Cfg::Router2Cfg(Context *ctx) perf_profile = ctx->setting("router2/perfProfile", false); } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.cc b/ecp5/arch.cc index db043f35..3c00099f 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -71,12 +71,13 @@ const char *chipdb_blob_25k = nullptr; const char *chipdb_blob_45k = nullptr; const char *chipdb_blob_85k = nullptr; -boost::iostreams::mapped_file_source blob_files[3]; +boost::iostreams::mapped_file blob_files[3]; const char *mmap_file(int index, const char *filename) { try { - blob_files[index].open(filename); + // WASI only supports MAP_PRIVATE + blob_files[index].open(filename, boost::iostreams::mapped_file::priv); if (!blob_files[index].is_open()) log_error("Unable to read chipdb %s\n", filename); return (const char *)blob_files[index].data(); diff --git a/ecp5/family.cmake b/ecp5/family.cmake index 9415e37e..247a307a 100644 --- a/ecp5/family.cmake +++ b/ecp5/family.cmake @@ -70,8 +70,9 @@ if (NOT EXTERNAL_CHIPDB) target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w) set(PREV_DEV_CC_BBA_DB) foreach (dev ${devices}) - set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ecp5/chipdbs/chipdb-${dev}.cc) set(DEV_CC_BBA_DB ${CMAKE_CURRENT_BINARY_DIR}/ecp5/chipdbs/chipdb-${dev}.bba) + set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ecp5/chipdbs/chipdb-${dev}.cc) + set(DEV_BIN_DB ${CMAKE_CURRENT_BINARY_DIR}/ecp5/chipdbs/chipdb-${dev}.bin) set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc) set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/gfx.h) if (PREGENERATED_BBA_PATH) @@ -85,11 +86,19 @@ if (NOT EXTERNAL_CHIPDB) COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB} DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB} ) - add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new - COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} - DEPENDS bbasm ${DEV_CC_BBA_DB} - ) + if(USE_C_EMBED) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm --e ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new ${DEV_BIN_DB} + COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) + else() + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new + COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) + endif() endif() if (SERIALIZE_CHIPDB) set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB}) diff --git a/ice40/arch.cc b/ice40/arch.cc index 6d07a949..645e93cb 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -57,12 +57,12 @@ const char *chipdb_blob_5k = nullptr; const char *chipdb_blob_u4k = nullptr; const char *chipdb_blob_8k = nullptr; -boost::iostreams::mapped_file_source blob_files[5]; +boost::iostreams::mapped_file blob_files[5]; const char *mmap_file(int index, const char *filename) { try { - blob_files[index].open(filename); + blob_files[index].open(filename, boost::iostreams::mapped_file::priv); if (!blob_files[index].is_open()) log_error("Unable to read chipdb %s\n", filename); return (const char *)blob_files[index].data();