enabled asmjs as additional build target, and added code to monero_utils/MyMoneroCoreBridge.js with preliminary wasm support detection and fallback to loading separated asm.js file (and different MyMoneroCoreCpp for ASMJS) to remove need for wasm users to load asmjs and asmjs loading code; the asm.js file is separated out from MyMoneroCoreCpp_ASMJS.js with --separate-asm to improve initial load time and, more importantly here, to enable bin/package_browser_js and other webpack like builds to keep the majority of the asmjs separate from the main mymonero_core.js (or e.g. mymonero_app_browser.js) bundle, so that wasm users don't have to load the asmjs along with the main app bundle - however, the overhead of bundling MyMoneroCoreCpp_ASMJS.js into such a main bundle does still exist. in near future, it would be nice to add an option to webpack.config.browser.common.js or bin/package_browser_js or possible just inheriting/alternate versions of those which can opt to remove asmjs support and throw an exception or alert if it's simply not supported - this is probably what MyMonero will move to within a year or two as legacy browsers fade from usage

pull/66/head
Paul Shapiro 6 years ago
parent 64bc1cfa96
commit 18b281e134

1
.gitignore vendored

@ -14,3 +14,4 @@ contrib/boost-sdk/bin.v2
tests/web/mymonero-core.js
tests/web/mymonero_core_js
tests/web/mymonero-core.js.map

@ -1,5 +1,7 @@
monero_utils/MyMoneroCoreCpp.js
monero_utils/MyMoneroCoreCpp.wasm
monero_utils/MyMoneroCoreCpp_WASM.js
monero_utils/MyMoneroCoreCpp_WASM.wasm
monero_utils/MyMoneroCoreCpp_ASMJS.js
monero_utils/MyMoneroCoreCpp_ASMJS.asm.js
package.json
yarn.lock

@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.4.1)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
project(MyMoneroCoreCpp)
option(MM_EM_ASMJS "Build for asmjs instead of wasm" OFF) # NOTE: if you change this, you should delete your cmake build cache
#
include_directories("build/boost/include") # must exist already - run bin/build-boost-emscripten.sh
#
@ -184,8 +183,10 @@ set_target_properties(
#
#
set (
EMCC_LINKED_FLAGS___MM_BASE
EMCC_LINKER_FLAGS___BASE
#unsure if the -I...boost..include is necessary here due to include above
#
# TODO? does EXPORT_NAME need to be the same for both targets? (or should it be set per-target with _WASM, _ASMJS?)
"-Wall -std=c++11 \
--bind -s MODULARIZE=1 \
-s 'EXPORT_NAME=\"MyMoneroCoreCpp\"' \
@ -206,64 +207,75 @@ set (
#
# -s SAFE_HEAP=1
)
if (MM_EM_ASMJS)
message(STATUS "> Building asmjs")
set(
EMCC_LINKER_FLAGS
"${EMCC_LINKED_FLAGS___MM_BASE} \
set(
EMCC_LINKER_FLAGS__ASMJS
"${EMCC_LINKER_FLAGS___BASE} \
-s WASM=0 \
-Os \
--separate-asm \
-s AGGRESSIVE_VARIABLE_ELIMINATION=1 \
-s \"BINARYEN_METHOD='asmjs'\" \
-s ALLOW_MEMORY_GROWTH=0 \
--memory-init-file 0 \
"
# --memory-init-file is explicitly set to 0 because optimization level is Oz, and some clients like React Native have trouble loading .mem files
# --closure 1 \
)
else ()
message(STATUS "> Building wasm")
set(
EMCC_LINKER_FLAGS
"${EMCC_LINKED_FLAGS___MM_BASE} \
# --memory-init-file is explicitly set to 0 because optimization level is Oz, and some clients like React Native have trouble loading .mem files
# .... it also didn't seem to have a giant impact on perf. but this should be tested again
#
# --closure 1 \
)
set(
EMCC_LINKER_FLAGS__WASM
"${EMCC_LINKER_FLAGS___BASE} \
-s WASM=1 \
-Oz \
-s \"BINARYEN_METHOD='native-wasm'\" \
-s ALLOW_MEMORY_GROWTH=1 \
--post-js ${CMAKE_CURRENT_LIST_DIR}/src/module-post.js \
"
# -g \
)
endif ()
# -g \
)
#
# -s EXPORTED_FUNCTIONS='[\"_sc_reduce\", \"_sc_reduce32\", \"_sc_check\", \"_sc_add\", \"_ge_fromfe_frombytes_vartime\", \"_ge_mul8\", \"_ge_p1p1_to_p3\", \"_ge_p3_tobytes\", \"_ge_scalarmult\", \"_ge_tobytes\", \"_sc_sub\", \"_sc_mulsub\", \"_ge_scalarmult_base\", \"_sc_0\", \"_ge_double_scalarmult_base_vartime\", \"_ge_double_scalarmult_precomp_vartime\", \"_ge_frombytes_vartime\", \"_ge_dsm_precomp\"]' \
message(STATUS "EMCC_LINKER_FLAGS ${EMCC_LINKER_FLAGS}")
# extract the filename without an extension (NAME_WE)
add_executable(
MyMoneroCoreCpp
${SRC_FILES}
#
#
message(STATUS "EMCC_LINKER_FLAGS__WASM ${EMCC_LINKER_FLAGS__WASM}")
message(STATUS "EMCC_LINKER_FLAGS__ASMJS ${EMCC_LINKER_FLAGS__ASMJS}")
#
add_executable(MyMoneroCoreCpp_WASM ${SRC_FILES})
add_executable(MyMoneroCoreCpp_ASMJS ${SRC_FILES})
#
set_target_properties(MyMoneroCoreCpp_WASM PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS__WASM}")
set_target_properties(MyMoneroCoreCpp_ASMJS PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS__ASMJS}")
#
# set_target_properties(MyMoneroCoreCpp_WASM PROPERTIES SUFFIX ".html")
# set_target_properties(MyMoneroCoreCpp_ASMJS PROPERTIES SUFFIX ".html")
#
# boost_atomic
# boost_date_time
# boost_filesystem
# boost_locale
# boost_program_options
# boost_regex
# boost_serialization
# boost_signals
# boost_timer
# boost_wserialization
#
target_link_libraries(
MyMoneroCoreCpp_WASM
#
boost_chrono
boost_system
boost_thread
#
${log-lib}
)
set_target_properties(MyMoneroCoreCpp PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS}")
# set_target_properties(MyMoneroCoreCpp PROPERTIES SUFFIX ".html")
target_link_libraries(
MyMoneroCoreCpp
MyMoneroCoreCpp_ASMJS
#
# boost_atomic
boost_chrono
# boost_date_time
# boost_filesystem
# boost_locale
# boost_program_options
# boost_regex
# boost_serialization
# boost_signals
boost_system
boost_thread
# boost_timer
# boost_wserialization
#
${log-lib}
)

@ -32,7 +32,7 @@ There is also a chain of build scripts which is capable of building a JS module
`monero_utils` contains Monero- and MyMonero-specific implementations, wrappers, and declarations, and the MyMonero JS and wasm implementations for the underlying cryptography behind Monero.
`monero_utils/MyMoneroCoreCpp.(js,wasm)` are produced by transpiling Monero core C++ code to JS via Emscripten (See *Building MyMoneroCoreCpp*). A Module instance is managed by `monero_utils/MyMoneroCoreBridge.js`.
`monero_utils/MyMoneroCoreCpp*` are produced by transpiling Monero core C++ code to JS via Emscripten (See *Building MyMoneroCoreCpp*). A Module instance is managed by `monero_utils/MyMoneroCoreBridge.js`.
A ready-made entrypoint for interacting with `MyMoneroCoreBridge` is located at `monero_utils/monero_utils.js` with usage `require("./monero_utils/monero_utils").then(function(monero_utils) { })`.

@ -1,10 +1,7 @@
#!/bin/sh
bin/build-emcpp.sh &&
cp build/MyMoneroCoreCpp.js monero_utils/;
# it's ok if certain combinations of the following error.
# e.g. under wasm builds, you wouldn't want cp .wasm to be erroring.
# under asmjs builds with .mem file enabled, you'd not want .mem to be erroring.
cp build/MyMoneroCoreCpp.wasm monero_utils/;
cp build/MyMoneroCoreCpp.wast monero_utils/;
cp build/MyMoneroCoreCpp.js.mem monero_utils/
cp build/MyMoneroCoreCpp_WASM.js monero_utils/;
cp build/MyMoneroCoreCpp_WASM.wasm monero_utils/;
cp build/MyMoneroCoreCpp_ASMJS.js monero_utils/;
cp build/MyMoneroCoreCpp_ASMJS.asm.js monero_utils/

@ -12,5 +12,8 @@ rm -rf ./build/mymonero_core_js/;
echo "🔁 mkdir -p ./build/mymonero_core_js/monero_utils/";
mkdir -p ./build/mymonero_core_js/monero_utils/;
echo "🔁 cp monero_utils/MyMoneroCoreCpp.wasm ./build/mymonero_core_js/monero_utils/";
cp monero_utils/MyMoneroCoreCpp.wasm ./build/mymonero_core_js/monero_utils/;
echo "🔁 cp monero_utils/MyMoneroCoreCpp_WASM.wasm ./build/mymonero_core_js/monero_utils/";
cp monero_utils/MyMoneroCoreCpp_WASM.wasm ./build/mymonero_core_js/monero_utils/;
echo "🔁 cp monero_utils/MyMoneroCoreCpp_ASMJS.asm.js ./build/mymonero_core_js/monero_utils/";
cp monero_utils/MyMoneroCoreCpp_ASMJS.asm.js ./build/mymonero_core_js/monero_utils/;

@ -626,65 +626,117 @@ module.exports = function(options)
{
options = options || {}
//
const ENVIRONMENT_IS_WEB = typeof window==="object";
const ENVIRONMENT_IS_WORKER = typeof importScripts==="function";
const ENVIRONMENT_IS_NODE = typeof process==="object" && process.browser !== true && typeof require==="function" && ENVIRONMENT_IS_WORKER == false; // we want this to be true for Electron but not for a WebView
const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
function locateFile(filename, scriptDirectory)
{
// if (options["locateFile"]) {
// return options["locateFile"](filename, scriptDirectory)
// }
var this_scriptDirectory = scriptDirectory
const lastChar = this_scriptDirectory.charAt(this_scriptDirectory.length - 1)
if (lastChar == "/" || lastChar == "\\") {
// ^-- this is not a '\\' on Windows because emscripten actually appends a '/'
this_scriptDirectory = this_scriptDirectory.substring(0, this_scriptDirectory.length - 1) // remove trailing "/"
}
var fullPath = null; // add trailing slash to this
if (ENVIRONMENT_IS_NODE) {
const path = require('path')
const lastPathComponent = path.basename(this_scriptDirectory)
if (lastPathComponent == "monero_utils") { // typical node or electron-main process
fullPath = path.format({
dir: this_scriptDirectory,
base: filename
})
} else {
console.warn("MyMoneroCoreBridge/locateFile() on node.js didn't find \"monero_utils\" (or possibly MyMoneroCoreBridge.js) itself in the expected location in the following path. The function may need to be expanded but it might in normal situations be likely to be another bug." , pathTo_cryptonoteUtilsDir)
}
} else if (ENVIRONMENT_IS_WEB) {
var pathTo_cryptonoteUtilsDir;
if (typeof __dirname !== undefined && __dirname !== "/") { // looks like node running in browser.. (but not going to assume it's electron-renderer since that should be taken care of by monero_utils.js itself)
// but just in case it is... here's an attempt to support it
// have to check != "/" b/c webpack (I think) replaces __dirname
pathTo_cryptonoteUtilsDir = "file://" + __dirname + "/" // prepending "file://" because it's going to try to stream it
} else { // actual web browser
pathTo_cryptonoteUtilsDir = this_scriptDirectory + "/mymonero_core_js/monero_utils/" // this works for the MyMonero browser build, and is quite general, at least
}
fullPath = pathTo_cryptonoteUtilsDir + filename
}
if (fullPath == null) {
throw "Unable to derive fullPath. Please pass locateFile() to cryptonote_utils init."
}
//
return fullPath
}
return new Promise(function(resolve) {
const ENVIRONMENT_IS_WEB = typeof window==="object";
const ENVIRONMENT_IS_WORKER = typeof importScripts==="function";
const ENVIRONMENT_IS_NODE = typeof process==="object" && process.browser !== true && typeof require==="function" && ENVIRONMENT_IS_WORKER == false; // we want this to be true for Electron but not for a WebView
const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
var Module_template = {}
if (options.asmjs != true || options.wasm == true) { // wasm
Module_template["locateFile"] = function(filename, scriptDirectory)
{
// if (options["locateFile"]) {
// return options["locateFile"](filename, scriptDirectory)
// }
var this_scriptDirectory = scriptDirectory
const lastChar = this_scriptDirectory.charAt(this_scriptDirectory.length - 1)
if (lastChar == "/" || lastChar == "\\") {
// ^-- this is not a '\\' on Windows because emscripten actually appends a '/'
this_scriptDirectory = this_scriptDirectory.substring(0, this_scriptDirectory.length - 1) // remove trailing "/"
}
var fullPath = null; // add trailing slash to this
if (ENVIRONMENT_IS_NODE) {
const path = require('path')
const lastPathComponent = path.basename(this_scriptDirectory)
if (lastPathComponent == "monero_utils") { // typical node or electron-main process
fullPath = path.format({
dir: this_scriptDirectory,
base: filename
})
} else {
console.warn("MyMoneroCoreBridge/locateFile() on node.js didn't find \"monero_utils\" (or possibly MyMoneroCoreBridge.js) itself in the expected location in the following path. The function may need to be expanded but it might in normal situations be likely to be another bug." , pathTo_cryptonoteUtilsDir)
}
} else if (ENVIRONMENT_IS_WEB) {
var pathTo_cryptonoteUtilsDir;
if (typeof __dirname !== undefined && __dirname !== "/") { // looks like node running in browser.. (but not going to assume it's electron-renderer since that should be taken care of by monero_utils.js itself)
// but just in case it is... here's an attempt to support it
// have to check != "/" b/c webpack (I think) replaces __dirname
pathTo_cryptonoteUtilsDir = "file://" + __dirname + "/" // prepending "file://" because it's going to try to stream it
} else { // actual web browser
pathTo_cryptonoteUtilsDir = this_scriptDirectory + "/mymonero_core_js/monero_utils/" // this works for the MyMonero browser build, and is quite general, at least
}
fullPath = pathTo_cryptonoteUtilsDir + filename
}
if (fullPath == null) {
throw "Unable to derive fullPath. Please pass locateFile() to cryptonote_utils init."
}
//
return fullPath
}
Module_template["locateFile"] = locateFile
//
// NOTE: This requires src/module-post.js to be included as post-js in CMakeLists.txt under a wasm build
require("./MyMoneroCoreCpp")(Module_template).ready.then(function(thisModule)
require("./MyMoneroCoreCpp_WASM")(Module_template).ready.then(function(thisModule)
{
const instance = new MyMoneroCoreBridge(thisModule);
resolve(instance);
}).catch(function(e) {
console.error("Error loading MyMoneroCoreCpp:", e);
console.error("Error loading MyMoneroCoreCpp_WASM:", e);
reject(e);
});
} else { // this is synchronous so we can resolve immediately
resolve(new MyMoneroCoreBridge(require("./MyMoneroCoreCpp")(Module_template)))
var scriptDirectory=""; // this was extracted from emscripten - it could get factored if anything else would ever need it
if (ENVIRONMENT_IS_NODE) {
scriptDirectory=__dirname+"/";
} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = self.location.href
} else if (document.currentScript) {
scriptDirectory = document.currentScript.src
}
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
if(_scriptDir){
scriptDirectory = _scriptDir
}
if (scriptDirectory.indexOf("blob:") !== 0) {
scriptDirectory = scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)
} else {
scriptDirectory = ""
}
}
var read_fn;
if (ENVIRONMENT_IS_NODE) {
read_fn = function(filepath)
{
return require("fs").readFileSync(require("path").normalize(filepath)).toString()
};
} else if (ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER) {
read_fn = function(url)
{
var xhr = new XMLHttpRequest;
xhr.open("GET", url, false);
xhr.send(null);
return xhr.responseText
};
} else {
throw "Unsupported environment - please implement file reading for asmjs fallback case"
}
const filepath = locateFile('MyMoneroCoreCpp_ASMJS.asm.js', scriptDirectory)
const content = read_fn(filepath)
// TODO: verify content - for now, relying on same-origin and tls/ssl
var Module = {}
try {
eval(content) // emscripten also does an eval
} catch (e) {
reject(e)
return
}
setTimeout(function()
{ // "delaying even 1ms is enough to allow compilation memory to be reclaimed"
Module_template['asm'] = Module['asm']
Module = null
resolve(new MyMoneroCoreBridge(require("./MyMoneroCoreCpp_ASMJS")(Module_template)))
}, 1)
}
});
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -96,31 +96,29 @@ const moneroUtils_promise = new Promise(function(resolve, reject)
}
_try(0)
} else {
var buildHasASMJS = false // TODO: switch this to true when parallel builds are done
var hasWebAssembly = false
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module) {
var isInstance = new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
if (isInstance) {
// TODO: add ios 11 mobile safari bug check to hasWebAssembly
var use_asmjs = false;
if (ENVIRONMENT_IS_WEB) {
var hasWebAssembly = false
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module) {
var isInstance = new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
if (isInstance) {
// TODO: add ios 11 mobile safari bug check to hasWebAssembly
}
// until then…
hasWebAssembly = isInstance
}
// until then…
hasWebAssembly = isInstance
}
} catch (e) {
// avoiding empty block statement warning..
hasWebAssembly = false // to be clear
}
} catch (e) {
// avoiding empty block statement warning..
hasWebAssembly = false // to be clear
}
var asmjs = hasWebAssembly != true
if (asmjs && buildHasASMJS != true) {
reject(new Error("No WebAssembly support found - build with asmjs support"))
return;
}
// console.log("Using wasm: ", !asmjs)
const coreBridgeLoading_promise = require('./MyMoneroCoreBridge')({ asmjs: asmjs }); // this returns a promise
use_asmjs = hasWebAssembly != true
}
console.log("Using wasm: ", !use_asmjs)
const coreBridgeLoading_promise = require('./MyMoneroCoreBridge')({ asmjs: use_asmjs });
coreBridgeLoading_promise.catch(function(e)
{
console.error("Error: ", e);

@ -46,7 +46,9 @@
"verbose": true,
"coveragePathIgnorePatterns": [
"node_modules",
"monero_utils/MyMoneroCoreCpp.js",
"monero_utils/MyMoneroCoreCpp_WASM.js",
"monero_utils/MyMoneroCoreCpp_ASMJS.js",
"monero_utils/MyMoneroCoreCpp_ASMJS.asm.js",
"tests/emjs/*"
]
}

@ -185,7 +185,7 @@ function tests(Module)
}
}
console.time("Load module")
require('../monero_utils/MyMoneroCoreBridge')({asmjs: false}).then(function(instance)
require('../monero_utils/MyMoneroCoreBridge')({asmjs: false}).then(function(instance) // this can be switched to manually test asmjs vs wasm - can be exposed to option
{
console.timeEnd("Load module")
console.log("Loaded instance:", instance)

@ -40,7 +40,9 @@ module.exports = function(wallaby) {
],
filesWithNoCoverageCalculated: [
"monero_utils/MyMoneroCoreCpp.js",
"monero_utils/MyMoneroCoreCpp_WASM.js",
"monero_utils/MyMoneroCoreCpp_ASMJS.js",
"monero_utils/MyMoneroCoreCpp_ASMJS.asm.js"
],
tests: ["./tests/**/*spec.js"],

@ -51,8 +51,6 @@ module.exports =
'node_modules'
]
},
externals: {
},
stats: {
colors: true
},
@ -78,7 +76,15 @@ module.exports =
},
{
test: /\.js$/,
exclude: path.join(__dirname, 'node_modules'),
exclude: {
test: [
path.join(__dirname, 'node_modules')
],
exclude: [
'monero_utils/MyMoneroCoreCpp_ASMJS.asm.js',
'monero_utils/MyMoneroCoreCpp_ASMJS.wasm'
]
},
use: [
{
loader: 'babel-loader',

Loading…
Cancel
Save