pebble/third_party/jerryscript/js_tooling/_js_tooling.js

312 lines
10 KiB
JavaScript
Raw Permalink Normal View History

// This file will be concatenated to the end of the cross-compiled compiler
// it's been written in a way such that
// 1. (development) it can be used to develop the functions and call require the cross-compiled version
// 2. concatenated into the actual compiler as either
// 2a.) (CLI) ...an npm module so that the functionality is exposed as functions via CommonJS
// 2b.) (plain JS) ...as a standalone JS file to be used in a browser or on an empty JS context
(function(global, jerry){
if (typeof jerry === 'undefined') {
// in development environment (if uses include _js_tooling.js instead of js_tooling.js) we use this indirection
// to write wrapper code without the need to re-run Emscripten
jerry = require('../../../../../build/src/fw/vendor/jerryscript/js_tooling/js_tooling.js');
}
// size_t
// jerry_parse_and_save_snapshot_from_zt_utf8_string (
// const jerry_char_t *zt_utf8_source_p, /**< zero-terminated UTF-8 script source */
// bool is_for_global, /**< snapshot would be executed as global (true)
// bool is_strict, /**< strict mode */
// uint8_t *buffer_p, /**< buffer to save snapshot to */
// size_t buffer_size) /**< the buffer's size */
var jerry_parse_and_save_snapshot_from_zt_utf8_string = function(zt_utf8_source_p, is_for_global, is_strict, buffer_p, buffer_size) {
return jerry['ccall'](
'jerry_parse_and_save_snapshot_from_zt_utf8_string',
'number',
['string', 'number', 'number', 'number', 'number'],
[zt_utf8_source_p, is_for_global, is_strict, buffer_p, buffer_size]);
};
// uint32_t legacy_defective_checksum_memory(const void * restrict data, size_t length);
var legacy_defective_checksum_memory = function(data, length) {
return jerry['ccall'](
'legacy_defective_checksum_memory',
'number',
['number', 'number'],
[data, length]
);
};
// size_t size_t rocky_fill_header(uint8_t *buffer, size_t buffer_size);
var rocky_fill_header = function(buffer, buffer_size) {
return jerry['ccall'](
'rocky_fill_header',
'number',
['number', 'number'],
[buffer, buffer_size]
);
};
// void jerry_port_set_errormsg_handler(JerryPortErrorMsgHandler handler)
var jerry_port_set_errormsg_handler = function(funcPtr) {
return jerry['ccall'](
'jerry_port_set_errormsg_handler',
'void',
['number'],
[funcPtr]
);
};
var malloc = jerry['_malloc'];
var memset = jerry['_memset'];
var getValue = jerry['getValue'];
var setValue = jerry['setValue'];
var free = jerry['_free'];
function error(reason) {
return {
'result': 'error',
'reason': reason
};
}
// helper functions for logging timings
var captureLevel = 0;
function captureDuration(msg) {
var d = new Date();
d.msg = msg;
d.level = captureLevel++;
return d;
}
var JS_TOOLING_LOGGING;
function logDuration(d, msg) {
if (typeof(JS_TOOLING_LOGGING) === 'undefined' || !JS_TOOLING_LOGGING) {
return;
}
var indentation = '';
for (var i = 0; i < d.level; i++) {
indentation = ' ' + indentation;
}
var duration = Math.floor((new Date().getTime()-d.getTime())) + 'ms';
while (duration.length < 7) {
duration = ' ' + duration;
}
console.log(indentation + duration + ' - '+ d.msg);
captureLevel--;
}
var defaultSnapshotMaxSize = 24 * 1024;
function createSnapshot(js, options) {
var timeCreateSnapshot = captureDuration('createSnapshot');
options = options || {};
options.maxsize = Math.max(0, options.maxsize || defaultSnapshotMaxSize);
options.padding = Math.max(0, options.padding || 0);
js += '\n'; // work around: JerryScript sometimes cannot handle missing \n at EOF
var bufferAlignment = 8;
var bufferAlignmentMinus = bufferAlignment - 1;
var bufferSize = 256 * 1024;
if (options.maxsize > bufferSize) {
return error('maxsize (' + options.maxsize + ') cannot exceed ' + bufferSize);
}
var buffer = malloc(bufferSize + bufferAlignmentMinus);
memset(buffer, 0, bufferSize + bufferAlignmentMinus);
var alignedBuffer = Math.floor((buffer + bufferAlignmentMinus) / bufferAlignment) * bufferAlignment;
// add (default) prefix to the buffer
var prefixLen;
if (typeof options.prefix !== 'string') {
prefixLen = rocky_fill_header(alignedBuffer, bufferSize);
} else {
prefixLen = options.prefix.length;
for (var i = 0; i < prefixLen; i++) {
setValue(alignedBuffer + i, options.prefix.charCodeAt(i), 'i8');
}
}
if (prefixLen % 8 != 0) {
return error('length of prefix must be divisible by 8')
}
var jerryBufferStart = alignedBuffer + prefixLen;
var jerryMaxBufferSize = bufferSize - prefixLen;
var timeJerryInit = captureDuration('jerry_init');
jerry['_jerry_init'](0);
logDuration(timeJerryInit);
var collectedErrors = [];
var errorHandlerPtr = jerry['Runtime'].addFunction(function(msgPtr) {
var msg = jerry['Pointer_stringify'](msgPtr).trim();
if (msg !== 'Error:') {
collectedErrors.push(msg);
}
return true;
});
jerry_port_set_errormsg_handler(errorHandlerPtr);
var timeJerry = captureDuration('jerry_parse_and_save_snapshot');
try {
var jerryUsedBuffer = jerry_parse_and_save_snapshot_from_zt_utf8_string(
js, 1, 0, jerryBufferStart, jerryMaxBufferSize);
} catch(e) {
if (collectedErrors.length == 0) {
// in case no other error was logged through JerryScript we will at least have the Exit code
collectedErrors.push(e.message.trim());
}
return error(collectedErrors.join('. '));
}
logDuration(timeJerry);
jerry['Runtime'].removeFunction(errorHandlerPtr);
var timeJerryCleanup = captureDuration('jerry_cleanup');
jerry['_jerry_cleanup']();
logDuration(timeJerryCleanup);
// TODO: free buffer once we know how to do that reliably
if (jerryUsedBuffer == 0) {
if (collectedErrors.length === 0) {
return error('JS compilation error (no further details available)');
} else {
return error(
'JS compilation error(s): ' + collectedErrors.join(', '));
}
}
var timeArrayConversion = captureDuration('snapshot array conversion');
var snapshotLen = jerryUsedBuffer + prefixLen + options.padding;
if (snapshotLen > options.maxsize) {
return error('snapshot size ' + snapshotLen + ' exceeds maximum size ' + options.maxsize);
}
var b = new Array(snapshotLen);
// add actual snapshot data
for (i = 0; i < snapshotLen; i++) {
// TODO: should we shift this to 0..255 by adding 128
b[i] = getValue(alignedBuffer + i, 'i8');
}
logDuration(timeArrayConversion);
// TODO: Emscripten MEMORY LEAK - buffer must be freed here but calling
// jerry._free(buffer);
// here fails when calling the function the second time
logDuration(timeCreateSnapshot);
return {
'result': 'success',
'snapshot': b,
'snapshotSize': snapshotLen,
'snapshotMaxSize': options.maxsize
};
}
function patchPBPack(js, pbpack, options) {
var timepatchPBPack = captureDuration('patchPBPack');
function readUInt32(offset) {
var result = 0;
for (var i = 0; i < 4; i++) {
result |= pbpack[offset + i] << (i * 8);
}
return result;
}
function writeUInt32(offset, value) {
for (var i = 0; i < 4; i++) {
pbpack[offset + i] = (value >> (i * 8)) & 0xff;
}
}
var ENTRIES_OFFSET = 0x0C;
var CONTENT_OFFSET = 0x100C;
function offsetsForEntry(entry) {
var entryOffset = ENTRIES_OFFSET + entry * 16;
return {
'size': entryOffset + 8,
'content': CONTENT_OFFSET + readUInt32(entryOffset + 4)
};
}
function findPJSEntry(numEntries) {
for (var entry = 0; entry < numEntries; entry++) {
var offsets = offsetsForEntry(entry);
if (readUInt32(offsets.size) >= 4) {
var first4bytes = readUInt32(offsets.content);
if (first4bytes == 0x00534a50) { // 'PJS\0'==0x50,0x4A,0x53,x00 in with correct endian
return offsets;
}
}
}
return undefined;
}
var numEntries = readUInt32(0x00);
if (numEntries > 256) return error('pbpack contains more than 256 entries: ' + numEntries);
var pjsEntryOffsets = findPJSEntry(numEntries);
if (typeof pjsEntryOffsets === 'undefined') {
return error('could not find resource to patch in ' + numEntries + ' entries');
}
// TODO: implement shortcut that skips the step if versions match
var snapshot = createSnapshot(js, options);
if (snapshot['result'] !== 'success') return error(snapshot['reason']);
snapshot = snapshot['snapshot'];
var requiredSpace = snapshot.length;
var availableSpace = readUInt32(pjsEntryOffsets.size);
if (availableSpace < requiredSpace) {
return error('required byte size (' + requiredSpace + ') for resource exceeds maximum (' + availableSpace + ')');
}
var timeCopySnapshot = captureDuration('copy snapshot to pbpack');
for(var i = 0; i < snapshot.length; i++) {
pbpack[pjsEntryOffsets.content + i] = snapshot[i];
}
logDuration(timeCopySnapshot);
// ---- calc CRC
var timeCRC = captureDuration('calc CRC');
var CRC_OFFSET = 0x4;
var numCheckedBytes = pbpack.length - CONTENT_OFFSET;
var buffer = malloc(numCheckedBytes);
for(i = CONTENT_OFFSET; i < pbpack.length; i++) {
var value = pbpack[i];
setValue(buffer - CONTENT_OFFSET + i, value, 'i8');
}
var crcResult = legacy_defective_checksum_memory(buffer, numCheckedBytes);
// console.log('CRC 0x' + crcResult.toString(16));
free(buffer);
writeUInt32(CRC_OFFSET, crcResult);
logDuration(timeCRC);
logDuration(timepatchPBPack, 'PBPack');
return {
'result': 'success',
'pbpack': pbpack
};
}
var exports = global; // default, in case we are not running inside a node instance
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports;
}
exports['createSnapshot'] = createSnapshot;
exports['patchPBPack'] = patchPBPack;
exports['defaultSnapshotMaxSize'] = defaultSnapshotMaxSize;
})(this, (typeof Module !== 'undefined') ? Module : undefined);