UNPKG

wasmexplorer-wasm-compiler

Version:

Compile `.c/++` files to `.wasm` format using [WasmExplorer](https://mbebenita.github.io/WasmExplorer/) service by [@mbebenita](https://github.com/mbebenita).

212 lines (169 loc) 5.08 kB
/** * @module wasmexplorer-wasm-compiler */ const fs = require('fs'); const path = require('path'); const request = require('request'); const utils = require('./utils'); const activeRequests = {}; const method = 'POST'; const url = 'https://wasmexplorer-service.herokuapp.com/service.php'; /** * Options for compiler * @global * @typedef {object} CompilerOptions * @property {string} language=C99 - Language used in souuce. One of [C89, C99, C++98, C++11, C++14, C++1z] * @property {string} optimisationLevel=s - Optimisation level to apply. One of [0, 1, 2, 3, 4, s] * @property {bool} clean=false - Generate clean WAST. * @property {bool} fastMath=false - Approximate floating point calculations for faster execution (may yield incorrect results. * @property {bool} noInline=false - Do not expand functions inline. * @property {bool} noRTTI=false - Do not generate runtime type information. * @property {bool} noExceptions=false - Disable exception handling. try/catch will raise errors. */ /** * Return default options for compiler * @returns {CompilerOptions} */ function getDefaultCompilerOptions() { return { language: 'C99', optimisationLevel: 's', clean: false, fastMath: false, noInline: false, noRTTI: false, noExceptions: false, }; } /** * Convert source to WAST * @returns {promise} */ function cxx2wast(action, input, options, srcFilepath) { return new Promise((res, rej) => { const req = request( { method, url, form: { action, input, options } }, (err, resp) => { // activeRequests[srcFilepath] = null; if (err) { return rej(err); } res(resp.body); }, ); activeRequests[srcFilepath] = req; }); } /** * Convert C source to WAST * @returns {cxx2wast()} */ function c2wast(input, options, srcFilepath) { return cxx2wast('c2wast', input, options, srcFilepath); } /** * Convert C++ source to WAST * @returns {cxx2wast()} */ function cpp2wast(input, options, srcFilepath) { return cxx2wast('cpp2wast', input, options, srcFilepath); } /** * Convert WAST to WASM * @returns {promise} */ function wast2wasm(input, srcFilepath) { return new Promise((res, rej) => { const req = request( { method, url, form: { action: 'wast2wasm', input } }, (err, resp) => { // activeRequests[srcFilepath] = null; if (err) { return rej(err); } res(resp.body); }, ); activeRequests[srcFilepath] = req; }); } /** * Check whether source code is in C++ or not. * @param {string} language - Language used in source * @returns {bool} */ function isCPP(language) { return language.toUpperCase().startsWith('C++'); } /** * Convert compiler options into gcc flags string. * @param {CompilerOptions} compilerOptions * @returns {string} */ function stringify(compilerOptions) { const flags = []; const languages = ['C89', 'C99', 'C++98', 'C++11', 'C++14', 'C++1z']; const optimisationLevels = ['0', '1', '2', '3', '4', 's']; const language = compilerOptions.language.toUpperCase(); if (languages.includes(language)) { flags.push(`-std=${language}`); } const optimisationLevel = compilerOptions.optimisationLevel.toString(); if (optimisationLevels.includes(optimisationLevel)) { flags.push(`-O${optimisationLevel}`); } if (compilerOptions.clean) { flags.push('--clean'); } if (compilerOptions.fastMath) { flags.push('-ffast-math'); } if (compilerOptions.noInline) { flags.push('-fno-inline'); } if (compilerOptions.noRTTI) { flags.push('-fno-rtti'); } if (compilerOptions.noExceptions) { flags.push('-fno-exceptions'); } return flags.join(' '); } /** * @global * @param {string} srcFilepath - Source file's absolute path. * @param {string} dstFilepath - Destination file's absolute path. * @param {CompilerOptions} compilerOptions - Options for compiler */ async function compile(srcFilepath, dstFilepath, compilerOptions = {}) { utils.info(`Begin compiling ${srcFilepath}`); compilerOptions = Object.assign(getDefaultCompilerOptions(), compilerOptions); if (srcFilepath in activeRequests) { const req = activeRequests[srcFilepath]; req.abort(); } const src = fs.readFileSync(srcFilepath, 'utf8'); const optionsString = stringify(compilerOptions); utils.log('Building...'); const wast = isCPP(compilerOptions.language) ? await cpp2wast(src, optionsString, srcFilepath) : await c2wast(src, optionsString, srcFilepath); if (wast.includes('...')) { const errorMsg = wast.replace( /\.\.\.[a-z0-9]{32}\.(.*):(\d+):(\d+)/g, `${dstFilepath}:$2:$3`, ); utils.error(errorMsg); return; } utils.log('Compiling...'); const wasm = await wast2wasm(wast, srcFilepath); fs.writeFileSync( dstFilepath, wasm.replace('-----WASM binary data\n', ''), 'base64', ); utils.success(`Finished compiling ${path.basename(dstFilepath)}`); delete activeRequests[srcFilepath]; } module.exports = compile;