porffor 0.55.13 → 0.55.15

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
@@ -108,7 +108,6 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
108
108
  - `codegen.js`: code (wasm) generation, ast -> wasm. The bulk of the effort
109
109
  - `cyclone.js`: wasm partial constant evaluator (it is fast and dangerous hence "cyclone")
110
110
  - `disassemble.js`: wasm disassembler using internal debug info
111
- - `embedding.js`: utils for embedding consts
112
111
  - `encoding.js`: utils for encoding things as bytes as wasm expects
113
112
  - `expression.js`: mapping most operators to an opcode (advanced are as built-ins eg `f64_%`)
114
113
  - `havoc.js`: wasm rewrite library (it wreaks havoc upon wasm bytecode hence "havoc")
@@ -135,11 +135,16 @@ export const __Porffor_object_lookup = (obj: any, target: any): i32 => {
135
135
  const size: i32 = Porffor.wasm.i32.load(obj, 0, 0);
136
136
  const endPtr: i32 = ptr + size * 14;
137
137
 
138
+ let out: boolean = false;
138
139
  if (targetType == Porffor.TYPES.symbol) {
139
140
  const targetSym: symbol = target;
140
141
  for (; ptr < endPtr; ptr += 14) {
141
142
  const keyRaw: i32 = Porffor.wasm.i32.load(ptr, 0, 0);
142
- if (keyRaw == 0) break; // ran out of keys
143
+ if (keyRaw == 0) {
144
+ if (out) break; // ran out of keys
145
+ out = true;
146
+ }
147
+
143
148
  if (keyRaw >>> 30 == 3) { // MSB 1 and 2 set, symbol
144
149
  const keySym: symbol = keyRaw & 0x3FFFFFFF; // unset MSB
145
150
  if (keySym == targetSym) return ptr;
@@ -148,7 +153,10 @@ export const __Porffor_object_lookup = (obj: any, target: any): i32 => {
148
153
  } else {
149
154
  for (; ptr < endPtr; ptr += 14) {
150
155
  const keyRaw: i32 = Porffor.wasm.i32.load(ptr, 0, 0);
151
- if (keyRaw == 0) break; // ran out of keys
156
+ if (keyRaw == 0) {
157
+ if (out) break; // ran out of keys
158
+ out = true;
159
+ }
152
160
 
153
161
  const msb: i32 = keyRaw >>> 30;
154
162
  if (msb == 0) {
@@ -137,6 +137,11 @@ export const __Math_pow = (base: number, exponent: number): number => {
137
137
  // 2. If exponent is either +0𝔽 or -0𝔽, return 1𝔽.
138
138
  if (exponent == 0) return 1;
139
139
 
140
+ // opt: use bit shift for base 2
141
+ if (base == 2) {
142
+ if (Porffor.fastAnd(Number.isInteger(exponent), exponent > 0, exponent < 31)) return 2 << (exponent - 1);
143
+ }
144
+
140
145
  if (!Number.isFinite(base)) {
141
146
  // 3. If base is NaN, return NaN.
142
147
  if (Number.isNaN(base)) return base;
@@ -259,7 +264,7 @@ export const __Math_expm1 = (x: number): number => {
259
264
  return x;
260
265
  }
261
266
 
262
- // use exp(x) - 1 for large x (perf)
267
+ // opt: use exp(x) - 1 for large x
263
268
  if (Math.abs(x) > 1e-5) return Math.exp(x) - 1;
264
269
 
265
270
  // Taylor series
@@ -280,7 +285,7 @@ export const __Math_log1p = (x: number): number => {
280
285
  if (x == -1) return -Infinity; // log(0) = -inf
281
286
  if (!Number.isFinite(x)) return x;
282
287
 
283
- // use exp(x) - 1 for large x (perf)
288
+ // opt: use exp(x) - 1 for large x
284
289
  if (Math.abs(x) > 1e-5) return Math.log(1 + x);
285
290
 
286
291
  // Taylor series
@@ -454,4 +459,83 @@ export const __Math_atan2 = (y: number, x: number): number => {
454
459
 
455
460
  if (y >= 0) return Math.atan(ratio) + Math.PI;
456
461
  return Math.atan(ratio) - Math.PI;
462
+ };
463
+
464
+ export const __Math_sumPrecise = (values: any[]): number => {
465
+ // based on "Fast exact summation using small and large superaccumulators" by Radford M. Neal
466
+ // https://arxiv.org/abs/1505.05571
467
+ // accuracy is top priority, it is fine for this to be slow(er)
468
+
469
+ // small superaccumulator uses 67 chunks: (1 << (11 - 5)) + 3
470
+ // 11 is the number of exponent bits in IEEE-754 double precision
471
+ // 5 is the number of low-order exponent bits stored per chunk
472
+ const SMALL_SLOTS: number = 67;
473
+ const SMALL_MIN: number = -970;
474
+ const small: Float64Array = new Float64Array(SMALL_SLOTS);
475
+
476
+ // large superaccumulator uses 4096 chunks: 1 << (11 + 1)
477
+ // 11 is the number of exponent bits in IEEE-754 double precision
478
+ const LARGE_SLOTS: number = 4096;
479
+ const LARGE_MIN: number = -1074;
480
+ const large: Float64Array = new Float64Array(LARGE_SLOTS);
481
+
482
+ for (const _ of values) {
483
+ if (Porffor.rawType(_) != Porffor.TYPES.number) throw new TypeError('Math.sumPrecise must have only numbers in values');
484
+
485
+ const v: number = _;
486
+ if (v == 0) continue;
487
+
488
+ const exp: number = Porffor.number.getExponent(v);
489
+
490
+ // check if value fits in small superaccumulator
491
+ if (exp >= SMALL_MIN && exp < SMALL_MIN + SMALL_SLOTS) {
492
+ // map the exponent to an array index (-970 -> 0, -969 -> 1, etc)
493
+ const slot: number = exp - SMALL_MIN;
494
+ let y: number = v;
495
+
496
+ // cascade up through slots, similar to carrying digits in decimal
497
+ // but operating in binary and handling floating point carefully
498
+ for (let i: number = slot; i < SMALL_SLOTS - 1; i++) {
499
+ const sum: number = small[i] + y;
500
+ y = sum;
501
+
502
+ // a number fits in slot i if its magnitude is less than 2^(i+SMALL_MIN+1)
503
+ const slotLimit: number = Math.pow(2, i + SMALL_MIN + 1);
504
+ if (y >= -slotLimit && y < slotLimit) {
505
+ small[i] = y;
506
+ y = 0;
507
+ break;
508
+ }
509
+
510
+ // doesn't fit, clear this slot and continue cascading
511
+ small[i] = 0;
512
+ }
513
+
514
+ // if we still have a non-zero value after cascading through small,
515
+ // it needs to go into the large superaccumulator
516
+ if (y != 0) {
517
+ large[Porffor.number.getExponent(y) - LARGE_MIN] += y;
518
+ }
519
+ } else {
520
+ // exponent is outside small superaccumulator range,
521
+ // put it directly in the large superaccumulator
522
+ large[Porffor.number.getExponent(v) - LARGE_MIN] += v;
523
+ }
524
+ }
525
+
526
+ // combine results from both superaccumulators,
527
+ // process from highest to lowest to maintain precision
528
+ // todo: handle -0 (see test262 test)
529
+ let sum: number = -0;
530
+ for (let i: number = LARGE_SLOTS - 1; i >= 0; i--) {
531
+ sum += large[i];
532
+ }
533
+
534
+ for (let i: number = SMALL_SLOTS - 1; i >= 0; i--) {
535
+ sum += small[i];
536
+ }
537
+
538
+ // todo: free large and small
539
+
540
+ return sum;
457
541
  };
@@ -559,6 +559,8 @@ export const parseInt = (input: any, radix: any): f64 => {
559
559
 
560
560
  let defaultRadix: boolean = false;
561
561
  radix = ecma262.ToIntegerOrInfinity(radix);
562
+ if (!Number.isFinite(radix)) radix = 0; // infinity/NaN -> default
563
+
562
564
  if (radix == 0) {
563
565
  defaultRadix = true;
564
566
  radix = 10;
@@ -89,6 +89,10 @@ type PorfforGlobal = {
89
89
  appendPadNum(str: bytestring, num: number, len: number): i32;
90
90
  }
91
91
 
92
+ number: {
93
+ getExponent(v: f64): i32;
94
+ }
95
+
92
96
  print(x: any): i32;
93
97
 
94
98
  randomByte(): i32;
@@ -1,10 +1,9 @@
1
1
  import * as PrecompiledBuiltins from './builtins_precompiled.js';
2
2
  import ObjectBuiltins from './builtins_objects.js';
3
3
  import { Blocktype, Opcodes, Valtype, ValtypeSize } from './wasmSpec.js';
4
- import { number } from './embedding.js';
5
4
  import { TYPES, TYPE_NAMES } from './types.js';
5
+ import { number, unsignedLEB128 } from './encoding.js';
6
6
  import './prefs.js';
7
- import { unsignedLEB128 } from './encoding.js';
8
7
 
9
8
  export const importedFuncs = [
10
9
  {
@@ -230,90 +229,26 @@ export const BuiltinFuncs = function() {
230
229
  };
231
230
 
232
231
 
233
- this.__Math_sqrt = {
234
- floatOnly: true,
235
- params: [ valtypeBinary ],
236
- locals: [],
237
- returns: [ valtypeBinary ],
238
- returnType: TYPES.number,
239
- wasm: [
240
- [ Opcodes.local_get, 0 ],
241
- [ Opcodes.f64_sqrt ]
242
- ]
243
- };
244
-
245
- this.__Math_abs = {
246
- floatOnly: true,
247
- params: [ valtypeBinary ],
248
- locals: [],
249
- returns: [ valtypeBinary ],
250
- returnType: TYPES.number,
251
- wasm: [
252
- [ Opcodes.local_get, 0 ],
253
- [ Opcodes.f64_abs ]
254
- ]
255
- };
256
-
257
- this.__Math_sign = {
258
- floatOnly: true,
259
- params: [ valtypeBinary ],
260
- locals: [],
261
- returns: [ valtypeBinary ],
262
- returnType: TYPES.number,
263
- wasm: [
264
- number(1),
265
- [ Opcodes.local_get, 0 ],
266
- [ Opcodes.f64_copysign ]
267
- ]
268
- };
269
-
270
- this.__Math_floor = {
271
- floatOnly: true,
272
- params: [ valtypeBinary ],
273
- locals: [],
274
- returns: [ valtypeBinary ],
275
- returnType: TYPES.number,
276
- wasm: [
277
- [ Opcodes.local_get, 0 ],
278
- [ Opcodes.f64_floor ]
279
- ]
280
- };
281
-
282
- this.__Math_ceil = {
283
- floatOnly: true,
284
- params: [ valtypeBinary ],
285
- locals: [],
286
- returns: [ valtypeBinary ],
287
- returnType: TYPES.number,
288
- wasm: [
289
- [ Opcodes.local_get, 0 ],
290
- [ Opcodes.f64_ceil ]
291
- ]
292
- };
293
-
294
- this.__Math_round = {
295
- floatOnly: true,
296
- params: [ valtypeBinary ],
297
- locals: [],
298
- returns: [ valtypeBinary ],
299
- returnType: TYPES.number,
300
- wasm: [
301
- [ Opcodes.local_get, 0 ],
302
- [ Opcodes.f64_nearest ]
303
- ]
304
- };
305
-
306
- this.__Math_trunc = {
307
- floatOnly: true,
308
- params: [ valtypeBinary ],
309
- locals: [],
310
- returns: [ valtypeBinary ],
311
- returnType: TYPES.number,
312
- wasm: [
313
- [ Opcodes.local_get, 0 ],
314
- [ Opcodes.f64_trunc ]
315
- ]
316
- };
232
+ for (const [ name, op, prefix = [ [ Opcodes.local_get, 0 ] ] ] of [
233
+ [ 'sqrt', Opcodes.f64_sqrt ],
234
+ [ 'abs', Opcodes.f64_abs ],
235
+ [ 'sign', Opcodes.f64_copysign, [ number(1), [ Opcodes.local_get, 0 ] ] ],
236
+ [ 'floor', Opcodes.f64_floor ],
237
+ [ 'ceil', Opcodes.f64_ceil ],
238
+ [ 'round', Opcodes.f64_nearest ],
239
+ [ 'trunc', Opcodes.f64_trunc ]
240
+ ]) {
241
+ this[`__Math_${name}`] = {
242
+ params: [ Valtype.f64 ],
243
+ locals: [],
244
+ returns: [ Valtype.f64 ],
245
+ returnType: TYPES.number,
246
+ wasm: [
247
+ ...prefix,
248
+ [ op ]
249
+ ]
250
+ };
251
+ }
317
252
 
318
253
  // todo: does not follow spec with +-Infinity and values >2**32
319
254
  this.__Math_clz32 = {
@@ -359,10 +294,7 @@ export const BuiltinFuncs = function() {
359
294
  ]
360
295
  };
361
296
 
362
- // this is an implementation of xorshift128+ (in wasm bytecode)
363
- // fun fact: v8, SM, JSC also use this (you will need this fun fact to maintain your sanity reading this code)
364
297
  const prngSeed0 = (Math.random() * (2 ** 30)) | 0, prngSeed1 = (Math.random() * (2 ** 30)) | 0;
365
-
366
298
  const prng = ({
367
299
  'lcg32': {
368
300
  globals: [ Valtype.i32 ],
@@ -1025,5 +957,24 @@ export const BuiltinFuncs = function() {
1025
957
  table: true
1026
958
  };
1027
959
 
960
+ this.__Porffor_number_getExponent = {
961
+ params: [ Valtype.f64 ],
962
+ returns: [ Valtype.i32 ],
963
+ returnType: TYPES.number,
964
+ wasm: [
965
+ // extract exponent bits from f64 with bit manipulation
966
+ [ Opcodes.local_get, 0 ],
967
+ [ Opcodes.i64_reinterpret_f64 ],
968
+ number(52, Valtype.i64),
969
+ [ Opcodes.i64_shr_u ],
970
+ number(0x7FF, Valtype.i64),
971
+ [ Opcodes.i64_and ],
972
+ [ Opcodes.i32_wrap_i64 ],
973
+ number(1023, Valtype.i32),
974
+ [ Opcodes.i32_sub ]
975
+ ]
976
+ };
977
+
978
+
1028
979
  PrecompiledBuiltins.BuiltinFuncs.call(this);
1029
980
  };
@@ -1,6 +1,6 @@
1
1
  import { Blocktype, Opcodes, PageSize, Valtype } from './wasmSpec.js';
2
2
  import { TYPES } from './types.js';
3
- import { number } from './embedding.js';
3
+ import { number } from './encoding.js';
4
4
 
5
5
  export default function({ builtinFuncs }, Prefs) {
6
6
  const makePrefix = name => (name.startsWith('__') ? '' : '__') + name + '_';