diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a5527ca29..9a7a111a15 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,17 @@ jobs: - run: opam install --yes ocamlbuild.0.14.0 - run: cd interpreter && opam exec make all + ref-interpreter-js-library: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup OCaml + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: 4.12.x + - run: opam install --yes ocamlbuild.0.14.0 ocamlfind.1.9.5 js_of_ocaml.4.0.0 js_of_ocaml-ppx.4.0.0 + - run: cd interpreter && opam exec make wast.js + build-js-api-spec: runs-on: ubuntu-latest steps: diff --git a/interpreter/Makefile b/interpreter/Makefile index 1783a9c4d8..7820e64282 100644 --- a/interpreter/Makefile +++ b/interpreter/Makefile @@ -2,7 +2,7 @@ # package manager to build. However, Opam package management is available # optionally through the check/install/uninstall targets. # -# The $(JSLIB) target requires node.js and BuckleScript. +# The $(JSLIB).js target requires Js_of_ocaml (using ocamlfind). # # See README.me for instructions. @@ -14,7 +14,7 @@ UNOPT = $(NAME).debug OPT = $(NAME) LIB = $(NAME) ZIP = $(NAME).zip -JSLIB = wast.js +JSLIB = wast WINMAKE = winmake.bat DIRS = util syntax binary text valid runtime exec script host main tests @@ -22,6 +22,7 @@ LIBS = bigarray FLAGS = -lexflags -ml -cflags '-w +a-4-27-42-44-45-70 -warn-error +a-3' OCBA = ocamlbuild $(FLAGS) $(DIRS:%=-I %) OCB = $(OCBA) $(LIBS:%=-libs %) +JSO = js_of_ocaml -q --opt 3 JS = # set to JS shell command to run JS tests @@ -35,7 +36,7 @@ opt: $(OPT) unopt: $(UNOPT) libopt: _build/$(LIB).cmx _build/$(LIB).cmxa libunopt: _build/$(LIB).cmo _build/$(LIB).cma -jslib: $(JSLIB) +jslib: $(JSLIB).js all: unopt opt libunopt libopt test land: $(WINMAKE) all zip: $(ZIP) @@ -108,14 +109,15 @@ _build/$(LIB).cmxa: $(FILES) $(LIB).mllib _tags Makefile # Building JavaScript library -.PHONY: $(JSLIB) -$(JSLIB): $(UNOPT) - mkdir -p _build/jslib/src - cp meta/jslib/* _build/jslib - cp $(DIRS:%=_build/%/*.ml*) meta/jslib/*.ml _build/jslib/src - rm _build/jslib/src/*.ml[^i] - (cd _build/jslib; ./build.sh ../../$@) +JSLIB_DIR = meta/jslib +JSLIB_FLAGS = -I $(JSLIB_DIR) -use-ocamlfind -pkg js_of_ocaml -pkg js_of_ocaml-ppx +.INTERMEDIATE: $(JSLIB).byte +$(JSLIB).byte: $(JSLIB_DIR)/$(JSLIB).ml + $(OCBA) $(JSLIB_FLAGS) $@ + +$(JSLIB).js: $(JSLIB).byte + $(JSO) $< # Building Windows build file @@ -181,7 +183,7 @@ $(ZIP): $(WINMAKE) git archive --format=zip --prefix=$(NAME)/ -o $@ HEAD clean: - rm -rf _build/jslib $(LIB).mlpack _tags + rm -rf _build/jslib $(LIB).mlpack _tags $(JSLIB).js $(OCB) -clean diff --git a/interpreter/README.md b/interpreter/README.md index 6f55c1f3f4..11258ae62e 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -65,7 +65,10 @@ The Makefile also provides a target to compile (parts of) the interpreter into a ``` make wast.js ``` -Building this target requires node.js and BuckleScript. +Building this target requires `js_of_ocaml`, which can be installed with OPAM: +``` +opam install js_of_ocaml js_of_ocaml-ppx +``` ## Synopsis @@ -139,7 +142,7 @@ WebAssemblyText.encode(source) ``` which turns a module in S-expression syntax into a WebAssembly binary, and ``` -WebAssemblyText.decode(binary, width = 80) +WebAssemblyText.decode(binary, width) ``` which pretty-prints a binary back into a canonicalised S-expression string. @@ -151,7 +154,7 @@ let binary = WebAssemblyText.encode(source) (new WebAssembly.Instance(new WebAssembly.Module(binary))).exports.f(3, 4) // => 7 -WebAssemblyText.decode(binary) +WebAssemblyText.decode(binary, 80) // => // (module // (type $0 (func (param i32 i32) (result i32))) @@ -160,6 +163,20 @@ WebAssemblyText.decode(binary) // ) ``` +Depending on how you load the library, the object may be accessed in different ways. For example, using `require` in node.js: + +``` +let wast = require("./wast.js"); +let binary = wast.WebAssemblyText.encode("(module)"); +``` + +Or using `load` from a JavaScript shell: + +``` +load("./wast.js"); +let binary = WebAssemblyText.encode("(module)"); +``` + ## S-Expression Syntax diff --git a/interpreter/dune b/interpreter/dune index 6f6dfff3e0..48274aad99 100644 --- a/interpreter/dune +++ b/interpreter/dune @@ -4,10 +4,9 @@ (name wasm) ; The 'main' module shall not be part of the library, as it would start the ; Wasm REPL every time in all the dependencies. - ; We also need to exclude the 'wasm' module as it overlaps with the library - ; name. + ; We exclude the 'wast' module as it is only used for the JS build. ; 'smallint' is a separate test module. - (modules :standard \ main wasm smallint)) + (modules :standard \ main smallint wast)) (executable (name main) diff --git a/interpreter/meta/jslib/bsconfig.json b/interpreter/meta/jslib/bsconfig.json deleted file mode 100644 index d313bf93d8..0000000000 --- a/interpreter/meta/jslib/bsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "wasm", - "sources": [ - {"dir": "src"}, - ] -} diff --git a/interpreter/meta/jslib/build.sh b/interpreter/meta/jslib/build.sh deleted file mode 100755 index 14471217b5..0000000000 --- a/interpreter/meta/jslib/build.sh +++ /dev/null @@ -1,97 +0,0 @@ -link () { -echo "// DO NOT EDIT. Generated from WebAssembly spec interpreter" -echo " -let WebAssemblyText = (function() { -  let _registry = {__proto__: null}; - function normalize(file) { - return file.split('/').reverse()[0].split('.')[0]; - } -  function require(file) { -    let name = normalize(file); - if (!(name in _registry)) { - throw new Error('missing module: ' + name) -    } else if (typeof _registry[name] === 'function') { -" -if (($LOG == 1)) -then - echo 1>&2 Logging on - echo " - console.log(name); -" -fi -echo " -      let f = _registry[name]; -      _registry[name] = function() { throw new Error('cyclic module: ' + name) }; -      _registry[name] = f(); -    } -    return _registry[name]; -  } -" - -for file in $* -do - echo 1>&2 Including $file - name=`basename $file | sed s/[.]js//g` - echo " - _registry['$name'] = function() { -    let exports = {}; -//////// start of $name.js ////////" - cat $file - echo "//////// end of $name.js //////// -    return exports; -  }; -" -done - -echo " - function binary(bytes) { - let buffer = new ArrayBuffer(bytes.length); - let view = new Uint8Array(buffer); - for (let i = 0; i < bytes.length; ++i) { - view[i] = bytes.charCodeAt(i); - } - return buffer; - } - function bytes(buffer) { - let string = ''; - let view = new Uint8Array(buffer); - for (let i = 0; i < view.length; ++i) { - string += String.fromCodePoint(view[i]); - } - return string; - } -  let Wasm = require('wasm'); - return { - encode(s) { return binary(Wasm.encode(s)) }, - decode(b, w = 80) { return Wasm.decode(bytes(b), w) } - }; -})(); - -" -} - -echo 1>&2 ==== Preparing ==== -npm link bs-platform - -echo 1>&2 ==== Compiling ==== -bsb || exit 1 - -echo 1>&2 ==== Linking full version ==== -LOG=1 -FILES='node_modules/bs-platform/lib/js/*.js lib/js/src/*.js' -link $FILES >temp.js || exit 1 - -echo 1>&2 ==== Running for dependencies ==== -node temp.js | tee temp.log || exit 1 - -echo 1>&2 ==== Linking stripped version ==== -used='' -for file in `ls $FILES` -do - if grep -q `basename $file | sed s/.js//g` temp.log - then - used="$used $file" - fi -done -LOG=0 -link $used >$1 || exit 1 diff --git a/interpreter/meta/jslib/wasm.ml b/interpreter/meta/jslib/wasm.ml deleted file mode 100644 index 915fdafb08..0000000000 --- a/interpreter/meta/jslib/wasm.ml +++ /dev/null @@ -1,9 +0,0 @@ -let encode s = - let def = Parse.string_to_module s in - match def.Source.it with - | Script.Textual m -> Encode.encode m - | Script.Encoded (_, bs) -> bs - -let decode s width = - let m = Decode.decode "(decode)" s in - Sexpr.to_string width (Arrange.module_ m) diff --git a/interpreter/meta/jslib/wast.ml b/interpreter/meta/jslib/wast.ml new file mode 100644 index 0000000000..9af04f9189 --- /dev/null +++ b/interpreter/meta/jslib/wast.ml @@ -0,0 +1,26 @@ +(* Implements a wrapper library that allows the use of the reference + * interpreter's encode/decode functionality in JavaScript. + *) +open Js_of_ocaml + +let _ = + Js.export "WebAssemblyText" + (object%js (_self) + + method encode (s : Js.js_string Js.t) : (Typed_array.arrayBuffer Js.t) = + let def = Parse.string_to_module (Js.to_string s) in + let bs = + match def.Source.it with + | Script.Textual m -> (Encode.encode m) + | Script.Encoded (_, bs) -> bs + | Script.Quoted (_, _) -> failwith "Unsupported" in + let buf = new%js Typed_array.arrayBuffer (String.length bs) in + let u8arr = new%js Typed_array.uint8Array_fromBuffer buf in + String.iteri (fun i c -> Typed_array.set u8arr i (int_of_char c)) bs; buf + + method decode (buf : Typed_array.arrayBuffer Js.t) width : (Js.js_string Js.t) = + let s = Typed_array.String.of_uint8Array (new%js Typed_array.uint8Array_fromBuffer buf) in + let m = Decode.decode "(decode)" s in + Js.string (Sexpr.to_string width (Arrange.module_ m)) + + end)