porffor 0.60.6 → 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 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
- - `builtins_object.js`: all the various built-in objects (think `String`, `globalThis`, etc.)
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/2c.js CHANGED
@@ -429,6 +429,7 @@ export default ({ funcs, globals, data, pages }) => {
429
429
  for (const name in symbols) {
430
430
  line(`*(void**)(&${name}) = dlsym(_dl, "${name}")`);
431
431
  ffiFuncs[name] = symbols[name];
432
+ cified.add(name, true);
432
433
  }
433
434
 
434
435
  continue;
@@ -642,9 +643,13 @@ extern ${importFunc.returns.length > 0 ? CValtype[importFunc.returns[0]] : 'void
642
643
 
643
644
  case 'time': {
644
645
  const id = tmpId++;
645
- line(`time_t _time_t${id}`);
646
- line(`time(&_time_t${id})`);
647
- line(`f64 _time_out${id} = (f64)_time_t${id} * 1000.0`);
646
+ platformSpecific(`
647
+ time_t _time_t${id};
648
+ time(&_time_t${id});
649
+ f64 _time_out${id} = (f64)_time_t${id} * 1000.0;`, `
650
+ struct timespec _ts${id};
651
+ clock_gettime(CLOCK_REALTIME, &_ts${id});
652
+ f64 _time_out${id} = (f64)_ts${id}.tv_sec * 1000.0 + (f64)_ts${id}.tv_nsec / 1.0e6;`);
648
653
  vals.push(`_time_out${id}`);
649
654
 
650
655
  includes.set('time.h', true);
@@ -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;
@@ -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(ctx) {
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
- ObjectBuiltins.call(this, ctx, Prefs);
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
@@ -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: (scope, name) => allocPage({ scope, pages }, name),
1527
+ allocPage,
1469
1528
  allocLargePage: (scope, name) => {
1470
- const _ = allocPage({ scope, pages }, name);
1471
- allocPage({ scope, pages }, name + '#2');
1529
+ const _ = allocPage(scope, name);
1530
+ allocPage(scope, name + '#2');
1472
1531
 
1473
1532
  return _;
1474
1533
  }
@@ -2638,7 +2697,8 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
2638
2697
  args.push({
2639
2698
  type: 'ArrayExpression',
2640
2699
  elements: restArgs,
2641
- _doNotMarkTypeUsed: true
2700
+ _doNotMarkTypeUsed: true,
2701
+ _staticAlloc: func.internal
2642
2702
  });
2643
2703
  }
2644
2704
  }
@@ -5581,7 +5641,7 @@ const makeString = (scope, str, bytestring = true) => {
5581
5641
  if (c > 0xFF) bytestring = false;
5582
5642
  }
5583
5643
 
5584
- const ptr = allocStr({ scope, pages }, str, bytestring);
5644
+ const ptr = allocStr(scope, str, bytestring);
5585
5645
  makeData(scope, elements, str, bytestring ? 'i8' : 'i16');
5586
5646
 
5587
5647
  return [ number(ptr) ];
@@ -5594,10 +5654,10 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', static
5594
5654
  const out = [];
5595
5655
  let pointer;
5596
5656
 
5597
- if (staticAlloc) {
5657
+ if (staticAlloc || decl._staticAlloc) {
5598
5658
  const uniqueName = name === '$undeclared' ? name + uniqId() : name;
5599
5659
 
5600
- const ptr = allocPage({ scope, pages }, uniqueName);
5660
+ const ptr = allocPage(scope, uniqueName);
5601
5661
  pointer = number(ptr, Valtype.i32);
5602
5662
 
5603
5663
  scope.arrays ??= new Map();
@@ -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', '--scoped-page-names', '--fast-length', '--parse-types', '--opt-types', '--no-passive-data', '--active-data', '--no-treeshake-wasm-imports', '--no-coctc'];
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "An ahead-of-time JavaScript compiler",
4
- "version": "0.60.6",
4
+ "version": "0.60.8",
5
5
  "author": "Oliver Medhurst <honk@goose.icu>",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/runtime/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.60.6';
3
+ globalThis.version = '0.60.8';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
package/AGENT.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.
@@ -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
- };