From ca85b555aa3694c077e1cd5e20d821153edb00dc Mon Sep 17 00:00:00 2001 From: John Smith Date: Tue, 15 Mar 2022 09:33:34 -0400 Subject: [PATCH] WASM work --- Cargo.lock | 7 + setup_linux.sh | 2 +- veilid-core/Cargo.toml | 6 +- veilid-core/src/core_context.rs | 38 +- veilid-core/src/intf/wasm/utils/mod.rs | 10 - veilid-core/src/lib.rs | 2 + .../src/tests/common/test_veilid_config.rs | 4 +- veilid-core/src/veilid_api/mod.rs | 12 +- veilid-core/src/veilid_config.rs | 4 +- veilid-core/tests/node.rs | 4 + veilid-core/tests/web.rs | 4 + veilid-flutter/example/lib/config.dart | 14 +- veilid-flutter/lib/veilid.dart | 10 +- veilid-flutter/lib/veilid_ffi.dart | 20 +- veilid-flutter/lib/veilid_js.dart | 2 +- veilid-flutter/pubspec.yaml | 2 +- veilid-flutter/rust/src/dart_ffi.rs | 4 +- veilid-flutter/rust/src/lib.rs | 2 +- veilid-server/src/settings.rs | 2 +- veilid-wasm/Cargo.toml | 7 +- veilid-wasm/src/js_veilid_core.rs | 278 ------------- veilid-wasm/src/lib.rs | 202 +++++++++- veilid-wasm/src/utils.rs | 38 -- veilid-wasm/wasm-sourcemap.py | 377 ++++++++++++++++++ veilid-wasm/wasm_build.sh | 16 + 25 files changed, 672 insertions(+), 395 deletions(-) delete mode 100644 veilid-wasm/src/js_veilid_core.rs delete mode 100644 veilid-wasm/src/utils.rs create mode 100755 veilid-wasm/wasm-sourcemap.py create mode 100755 veilid-wasm/wasm_build.sh diff --git a/Cargo.lock b/Cargo.lock index beb7f507..1b911d90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4316,8 +4316,13 @@ version = "0.1.0" dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", + "futures-util", "js-sys", + "lazy_static", "log", + "send_wrapper", + "serde 1.0.136", + "serde_json", "veilid-core", "wasm-bindgen", "wasm-bindgen-futures", @@ -4374,6 +4379,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", + "serde 1.0.136", + "serde_json", "wasm-bindgen-macro", ] diff --git a/setup_linux.sh b/setup_linux.sh index 3d89d18c..d7897d1d 100755 --- a/setup_linux.sh +++ b/setup_linux.sh @@ -63,6 +63,6 @@ cargo install cargo-ndk cargo install cargo-apk # Ensure packages are installed -sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk +sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk llvm diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 6f7e29e1..17e3a2df 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -74,11 +74,8 @@ libc = "^0" # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "^0" -console_error_panic_hook = "^0" -wee_alloc = "^0" js-sys = "^0" wasm-bindgen-futures = "^0" -wasm-logger = "^0" hashbrown = "^0" lru = {version = "^0", features = ["hashbrown"] } no-std-net = { path = "../external/no-std-net", features = ["serde"] } @@ -149,6 +146,9 @@ simplelog = { version = "^0", features=["test"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "^0" +console_error_panic_hook = "^0" +wee_alloc = "^0" +wasm-logger = "^0" ### BUILD OPTIONS diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index b38f3fb6..52f30cd7 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -1,4 +1,3 @@ -use crate::api_logger::*; use crate::attachment_manager::*; use crate::dht::crypto::Crypto; use crate::intf::*; @@ -10,6 +9,8 @@ cfg_if! { if #[cfg(target_arch = "wasm32")] { pub type UpdateCallback = Arc; } else { + use crate::api_logger::*; + pub type UpdateCallback = Arc; } } @@ -59,18 +60,23 @@ impl ServicesContext { } pub async fn startup(&mut self) -> Result<(), VeilidAPIError> { - let api_log_level: VeilidConfigLogLevel = self.config.get().api_log_level; - if api_log_level != VeilidConfigLogLevel::Off { - ApiLogger::init( - api_log_level.to_level_filter(), - self.update_callback.clone(), - ) - .await; - for ig in crate::DEFAULT_LOG_IGNORE_LIST { - ApiLogger::add_filter_ignore_str(ig); + let log_level: VeilidConfigLogLevel = self.config.get().log_level; + if log_level != VeilidConfigLogLevel::Off { + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + // Logging is managed by application + } else { + ApiLogger::init( + log_level.to_level_filter(), + self.update_callback.clone(), + ) + .await; + for ig in crate::DEFAULT_LOG_IGNORE_LIST { + ApiLogger::add_filter_ignore_str(ig); + } + info!("Veilid logging initialized"); + } } - - info!("Veilid API logging initialized"); } info!("Veilid API starting up"); @@ -165,7 +171,13 @@ impl ServicesContext { info!("Veilid API shutdown complete"); // api logger terminate is idempotent - ApiLogger::terminate().await; + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + // Logging is managed by application + } else { + ApiLogger::terminate().await; + } + } // send final shutdown update (self.update_callback)(VeilidUpdate::Shutdown); diff --git a/veilid-core/src/intf/wasm/utils/mod.rs b/veilid-core/src/intf/wasm/utils/mod.rs index cf34813b..a29d4481 100644 --- a/veilid-core/src/intf/wasm/utils/mod.rs +++ b/veilid-core/src/intf/wasm/utils/mod.rs @@ -4,16 +4,6 @@ use crate::xx::*; use core::sync::atomic::{AtomicI8, Ordering}; use js_sys::{global, Reflect}; -cfg_if! { - if #[cfg(feature = "wee_alloc")] { - // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global - // allocator. - extern crate wee_alloc; - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - } -} - #[wasm_bindgen] extern "C" { // Use `js_namespace` here to bind `console.log(..)` instead of just diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index fc00509b..7d61dbb4 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -4,7 +4,9 @@ #[macro_use] extern crate alloc; +#[cfg(not(target_arch = "wasm32"))] mod api_logger; + mod attachment_manager; mod callback_state_machine; mod connection_manager; diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index 9a045a27..dcad6a10 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -169,7 +169,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn { match key.as_str() { "program_name" => Ok(Box::new(String::from("Veilid"))), "namespace" => Ok(Box::new(String::from(""))), - "api_log_level" => Ok(Box::new(VeilidConfigLogLevel::Off)), + "log_level" => Ok(Box::new(VeilidConfigLogLevel::Off)), "capabilities.protocol_udp" => Ok(Box::new(true)), "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), @@ -270,7 +270,7 @@ pub async fn test_config() { let inner = vc.get(); assert_eq!(inner.program_name, String::from("Veilid")); assert_eq!(inner.namespace, String::from("")); - assert_eq!(inner.api_log_level, VeilidConfigLogLevel::Off); + assert_eq!(inner.log_level, VeilidConfigLogLevel::Off); assert_eq!(inner.capabilities.protocol_udp, true); assert_eq!(inner.capabilities.protocol_connect_tcp, true); assert_eq!(inner.capabilities.protocol_accept_tcp, true); diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 0581b140..29962de2 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -21,7 +21,6 @@ pub use network_manager::NetworkManager; pub use routing_table::RoutingTable; pub use rpc_processor::InfoAnswer; -use api_logger::*; use core::fmt; use core_context::{api_shutdown, VeilidCoreContext}; use rpc_processor::{RPCError, RPCProcessor}; @@ -1219,8 +1218,15 @@ impl VeilidAPI { } // Change api logging level if it is enabled - pub async fn change_api_log_level(&self, log_level: VeilidConfigLogLevel) { - ApiLogger::change_log_level(log_level.to_level_filter()); + pub async fn change_log_level(&self, log_level: VeilidConfigLogLevel) { + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + set_max_level(log_level.to_level_filter()); + } else { + use api_logger::ApiLogger; + ApiLogger::change_log_level(log_level.to_level_filter()); + } + } } //////////////////////////////////////////////////////////////// diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 5aced93d..3a3f1f1a 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -206,7 +206,7 @@ impl Default for VeilidConfigLogLevel { pub struct VeilidConfigInner { pub program_name: String, pub namespace: String, - pub api_log_level: VeilidConfigLogLevel, + pub log_level: VeilidConfigLogLevel, pub capabilities: VeilidConfigCapabilities, pub protected_store: VeilidConfigProtectedStore, pub table_store: VeilidConfigTableStore, @@ -262,7 +262,7 @@ impl VeilidConfig { let mut inner = self.inner.write(); get_config!(inner.program_name); get_config!(inner.namespace); - get_config!(inner.api_log_level); + get_config!(inner.log_level); get_config!(inner.capabilities.protocol_udp); get_config!(inner.capabilities.protocol_connect_tcp); get_config!(inner.capabilities.protocol_accept_tcp); diff --git a/veilid-core/tests/node.rs b/veilid-core/tests/node.rs index f5080a43..3bc717af 100644 --- a/veilid-core/tests/node.rs +++ b/veilid-core/tests/node.rs @@ -8,6 +8,10 @@ use wasm_bindgen_test::*; wasm_bindgen_test_configure!(); +extern crate wee_alloc; +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + static SETUP_ONCE: Once = Once::new(); pub fn setup() -> () { SETUP_ONCE.call_once(|| { diff --git a/veilid-core/tests/web.rs b/veilid-core/tests/web.rs index a7a29733..d5abfe3b 100644 --- a/veilid-core/tests/web.rs +++ b/veilid-core/tests/web.rs @@ -8,6 +8,10 @@ use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); +extern crate wee_alloc; +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + static SETUP_ONCE: Once = Once::new(); pub fn setup() -> () { SETUP_ONCE.call_once(|| { diff --git a/veilid-flutter/example/lib/config.dart b/veilid-flutter/example/lib/config.dart index 2ff86541..af3533b0 100644 --- a/veilid-flutter/example/lib/config.dart +++ b/veilid-flutter/example/lib/config.dart @@ -7,7 +7,7 @@ Future getDefaultVeilidConfig() async { return VeilidConfig( programName: "Veilid Plugin Test", namespace: "", - apiLogLevel: VeilidConfigLogLevel.info, + logLevel: VeilidConfigLogLevel.info, capabilities: VeilidConfigCapabilities( protocolUDP: !kIsWeb, protocolConnectTCP: !kIsWeb, @@ -24,13 +24,17 @@ Future getDefaultVeilidConfig() async { delete: false, ), tableStore: VeilidConfigTableStore( - directory: p.join((await getApplicationSupportDirectory()).absolute.path, - "table_store"), + directory: kIsWeb + ? "" + : p.join((await getApplicationSupportDirectory()).absolute.path, + "table_store"), delete: false, ), blockStore: VeilidConfigBlockStore( - directory: p.join((await getApplicationSupportDirectory()).absolute.path, - "block_store"), + directory: kIsWeb + ? "" + : p.join((await getApplicationSupportDirectory()).absolute.path, + "block_store"), delete: false, ), network: VeilidConfigNetwork( diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index a8c09209..5232b6f2 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -705,7 +705,7 @@ class VeilidConfigCapabilities { class VeilidConfig { String programName; String namespace; - VeilidConfigLogLevel apiLogLevel; + VeilidConfigLogLevel logLevel; VeilidConfigCapabilities capabilities; VeilidConfigProtectedStore protectedStore; VeilidConfigTableStore tableStore; @@ -715,7 +715,7 @@ class VeilidConfig { VeilidConfig({ required this.programName, required this.namespace, - required this.apiLogLevel, + required this.logLevel, required this.capabilities, required this.protectedStore, required this.tableStore, @@ -727,7 +727,7 @@ class VeilidConfig { return { 'program_name': programName, 'namespace': namespace, - 'api_log_level': apiLogLevel.json, + 'log_level': logLevel.json, 'capabilities': capabilities.json, 'protected_store': protectedStore.json, 'table_store': tableStore.json, @@ -739,7 +739,7 @@ class VeilidConfig { VeilidConfig.fromJson(Map json) : programName = json['program_name'], namespace = json['namespace'], - apiLogLevel = json['api_log_level'], + logLevel = json['log_level'], capabilities = VeilidConfigCapabilities.fromJson(json['capabilities']), protectedStore = VeilidConfigProtectedStore.fromJson(json['protected_store']), @@ -993,7 +993,7 @@ abstract class Veilid { Stream startupVeilidCore(VeilidConfig config); Future getVeilidState(); - Future changeApiLogLevel(VeilidConfigLogLevel logLevel); + Future changeLogLevel(VeilidConfigLogLevel logLevel); Future shutdownVeilidCore(); Future debug(String command); String veilidVersionString(); diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 0e4c2acc..a37cb411 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -35,9 +35,9 @@ typedef _StartupVeilidCoreDart = void Function(int, Pointer); // fn get_veilid_state(port: i64) typedef _GetVeilidStateC = Void Function(Int64); typedef _GetVeilidStateDart = void Function(int); -// fn change_api_log_level(port: i64, log_level: FfiStr) -typedef _ChangeApiLogLevelC = Void Function(Int64, Pointer); -typedef _ChangeApiLogLevelDart = void Function(int, Pointer); +// fn change_log_level(port: i64, log_level: FfiStr) +typedef _ChangeLogLevelC = Void Function(Int64, Pointer); +typedef _ChangeLogLevelDart = void Function(int, Pointer); // fn debug(port: i64, log_level: FfiStr) typedef _DebugC = Void Function(Int64, Pointer); typedef _DebugDart = void Function(int, Pointer); @@ -243,7 +243,7 @@ class VeilidFFI implements Veilid { final _FreeStringDart _freeString; final _StartupVeilidCoreDart _startupVeilidCore; final _GetVeilidStateDart _getVeilidState; - final _ChangeApiLogLevelDart _changeApiLogLevel; + final _ChangeLogLevelDart _changeLogLevel; final _ShutdownVeilidCoreDart _shutdownVeilidCore; final _DebugDart _debug; final _VeilidVersionStringDart _veilidVersionString; @@ -259,9 +259,9 @@ class VeilidFFI implements Veilid { _getVeilidState = dylib.lookupFunction<_GetVeilidStateC, _GetVeilidStateDart>( 'get_veilid_state'), - _changeApiLogLevel = - dylib.lookupFunction<_ChangeApiLogLevelC, _ChangeApiLogLevelDart>( - 'change_api_log_level'), + _changeLogLevel = + dylib.lookupFunction<_ChangeLogLevelC, _ChangeLogLevelDart>( + 'change_log_level'), _shutdownVeilidCore = dylib.lookupFunction<_ShutdownVeilidCoreC, _ShutdownVeilidCoreDart>( 'shutdown_veilid_core'), @@ -299,11 +299,11 @@ class VeilidFFI implements Veilid { } @override - Future changeApiLogLevel(VeilidConfigLogLevel logLevel) async { + Future changeLogLevel(VeilidConfigLogLevel logLevel) async { var nativeLogLevel = logLevel.json.toNativeUtf8(); - final recvPort = ReceivePort("change_api_log_level"); + final recvPort = ReceivePort("change_log_level"); final sendPort = recvPort.sendPort; - _changeApiLogLevel(sendPort.nativePort, nativeLogLevel); + _changeLogLevel(sendPort.nativePort, nativeLogLevel); malloc.free(nativeLogLevel); return processFutureVoid(recvPort.first); } diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 5f3da651..b0fe4ab5 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -20,7 +20,7 @@ class VeilidJS implements Veilid { } @override - Future changeApiLogLevel(VeilidConfigLogLevel logLevel) { + Future changeLogLevel(VeilidConfigLogLevel logLevel) { throw UnimplementedError(); } diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index 377c29b1..a661be04 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -48,7 +48,7 @@ flutter: # To add assets to your plugin package, add an assets section, like this: # assets: - # - images/a_dot_burr.jpeg + # - assets/foo.wasm # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 7f083927..39a0d965 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -143,12 +143,12 @@ pub extern "C" fn get_veilid_state(port: i64) { } #[no_mangle] -pub extern "C" fn change_api_log_level(port: i64, log_level: FfiStr) { +pub extern "C" fn change_log_level(port: i64, log_level: FfiStr) { let log_level = log_level.into_opt_string(); DartIsolateWrapper::new(port).spawn_result_json(async move { let log_level: veilid_core::VeilidConfigLogLevel = deserialize_opt_json(log_level)?; let veilid_api = get_veilid_api().await?; - veilid_api.change_api_log_level(log_level).await; + veilid_api.change_log_level(log_level).await; APIRESULT_VOID }); } diff --git a/veilid-flutter/rust/src/lib.rs b/veilid-flutter/rust/src/lib.rs index ef3f0452..e19aa7fe 100644 --- a/veilid-flutter/rust/src/lib.rs +++ b/veilid-flutter/rust/src/lib.rs @@ -6,7 +6,7 @@ cfg_if! { mod dart_isolate_wrapper; mod dart_serialize; } else { - mod wasm; + //mod wasm; } } diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index aca72665..90793a8b 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -746,7 +746,7 @@ impl Settings { } else { format!("subnode{}", inner.testing.subnode_index) })), - "api_log_level" => Ok(Box::new(veilid_core::VeilidConfigLogLevel::Off)), + "log_level" => Ok(Box::new(veilid_core::VeilidConfigLogLevel::Off)), "capabilities.protocol_udp" => Ok(Box::new(true)), "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 812ec6d9..18e934a2 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -9,7 +9,7 @@ license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)" crate-type = ["cdylib", "rlib"] [dependencies] -wasm-bindgen = "^0" +wasm-bindgen = { version = "^0", features = ["serde-serialize"] } console_error_panic_hook = "^0" wee_alloc = "^0" wasm-logger = "^0" @@ -18,6 +18,11 @@ veilid-core = { path = "../veilid-core" } cfg-if = "^1" wasm-bindgen-futures = "^0" js-sys = "^0" +serde_json = "^1" +serde = "^1" +lazy_static = "^1" +send_wrapper = "^0" +futures-util = { version = "^0", default_features = false, features = ["alloc"] } [dev-dependencies] wasm-bindgen-test = "^0" diff --git a/veilid-wasm/src/js_veilid_core.rs b/veilid-wasm/src/js_veilid_core.rs deleted file mode 100644 index e4eb7922..00000000 --- a/veilid-wasm/src/js_veilid_core.rs +++ /dev/null @@ -1,278 +0,0 @@ -use crate::*; -pub use wasm_bindgen_futures::*; - -#[wasm_bindgen(js_name = VeilidStateChange)] -pub struct JsVeilidStateChange { - kind: String, // "attachment" => AttachmentState(String) - from: JsValue, - to: JsValue, -} -#[wasm_bindgen(js_name = VeilidState)] -pub struct JsVeilidState { - kind: String, // "attachment" => AttachmentState(String) - state: JsValue, -} - -#[wasm_bindgen(js_name = VeilidCore)] -pub struct JsVeilidCore { - core: VeilidCore, -} - -#[wasm_bindgen(js_class = VeilidCore)] -impl JsVeilidCore { - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - set_panic_hook(); - JsVeilidCore { - core: VeilidCore::new(), - } - } - fn value_to_string(val: JsValue) -> Result, ()> { - Ok(Box::new(val.as_string().ok_or(())?)) - } - fn value_to_option_string(val: JsValue) -> Result, ()> { - if val.is_null() || val.is_undefined() { - return Ok(Box::new(Option::::None)); - } - Ok(Box::new(Some(val.as_string().ok_or(())?))) - } - fn value_to_bool(val: JsValue) -> Result, ()> { - Ok(Box::new(val.is_truthy())) - } - fn value_to_u8(val: JsValue) -> Result, ()> { - Ok(Box::new(f64_try_to_unsigned::( - val.as_f64().ok_or(())?, - )?)) - } - fn value_to_u32(val: JsValue) -> Result, ()> { - Ok(Box::new(f64_try_to_unsigned::( - val.as_f64().ok_or(())?, - )?)) - } - fn value_to_u64(val: JsValue) -> Result, ()> { - Ok(Box::new(f64_try_to_unsigned::( - val.as_f64().ok_or(())?, - )?)) - } - fn value_to_option_u64(val: JsValue) -> Result, ()> { - if val.is_null() || val.is_undefined() { - return Ok(Box::new(Option::::None)); - } - - Ok(Box::new(Some(f64_try_to_unsigned::( - val.as_f64().ok_or(())?, - )?))) - } - fn value_to_dht_key(val: JsValue) -> Result, ()> { - Ok(Box::new( - DHTKey::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?, - )) - } - fn value_to_dht_key_secret(val: JsValue) -> Result, ()> { - Ok(Box::new( - DHTKeySecret::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?, - )) - } - fn value_to_vec_string(val: JsValue) -> Result, ()> { - let arrval = val.dyn_into::().map_err(drop)?.to_vec(); - let mut out = Vec::::with_capacity(arrval.len()); - for v in arrval { - out.push(v.as_string().ok_or(())?); - } - Ok(Box::new(out)) - } - - fn translate_config_callback(key: &str, val: JsValue) -> Result, ()> { - match key { - // xxx: lots of missing keys here - "namespace" => Self::value_to_string(val), - "capabilities.protocol_udp" => Self::value_to_bool(val), - "capabilities.protocol_connect_tcp" => Self::value_to_bool(val), - "capabilities.protocol_accept_tcp" => Self::value_to_bool(val), - "capabilities.protocol_connect_ws" => Self::value_to_bool(val), - "capabilities.protocol_accept_ws" => Self::value_to_bool(val), - "capabilities.protocol_connect_wss" => Self::value_to_bool(val), - "capabilities.protocol_accept_wss" => Self::value_to_bool(val), - "tablestore.directory" => Self::value_to_string(val), - "network.max_connections" => Self::value_to_u32(val), - "network.node_id" => Self::value_to_dht_key(val), - "network.node_id_secret" => Self::value_to_dht_key_secret(val), - "network.bootstrap" => Self::value_to_vec_string(val), - "network.rpc.concurrency" => Self::value_to_u32(val), - "network.rpc.queue_size" => Self::value_to_u32(val), - "network.rpc.max_timestamp_behind" => Self::value_to_option_u64(val), - "network.rpc.max_timestamp_ahead" => Self::value_to_option_u64(val), - "network.rpc.timeout" => Self::value_to_u64(val), - "network.rpc.max_route_hop_count" => Self::value_to_u8(val), - "network.dht.resolve_node_timeout" => Self::value_to_option_u64(val), - "network.dht.resolve_node_count" => Self::value_to_u32(val), - "network.dht.resolve_node_fanout" => Self::value_to_u32(val), - "network.dht.max_find_node_count" => Self::value_to_u32(val), - "network.dht.get_value_timeout" => Self::value_to_option_u64(val), - "network.dht.get_value_count" => Self::value_to_u32(val), - "network.dht.get_value_fanout" => Self::value_to_u32(val), - "network.dht.set_value_timeout" => Self::value_to_option_u64(val), - "network.dht.set_value_count" => Self::value_to_u32(val), - "network.dht.set_value_fanout" => Self::value_to_u32(val), - "network.dht.min_peer_count" => Self::value_to_u32(val), - "network.dht.min_peer_refresh_time" => Self::value_to_u64(val), - "network.dht.validate_dial_info_receipt_time" => Self::value_to_u64(val), - "network.upnp" => Self::value_to_bool(val), - "network.natpmp" => Self::value_to_bool(val), - "network.address_filter" => Self::value_to_bool(val), - "network.restricted_nat_retries" => Self::value_to_u32(val), - "network.tls.certificate_path" => Self::value_to_string(val), - "network.tls.private_key_path" => Self::value_to_string(val), - "network.application.path" => Self::value_to_string(val), - "network.application.https.enabled" => Self::value_to_bool(val), - "network.application.https.listen_address" => Self::value_to_string(val), - "network.application.http.enabled" => Self::value_to_bool(val), - "network.application.http.listen_address" => Self::value_to_string(val), - "network.protocol.udp.enabled" => Self::value_to_bool(val), - "network.protocol.udp.socket_pool_size" => Self::value_to_u32(val), - "network.protocol.udp.listen_address" => Self::value_to_string(val), - "network.protocol.udp.public_address" => Self::value_to_option_string(val), - "network.protocol.tcp.connect" => Self::value_to_bool(val), - "network.protocol.tcp.listen" => Self::value_to_bool(val), - "network.protocol.tcp.max_connections" => Self::value_to_u32(val), - "network.protocol.tcp.listen_address" => Self::value_to_string(val), - "network.protocol.tcp.public_address" => Self::value_to_option_string(val), - "network.protocol.ws.connect" => Self::value_to_bool(val), - "network.protocol.ws.listen" => Self::value_to_bool(val), - "network.protocol.ws.max_connections" => Self::value_to_u32(val), - "network.protocol.ws.listen_address" => Self::value_to_string(val), - "network.protocol.ws.path" => Self::value_to_string(val), - "network.protocol.ws.public_address" => Self::value_to_option_string(val), - "network.protocol.wss.connect" => Self::value_to_bool(val), - "network.protocol.wss.listen" => Self::value_to_bool(val), - "network.protocol.wss.max_connections" => Self::value_to_u32(val), - "network.protocol.wss.listen_address" => Self::value_to_string(val), - "network.protocol.wss.path" => Self::value_to_string(val), - "network.protocol.wss.public_address" => Self::value_to_option_string(val), - _ => return Err(()), - } - } - fn translate_veilid_state(state: JsVeilidState) -> Result { - Ok(match state.kind.as_str() { - "attachment" => { - let state_string = state - .state - .as_string() - .ok_or(JsValue::from_str("state should be a string"))?; - let astate = AttachmentState::try_from(state_string) - .map_err(|e| JsValue::from_str(format!("invalid state: {:?}", e).as_str()))?; - VeilidState::Attachment(astate) - } - _ => return Err(JsValue::from_str("unknown state kind")), - }) - } - // xxx rework this for new veilid_api mechanism which should be its own js object now - pub fn startup( - &self, - js_state_change_callback: Function, - js_config_callback: Function, - ) -> Promise { - let core = self.core.clone(); - future_to_promise(async move { - let vcs = VeilidCoreSetup { - state_change_callback: Arc::new( - move |change: VeilidStateChange| -> SystemPinBoxFuture<()> { - let js_state_change_callback = js_state_change_callback.clone(); - Box::pin(async move { - let js_change = match change { - VeilidStateChange::Attachment { - old_state, - new_state, - } => JsVeilidStateChange { - kind: "attachment".to_owned(), - from: JsValue::from_str(old_state.to_string().as_str()), - to: JsValue::from_str(new_state.to_string().as_str()), - }, - }; - - let ret = match Function::call1( - &js_state_change_callback, - &JsValue::UNDEFINED, - &JsValue::from(js_change), - ) { - Ok(v) => v, - Err(e) => { - error!("calling state change callback failed: {:?}", e); - return; - } - }; - let retp: Promise = match ret.dyn_into() { - Ok(v) => v, - Err(e) => { - error!( - "state change callback did not return a promise: {:?}", - e - ); - return; - } - }; - match JsFuture::from(retp).await { - Ok(_) => (), - Err(e) => { - error!("state change callback returned an error: {:?}", e); - return; - } - }; - }) - }, - ), - config_callback: Arc::new( - move |key: String| -> Result, String> { - let val = Function::call1( - &js_config_callback, - &JsValue::UNDEFINED, - &JsValue::from_str(key.as_str()), - ) - .map_err(|_| { - format!("Failed to get config from callback for key '{}'", key) - })?; - - Self::translate_config_callback(key.as_str(), val) - .map_err(|_| format!("invalid value type for config key '{}'", key)) - }, - ), - }; - - match core.startup(vcs).await { - Ok(_) => Ok(JsValue::UNDEFINED), - Err(e) => Err(JsValue::from_str( - format!("VeilidCore startup() failed: {}", e.to_string()).as_str(), - )), - } - }) - } - - pub fn send_state_update(&self) { - self.core.send_state_update(); - } - - pub fn shutdown(&self) -> Promise { - let core = self.core.clone(); - future_to_promise(async move { - core.shutdown().await; - Ok(JsValue::UNDEFINED) - }) - } - - pub fn attach(&self) -> Promise { - let core = self.core.clone(); - future_to_promise(async move { - core.attach(); - Ok(JsValue::UNDEFINED) - }) - } - - pub fn detach(&self) -> Promise { - let core = self.core.clone(); - future_to_promise(async move { - core.detach(); - Ok(JsValue::UNDEFINED) - }) - } - -} diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 86e8a845..b296448d 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -1,25 +1,191 @@ +// wasm-bindgen and clippy don't play well together yet +#![allow(clippy::all)] #![cfg(target_arch = "wasm32")] #![no_std] -#[macro_use] extern crate alloc; +use alloc::string::String; +use alloc::sync::Arc; +use core::any::{Any, TypeId}; +use core::cell::RefCell; +use futures_util::FutureExt; +use js_sys::*; +use lazy_static::*; +use log::*; +use send_wrapper::*; +use serde::*; +use veilid_core::xx::*; +use veilid_core::*; +use wasm_bindgen_futures::*; -pub use log::*; -pub use wasm_bindgen::prelude::*; -pub use wasm_bindgen::JsCast; +// Allocator +extern crate wee_alloc; +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -pub use alloc::boxed::Box; -pub use alloc::string::String; -pub use alloc::sync::Arc; -pub use alloc::vec::Vec; -pub use core::convert::TryFrom; -pub use js_sys::*; -pub use js_veilid_core::*; -pub use utils::*; -pub use veilid_core::dht::key::*; -pub use veilid_core::xx::*; -pub use veilid_core::*; -pub use wasm_logger::*; +static SETUP_ONCE: Once = Once::new(); +pub fn setup() -> () { + SETUP_ONCE.call_once(|| {}); +} -mod js_veilid_core; -mod utils; +// API Singleton + +lazy_static! { + static ref VEILID_API: SendWrapper>> = + SendWrapper::new(RefCell::new(None)); +} + +fn get_veilid_api() -> Result { + (*VEILID_API) + .borrow() + .clone() + .ok_or(veilid_core::VeilidAPIError::NotInitialized) +} + +fn take_veilid_api() -> Result { + (**VEILID_API) + .take() + .ok_or(veilid_core::VeilidAPIError::NotInitialized) +} + +// JSON Marshalling + +pub fn serialize_json(val: T) -> String { + serde_json::to_string(&val).expect("failed to serialize json value") +} + +pub fn deserialize_json( + arg: &str, +) -> Result { + serde_json::from_str(arg).map_err(|e| veilid_core::VeilidAPIError::ParseError { + message: e.to_string(), + value: String::new(), + }) +} + +pub fn to_json(val: T) -> JsValue { + JsValue::from_str(&serialize_json(val)) +} + +pub fn from_json(val: JsValue) -> Result { + let s = val + .as_string() + .ok_or_else(|| veilid_core::VeilidAPIError::ParseError { + message: "Value is not String".to_owned(), + value: String::new(), + })?; + deserialize_json(&s) +} + +// Utility types for async API results +type APIResult = Result; +const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(()); + +pub fn wrap_api_future(future: F) -> Promise +where + F: Future> + 'static, + T: Serialize + 'static, +{ + future_to_promise(future.map(|res| { + res.map(|v| { + if TypeId::of::<()>() == v.type_id() { + JsValue::UNDEFINED + } else { + to_json(v) + } + }) + .map_err(|e| to_json(e)) + })) +} + +// WASM Bindings + +#[wasm_bindgen(js_namespace = veilid)] +pub fn initialize_veilid_wasm() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(Level::Info)); +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn startup_veilid_core(update_callback: Function, json_config: String) -> Promise { + wrap_api_future(async move { + let update_callback = Arc::new(move |update: VeilidUpdate| { + let _ret = + match Function::call1(&update_callback, &JsValue::UNDEFINED, &to_json(update)) { + Ok(v) => v, + Err(e) => { + error!("calling update callback failed: {:?}", e); + return; + } + }; + }); + + if VEILID_API.borrow().is_some() { + return Err(veilid_core::VeilidAPIError::AlreadyInitialized); + } + + let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; + VEILID_API.replace(Some(veilid_api)); + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn get_veilid_state() -> Promise { + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let core_state = veilid_api.get_state().await?; + Ok(core_state) + }) +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn change_log_level(log_level: String) -> Promise { + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let log_level: veilid_core::VeilidConfigLogLevel = deserialize_json(&log_level)?; + veilid_api.change_log_level(log_level).await; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn shutdown_veilid_core() -> Promise { + wrap_api_future(async move { + let veilid_api = take_veilid_api()?; + veilid_api.shutdown().await; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn debug(command: String) -> Promise { + wrap_api_future(async move { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + Ok(out) + }) +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn veilid_version_string() -> String { + veilid_core::veilid_version_string() +} + +#[derive(Serialize)] +pub struct VeilidVersion { + pub major: u32, + pub minor: u32, + pub patch: u32, +} + +#[wasm_bindgen(js_namespace = veilid)] +pub fn veilid_version() -> JsValue { + let (major, minor, patch) = veilid_core::veilid_version(); + let vv = VeilidVersion { + major, + minor, + patch, + }; + JsValue::from_serde(&vv).unwrap() +} diff --git a/veilid-wasm/src/utils.rs b/veilid-wasm/src/utils.rs deleted file mode 100644 index 0294b6ba..00000000 --- a/veilid-wasm/src/utils.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cfg_if::*; -use wasm_bindgen::prelude::*; -//use wasm_bindgen_futures::*; - -cfg_if! { - if #[cfg(feature = "wee_alloc")] { - // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global - // allocator. - extern crate wee_alloc; - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - } -} - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console, js_name = log)] - pub fn console_log(s: &str); - - #[wasm_bindgen] - pub fn alert(s: &str); -} - -pub fn set_panic_hook() { - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); -} - -pub fn f64_try_to_unsigned(f: f64) -> Result -where - T: core::convert::TryFrom, -{ - let rf = f.floor(); - if rf < 0.0 { - return Err(()); - } - T::try_from(rf as u64).map_err(drop) -} diff --git a/veilid-wasm/wasm-sourcemap.py b/veilid-wasm/wasm-sourcemap.py new file mode 100755 index 00000000..2d29c36d --- /dev/null +++ b/veilid-wasm/wasm-sourcemap.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Emscripten Authors. All rights reserved. +# Emscripten is available under two separate licenses, the MIT license and the +# University of Illinois/NCSA Open Source License. Both these licenses can be +# found in the LICENSE file. + +"""Utility tools that extracts DWARF information encoded in a wasm output +produced by the LLVM tools, and encodes it as a wasm source map. Additionally, +it can collect original sources, change files prefixes, and strip debug +sections from a wasm file. +""" + +import argparse +from collections import OrderedDict +import json +import logging +from math import floor, log +import os +import re +from subprocess import Popen, PIPE +from pathlib import Path +import sys + +__scriptdir__ = os.path.dirname(os.path.abspath(__file__)) +__rootdir__ = os.path.dirname(__scriptdir__) +sys.path.append(__rootdir__) + +logger = logging.getLogger('wasm-sourcemap') + + +def parse_args(): + parser = argparse.ArgumentParser( + prog='wasm-sourcemap.py', description=__doc__) + parser.add_argument('wasm', help='wasm file') + parser.add_argument('-o', '--output', help='output source map') + parser.add_argument('-p', '--prefix', nargs='*', + help='replace source debug filename prefix for source map', default=[]) + parser.add_argument('-s', '--sources', action='store_true', + help='read and embed source files from file system into source map') + parser.add_argument('-l', '--load-prefix', nargs='*', + help='replace source debug filename prefix for reading sources from file system (see also --sources)', default=[]) + parser.add_argument('-w', nargs='?', help='set output wasm file') + parser.add_argument('-x', '--strip', action='store_true', + help='removes debug and linking sections') + parser.add_argument('-u', '--source-map-url', nargs='?', + help='specifies sourceMappingURL section contest') + parser.add_argument( + '--dwarfdump', help="path to llvm-dwarfdump executable") + parser.add_argument('--dwarfdump-output', nargs='?', + help=argparse.SUPPRESS) + parser.add_argument( + '--basepath', help='base path for source files, which will be relative to this') + return parser.parse_args() + + +class Prefixes: + def __init__(self, args): + prefixes = [] + for p in args: + if '=' in p: + prefix, replacement = p.split('=') + prefixes.append({'prefix': prefix, 'replacement': replacement}) + else: + prefixes.append({'prefix': p, 'replacement': None}) + self.prefixes = prefixes + self.cache = {} + + def resolve(self, name): + if name in self.cache: + return self.cache[name] + + for p in self.prefixes: + if name.startswith(p['prefix']): + if p['replacement'] is None: + result = name[len(p['prefix'])::] + else: + result = p['replacement'] + name[len(p['prefix'])::] + break + self.cache[name] = result + return result + + +# SourceMapPrefixes contains resolver for file names that are: +# - "sources" is for names that output to source maps JSON +# - "load" is for paths that used to load source text +class SourceMapPrefixes: + def __init__(self, sources, load): + self.sources = sources + self.load = load + + def provided(self): + return bool(self.sources.prefixes or self.load.prefixes) + + +def encode_vlq(n): + VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + x = (n << 1) if n >= 0 else ((-n << 1) + 1) + result = "" + while x > 31: + result = result + VLQ_CHARS[32 + (x & 31)] + x = x >> 5 + return result + VLQ_CHARS[x] + + +def read_var_uint(wasm, pos): + n = 0 + shift = 0 + b = ord(wasm[pos:pos + 1]) + pos = pos + 1 + while b >= 128: + n = n | ((b - 128) << shift) + b = ord(wasm[pos:pos + 1]) + pos = pos + 1 + shift += 7 + return n + (b << shift), pos + + +def strip_debug_sections(wasm): + logger.debug('Strip debug sections') + pos = 8 + stripped = wasm[:pos] + + while pos < len(wasm): + section_start = pos + section_id, pos_ = read_var_uint(wasm, pos) + section_size, section_body = read_var_uint(wasm, pos_) + pos = section_body + section_size + if section_id == 0: + name_len, name_pos = read_var_uint(wasm, section_body) + name_end = name_pos + name_len + name = wasm[name_pos:name_end] + if name == "linking" or name == "sourceMappingURL" or name.startswith("reloc..debug_") or name.startswith(".debug_"): + continue # skip debug related sections + stripped = stripped + wasm[section_start:pos] + + return stripped + + +def encode_uint_var(n): + result = bytearray() + while n > 127: + result.append(128 | (n & 127)) + n = n >> 7 + result.append(n) + return bytes(result) + + +def append_source_mapping(wasm, url): + logger.debug('Append sourceMappingURL section') + section_name = "sourceMappingURL" + section_content = encode_uint_var( + len(section_name)) + section_name + encode_uint_var(len(url)) + url + return wasm + encode_uint_var(0) + encode_uint_var(len(section_content)) + section_content + + +def get_code_section_offset(wasm): + logger.debug('Read sections index') + pos = 8 + + while pos < len(wasm): + section_id, pos_ = read_var_uint(wasm, pos) + section_size, pos = read_var_uint(wasm, pos_) + if section_id == 10: + return pos + pos = pos + section_size + + +def remove_dead_entries(entries): + # Remove entries for dead functions. It is a heuristics to ignore data if the + # function starting address near to 0 (is equal to its size field length). + block_start = 0 + cur_entry = 0 + while cur_entry < len(entries): + if not entries[cur_entry]['eos']: + cur_entry += 1 + continue + fn_start = entries[block_start]['address'] + # Calculate the LEB encoded function size (including size field) + fn_size_length = floor( + log(entries[cur_entry]['address'] - fn_start + 1, 128)) + 1 + min_live_offset = 1 + fn_size_length # 1 byte is for code section entries + if fn_start < min_live_offset: + # Remove dead code debug info block. + del entries[block_start:cur_entry + 1] + cur_entry = block_start + continue + cur_entry += 1 + block_start = cur_entry + + +def read_dwarf_entries(wasm, options): + if options.dwarfdump_output: + output = Path(options.dwarfdump_output).read_bytes() + elif options.dwarfdump: + logger.debug('Reading DWARF information from %s' % wasm) + if not os.path.exists(options.dwarfdump): + logger.error('llvm-dwarfdump not found: ' + options.dwarfdump) + sys.exit(1) + process = Popen([options.dwarfdump, '-debug-info', + '-debug-line', '--recurse-depth=0', wasm], stdout=PIPE) + output, err = process.communicate() + exit_code = process.wait() + if exit_code != 0: + logger.error( + 'Error during llvm-dwarfdump execution (%s)' % exit_code) + sys.exit(1) + else: + logger.error('Please specify either --dwarfdump or --dwarfdump-output') + sys.exit(1) + + entries = [] + debug_line_chunks = re.split( + r"debug_line\[(0x[0-9a-f]*)\]", output.decode('utf-8')) + maybe_debug_info_content = debug_line_chunks[0] + for i in range(1, len(debug_line_chunks), 2): + stmt_list = debug_line_chunks[i] + comp_dir_match = re.search(r"DW_AT_stmt_list\s+\(" + stmt_list + r"\)\s+" + + r"DW_AT_comp_dir\s+\(\"([^\"]+)", maybe_debug_info_content) + comp_dir = comp_dir_match.group( + 1) if comp_dir_match is not None else "" + + line_chunk = debug_line_chunks[i + 1] + + # include_directories[ 1] = "/Users/yury/Work/junk/sqlite-playground/src" + # file_names[ 1]: + # name: "playground.c" + # dir_index: 1 + # mod_time: 0x00000000 + # length: 0x00000000 + # + # Address Line Column File ISA Discriminator Flags + # ------------------ ------ ------ ------ --- ------------- ------------- + # 0x0000000000000006 22 0 1 0 0 is_stmt + # 0x0000000000000007 23 10 1 0 0 is_stmt prologue_end + # 0x000000000000000f 23 3 1 0 0 + # 0x0000000000000010 23 3 1 0 0 end_sequence + # 0x0000000000000011 28 0 1 0 0 is_stmt + + include_directories = {'0': comp_dir} + for dir in re.finditer(r"include_directories\[\s*(\d+)\] = \"([^\"]*)", line_chunk): + include_directories[dir.group(1)] = dir.group(2) + + files = {} + for file in re.finditer(r"file_names\[\s*(\d+)\]:\s+name: \"([^\"]*)\"\s+dir_index: (\d+)", line_chunk): + dir = include_directories[file.group(3)] + file_path = (dir + '/' if file.group(2) + [0] != '/' else '') + file.group(2) + files[file.group(1)] = file_path + + for line in re.finditer(r"\n0x([0-9a-f]+)\s+(\d+)\s+(\d+)\s+(\d+)(.*?end_sequence)?", line_chunk): + entry = {'address': int(line.group(1), 16), 'line': int(line.group(2)), 'column': int( + line.group(3)), 'file': files[line.group(4)], 'eos': line.group(5) is not None} + if not entry['eos']: + entries.append(entry) + else: + # move end of function to the last END operator + entry['address'] -= 1 + if entries[-1]['address'] == entry['address']: + # last entry has the same address, reusing + entries[-1]['eos'] = True + else: + entries.append(entry) + + remove_dead_entries(entries) + + # return entries sorted by the address field + return sorted(entries, key=lambda entry: entry['address']) + + +def normalize_path(path): + return path.replace('\\', '/').replace('//', '/') + + +def build_sourcemap(entries, code_section_offset, prefixes, collect_sources, base_path): + sources = [] + sources_content = [] if collect_sources else None + mappings = [] + sources_map = {} + last_address = 0 + last_source_id = 0 + last_line = 1 + last_column = 1 + for entry in entries: + line = entry['line'] + column = entry['column'] + # ignore entries with line 0 + if line == 0: + continue + # start at least at column 1 + if column == 0: + column = 1 + address = entry['address'] + code_section_offset + file_name = entry['file'] + file_name = normalize_path(file_name) + # if prefixes were provided, we use that; otherwise, we emit a relative + # path + if prefixes.provided(): + source_name = prefixes.sources.resolve(file_name) + else: + try: + file_name = os.path.relpath(file_name, base_path) + except ValueError: + file_name = os.path.abspath(file_name) + file_name = normalize_path(file_name) + source_name = file_name + if source_name not in sources_map: + source_id = len(sources) + sources_map[source_name] = source_id + sources.append(source_name) + if collect_sources: + load_name = prefixes.load.resolve(file_name) + try: + with open(load_name, 'r') as infile: + source_content = infile.read() + sources_content.append(source_content) + except IOError: + print('Failed to read source: %s' % load_name) + sources_content.append(None) + else: + source_id = sources_map[source_name] + + address_delta = address - last_address + source_id_delta = source_id - last_source_id + line_delta = line - last_line + column_delta = column - last_column + mappings.append(encode_vlq(address_delta) + encode_vlq(source_id_delta) + + encode_vlq(line_delta) + encode_vlq(column_delta)) + last_address = address + last_source_id = source_id + last_line = line + last_column = column + return OrderedDict([('version', 3), + ('names', []), + ('sources', sources), + ('sourcesContent', sources_content), + ('mappings', ','.join(mappings))]) + + +def main(): + options = parse_args() + + wasm_input = options.wasm + with open(wasm_input, 'rb') as infile: + wasm = infile.read() + + entries = read_dwarf_entries(wasm_input, options) + + code_section_offset = get_code_section_offset(wasm) + + prefixes = SourceMapPrefixes(sources=Prefixes( + options.prefix), load=Prefixes(options.load_prefix)) + + logger.debug('Saving to %s' % options.output) + map = build_sourcemap(entries, code_section_offset, + prefixes, options.sources, options.basepath) + with open(options.output, 'w') as outfile: + json.dump(map, outfile, separators=(',', ':')) + + if options.strip: + wasm = strip_debug_sections(wasm) + + if options.source_map_url: + wasm = append_source_mapping(wasm, options.source_map_url) + + if options.w: + logger.debug('Saving wasm to %s' % options.w) + with open(options.w, 'wb') as outfile: + outfile.write(wasm) + + logger.debug('Done') + return 0 + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG if os.environ.get( + 'EMCC_DEBUG') else logging.INFO) + sys.exit(main()) diff --git a/veilid-wasm/wasm_build.sh b/veilid-wasm/wasm_build.sh new file mode 100755 index 00000000..614973dc --- /dev/null +++ b/veilid-wasm/wasm_build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +if [[ "$1" == "release" ]]; then + RELEASE=-r + GENERATE_SOURCE_MAP= +else + RELEASE= + RUSTFLAGS="-O -g" + GENERATE_SOURCE_MAP="./wasm-sourcemap.py ../target/wasm32-unknown-unknown/debug/veilid_flutter.wasm -o ../target/wasm32-unknown-unknown/debug/veilid_flutter.wasm.map --dwarfdump `which llvm-dwarfdump`" +fi + +pushd $SCRIPTDIR 2> /dev/null +cargo build --target wasm32-unknown-unknown $RELEASE +$GENERATE_SOURCE_MAP +popd 2> /dev/null \ No newline at end of file