porffor 0.2.0-6aff0fa → 0.2.0-767de65

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.
Files changed (50) hide show
  1. package/.vscode/launch.json +18 -0
  2. package/LICENSE +20 -20
  3. package/README.md +54 -41
  4. package/asur/index.js +624 -340
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +1 -1
  7. package/compiler/{sections.js → assemble.js} +57 -10
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +3 -5
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/escape.ts +141 -0
  14. package/compiler/builtins/int.ts +147 -0
  15. package/compiler/builtins/number.ts +527 -0
  16. package/compiler/builtins/porffor.d.ts +27 -8
  17. package/compiler/builtins/string.ts +1055 -0
  18. package/compiler/builtins/tostring.ts +45 -0
  19. package/compiler/builtins.js +405 -108
  20. package/compiler/{codeGen.js → codegen.js} +654 -246
  21. package/compiler/embedding.js +22 -22
  22. package/compiler/encoding.js +108 -10
  23. package/compiler/generated_builtins.js +682 -2
  24. package/compiler/index.js +16 -14
  25. package/compiler/log.js +2 -2
  26. package/compiler/opt.js +23 -22
  27. package/compiler/parse.js +31 -25
  28. package/compiler/precompile.js +19 -25
  29. package/compiler/prefs.js +2 -2
  30. package/compiler/prototype.js +2 -18
  31. package/compiler/types.js +37 -0
  32. package/compiler/wasmSpec.js +13 -1
  33. package/compiler/wrap.js +36 -42
  34. package/empty.js +0 -0
  35. package/hello.js +2 -0
  36. package/package.json +9 -5
  37. package/porf +2 -0
  38. package/rhemyn/compile.js +3 -2
  39. package/rhemyn/parse.js +323 -320
  40. package/rhemyn/test/parse.js +58 -58
  41. package/runner/compare.js +34 -34
  42. package/runner/debug.js +122 -0
  43. package/runner/index.js +26 -10
  44. package/runner/profiler.js +45 -26
  45. package/runner/repl.js +40 -7
  46. package/runner/sizes.js +37 -37
  47. package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
  48. package/runner/info.js +0 -89
  49. package/runner/transform.js +0 -15
  50. package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
10
  import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
11
12
 
12
13
  let globals = {};
13
14
  let globalInd = 0;
@@ -24,35 +25,37 @@ const debug = str => {
24
25
  const logChar = n => {
25
26
  code.push(...number(n));
26
27
 
27
- code.push(Opcodes.call);
28
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
29
29
  };
30
30
 
31
31
  for (let i = 0; i < str.length; i++) {
32
32
  logChar(str.charCodeAt(i));
33
33
  }
34
34
 
35
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
36
36
 
37
37
  return code;
38
38
  };
39
39
 
40
- const todo = msg => {
41
- class TodoError extends Error {
42
- constructor(message) {
43
- super(message);
44
- this.name = 'TodoError';
45
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
46
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
47
50
 
48
- throw new TodoError(`todo: ${msg}`);
49
-
50
- const code = [];
51
-
52
- code.push(...debug(`todo! ` + msg));
53
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
54
53
 
55
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
56
59
  };
57
60
 
58
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -105,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
108
  return generateUnary(scope, decl);
106
109
 
107
110
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
112
 
110
113
  case 'IfStatement':
111
114
  return generateIf(scope, decl);
@@ -116,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
119
  case 'WhileStatement':
117
120
  return generateWhile(scope, decl);
118
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
119
125
  case 'ForOfStatement':
120
126
  return generateForOf(scope, decl);
121
127
 
@@ -125,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
131
  case 'ContinueStatement':
126
132
  return generateContinue(scope, decl);
127
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
128
137
  case 'EmptyStatement':
129
138
  return generateEmpty(scope, decl);
130
139
 
@@ -138,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
147
  return generateTry(scope, decl);
139
148
 
140
149
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
142
151
  return [];
143
152
 
144
153
  case 'ArrayExpression':
@@ -152,10 +161,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
161
  const funcsBefore = funcs.length;
153
162
  generate(scope, decl.declaration);
154
163
 
155
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
156
171
 
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
159
174
 
160
175
  return [];
161
176
 
@@ -202,33 +217,42 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
202
217
  },
203
218
 
204
219
  __Porffor_bs: str => [
205
- ...makeString(scope, str, undefined, undefined, true),
220
+ ...makeString(scope, str, global, name, true),
206
221
 
207
- ...number(TYPES._bytestring, Valtype.i32),
208
- setLastType(scope)
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
209
226
  ],
210
227
  __Porffor_s: str => [
211
- ...makeString(scope, str, undefined, undefined, false),
228
+ ...makeString(scope, str, global, name, false),
212
229
 
213
- ...number(TYPES.string, Valtype.i32),
214
- setLastType(scope)
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
215
234
  ],
216
235
  };
217
236
 
218
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
219
238
  // hack for inline asm
220
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
240
 
222
241
  const { quasis, expressions } = decl.quasi;
223
242
  let str = quasis[0].value.raw;
224
243
 
225
244
  for (let i = 0; i < expressions.length; i++) {
226
245
  const e = expressions[i];
227
- str += lookupName(scope, e.name)[0].idx;
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
228
252
  str += quasis[i + 1].value.raw;
229
253
  }
230
254
 
231
- return funcs[name](str);
255
+ return funcs[func](str);
232
256
  }
233
257
 
234
258
  default:
@@ -238,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
238
262
  return [];
239
263
  }
240
264
 
241
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
242
266
  }
243
267
  };
244
268
 
@@ -266,7 +290,7 @@ const lookupName = (scope, _name) => {
266
290
  return [ undefined, undefined ];
267
291
  };
268
292
 
269
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
270
294
  ...generateThrow(scope, {
271
295
  argument: {
272
296
  type: 'NewExpression',
@@ -293,7 +317,7 @@ const generateIdent = (scope, decl) => {
293
317
 
294
318
  let wasm = builtinVars[name];
295
319
  if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
- return wasm;
320
+ return wasm.slice();
297
321
  }
298
322
 
299
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -301,6 +325,11 @@ const generateIdent = (scope, decl) => {
301
325
  return number(1);
302
326
  }
303
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
304
333
  if (local?.idx === undefined) {
305
334
  // no local var with name
306
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -331,14 +360,18 @@ const generateReturn = (scope, decl) => {
331
360
  // just bare "return"
332
361
  return [
333
362
  ...number(UNDEFINED), // "undefined" if func returns
334
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
335
366
  [ Opcodes.return ]
336
367
  ];
337
368
  }
338
369
 
339
370
  return [
340
371
  ...generate(scope, decl.argument),
341
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
342
375
  [ Opcodes.return ]
343
376
  ];
344
377
  };
@@ -352,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
352
385
  return idx;
353
386
  };
354
387
 
355
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
356
390
 
357
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
358
392
  const checks = {
@@ -361,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
361
395
  '??': nullish
362
396
  };
363
397
 
364
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
365
399
 
366
400
  // generic structure for {a} OP {b}
367
401
  // -->
@@ -369,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
369
403
 
370
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
371
405
  // (like if we are in an if condition - very common)
372
- const leftIsInt = isIntOp(left[left.length - 1]);
373
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
374
408
 
375
409
  const canInt = leftIsInt && rightIsInt;
376
410
 
@@ -387,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
387
421
  ...right,
388
422
  // note type
389
423
  ...rightType,
390
- setLastType(scope),
424
+ ...setLastType(scope),
391
425
  [ Opcodes.else ],
392
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
393
427
  // note type
394
428
  ...leftType,
395
- setLastType(scope),
429
+ ...setLastType(scope),
396
430
  [ Opcodes.end ],
397
431
  Opcodes.i32_from
398
432
  ];
@@ -406,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
406
440
  ...right,
407
441
  // note type
408
442
  ...rightType,
409
- setLastType(scope),
443
+ ...setLastType(scope),
410
444
  [ Opcodes.else ],
411
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
412
446
  // note type
413
447
  ...leftType,
414
- setLastType(scope),
448
+ ...setLastType(scope),
415
449
  [ Opcodes.end ]
416
450
  ];
417
451
  };
418
452
 
419
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
420
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
421
455
  // todo: convert left and right to strings if not
422
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -427,7 +461,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
427
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
428
462
 
429
463
  if (assign) {
430
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
431
465
 
432
466
  return [
433
467
  // setup right
@@ -452,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
452
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
453
487
 
454
488
  // copy right
455
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
456
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
457
491
 
458
492
  [ Opcodes.local_get, leftLength ],
459
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
460
494
  [ Opcodes.i32_mul ],
461
495
  [ Opcodes.i32_add ],
462
496
 
@@ -465,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
465
499
  ...number(ValtypeSize.i32, Valtype.i32),
466
500
  [ Opcodes.i32_add ],
467
501
 
468
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
469
503
  [ Opcodes.local_get, rightLength ],
470
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
471
505
  [ Opcodes.i32_mul ],
472
506
 
473
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -525,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
525
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
526
560
 
527
561
  // copy right
528
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
529
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
530
564
 
531
565
  [ Opcodes.local_get, leftLength ],
532
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
533
567
  [ Opcodes.i32_mul ],
534
568
  [ Opcodes.i32_add ],
535
569
 
@@ -538,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
538
572
  ...number(ValtypeSize.i32, Valtype.i32),
539
573
  [ Opcodes.i32_add ],
540
574
 
541
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
542
576
  [ Opcodes.local_get, rightLength ],
543
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
544
578
  [ Opcodes.i32_mul ],
545
579
 
546
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -550,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
550
584
  ];
551
585
  };
552
586
 
553
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
554
588
  // todo: this should be rewritten into a func
555
589
  // todo: convert left and right to strings if not
556
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -559,7 +593,6 @@ const compareStrings = (scope, left, right) => {
559
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
560
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
561
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
562
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
563
596
 
564
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
565
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -587,7 +620,6 @@ const compareStrings = (scope, left, right) => {
587
620
 
588
621
  [ Opcodes.local_get, rightPointer ],
589
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
590
- [ Opcodes.local_tee, rightLength ],
591
623
 
592
624
  // fast path: check leftLength != rightLength
593
625
  [ Opcodes.i32_ne ],
@@ -602,11 +634,13 @@ const compareStrings = (scope, left, right) => {
602
634
  ...number(0, Valtype.i32),
603
635
  [ Opcodes.local_set, index ],
604
636
 
605
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
606
638
  // we do this instead of having to do mul/div each iter for perf™
607
639
  [ Opcodes.local_get, leftLength ],
608
- ...number(ValtypeSize.i16, Valtype.i32),
609
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
610
644
  [ Opcodes.local_set, indexEnd ],
611
645
 
612
646
  // iterate over each char and check if eq
@@ -616,13 +650,17 @@ const compareStrings = (scope, left, right) => {
616
650
  [ Opcodes.local_get, index ],
617
651
  [ Opcodes.local_get, leftPointer ],
618
652
  [ Opcodes.i32_add ],
619
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
620
656
 
621
657
  // fetch right
622
658
  [ Opcodes.local_get, index ],
623
659
  [ Opcodes.local_get, rightPointer ],
624
660
  [ Opcodes.i32_add ],
625
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
626
664
 
627
665
  // not equal, "return" false
628
666
  [ Opcodes.i32_ne ],
@@ -631,13 +669,13 @@ const compareStrings = (scope, left, right) => {
631
669
  [ Opcodes.br, 2 ],
632
670
  [ Opcodes.end ],
633
671
 
634
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
635
673
  [ Opcodes.local_get, index ],
636
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
637
675
  [ Opcodes.i32_add ],
638
676
  [ Opcodes.local_tee, index ],
639
677
 
640
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
641
679
  [ Opcodes.local_get, indexEnd ],
642
680
  [ Opcodes.i32_ne ],
643
681
  [ Opcodes.br_if, 0 ],
@@ -658,13 +696,14 @@ const compareStrings = (scope, left, right) => {
658
696
  };
659
697
 
660
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
661
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
662
700
  ...wasm,
663
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
664
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
665
704
 
666
705
  const useTmp = knownType(scope, type) == null;
667
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
668
707
 
669
708
  const def = [
670
709
  // if value != 0
@@ -716,7 +755,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
716
755
 
717
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
718
757
  const useTmp = knownType(scope, type) == null;
719
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
720
759
 
721
760
  return [
722
761
  ...wasm,
@@ -762,7 +801,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
762
801
 
763
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
764
803
  const useTmp = knownType(scope, type) == null;
765
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
766
805
 
767
806
  return [
768
807
  ...wasm,
@@ -856,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
856
895
  // todo: if equality op and an operand is undefined, return false
857
896
  // todo: niche null hell with 0
858
897
 
859
- // todo: this should be dynamic but for now only static
860
898
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
861
899
  if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
862
901
  // string concat (a + b)
863
- return concatStrings(scope, left, right, _global, _name, assign);
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
864
903
  }
865
904
 
866
905
  // not an equality op, NaN
@@ -883,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
883
922
  }
884
923
  }
885
924
 
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
951
+
886
952
  let ops = operatorOpcode[valtype][op];
887
953
 
888
954
  // some complex ops are implemented as builtin funcs
@@ -898,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
898
964
  ]);
899
965
  }
900
966
 
901
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
902
968
 
903
969
  if (!Array.isArray(ops)) ops = [ ops ];
904
970
  ops = [ ops ];
905
971
 
906
972
  let tmpLeft, tmpRight;
907
973
  // if equal op, check if strings for compareStrings
908
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
909
- // todo: intelligent partial skip later
910
- // if neither known are string, stop this madness
911
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
912
- return;
913
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
914
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
915
979
  tmpLeft = localTmp(scope, '__tmpop_left');
916
980
  tmpRight = localTmp(scope, '__tmpop_right');
917
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
918
1023
  ops.unshift(...stringOnly([
919
1024
  // if left is string
920
1025
  ...leftType,
@@ -926,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
926
1031
  ...number(TYPES.string, Valtype.i32),
927
1032
  [ Opcodes.i32_eq ],
928
1033
 
929
- // if either are true
930
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
931
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
932
1041
 
933
- // todo: convert non-strings to strings, for now fail immediately if one is not
934
- // if left is not string
1042
+ // if left is bytestring
935
1043
  ...leftType,
936
- ...number(TYPES.string, Valtype.i32),
937
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
938
1046
 
939
- // if right is not string
1047
+ // if right is bytestring
940
1048
  ...rightType,
941
- ...number(TYPES.string, Valtype.i32),
942
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
943
1051
 
944
- // if either are true
945
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
946
1054
  [ Opcodes.if, Blocktype.void ],
947
- ...number(0, Valtype.i32),
948
- [ Opcodes.br, 2 ],
949
- [ Opcodes.end ],
950
-
951
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
952
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
953
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
954
1057
  [ Opcodes.br, 1 ],
955
1058
  [ Opcodes.end ],
@@ -961,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
961
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
962
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
963
1066
  // }
964
- })();
1067
+ }
965
1068
 
966
1069
  return finalize([
967
1070
  ...left,
@@ -1037,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1037
1140
  params,
1038
1141
  locals,
1039
1142
  returns,
1040
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1041
1144
  wasm,
1042
1145
  internal: true,
1043
1146
  index: currentFuncIndex++
@@ -1060,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1060
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1061
1164
  };
1062
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1063
1167
  // T = JS type, V = value/pointer
1064
1168
  // 0bTTT
1065
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1083,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
1083
1187
  // 4: internal type
1084
1188
  // 5: pointer
1085
1189
 
1086
- const TYPES = {
1087
- number: 0x00,
1088
- boolean: 0x01,
1089
- string: 0x02,
1090
- undefined: 0x03,
1091
- object: 0x04,
1092
- function: 0x05,
1093
- symbol: 0x06,
1094
- bigint: 0x07,
1095
-
1096
- // these are not "typeof" types but tracked internally
1097
- _array: 0x10,
1098
- _regexp: 0x11,
1099
- _bytestring: 0x12
1100
- };
1101
-
1102
- const TYPE_NAMES = {
1103
- [TYPES.number]: 'Number',
1104
- [TYPES.boolean]: 'Boolean',
1105
- [TYPES.string]: 'String',
1106
- [TYPES.undefined]: 'undefined',
1107
- [TYPES.object]: 'Object',
1108
- [TYPES.function]: 'Function',
1109
- [TYPES.symbol]: 'Symbol',
1110
- [TYPES.bigint]: 'BigInt',
1111
-
1112
- [TYPES._array]: 'Array',
1113
- [TYPES._regexp]: 'RegExp',
1114
- [TYPES._bytestring]: 'ByteString'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1115
1195
  };
1116
1196
 
1117
1197
  const getType = (scope, _name) => {
1118
1198
  const name = mapName(_name);
1119
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1120
1202
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1121
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1122
1204
 
@@ -1124,11 +1206,10 @@ const getType = (scope, _name) => {
1124
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1125
1207
 
1126
1208
  let type = TYPES.undefined;
1127
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1128
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1129
1211
 
1130
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1131
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1132
1213
 
1133
1214
  return number(type, Valtype.i32);
1134
1215
  };
@@ -1151,15 +1232,16 @@ const setType = (scope, _name, type) => {
1151
1232
  ];
1152
1233
 
1153
1234
  // throw new Error('could not find var');
1235
+ return [];
1154
1236
  };
1155
1237
 
1156
1238
  const getLastType = scope => {
1157
1239
  scope.gotLastType = true;
1158
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1159
1241
  };
1160
1242
 
1161
1243
  const setLastType = scope => {
1162
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1163
1245
  };
1164
1246
 
1165
1247
  const getNodeType = (scope, node) => {
@@ -1184,7 +1266,7 @@ const getNodeType = (scope, node) => {
1184
1266
  const name = node.callee.name;
1185
1267
  if (!name) {
1186
1268
  // iife
1187
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1188
1270
 
1189
1271
  // presume
1190
1272
  // todo: warn here?
@@ -1198,7 +1280,7 @@ const getNodeType = (scope, node) => {
1198
1280
  if (func.returnType) return func.returnType;
1199
1281
  }
1200
1282
 
1201
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1283
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1202
1284
  if (internalConstrs[name]) return internalConstrs[name].type;
1203
1285
 
1204
1286
  // check if this is a prototype function
@@ -1218,7 +1300,7 @@ const getNodeType = (scope, node) => {
1218
1300
  return TYPES.number;
1219
1301
  }
1220
1302
 
1221
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1303
+ if (scope.locals['#last_type']) return getLastType(scope);
1222
1304
 
1223
1305
  // presume
1224
1306
  // todo: warn here?
@@ -1273,6 +1355,7 @@ const getNodeType = (scope, node) => {
1273
1355
 
1274
1356
  // todo: this should be dynamic but for now only static
1275
1357
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1358
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1276
1359
 
1277
1360
  return TYPES.number;
1278
1361
 
@@ -1309,15 +1392,21 @@ const getNodeType = (scope, node) => {
1309
1392
 
1310
1393
  // ts hack
1311
1394
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1395
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1312
1396
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1313
1397
 
1314
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1398
+ if (scope.locals['#last_type']) return getLastType(scope);
1315
1399
 
1316
1400
  // presume
1317
1401
  return TYPES.number;
1318
1402
  }
1319
1403
 
1320
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1404
+ if (node.type === 'TaggedTemplateExpression') {
1405
+ // hack
1406
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1407
+ }
1408
+
1409
+ if (scope.locals['#last_type']) return getLastType(scope);
1321
1410
 
1322
1411
  // presume
1323
1412
  // todo: warn here?
@@ -1350,7 +1439,7 @@ const generateLiteral = (scope, decl, global, name) => {
1350
1439
  return makeString(scope, decl.value, global, name);
1351
1440
 
1352
1441
  default:
1353
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1442
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1354
1443
  }
1355
1444
  };
1356
1445
 
@@ -1359,6 +1448,8 @@ const countLeftover = wasm => {
1359
1448
 
1360
1449
  for (let i = 0; i < wasm.length; i++) {
1361
1450
  const inst = wasm[i];
1451
+ if (inst[0] == null) continue;
1452
+
1362
1453
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1363
1454
  if (inst[0] === Opcodes.if) count--;
1364
1455
  if (inst[1] !== Blocktype.void) count++;
@@ -1470,10 +1561,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1470
1561
  name = func.name;
1471
1562
  }
1472
1563
 
1473
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1564
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1474
1565
  // literal eval hack
1475
- const code = decl.arguments[0].value;
1476
- const parsed = parse(code, []);
1566
+ const code = decl.arguments[0]?.value ?? '';
1567
+
1568
+ let parsed;
1569
+ try {
1570
+ parsed = parse(code, []);
1571
+ } catch (e) {
1572
+ if (e.name === 'SyntaxError') {
1573
+ // throw syntax errors of evals at runtime instead
1574
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1575
+ }
1576
+
1577
+ throw e;
1578
+ }
1477
1579
 
1478
1580
  const out = generate(scope, {
1479
1581
  type: 'BlockStatement',
@@ -1487,13 +1589,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1487
1589
  const finalStatement = parsed.body[parsed.body.length - 1];
1488
1590
  out.push(
1489
1591
  ...getNodeType(scope, finalStatement),
1490
- setLastType(scope)
1592
+ ...setLastType(scope)
1491
1593
  );
1492
1594
  } else if (countLeftover(out) === 0) {
1493
1595
  out.push(...number(UNDEFINED));
1494
1596
  out.push(
1495
1597
  ...number(TYPES.undefined, Valtype.i32),
1496
- setLastType(scope)
1598
+ ...setLastType(scope)
1497
1599
  );
1498
1600
  }
1499
1601
 
@@ -1515,6 +1617,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1617
 
1516
1618
  target = { ...decl.callee };
1517
1619
  target.name = spl.slice(0, -1).join('_');
1620
+
1621
+ // failed to lookup name, abort
1622
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1518
1623
  }
1519
1624
 
1520
1625
  // literal.func()
@@ -1537,7 +1642,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1537
1642
  Opcodes.i32_from_u,
1538
1643
 
1539
1644
  ...number(TYPES.boolean, Valtype.i32),
1540
- setLastType(scope)
1645
+ ...setLastType(scope)
1541
1646
  ];
1542
1647
  }
1543
1648
 
@@ -1562,12 +1667,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1562
1667
  // }
1563
1668
 
1564
1669
  if (protoName) {
1670
+ const protoBC = {};
1671
+
1672
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1673
+
1674
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1675
+ for (const x of builtinProtoCands) {
1676
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1677
+ if (type == null) continue;
1678
+
1679
+ protoBC[type] = generateCall(scope, {
1680
+ callee: {
1681
+ name: x
1682
+ },
1683
+ arguments: [ target, ...decl.arguments ],
1684
+ _protoInternalCall: true
1685
+ });
1686
+ }
1687
+ }
1688
+
1565
1689
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1566
1690
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1567
1691
  return acc;
1568
1692
  }, {});
1569
1693
 
1570
- // no prototype function candidates, ignore
1571
1694
  if (Object.keys(protoCands).length > 0) {
1572
1695
  // use local for cached i32 length as commonly used
1573
1696
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1585,7 +1708,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1585
1708
 
1586
1709
  let allOptUnused = true;
1587
1710
  let lengthI32CacheUsed = false;
1588
- const protoBC = {};
1589
1711
  for (const x in protoCands) {
1590
1712
  const protoFunc = protoCands[x];
1591
1713
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1593,7 +1715,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1593
1715
  ...RTArrayUtil.getLength(getPointer),
1594
1716
 
1595
1717
  ...number(TYPES.number, Valtype.i32),
1596
- setLastType(scope)
1718
+ ...setLastType(scope)
1597
1719
  ];
1598
1720
  continue;
1599
1721
  }
@@ -1630,7 +1752,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1630
1752
  ...protoOut,
1631
1753
 
1632
1754
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1633
- setLastType(scope),
1755
+ ...setLastType(scope),
1634
1756
  [ Opcodes.end ]
1635
1757
  ];
1636
1758
  }
@@ -1656,10 +1778,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1656
1778
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1657
1779
  ];
1658
1780
  }
1781
+
1782
+ if (Object.keys(protoBC).length > 0) {
1783
+ return typeSwitch(scope, getNodeType(scope, target), {
1784
+ ...protoBC,
1785
+
1786
+ // TODO: error better
1787
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1788
+ }, valtypeBinary);
1789
+ }
1659
1790
  }
1660
1791
 
1661
1792
  // TODO: only allows callee as literal
1662
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1793
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1663
1794
 
1664
1795
  let idx = funcIndex[name] ?? importedFuncs[name];
1665
1796
  if (idx === undefined && builtinFuncs[name]) {
@@ -1695,9 +1826,28 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1826
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1696
1827
  const wasmOps = {
1697
1828
  // pointer, align, offset
1698
- i32_load8_u: { imms: 2, args: 1 },
1829
+ i32_load: { imms: 2, args: 1, returns: 1 },
1830
+ // pointer, value, align, offset
1831
+ i32_store: { imms: 2, args: 2, returns: 0 },
1832
+ // pointer, align, offset
1833
+ i32_load8_u: { imms: 2, args: 1, returns: 1 },
1699
1834
  // pointer, value, align, offset
1700
- i32_store8: { imms: 2, args: 2 },
1835
+ i32_store8: { imms: 2, args: 2, returns: 0 },
1836
+ // pointer, align, offset
1837
+ i32_load16_u: { imms: 2, args: 1, returns: 1 },
1838
+ // pointer, value, align, offset
1839
+ i32_store16: { imms: 2, args: 2, returns: 0 },
1840
+
1841
+ // pointer, align, offset
1842
+ f64_load: { imms: 2, args: 1, returns: 1 },
1843
+ // pointer, value, align, offset
1844
+ f64_store: { imms: 2, args: 2, returns: 0 },
1845
+
1846
+ // value
1847
+ i32_const: { imms: 1, args: 0, returns: 1 },
1848
+
1849
+ // a, b
1850
+ i32_or: { imms: 0, args: 2, returns: 1 },
1701
1851
  };
1702
1852
 
1703
1853
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1706,28 +1856,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1706
1856
  const op = wasmOps[opName];
1707
1857
 
1708
1858
  const argOut = [];
1709
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1859
+ for (let i = 0; i < op.args; i++) argOut.push(
1860
+ ...generate(scope, decl.arguments[i]),
1861
+ Opcodes.i32_to
1862
+ );
1710
1863
 
1711
1864
  // literals only
1712
1865
  const imms = decl.arguments.slice(op.args).map(x => x.value);
1713
1866
 
1714
1867
  return [
1715
1868
  ...argOut,
1716
- [ Opcodes[opName], ...imms ]
1869
+ [ Opcodes[opName], ...imms ],
1870
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1717
1871
  ];
1718
1872
  }
1719
1873
  }
1720
1874
 
1721
1875
  if (idx === undefined) {
1722
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1723
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1876
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1877
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1724
1878
  }
1725
1879
 
1726
1880
  const func = funcs.find(x => x.index === idx);
1727
1881
 
1728
1882
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1729
1883
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1730
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1884
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1731
1885
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1732
1886
 
1733
1887
  let args = decl.arguments;
@@ -1748,7 +1902,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1748
1902
  const arg = args[i];
1749
1903
  out = out.concat(generate(scope, arg));
1750
1904
 
1751
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1905
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1906
+ out.push(Opcodes.i32_to);
1907
+ }
1908
+
1909
+ if (importedFuncs[name] && name.startsWith('profile')) {
1752
1910
  out.push(Opcodes.i32_to);
1753
1911
  }
1754
1912
 
@@ -1767,9 +1925,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1767
1925
  // ...number(type, Valtype.i32),
1768
1926
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1769
1927
  // );
1770
- } else out.push(setLastType(scope));
1928
+ } else out.push(...setLastType(scope));
1771
1929
 
1772
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1930
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
1931
  out.push(Opcodes.i32_from);
1774
1932
  }
1775
1933
 
@@ -1780,7 +1938,7 @@ const generateNew = (scope, decl, _global, _name) => {
1780
1938
  // hack: basically treat this as a normal call for builtins for now
1781
1939
  const name = mapName(decl.callee.name);
1782
1940
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1783
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1941
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1784
1942
 
1785
1943
  return generateCall(scope, decl, _global, _name);
1786
1944
  };
@@ -1914,8 +2072,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1914
2072
  [ Opcodes.block, returns ]
1915
2073
  ];
1916
2074
 
1917
- // todo: use br_table?
1918
-
1919
2075
  for (const x in bc) {
1920
2076
  if (x === 'default') continue;
1921
2077
 
@@ -1971,12 +2127,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1971
2127
  };
1972
2128
 
1973
2129
  const typeAnnoToPorfType = x => {
1974
- if (TYPES[x]) return TYPES[x];
1975
- if (TYPES['_' + x]) return TYPES['_' + x];
2130
+ if (!x) return null;
2131
+ if (TYPES[x] != null) return TYPES[x];
2132
+ if (TYPES['_' + x] != null) return TYPES['_' + x];
1976
2133
 
1977
2134
  switch (x) {
1978
2135
  case 'i32':
1979
2136
  case 'i64':
2137
+ case 'f64':
1980
2138
  return TYPES.number;
1981
2139
  }
1982
2140
 
@@ -1987,7 +2145,7 @@ const extractTypeAnnotation = decl => {
1987
2145
  let a = decl;
1988
2146
  while (a.typeAnnotation) a = a.typeAnnotation;
1989
2147
 
1990
- let type, elementType;
2148
+ let type = null, elementType = null;
1991
2149
  if (a.typeName) {
1992
2150
  type = a.typeName.name;
1993
2151
  } else if (a.type.endsWith('Keyword')) {
@@ -2018,7 +2176,7 @@ const generateVar = (scope, decl) => {
2018
2176
  for (const x of decl.declarations) {
2019
2177
  const name = mapName(x.id.name);
2020
2178
 
2021
- if (!name) return todo('destructuring is not supported yet');
2179
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2022
2180
 
2023
2181
  if (x.init && isFuncType(x.init.type)) {
2024
2182
  // hack for let a = function () { ... }
@@ -2035,9 +2193,10 @@ const generateVar = (scope, decl) => {
2035
2193
  continue; // always ignore
2036
2194
  }
2037
2195
 
2038
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2196
+ const typed = typedInput && x.id.typeAnnotation;
2197
+ let idx = allocVar(scope, name, global, !typed);
2039
2198
 
2040
- if (typedInput && x.id.typeAnnotation) {
2199
+ if (typed) {
2041
2200
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2042
2201
  }
2043
2202
 
@@ -2055,7 +2214,8 @@ const generateVar = (scope, decl) => {
2055
2214
  return out;
2056
2215
  };
2057
2216
 
2058
- const generateAssign = (scope, decl) => {
2217
+ // todo: optimize this func for valueUnused
2218
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2059
2219
  const { type, name } = decl.left;
2060
2220
 
2061
2221
  if (type === 'ObjectPattern') {
@@ -2073,9 +2233,9 @@ const generateAssign = (scope, decl) => {
2073
2233
  // hack: .length setter
2074
2234
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2075
2235
  const name = decl.left.object.name;
2076
- const pointer = arrays.get(name);
2236
+ const pointer = scope.arrays?.get(name);
2077
2237
 
2078
- const aotPointer = pointer != null;
2238
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2079
2239
 
2080
2240
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2081
2241
 
@@ -2100,9 +2260,9 @@ const generateAssign = (scope, decl) => {
2100
2260
  // arr[i]
2101
2261
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2102
2262
  const name = decl.left.object.name;
2103
- const pointer = arrays.get(name);
2263
+ const pointer = scope.arrays?.get(name);
2104
2264
 
2105
- const aotPointer = pointer != null;
2265
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2106
2266
 
2107
2267
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2108
2268
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2158,7 +2318,7 @@ const generateAssign = (scope, decl) => {
2158
2318
  ];
2159
2319
  }
2160
2320
 
2161
- if (!name) return todo('destructuring is not supported yet');
2321
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2162
2322
 
2163
2323
  const [ local, isGlobal ] = lookupName(scope, name);
2164
2324
 
@@ -2263,7 +2423,7 @@ const generateUnary = (scope, decl) => {
2263
2423
  return out;
2264
2424
  }
2265
2425
 
2266
- case 'delete':
2426
+ case 'delete': {
2267
2427
  let toReturn = true, toGenerate = true;
2268
2428
 
2269
2429
  if (decl.argument.type === 'Identifier') {
@@ -2285,9 +2445,26 @@ const generateUnary = (scope, decl) => {
2285
2445
 
2286
2446
  out.push(...number(toReturn ? 1 : 0));
2287
2447
  return out;
2448
+ }
2449
+
2450
+ case 'typeof': {
2451
+ let overrideType, toGenerate = true;
2452
+
2453
+ if (decl.argument.type === 'Identifier') {
2454
+ const out = generateIdent(scope, decl.argument);
2288
2455
 
2289
- case 'typeof':
2290
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2456
+ // if ReferenceError (undeclared var), ignore and return undefined
2457
+ if (out[1]) {
2458
+ // does not exist (2 ops from throw)
2459
+ overrideType = number(TYPES.undefined, Valtype.i32);
2460
+ toGenerate = false;
2461
+ }
2462
+ }
2463
+
2464
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2465
+ disposeLeftover(out);
2466
+
2467
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2291
2468
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2292
2469
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2293
2470
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2298,27 +2475,30 @@ const generateUnary = (scope, decl) => {
2298
2475
 
2299
2476
  // object and internal types
2300
2477
  default: makeString(scope, 'object', false, '#typeof_result'),
2301
- });
2478
+ }));
2479
+
2480
+ return out;
2481
+ }
2302
2482
 
2303
2483
  default:
2304
- return todo(`unary operator ${decl.operator} not implemented yet`);
2484
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2305
2485
  }
2306
2486
  };
2307
2487
 
2308
- const generateUpdate = (scope, decl) => {
2488
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2309
2489
  const { name } = decl.argument;
2310
2490
 
2311
2491
  const [ local, isGlobal ] = lookupName(scope, name);
2312
2492
 
2313
2493
  if (local === undefined) {
2314
- return todo(`update expression with undefined variable`);
2494
+ return todo(scope, `update expression with undefined variable`, true);
2315
2495
  }
2316
2496
 
2317
2497
  const idx = local.idx;
2318
2498
  const out = [];
2319
2499
 
2320
2500
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2321
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2501
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2322
2502
 
2323
2503
  switch (decl.operator) {
2324
2504
  case '++':
@@ -2331,7 +2511,7 @@ const generateUpdate = (scope, decl) => {
2331
2511
  }
2332
2512
 
2333
2513
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2334
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2514
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2335
2515
 
2336
2516
  return out;
2337
2517
  };
@@ -2371,7 +2551,7 @@ const generateConditional = (scope, decl) => {
2371
2551
  // note type
2372
2552
  out.push(
2373
2553
  ...getNodeType(scope, decl.consequent),
2374
- setLastType(scope)
2554
+ ...setLastType(scope)
2375
2555
  );
2376
2556
 
2377
2557
  out.push([ Opcodes.else ]);
@@ -2380,7 +2560,7 @@ const generateConditional = (scope, decl) => {
2380
2560
  // note type
2381
2561
  out.push(
2382
2562
  ...getNodeType(scope, decl.alternate),
2383
- setLastType(scope)
2563
+ ...setLastType(scope)
2384
2564
  );
2385
2565
 
2386
2566
  out.push([ Opcodes.end ]);
@@ -2394,7 +2574,7 @@ const generateFor = (scope, decl) => {
2394
2574
  const out = [];
2395
2575
 
2396
2576
  if (decl.init) {
2397
- out.push(...generate(scope, decl.init));
2577
+ out.push(...generate(scope, decl.init, false, undefined, true));
2398
2578
  disposeLeftover(out);
2399
2579
  }
2400
2580
 
@@ -2412,7 +2592,7 @@ const generateFor = (scope, decl) => {
2412
2592
  out.push(...generate(scope, decl.body));
2413
2593
  out.push([ Opcodes.end ]);
2414
2594
 
2415
- if (decl.update) out.push(...generate(scope, decl.update));
2595
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2416
2596
 
2417
2597
  out.push([ Opcodes.br, 1 ]);
2418
2598
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2440,6 +2620,36 @@ const generateWhile = (scope, decl) => {
2440
2620
  return out;
2441
2621
  };
2442
2622
 
2623
+ const generateDoWhile = (scope, decl) => {
2624
+ const out = [];
2625
+
2626
+ out.push([ Opcodes.loop, Blocktype.void ]);
2627
+ depth.push('dowhile');
2628
+
2629
+ // block for break (includes all)
2630
+ out.push([ Opcodes.block, Blocktype.void ]);
2631
+ depth.push('block');
2632
+
2633
+ // block for continue
2634
+ // includes body but not test+loop so we can exit body at anytime
2635
+ // and still test+loop after
2636
+ out.push([ Opcodes.block, Blocktype.void ]);
2637
+ depth.push('block');
2638
+
2639
+ out.push(...generate(scope, decl.body));
2640
+
2641
+ out.push([ Opcodes.end ]);
2642
+ depth.pop();
2643
+
2644
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2645
+ out.push([ Opcodes.br_if, 1 ]);
2646
+
2647
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2648
+ depth.pop(); depth.pop();
2649
+
2650
+ return out;
2651
+ };
2652
+
2443
2653
  const generateForOf = (scope, decl) => {
2444
2654
  const out = [];
2445
2655
 
@@ -2476,7 +2686,10 @@ const generateForOf = (scope, decl) => {
2476
2686
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2477
2687
  }
2478
2688
 
2689
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2690
+
2479
2691
  const [ local, isGlobal ] = lookupName(scope, leftName);
2692
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2480
2693
 
2481
2694
  depth.push('block');
2482
2695
  depth.push('block');
@@ -2485,6 +2698,7 @@ const generateForOf = (scope, decl) => {
2485
2698
  // hack: this is naughty and will break things!
2486
2699
  let newOut = number(0, Valtype.f64), newPointer = -1;
2487
2700
  if (pages.hasAnyString) {
2701
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2488
2702
  0, [ newOut, newPointer ] = makeArray(scope, {
2489
2703
  rawElements: new Array(1)
2490
2704
  }, isGlobal, leftName, true, 'i16');
@@ -2576,6 +2790,56 @@ const generateForOf = (scope, decl) => {
2576
2790
  [ Opcodes.end ],
2577
2791
  [ Opcodes.end ]
2578
2792
  ],
2793
+ [TYPES._bytestring]: [
2794
+ ...setType(scope, leftName, TYPES._bytestring),
2795
+
2796
+ [ Opcodes.loop, Blocktype.void ],
2797
+
2798
+ // setup new/out array
2799
+ ...newOut,
2800
+ [ Opcodes.drop ],
2801
+
2802
+ ...number(0, Valtype.i32), // base 0 for store after
2803
+
2804
+ // load current string ind {arg}
2805
+ [ Opcodes.local_get, pointer ],
2806
+ [ Opcodes.local_get, counter ],
2807
+ [ Opcodes.i32_add ],
2808
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2809
+
2810
+ // store to new string ind 0
2811
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2812
+
2813
+ // return new string (page)
2814
+ ...number(newPointer),
2815
+
2816
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2817
+
2818
+ [ Opcodes.block, Blocktype.void ],
2819
+ [ Opcodes.block, Blocktype.void ],
2820
+ ...generate(scope, decl.body),
2821
+ [ Opcodes.end ],
2822
+
2823
+ // increment iter pointer
2824
+ // [ Opcodes.local_get, pointer ],
2825
+ // ...number(1, Valtype.i32),
2826
+ // [ Opcodes.i32_add ],
2827
+ // [ Opcodes.local_set, pointer ],
2828
+
2829
+ // increment counter by 1
2830
+ [ Opcodes.local_get, counter ],
2831
+ ...number(1, Valtype.i32),
2832
+ [ Opcodes.i32_add ],
2833
+ [ Opcodes.local_tee, counter ],
2834
+
2835
+ // loop if counter != length
2836
+ [ Opcodes.local_get, length ],
2837
+ [ Opcodes.i32_ne ],
2838
+ [ Opcodes.br_if, 1 ],
2839
+
2840
+ [ Opcodes.end ],
2841
+ [ Opcodes.end ]
2842
+ ],
2579
2843
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2580
2844
  }, Blocktype.void));
2581
2845
 
@@ -2586,28 +2850,65 @@ const generateForOf = (scope, decl) => {
2586
2850
  return out;
2587
2851
  };
2588
2852
 
2853
+ // find the nearest loop in depth map by type
2589
2854
  const getNearestLoop = () => {
2590
2855
  for (let i = depth.length - 1; i >= 0; i--) {
2591
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2856
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2592
2857
  }
2593
2858
 
2594
2859
  return -1;
2595
2860
  };
2596
2861
 
2597
2862
  const generateBreak = (scope, decl) => {
2598
- const nearestLoop = depth.length - getNearestLoop();
2863
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2864
+ const type = depth[target];
2865
+
2866
+ // different loop types have different branch offsets
2867
+ // as they have different wasm block/loop/if structures
2868
+ // we need to use the right offset by type to branch to the one we want
2869
+ // for a break: exit the loop without executing anything else inside it
2870
+ const offset = ({
2871
+ for: 2, // loop > if (wanted branch) > block (we are here)
2872
+ while: 2, // loop > if (wanted branch) (we are here)
2873
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2874
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2875
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2876
+ })[type];
2877
+
2599
2878
  return [
2600
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2879
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2601
2880
  ];
2602
2881
  };
2603
2882
 
2604
2883
  const generateContinue = (scope, decl) => {
2605
- const nearestLoop = depth.length - getNearestLoop();
2884
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2885
+ const type = depth[target];
2886
+
2887
+ // different loop types have different branch offsets
2888
+ // as they have different wasm block/loop/if structures
2889
+ // we need to use the right offset by type to branch to the one we want
2890
+ // for a continue: do test for the loop, and then loop depending on that success
2891
+ const offset = ({
2892
+ for: 3, // loop (wanted branch) > if > block (we are here)
2893
+ while: 1, // loop (wanted branch) > if (we are here)
2894
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2895
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2896
+ })[type];
2897
+
2606
2898
  return [
2607
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2899
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2608
2900
  ];
2609
2901
  };
2610
2902
 
2903
+ const generateLabel = (scope, decl) => {
2904
+ scope.labels ??= new Map();
2905
+
2906
+ const name = decl.label.name;
2907
+ scope.labels.set(name, depth.length);
2908
+
2909
+ return generate(scope, decl.body);
2910
+ };
2911
+
2611
2912
  const generateThrow = (scope, decl) => {
2612
2913
  scope.throws = true;
2613
2914
 
@@ -2640,7 +2941,7 @@ const generateThrow = (scope, decl) => {
2640
2941
  };
2641
2942
 
2642
2943
  const generateTry = (scope, decl) => {
2643
- if (decl.finalizer) return todo('try finally not implemented yet');
2944
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2644
2945
 
2645
2946
  const out = [];
2646
2947
 
@@ -2671,7 +2972,7 @@ const generateAssignPat = (scope, decl) => {
2671
2972
  // TODO
2672
2973
  // if identifier declared, use that
2673
2974
  // else, use default (right)
2674
- return todo('assignment pattern (optional arg)');
2975
+ return todo(scope, 'assignment pattern (optional arg)');
2675
2976
  };
2676
2977
 
2677
2978
  let pages = new Map();
@@ -2750,16 +3051,20 @@ const getAllocType = itemType => {
2750
3051
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2751
3052
  const out = [];
2752
3053
 
3054
+ scope.arrays ??= new Map();
3055
+
2753
3056
  let firstAssign = false;
2754
- if (!arrays.has(name) || name === '$undeclared') {
3057
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2755
3058
  firstAssign = true;
2756
3059
 
2757
3060
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2758
3061
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2759
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3062
+
3063
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3064
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2760
3065
  }
2761
3066
 
2762
- const pointer = arrays.get(name);
3067
+ const pointer = scope.arrays.get(name);
2763
3068
 
2764
3069
  const useRawElements = !!decl.rawElements;
2765
3070
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2845,20 +3150,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2845
3150
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2846
3151
  };
2847
3152
 
2848
- let arrays = new Map();
2849
3153
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2850
3154
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2851
3155
  };
2852
3156
 
2853
3157
  export const generateMember = (scope, decl, _global, _name) => {
2854
3158
  const name = decl.object.name;
2855
- const pointer = arrays.get(name);
3159
+ const pointer = scope.arrays?.get(name);
2856
3160
 
2857
- const aotPointer = pointer != null;
3161
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2858
3162
 
2859
3163
  // hack: .length
2860
3164
  if (decl.property.name === 'length') {
2861
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3165
+ const func = funcs.find(x => x.name === name);
3166
+ if (func) {
3167
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3168
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3169
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3170
+ }
3171
+
3172
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3173
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3174
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3175
+
2862
3176
  return [
2863
3177
  ...(aotPointer ? number(0, Valtype.i32) : [
2864
3178
  ...generate(scope, decl.object),
@@ -2902,7 +3216,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2902
3216
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2903
3217
 
2904
3218
  ...number(TYPES.number, Valtype.i32),
2905
- setLastType(scope)
3219
+ ...setLastType(scope)
2906
3220
  ],
2907
3221
 
2908
3222
  [TYPES.string]: [
@@ -2934,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2934
3248
  ...number(newPointer),
2935
3249
 
2936
3250
  ...number(TYPES.string, Valtype.i32),
2937
- setLastType(scope)
3251
+ ...setLastType(scope)
2938
3252
  ],
2939
3253
  [TYPES._bytestring]: [
2940
3254
  // setup new/out array
@@ -2953,19 +3267,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2953
3267
  ]),
2954
3268
 
2955
3269
  // load current string ind {arg}
2956
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3270
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2957
3271
 
2958
3272
  // store to new string ind 0
2959
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3273
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2960
3274
 
2961
3275
  // return new string (page)
2962
3276
  ...number(newPointer),
2963
3277
 
2964
3278
  ...number(TYPES._bytestring, Valtype.i32),
2965
- setLastType(scope)
3279
+ ...setLastType(scope)
2966
3280
  ],
2967
3281
 
2968
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3282
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2969
3283
  });
2970
3284
  };
2971
3285
 
@@ -2975,28 +3289,36 @@ const objectHack = node => {
2975
3289
  if (!node) return node;
2976
3290
 
2977
3291
  if (node.type === 'MemberExpression') {
2978
- if (node.computed || node.optional) return node;
3292
+ const out = (() => {
3293
+ if (node.computed || node.optional) return;
2979
3294
 
2980
- let objectName = node.object.name;
3295
+ let objectName = node.object.name;
2981
3296
 
2982
- // if object is not identifier or another member exp, give up
2983
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3297
+ // if object is not identifier or another member exp, give up
3298
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3299
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2984
3300
 
2985
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3301
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2986
3302
 
2987
- // if .length, give up (hack within a hack!)
2988
- if (node.property.name === 'length') return node;
3303
+ // if .length, give up (hack within a hack!)
3304
+ if (node.property.name === 'length') {
3305
+ node.object = objectHack(node.object);
3306
+ return;
3307
+ }
2989
3308
 
2990
- // no object name, give up
2991
- if (!objectName) return node;
3309
+ // no object name, give up
3310
+ if (!objectName) return;
2992
3311
 
2993
- const name = '__' + objectName + '_' + node.property.name;
2994
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3312
+ const name = '__' + objectName + '_' + node.property.name;
3313
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2995
3314
 
2996
- return {
2997
- type: 'Identifier',
2998
- name
2999
- };
3315
+ return {
3316
+ type: 'Identifier',
3317
+ name
3318
+ };
3319
+ })();
3320
+
3321
+ if (out) return out;
3000
3322
  }
3001
3323
 
3002
3324
  for (const x in node) {
@@ -3010,8 +3332,8 @@ const objectHack = node => {
3010
3332
  };
3011
3333
 
3012
3334
  const generateFunc = (scope, decl) => {
3013
- if (decl.async) return todo('async functions are not supported');
3014
- if (decl.generator) return todo('generator functions are not supported');
3335
+ if (decl.async) return todo(scope, 'async functions are not supported');
3336
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3015
3337
 
3016
3338
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3017
3339
  const params = decl.params ?? [];
@@ -3027,6 +3349,11 @@ const generateFunc = (scope, decl) => {
3027
3349
  name
3028
3350
  };
3029
3351
 
3352
+ if (typedInput && decl.returnType) {
3353
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3354
+ innerScope.returns = [ valtypeBinary ];
3355
+ }
3356
+
3030
3357
  for (let i = 0; i < params.length; i++) {
3031
3358
  allocVar(innerScope, params[i].name, false);
3032
3359
 
@@ -3089,16 +3416,6 @@ const generateCode = (scope, decl) => {
3089
3416
  };
3090
3417
 
3091
3418
  const internalConstrs = {
3092
- Boolean: {
3093
- generate: (scope, decl) => {
3094
- if (decl.arguments.length === 0) return number(0);
3095
-
3096
- // should generate/run all args
3097
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3098
- },
3099
- type: TYPES.boolean
3100
- },
3101
-
3102
3419
  Array: {
3103
3420
  generate: (scope, decl, global, name) => {
3104
3421
  // new Array(i0, i1, ...)
@@ -3116,7 +3433,7 @@ const internalConstrs = {
3116
3433
 
3117
3434
  // todo: check in wasm instead of here
3118
3435
  const literalValue = arg.value ?? 0;
3119
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3436
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3120
3437
 
3121
3438
  return [
3122
3439
  ...number(0, Valtype.i32),
@@ -3127,7 +3444,8 @@ const internalConstrs = {
3127
3444
  ...number(pointer)
3128
3445
  ];
3129
3446
  },
3130
- type: TYPES._array
3447
+ type: TYPES._array,
3448
+ length: 1
3131
3449
  },
3132
3450
 
3133
3451
  __Array_of: {
@@ -3139,7 +3457,94 @@ const internalConstrs = {
3139
3457
  }, global, name);
3140
3458
  },
3141
3459
  type: TYPES._array,
3460
+ notConstr: true,
3461
+ length: 0
3462
+ },
3463
+
3464
+ __Porffor_fastOr: {
3465
+ generate: (scope, decl) => {
3466
+ const out = [];
3467
+
3468
+ for (let i = 0; i < decl.arguments.length; i++) {
3469
+ out.push(
3470
+ ...generate(scope, decl.arguments[i]),
3471
+ Opcodes.i32_to_u,
3472
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3473
+ );
3474
+ }
3475
+
3476
+ return out;
3477
+ },
3478
+ type: TYPES.boolean,
3479
+ notConstr: true
3480
+ },
3481
+
3482
+ __Porffor_fastAnd: {
3483
+ generate: (scope, decl) => {
3484
+ const out = [];
3485
+
3486
+ for (let i = 0; i < decl.arguments.length; i++) {
3487
+ out.push(
3488
+ ...generate(scope, decl.arguments[i]),
3489
+ Opcodes.i32_to_u,
3490
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3491
+ );
3492
+ }
3493
+
3494
+ return out;
3495
+ },
3496
+ type: TYPES.boolean,
3142
3497
  notConstr: true
3498
+ },
3499
+
3500
+ Boolean: {
3501
+ generate: (scope, decl) => {
3502
+ // todo: boolean object when used as constructor
3503
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3504
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3505
+ },
3506
+ type: TYPES.boolean,
3507
+ length: 1
3508
+ },
3509
+
3510
+ __Math_max: {
3511
+ generate: (scope, decl) => {
3512
+ const out = [
3513
+ ...number(-Infinity)
3514
+ ];
3515
+
3516
+ for (let i = 0; i < decl.arguments.length; i++) {
3517
+ out.push(
3518
+ ...generate(scope, decl.arguments[i]),
3519
+ [ Opcodes.f64_max ]
3520
+ );
3521
+ }
3522
+
3523
+ return out;
3524
+ },
3525
+ type: TYPES.number,
3526
+ notConstr: true,
3527
+ length: 2
3528
+ },
3529
+
3530
+ __Math_min: {
3531
+ generate: (scope, decl) => {
3532
+ const out = [
3533
+ ...number(Infinity)
3534
+ ];
3535
+
3536
+ for (let i = 0; i < decl.arguments.length; i++) {
3537
+ out.push(
3538
+ ...generate(scope, decl.arguments[i]),
3539
+ [ Opcodes.f64_min ]
3540
+ );
3541
+ }
3542
+
3543
+ return out;
3544
+ },
3545
+ type: TYPES.number,
3546
+ notConstr: true,
3547
+ length: 2
3143
3548
  }
3144
3549
  };
3145
3550
 
@@ -3168,7 +3573,6 @@ export default program => {
3168
3573
  funcs = [];
3169
3574
  funcIndex = {};
3170
3575
  depth = [];
3171
- arrays = new Map();
3172
3576
  pages = new Map();
3173
3577
  data = [];
3174
3578
  currentFuncIndex = importedFuncs.length;
@@ -3182,6 +3586,10 @@ export default program => {
3182
3586
 
3183
3587
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3184
3588
 
3589
+ globalThis.pageSize = PageSize;
3590
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3591
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3592
+
3185
3593
  // set generic opcodes for current valtype
3186
3594
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3187
3595
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3190,10 +3598,10 @@ export default program => {
3190
3598
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3191
3599
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3192
3600
 
3193
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3194
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3195
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3196
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3601
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3602
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3603
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3604
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3197
3605
 
3198
3606
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3199
3607
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3206,10 +3614,6 @@ export default program => {
3206
3614
 
3207
3615
  program.id = { name: 'main' };
3208
3616
 
3209
- globalThis.pageSize = PageSize;
3210
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3211
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3212
-
3213
3617
  const scope = {
3214
3618
  locals: {},
3215
3619
  localInd: 0
@@ -3220,7 +3624,7 @@ export default program => {
3220
3624
  body: program.body
3221
3625
  };
3222
3626
 
3223
- if (Prefs.astLog) console.log(program.body.body);
3627
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3224
3628
 
3225
3629
  generateFunc(scope, program);
3226
3630
 
@@ -3237,7 +3641,11 @@ export default program => {
3237
3641
  }
3238
3642
 
3239
3643
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3240
- main.returns = [];
3644
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3645
+ main.wasm.splice(main.wasm.length - 1, 1);
3646
+ } else {
3647
+ main.returns = [];
3648
+ }
3241
3649
  }
3242
3650
 
3243
3651
  if (lastInst[0] === Opcodes.call) {