Port nextpnr-{ice40,ecp5} to WASI.
This involves very few changes, all typical to WASM ports: * WASM doesn't currently support threads or atomics so those are disabled. * WASM doesn't currently support exceptions so the exception machinery is stubbed out. * WASM doesn't (and can't) have mmap(), so an emulation library is used. That library currently doesn't support MAP_SHARED flags, so MAP_PRIVATE is used instead. There is also an update to bring ECP5 bbasm CMake rules to parity with iCE40 ones, since although it is possible to embed chipdb into nextpnr on WASM, a 200 MB WASM file has very few practical uses. The README is not updated and there is no included toolchain file because at the moment it's not possible to build nextpnr with upstream boost and wasi-libc. Boost requires a patch (merged, will be available in boost 1.74.0), wasi-libc requires a few unmerged patches.
This commit is contained in:
parent
2692c6f6cc
commit
e7bb04769d
@ -14,6 +14,24 @@ option(SERIALIZE_CHIPDB "Never build chipdb in parallel to reduce peak memory us
|
|||||||
|
|
||||||
set(Boost_NO_BOOST_CMAKE ON)
|
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 "")
|
set(link_param "")
|
||||||
if (STATIC_BUILD)
|
if (STATIC_BUILD)
|
||||||
set(Boost_USE_STATIC_LIBS ON)
|
set(Boost_USE_STATIC_LIBS ON)
|
||||||
@ -84,7 +102,6 @@ else()
|
|||||||
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
|
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_DEFIN)
|
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/3rdparty/sanitizers-cmake/cmake;." ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/3rdparty/sanitizers-cmake/cmake;." ${CMAKE_MODULE_PATH})
|
||||||
|
|
||||||
@ -99,7 +116,10 @@ endif()
|
|||||||
find_package(Sanitizers)
|
find_package(Sanitizers)
|
||||||
|
|
||||||
# List of Boost libraries to include
|
# 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)
|
if (BUILD_GUI AND NOT BUILD_PYTHON)
|
||||||
message(FATAL_ERROR "GUI requires Python to build")
|
message(FATAL_ERROR "GUI requires Python to build")
|
||||||
@ -229,6 +249,10 @@ foreach (family ${ARCH})
|
|||||||
|
|
||||||
# Add the CLI binary target
|
# Add the CLI binary target
|
||||||
add_executable(${PROGRAM_PREFIX}nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES})
|
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)
|
install(TARGETS ${PROGRAM_PREFIX}nextpnr-${family} RUNTIME DESTINATION bin)
|
||||||
target_compile_definitions(${PROGRAM_PREFIX}nextpnr-${family} PRIVATE MAIN_EXECUTABLE)
|
target_compile_definitions(${PROGRAM_PREFIX}nextpnr-${family} PRIVATE MAIN_EXECUTABLE)
|
||||||
|
|
||||||
|
@ -23,6 +23,25 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "util.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
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
assertion_failure::assertion_failure(std::string msg, std::string expr_str, std::string filename, int line)
|
assertion_failure::assertion_failure(std::string msg, std::string expr_str, std::string filename, int line)
|
||||||
|
@ -33,7 +33,9 @@
|
|||||||
#include <boost/functional/hash.hpp>
|
#include <boost/functional/hash.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef NEXTPNR_H
|
#ifndef NEXTPNR_H
|
||||||
#define NEXTPNR_H
|
#define NEXTPNR_H
|
||||||
@ -647,6 +649,7 @@ struct DeterministicRNG
|
|||||||
|
|
||||||
struct BaseCtx
|
struct BaseCtx
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
// Lock to perform mutating actions on the Context.
|
// Lock to perform mutating actions on the Context.
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
boost::thread::id mutex_owner;
|
boost::thread::id mutex_owner;
|
||||||
@ -655,6 +658,7 @@ struct BaseCtx
|
|||||||
// method will lock/unlock it when its' released the main mutex to make
|
// method will lock/unlock it when its' released the main mutex to make
|
||||||
// sure the UI is not starved.
|
// sure the UI is not starved.
|
||||||
std::mutex ui_mutex;
|
std::mutex ui_mutex;
|
||||||
|
#endif
|
||||||
|
|
||||||
// ID String database.
|
// ID String database.
|
||||||
mutable std::unordered_map<std::string, int> *idstring_str_to_idx;
|
mutable std::unordered_map<std::string, int> *idstring_str_to_idx;
|
||||||
@ -706,28 +710,36 @@ struct BaseCtx
|
|||||||
// Must be called before performing any mutating changes on the Ctx/Arch.
|
// Must be called before performing any mutating changes on the Ctx/Arch.
|
||||||
void lock(void)
|
void lock(void)
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
mutex_owner = boost::this_thread::get_id();
|
mutex_owner = boost::this_thread::get_id();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void unlock(void)
|
void unlock(void)
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
NPNR_ASSERT(boost::this_thread::get_id() == mutex_owner);
|
NPNR_ASSERT(boost::this_thread::get_id() == mutex_owner);
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be called by the UI before rendering data. This lock will be
|
// Must be called by the UI before rendering data. This lock will be
|
||||||
// prioritized when processing code calls yield().
|
// prioritized when processing code calls yield().
|
||||||
void lock_ui(void)
|
void lock_ui(void)
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
ui_mutex.lock();
|
ui_mutex.lock();
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void unlock_ui(void)
|
void unlock_ui(void)
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
ui_mutex.unlock();
|
ui_mutex.unlock();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield to UI by unlocking the main mutex, flashing the UI mutex and
|
// 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.
|
// Must be called with the main lock taken.
|
||||||
void yield(void)
|
void yield(void)
|
||||||
{
|
{
|
||||||
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
unlock();
|
unlock();
|
||||||
ui_mutex.lock();
|
ui_mutex.lock();
|
||||||
ui_mutex.unlock();
|
ui_mutex.unlock();
|
||||||
lock();
|
lock();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
IdString id(const std::string &s) const { return IdString(this, s); }
|
IdString id(const std::string &s) const { return IdString(this, s); }
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
#include <Eigen/IterativeLinearSolvers>
|
#include <Eigen/IterativeLinearSolvers>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/thread.hpp>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -154,9 +153,14 @@ class HeAPPlacer
|
|||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
setup_solve_cells();
|
setup_solve_cells();
|
||||||
auto solve_startt = std::chrono::high_resolution_clock::now();
|
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); });
|
boost::thread xaxis([&]() { build_solve_direction(false, -1); });
|
||||||
build_solve_direction(true, -1);
|
build_solve_direction(true, -1);
|
||||||
xaxis.join();
|
xaxis.join();
|
||||||
|
#endif
|
||||||
auto solve_endt = std::chrono::high_resolution_clock::now();
|
auto solve_endt = std::chrono::high_resolution_clock::now();
|
||||||
solve_time += std::chrono::duration<double>(solve_endt - solve_startt).count();
|
solve_time += std::chrono::duration<double>(solve_endt - solve_startt).count();
|
||||||
|
|
||||||
@ -211,13 +215,16 @@ class HeAPPlacer
|
|||||||
// Heuristic: don't bother with threading below a certain size
|
// Heuristic: don't bother with threading below a certain size
|
||||||
auto solve_startt = std::chrono::high_resolution_clock::now();
|
auto solve_startt = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
if (solve_cells.size() < 500) {
|
#ifndef NPNR_DISABLE_THREADS
|
||||||
build_solve_direction(false, (iter == 0) ? -1 : iter);
|
if (solve_cells.size() >= 500) {
|
||||||
build_solve_direction(true, (iter == 0) ? -1 : iter);
|
|
||||||
} else {
|
|
||||||
boost::thread xaxis([&]() { build_solve_direction(false, (iter == 0) ? -1 : iter); });
|
boost::thread xaxis([&]() { build_solve_direction(false, (iter == 0) ? -1 : iter); });
|
||||||
build_solve_direction(true, (iter == 0) ? -1 : iter);
|
build_solve_direction(true, (iter == 0) ? -1 : iter);
|
||||||
xaxis.join();
|
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();
|
auto solve_endt = std::chrono::high_resolution_clock::now();
|
||||||
solve_time += std::chrono::duration<double>(solve_endt - solve_startt).count();
|
solve_time += std::chrono::duration<double>(solve_endt - solve_startt).count();
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <thread>
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "router1.h"
|
#include "router1.h"
|
||||||
@ -985,8 +984,22 @@ struct Router2
|
|||||||
}
|
}
|
||||||
if (ctx->verbose)
|
if (ctx->verbose)
|
||||||
log_info("%d/%d nets not multi-threadable\n", int(tcs.at(N).route_nets.size()), int(route_queue.size()));
|
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
|
// Multithreaded part of routing - quadrants
|
||||||
std::vector<std::thread> threads;
|
std::vector<boost::thread> threads;
|
||||||
for (int i = 0; i < Nq; i++) {
|
for (int i = 0; i < Nq; i++) {
|
||||||
threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i)); });
|
threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i)); });
|
||||||
}
|
}
|
||||||
@ -1007,6 +1020,7 @@ struct Router2
|
|||||||
for (auto &t : threads)
|
for (auto &t : threads)
|
||||||
t.join();
|
t.join();
|
||||||
threads.clear();
|
threads.clear();
|
||||||
|
#endif
|
||||||
// Singlethreaded part of routing - nets that cross partitions
|
// Singlethreaded part of routing - nets that cross partitions
|
||||||
// or don't fit within bounding box
|
// or don't fit within bounding box
|
||||||
for (auto st_net : tcs.at(N).route_nets)
|
for (auto st_net : tcs.at(N).route_nets)
|
||||||
@ -1130,4 +1144,4 @@ Router2Cfg::Router2Cfg(Context *ctx)
|
|||||||
perf_profile = ctx->setting<float>("router2/perfProfile", false);
|
perf_profile = ctx->setting<float>("router2/perfProfile", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -71,12 +71,13 @@ const char *chipdb_blob_25k = nullptr;
|
|||||||
const char *chipdb_blob_45k = nullptr;
|
const char *chipdb_blob_45k = nullptr;
|
||||||
const char *chipdb_blob_85k = 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)
|
const char *mmap_file(int index, const char *filename)
|
||||||
{
|
{
|
||||||
try {
|
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())
|
if (!blob_files[index].is_open())
|
||||||
log_error("Unable to read chipdb %s\n", filename);
|
log_error("Unable to read chipdb %s\n", filename);
|
||||||
return (const char *)blob_files[index].data();
|
return (const char *)blob_files[index].data();
|
||||||
|
@ -70,8 +70,9 @@ if (NOT EXTERNAL_CHIPDB)
|
|||||||
target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w)
|
target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w)
|
||||||
set(PREV_DEV_CC_BBA_DB)
|
set(PREV_DEV_CC_BBA_DB)
|
||||||
foreach (dev ${devices})
|
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_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_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/constids.inc)
|
||||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/gfx.h)
|
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/gfx.h)
|
||||||
if (PREGENERATED_BBA_PATH)
|
if (PREGENERATED_BBA_PATH)
|
||||||
@ -85,11 +86,19 @@ if (NOT EXTERNAL_CHIPDB)
|
|||||||
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
||||||
DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
|
DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
|
||||||
)
|
)
|
||||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
if(USE_C_EMBED)
|
||||||
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
|
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||||
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
|
COMMAND bbasm --e ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new ${DEV_BIN_DB}
|
||||||
DEPENDS bbasm ${DEV_CC_BBA_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()
|
endif()
|
||||||
if (SERIALIZE_CHIPDB)
|
if (SERIALIZE_CHIPDB)
|
||||||
set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB})
|
set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB})
|
||||||
|
@ -57,12 +57,12 @@ const char *chipdb_blob_5k = nullptr;
|
|||||||
const char *chipdb_blob_u4k = nullptr;
|
const char *chipdb_blob_u4k = nullptr;
|
||||||
const char *chipdb_blob_8k = 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)
|
const char *mmap_file(int index, const char *filename)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
blob_files[index].open(filename);
|
blob_files[index].open(filename, boost::iostreams::mapped_file::priv);
|
||||||
if (!blob_files[index].is_open())
|
if (!blob_files[index].is_open())
|
||||||
log_error("Unable to read chipdb %s\n", filename);
|
log_error("Unable to read chipdb %s\n", filename);
|
||||||
return (const char *)blob_files[index].data();
|
return (const char *)blob_files[index].data();
|
||||||
|
Loading…
Reference in New Issue
Block a user