Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,2 @@
js_tooling.js
node_modules

View file

@ -0,0 +1,311 @@
// 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);

View file

@ -0,0 +1,74 @@
#!/usr/bin/env node
// generate_snapshot.js is a barebones tool for creating snapshots from within an automated process,
// with the goal of eliminating build-time dependencies in exchange for a more narrow set of
// capabilities
var fs = require('fs');
var jsCompiler = require('./js_tooling');
function writeToFile(argv, dataToWrite) {
var data;
if (Array.isArray(dataToWrite)) {
data = byteArrayToStr(dataToWrite);
}
else if (typeof dataToWrite == 'object') {
data = JSON.stringify(dataToWrite);
}
else {
data = dataToWrite;
}
fs.writeFileSync(argv.output, data, 'binary');
}
function byteArrayToStr(arr) {
var result = '';
for (i = 0; i < arr.length; i++) {
result += String.fromCharCode(arr[i]);
}
return result;
}
if (require.main === module) {
if (process.argv.length < 4) {
console.log("Usage: " + __filename + " JS_FILE OUTPUT [MEMORY_REPORT]");
process.exit(-1);
}
var js_file = process.argv[2];
var output_file = process.argv[3];
var snapshot_size_file = 'snapshot_size.json';
if (process.argv.length == 5) {
snapshot_size_file = process.argv[4];
}
var expectedArgs = {
prefix: function(v) {return decodeURIComponent(v)},
maxsize: function(v) {return parseInt(v)},
padding: function(v) {return parseInt(v)}
};
var options = {};
Object.keys(expectedArgs).forEach(function(k) {
var idx = process.argv.indexOf('--' + k);
if (idx >= 0) {
options[k] = expectedArgs[k](process.argv[idx+1]);
}
});
var js = fs.readFileSync(js_file, 'utf8');
var result = jsCompiler.createSnapshot(js, options);
if (result.result != 'success') {
console.error(result['result'] + ':', result['reason']);
process.exit(1);
} else {
writeToFile({'output': output_file}, result['snapshot']);
// This call writes out the bytecode memory usage for
// /tools/resources/resource_map/resource_generator.py to read. Please update that file
// if you make changes to the output format/filename here
var snapshot_size_data = {size: result['snapshotSize'], max: result['snapshotMaxSize']};
writeToFile({'output': snapshot_size_file}, snapshot_size_data);
}
}

155
third_party/jerryscript/js_tooling/index.js vendored Executable file
View file

@ -0,0 +1,155 @@
#!/usr/bin/env node
// entry point for node-based CLI that produces snapshots (e.g. Pebble SDK)
var yargs = require('yargs');
var jsCompiler = require('./js_tooling');
var fs = require('fs');
var zip = require('node-zip');
yargs
.command('compile', 'compiles JavaScript to byte code', function(yargs) {
return defaultOptions(yargs);
}, compile)
.command('patch', 'patches an existing PBPack or PBW file', function(yargs) {
return defaultOptions(yargs)
.option('pbpack', {
describe: 'PBPack file to patch',
type: 'string',
})
.option('pbw', {
describe: 'PBW file to patch',
type: 'string',
})
.check(function(argv, arr) {
if (typeof (argv.pbw) == typeof (argv.pbpack)) {
throw 'Need to specifiy either --pbw or --pbpack'
}
return true;
});
}, patch)
.help()
.detectLocale(false) // force english
.check(function(argv, arr) {
throw "No command provided."
})
.argv;
function defaultOptions(yargs) {
return yargs
.option('js', {
describe: 'JavaScript input file (or - for stdin)',
demand: true,
type: 'string'
})
.option('prefix', {
describe: 'Non-standard sequence of bytes to prepend snapshot with (passed through decodeURIComponent())',
type: 'string'
})
.option('maxsize', {
describe: 'Maximum number of bytes the resulting snapshot (including padding) can be',
type: 'number',
default: jsCompiler.defaultSnapshotMaxSize
})
.option('padding', {
describe: 'Number of bytes to add to the snapshot for padding',
type: 'number',
default: 0
})
.option('output', {
describe: 'output file (or - for stdout)',
demand: true,
type: 'string'
})
.strict()
.check(function(argv, arr) {
if (argv._.length != 1) {
throw 'Ambiguous command provided: "' + argv._.join(' ') + '"'
}
return true;
});
}
function snapshotOptions(argv) {
return {
prefix: argv.prefix ? decodeURIComponent(argv.prefix) : undefined,
maxsize: argv.maxsize,
padding: argv.padding
};
}
function compile(argv) {
var js = readJS(argv);
var result = jsCompiler.createSnapshot(js, snapshotOptions(argv));
bailOnError(result);
writeOutput(argv, result.snapshot)
}
function patchPBW(js, inputBytes, argv) {
var pbw = zip(inputBytes);
// add or replace the JS in the PBW
pbw.file('rocky-app.js', js);
// this does the actual compiling (multiple times, yes, there's room for improvement)
Object.getOwnPropertyNames(pbw.files).forEach(function(prop) {
if (prop.endsWith('.pbpack')) {
var pbpack = pbw.files[prop].asUint8Array();
var result = jsCompiler.patchPBPack(js, pbpack, snapshotOptions(argv));
if (result.result != 'success') {
return result;
}
pbw.file(prop, new Buffer(result.pbpack));
}
});
return {
result: 'success',
pbw: pbw.generate({type:'string', compression:'DEFLATE'})
};
}
function patch(argv) {
var js = readJS(argv);
var inputBytes = strToByteArray(fs.readFileSync(argv.pbpack || argv.pbw, 'binary'));
var result = argv.pbpack ? jsCompiler.patchPBPack(js, inputBytes, snapshotOptions(argv)) : patchPBW(js, inputBytes, argv);
bailOnError(result);
writeOutput(argv, result.pbpack || result.pbw);
}
function strToByteArray(str) {
var result = new Array(str.length);
for (var i = 0; i < str.length; i++) {
result[i] = str[i].charCodeAt(0);
}
return result;
}
function byteArrayToStr(arr) {
var result = '';
for (i = 0; i < arr.length; i++) {
result += String.fromCharCode(arr[i]);
}
return result;
}
function bailOnError(returnValue, prop) {
if (returnValue.result != 'success') {
console.error(returnValue.result + ':', returnValue.reason);
process.exit(1);
}
}
function writeOutput(argv, bytesOrString) {
var data = typeof bytesOrString === 'string' ? bytesOrString : byteArrayToStr(bytesOrString);
var isStdOut = argv.output == '/dev/stdout';
if (isStdOut) {
fs.writeSync(1, data, 'binary');
} else {
fs.writeFileSync(argv.output, data, 'binary');
}
}
function readJS(argv) {
return fs.readFileSync(argv.js, 'utf8');
}

View file

@ -0,0 +1,70 @@
#include "jerry-api.h"
#include "jerry-port.h"
#include "jcontext.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
jerry_context_t jerry_global_context;
jmem_heap_t jerry_global_heap __attribute__((__aligned__(JMEM_ALIGNMENT)));
jerry_hash_table_t jerry_global_hash_table;
size_t jerry_parse_and_save_snapshot_from_zt_utf8_string(
const jerry_char_t *zt_utf8_source_p,
bool is_for_global, bool is_strict,
uint8_t *buffer_p, size_t buffer_size) {
return jerry_parse_and_save_snapshot(
zt_utf8_source_p, strlen((char *)zt_utf8_source_p),
is_for_global, is_strict,
buffer_p, buffer_size);
}
// helper routine to create proper snapshot header from js_tooling
size_t rocky_fill_header(uint8_t *buffer, size_t buffer_size) {
const uint8_t ROCKY_EXPECTED_SNAPSHOT_HEADER[8] = {'P', 'J', 'S', 0, CAPABILITY_JAVASCRIPT_BYTECODE_VERSION, 0, 0, 0};
const size_t ROCKY_HEADER_SIZE = sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
if (buffer == NULL || buffer_size < ROCKY_HEADER_SIZE) {
return 0;
}
memcpy(buffer, ROCKY_EXPECTED_SNAPSHOT_HEADER, ROCKY_HEADER_SIZE);
return ROCKY_HEADER_SIZE;
}
int test_str_len(char * str) {
return strlen(str);
}
// return true, if you handled the error message
typedef bool (*JerryPortErrorMsgHandler)(char *msg);
static JerryPortErrorMsgHandler s_jerry_port_errormsg_handler;
void jerry_port_set_errormsg_handler(JerryPortErrorMsgHandler handler) {
s_jerry_port_errormsg_handler = handler;
}
static void prv_log(const char *format, va_list args) {
char msg[1024] = {0};
const int result = vsnprintf(msg, sizeof(msg), format, args);
if (s_jerry_port_errormsg_handler == NULL || !s_jerry_port_errormsg_handler(msg)) {
printf("%s", msg);
}
}
void jerry_port_log(jerry_log_level_t level, const char* format, ...) {
va_list args;
va_start(args, format);
prv_log(format, args);
va_end(args);
}
void jerry_port_console(const char *format, ...) {
if (format[0] == '\n' && strlen(format) == 1) {
return;
}
va_list args;
va_start(args, format);
prv_log(format, args);
va_end(args);
}

View file

@ -0,0 +1,9 @@
{
"__comment": "Only needed for the node.js-based version of this compiler if we want to do CLI-based compiling",
"name": "js_compiler",
"version": "0.0.1",
"devDependencies": {
"node-zip": "^1.1.1",
"yargs": "^4.7.1"
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,26 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
};

View file

@ -0,0 +1 @@
"😀😢";

View file

@ -0,0 +1 @@
var x = "

View file

@ -0,0 +1,19 @@
{
"name": "js_tooling-unittests",
"version": "1.0.0",
"description": "",
"main": "test_js_tooling.js",
"devDependencies": {
"eslint": "^2.10.2",
"eslint-plugin-import": "^1.11.1",
"eslint-plugin-jsx-a11y": "^2.0.1",
"mocha": "^2.5.3",
"unroll": "^1.1.0"
},
"scripts": {
"test": "NODE_PATH=.. ./node_modules/mocha/bin/mocha *.js",
"lint": "./node_modules/eslint/bin/eslint.js -c .eslintrc.js *.js"
},
"author": "",
"license": "© Pebble Technology Corp."
}

View file

@ -0,0 +1,24 @@
/* eslint-env mocha */
/* eslint func-names: 0 */
const assert = require('assert');
const path = require('path');
const unroll = require('unroll');
unroll.use(it);
const fs = require('fs');
const jsCompiler = require('../_js_tooling.js');
describe('js_tooling.js', () => {
unroll('compiles #filename with #expectedResult', (done, fixture) => {
var js_file = path.join('fixtures', fixture.filename);
var js = fs.readFileSync(js_file, 'utf8');
const result = jsCompiler.createSnapshot(js);
assert.equal(result.result, fixture.expectedResult);
done();
}, [
['filename', 'expectedResult'],
['multiple-emojis.js', 'success'],
['syntax-error.js', 'error']
]);
});

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python
import re
import sys
# when cross-compiling JerryScript with Emscripten, we need to fix a few minor things
# to account for the various setups as some of the code assumes to be execute inside of node
def replace_ensured(s, old, new):
result = s.replace(old, new)
# make sure our search pattern `old` actually matched something
# if we didn't change anything it means that we missed the Emscrupten outout (e.g. new version)
assert result != s, "Emscripten output does not match expected output of 1.35.0"
return result
# load file to be processed
with open(sys.argv[1], "r") as f:
source = f.read()
# source = replace_ensured(source, "func = eval('_' + ident); // explicit lookup",
# "// func = eval('_' + ident); // explicit lookup")
source = replace_ensured(source, "process['on']('uncaughtException',",
"process['on']('uncaughtException-ignore',")
source = "(function(){\n%s\n})(this);" % source
with open(sys.argv[1], "w") as f:
f.write(source)