Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Rust ⇋ JavaScript

7,266 views

Published on

My talk from RustFest (Kyiv, 2017)

Published in: Technology

Rust ⇋ JavaScript

  1. 1. Rust JavaScript Ingvar Stepanyan @RReverser
  2. 2. Wait but why
  3. 3. JavaScript: true cross-platform target
  4. 4. JavaScript: true cross-platform target
  5. 5. JavaScript: true cross-platform target
  6. 6. JavaScript: true cross-platform target
  7. 7. JavaScript: true cross-platform target
  8. 8. JavaScript: true cross-platform target
  9. 9. JavaScript: true cross-platform target
  10. 10. JavaScript: true cross-platform target
  11. 11. JavaScript: true cross-platform target
  12. 12. Wait but how
  13. 13. C/C++ CFG/SSA LLVM bitcode Native objects Native executable Clang (LLVM)
  14. 14. C/C++ CFG/SSA  LLVM bitcode JavaScript / WASM Emscripten (LLVM)
  15. 15. Rust CFG/SSA LLVM bitcode Native objects Native executable Rust (LLVM)
  16. 16. Emscripten + Rust Rust CFG/SSA LLVM bitcode Native objects Native executable C/C++ CFG/SSA  LLVM bitcode JavaScript / WASM
  17. 17. Emscripten + Rust Rust CFG/SSA LLVM bitcode Native objects Native executable C/C++ CFG/SSA  LLVM bitcode JavaScript / WASM
  18. 18. Emscripten + Rust Rust CFG/SSA LLVM bitcode C/C++ CFG/SSA  LLVM bitcode JavaScript / WASM
  19. 19. Hell o'world
  20. 20. // hello.rs fn main() { println!("Hello, world"); }
  21. 21. $ rustc --target=asmjs-unknown-emscripten hello.rs $ node hello.js Hello, world
  22. 22. Exporting functions #[no_mangle] pub extern fn add_integers(x: i32, y: i32) -> i32 { return x + y; }
  23. 23. Exporting functions #[no_mangle] pub extern fn add_integers(x: i32, y: i32) -> i32 { return x + y; } fn main() {}
  24. 24. $ rustc --target=asmjs-unknown-emscripten hello.rs $ node > require('./hello') $
  25. 25. Exporting functions #![feature(link_args)] #[no_mangle] pub extern fn add_integers(x: i32, y: i32) -> i32 { return x + y; } #[link_args = "-s NO_EXIT_RUNTIME=1"] extern {} fn main() {}
  26. 26. Exporting functions #[no_mangle] pub extern fn add_integers(x: i32, y: i32) -> i32 { return x + y; } extern { fn emscripten_exit_with_live_runtime(); } fn main() { unsafe { emscripten_exit_with_live_runtime(); } }
  27. 27. $ rustc --target=asmjs-unknown-emscripten hello.rs $ node > require('./hello') exit(0) implicitly called by end of main(), but noExitRuntime, so not exiting the runtime (you can use emscripten_force_exit, if you want to force a true shutdown) [Emscripten Module object] > require('./hello')._add_integers(10, 20) 30
  28. 28. Exporting functions $ emcc ... "hello.0.o" "-o" "hello.js" "-s" "EXPORTED_FUNCTIONS=["_add_integers","_main", "_rust_eh_personality"]" ...
  29. 29. Exporting functions var real__add_integers = asm["_add_integers"]; asm["_add_integers"] = function() { assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)'); assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)'); return real__add_integers.apply(null, arguments); };
  30. 30. Generated code (asm.js) function _add_integers($0,$1) { $0 = $0|0; $1 = $1|0; var $$arith = 0, $$ispos = 0, $$negcheck = 0, $$negtemp = 0, $$poscheck = 0, $ $postemp = 0, $$select = 0, $2 = 0, label = 0, sp = 0; sp = STACKTOP; $$arith = (($0) + ($1))|0; $$postemp = (($0) + -2147483648)|0; $$negtemp = (($0) + 2147483647)|0; $$poscheck = ($$arith|0)<($$postemp|0); $$negcheck = ($$arith|0)>($$negtemp|0); $$ispos = ($0|0)>=(0); $$select = $$ispos ? $$poscheck : $$negcheck; $2 = $$select; if ($2) { __ZN4core9panicking5panic17h0c8c35aaab94c092E(2160); // unreachable; } else { return ($$arith|0); } return (0)|0; }
  31. 31. Optimized code (asm.js) function _add_integers($0, $1) { $0 = $0 | 0; $1 = $1 | 0; return $1 + $0 | 0; //@line 47 }
  32. 32. Optimized code (WASM) (func $_add_integers (param $0 i32) (param $1 i32) (result i32) (i32.add (get_local $1) (get_local $0) ) )
  33. 33. asm.js <-> wasm x+y|0 (i32.add (get_local $x) (get_local $y)) +(x+y) (i32.add (get_local $x) (get_local $y)) f()|0 (call $f) HEAP32[ptr>>2]|0 (i32.load (get_local $ptr))
  34. 34. Memory model var HEAP8 = new global.Int8Array(buffer); var HEAP16 = new global.Int16Array(buffer); var HEAP32 = new global.Int32Array(buffer); var HEAPU8 = new global.Uint8Array(buffer); var HEAPU16 = new global.Uint16Array(buffer); var HEAPU32 = new global.Uint32Array(buffer); var HEAPF32 = new global.Float32Array(buffer); var HEAPF64 = new global.Float64Array(buffer);
  35. 35. Memory model
  36. 36. Calling JavaScript #include <emscripten.h> int main() { EM_ASM( alert("who doesn't like popups?"); ); return 0; }
  37. 37. Calling JavaScript // system/include/emscripten/em_asm.h #define EM_ASM(...) emscripten_asm_const(#__VA_ARGS__) #define EM_ASM_(code, ...) emscripten_asm_const_int(#code, __VA_ARGS__) ...
  38. 38. Calling JavaScript emscripten_asm_const: true, emscripten_asm_const_int: true, emscripten_asm_const_double: true,
  39. 39. Calling JavaScript function _main() { var label = 0, sp = 0; sp = STACKTOP; _emscripten_asm_const_v(0); return 0; }
  40. 40. Calling JavaScript var ASM_CONSTS = [function() { alert("who doesn't like popups?"); }]; function _emscripten_asm_const_v(code) { return ASM_CONSTS[code](); }
  41. 41. Calling JavaScript extern { fn emscripten_asm_const(code: &str, ...); }
  42. 42. Calling JavaScript extern { fn emscripten_asm_const(code: *const u8, ...); }
  43. 43. Calling JavaScript unsafe { emscripten_asm_const_int(b"alert("who doesn't like popups?");"); }
  44. 44. Calling JavaScript unsafe { emscripten_asm_const_int(b"alert("who doesn't like popups?");".as_ptr()); }
  45. 45. Calling JavaScript unsafe { emscripten_asm_const_int(b"alert("who doesn't like popups?");" as *const u8); }
  46. 46. Calling JavaScript unsafe { emscripten_asm_const_int(b"alert("who doesn't like popups?");0" as *const u8); }
  47. 47. Calling JavaScript macro_rules! js { ($expr:expr $(,$arg:expr)*) => (unsafe { emscripten_asm_const( concat!(stringify!($expr), "0") as *const str as *const u8 ) }) }
  48. 48. Calling JavaScript js! { alert("who doesn't like popups?"); }
  49. 49. Calling JavaScript
  50. 50. But what about fancy types
  51. 51. Embind C++ magic
  52. 52. #include <emscripten/bind.h> using namespace emscripten; class MyClass { public: MyClass(int x) : x(x) {} int getX() const { return x; } void setX(int x_) { x = x_; } private: int x; };
  53. 53. // Binding code EMSCRIPTEN_BINDINGS(my_class_example) { class_<MyClass>("MyClass") .constructor<int>() .property("x", &MyClass::getX, &MyClass::setX) ; }
  54. 54. $ emcc --bind hello.cpp -o hello.js
  55. 55. > module = require('./hello') [Emscripten Module object] > myObj = new module.MyClass(10) MyClass {} > myObj.x 10
  56. 56. > myObj.$$.ptr 5247384 > module.HEAP32[myObj.$$.ptr >> 2] 10
  57. 57. Embind C++ magic
  58. 58. EMSCRIPTEN_ALWAYS_INLINE explicit class_(const char* name) { using namespace internal; BaseSpecifier::template verify<ClassType>(); auto _getActualType = &getActualType<ClassType>; auto upcast = BaseSpecifier::template getUpcaster<ClassType>(); auto downcast = BaseSpecifier::template getDowncaster<ClassType>(); auto destructor = &raw_destructor<ClassType>; _embind_register_class( TypeID<ClassType>::get(), TypeID<AllowedRawPointer<ClassType>>::get(), TypeID<AllowedRawPointer<const ClassType>>::get(), BaseSpecifier::get(), getSignature(_getActualType), reinterpret_cast<GenericFunction>(_getActualType), getSignature(upcast), reinterpret_cast<GenericFunction>(upcast), getSignature(downcast), reinterpret_cast<GenericFunction>(downcast), name, getSignature(destructor), reinterpret_cast<GenericFunction>(destructor)); }
  59. 59. EMSCRIPTEN_ALWAYS_INLINE explicit class_(const char* name) { using namespace internal; BaseSpecifier::template verify<ClassType>(); auto _getActualType = &getActualType<ClassType>; auto upcast = BaseSpecifier::template getUpcaster<ClassType>(); auto downcast = BaseSpecifier::template getDowncaster<ClassType>(); auto destructor = &raw_destructor<ClassType>; _embind_register_class( TypeID<ClassType>::get(), TypeID<AllowedRawPointer<ClassType>>::get(), TypeID<AllowedRawPointer<const ClassType>>::get(), BaseSpecifier::get(), getSignature(_getActualType), reinterpret_cast<GenericFunction>(_getActualType), getSignature(upcast), reinterpret_cast<GenericFunction>(upcast), getSignature(downcast), reinterpret_cast<GenericFunction>(downcast), name, getSignature(destructor), reinterpret_cast<GenericFunction>(destructor)); }
  60. 60. Reverse-engineering FTW
  61. 61. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( ... ); ... }
  62. 62. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  63. 63. #![feature(core_intrinsics)] ::std::intrinsics::type_id::<T>()
  64. 64. #![feature(core_intrinsics)] ::std::intrinsics::type_id::<T>() as u32
  65. 65. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  66. 66. extern fn get_actual_type<T: 'static>(arg: *const void) -> u32 { unsafe { type_id::<T>() } }
  67. 67. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  68. 68. void smth(int x, float y, const char *z);
  69. 69. void smth(int x, float y, const char *z); "vifi"
  70. 70. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  71. 71. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  72. 72. ::std::intrinsics::type_name::<T>()
  73. 73. CString::new(::std::intrinsics::type_name::<T>()) .unwrap() .as_ptr(),
  74. 74. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  75. 75. extern fn destructure<T: 'static>(arg: *mut void) { unsafe { Box::from_raw(arg as *mut T); } }
  76. 76. // Implemented in JavaScript. Don't call these directly. extern "C" { ... void _embind_register_class( TYPEID classType, TYPEID pointerType, TYPEID constPointerType, TYPEID baseClassType, const char* getActualTypeSignature, GenericFunction getActualType, const char* upcastSignature, GenericFunction upcast, const char* downcastSignature, GenericFunction downcast, const char* className, const char* destructorSignature, GenericFunction destructor); ... }
  77. 77. unsafe { _embind_register_class( type_id::<T>(), type_id::<*mut T>(), type_id::<*const T>(), null(), b"ii0".as_ptr(), get_actual_type::<T>, b"v0".as_ptr(), noop, b"v0".as_ptr(), noop, CString::new(type_name::<T>()).unwrap().as_ptr(), b"vi0".as_ptr(), destructure::<T> ) }
  78. 78. unsafe { _embind_register_class( type_id::<T>(), type_id::<*mut T>(), type_id::<*const T>(), null(), cstr!("ii"), get_actual_type::<T>, cstr!("v"), noop, cstr!("v"), noop, CString::new(type_name::<T>()).unwrap().as_ptr(), cstr!("vi"), destructure::<T> ) }
  79. 79. void _embind_register_class_constructor( TYPEID classType, unsigned argCount, const TYPEID argTypes[], const char* invokerSignature, GenericFunction invoker, GenericFunction constructor);
  80. 80. extern { fn _embind_register_class_constructor( cls_type: TypeId, arg_count: usize, arg_types: *const TypeId, invoker_signature: CStr, invoker: extern fn ( fn () -> Box<void> ) -> *mut void, ctor: fn () -> Box<void> ); }
  81. 81. #[allow(improper_ctypes)] extern { fn _embind_register_class_constructor( cls_type: TypeId, arg_count: usize, arg_types: *const TypeId, invoker_signature: CStr, invoker: extern fn ( fn () -> Box<void> ) -> *mut void, ctor: fn () -> Box<void> ); }
  82. 82. extern fn invoker(f: fn () -> Box<void>) -> *mut void { Box::into_raw(f()) }
  83. 83. unsafe { let arg_types = [type_id::<*mut T>()]; _embind_register_class_constructor( type_id::<T>(), arg_types.len(), arg_types.as_ptr(), cstr!("ii"), invoker, ::std::mem::transmute( Box::<T>::default as fn () -> Box<_> ) ) }
  84. 84. register_class::<MyStruct>(); register_class_default_ctor::<MyStruct>();
  85. 85. _embind_register_* • void • bool • integer • float • std_string • std_wstring • memory_view • function • class • enum • smart_ptr • ...
  86. 86. JavaScript static libraries
  87. 87. mergeInto(LibraryManager.library, { my_js: function() { alert('hi'); }, });
  88. 88. #[link_args = "--js-library rustlib.js"] extern { fn my_js(); }
  89. 89. mergeInto(LibraryManager.library, { _embind_register_rust_string__deps: ['$registerType'], _embind_register_rust_string: function(rawType) { registerType(rawType, { name: "&str", 'argPackAdvance': 8, 'readValueFromPointer': function (pointer) { pointer >>= 2; var length = HEAPU32[pointer + 1]; pointer = HEAPU32[pointer]; return Pointer_stringify(pointer, length); } }); }, })
  90. 90. #[link_args = "--js-library rustlib.js"] extern { fn _embind_register_rust_string(type_id: u32); } ... _embind_register_rust_string(type_id::<&str>());
  91. 91. Garbage collection
  92. 92. Garbage collection window [1] 5
  93. 93. Garbage collection window [2] 5 _emval_incref (aka Clone)
  94. 94. Garbage collection window [2] "document" 5 6 _emval_take_value(...) aka Val::from("document").handle
  95. 95. Garbage collection window [2] "document" document 5 6 7 _emval_get_property(...) aka global.get("document").handle
  96. 96. Garbage collection window [1] document 5 7 _emval_decref(...) aka Drop for "document" and Drop for window
  97. 97. Serde + Emscripten
  98. 98. #[derive(Serialize)] struct S { x: &'static str, y: u64, z: [f64; 2] }
  99. 99. let s = S { x: "hello, world", y: 42, z: [123.456, 789.] }; let val = s.serialize(Serializer).unwrap();
  100. 100. { x: "hello, world", y: 42, z: [123.456, 789] }
  101. 101. Speed (2.8M JSON) native JSON.parse serde-json 0ms 25ms 50ms 75ms 100ms
  102. 102. Speed (2.8M JSON) via JSON embind 0ms 27.5ms 55ms 82.5ms 110ms
  103. 103. Useful links • https://github.com/RReverser/asmjs-experiments - safe bindings for Embind • https://kripken.github.io/emscripten-site/docs/ api_reference/emscripten.h.html - Emscripten APIs • https://github.com/rust-lang/rust/pull/41409 - allow linking JS libraries as normal libs • https://github.com/rust-lang/cargo/pull/3954 - let Cargo run binaries, tests, benchmarks on Emscripten target via Node.js
  104. 104. unsafe { get_questions() } Ingvar Stepanyan @RReverser

×