@ruby/wasm-wasi 2.5.1 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/browser.script.umd.js +181 -66
  2. package/dist/browser.umd.js +181 -66
  3. package/dist/cjs/bindgen/interfaces/ruby-js-js-runtime.d.ts +45 -0
  4. package/dist/cjs/bindgen/interfaces/ruby-js-ruby-runtime.d.ts +42 -0
  5. package/dist/cjs/bindgen/{rb-js-abi-host.d.ts → legacy/rb-js-abi-host.d.ts} +3 -3
  6. package/dist/cjs/bindgen/{rb-js-abi-host.js → legacy/rb-js-abi-host.js} +2 -2
  7. package/dist/cjs/binding.d.ts +54 -0
  8. package/dist/cjs/binding.js +72 -0
  9. package/dist/cjs/browser.js +6 -1
  10. package/dist/cjs/node.js +1 -1
  11. package/dist/cjs/vm.d.ts +12 -5
  12. package/dist/cjs/vm.js +104 -63
  13. package/dist/esm/bindgen/interfaces/ruby-js-js-runtime.d.ts +45 -0
  14. package/dist/esm/bindgen/interfaces/ruby-js-ruby-runtime.d.ts +42 -0
  15. package/dist/esm/bindgen/{rb-js-abi-host.d.ts → legacy/rb-js-abi-host.d.ts} +3 -3
  16. package/dist/esm/bindgen/{rb-js-abi-host.js → legacy/rb-js-abi-host.js} +2 -2
  17. package/dist/esm/binding.d.ts +54 -0
  18. package/dist/esm/binding.js +66 -0
  19. package/dist/esm/browser.js +7 -2
  20. package/dist/esm/node.js +1 -1
  21. package/dist/esm/vm.d.ts +12 -5
  22. package/dist/esm/vm.js +104 -63
  23. package/dist/index.umd.js +169 -63
  24. package/package.json +9 -8
  25. /package/dist/cjs/bindgen/{intrinsics.d.ts → legacy/intrinsics.d.ts} +0 -0
  26. /package/dist/cjs/bindgen/{intrinsics.js → legacy/intrinsics.js} +0 -0
  27. /package/dist/cjs/bindgen/{rb-abi-guest.d.ts → legacy/rb-abi-guest.d.ts} +0 -0
  28. /package/dist/cjs/bindgen/{rb-abi-guest.js → legacy/rb-abi-guest.js} +0 -0
  29. /package/dist/esm/bindgen/{intrinsics.d.ts → legacy/intrinsics.d.ts} +0 -0
  30. /package/dist/esm/bindgen/{intrinsics.js → legacy/intrinsics.js} +0 -0
  31. /package/dist/esm/bindgen/{rb-abi-guest.d.ts → legacy/rb-abi-guest.d.ts} +0 -0
  32. /package/dist/esm/bindgen/{rb-abi-guest.js → legacy/rb-abi-guest.js} +0 -0
package/dist/index.umd.js CHANGED
@@ -500,13 +500,13 @@
500
500
  data_view(memory).setInt32(arg1 + 4, len0, true);
501
501
  data_view(memory).setInt32(arg1 + 0, ptr0, true);
502
502
  };
503
- imports["rb-js-abi-host"]["js-value-to-integer: func(value: handle<js-abi-value>) -> variant { f64(float64), bignum(string) }"] = function(arg0, arg1) {
503
+ imports["rb-js-abi-host"]["js-value-to-integer: func(value: handle<js-abi-value>) -> variant { as-float(float64), bignum(string) }"] = function(arg0, arg1) {
504
504
  const memory = get_export("memory");
505
505
  const realloc = get_export("cabi_realloc");
506
506
  const ret0 = obj.jsValueToInteger(resources0.get(arg0));
507
507
  const variant1 = ret0;
508
508
  switch (variant1.tag) {
509
- case "f64": {
509
+ case "as-float": {
510
510
  const e = variant1.val;
511
511
  data_view(memory).setInt8(arg1 + 0, 0, true);
512
512
  data_view(memory).setFloat64(arg1 + 8, +e, true);
@@ -702,6 +702,72 @@
702
702
  };
703
703
  }
704
704
 
705
+ class LegacyBinding extends RbAbiGuest {
706
+ async setInstance(instance) {
707
+ await this.instantiate(instance);
708
+ }
709
+ }
710
+ class ComponentBinding {
711
+ constructor() { }
712
+ setUnderlying(underlying) {
713
+ this.underlying = underlying;
714
+ }
715
+ rubyShowVersion() {
716
+ this.underlying.rubyShowVersion();
717
+ }
718
+ rubyInit() {
719
+ this.underlying.rubyInit();
720
+ }
721
+ rubySysinit(args) {
722
+ this.underlying.rubySysinit(args);
723
+ }
724
+ rubyOptions(args) {
725
+ this.underlying.rubyOptions(args);
726
+ }
727
+ rubyScript(name) {
728
+ this.underlying.rubyScript(name);
729
+ }
730
+ rubyInitLoadpath() {
731
+ this.underlying.rubyInitLoadpath();
732
+ }
733
+ rbEvalStringProtect(str) {
734
+ return this.underlying.rbEvalStringProtect(str);
735
+ }
736
+ rbFuncallvProtect(recv, mid, args) {
737
+ return this.underlying.rbFuncallvProtect(recv, mid, args);
738
+ }
739
+ rbIntern(name) {
740
+ return this.underlying.rbIntern(name);
741
+ }
742
+ rbErrinfo() {
743
+ return this.underlying.rbErrinfo();
744
+ }
745
+ rbClearErrinfo() {
746
+ return this.underlying.rbClearErrinfo();
747
+ }
748
+ rstringPtr(value) {
749
+ return this.underlying.rstringPtr(value);
750
+ }
751
+ rbVmBugreport() {
752
+ this.underlying.rbVmBugreport();
753
+ }
754
+ rbGcEnable() {
755
+ return this.underlying.rbGcEnable();
756
+ }
757
+ rbGcDisable() {
758
+ return this.underlying.rbGcDisable();
759
+ }
760
+ rbSetShouldProhibitRewind(newValue) {
761
+ return this.underlying.rbSetShouldProhibitRewind(newValue);
762
+ }
763
+ async setInstance(instance) {
764
+ // No-op
765
+ }
766
+ addToImports(imports) {
767
+ // No-op
768
+ }
769
+ }
770
+
705
771
  /**
706
772
  * A Ruby VM instance
707
773
  *
@@ -722,7 +788,7 @@
722
788
  *
723
789
  */
724
790
  class RubyVM {
725
- constructor() {
791
+ constructor(binding) {
726
792
  this.instance = null;
727
793
  this.interfaceState = {
728
794
  hasJSFrameAfterRbFrame: false,
@@ -731,6 +797,7 @@
731
797
  // if the call stack has sandwitched JS frames like JS -> Ruby -> JS -> Ruby.
732
798
  const proxyExports = (exports) => {
733
799
  const excludedMethods = [
800
+ "setInstance",
734
801
  "addToImports",
735
802
  "instantiate",
736
803
  "rbSetShouldProhibitRewind",
@@ -765,10 +832,34 @@
765
832
  }
766
833
  return exports;
767
834
  };
768
- this.guest = proxyExports(new RbAbiGuest());
835
+ this.guest = proxyExports(binding !== null && binding !== void 0 ? binding : new LegacyBinding());
769
836
  this.transport = new JsValueTransport();
770
837
  this.exceptionFormatter = new RbExceptionFormatter();
771
838
  }
839
+ static async _instantiate(initComponent, options) {
840
+ const binding = new ComponentBinding();
841
+ const vm = new RubyVM(binding);
842
+ class JsAbiValue {
843
+ constructor(underlying) {
844
+ this.underlying = underlying;
845
+ }
846
+ }
847
+ const imports = vm.getImports((from) => new JsAbiValue(from), (to) => to.underlying);
848
+ const component = await initComponent(Object.assign(Object.assign({}, imports), { throwProhibitRewindException: (message) => {
849
+ vm.throwProhibitRewindException(message);
850
+ }, procToJsFunction: () => {
851
+ const rbValue = new RbValue(component.exportRbValueToJs(), vm, vm.privateObject());
852
+ return new JsAbiValue((...args) => {
853
+ return rbValue.call("call", ...args.map((arg) => vm.wrap(arg))).toJS();
854
+ });
855
+ }, rbObjectToJsRbValue: () => {
856
+ const rbValue = new RbValue(component.exportRbValueToJs(), vm, vm.privateObject());
857
+ return new JsAbiValue(rbValue);
858
+ }, JsAbiValue: JsAbiValue }));
859
+ binding.setUnderlying(component);
860
+ vm.initialize(options.args);
861
+ return vm;
862
+ }
772
863
  /**
773
864
  * Initialize the Ruby VM with the given command line arguments
774
865
  * @param args The command line arguments to pass to Ruby. Must be
@@ -792,7 +883,7 @@
792
883
  */
793
884
  async setInstance(instance) {
794
885
  this.instance = instance;
795
- await this.guest.instantiate(instance);
886
+ await this.guest.setInstance(instance);
796
887
  }
797
888
  /**
798
889
  * Add intrinsic import entries, which is necessary to interact JavaScript
@@ -801,43 +892,36 @@
801
892
  */
802
893
  addToImports(imports) {
803
894
  this.guest.addToImports(imports);
804
- function wrapTry(f) {
805
- return (...args) => {
806
- try {
807
- return { tag: "success", val: f(...args) };
808
- }
809
- catch (e) {
810
- if (e instanceof RbFatalError) {
811
- // RbFatalError should not be caught by Ruby because it Ruby VM
812
- // can be already in an inconsistent state.
813
- throw e;
814
- }
815
- return { tag: "failure", val: e };
816
- }
817
- };
818
- }
819
895
  imports["rb-js-abi-host"] = {
820
896
  rb_wasm_throw_prohibit_rewind_exception: (messagePtr, messageLen) => {
821
897
  const memory = this.instance.exports.memory;
822
898
  const str = new TextDecoder().decode(new Uint8Array(memory.buffer, messagePtr, messageLen));
823
- let message = "Ruby APIs that may rewind the VM stack are prohibited under nested VM operation " +
824
- `(${str})\n` +
825
- "Nested VM operation means that the call stack has sandwitched JS frames like JS -> Ruby -> JS -> Ruby " +
826
- "caused by something like `window.rubyVM.eval(\"JS.global[:rubyVM].eval('Fiber.yield')\")`\n" +
827
- "\n" +
828
- "Please check your call stack and make sure that you are **not** doing any of the following inside the nested Ruby frame:\n" +
829
- " 1. Switching fibers (e.g. Fiber#resume, Fiber.yield, and Fiber#transfer)\n" +
830
- " Note that `evalAsync` JS API switches fibers internally\n" +
831
- " 2. Raising uncaught exceptions\n" +
832
- " Please catch all exceptions inside the nested operation\n" +
833
- " 3. Calling Continuation APIs\n";
834
- const error = new RbValue(this.guest.rbErrinfo(), this, this.privateObject());
835
- if (error.call("nil?").toString() === "false") {
836
- message += "\n" + this.exceptionFormatter.format(error, this, this.privateObject());
837
- }
838
- throw new RbFatalError(message);
899
+ this.throwProhibitRewindException(str);
839
900
  },
840
901
  };
902
+ addRbJsAbiHostToImports(imports, this.getImports((value) => value, (value) => value), (name) => {
903
+ return this.instance.exports[name];
904
+ });
905
+ }
906
+ throwProhibitRewindException(str) {
907
+ let message = "Ruby APIs that may rewind the VM stack are prohibited under nested VM operation " +
908
+ `(${str})\n` +
909
+ "Nested VM operation means that the call stack has sandwitched JS frames like JS -> Ruby -> JS -> Ruby " +
910
+ "caused by something like `window.rubyVM.eval(\"JS.global[:rubyVM].eval('Fiber.yield')\")`\n" +
911
+ "\n" +
912
+ "Please check your call stack and make sure that you are **not** doing any of the following inside the nested Ruby frame:\n" +
913
+ " 1. Switching fibers (e.g. Fiber#resume, Fiber.yield, and Fiber#transfer)\n" +
914
+ " Note that `evalAsync` JS API switches fibers internally\n" +
915
+ " 2. Raising uncaught exceptions\n" +
916
+ " Please catch all exceptions inside the nested operation\n" +
917
+ " 3. Calling Continuation APIs\n";
918
+ const error = new RbValue(this.guest.rbErrinfo(), this, this.privateObject());
919
+ if (error.call("nil?").toString() === "false") {
920
+ message += "\n" + this.exceptionFormatter.format(error, this, this.privateObject());
921
+ }
922
+ throw new RbFatalError(message);
923
+ }
924
+ getImports(toJSAbiValue, fromJSAbiValue) {
841
925
  // NOTE: The GC may collect objects that are still referenced by Wasm
842
926
  // locals because Asyncify cannot scan the Wasm stack above the JS frame.
843
927
  // So we need to keep track whether the JS frame is sandwitched by Ruby
@@ -856,9 +940,24 @@
856
940
  }
857
941
  return imports;
858
942
  };
859
- addRbJsAbiHostToImports(imports, proxyImports({
943
+ function wrapTry(f) {
944
+ return (...args) => {
945
+ try {
946
+ return { tag: "success", val: f(...args) };
947
+ }
948
+ catch (e) {
949
+ if (e instanceof RbFatalError) {
950
+ // RbFatalError should not be caught by Ruby because it Ruby VM
951
+ // can be already in an inconsistent state.
952
+ throw e;
953
+ }
954
+ return { tag: "failure", val: toJSAbiValue(e) };
955
+ }
956
+ };
957
+ }
958
+ return proxyImports({
860
959
  evalJs: wrapTry((code) => {
861
- return Function(code)();
960
+ return toJSAbiValue(Function(code)());
862
961
  }),
863
962
  isJs: (value) => {
864
963
  // Just for compatibility with the old JS API
@@ -866,45 +965,47 @@
866
965
  },
867
966
  globalThis: () => {
868
967
  if (typeof globalThis !== "undefined") {
869
- return globalThis;
968
+ return toJSAbiValue(globalThis);
870
969
  }
871
970
  else if (typeof global !== "undefined") {
872
- return global;
971
+ return toJSAbiValue(global);
873
972
  }
874
973
  else if (typeof window !== "undefined") {
875
- return window;
974
+ return toJSAbiValue(window);
876
975
  }
877
976
  throw new Error("unable to locate global object");
878
977
  },
879
978
  intToJsNumber: (value) => {
880
- return value;
979
+ return toJSAbiValue(value);
881
980
  },
882
981
  floatToJsNumber: (value) => {
883
- return value;
982
+ return toJSAbiValue(value);
884
983
  },
885
984
  stringToJsString: (value) => {
886
- return value;
985
+ return toJSAbiValue(value);
887
986
  },
888
987
  boolToJsBool: (value) => {
889
- return value;
988
+ return toJSAbiValue(value);
890
989
  },
891
990
  procToJsFunction: (rawRbAbiValue) => {
892
991
  const rbValue = this.rbValueOfPointer(rawRbAbiValue);
893
- return (...args) => {
992
+ return toJSAbiValue((...args) => {
894
993
  return rbValue.call("call", ...args.map((arg) => this.wrap(arg))).toJS();
895
- };
994
+ });
896
995
  },
897
996
  rbObjectToJsRbValue: (rawRbAbiValue) => {
898
- return this.rbValueOfPointer(rawRbAbiValue);
997
+ return toJSAbiValue(this.rbValueOfPointer(rawRbAbiValue));
899
998
  },
900
999
  jsValueToString: (value) => {
1000
+ value = fromJSAbiValue(value);
901
1001
  // According to the [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value)
902
1002
  // `String(value)` always returns a string.
903
1003
  return String(value);
904
1004
  },
905
1005
  jsValueToInteger(value) {
1006
+ value = fromJSAbiValue(value);
906
1007
  if (typeof value === "number") {
907
- return { tag: "f64", val: value };
1008
+ return { tag: "as-float", val: value };
908
1009
  }
909
1010
  else if (typeof value === "bigint") {
910
1011
  return { tag: "bignum", val: BigInt(value).toString(10) + "\0" };
@@ -913,38 +1014,40 @@
913
1014
  return { tag: "bignum", val: value + "\0" };
914
1015
  }
915
1016
  else if (typeof value === "undefined") {
916
- return { tag: "f64", val: 0 };
1017
+ return { tag: "as-float", val: 0 };
917
1018
  }
918
1019
  else {
919
- return { tag: "f64", val: Number(value) };
1020
+ return { tag: "as-float", val: Number(value) };
920
1021
  }
921
1022
  },
922
1023
  exportJsValueToHost: (value) => {
923
1024
  // See `JsValueExporter` for the reason why we need to do this
924
- this.transport.takeJsValue(value);
1025
+ this.transport.takeJsValue(fromJSAbiValue(value));
925
1026
  },
926
1027
  importJsValueFromHost: () => {
927
- return this.transport.consumeJsValue();
1028
+ return toJSAbiValue(this.transport.consumeJsValue());
928
1029
  },
929
1030
  instanceOf: (value, klass) => {
1031
+ klass = fromJSAbiValue(klass);
930
1032
  if (typeof klass === "function") {
931
- return value instanceof klass;
1033
+ return fromJSAbiValue(value) instanceof klass;
932
1034
  }
933
1035
  else {
934
1036
  return false;
935
1037
  }
936
1038
  },
937
1039
  jsValueTypeof(value) {
938
- return typeof value;
1040
+ return typeof fromJSAbiValue(value);
939
1041
  },
940
1042
  jsValueEqual(lhs, rhs) {
941
- return lhs == rhs;
1043
+ return fromJSAbiValue(lhs) == fromJSAbiValue(rhs);
942
1044
  },
943
1045
  jsValueStrictlyEqual(lhs, rhs) {
944
- return lhs === rhs;
1046
+ return fromJSAbiValue(lhs) === fromJSAbiValue(rhs);
945
1047
  },
946
1048
  reflectApply: wrapTry((target, thisArgument, args) => {
947
- return Reflect.apply(target, thisArgument, args);
1049
+ const jsArgs = args.map((arg) => fromJSAbiValue(arg));
1050
+ return toJSAbiValue(Reflect.apply(fromJSAbiValue(target), fromJSAbiValue(thisArgument), jsArgs));
948
1051
  }),
949
1052
  reflectConstruct: function (target, args) {
950
1053
  throw new Error("Function not implemented.");
@@ -953,7 +1056,7 @@
953
1056
  throw new Error("Function not implemented.");
954
1057
  },
955
1058
  reflectGet: wrapTry((target, propertyKey) => {
956
- return target[propertyKey];
1059
+ return toJSAbiValue(fromJSAbiValue(target)[propertyKey]);
957
1060
  }),
958
1061
  reflectGetOwnPropertyDescriptor: function (target, propertyKey) {
959
1062
  throw new Error("Function not implemented.");
@@ -974,13 +1077,11 @@
974
1077
  throw new Error("Function not implemented.");
975
1078
  },
976
1079
  reflectSet: wrapTry((target, propertyKey, value) => {
977
- return Reflect.set(target, propertyKey, value);
1080
+ return toJSAbiValue(Reflect.set(fromJSAbiValue(target), propertyKey, fromJSAbiValue(value)));
978
1081
  }),
979
1082
  reflectSetPrototypeOf: function (target, prototype) {
980
1083
  throw new Error("Function not implemented.");
981
1084
  },
982
- }), (name) => {
983
- return this.instance.exports[name];
984
1085
  });
985
1086
  }
986
1087
  /**
@@ -1313,7 +1414,12 @@
1313
1414
  }
1314
1415
  // All JS exceptions triggered by Ruby code are translated to Ruby exceptions,
1315
1416
  // so non-RbError exceptions are unexpected.
1316
- vm.guest.rbVmBugreport();
1417
+ try {
1418
+ vm.guest.rbVmBugreport();
1419
+ }
1420
+ catch (e) {
1421
+ console.error("Tried to report internal Ruby VM state but failed: ", e);
1422
+ }
1317
1423
  if (e instanceof WebAssembly.RuntimeError && e.message === "unreachable") {
1318
1424
  const error = new RbError(`Something went wrong in Ruby VM: ${e}`);
1319
1425
  error.stack = e.stack;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruby/wasm-wasi",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "WebAssembly port of CRuby with WASI",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -39,28 +39,29 @@
39
39
  ],
40
40
  "license": "MIT",
41
41
  "scripts": {
42
- "test:run": "npm run test:unit && npm run test:jest && npm run test:e2e",
43
- "test:jest": "NODE_OPTIONS=\"--experimental-wasi-unstable-preview1\" jest --coverage",
42
+ "test:run:all": "npm run test:run && ENABLE_COMPONENT_TESTS=1 npm run test:run",
43
+ "test:run": "npm run test:unit && npm run test:vitest -- --run && npm run test:e2e",
44
+ "test:vitest": "vitest ./test/",
44
45
  "test:unit": "./tools/run-test-unit.mjs",
45
46
  "test:e2e": "playwright install && npm run test:e2e:examples && npm run test:e2e:integrations",
46
47
  "test:e2e:examples": "playwright test -c test-e2e/playwright.examples.config.ts",
47
48
  "test:e2e:integrations": "playwright test -c test-e2e/playwright.integrations.config.ts",
48
49
  "serve:example": "BUNDLE_GEMFILE=../../../Gemfile bundle exec ruby -run -e httpd ./example -p 8085",
49
50
  "format": "prettier --write .",
50
- "build:static": "./tools/pack-bindgen-src.sh ./dist",
51
+ "build:static": "./tools/pack-bindgen-src.rb ./dist",
51
52
  "build:rollup": "rollup -c rollup.config.mjs",
52
53
  "build:tsc": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json",
53
54
  "build": "npm run build:rollup && npm run build:tsc && npm run build:static && ./tools/post-build.sh ./dist"
54
55
  },
55
56
  "devDependencies": {
57
+ "@bjorn3/browser_wasi_shim": "^0.3.0",
58
+ "@bytecodealliance/jco": "../../../vendor/jco",
56
59
  "@rollup/plugin-node-resolve": "^15.2.3",
57
60
  "@rollup/plugin-typescript": "^11.1.6",
58
- "@types/jest": "^29.5.11",
59
61
  "@types/node": "20.12.2",
60
- "jest": "^29.7.0",
61
62
  "prettier": "^3.2.5",
62
- "@bjorn3/browser_wasi_shim": "^0.2.19",
63
- "typescript": "^5.4.3"
63
+ "typescript": "^5.4.3",
64
+ "vitest": "^1.5.3"
64
65
  },
65
66
  "dependencies": {
66
67
  "tslib": "^2.6.1"