├── test ├── test.bc ├── test.cpp ├── README.md ├── Makefile ├── test.js └── webpack.config.js ├── package.json ├── .gitignore ├── UNLICENSE ├── index.js └── README.md /test/test.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/llvmbc-wasm-loader/master/test/test.bc -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | extern "C" __attribute__((used)) int some_function() { 2 | return 123; 3 | } 4 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # A minimal test for `llvmbc-wasm-loader` 2 | 3 | Simple invocation: 4 | 5 | ```bash 6 | $ make 7 | ``` 8 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | test: bundle.js 2 | node bundle.js 3 | 4 | bundle.js: test.bc test.js 5 | webpack --debug 6 | 7 | test.bc: test.cpp 8 | em++ test.cpp -o test.bc 9 | 10 | .PHONY: test 11 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import test from './test.bc' 2 | import test2 from './test.bc?withFunctionPointers' 3 | 4 | test.ready.then(() => { 5 | if (test._some_function() != 123) { 6 | throw new Error("expected some_function() to return 123") 7 | } 8 | }) 9 | 10 | test2.ready.then(() => { 11 | let funcPtr = test2.Runtime.addFunction(function() { 12 | }) 13 | if (test2._some_function() != 123) { 14 | throw new Error("expected some_function() to return 123") 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llvmbc-wasm-loader", 3 | "version": "1.0.0", 4 | "author": "Zack Voase ", 5 | "description": "Webpack loader which converts LLVM bytecode to WASM using Emscripten", 6 | "homepage": "https://github.com/zacharyvoase/llvmbc-wasm-loader", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/zacharyvoase/llvmbc-wasm-loader.git" 10 | }, 11 | "keywords": [ 12 | "llvm", 13 | "wasm", 14 | "webassembly", 15 | "webpack" 16 | ], 17 | "license": "Unlicense", 18 | "main": "index.js", 19 | "dependencies": { 20 | "loader-utils": "^1.1.0", 21 | "shell-escape": "^0.2.0", 22 | "tmp": "^0.0.33" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | target: 'web', 3 | entry: "./test.js", 4 | output: { 5 | path: __dirname, 6 | filename: "bundle.js" 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.bc$/, 12 | oneOf: [ 13 | { 14 | resourceQuery: /^\?withFunctionPointers$/, 15 | use: [ 16 | { 17 | loader: '..', 18 | options: { 19 | command: ['emcc', '-s', 'NO_EXIT_RUNTIME=1', '-s', 'RESERVED_FUNCTION_POINTERS=10'] 20 | } 21 | } 22 | ] 23 | }, 24 | { 25 | use: '..' 26 | } 27 | ] 28 | } 29 | ] 30 | }, 31 | node: { 32 | fs: 'empty', 33 | path: 'empty' 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | const fs = require('fs') 3 | const loaderUtils = require('loader-utils') 4 | const path = require('path') 5 | const shellEscape = require('shell-escape') 6 | const tmp = require('tmp') 7 | 8 | const defaultOptions = { 9 | command: [ 10 | 'emcc', '-s', 'NO_EXIT_RUNTIME=1' 11 | ] 12 | } 13 | 14 | module.exports = function(source) { 15 | const callback = this.async() 16 | 17 | const opts = Object.assign({}, defaultOptions, loaderUtils.getOptions(this)) 18 | 19 | const tmpDir = tmp.dirSync({ unsafeCleanup: true }); 20 | 21 | bcDigest = loaderUtils.getHashDigest(source, 'sha1', 'hex', 16); 22 | 23 | const outFile = path.join(tmpDir.name, "compiled." + bcDigest + ".js") 24 | const outWasm = path.join(tmpDir.name, "compiled." + bcDigest + ".wasm") 25 | 26 | const command = opts.command.concat([this.resourcePath, '-o', outFile, '-s', 'WASM=1']) 27 | if (this.debug) { 28 | console.log(shellEscape(command)) 29 | } 30 | 31 | child_process.exec(shellEscape(command), { cwd: this.context }, (err, stdout, stderr) => { 32 | if (err) { 33 | return callback(err, null); 34 | } 35 | const out = fs.readFileSync(outFile); 36 | const binaryWasm = fs.readFileSync(outWasm); 37 | const encodedWasm = binaryWasm.toString('base64'); 38 | const moduleSrc = ` 39 | var b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 40 | 41 | var lookup = new Uint8Array(256); 42 | for (var i = 0; i < b64chars.length; i++) { 43 | lookup[b64chars.charCodeAt(i)] = i; 44 | } 45 | 46 | var decode = function(length, b64) { 47 | var arrayBuffer = new ArrayBuffer(length); 48 | var bytes = new Uint8Array(arrayBuffer); 49 | var p = 0, enc1, enc2, enc3, enc4; 50 | 51 | for (i = 0; i < b64.length; i+= 4) { 52 | enc1 = lookup[b64.charCodeAt(i)]; 53 | enc2 = lookup[b64.charCodeAt(i + 1)]; 54 | enc3 = lookup[b64.charCodeAt(i + 2)]; 55 | enc4 = lookup[b64.charCodeAt(i + 3)]; 56 | bytes[p++] = (enc1 << 2) | (enc2 >> 4); 57 | bytes[p++] = ((enc2 & 15) << 4) | (enc3 >> 2); 58 | bytes[p++] = ((enc3 & 3) << 6) | (enc4 & 63); 59 | } 60 | 61 | return arrayBuffer; 62 | } 63 | 64 | module.exports = (function() { 65 | var Module = {}, readyResolve; 66 | 67 | Module['ready'] = new Promise((resolve, reject) => { 68 | readyResolve = resolve; 69 | }); 70 | 71 | Module['onRuntimeInitialized'] = () => readyResolve(); 72 | 73 | Module['wasmBinary'] = decode(${binaryWasm.length}, "${encodedWasm}"); 74 | 75 | ${out} 76 | 77 | return Module; 78 | })(); 79 | `; 80 | tmpDir.removeCallback(); 81 | callback(null, moduleSrc); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLVM Bytecode Loader 2 | 3 | This package allows you to `require()` LLVM bytecode files, automatically 4 | compiling them to JS + WebAssembly using [Emscripten][]. 5 | 6 | [emscripten]: https://kripken.github.io/emscripten-site/index.html 7 | 8 | ## Installation & Requirements 9 | 10 | This loader itself can be installed via npm or Yarn: 11 | 12 | npm install llvmbc-wasm-loader 13 | 14 | You'll also need a working Emscripten installation, as the loader shells out to 15 | `emcc` to convert the bytecode files to JS/WASM. 16 | 17 | 18 | ## Basic usage 19 | 20 | Create a simple C++ file: 21 | 22 | ```cpp 23 | // mylibrary.cpp 24 | #include 25 | 26 | extern "C" EMSCRIPTEN_KEEPALIVE int add(int x, int y) { 27 | return x + y; 28 | } 29 | ``` 30 | 31 | Use `em++` to compile it to LLVM Intermediate Representation bytecode: 32 | 33 | ```bash 34 | $ em++ mylibrary.cpp -o mylibrary.bc 35 | ``` 36 | 37 | `require()` it from your JS file: 38 | 39 | ```js 40 | // mylibrary.js 41 | var lib = require('llvmbc-wasm-loader!./mylibrary.bc') 42 | 43 | // You need to wait for this Promise to complete: 44 | lib.ready.then(function() { 45 | console.log("1 + 2 = " + lib._add(1, 2)); 46 | } 47 | ``` 48 | 49 | Write a webpack config: 50 | 51 | ```js 52 | // webpack.config.js 53 | module.exports = { 54 | target: 'node', 55 | entry: './mylibrary.js', 56 | output: { 57 | filename: 'bundle.js' 58 | } 59 | } 60 | ``` 61 | 62 | Compile with webpack: 63 | 64 | ```bash 65 | $ webpack 66 | ``` 67 | 68 | And run the bundle with node: 69 | 70 | ```bash 71 | $ node bundle.js 72 | 1 + 2 = 3 73 | ``` 74 | 75 | 76 | ## Loader Options 77 | 78 | ### `emscriptenCommand` 79 | 80 | This should be a list representing the `emcc` command line to be invoked to 81 | produce the `.js` and `.wasm` files for a given input. The default is: 82 | 83 | ```js 84 | [ 85 | 'emcc', 86 | // Prevents the runtime from being shutdown after invocation of a 87 | // `main()` function, if any. This allows for library usage. 88 | '-s', 'NO_EXIT_RUNTIME=1', 89 | ] 90 | ``` 91 | 92 | To all command lines, the following necessary arguments will be appended: 93 | 94 | ```js 95 | [ 96 | '-s', 'WASM=1', // Necessary to produce WASM output 97 | INFILE, 98 | '-o', OUTFILE 99 | ] 100 | ``` 101 | 102 | If you're using multiple distinct `emcc` invocations in your application, we 103 | recommend using [`resourceQuery`][rq] to differentiate between them, rather 104 | than specifying rather unwieldy commands in the loader query syntax. In your 105 | `webpack.config.js`: 106 | 107 | ```js 108 | module.exports = { 109 | // ... 110 | module: { 111 | rules: { 112 | { 113 | test: /\.bc$/, 114 | // `oneOf` will pick the first matching rule: 115 | oneOf: [ 116 | // `import '.../foo.bc?withFunctionPointers'` will resolve to these options: 117 | { 118 | resourceQuery: /^\?withFunctionPointers$/, 119 | use: { 120 | loader: 'llvmbc-wasm-loader', 121 | options: { 122 | command: ['emcc', '-s', 'NO_EXIT_RUNTIME=1', '-s', 'RESERVED_FUNCTION_POINTERS=1'] 123 | } 124 | } 125 | }, 126 | // All other imports will resolve to the default config: 127 | { 128 | use: 'llvmbc-wasm-loader' 129 | } 130 | ] 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | Then, from your application: 138 | 139 | ```js 140 | import lib1 from './lib1.bc?withFunctionPointers' 141 | import lib2 from './lib2.bc' 142 | 143 | // ... 144 | ``` 145 | 146 | [rq]: https://webpack.js.org/configuration/module/#rule-resourcequery 147 | 148 | 149 | ## License 150 | 151 | This library is released under the Unlicense: 152 | 153 | > This is free and unencumbered software released into the public domain. 154 | > 155 | > Anyone is free to copy, modify, publish, use, compile, sell, or 156 | > distribute this software, either in source code form or as a compiled 157 | > binary, for any purpose, commercial or non-commercial, and by any 158 | > means. 159 | > 160 | > In jurisdictions that recognize copyright laws, the author or authors 161 | > of this software dedicate any and all copyright interest in the 162 | > software to the public domain. We make this dedication for the benefit 163 | > of the public at large and to the detriment of our heirs and 164 | > successors. We intend this dedication to be an overt act of 165 | > relinquishment in perpetuity of all present and future rights to this 166 | > software under copyright law. 167 | > 168 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 169 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 170 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 171 | > IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 172 | > OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 173 | > ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 174 | > OTHER DEALINGS IN THE SOFTWARE. 175 | > 176 | > For more information, please refer to 177 | --------------------------------------------------------------------------------