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
JavaScript
/**
* @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;