Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Rust ⇋ JavaScript

8.758 Aufrufe

Veröffentlicht am

My talk from RustFest (Kyiv, 2017)

Veröffentlicht in: Technologie
  • Als Erste(r) kommentieren

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

×