mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
5 years ago
committed by
GitHub
6 changed files with 1038 additions and 0 deletions
@ -0,0 +1,151 @@ |
|||
'use strict'; |
|||
|
|||
const { assert } = require('../util/assert'); |
|||
const { createBareObject } = require('../util/bare'); |
|||
|
|||
const mathOneargMagic = createBareObject({ |
|||
fabs: 0, // BI_MATH_FABS_IDX
|
|||
acos: 1, // BI_MATH_ACOS_IDX
|
|||
asin: 2, // BI_MATH_ASIN_IDX
|
|||
atan: 3, // BI_MATH_ATAN_IDX
|
|||
ceil: 4, // BI_MATH_CEIL_IDX
|
|||
cos: 5, // BI_MATH_COS_IDX
|
|||
exp: 6, // BI_MATH_EXP_IDX
|
|||
floor: 7, // BI_MATH_FLOOR_IDX
|
|||
log: 8, // BI_MATH_LOG_IDX
|
|||
round: 9, // BI_MATH_ROUND_IDX
|
|||
sin: 10, // BI_MATH_SIN_IDX
|
|||
sqrt: 11, // BI_MATH_SQRT_IDX
|
|||
tan: 12, // BI_MATH_TAN_IDX
|
|||
cbrt: 13, // BI_MATH_CBRT_IDX
|
|||
log2: 14, // BI_MATH_LOG2_IDX
|
|||
log10: 15, // BI_MATH_LOG10_IDX
|
|||
trunc: 16 // BI_MATH_TRUNC_IDX
|
|||
}); |
|||
|
|||
const mathTwoargMagic = createBareObject({ |
|||
atan2: 0, // BI_MATH_ATAN2_IDX
|
|||
pow: 1 // BI_MATH_POW_IDX
|
|||
}); |
|||
|
|||
const arrayIterMagic = createBareObject({ |
|||
every: 0, // BI_ARRAY_ITER_EVERY
|
|||
some: 1, // BI_ARRAY_ITER_SOME
|
|||
forEach: 2, // BI_ARRAY_ITER_FOREACH
|
|||
map: 3, // BI_ARRAY_ITER_MAP
|
|||
filter: 4 // BI_ARRAY_ITER_FILTER
|
|||
}); |
|||
|
|||
// Magic value for typedarray/node.js buffer read field operations.
|
|||
function magicReadField(elem, signed, bigendian, typedarray) { |
|||
assert(typeof signed === 'boolean'); |
|||
assert(typeof bigendian === 'boolean'); |
|||
assert(typeof typedarray === 'boolean'); |
|||
|
|||
// Must match duk__FLD_xxx in duk_bi_buffer.c.
|
|||
var elemNum = createBareObject({ |
|||
'8bit': 0, |
|||
'16bit': 1, |
|||
'32bit': 2, |
|||
'float': 3, |
|||
'double': 4, |
|||
'varint': 5 |
|||
})[elem]; |
|||
var signedNum = (signed ? 1 : 0); |
|||
var bigendianNum = (bigendian ? 1 : 0); |
|||
var typedarrayNum = (typedarray ? 1 : 0); |
|||
return elemNum + (signedNum << 4) + (bigendianNum << 3) + (typedarrayNum << 5); |
|||
} |
|||
exports.magicReadField = magicReadField; |
|||
|
|||
// Magic value for typedarray/node.js buffer write field operations.
|
|||
function magicWriteField(elem, signed, bigendian, typedarray) { |
|||
return magicReadField(elem, signed, bigendian, typedarray); |
|||
} |
|||
exports.magicWriteField = magicWriteField; |
|||
|
|||
// Magic value for typedarray constructors.
|
|||
function magicTypedArrayConstructor(elem, shift) { |
|||
// Must match duk_hbufobj.h header.
|
|||
var elemNum = createBareObject({ |
|||
'uint8': 0, |
|||
'uint8clamped': 1, |
|||
'int8': 2, |
|||
'uint16': 3, |
|||
'int16': 4, |
|||
'uint32': 5, |
|||
'int32': 6, |
|||
'float32': 7, |
|||
'float64': 8 |
|||
})[elem]; |
|||
return (elemNum << 2) + shift; |
|||
} |
|||
exports.magicTypedArrayConstructor = magicTypedArrayConstructor; |
|||
|
|||
function resolveMagic(elem, objIdToBidx) { |
|||
var tmp; |
|||
|
|||
console.debug('resolve magic:', elem); |
|||
|
|||
if (elem === void 0 || elem === null) { |
|||
return 0; |
|||
} |
|||
if (typeof elem === 'number') { |
|||
elem = { type: 'plain', value: elem }; |
|||
} |
|||
if (!(typeof elem === 'object' && elem !== null)) { |
|||
throw new TypeError('invalid magic'); |
|||
} |
|||
|
|||
switch (elem.type) { |
|||
case 'bidx': { |
|||
// Maps to thr->builtins[].
|
|||
tmp = objIdToBidx[elem.id]; |
|||
if (typeof tmp !== 'number') { |
|||
throw new TypeError('invalid bidx magic: ' + elem.id); |
|||
} |
|||
return tmp; |
|||
} |
|||
case 'plain': { |
|||
let v = Math.floor(elem.value); |
|||
if (!(v >= -0x8000 && v <= 0x7fff)) { |
|||
throw new TypeError('invalid plain value for magic: ' + v); |
|||
} |
|||
return v; |
|||
} |
|||
case 'math_onearg': { |
|||
tmp = mathOneargMagic[elem.funcname]; |
|||
if (typeof tmp !== 'number') { |
|||
throw new TypeError('invalid math_onearg magic: ' + elem.funcname); |
|||
} |
|||
return tmp; |
|||
} |
|||
case 'math_twoarg': { |
|||
tmp = mathTwoargMagic[elem.funcname]; |
|||
if (typeof tmp !== 'number') { |
|||
throw new TypeError('invalid math_twoarg magic: ' + elem.funcname); |
|||
} |
|||
return tmp; |
|||
} |
|||
case 'array_iter': { |
|||
tmp = arrayIterMagic[elem.funcname]; |
|||
if (typeof tmp !== 'number') { |
|||
throw new TypeError('invalid array_iter magic: ' + elem.funcname); |
|||
} |
|||
return tmp; |
|||
} |
|||
case 'typedarray_constructor': { |
|||
return magicTypedArrayConstructor(elem.elem, elem.shift); |
|||
} |
|||
case 'buffer_readfield': { |
|||
return magicReadField(elem.elem, elem.signed, elem.bigendian, elem.typedarray); |
|||
} |
|||
case 'buffer_writefield': { |
|||
return magicWriteField(elem.elem, elem.signed, elem.bigendian, elem.typedarray); |
|||
} |
|||
default: { |
|||
throw new TypeError('invalid magic type: ' + elem.type); |
|||
} |
|||
} |
|||
} |
|||
exports.resolveMagic = resolveMagic; |
@ -0,0 +1,134 @@ |
|||
/* |
|||
* Garbage collect built-in objects and strings based on reachability roots. |
|||
*/ |
|||
|
|||
'use strict'; |
|||
|
|||
const { |
|||
walkObjects, |
|||
walkStrings, |
|||
walkObjectProperties, |
|||
walkObjectsAndProperties, |
|||
propDefault |
|||
} = require('./util'); |
|||
const { createBareObject } = require('../../util/bare'); |
|||
|
|||
// Mark objects with 'bidx' forcibly reachable.
|
|||
function markBidxObjectsReachable(meta) { |
|||
walkObjects(meta, (o) => { |
|||
if (propDefault(o, 'bidx_used', false)) { |
|||
o._force_reachable = 'bidx_used'; |
|||
} |
|||
}); |
|||
} |
|||
exports.markBidxObjectsReachable = markBidxObjectsReachable; |
|||
|
|||
// Mark strings with actively referenced 'stridx' forcibly reachable.
|
|||
function markStridxStringsReachable(meta) { |
|||
walkStrings(meta, (s) => { |
|||
if (s.stridx_used) { |
|||
s._force_reachable = 'stridx'; |
|||
} |
|||
}); |
|||
} |
|||
exports.markStridxStringsReachable = markStridxStringsReachable; |
|||
|
|||
// Delete objects and strings not reachable from reachability roots or
|
|||
// forced to be reachable. Such objects can't be reached at runtime
|
|||
// so they're useless in RAM or ROM init data.
|
|||
function removeUnreachableObjectsAndStrings(meta) { |
|||
var reachable = createBareObject({}); |
|||
|
|||
// First prune objects: keep only reachable and forced objects.
|
|||
|
|||
walkObjects(meta, (o) => { |
|||
if (propDefault(o, '_force_reachable', false)) { |
|||
reachable[o.id] = true; |
|||
} |
|||
}); |
|||
|
|||
function markId(objId) { |
|||
if (objId) { |
|||
reachable[objId] = true; |
|||
} |
|||
} |
|||
|
|||
// Keep marking until steady state.
|
|||
console.debug('original object count: ' + meta.objects.length); |
|||
for (;;) { |
|||
let reachableCount = Object.keys(reachable).length; |
|||
|
|||
walkObjects(meta, (o) => { |
|||
if (!reachable[o.id]) { |
|||
return; |
|||
} |
|||
markId(o.internal_prototype); |
|||
walkObjectProperties(o, (p) => { |
|||
// Shorthand has been normalized so no need
|
|||
// to support it here.
|
|||
let v = p.value; |
|||
if (typeof v === 'object' && v !== null) { |
|||
switch (v.type) { |
|||
case 'object': |
|||
markId(v.id); |
|||
break; |
|||
case 'accessor': |
|||
markId(v.getter_id); |
|||
markId(v.setter_id); |
|||
break; |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
let newReachableCount = Object.keys(reachable).length; |
|||
console.debug('mark reachable, reachable count ' + reachableCount + ' -> ' + newReachableCount); |
|||
|
|||
if (reachableCount === newReachableCount) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
let numDeleted = 0; |
|||
meta.objects = meta.objects.filter((o) => { |
|||
if (reachable[o.id]) { |
|||
return true; |
|||
} else { |
|||
console.debug('object ' + o.id + ' not reachable, dropping: ' + JSON.stringify(o)); |
|||
numDeleted++; |
|||
return false; |
|||
} |
|||
}); |
|||
|
|||
// Then prune strings: keep only reachable and forced strings.
|
|||
// (This is not very relevant for RAM initdata because only
|
|||
// strings with stridx are ultimately kept.)
|
|||
|
|||
var reachableStrings = {}; |
|||
var numDeletedStrings = 0; |
|||
walkObjectsAndProperties(meta, null, (p, o) => { |
|||
void o; |
|||
reachableStrings[p.key] = true; |
|||
if (typeof p.value === 'string') { |
|||
reachableStrings[p.value] = true; |
|||
} |
|||
}); |
|||
meta.strings = meta.strings.filter((s) => { |
|||
if (reachableStrings[s.str]) { |
|||
return true; |
|||
} else if (s._force_reachable) { |
|||
console.debug('string not reachable but forced, keep:', s.str); |
|||
return true; |
|||
} else { |
|||
console.debug('string not reachable, drop:', s.str); |
|||
numDeletedStrings++; |
|||
return false; |
|||
} |
|||
}); |
|||
|
|||
if (numDeleted > 0 || numDeletedStrings > 0) { |
|||
console.log('deleted ' + numDeleted + ' unreachable objects, ' + |
|||
numDeletedStrings + ' unreachable strings'); |
|||
} |
|||
} |
|||
exports.removeUnreachableObjectsAndStrings = removeUnreachableObjectsAndStrings; |
@ -0,0 +1,672 @@ |
|||
'use strict'; |
|||
|
|||
const { BitEncoder } = require('../util/bitencoder'); |
|||
const { bitpack5BitBstr } = require('../formats/bitpack_5bit'); |
|||
const { walkObjectsAndProperties, findObjectById, findPropertyByKey, propDefault } = require('./metadata/util'); |
|||
const { classToNumber } = require('./classnames'); |
|||
const { resolveMagic } = require('./magic'); |
|||
const { assert } = require('../util/assert'); |
|||
const { hexDecode } = require('../util/hex'); |
|||
const { shallowCloneArray } = require('../util/clone'); |
|||
const { createBareObject } = require('../util/bare'); |
|||
|
|||
// Default property attributes.
|
|||
const LENGTH_PROPERTY_ATTRIBUTES = 'c'; |
|||
//const ACCESSOR_PROPERTY_ATTRIBUTES = 'c';
|
|||
const DEFAULT_DATA_PROPERTY_ATTRIBUTES = 'wc'; |
|||
const DEFAULT_FUNC_PROPERTY_ATTRIBUTES = 'wc'; |
|||
|
|||
// Encoding constants (must match duk_hthread_builtins.c).
|
|||
const PROP_FLAGS_BITS = 3; |
|||
const LENGTH_PROP_BITS = 3; |
|||
const NARGS_BITS = 3; |
|||
const PROP_TYPE_BITS = 3; |
|||
|
|||
const NARGS_VARARGS_MARKER = 0x07; |
|||
|
|||
const PROP_TYPE_DOUBLE = 0; |
|||
const PROP_TYPE_STRING = 1; |
|||
const PROP_TYPE_STRIDX = 2; |
|||
const PROP_TYPE_BUILTIN = 3; |
|||
const PROP_TYPE_UNDEFINED = 4; |
|||
const PROP_TYPE_BOOLEAN_TRUE = 5; |
|||
const PROP_TYPE_BOOLEAN_FALSE = 6; |
|||
const PROP_TYPE_ACCESSOR = 7; |
|||
|
|||
// Property descriptor flags, must match duk_hobject.h.
|
|||
const PROPDESC_FLAG_WRITABLE = (1 << 0); |
|||
const PROPDESC_FLAG_ENUMERABLE = (1 << 1); |
|||
const PROPDESC_FLAG_CONFIGURABLE = (1 << 2); |
|||
const PROPDESC_FLAG_ACCESSOR = (1 << 3); |
|||
|
|||
// Encode property flags for RAM initializers.
|
|||
function encodePropertyFlags(flags) { |
|||
var res = 0; |
|||
var nflags = 0; |
|||
if (flags.indexOf('w') >= 0) { |
|||
nflags++; |
|||
res |= PROPDESC_FLAG_WRITABLE; |
|||
} |
|||
if (flags.indexOf('e') >= 0) { |
|||
nflags++; |
|||
res |= PROPDESC_FLAG_ENUMERABLE; |
|||
} |
|||
if (flags.indexOf('c') >= 0) { |
|||
nflags++; |
|||
res |= PROPDESC_FLAG_CONFIGURABLE; |
|||
} |
|||
if (flags.indexOf('a') >= 0) { |
|||
nflags++; |
|||
res |= PROPDESC_FLAG_ACCESSOR; |
|||
} |
|||
|
|||
if (nflags !== flags.length) { |
|||
throw new TypeError('invalid property flags: ' + flags); |
|||
} |
|||
|
|||
return res; |
|||
} |
|||
|
|||
// Get helper maps for RAM objects.
|
|||
function getRamobjNativeFuncMaps(meta) { |
|||
var nativeFound = {}; |
|||
var nativeFuncs = []; |
|||
var natfuncNameToNatidx = {}; |
|||
|
|||
nativeFuncs.push(null); // natidx 0 is reserved for NULL
|
|||
|
|||
walkObjectsAndProperties(meta, (o) => { |
|||
if (typeof o.native !== 'undefined') { |
|||
nativeFound[o.native] = true; |
|||
} |
|||
}, (p, o) => { |
|||
void o; |
|||
let val = p.value; |
|||
if (typeof val === 'object' && val !== null) { |
|||
switch (val.type) { |
|||
case 'accessor': |
|||
if (typeof val.getter_id !== 'undefined') { |
|||
let getter = findObjectById(meta, val.getter_id); |
|||
nativeFound[getter.native] = true; |
|||
} |
|||
if (typeof val.setter_id !== 'undefined') { |
|||
let setter = findObjectById(meta, val.setter_id); |
|||
nativeFound[setter.native] = true; |
|||
} |
|||
break; |
|||
case 'object': |
|||
{ |
|||
let target = findObjectById(meta, val.id); |
|||
if (typeof target.native !== 'undefined') { |
|||
nativeFound[target.native] = true; |
|||
} |
|||
} |
|||
break; |
|||
case 'lightfunc': |
|||
// No lightfunc support for RAM initializer now.
|
|||
break; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
Object.keys(nativeFound).sort().forEach((k, idx) => { |
|||
void idx; |
|||
natfuncNameToNatidx[k] = nativeFuncs.length; |
|||
nativeFuncs.push(k); // native func names
|
|||
}); |
|||
|
|||
return { nativeFuncs, natfuncNameToNatidx }; |
|||
} |
|||
exports.getRamobjNativeFuncMaps = getRamobjNativeFuncMaps; |
|||
|
|||
// Generate bit-packed RAM string init data.
|
|||
function generateRamStringInitDataBitpacked(meta) { |
|||
var be = new BitEncoder(); |
|||
|
|||
var maxLen = 0; |
|||
var stats = createBareObject({ |
|||
numInputBytes: 0, |
|||
numOptimal: 0, |
|||
numLookup1: 0, |
|||
numLookup2: 0, |
|||
numSwitch1: 0, |
|||
numSwitch: 0, |
|||
numEightBit: 0 |
|||
}); |
|||
|
|||
for (let strObj of meta.strings_stridx) { |
|||
let s = strObj.str; |
|||
maxLen = Math.max(maxLen, s.length); |
|||
bitpack5BitBstr(be, s, stats); |
|||
} |
|||
|
|||
// End marker not necessary, C code knows length from define.
|
|||
|
|||
console.debug('RAM string init data: ' + be.getStatsString()); |
|||
let res = be.getBytes(); |
|||
|
|||
console.debug(meta.strings_stridx.length + ' ram strings, ' + stats.numInputBytes + ' input data bytes, ' + |
|||
res.length + ' bytes of string init data, ' + maxLen + ' maximum string length, ' + |
|||
(res.length * 8 / stats.numInputBytes) + ' bits/char, ' + |
|||
'encoding stats: ' + JSON.stringify(stats)); |
|||
|
|||
return { data: res, maxLen: maxLen }; |
|||
} |
|||
exports.generateRamStringInitDataBitpacked = generateRamStringInitDataBitpacked; |
|||
|
|||
// Helper to find a property from a property list, remove it from the
|
|||
// property list, and return the removed property.
|
|||
function stealProp(props, key, opts) { |
|||
opts = opts || {}; |
|||
for (let i = 0; i < props.length; i++) { |
|||
let prop = props[i]; |
|||
if (prop.key === key) { |
|||
let isAccessor = (typeof prop.value === 'object' && prop.value !== null && prop.value.type === 'accessor'); |
|||
if (!isAccessor || opts.allowAccessor) { |
|||
var res = props.splice(i, 1); |
|||
return res[0]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Generate init data for an object, no properties yet.
|
|||
function generateRamObjectInitDataForObject(meta, be, obj, stringToStridx, natfuncNameToNatidx, objIdToBidx) { |
|||
function emitStridx(strval) { |
|||
var stridx = stringToStridx[strval]; |
|||
be.varuint(stridx); |
|||
} |
|||
void emitStridx; |
|||
function emitStridxOrString(strval) { |
|||
var stridx = stringToStridx[strval]; |
|||
if (typeof stridx === 'number') { |
|||
be.varuint(stridx + 1); |
|||
} else { |
|||
be.varuint(0); |
|||
bitpack5BitBstr(be, strval, null); |
|||
} |
|||
} |
|||
function emitNatidx(nativeName) { |
|||
var natidx = natfuncNameToNatidx[nativeName]; |
|||
be.varuint(natidx); |
|||
} |
|||
|
|||
var classNum = classToNumber(obj.class); |
|||
be.varuint(classNum); |
|||
|
|||
var props = shallowCloneArray(obj.properties); // Clone so we can steal.
|
|||
|
|||
var propProto = stealProp(props, 'prototype', { allowAccessor: false }); |
|||
void propProto; |
|||
var propConstructor = stealProp(props, 'constructor', { allowAccessor: false }); |
|||
void propConstructor; |
|||
var propName = stealProp(props, 'name', { allowAccessor: false }); |
|||
var propLength = stealProp(props, 'length', { allowAccessor: false }); |
|||
|
|||
var length = -1; // default value, signifies varargs
|
|||
if (propLength) { |
|||
assert(typeof propLength.value === 'number'); |
|||
length = propLength.value; |
|||
be.bits(1, 1); // flag: have length
|
|||
be.bits(length, LENGTH_PROP_BITS); |
|||
} else { |
|||
be.bits(0, 1); // flag: no length
|
|||
} |
|||
|
|||
// The attributes for 'length' are standard ("none") except for
|
|||
// Array.prototype.length which must be writable (this is handled
|
|||
// separately in duk_hthread_builtins.c).
|
|||
|
|||
var lenAttrs = LENGTH_PROPERTY_ATTRIBUTES; |
|||
if (propLength) { |
|||
lenAttrs = propLength.attributes; |
|||
} |
|||
if (lenAttrs !== LENGTH_PROPERTY_ATTRIBUTES) { |
|||
// Attributes are assumed to be the same, except for Array.prototype.
|
|||
if (obj.class !== 'Array') { // Array.prototype is the only one with this class
|
|||
throw new TypeError('non-default length attributes for unexpected object'); |
|||
} |
|||
} |
|||
|
|||
// For 'Function' classed objects, emit the native function stuff.
|
|||
// Unfortunately this is more or less a copy of what we do for
|
|||
// function properties now. This should be addressed if a rework
|
|||
// on the init format is done.
|
|||
|
|||
if (obj.class === 'Function') { |
|||
emitNatidx(obj.native); |
|||
|
|||
// Nargs.
|
|||
if (propDefault(obj, 'varargs', false)) { |
|||
be.bits(1, 1); // flag: non-default nargs
|
|||
be.bits(NARGS_VARARGS_MARKER, NARGS_BITS); // varargs
|
|||
} else if (typeof obj.nargs === 'number' && obj.nargs !== length) { |
|||
be.bits(1, 1); // flag: non-default nargs
|
|||
be.bits(obj.nargs, NARGS_BITS); |
|||
} else { |
|||
assert(typeof length === 'number'); |
|||
be.bits(0, 1); // flag: default nargs is OK
|
|||
} |
|||
|
|||
// Function .name.
|
|||
assert(propName); |
|||
assert(typeof propName.value === 'string'); |
|||
emitStridxOrString(propName.value); |
|||
|
|||
// All Function-classed global level objects are callable
|
|||
// (have [[Call]]) but not all are constructable (have
|
|||
// [[Construct]]). Flag that.
|
|||
assert(obj.callable === true); |
|||
if (propDefault(obj, 'constructable', false)) { |
|||
be.bits(1, 1); // flag: constructable
|
|||
} else { |
|||
be.bits(0, 1); // flag: not constructable
|
|||
} |
|||
// DUK_HOBJECT_FLAG_SPECIAL_CALL is handled at runtime without init data.
|
|||
|
|||
// Convert signed magic to 16-bit unsigned for encoding.
|
|||
var magic = resolveMagic(propDefault(obj, 'magic', null), objIdToBidx) & 0xffff; |
|||
assert(magic >= 0 && magic <= 0xffff); |
|||
be.varuint(magic); |
|||
} |
|||
} |
|||
|
|||
// Generate init data for object properties.
|
|||
function generateRamObjectInitDataForProps(meta, be, obj, stringToStridx, natfuncNameToNatidx, objIdToBidx, doubleByteOrder) { |
|||
var countNormal = 0; |
|||
var countFunction = 0; |
|||
|
|||
function emitBidx(bi_id) { |
|||
be.varuint(objIdToBidx[bi_id]); |
|||
} |
|||
function emitBidxOrNone(bi_id) { |
|||
if (typeof bi_id === 'string') { |
|||
be.varuint(objIdToBidx[bi_id] + 1); |
|||
} else { |
|||
be.varuint(0); |
|||
} |
|||
} |
|||
function emitStridx(strval) { |
|||
var stridx = stringToStridx[strval]; |
|||
be.varuint(stridx); |
|||
} |
|||
function emitStridxOrString(strval) { |
|||
var stridx = stringToStridx[strval]; |
|||
if (typeof stridx === 'number') { |
|||
be.varuint(stridx + 1); |
|||
} else { |
|||
be.varuint(0); |
|||
bitpack5BitBstr(be, strval, null); |
|||
} |
|||
} |
|||
function emitNatidx(nativeName) { |
|||
var natidx; |
|||
if (typeof nativeName === 'string') { |
|||
natidx = natfuncNameToNatidx[nativeName]; |
|||
} else { |
|||
natidx = 0; // 0 is NULL in the native functions table, denotes missing function.
|
|||
} |
|||
be.varuint(natidx); |
|||
} |
|||
|
|||
var props = shallowCloneArray(obj.properties); // Clone so we can steal.
|
|||
|
|||
// Internal prototype: not an actual property so not in property list.
|
|||
emitBidxOrNone(obj.internal_prototype); |
|||
|
|||
// External prototype: encoded specially, steal from property list.
|
|||
var propProto = stealProp(props, 'prototype'); |
|||
if (propProto) { |
|||
assert(typeof propProto.value === 'object' && propProto.value !== null && propProto.value.type === 'object'); |
|||
assert(propProto.attributes === ''); |
|||
emitBidxOrNone(propProto.value.id); |
|||
} else { |
|||
emitBidxOrNone(null); |
|||
} |
|||
|
|||
// External constructor: encoded specially, steal from property list.
|
|||
var propConstructor = stealProp(props, 'constructor'); |
|||
if (propConstructor) { |
|||
assert(typeof propConstructor.value === 'object' && propConstructor.value !== null && propConstructor.value.type === 'object'); |
|||
assert(propConstructor.attributes === 'wc'); |
|||
emitBidxOrNone(propConstructor.value.id); |
|||
} else { |
|||
emitBidxOrNone(null); |
|||
} |
|||
|
|||
// Name: encoded specially for function objects, so steal and ignore here.
|
|||
if (obj.class === 'Function') { |
|||
let propName = stealProp(props, 'name', { allowAccessor: false }); |
|||
assert(propName); |
|||
assert(typeof propName.value === 'string'); |
|||
assert(propName.attributes === 'c'); |
|||
} |
|||
|
|||
// length: encoded specially, so steal and ignore.
|
|||
var propLength = stealProp(props, 'length', { allowAccessor: false }); |
|||
void propLength; |
|||
|
|||
// Date.prototype.toGMTString needs special handling and is handled
|
|||
// directly in duk_hthread_builtins.c; so steal and ignore here.
|
|||
if (obj.id === 'bi_date_prototype') { |
|||
let propToGmtString = stealProp(props, 'toGMTString'); |
|||
console.debug('stole .toGMTString property'); |
|||
} |
|||
|
|||
// Split properties into non-toplevel functions and other properties.
|
|||
// This split is a bit arbitrary, but is used to reduce flag bits in
|
|||
// the bit stream.
|
|||
var values = []; |
|||
var functions = []; |
|||
props.forEach((prop) => { |
|||
if (typeof prop.value === 'object' && prop.value !== null && prop.value.type === 'object') { |
|||
var target = findObjectById(meta, prop.value.id); |
|||
assert(target); |
|||
if (typeof target.native === 'string' && // native function
|
|||
typeof target.bidx === 'undefined') { // but not a top level built-in
|
|||
functions.push(prop); |
|||
return; |
|||
} |
|||
} |
|||
values.push(prop); |
|||
}); |
|||
console.debug(obj.id + ': ' + values.length + ' values, ' + functions.length + ' functions'); |
|||
|
|||
// Encode 'values'.
|
|||
be.varuint(values.length); |
|||
|
|||
values.forEach((prop) => { |
|||
var val = prop.value; |
|||
|
|||
countNormal++; |
|||
|
|||
// Key.
|
|||
emitStridxOrString(prop.key); |
|||
|
|||
// Attributes. Attribute check doesn't check for accessor flag; that
|
|||
// is now automatically set by C code when value is an accessor type.
|
|||
// Accessors must not have 'writable', so they'll always have
|
|||
// non-default attributes (less footprint than adding a different
|
|||
// default).
|
|||
var defaultAttrs = DEFAULT_DATA_PROPERTY_ATTRIBUTES; |
|||
var attrs = propDefault(prop, 'attributes', defaultAttrs); |
|||
attrs = attrs.replace('a', ''); // RAM bitstream doesn't encode the 'accessor' attribute.
|
|||
if (attrs !== defaultAttrs) { |
|||
console.debug('non-default attributes for ' + prop.key + ': ' + attrs + ' vs ' + defaultAttrs); |
|||
be.bits(1, 1); // flag: have custom attributes
|
|||
be.bits(encodePropertyFlags(attrs), PROP_FLAGS_BITS); |
|||
} else { |
|||
be.bits(0, 1); // flag: no custom attributes
|
|||
} |
|||
|
|||
// Value.
|
|||
if (val === void 0 || val === null) { |
|||
// RAM format doesn't support "null", use undefined.
|
|||
be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS); |
|||
} else if (typeof val === 'boolean') { |
|||
if (val) { |
|||
be.bits(PROP_TYPE_BOOLEAN_TRUE, PROP_TYPE_BITS); |
|||
} else { |
|||
be.bits(PROP_TYPE_BOOLEAN_FALSE, PROP_TYPE_BITS); |
|||
} |
|||
} else if (typeof val === 'number' || (typeof val === 'object' && val !== null && val.type === 'double')) { |
|||
// Avoid converting a manually specified NaN temporarily into
|
|||
// a float to avoid risk of e.g. NaN being replaced by another.
|
|||
if (typeof val === 'object') { |
|||
val = hexDecode(val.bytes); |
|||
} else { |
|||
let tmpAb = new ArrayBuffer(8); |
|||
let tmpDv = new DataView(tmpAb); |
|||
tmpDv.setFloat64(0, val); // big endian
|
|||
val = new Uint8Array(tmpAb); |
|||
} |
|||
assert(val instanceof Uint8Array); |
|||
assert(val.length === 8); |
|||
|
|||
be.bits(PROP_TYPE_DOUBLE, PROP_TYPE_BITS); |
|||
|
|||
// Encoding of double must match target architecture byte order.
|
|||
var indexList = ({ |
|||
big: [ 0, 1, 2, 3, 4, 5, 6, 7 ], |
|||
little: [ 7, 6, 5, 4, 3, 2, 1, 0 ], |
|||
mixed: [ 3, 2, 1, 0, 7, 6, 5, 4 ] |
|||
})[doubleByteOrder]; |
|||
assert(indexList); |
|||
|
|||
let dataU8 = new Uint8Array(8); |
|||
for (let i = 0; i < indexList.length; i++) { |
|||
dataU8[i] = val[indexList[i]]; |
|||
} |
|||
be.uint8array(dataU8); |
|||
} else if (typeof val === 'string') { |
|||
let stridx = stringToStridx[val]; |
|||
if (typeof stridx === 'number') { |
|||
// String value is in built-in string table -> encode
|
|||
// using a string index. This saves some space,
|
|||
// especially for the 'name' property of errors
|
|||
// ('EvalError' etc).
|
|||
be.bits(PROP_TYPE_STRIDX, PROP_TYPE_BITS); |
|||
emitStridx(val); |
|||
} else { |
|||
// Not in string table, bitpack string value as is.
|
|||
be.bits(PROP_TYPE_STRING, PROP_TYPE_BITS); |
|||
bitpack5BitBstr(be, val); |
|||
} |
|||
} else if (typeof val === 'object' && val !== null) { |
|||
if (val.type === 'object') { |
|||
be.bits(PROP_TYPE_BUILTIN, PROP_TYPE_BITS); |
|||
emitBidx(val.id); |
|||
} else if (val.type === 'undefined') { |
|||
be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS); |
|||
} else if (val.type === 'accessor') { |
|||
be.bits(PROP_TYPE_ACCESSOR, PROP_TYPE_BITS); |
|||
let getterNatfun, setterNatfun; |
|||
let getterMagic = 0, setterMagic = 0; |
|||
if (typeof val.getter_id === 'string') { |
|||
let getterFn = findObjectById(meta, val.getter_id); |
|||
getterNatfun = getterFn.native; |
|||
assert(getterFn.nargs === 0); |
|||
getterMagic = getterFn.magic; |
|||
} |
|||
if (typeof val.setter_id === 'string') { |
|||
let setterFn = findObjectById(meta, val.setter_id); |
|||
setterNatfun = setterFn.native; |
|||
assert(setterFn.nargs === 1); |
|||
setterMagic = setterFn.magic; |
|||
} |
|||
if (getterNatfun && setterNatfun) { |
|||
assert(getterMagic === setterMagic); |
|||
} |
|||
emitNatidx(getterNatfun); |
|||
emitNatidx(setterNatfun); |
|||
be.varuint(getterMagic); |
|||
} else if (val.type === 'lightfunc') { |
|||
console.log('RAM init data format doesn\'t support "lightfunc" now, value replaced with "undefined" for ' + prop.name); |
|||
be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS); |
|||
} else { |
|||
throw new TypeError('unsupported value'); |
|||
} |
|||
} else { |
|||
throw new TypeError('unsupported value'); |
|||
} |
|||
}); |
|||
|
|||
// Encode 'functions'.
|
|||
be.varuint(functions.length); |
|||
|
|||
functions.forEach((prop) => { |
|||
var val = prop.value; |
|||
var funObj = findObjectById(meta, val.id); |
|||
assert(funObj); |
|||
var propLen = findPropertyByKey(funObj, 'length'); |
|||
assert(propLen); |
|||
assert(typeof propLen.value === 'number'); |
|||
var length = propLen.value; |
|||
|
|||
countFunction++; |
|||
|
|||
// Key.
|
|||
emitStridxOrString(prop.key); |
|||
|
|||
// Native function.
|
|||
emitNatidx(funObj.native); |
|||
|
|||
// Length.
|
|||
be.bits(length, LENGTH_PROP_BITS); |
|||
|
|||
// Nargs.
|
|||
if (propDefault(funObj, 'varargs', false)) { |
|||
be.bits(1, 1); // flag: non-default nargs
|
|||
be.bits(NARGS_VARARGS_MARKER, NARGS_BITS); |
|||
} else if (typeof funObj.nargs === 'number' && funObj.nargs !== length) { |
|||
be.bits(1, 1); // flag: non-default nargs
|
|||
be.bits(funObj.nargs, NARGS_BITS); |
|||
} else { |
|||
be.bits(0, 1); // flag: default nargs OK
|
|||
} |
|||
|
|||
// Magic.
|
|||
var magic = resolveMagic(propDefault(funObj, 'magic', null), objIdToBidx) & 0xffff; |
|||
assert(magic >= 0 && magic <= 0xffff); |
|||
be.varuint(magic); |
|||
|
|||
// Property attributes.
|
|||
var defaultAttrs = DEFAULT_FUNC_PROPERTY_ATTRIBUTES; |
|||
var attrs = propDefault(prop, 'attributes', defaultAttrs); |
|||
attrs = attrs.replace('a', ''); // RAM bitstream doesn't encode the 'accessor' attribute.
|
|||
if (attrs !== defaultAttrs) { |
|||
console.debug('non-default attributes for ' + prop.key + ': ' + attrs + ' vs ' + defaultAttrs); |
|||
be.bits(1, 1); // flag: have custom attributes
|
|||
be.bits(encodePropertyFlags(attrs), PROP_FLAGS_BITS); |
|||
} else { |
|||
be.bits(0, 1); // flag: no custom attributes
|
|||
} |
|||
}); |
|||
|
|||
return { countNormal, countFunction }; |
|||
} |
|||
|
|||
// Generate init data for objects and their properties.
|
|||
function generateRamObjectInitDataBitpacked(meta, nativeFuncs, natfuncNameToNatidx, doubleByteOrder) { |
|||
// RAM initialization is based on a specially filtered list of top
|
|||
// level objects which includes objects with 'bidx' and objects
|
|||
// which aren't handled as inline values in the init bitstream.
|
|||
|
|||
var objList = meta.objects_ram_toplevel |
|||
var objIdToBidx = meta._objid_to_ramidx; |
|||
var stringToStridx = meta._plain_to_stridx; |
|||
|
|||
var be = new BitEncoder(); |
|||
var countBuiltins = 0; |
|||
var countNormalProps = 0; |
|||
var countFunctionProps = 0; |
|||
|
|||
objList.forEach((obj) => { |
|||
countBuiltins++; |
|||
generateRamObjectInitDataForObject(meta, be, obj, stringToStridx, natfuncNameToNatidx, objIdToBidx); |
|||
}); |
|||
objList.forEach((obj) => { |
|||
var { countNormal, countFunction } = generateRamObjectInitDataForProps(meta, be, obj, stringToStridx, natfuncNameToNatidx, objIdToBidx, doubleByteOrder); |
|||
countNormalProps += countNormal; |
|||
countFunctionProps += countFunction; |
|||
}); |
|||
|
|||
var data = be.getBytes(); |
|||
console.debug('RAM object init data: ' + be.getStatsString()); |
|||
|
|||
console.debug(countBuiltins + ' ram builtins, ' + countNormalProps + ' normal properties, ' + |
|||
countFunctionProps + ' function properties, ' + data.length + ' bytes of RAM object init data'); |
|||
|
|||
return { data }; |
|||
} |
|||
exports.generateRamObjectInitDataBitpacked = generateRamObjectInitDataBitpacked; |
|||
|
|||
function emitRamObjectNativeFuncDeclarations(gcHdr, ramNativeFuncs) { |
|||
ramNativeFuncs.forEach((fname) => { |
|||
// Visibility depends on whether the function is Duktape internal or user.
|
|||
// Use a simple prefix check.
|
|||
if (fname === null) { |
|||
// Zero index is special.
|
|||
return; |
|||
} |
|||
assert(typeof fname === 'string'); |
|||
let visibility = (fname.startsWith('duk_') ? 'DUK_INTERNAL_DECL' : 'extern'); |
|||
gcHdr.emitLine(visibility + ' duk_ret_t ' + fname + '(duk_context *ctx);'); |
|||
}); |
|||
} |
|||
exports.emitRamObjectNativeFuncDeclarations = emitRamObjectNativeFuncDeclarations; |
|||
|
|||
function emitRamObjectNativeFuncArray(gcSrc, ramNativeFuncs) { |
|||
gcSrc.emitLine('DUK_INTERNAL const duk_c_function duk_bi_native_functions[' + ramNativeFuncs.length + '] = {'); |
|||
ramNativeFuncs.forEach((fname, idx) => { |
|||
// The function pointer cast here makes BCC complain about
|
|||
// "initializer too complicated", so omit the cast.
|
|||
//gcSrc.emitLine('\t(duk_c_function) ' + func + ',');
|
|||
let comma = (idx < ramNativeFuncs.length - 1 ? ',' : ''); |
|||
if (fname === null) { |
|||
gcSrc.emitLine('\tNULL' + comma); |
|||
} else { |
|||
assert(typeof fname === 'string'); |
|||
gcSrc.emitLine('\t' + fname + comma); |
|||
} |
|||
}); |
|||
gcSrc.emitLine('};'); |
|||
} |
|||
exports.emitRamObjectNativeFuncArray = emitRamObjectNativeFuncArray; |
|||
|
|||
function emitRamObjectNativeFuncArrayDeclaration(gcSrc, ramNativeFuncs) { |
|||
gcSrc.emitLine('DUK_INTERNAL_DECL const duk_c_function duk_bi_native_functions[' + ramNativeFuncs.length + '];'); |
|||
} |
|||
exports.emitRamObjectNativeFuncArrayDeclaration = emitRamObjectNativeFuncArrayDeclaration; |
|||
|
|||
function emitRamObjectInitData(gcSrc, data) { |
|||
gcSrc.emitArray(data, { |
|||
tableName: 'duk_builtins_data', |
|||
typeName: 'duk_uint8_t', |
|||
useConst: true, |
|||
useCast: false, |
|||
visibility: 'DUK_INTERNAL' |
|||
}); |
|||
} |
|||
exports.emitRamObjectInitData = emitRamObjectInitData; |
|||
|
|||
function emitRamObjectInitDataDeclaration(gcHdr, data) { |
|||
gcHdr.emitLine('#if !defined(DUK_SINGLE_FILE)'); |
|||
gcHdr.emitLine('DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[' + data.length + '];'); |
|||
gcHdr.emitLine('#endif /* !DUK_SINGLE_FILE */'); |
|||
gcHdr.emitDefine('DUK_BUILTINS_DATA_LENGTH', data.length); |
|||
} |
|||
exports.emitRamObjectInitDataDeclaration = emitRamObjectInitDataDeclaration; |
|||
|
|||
function emitRamStringHeader(gcHdr, data, strMaxLen) { |
|||
gcHdr.emitLine('#if !defined(DUK_SINGLE_FILE)'); |
|||
gcHdr.emitLine('DUK_INTERNAL_DECL const duk_uint8_t duk_strings_data[' + data.length + '];'); |
|||
gcHdr.emitLine('#endif /* !DUK_SINGLE_FILE */'); |
|||
gcHdr.emitDefine('DUK_STRDATA_MAX_STRLEN', strMaxLen); |
|||
gcHdr.emitDefine('DUK_STRDATA_DATA_LENGTH', data.length); |
|||
} |
|||
exports.emitRamStringHeader = emitRamStringHeader; |
|||
|
|||
function emitRamStringInitData(gcSrc, data) { |
|||
gcSrc.emitArray(data, { |
|||
tableName: 'duk_strings_data', |
|||
typeName: 'duk_uint8_t', |
|||
useConst: true, |
|||
useCast: false, |
|||
visibility: 'DUK_INTERNAL' |
|||
}); |
|||
} |
|||
exports.emitRamStringInitData = emitRamStringInitData; |
|||
|
|||
function emitRamObjectHeader(gcHdr, meta) { |
|||
var objList = meta.objects_bidx; |
|||
objList.forEach((obj, idx) => { |
|||
var tmp = obj.id.toUpperCase().split('_').slice(1).join('_'); // bi_foo_bar -> FOO_BAR
|
|||
var defName = 'DUK_BIDX_' + tmp; |
|||
gcHdr.emitDefine(defName, idx); |
|||
}); |
|||
gcHdr.emitDefine('DUK_NUM_BUILTINS', objList.length); |
|||
gcHdr.emitDefine('DUK_NUM_BIDX_BUILTINS', objList.length); // Objects with 'bidx'
|
|||
gcHdr.emitDefine('DUK_NUM_ALL_BUILTINS', meta.objects_ram_toplevel.length); // Objects with 'bidx' + temps needed in init.
|
|||
} |
|||
exports.emitRamObjectHeader = emitRamObjectHeader; |
Loading…
Reference in new issue