porffor 0.60.7 → 0.60.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/compiler/assemble.js +3 -3
- package/compiler/builtins.js +348 -6
- package/compiler/codegen.js +65 -6
- package/compiler/precompile.js +1 -1
- package/package.json +1 -1
- package/runtime/index.js +1 -1
- package/AGENTS.md +0 -20
- package/compiler/allocator.js +0 -65
- package/compiler/builtins_objects.js +0 -349
package/README.md
CHANGED
@@ -92,11 +92,9 @@ Porffor uses a unique versioning system, here's an example: `0.48.7`. Let's brea
|
|
92
92
|
- `compiler`: contains the compiler itself
|
93
93
|
- `builtins`: built-in apis written in typescript
|
94
94
|
- `2c.js`: custom wasm-to-c engine
|
95
|
-
- `allocator.js`: static/compile-time allocator
|
96
95
|
- `assemble.js`: assembles wasm ops and metadata into a spec-compliant wasm module/file
|
97
96
|
- `builtins.js`: all manually written built-ins of the engine (spec, custom. vars, funcs)
|
98
|
-
- `
|
99
|
-
- `builtins_precompiled.js`: dynamically generated builtins from the `builtins/` folder
|
97
|
+
- `builtins_precompiled.js`: generated builtins from the `builtins/` folder
|
100
98
|
- `codegen.js`: code (wasm) generation, ast -> wasm. The bulk of the effort
|
101
99
|
- `cyclone.js`: wasm partial constant evaluator (it is fast and dangerous hence "cyclone")
|
102
100
|
- `disassemble.js`: wasm disassembler using internal debug info
|
package/compiler/assemble.js
CHANGED
@@ -197,10 +197,10 @@ export default (funcs, globals, tags, pages, data, noTreeshake = false) => {
|
|
197
197
|
}
|
198
198
|
time('func section');
|
199
199
|
|
200
|
-
if (pages.has('func lut')) {
|
200
|
+
if (pages.has('#func lut')) {
|
201
201
|
if (data.addedFuncArgcLut) {
|
202
202
|
// remove existing data
|
203
|
-
data = data.filter(x => x.page !== 'func lut');
|
203
|
+
data = data.filter(x => x.page !== '#func lut');
|
204
204
|
}
|
205
205
|
|
206
206
|
// generate func lut data
|
@@ -233,7 +233,7 @@ export default (funcs, globals, tags, pages, data, noTreeshake = false) => {
|
|
233
233
|
if (Prefs.debugFuncLut) log('assemble', `func lut using ${bytes.length}/${pageSize * 2} (${bytesPerFunc} bytes per func)`);
|
234
234
|
|
235
235
|
data.push({
|
236
|
-
page: 'func lut',
|
236
|
+
page: '#func lut',
|
237
237
|
bytes
|
238
238
|
});
|
239
239
|
data.addedFuncArgcLut = true;
|
package/compiler/builtins.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import * as PrecompiledBuiltins from './builtins_precompiled.js';
|
2
|
-
import ObjectBuiltins from './builtins_objects.js';
|
3
2
|
import { PageSize, Blocktype, Opcodes, Valtype } from './wasmSpec.js';
|
4
3
|
import { TYPES, TYPE_NAMES } from './types.js';
|
5
4
|
import { number, unsignedLEB128 } from './encoding.js';
|
@@ -62,7 +61,7 @@ export const createImport = (name, params, returns, js = null, c = null) => {
|
|
62
61
|
export const UNDEFINED = 0;
|
63
62
|
export const NULL = 0;
|
64
63
|
|
65
|
-
export const BuiltinVars = function(
|
64
|
+
export const BuiltinVars = function({ builtinFuncs }) {
|
66
65
|
this.undefined = () => [ number(UNDEFINED) ];
|
67
66
|
this.undefined.type = TYPES.undefined;
|
68
67
|
|
@@ -121,7 +120,350 @@ export const BuiltinVars = function(ctx) {
|
|
121
120
|
this[`__Symbol_${x}`].type = TYPES.symbol;
|
122
121
|
}
|
123
122
|
|
124
|
-
|
123
|
+
// builtin objects
|
124
|
+
const makePrefix = name => (name.startsWith('__') ? '' : '__') + name + '_';
|
125
|
+
|
126
|
+
const done = new Set();
|
127
|
+
const object = (name, props) => {
|
128
|
+
done.add(name);
|
129
|
+
const prefix = name === 'globalThis' ? '' : makePrefix(name);
|
130
|
+
|
131
|
+
// already a func
|
132
|
+
const existingFunc = builtinFuncs[name];
|
133
|
+
|
134
|
+
builtinFuncs['#get_' + name] = {
|
135
|
+
params: [],
|
136
|
+
locals: [ Valtype.i32 ],
|
137
|
+
returns: [ Valtype.i32 ],
|
138
|
+
returnType: TYPES.object,
|
139
|
+
wasm: (scope, { allocPage, makeString, generate, getNodeType, builtin, funcRef, glbl }) => {
|
140
|
+
if (globalThis.precompile) return [ [ 'get object', name ] ];
|
141
|
+
|
142
|
+
// todo/perf: precompute bytes here instead of calling real funcs if we really care about perf later
|
143
|
+
|
144
|
+
let ptr;
|
145
|
+
if (existingFunc) {
|
146
|
+
ptr = funcRef(name)[0][1];
|
147
|
+
} else {
|
148
|
+
ptr = allocPage(scope, `builtin object: ${name}`);
|
149
|
+
}
|
150
|
+
|
151
|
+
const getPtr = glbl(Opcodes.global_get, `getptr_${name}`, Valtype.i32)[0];
|
152
|
+
const out = [
|
153
|
+
// check if already made/cached
|
154
|
+
getPtr,
|
155
|
+
[ Opcodes.if, Blocktype.void ],
|
156
|
+
getPtr,
|
157
|
+
[ Opcodes.return ],
|
158
|
+
[ Opcodes.end ],
|
159
|
+
|
160
|
+
// set cache & ptr for use
|
161
|
+
number(ptr, Valtype.i32),
|
162
|
+
[ Opcodes.local_tee, 0 ],
|
163
|
+
glbl(Opcodes.global_set, `getptr_${name}`, Valtype.i32)[0],
|
164
|
+
|
165
|
+
[ Opcodes.local_get, 0 ],
|
166
|
+
Opcodes.i32_from_u,
|
167
|
+
number(existingFunc ? TYPES.function : TYPES.object, Valtype.i32),
|
168
|
+
[ Opcodes.call, builtin('__Porffor_object_underlying') ],
|
169
|
+
[ Opcodes.drop ],
|
170
|
+
[ Opcodes.local_set, 0 ]
|
171
|
+
];
|
172
|
+
|
173
|
+
for (const x in props) {
|
174
|
+
let value = {
|
175
|
+
type: 'Identifier',
|
176
|
+
name: prefix + x
|
177
|
+
};
|
178
|
+
|
179
|
+
if (x === '__proto__') {
|
180
|
+
out.push(
|
181
|
+
[ Opcodes.local_get, 0 ],
|
182
|
+
number(TYPES.object, Valtype.i32),
|
183
|
+
|
184
|
+
...generate(scope, value),
|
185
|
+
Opcodes.i32_to_u,
|
186
|
+
...getNodeType(scope, value),
|
187
|
+
|
188
|
+
[ Opcodes.call, builtin('__Porffor_object_setPrototype') ]
|
189
|
+
);
|
190
|
+
continue;
|
191
|
+
}
|
192
|
+
|
193
|
+
let add = true;
|
194
|
+
if (existingFunc && (x === 'prototype' || x === 'constructor')) add = false;
|
195
|
+
|
196
|
+
let flags = 0b0000;
|
197
|
+
const d = props[x];
|
198
|
+
if (d.configurable) flags |= 0b0010;
|
199
|
+
if (d.enumerable) flags |= 0b0100;
|
200
|
+
if (d.writable) flags |= 0b1000;
|
201
|
+
|
202
|
+
out.push(
|
203
|
+
[ Opcodes.local_get, 0 ],
|
204
|
+
number(TYPES.object, Valtype.i32),
|
205
|
+
|
206
|
+
...makeString(scope, x),
|
207
|
+
Opcodes.i32_to_u,
|
208
|
+
number(TYPES.bytestring, Valtype.i32),
|
209
|
+
|
210
|
+
...generate(scope, value),
|
211
|
+
...getNodeType(scope, value),
|
212
|
+
|
213
|
+
number(flags, Valtype.i32),
|
214
|
+
number(TYPES.number, Valtype.i32),
|
215
|
+
|
216
|
+
[ Opcodes.call, builtin(add ? '__Porffor_object_fastAdd' : '__Porffor_object_define') ]
|
217
|
+
);
|
218
|
+
}
|
219
|
+
|
220
|
+
// return ptr
|
221
|
+
out.push(getPtr);
|
222
|
+
return out;
|
223
|
+
}
|
224
|
+
};
|
225
|
+
|
226
|
+
this[name] = (scope, { builtin }) => [
|
227
|
+
[ Opcodes.call, builtin('#get_' + name) ],
|
228
|
+
Opcodes.i32_from_u
|
229
|
+
];
|
230
|
+
this[name].type = existingFunc ? TYPES.function : TYPES.object;
|
231
|
+
|
232
|
+
for (const x in props) {
|
233
|
+
const d = props[x];
|
234
|
+
const k = prefix + x;
|
235
|
+
|
236
|
+
if (Object.hasOwn(d, 'value') && !Object.hasOwn(builtinFuncs, k) && !Object.hasOwn(this, k)) {
|
237
|
+
if (Array.isArray(d.value) || typeof d.value === 'function') {
|
238
|
+
this[k] = d.value;
|
239
|
+
continue;
|
240
|
+
}
|
241
|
+
|
242
|
+
if (typeof d.value === 'number') {
|
243
|
+
this[k] = [ number(d.value) ];
|
244
|
+
this[k].type = TYPES.number;
|
245
|
+
continue;
|
246
|
+
}
|
247
|
+
|
248
|
+
if (typeof d.value === 'string') {
|
249
|
+
this[k] = (scope, { makeString }) => makeString(scope, d.value);
|
250
|
+
this[k].type = TYPES.bytestring;
|
251
|
+
continue;
|
252
|
+
}
|
253
|
+
|
254
|
+
if (d.value === null) {
|
255
|
+
this[k] = this.null;
|
256
|
+
continue;
|
257
|
+
}
|
258
|
+
|
259
|
+
throw new Error(`unsupported value type (${typeof d.value})`);
|
260
|
+
}
|
261
|
+
}
|
262
|
+
};
|
263
|
+
|
264
|
+
const props = (base, vals) => {
|
265
|
+
const out = {};
|
266
|
+
|
267
|
+
if (Array.isArray(vals)) {
|
268
|
+
// array of keys with no value
|
269
|
+
for (const x of vals) {
|
270
|
+
out[x] = {
|
271
|
+
...base
|
272
|
+
};
|
273
|
+
}
|
274
|
+
} else for (const x in vals) {
|
275
|
+
// object of key values
|
276
|
+
out[x] = {
|
277
|
+
...base,
|
278
|
+
value: vals[x]
|
279
|
+
};
|
280
|
+
}
|
281
|
+
|
282
|
+
return out;
|
283
|
+
};
|
284
|
+
|
285
|
+
const builtinFuncKeys = Object.keys(builtinFuncs);
|
286
|
+
const autoFuncKeys = name => {
|
287
|
+
const prefix = makePrefix(name);
|
288
|
+
return builtinFuncKeys.filter(x => x.startsWith(prefix)).map(x => x.slice(prefix.length)).filter(x => !x.startsWith('prototype_'));
|
289
|
+
};
|
290
|
+
const autoFuncs = name => ({
|
291
|
+
...props({
|
292
|
+
writable: true,
|
293
|
+
enumerable: false,
|
294
|
+
configurable: true
|
295
|
+
}, autoFuncKeys(name)),
|
296
|
+
...(this[`__${name}_prototype`] ? {
|
297
|
+
prototype: {
|
298
|
+
writable: false,
|
299
|
+
enumerable: false,
|
300
|
+
configurable: false
|
301
|
+
}
|
302
|
+
} : {})
|
303
|
+
});
|
304
|
+
|
305
|
+
object('Math', {
|
306
|
+
...props({
|
307
|
+
writable: false,
|
308
|
+
enumerable: false,
|
309
|
+
configurable: false
|
310
|
+
}, {
|
311
|
+
E: Math.E,
|
312
|
+
LN10: Math.LN10,
|
313
|
+
LN2: Math.LN2,
|
314
|
+
LOG10E: Math.LOG10E,
|
315
|
+
LOG2E: Math.LOG2E,
|
316
|
+
PI: Math.PI,
|
317
|
+
SQRT1_2: Math.SQRT1_2,
|
318
|
+
SQRT2: Math.SQRT2,
|
319
|
+
|
320
|
+
// https://github.com/rwaldron/proposal-math-extensions/issues/10
|
321
|
+
RAD_PER_DEG: Math.PI / 180,
|
322
|
+
DEG_PER_RAD: 180 / Math.PI
|
323
|
+
}),
|
324
|
+
|
325
|
+
...autoFuncs('Math')
|
326
|
+
});
|
327
|
+
|
328
|
+
// automatically generate objects for prototypes
|
329
|
+
for (const x of builtinFuncKeys.reduce((acc, x) => {
|
330
|
+
const ind = x.indexOf('_prototype_');
|
331
|
+
if (ind === -1) return acc;
|
332
|
+
|
333
|
+
acc.add(x.slice(0, ind + 10));
|
334
|
+
return acc;
|
335
|
+
}, new Set())) {
|
336
|
+
const props = autoFuncs(x);
|
337
|
+
|
338
|
+
// special case: Object.prototype.__proto__ = null
|
339
|
+
if (x === '__Object_prototype') {
|
340
|
+
Object.defineProperty(props, '__proto__', { value: { value: null, configurable: true }, enumerable: true });
|
341
|
+
}
|
342
|
+
|
343
|
+
// special case: Function.prototype.length = 0
|
344
|
+
// special case: Function.prototype.name = ''
|
345
|
+
if (x === '__Function_prototype') {
|
346
|
+
props.length = { value: 0, configurable: true };
|
347
|
+
props.name = { value: '', configurable: true };
|
348
|
+
}
|
349
|
+
|
350
|
+
// add constructor for constructors
|
351
|
+
const name = x.slice(2, x.indexOf('_', 2));
|
352
|
+
if (builtinFuncs[name]?.constr) {
|
353
|
+
const value = (scope, { funcRef }) => funcRef(name);
|
354
|
+
value.type = TYPES.function;
|
355
|
+
|
356
|
+
props.constructor = {
|
357
|
+
value,
|
358
|
+
writable: true,
|
359
|
+
enumerable: false,
|
360
|
+
configurable: true
|
361
|
+
};
|
362
|
+
}
|
363
|
+
|
364
|
+
object(x, props);
|
365
|
+
}
|
366
|
+
|
367
|
+
|
368
|
+
object('Number', {
|
369
|
+
...props({
|
370
|
+
writable: false,
|
371
|
+
enumerable: false,
|
372
|
+
configurable: false
|
373
|
+
}, {
|
374
|
+
NaN: NaN,
|
375
|
+
POSITIVE_INFINITY: Infinity,
|
376
|
+
NEGATIVE_INFINITY: -Infinity,
|
377
|
+
MAX_VALUE: valtype === 'i32' ? 2147483647 : 1.7976931348623157e+308,
|
378
|
+
MIN_VALUE: valtype === 'i32' ? -2147483648 : 5e-324,
|
379
|
+
MAX_SAFE_INTEGER: valtype === 'i32' ? 2147483647 : 9007199254740991,
|
380
|
+
MIN_SAFE_INTEGER: valtype === 'i32' ? -2147483648 : -9007199254740991,
|
381
|
+
EPSILON: 2.220446049250313e-16
|
382
|
+
}),
|
383
|
+
|
384
|
+
...autoFuncs('Number')
|
385
|
+
});
|
386
|
+
|
387
|
+
// these technically not spec compliant as it should be classes or non-enumerable but eh
|
388
|
+
object('navigator', {
|
389
|
+
...props({
|
390
|
+
writable: false,
|
391
|
+
enumerable: true,
|
392
|
+
configurable: false
|
393
|
+
}, {
|
394
|
+
userAgent: `Porffor/${globalThis.version}`
|
395
|
+
})
|
396
|
+
});
|
397
|
+
|
398
|
+
for (const x of [
|
399
|
+
'console',
|
400
|
+
'crypto',
|
401
|
+
'performance',
|
402
|
+
]) {
|
403
|
+
object(x, props({
|
404
|
+
writable: true,
|
405
|
+
enumerable: true,
|
406
|
+
configurable: true
|
407
|
+
}, autoFuncKeys(x).slice(0, 12)));
|
408
|
+
}
|
409
|
+
|
410
|
+
for (const x of [ 'Array', 'ArrayBuffer', 'Atomics', 'Date', 'Error', 'JSON', 'Object', 'Promise', 'Reflect', 'String', 'Symbol', 'Uint8Array', 'Int8Array', 'Uint8ClampedArray', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'SharedArrayBuffer', 'BigInt', 'Boolean', 'DataView', 'AggregateError', 'TypeError', 'ReferenceError', 'SyntaxError', 'RangeError', 'EvalError', 'URIError', 'Function', 'Map', 'RegExp', 'Set', 'WeakMap', 'WeakRef', 'WeakSet' ]) {
|
411
|
+
object(x, autoFuncs(x));
|
412
|
+
}
|
413
|
+
|
414
|
+
const enumerableGlobals = [ 'atob', 'btoa', 'performance', 'crypto', 'navigator' ];
|
415
|
+
object('globalThis', {
|
416
|
+
// 19.1 Value Properties of the Global Object
|
417
|
+
// https://tc39.es/ecma262/#sec-value-properties-of-the-global-object
|
418
|
+
// 19.1.1 globalThis
|
419
|
+
globalThis: {
|
420
|
+
writable: true,
|
421
|
+
enumerable: false,
|
422
|
+
configurable: true
|
423
|
+
},
|
424
|
+
|
425
|
+
// 19.1.2 Infinity
|
426
|
+
// 19.1.3 NaN
|
427
|
+
// 19.1.4 undefined
|
428
|
+
...props({
|
429
|
+
writable: false,
|
430
|
+
enumerable: false,
|
431
|
+
configurable: false
|
432
|
+
}, [ 'Infinity', 'NaN', 'undefined' ]),
|
433
|
+
|
434
|
+
// 19.2 Function Properties of the Global Object
|
435
|
+
// https://tc39.es/ecma262/#sec-function-properties-of-the-global-object
|
436
|
+
// 19.3 Constructor Properties of the Global Object
|
437
|
+
// https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object
|
438
|
+
...props({
|
439
|
+
writable: true,
|
440
|
+
enumerable: false,
|
441
|
+
configurable: true
|
442
|
+
}, builtinFuncKeys.filter(x => !x.startsWith('__') && !enumerableGlobals.includes(x) && !x.startsWith('f64') && !x.startsWith('i32'))),
|
443
|
+
|
444
|
+
...props({
|
445
|
+
writable: true,
|
446
|
+
enumerable: true,
|
447
|
+
configurable: true
|
448
|
+
}, enumerableGlobals)
|
449
|
+
});
|
450
|
+
|
451
|
+
if (Prefs.logMissingObjects) for (const x of Object.keys(builtinFuncs).concat(Object.keys(this))) {
|
452
|
+
if (!x.startsWith('__')) continue;
|
453
|
+
const name = x.split('_').slice(2, -1).join('_');
|
454
|
+
|
455
|
+
let t = globalThis;
|
456
|
+
for (const x of name.split('_')) {
|
457
|
+
t = t[x];
|
458
|
+
if (!t) break;
|
459
|
+
}
|
460
|
+
if (!t) continue;
|
461
|
+
|
462
|
+
if (!done.has(name) && !done.has('__' + name)) {
|
463
|
+
console.log(name, !!builtinFuncs[name]);
|
464
|
+
done.add(name);
|
465
|
+
}
|
466
|
+
}
|
125
467
|
};
|
126
468
|
|
127
469
|
export const BuiltinFuncs = function() {
|
@@ -820,7 +1162,7 @@ export const BuiltinFuncs = function() {
|
|
820
1162
|
number(funcs.bytesPerFuncLut(), Valtype.i32)
|
821
1163
|
] ],
|
822
1164
|
[ Opcodes.i32_mul ],
|
823
|
-
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocLargePage(scope, 'func lut')) ]
|
1165
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocLargePage(scope, '#func lut')) ]
|
824
1166
|
],
|
825
1167
|
table: true
|
826
1168
|
};
|
@@ -837,7 +1179,7 @@ export const BuiltinFuncs = function() {
|
|
837
1179
|
[ Opcodes.i32_mul ],
|
838
1180
|
number(2, Valtype.i32),
|
839
1181
|
[ Opcodes.i32_add ],
|
840
|
-
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocLargePage(scope, 'func lut')) ]
|
1182
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(allocLargePage(scope, '#func lut')) ]
|
841
1183
|
],
|
842
1184
|
table: true
|
843
1185
|
};
|
@@ -854,7 +1196,7 @@ export const BuiltinFuncs = function() {
|
|
854
1196
|
[ Opcodes.i32_mul ],
|
855
1197
|
number(3, Valtype.i32),
|
856
1198
|
[ Opcodes.i32_add ],
|
857
|
-
number(allocLargePage(scope, 'func lut'), Valtype.i32),
|
1199
|
+
number(allocLargePage(scope, '#func lut'), Valtype.i32),
|
858
1200
|
[ Opcodes.i32_add ]
|
859
1201
|
],
|
860
1202
|
table: true
|
package/compiler/codegen.js
CHANGED
@@ -5,9 +5,68 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './bui
|
|
5
5
|
import { TYPES, TYPE_FLAGS, TYPE_NAMES } from './types.js';
|
6
6
|
import parse from './parse.js';
|
7
7
|
import { log } from './log.js';
|
8
|
-
import { allocPage, allocStr } from './allocator.js';
|
9
8
|
import './prefs.js';
|
10
9
|
|
10
|
+
const pagePtr = ind => {
|
11
|
+
if (ind === 0) return 16;
|
12
|
+
return ind * pageSize;
|
13
|
+
};
|
14
|
+
|
15
|
+
const allocPage = (scope, name) => {
|
16
|
+
if (!name.startsWith('#')) {
|
17
|
+
name = `${scope.name}/${name}`;
|
18
|
+
if (globalThis.precompile) name = `${globalThis.precompile}/${name}`;
|
19
|
+
}
|
20
|
+
|
21
|
+
if (pages.has(name)) {
|
22
|
+
return pagePtr(pages.get(name));
|
23
|
+
}
|
24
|
+
|
25
|
+
const ind = pages.size;
|
26
|
+
pages.set(name, ind);
|
27
|
+
|
28
|
+
scope.pages ??= new Map();
|
29
|
+
scope.pages.set(name, ind);
|
30
|
+
|
31
|
+
return pagePtr(ind);
|
32
|
+
};
|
33
|
+
|
34
|
+
const allocBytes = (scope, reason, bytes) => {
|
35
|
+
bytes += 2; // overallocate by 2 bytes to ensure null termination
|
36
|
+
|
37
|
+
const allocs = pages.allocs ??= new Map();
|
38
|
+
const bins = pages.bins ??= [];
|
39
|
+
|
40
|
+
if (allocs.has(reason)) {
|
41
|
+
return allocs.get(reason);
|
42
|
+
}
|
43
|
+
|
44
|
+
let bin = bins.find(x => (pageSize - x.used) >= bytes);
|
45
|
+
if (!bin) {
|
46
|
+
// new bin
|
47
|
+
const page = pages.size;
|
48
|
+
bin = {
|
49
|
+
used: 0,
|
50
|
+
page
|
51
|
+
};
|
52
|
+
|
53
|
+
const id = bins.push(bin);
|
54
|
+
pages.set(`#bin: ${id}`, page);
|
55
|
+
}
|
56
|
+
|
57
|
+
const ptr = pagePtr(bin.page) + bin.used;
|
58
|
+
bin.used += bytes;
|
59
|
+
|
60
|
+
allocs.set(reason, ptr);
|
61
|
+
return ptr;
|
62
|
+
};
|
63
|
+
|
64
|
+
export const allocStr = (scope, str, bytestring) => {
|
65
|
+
// basic string interning for ~free
|
66
|
+
const bytes = 4 + str.length * (bytestring ? 1 : 2);
|
67
|
+
return allocBytes(scope, str, bytes);
|
68
|
+
};
|
69
|
+
|
11
70
|
const todo = (scope, msg, expectsValue = undefined) => {
|
12
71
|
msg = `todo: ${msg}`;
|
13
72
|
|
@@ -1465,10 +1524,10 @@ const asmFuncToAsm = (scope, func, extra) => func(scope, {
|
|
1465
1524
|
wasm.push(Opcodes.i32_to_u);
|
1466
1525
|
return wasm;
|
1467
1526
|
},
|
1468
|
-
allocPage
|
1527
|
+
allocPage,
|
1469
1528
|
allocLargePage: (scope, name) => {
|
1470
|
-
const _ = allocPage(
|
1471
|
-
allocPage(
|
1529
|
+
const _ = allocPage(scope, name);
|
1530
|
+
allocPage(scope, name + '#2');
|
1472
1531
|
|
1473
1532
|
return _;
|
1474
1533
|
}
|
@@ -5582,7 +5641,7 @@ const makeString = (scope, str, bytestring = true) => {
|
|
5582
5641
|
if (c > 0xFF) bytestring = false;
|
5583
5642
|
}
|
5584
5643
|
|
5585
|
-
const ptr = allocStr(
|
5644
|
+
const ptr = allocStr(scope, str, bytestring);
|
5586
5645
|
makeData(scope, elements, str, bytestring ? 'i8' : 'i16');
|
5587
5646
|
|
5588
5647
|
return [ number(ptr) ];
|
@@ -5598,7 +5657,7 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', static
|
|
5598
5657
|
if (staticAlloc || decl._staticAlloc) {
|
5599
5658
|
const uniqueName = name === '$undeclared' ? name + uniqId() : name;
|
5600
5659
|
|
5601
|
-
const ptr = allocPage(
|
5660
|
+
const ptr = allocPage(scope, uniqueName);
|
5602
5661
|
pointer = number(ptr, Valtype.i32);
|
5603
5662
|
|
5604
5663
|
scope.arrays ??= new Map();
|
package/compiler/precompile.js
CHANGED
@@ -57,7 +57,7 @@ const compile = async (file, _funcs) => {
|
|
57
57
|
first = source.slice(0, source.indexOf('\n'));
|
58
58
|
}
|
59
59
|
|
60
|
-
let args = ['--module', '--todo-time=compile', '--truthy=no_nan_negative', '--no-rm-unused-types', '--
|
60
|
+
let args = ['--module', '--todo-time=compile', '--truthy=no_nan_negative', '--no-rm-unused-types', '--fast-length', '--parse-types', '--opt-types', '--no-passive-data', '--active-data', '--no-treeshake-wasm-imports', '--no-coctc'];
|
61
61
|
if (first.startsWith('// @porf')) {
|
62
62
|
args = first.slice('// @porf '.length).split(' ').concat(args);
|
63
63
|
}
|
package/package.json
CHANGED
package/runtime/index.js
CHANGED
package/AGENTS.md
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
you are an assistant coder for Porffor - an early javascript and typescript to webassembly and native ahead-of-time compiler. the codebase is written in js and ts.
|
2
|
-
|
3
|
-
- built-ins apis (Date, String.prototype, etc) are written in ts inside `compiler/builtins`
|
4
|
-
- once you update a file inside that directory, you MUST run `./porf precompile` to compile to make your changes effective
|
5
|
-
- test your work using the test262 tools available, iterate independently but do not get stuck on one chain of thought or approach. paths for test262 tools should be relative to the 'test262/test' directory (e.g., 'built-ins/RegExp' NOT 'test262/test/built-ins/RegExp').
|
6
|
-
- to get an overview of the most failing directories, use the command: `node test262/fails.cjs`
|
7
|
-
- to just evaluate code in the engine, use the command: `./porf -p "..."`
|
8
|
-
- after finishing, check if your work/diff could be simplified
|
9
|
-
- always use single quotes (unless double is required)
|
10
|
-
- 2-space indentation, LF line endings, no trailing whitespace
|
11
|
-
- built-in APIs in `compiler/builtins/` written in TypeScript
|
12
|
-
- inline code in built-ins, avoid helper functions
|
13
|
-
- use semicolons
|
14
|
-
- do not use trailing commas
|
15
|
-
- use const/let, avoid var
|
16
|
-
- follow existing naming: camelCase for variables, PascalCase for types
|
17
|
-
- when possible, do not concat strings and instead write to them directly using wasm memory
|
18
|
-
- there are no closures, you cannot use locals between functions. use arguments or globals if really necessary
|
19
|
-
- The user prefers that prototype functions not have explicit return types in this codebase.
|
20
|
-
- The user prefers not to use global variables in built-in functions.
|
package/compiler/allocator.js
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
import './prefs.js';
|
2
|
-
|
3
|
-
const pagePtr = ind => {
|
4
|
-
if (ind === 0) return 16;
|
5
|
-
return ind * pageSize;
|
6
|
-
};
|
7
|
-
|
8
|
-
export const nameToReason = (scope, name) => {
|
9
|
-
let scopeName = scope.name;
|
10
|
-
if (globalThis.precompile && scopeName === '#main') scopeName = globalThis.precompile;
|
11
|
-
|
12
|
-
return `${Prefs.scopedPageNames ? (scopeName + '/') : ''}${name}`;
|
13
|
-
};
|
14
|
-
|
15
|
-
export const allocPage = ({ scope, pages }, name) => {
|
16
|
-
const reason = nameToReason(scope, name);
|
17
|
-
|
18
|
-
if (pages.has(reason)) {
|
19
|
-
return pagePtr(pages.get(reason));
|
20
|
-
}
|
21
|
-
|
22
|
-
const ind = pages.size;
|
23
|
-
pages.set(reason, ind);
|
24
|
-
|
25
|
-
scope.pages ??= new Map();
|
26
|
-
scope.pages.set(reason, ind);
|
27
|
-
|
28
|
-
return pagePtr(ind);
|
29
|
-
};
|
30
|
-
|
31
|
-
export const allocBytes = ({ scope, pages }, reason, bytes) => {
|
32
|
-
bytes += 2; // overallocate by 2 bytes to ensure null termination
|
33
|
-
|
34
|
-
const allocs = pages.allocs ??= new Map();
|
35
|
-
const bins = pages.bins ??= [];
|
36
|
-
|
37
|
-
if (allocs.has(reason)) {
|
38
|
-
return allocs.get(reason);
|
39
|
-
}
|
40
|
-
|
41
|
-
let bin = bins.find(x => (pageSize - x.used) >= bytes);
|
42
|
-
if (!bin) {
|
43
|
-
// new bin
|
44
|
-
const page = pages.size;
|
45
|
-
bin = {
|
46
|
-
used: 0,
|
47
|
-
page
|
48
|
-
};
|
49
|
-
|
50
|
-
const id = bins.push(bin);
|
51
|
-
pages.set(`#bin: ${id}`, page);
|
52
|
-
}
|
53
|
-
|
54
|
-
const ptr = pagePtr(bin.page) + bin.used;
|
55
|
-
bin.used += bytes;
|
56
|
-
|
57
|
-
allocs.set(reason, ptr);
|
58
|
-
return ptr;
|
59
|
-
};
|
60
|
-
|
61
|
-
export const allocStr = ({ scope, pages }, str, bytestring) => {
|
62
|
-
// basic string interning for ~free
|
63
|
-
const bytes = 4 + str.length * (bytestring ? 1 : 2);
|
64
|
-
return allocBytes({ scope, pages }, str, bytes);
|
65
|
-
};
|
@@ -1,349 +0,0 @@
|
|
1
|
-
import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
|
2
|
-
import { TYPES } from './types.js';
|
3
|
-
import { number } from './encoding.js';
|
4
|
-
|
5
|
-
export default function ({ builtinFuncs }, Prefs) {
|
6
|
-
const makePrefix = name => (name.startsWith('__') ? '' : '__') + name + '_';
|
7
|
-
|
8
|
-
const done = new Set();
|
9
|
-
const object = (name, props) => {
|
10
|
-
done.add(name);
|
11
|
-
const prefix = name === 'globalThis' ? '' : makePrefix(name);
|
12
|
-
|
13
|
-
// already a func
|
14
|
-
const existingFunc = builtinFuncs[name];
|
15
|
-
|
16
|
-
builtinFuncs['#get_' + name] = {
|
17
|
-
params: [],
|
18
|
-
locals: [ Valtype.i32 ],
|
19
|
-
returns: [ Valtype.i32 ],
|
20
|
-
returnType: TYPES.object,
|
21
|
-
wasm: (scope, { allocPage, makeString, generate, getNodeType, builtin, funcRef, glbl }) => {
|
22
|
-
if (globalThis.precompile) return [ [ 'get object', name ] ];
|
23
|
-
|
24
|
-
// todo/perf: precompute bytes here instead of calling real funcs if we really care about perf later
|
25
|
-
|
26
|
-
let ptr;
|
27
|
-
if (existingFunc) {
|
28
|
-
ptr = funcRef(name)[0][1];
|
29
|
-
} else {
|
30
|
-
ptr = allocPage(scope, `builtin object: ${name}`);
|
31
|
-
}
|
32
|
-
|
33
|
-
const getPtr = glbl(Opcodes.global_get, `getptr_${name}`, Valtype.i32)[0];
|
34
|
-
const out = [
|
35
|
-
// check if already made/cached
|
36
|
-
getPtr,
|
37
|
-
[ Opcodes.if, Blocktype.void ],
|
38
|
-
getPtr,
|
39
|
-
[ Opcodes.return ],
|
40
|
-
[ Opcodes.end ],
|
41
|
-
|
42
|
-
// set cache & ptr for use
|
43
|
-
number(ptr, Valtype.i32),
|
44
|
-
[ Opcodes.local_tee, 0 ],
|
45
|
-
glbl(Opcodes.global_set, `getptr_${name}`, Valtype.i32)[0],
|
46
|
-
|
47
|
-
[ Opcodes.local_get, 0 ],
|
48
|
-
Opcodes.i32_from_u,
|
49
|
-
number(existingFunc ? TYPES.function : TYPES.object, Valtype.i32),
|
50
|
-
[ Opcodes.call, builtin('__Porffor_object_underlying') ],
|
51
|
-
[ Opcodes.drop ],
|
52
|
-
[ Opcodes.local_set, 0 ]
|
53
|
-
];
|
54
|
-
|
55
|
-
for (const x in props) {
|
56
|
-
let value = {
|
57
|
-
type: 'Identifier',
|
58
|
-
name: prefix + x
|
59
|
-
};
|
60
|
-
|
61
|
-
if (x === '__proto__') {
|
62
|
-
out.push(
|
63
|
-
[ Opcodes.local_get, 0 ],
|
64
|
-
number(TYPES.object, Valtype.i32),
|
65
|
-
|
66
|
-
...generate(scope, value),
|
67
|
-
Opcodes.i32_to_u,
|
68
|
-
...getNodeType(scope, value),
|
69
|
-
|
70
|
-
[ Opcodes.call, builtin('__Porffor_object_setPrototype') ]
|
71
|
-
);
|
72
|
-
continue;
|
73
|
-
}
|
74
|
-
|
75
|
-
let add = true;
|
76
|
-
if (existingFunc && (x === 'prototype' || x === 'constructor')) add = false;
|
77
|
-
|
78
|
-
let flags = 0b0000;
|
79
|
-
const d = props[x];
|
80
|
-
if (d.configurable) flags |= 0b0010;
|
81
|
-
if (d.enumerable) flags |= 0b0100;
|
82
|
-
if (d.writable) flags |= 0b1000;
|
83
|
-
|
84
|
-
out.push(
|
85
|
-
[ Opcodes.local_get, 0 ],
|
86
|
-
number(TYPES.object, Valtype.i32),
|
87
|
-
|
88
|
-
...makeString(scope, x),
|
89
|
-
Opcodes.i32_to_u,
|
90
|
-
number(TYPES.bytestring, Valtype.i32),
|
91
|
-
|
92
|
-
...generate(scope, value),
|
93
|
-
...getNodeType(scope, value),
|
94
|
-
|
95
|
-
number(flags, Valtype.i32),
|
96
|
-
number(TYPES.number, Valtype.i32),
|
97
|
-
|
98
|
-
[ Opcodes.call, builtin(add ? '__Porffor_object_fastAdd' : '__Porffor_object_define') ]
|
99
|
-
);
|
100
|
-
}
|
101
|
-
|
102
|
-
// return ptr
|
103
|
-
out.push(getPtr);
|
104
|
-
return out;
|
105
|
-
}
|
106
|
-
};
|
107
|
-
|
108
|
-
this[name] = (scope, { builtin }) => [
|
109
|
-
[ Opcodes.call, builtin('#get_' + name) ],
|
110
|
-
Opcodes.i32_from_u
|
111
|
-
];
|
112
|
-
this[name].type = existingFunc ? TYPES.function : TYPES.object;
|
113
|
-
|
114
|
-
for (const x in props) {
|
115
|
-
const d = props[x];
|
116
|
-
const k = prefix + x;
|
117
|
-
|
118
|
-
if (Object.hasOwn(d, 'value') && !Object.hasOwn(builtinFuncs, k) && !Object.hasOwn(this, k)) {
|
119
|
-
if (Array.isArray(d.value) || typeof d.value === 'function') {
|
120
|
-
this[k] = d.value;
|
121
|
-
continue;
|
122
|
-
}
|
123
|
-
|
124
|
-
if (typeof d.value === 'number') {
|
125
|
-
this[k] = [ number(d.value) ];
|
126
|
-
this[k].type = TYPES.number;
|
127
|
-
continue;
|
128
|
-
}
|
129
|
-
|
130
|
-
if (typeof d.value === 'string') {
|
131
|
-
this[k] = (scope, { makeString }) => makeString(scope, d.value);
|
132
|
-
this[k].type = TYPES.bytestring;
|
133
|
-
continue;
|
134
|
-
}
|
135
|
-
|
136
|
-
if (d.value === null) {
|
137
|
-
this[k] = this.null;
|
138
|
-
continue;
|
139
|
-
}
|
140
|
-
|
141
|
-
throw new Error(`unsupported value type (${typeof d.value})`);
|
142
|
-
}
|
143
|
-
}
|
144
|
-
};
|
145
|
-
|
146
|
-
const props = (base, vals) => {
|
147
|
-
const out = {};
|
148
|
-
|
149
|
-
if (Array.isArray(vals)) {
|
150
|
-
// array of keys with no value
|
151
|
-
for (const x of vals) {
|
152
|
-
out[x] = {
|
153
|
-
...base
|
154
|
-
};
|
155
|
-
}
|
156
|
-
} else for (const x in vals) {
|
157
|
-
// object of key values
|
158
|
-
out[x] = {
|
159
|
-
...base,
|
160
|
-
value: vals[x]
|
161
|
-
};
|
162
|
-
}
|
163
|
-
|
164
|
-
return out;
|
165
|
-
};
|
166
|
-
|
167
|
-
const builtinFuncKeys = Object.keys(builtinFuncs);
|
168
|
-
const autoFuncKeys = name => {
|
169
|
-
const prefix = makePrefix(name);
|
170
|
-
return builtinFuncKeys.filter(x => x.startsWith(prefix)).map(x => x.slice(prefix.length)).filter(x => !x.startsWith('prototype_'));
|
171
|
-
};
|
172
|
-
const autoFuncs = name => ({
|
173
|
-
...props({
|
174
|
-
writable: true,
|
175
|
-
enumerable: false,
|
176
|
-
configurable: true
|
177
|
-
}, autoFuncKeys(name)),
|
178
|
-
...(this[`__${name}_prototype`] ? {
|
179
|
-
prototype: {
|
180
|
-
writable: false,
|
181
|
-
enumerable: false,
|
182
|
-
configurable: false
|
183
|
-
}
|
184
|
-
} : {})
|
185
|
-
});
|
186
|
-
|
187
|
-
object('Math', {
|
188
|
-
...props({
|
189
|
-
writable: false,
|
190
|
-
enumerable: false,
|
191
|
-
configurable: false
|
192
|
-
}, {
|
193
|
-
E: Math.E,
|
194
|
-
LN10: Math.LN10,
|
195
|
-
LN2: Math.LN2,
|
196
|
-
LOG10E: Math.LOG10E,
|
197
|
-
LOG2E: Math.LOG2E,
|
198
|
-
PI: Math.PI,
|
199
|
-
SQRT1_2: Math.SQRT1_2,
|
200
|
-
SQRT2: Math.SQRT2,
|
201
|
-
|
202
|
-
// https://github.com/rwaldron/proposal-math-extensions/issues/10
|
203
|
-
RAD_PER_DEG: Math.PI / 180,
|
204
|
-
DEG_PER_RAD: 180 / Math.PI
|
205
|
-
}),
|
206
|
-
|
207
|
-
...autoFuncs('Math')
|
208
|
-
});
|
209
|
-
|
210
|
-
// automatically generate objects for prototypes
|
211
|
-
for (const x of builtinFuncKeys.reduce((acc, x) => {
|
212
|
-
const ind = x.indexOf('_prototype_');
|
213
|
-
if (ind === -1) return acc;
|
214
|
-
|
215
|
-
acc.add(x.slice(0, ind + 10));
|
216
|
-
return acc;
|
217
|
-
}, new Set())) {
|
218
|
-
const props = autoFuncs(x);
|
219
|
-
|
220
|
-
// special case: Object.prototype.__proto__ = null
|
221
|
-
if (x === '__Object_prototype') {
|
222
|
-
Object.defineProperty(props, '__proto__', { value: { value: null, configurable: true }, enumerable: true });
|
223
|
-
}
|
224
|
-
|
225
|
-
// special case: Function.prototype.length = 0
|
226
|
-
// special case: Function.prototype.name = ''
|
227
|
-
if (x === '__Function_prototype') {
|
228
|
-
props.length = { value: 0, configurable: true };
|
229
|
-
props.name = { value: '', configurable: true };
|
230
|
-
}
|
231
|
-
|
232
|
-
// add constructor for constructors
|
233
|
-
const name = x.slice(2, x.indexOf('_', 2));
|
234
|
-
if (builtinFuncs[name]?.constr) {
|
235
|
-
const value = (scope, { funcRef }) => funcRef(name);
|
236
|
-
value.type = TYPES.function;
|
237
|
-
|
238
|
-
props.constructor = {
|
239
|
-
value,
|
240
|
-
writable: true,
|
241
|
-
enumerable: false,
|
242
|
-
configurable: true
|
243
|
-
};
|
244
|
-
}
|
245
|
-
|
246
|
-
object(x, props);
|
247
|
-
}
|
248
|
-
|
249
|
-
|
250
|
-
object('Number', {
|
251
|
-
...props({
|
252
|
-
writable: false,
|
253
|
-
enumerable: false,
|
254
|
-
configurable: false
|
255
|
-
}, {
|
256
|
-
NaN: NaN,
|
257
|
-
POSITIVE_INFINITY: Infinity,
|
258
|
-
NEGATIVE_INFINITY: -Infinity,
|
259
|
-
MAX_VALUE: valtype === 'i32' ? 2147483647 : 1.7976931348623157e+308,
|
260
|
-
MIN_VALUE: valtype === 'i32' ? -2147483648 : 5e-324,
|
261
|
-
MAX_SAFE_INTEGER: valtype === 'i32' ? 2147483647 : 9007199254740991,
|
262
|
-
MIN_SAFE_INTEGER: valtype === 'i32' ? -2147483648 : -9007199254740991,
|
263
|
-
EPSILON: 2.220446049250313e-16
|
264
|
-
}),
|
265
|
-
|
266
|
-
...autoFuncs('Number')
|
267
|
-
});
|
268
|
-
|
269
|
-
// these technically not spec compliant as it should be classes or non-enumerable but eh
|
270
|
-
object('navigator', {
|
271
|
-
...props({
|
272
|
-
writable: false,
|
273
|
-
enumerable: true,
|
274
|
-
configurable: false
|
275
|
-
}, {
|
276
|
-
userAgent: `Porffor/${globalThis.version}`
|
277
|
-
})
|
278
|
-
});
|
279
|
-
|
280
|
-
for (const x of [
|
281
|
-
'console',
|
282
|
-
'crypto',
|
283
|
-
'performance',
|
284
|
-
]) {
|
285
|
-
object(x, props({
|
286
|
-
writable: true,
|
287
|
-
enumerable: true,
|
288
|
-
configurable: true
|
289
|
-
}, autoFuncKeys(x).slice(0, 12)));
|
290
|
-
}
|
291
|
-
|
292
|
-
for (const x of [ 'Array', 'ArrayBuffer', 'Atomics', 'Date', 'Error', 'JSON', 'Object', 'Promise', 'Reflect', 'String', 'Symbol', 'Uint8Array', 'Int8Array', 'Uint8ClampedArray', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'SharedArrayBuffer', 'BigInt', 'Boolean', 'DataView', 'AggregateError', 'TypeError', 'ReferenceError', 'SyntaxError', 'RangeError', 'EvalError', 'URIError', 'Function', 'Map', 'RegExp', 'Set', 'WeakMap', 'WeakRef', 'WeakSet' ]) {
|
293
|
-
object(x, autoFuncs(x));
|
294
|
-
}
|
295
|
-
|
296
|
-
const enumerableGlobals = [ 'atob', 'btoa', 'performance', 'crypto', 'navigator' ];
|
297
|
-
object('globalThis', {
|
298
|
-
// 19.1 Value Properties of the Global Object
|
299
|
-
// https://tc39.es/ecma262/#sec-value-properties-of-the-global-object
|
300
|
-
// 19.1.1 globalThis
|
301
|
-
globalThis: {
|
302
|
-
writable: true,
|
303
|
-
enumerable: false,
|
304
|
-
configurable: true
|
305
|
-
},
|
306
|
-
|
307
|
-
// 19.1.2 Infinity
|
308
|
-
// 19.1.3 NaN
|
309
|
-
// 19.1.4 undefined
|
310
|
-
...props({
|
311
|
-
writable: false,
|
312
|
-
enumerable: false,
|
313
|
-
configurable: false
|
314
|
-
}, [ 'Infinity', 'NaN', 'undefined' ]),
|
315
|
-
|
316
|
-
// 19.2 Function Properties of the Global Object
|
317
|
-
// https://tc39.es/ecma262/#sec-function-properties-of-the-global-object
|
318
|
-
// 19.3 Constructor Properties of the Global Object
|
319
|
-
// https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object
|
320
|
-
...props({
|
321
|
-
writable: true,
|
322
|
-
enumerable: false,
|
323
|
-
configurable: true
|
324
|
-
}, builtinFuncKeys.filter(x => !x.startsWith('__') && !enumerableGlobals.includes(x) && !x.startsWith('f64') && !x.startsWith('i32'))),
|
325
|
-
|
326
|
-
...props({
|
327
|
-
writable: true,
|
328
|
-
enumerable: true,
|
329
|
-
configurable: true
|
330
|
-
}, enumerableGlobals)
|
331
|
-
});
|
332
|
-
|
333
|
-
if (Prefs.logMissingObjects) for (const x of Object.keys(builtinFuncs).concat(Object.keys(this))) {
|
334
|
-
if (!x.startsWith('__')) continue;
|
335
|
-
const name = x.split('_').slice(2, -1).join('_');
|
336
|
-
|
337
|
-
let t = globalThis;
|
338
|
-
for (const x of name.split('_')) {
|
339
|
-
t = t[x];
|
340
|
-
if (!t) break;
|
341
|
-
}
|
342
|
-
if (!t) continue;
|
343
|
-
|
344
|
-
if (!done.has(name) && !done.has('__' + name)) {
|
345
|
-
console.log(name, !!builtinFuncs[name]);
|
346
|
-
done.add(name);
|
347
|
-
}
|
348
|
-
}
|
349
|
-
};
|