Skip to content

Commit

Permalink
update wasm build
Browse files Browse the repository at this point in the history
updated module interface in preamble.js,
build with support for runtime exit and simd

[skip_fishtest]
  • Loading branch information
dhbloo committed Jul 19, 2024
1 parent 6a077fd commit 9bd60a1
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 148 deletions.
56 changes: 37 additions & 19 deletions Rapfi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ option(USE_BMI2 "Enable BMI2 instruction" OFF)
option(USE_VNNI "Enable AVX512_VNNI/AVX_VNNI instruction" OFF)
option(USE_NEON "Enable NEON instruction" OFF)
option(USE_NEON_DOTPROD "Enable NEON dotprod instruction" OFF)
option(USE_WASM_SIMD "Enable WebAssembly SIMD instruction" OFF)
option(USE_WASM_SIMD_RELAXED "Enable WebAssembly Relaxed SIMD instruction" OFF)

option(ENABLE_LTO "Enable link time optimization" OFF)
option(FORCE_ASSERTION "Enable force assertion for all build type" OFF)
Expand Down Expand Up @@ -173,17 +175,12 @@ if(EMSCRIPTEN)
"Command modules is not supported for wasm build. "
"Must set option NO_COMMAND_MODULES to ON.")
endif()
set(USE_AVX2 OFF)
set(USE_AVX512 OFF)
set(USE_BMI2 OFF)
set(USE_VNNI OFF)

target_compile_options(rapfi
PRIVATE "-fexceptions"
PRIVATE "-flto"
# PRIVATE "-msimd128"
# PRIVATE "-msse"
)
if(USE_SSE OR USE_AVX2 OR USE_AVX512 OR USE_BMI2 OR USE_VNNI)
message(FATAL_ERROR
"SSE, AVX2, AVX512, BMI2, VNNI instruction set is not supported for wasm build.")
endif()

target_compile_options(rapfi PRIVATE "-fexceptions" PRIVATE "-flto")
string(CONCAT EMCC_FLAGS
"-fexceptions "
"-flto "
Expand All @@ -192,31 +189,52 @@ if(EMSCRIPTEN)
"--closure=1 "
"-s MODULARIZE=1 "
"-s EXPORT_NAME=Rapfi "
"-s ENVIRONMENT='web,worker' "
"-s EXPORTED_FUNCTIONS=\"['_gomocupLoopOnce']\" "
"-s EXPORTED_FUNCTIONS=\"['_main','_gomocupLoopOnce','_emscripten_force_exit']\" "
"-s EXPORTED_RUNTIME_METHODS=\"['cwrap']\" "
"-s DEMANGLE_SUPPORT=1 "
"-s EXIT_RUNTIME=1 "
"-s ALLOW_MEMORY_GROWTH=1 "
"-s INITIAL_MEMORY=134217728 "
"-s MAXIMUM_MEMORY=1073741824 "
"-s IMPORTED_MEMORY=1 "
"-s STACK_SIZE=2MB "
)

set(WASM_OUTPUT_NAME "rapfi")
if(NO_MULTI_THREADING)
set_target_properties(rapfi PROPERTIES OUTPUT_NAME "rapfi-single")
string(CONCAT WASM_OUTPUT_NAME ${WASM_OUTPUT_NAME} "-single")
else()
set_target_properties(rapfi PROPERTIES OUTPUT_NAME "rapfi-multi")
string(CONCAT WASM_OUTPUT_NAME ${WASM_OUTPUT_NAME} "-multi")
target_compile_options(rapfi PRIVATE "-pthread")
string(CONCAT EMCC_FLAGS ${EMCC_FLAGS}
"-s USE_PTHREADS=1 "
"-s PTHREAD_POOL_SIZE=1+navigator.hardwareConcurrency "
)
endif()

if(USE_WASM_SIMD OR USE_WASM_SIMD_RELAXED)
string(CONCAT WASM_OUTPUT_NAME ${WASM_OUTPUT_NAME} "-simd128")
target_compile_options(rapfi PRIVATE "-msimd128")
target_compile_definitions(rapfi PRIVATE USE_WASM_SIMD)
if(USE_WASM_SIMD_RELAXED)
string(CONCAT WASM_OUTPUT_NAME ${WASM_OUTPUT_NAME} "-relaxed")
target_compile_options(rapfi PRIVATE "-mrelaxed-simd")
target_compile_definitions(rapfi PRIVATE USE_WASM_SIMD_RELAXED)
endif()
endif()

if(FORCE_ASSERTION)
string(CONCAT EMCC_FLAGS ${EMCC_FLAGS} "-s ASSERTIONS=1 ")
endif()
if(CMAKE_BUILD_TYPE EQUAL "DEBUG")
string(CONCAT EMCC_FLAGS ${EMCC_FLAGS}
"--emit-symbol-map "
)
endif()

message(STATUS "WASM_OUTPUT_NAME: ${WASM_OUTPUT_NAME}")

set_target_properties(rapfi PROPERTIES LINK_FLAGS ${EMCC_FLAGS})
set_target_properties(rapfi PROPERTIES
OUTPUT_NAME ${WASM_OUTPUT_NAME}
LINK_FLAGS ${EMCC_FLAGS}
)

endif()

Expand Down
61 changes: 33 additions & 28 deletions Rapfi/command/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/

#include "command.h"

Expand Down Expand Up @@ -101,40 +101,46 @@ namespace CommandLine {

} // namespace CommandLine

/// The path to the config file
/// ConfigPath must be absolute or relative to current working directory.
/// Can be empty which means we will try to load from the default config path.
std::filesystem::path configPath;
std::filesystem::path overrideModelPath;
bool allowInternalConfigFallback = true;

/// loadConfig() loads config from configPath and optional override
/// model path. configPath must be absolute or relative to current
/// working directory.
/// Whether to allow fallback to internal config if the specified file is not found.
bool allowInternalConfig = true;

/// loadConfig() trys to load config according to the following order:
/// 1. Load from the current config path. If config file exists but fails to load,
/// it will not continue to load other config.
/// 2. Try to load from the default config path, which is the "config.toml" in the
/// current working directory or the binary executable directory.
/// 3. If the above two steps fail, and allowInternalConfig is true, it will
/// try to load from the internal config string. Internal config is only available
/// when the program is built with it.
bool loadConfig()
{
bool success = false;
const bool loadOverrideModel = !overrideModelPath.empty();
bool success = false;
std::ifstream configFile(configPath);

if (configFile.is_open()) {
MESSAGEL("Load config from " << configPath);
success = Config::loadConfig(configFile, loadOverrideModel);
if (!success)
return false;
if (loadOverrideModel)
success = loadModelFromFile(overrideModelPath);
}
else if (!allowInternalConfigFallback) {
ERRORL("Unable to open config file: " << configPath);
return false;
success = Config::loadConfig(configFile);
}

if (!success) {
if (!Config::InternalConfig.empty()) {
std::istringstream internalConfig(Config::InternalConfig);
success = Config::loadConfig(internalConfig);
else {
std::filesystem::path defaultConfigPath = CommandLine::getDefaultConfigPath();
configFile.open(defaultConfigPath);
if (configFile.is_open()) {
MESSAGEL("Load config from " << defaultConfigPath);
success = Config::loadConfig(configFile);
}
else if (allowInternalConfig) {
if (!Config::InternalConfig.empty()) {
std::istringstream internalConfig(Config::InternalConfig);
success = Config::loadConfig(internalConfig);
}
else
ERRORL("This version is not built with an internal config. "
"Must specify an external config!");
}
else
ERRORL("This version is not built with an internal config. "
"Must specify an external config!");
}

if (success && Config::ClearHashAfterConfigLoaded)
Expand All @@ -156,9 +162,8 @@ std::filesystem::path getModelFullPath(std::filesystem::path modelPath)
// If not succeeded, try to open from config directory if path is relative
if (std::filesystem::path(modelPath).is_relative()) {
std::filesystem::path configModelPath = configPath.remove_filename() / modelPath;
if (std::filesystem::exists(configModelPath)) {
if (std::filesystem::exists(configModelPath))
return configModelPath;
}
}

return modelPath;
Expand Down
7 changes: 1 addition & 6 deletions Rapfi/command/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ namespace CommandLine {
// Config loading

extern std::filesystem::path configPath;
extern std::filesystem::path overrideModelPath;
extern bool allowInternalConfigFallback;
extern bool allowInternalConfig;

bool loadConfig();
std::filesystem::path getModelFullPath(std::filesystem::path modelPath);
Expand All @@ -47,10 +46,6 @@ bool loadModelFromFile(std::filesystem::path modelPath);
// -------------------------------------------------
// Gomocup protocol

namespace GomocupProtocol {
extern "C" bool gomocupLoopOnce();
}

void gomocupLoop();

// -------------------------------------------------
Expand Down
45 changes: 33 additions & 12 deletions Rapfi/command/gomocup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ void getOption()
}
else if (token == "EVALUATOR_DRAW_BLACK_WINRATE") {
std::cin >> Config::EvaluatorDrawBlackWinRate;
Config::EvaluatorDrawBlackWinRate = std::clamp(Config::EvaluatorDrawBlackWinRate, 0.0f, 1.0f);
Config::EvaluatorDrawBlackWinRate =
std::clamp(Config::EvaluatorDrawBlackWinRate, 0.0f, 1.0f);
}
else if (token == "EVALUATOR_DRAW_RATIO") {
std::cin >> Config::EvaluatorDrawRatio;
Expand Down Expand Up @@ -453,10 +454,9 @@ void loadHash()

void reloadConfig()
{
configPath = readPathFromInput();
overrideModelPath.clear();
allowInternalConfigFallback = configPath.empty();
if (allowInternalConfigFallback)
configPath = readPathFromInput();
allowInternalConfig = configPath.empty();
if (allowInternalConfig)
MESSAGEL("No external config specified, reload internal config.");

if (!loadConfig())
Expand Down Expand Up @@ -1242,7 +1242,7 @@ void loadModel()

/// Enter protocol loop once and fetch and execute one command from stdin.
/// @return True if program should exit now.
extern "C" bool gomocupLoopOnce()
bool runProtocol()
{
std::string cmd;
std::cin >> cmd;
Expand All @@ -1261,9 +1261,10 @@ extern "C" bool gomocupLoopOnce()
};

// clang-format off
if (cmd == "STOP") { Search::Threads.stopThinking(); return false; }
else if (cmd == "YXSTOP") { Search::Threads.stopThinking(); return false; }
else if (thinking) return false;
if (cmd == "END") { Search::Threads.stopThinking(); return true; }
else if (cmd == "STOP") { Search::Threads.stopThinking(); return false; }
else if (cmd == "YXSTOP") { Search::Threads.stopThinking(); return false; }
else if (thinking) return false;

#ifdef MULTI_THREADING
std::lock_guard<std::mutex> lock(mtx);
Expand All @@ -1278,8 +1279,7 @@ extern "C" bool gomocupLoopOnce()
&& cmd != "YXQUERYDATABASETEXT"
&& cmd != "YXQUERYDATABASEALLT") Search::Threads.stopThinking();

if (cmd == "END") return true;
else if (cmd == "ABOUT") std::cout << getEngineInfo() << std::endl;
if (cmd == "ABOUT") std::cout << getEngineInfo() << std::endl;
else if (cmd == "START") start();
else if (cmd == "RECTSTART") rectStart();
else if (cmd == "INFO") getOption();
Expand Down Expand Up @@ -1337,10 +1337,16 @@ extern "C" bool gomocupLoopOnce()
/// This will only return after all searching threads have ended.
void Command::gomocupLoop()
{
#ifdef __EMSCRIPTEN__
// We do not run infinite loop in wasm build, instead we manually call
// gomocupLoopOnce() for each command to avoid hanging the main thread.
return;
#endif

// Init tuning parameter table
Tuning::TuneMap::init();

while (!GomocupProtocol::gomocupLoopOnce()) {
while (!GomocupProtocol::runProtocol()) {
#ifdef MULTI_THREADING
// For multi-threading build, yield before reading the next
// command to avoid possible busy waiting.
Expand All @@ -1351,3 +1357,18 @@ void Command::gomocupLoop()
// If there is any thread still running, wait until they exited.
Search::Threads.waitForIdle();
}


#ifdef __EMSCRIPTEN__
#include <emscripten.h>

extern "C" void gomocupLoopOnce()
{
bool shouldExit = Command::GomocupProtocol::runProtocol();
if (shouldExit) {
// If there is any thread still running, wait until they exited.
Search::Threads.waitForIdle();
emscripten_force_exit(EXIT_SUCCESS);
}
}
#endif
8 changes: 3 additions & 5 deletions Rapfi/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ void readValueModel(const cpptoml::table &t, SetterType setter);
/// @param skipModelLoading Whether to skip model loading. Can be useful when
/// model is loaded separately from a binary file.
/// @return Returns true if loading succeeded, otherwise returns false.
bool Config::loadConfig(std::istream &configStream, bool skipModelLoading)
bool Config::loadConfig(std::istream &configStream)
{
Search::Threads.setupEvaluator(nullptr);
Search::Threads.setupDatabase(nullptr);
Expand All @@ -251,10 +251,8 @@ bool Config::loadConfig(std::istream &configStream, bool skipModelLoading)
if (auto database = c->get_table("database"))
readDatabase(*database);

if (!skipModelLoading) {
if (auto model = c->get_table("model"))
readModel(*model);
}
if (auto model = c->get_table("model"))
readModel(*model);
}
catch (const std::exception &e) {
ERRORL("Failed to load config: " << e.what());
Expand Down
4 changes: 2 additions & 2 deletions Rapfi/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ inline Value winRateToValue(float winRate)
/// @param skipModelLoading Whether to skip model loading. This can be set when
/// model is overrided in command line.
/// @return True if config is loaded successfully.
bool loadConfig(std::istream &configStream, bool skipModelLoading = false);
bool loadConfig(std::istream &configStream);

/// Load a LZ4 compressed classical model from a binary stream.
/// Load a LZ4 compressed classical evaluation model from a binary stream.
/// @return Returns true if loaded successfully, otherwise returns false.
bool loadModel(std::istream &inStream);

Expand Down
36 changes: 21 additions & 15 deletions Rapfi/emscripten/preamble.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
Module["sendCommand"] = Module["sendCommand"] || null;
Module["receiveStdout"] = Module["receiveStdout"] || ((o) => console.log(o));
Module["receiveStderr"] = Module["receiveStderr"] || ((o) => console.error(o));
Module["onEngineReady"] = Module["onEngineReady"] || (() => {});
Module["terminate"] = Module["terminate"] || null;
Module["onReceiveStdout"] = Module["onReceiveStdout"] || ((o) => console.log(o));
Module["onReceiveStderr"] = Module["onReceiveStderr"] || ((o) => console.error(o));
Module["onExit"] = Module["onExit"] || ((code) => console.log("exited with code " + code));
Module["noExitRuntime"] = true; // Only exit when we explicitly want to do so

if (!Module["preRun"]) Module["preRun"] = [];
Module["preRun"].push(function () {
let stdin_buffer = { buffer: "", index: 0 };
let stdin_buffer = { data: "", index: 0 };
let stdout_buffer = "";
let stderr_buffer = "";

function stdin() {
if (stdin_buffer.index < stdin_buffer.buffer.length)
return stdin_buffer.buffer.charCodeAt(stdin_buffer.index++);
if (stdin_buffer.index < stdin_buffer.data.length)
return stdin_buffer.data.charCodeAt(stdin_buffer.index++);
else return null;
}

newline_charcode = "\n".charCodeAt(0)
stdout_fn = Module["onReceiveStdout"]
stderr_fn = Module["onReceiveStderr"]

function stdout(char) {
if (!char || char == "\n".charCodeAt(0)) {
Module["receiveStdout"](stdout_buffer);
if (!char || char == newline_charcode) {
stdout_fn(stdout_buffer);
stdout_buffer = "";
} else stdout_buffer += String.fromCharCode(char);
}

function stderr(char) {
if (!char || char == "\n".charCodeAt(0)) {
Module["receiveStderr"](stderr_buffer);
if (!char || char == newline_charcode) {
stderr_fn(stderr_buffer);
stderr_buffer = "";
} else stderr_buffer += String.fromCharCode(char);
}

// Redirect stdin, stdout, stderr
FS.init(stdin, stdout, stderr);
let execute_command = Module["cwrap"]("gomocupLoopOnce", "number", []);
const execute_command = Module["cwrap"]("gomocupLoopOnce", "number", []);
Module["sendCommand"] = function (data) {
stdin_buffer.buffer = data + "\n";
stdin_buffer.data = data + "\n";
stdin_buffer.index = 0;
execute_command();
};
Module["terminate"] = function () {
Module["_emscripten_force_exit"](0);
};
});
Module["onRuntimeInitialized"] = function () {
Module["onEngineReady"]();
};
Loading

0 comments on commit 9bd60a1

Please sign in to comment.