Copyright 2019 Moddable Tech, Inc.
Revised: November 9, 2019
The XS linker can preload modules by executing their body at link time. The resulting closures and objects will be in ROM at run time.
XS supports an aliasing mechanism to allow such closures and objects to be modified at run time. Modified closures and objects are aliased in RAM.
But the aliasing mechanism has a cost, even if closures and objects are never modified: XS uses one pointer for each aliasable closure and object.
So it is recommended to freeze objects and to use const
instead of let
in the body of modules. For details see Using XS Preload to Optimize Applications
But it is common to forget a few closures and objects so the XS linker now warns you about closures and objects that are aliasable.
The XS linker forces a garbage collection after preloading modules. So closures and objects that are not referenced by modules namespaces are discarded.
XS supports compartments, as defined by Secure ECMAScript. See the Draft Spec for Standalone SES for details.
Preloaded modules are shared by all compartments, their bodies are considered as vetted customization code. So the XS linker warnings are especially significant when security matters.
When the XS linker reports warnings about a module, it means that the module exports mutable closures or objects.
Here is a simple "test"
module which exports one object:
export const o = { p: 0 };
If the "test"
module is preloaded, the XS linker reports:
### warning: "test": o: not frozen
The fix is:
export const o = Object.freeze({ p: 0 });
Objects are checked recursively.
export const o = Object.freeze({ p: { q: 0 } });
The XS linker reports:
### warning: "test": o.p: not frozen
Using the non standard deep freeze feature of XS, the fix is:
export const o = Object.freeze({ p: { q: 0 } }, true);
Here is a simple "test"
module which exports one function that returns a variable in the module scope:
let v = 1;
export function f(a) {
return v;
}
If the "test"
module is preloaded, the XS linker reports:
### warning: "test": f() v: no const
The fix is:
const v = 1;
export function f() {
return v;
}
Closures and objects are checked together. For instance:
const o = { p: 0 };
export function f() {
return o;
}
The XS linker reports:
### warning: "test": f() o: not frozen
The fix is:
const o = Object.freeze({ p: 0 });
export function f() {
return o;
}
Preloaded modules can modify the global scope. The resulting object will be in ROM at runtime and will be used to initialize the global scope of the Moddable app (a.k.a. the SES start-compartment). The XS linker also reports warnings about globals.
Here is a simple "test"
module which adds one object to the global scope:
globalThis.g = { p: 0 };
If the "test"
module is preloaded, the XS linker reports:
### warning: globalThis.g: not frozen
The fix is:
globalThis.g = Object.freeze({ p: 0 });
Preloaded objects are either aliasable or frozen. Preloaded aliasable objects are mutable. Preloaded frozen objects are immutable, their properties and their private properties are read-only.
Most instances of built-ins classes can be preloaded but cannot be aliased. Once preloaded, such instances are immutable and several built-ins methods throw type errors.
Instances of |
Preload |
Alias |
Throw Type Error |
Array |
Yes |
Yes |
- |
ArrayBuffer* |
Yes |
No |
- |
AsyncGenerator* |
No |
No |
|
Boolean |
Yes |
No |
- |
DataView |
Yes |
No |
set |
Date |
Yes |
No |
setDate, setFullYear, setHours, setMilliseconds, setMinutes, setMonth, setSeconds, setTime, setYear, setUTCDate, setUTCFullYear, setUTCHours, setUTCMilliseconds, setUTCMinutes, setUTCMonth, setUTCSeconds |
Error |
Yes |
No |
- |
FinalizationGroup |
Yes |
No |
cleanupSome, register, unregister |
Function* |
Yes |
No |
- |
Generator* |
No |
No |
|
Map |
Yes |
No |
clear, delete, set |
Number |
Yes |
No |
- |
Promise |
Yes |
No |
- |
Proxy |
Yes |
No |
revoke |
RegExp |
Yes |
No |
|
Set |
Yes |
No |
add, clear, delete |
String |
Yes |
No |
- |
Symbol |
Yes |
No |
- |
TypedArray* |
Yes |
No |
copyWithin, fill, reverse, set, sort |
WeakMap |
Yes |
No |
delete, set |
WeakRef |
Yes |
No |
- |
WeakSet |
Yes |
No |
add, delete |
- Instances of ArrayBuffer are mostly accessed thru instances of DataView and TypedArray. Currently the endianness at build time has to be the same as the endianness at run time.
- Since classes are instances of Function, static properties and static private properties of preloaded classes are read-only.
- The XS linker reports errors when instances of AsyncGenerator or Generator are preloaded.
- For preloaded instances of TypedArray, integer-indexed assignment also throws a type error.