Skip to content

Commit

Permalink
[emscripten] build using emscripten possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Tran committed Dec 14, 2020
1 parent 42b75a6 commit 323a8b3
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 9 deletions.
8 changes: 7 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ include(FastDownwardMacros)

fast_downward_default_to_release_build()
project(fast-downward)
fast_downward_report_bitwidth()
if (EMSCRIPTEN)
MESSAGE("Configuring emscripten build.")
else()
MESSAGE("Configuring native build.")
fast_downward_report_bitwidth()
endif()

# Due to a bug in cmake, configuration types are only set up correctly on the second cmake run.
# This means that cmake has to be called twice for multi-config generators like Visual Studio.
fast_downward_set_configuration_types()
Expand Down
4 changes: 3 additions & 1 deletion src/cmake_modules/FastDownwardMacros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ macro(fast_downward_set_compiler_flags)
endmacro()

macro(fast_downward_set_linker_flags)
if(UNIX)
if (EMSCRIPTEN)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 --bind -s INVOKE_RUN=0 -s EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']")
elseif(UNIX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g")
endif()
endmacro()
Expand Down
33 changes: 33 additions & 0 deletions src/javascript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Planning in the browser
- Tested with emscripten version 2.0.8

## Building the JS+WASM files:

- make sure the emscripten sdk is installed and executables (mainly em++ and emcmake) are on the PATH.

- cmake needs to be installed

- you can use the `./configure_cmake.sh` file to configure cmake for an emscripten or native build using `./configure_cmake.sh emscripten` or `./configure_cmake.sh native` respectively.

- use above mentioned to switch to emscripten build type, this executes `cmake` with custom parameters: `CMAKE_CXX_COMPILER=em++` and `EMSCRIPTEN=1`.

- build the WebAssembly and Javascript files using `make` in the `/src` directory, output files can be found in `src/bin`.

- as the `CMAKE_CXX_COMPILER` stays to be `em++`, you need to change it back in case you want to build the native version. The configuration script assums `g++` to be your default native compiler.

## Using the JS+WASM files:

- instead of using STDIN to pass the translated `output.sas` file to the program, we added the option to use the `--input` argument, which takes a file handle and treats it as input stream

- thus, before calling the search, but after the Module was loaded, we need to save the translated output file on the browsers virtual filesystem

- There are multiple ways to do this, an example however can be found under `usage_example.js` loaded at the end of `index.html`. The example assumes `output.sas`, `downward.js` as well as `downward.wasm` files in the working directory

- the `runFastDownward()` function executes the example. Make sure to have added the necessary files in beforehand.

## Debug

- you can use the included function `Module.getExceptionMessage(pointer)` to get a more detailed exception message in case you need it for debugging:
in the `downward.js` file, you need to replace the original exception logging by the result of `Module.getExceptionMessage(pointer)`. I.e. replace `err('exception thrown: ' + toLog);` by
`err('exception thrown: ' + Module.getExceptionMessage(toLog));`
`
31 changes: 31 additions & 0 deletions src/javascript/configure_cmake.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
cd "$(dirname "$0")/../"
if [ $# -eq 0 ]; then
echo "No arguments provided"
echo "Run either with "
echo "./configure_cmake.sh emscripten"
echo "or "
echo "./configure_cmake.sh native"
exit 1
fi

if [ "$1" = "emscripten" ];
then
echo 'setting cmake settings for building with emscripten'
# in case of switch we need to execute twice, as cmake notices changed variables and executes itself again but fails as EMSCRIPTEN variable is not passed
emcmake cmake -DEMSCRIPTEN=True -DCMAKE_CXX_COMPILER=$(which em++) .
emcmake cmake -DEMSCRIPTEN=True -DCMAKE_CXX_COMPILER=$(which em++) .
elif [ "$1" = "native" ];
then
echo "setting cmake settings for native build"
# in case of switch we need to execute twice, as cmake notices changed variables and executes itself again but fails as EMSCRIPTEN variable is not passed
cmake -DEMSCRIPTEN=False -DCMAKE_CXX_COMPILER=$(which g++) .
cmake -DEMSCRIPTEN=False -DCMAKE_CXX_COMPILER=$(which g++) .
else
echo "argument not recognized"
echo "Run either with "
echo "./configure_cmake.sh emscripten"
echo "or "
echo "./configure_cmake.sh native"
exit 1
fi
13 changes: 13 additions & 0 deletions src/javascript/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">

<title>Planning in the browser</title>
</head>
<body>
<h1>Planning in the browser</h1>
</body>

<script type="text/javascript" src='usage_example.js'></script>
</html>
39 changes: 39 additions & 0 deletions src/javascript/usage_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
FILE_WASM='downward.wasm'
FILE_JAVASCRIPT='downward.js'
PATH_TO_INPUT='output.sas'

// Load the javascript file containing the Downward module
downwardscript = document.createElement('script');
downwardscript.src = FILE_JAVASCRIPT;
downwardscript.onload = function() {
// Fetch input data via XHR and save it on the browsers virtual filesystem once it is loaded
console.log("Fetching data from " + PATH_TO_INPUT);
var inputURL = PATH_TO_INPUT;
var inputXHR = new XMLHttpRequest();
inputXHR.open('GET', inputURL, true);
inputXHR.responseType = 'text';
inputXHR.onload = function() {
if (inputXHR.status === 200 || inputXHR.status === 0) {
let data = new TextEncoder().encode(inputXHR.response);
let stream = FS.open('output.sas', 'w+');
FS.write(stream, data, 0, data.length, 0);
FS.close(stream);
console.log('wrote to output.sas');
}
}
inputXHR.send();
}
document.body.appendChild(downwardscript);

// function to start the actual program
function runFastDownward() {
// define parameters and split them to a list
let parameter_string = "--search astar(lmcut()) --input output.sas"
let parameter_list = parameter_string.split(" ")

Module.callMain(parameter_list);

// results are saved on the virtual filesystem in the browser
let result = new TextDecoder().decode(FS.readFile('sas_plan'));
console.log(result);
}
3 changes: 3 additions & 0 deletions src/search/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ fast_downward_set_linker_flags()
# Collect source files needed for the active plugins.
include("${CMAKE_CURRENT_SOURCE_DIR}/DownwardFiles.cmake")
add_executable(downward ${PLANNER_SOURCES})
if (EMSCRIPTEN)
set_target_properties(downward PROPERTIES SUFFIX ".js")
endif()

## == Includes ==

Expand Down
32 changes: 32 additions & 0 deletions src/search/command_line.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
#include "options/predefinitions.h"
#include "options/registries.h"
#include "utils/strings.h"
#include "string.h"

#include <algorithm>
#include <vector>
#include <fstream>

using namespace std;

Expand Down Expand Up @@ -64,6 +66,11 @@ static shared_ptr<SearchEngine> parse_cmd_line_aux(
OptionParser parser(sanitize_arg_string(args[i]), registry,
predefinitions, dry_run);
engine = parser.start_parsing<shared_ptr<SearchEngine>>();
} else if (arg == "--input") {
if (is_last)
throw ArgError("missing argument after --input");
++i;
// do nothing as task already parsed in parse_root_task_input
} else if (arg == "--help" && dry_run) {
cout << "Help:" << endl;
bool txt2tags = false;
Expand Down Expand Up @@ -149,6 +156,31 @@ shared_ptr<SearchEngine> parse_cmd_line(
return parse_cmd_line_aux(args, registry, dry_run);
}

stringstream getBufferFrom(string filepath) {
ifstream in (filepath);
if (!in.is_open()) {
cout << "file " << filepath << "could not be opened " << endl;
throw ArgError("file could not be opened");
}
stringstream buffer;
buffer << in.rdbuf();
return buffer;
}

stringstream parse_root_task_input(int argc, const char **argv){
std::string input_filename = "";
for (int i = 1; i < argc; ++i) {
bool is_last = (i == argc - 1);
if (strcmp(argv[i],"--input") == 0) {
if (is_last) throw ArgError("missing argument after --input");
++i;
input_filename = argv[i];
return getBufferFrom(input_filename);
}
}
stringstream buffer;
return buffer;
}

string usage(const string &progname) {
return "usage: \n" +
Expand Down
2 changes: 2 additions & 0 deletions src/search/command_line.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ extern std::shared_ptr<SearchEngine> parse_cmd_line(
int argc, const char **argv, options::Registry &registry, bool dry_run,
bool is_unit_cost);

extern std::stringstream parse_root_task_input(int argc, const char **argv);

extern std::string usage(const std::string &progname);

#endif
7 changes: 6 additions & 1 deletion src/search/planner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ int main(int argc, const char **argv) {
bool unit_cost = false;
if (static_cast<string>(argv[1]) != "--help") {
utils::g_log << "reading input..." << endl;
tasks::read_root_task(cin);
stringstream acin = parse_root_task_input(argc, argv);
if (acin.peek() != char_traits<char>::eof()) {
tasks::read_root_task(acin);
} else {
tasks::read_root_task(cin);
}
utils::g_log << "done reading input!" << endl;
TaskProxy task_proxy(*tasks::g_root_task);
unit_cost = task_properties::is_unit_cost(task_proxy);
Expand Down
6 changes: 5 additions & 1 deletion src/search/utils/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
#define LINUX 0
#define OSX 1
#define WINDOWS 2
#define JAVASCRIPT 3

#if defined(_WIN32)
#if defined(__EMSCRIPTEN__)
#define OPERATING_SYSTEM JAVASCRIPT
#include "system_unix.h"
#elif defined(_WIN32)
#define OPERATING_SYSTEM WINDOWS
#include "system_windows.h"
#elif defined(__APPLE__)
Expand Down
27 changes: 22 additions & 5 deletions src/search/utils/system_unix.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "system.h"

#if OPERATING_SYSTEM == LINUX || OPERATING_SYSTEM == OSX
#if OPERATING_SYSTEM == LINUX || OPERATING_SYSTEM == OSX || OPERATING_SYSTEM == JAVASCRIPT
/*
NOTE:
Methods with the suffix "_reentrant" are meant to be used in event
Expand Down Expand Up @@ -39,6 +39,10 @@
#include <mach/mach.h>
#endif

#if OPERATING_SYSTEM == JAVASCRIPT
#include <emscripten/bind.h>
#endif

using namespace std;

namespace utils {
Expand All @@ -60,6 +64,17 @@ void write_reentrant(int filedescr, const char *message, int len) {
}
}

#if OPERATING_SYSTEM == JAVASCRIPT
using namespace emscripten;
std::string getExceptionMessage(intptr_t exceptionPtr) {
return std::string(reinterpret_cast<std::exception *>(exceptionPtr)->what());
}

EMSCRIPTEN_BINDINGS(fooBinding) {
emscripten::function("getExceptionMessage", &utils::getExceptionMessage);
};
#endif

void write_reentrant_str(int filedescr, const char *message) {
write_reentrant(filedescr, message, strlen(message));
}
Expand Down Expand Up @@ -97,8 +112,8 @@ void print_peak_memory_reentrant() {
write_reentrant_str(STDOUT_FILENO, "Peak memory: ");
write_reentrant_int(STDOUT_FILENO, get_peak_memory_in_kb());
write_reentrant_str(STDOUT_FILENO, " KB\n");
#else

#endif
#if OPERATING_SYSTEM == LINUX
int proc_file_descr = TEMP_FAILURE_RETRY(open("/proc/self/status", O_RDONLY));
if (proc_file_descr == -1) {
write_reentrant_str(
Expand Down Expand Up @@ -149,7 +164,7 @@ void print_peak_memory_reentrant() {

#if OPERATING_SYSTEM == LINUX
void exit_handler(int, void *) {
#elif OPERATING_SYSTEM == OSX
#elif OPERATING_SYSTEM == OSX || OPERATING_SYSTEM == JAVASCRIPT
void exit_handler() {
#endif
print_peak_memory_reentrant();
Expand Down Expand Up @@ -197,7 +212,7 @@ int get_peak_memory_in_kb() {
&t_info_count) == KERN_SUCCESS) {
memory_in_kb = t_info.virtual_size / 1024;
}
#else
#elif OPERATING_SYSTEM == LINUX
ifstream procfile;
procfile.open("/proc/self/status");
string word;
Expand All @@ -214,8 +229,10 @@ int get_peak_memory_in_kb() {
memory_in_kb = -1;
#endif

#if OPERATING_SYSTEM != JAVASCRIPT
if (memory_in_kb == -1)
cerr << "warning: could not determine peak memory" << endl;
#endif
return memory_in_kb;
}

Expand Down

0 comments on commit 323a8b3

Please sign in to comment.