porffor 0.2.0-fdf0fc5 → 0.14.0-032e4ad08

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 (48) hide show
  1. package/CONTRIBUTING.md +9 -3
  2. package/README.md +17 -14
  3. package/asur/index.js +1 -1
  4. package/byg/index.js +3 -24
  5. package/compiler/2c.js +66 -54
  6. package/compiler/assemble.js +51 -11
  7. package/compiler/builtins/annexb_string.js +10 -10
  8. package/compiler/builtins/annexb_string.ts +4 -3
  9. package/compiler/builtins/array.ts +85 -9
  10. package/compiler/builtins/base64.ts +2 -1
  11. package/compiler/builtins/boolean.ts +2 -2
  12. package/compiler/builtins/crypto.ts +2 -1
  13. package/compiler/builtins/date.ts +2 -3
  14. package/compiler/builtins/error.js +22 -0
  15. package/compiler/builtins/escape.ts +2 -3
  16. package/compiler/builtins/function.ts +1 -1
  17. package/compiler/builtins/int.ts +1 -1
  18. package/compiler/builtins/math.ts +410 -0
  19. package/compiler/builtins/number.ts +4 -7
  20. package/compiler/builtins/object.ts +1 -1
  21. package/compiler/builtins/porffor.d.ts +9 -8
  22. package/compiler/builtins/set.ts +197 -3
  23. package/compiler/builtins/string.ts +2 -1
  24. package/compiler/builtins/symbol.ts +62 -0
  25. package/compiler/builtins.js +41 -15
  26. package/compiler/codegen.js +653 -366
  27. package/compiler/decompile.js +3 -3
  28. package/compiler/embedding.js +2 -2
  29. package/compiler/encoding.js +0 -14
  30. package/compiler/expression.js +1 -1
  31. package/compiler/generated_builtins.js +771 -187
  32. package/compiler/index.js +5 -11
  33. package/compiler/opt.js +7 -7
  34. package/compiler/parse.js +2 -4
  35. package/compiler/precompile.js +18 -25
  36. package/compiler/prefs.js +6 -2
  37. package/compiler/prototype.js +185 -162
  38. package/compiler/wasmSpec.js +5 -0
  39. package/compiler/wrap.js +150 -90
  40. package/package.json +1 -1
  41. package/porffor_tmp.c +202 -0
  42. package/runner/compare.js +0 -1
  43. package/runner/debug.js +1 -6
  44. package/runner/index.js +5 -4
  45. package/runner/profiler.js +15 -42
  46. package/runner/repl.js +20 -10
  47. package/runner/sizes.js +2 -2
  48. package/runner/version.js +10 -8
@@ -1,14 +1,14 @@
1
- import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
- import { operatorOpcode } from "./expression.js";
4
- import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
- import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
- import { log } from "./log.js";
8
- import parse from "./parse.js";
9
- import * as Rhemyn from "../rhemyn/compile.js";
10
- import Prefs from './prefs.js';
1
+ import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
3
+ import { operatorOpcode } from './expression.js';
4
+ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
+ import { PrototypeFuncs } from './prototype.js';
6
+ import { number } from './embedding.js';
11
7
  import { TYPES, TYPE_NAMES } from './types.js';
8
+ import * as Rhemyn from '../rhemyn/compile.js';
9
+ import parse from './parse.js';
10
+ import { log } from './log.js';
11
+ import Prefs from './prefs.js';
12
12
 
13
13
  let globals = {};
14
14
  let globalInd = 0;
@@ -19,24 +19,6 @@ let funcIndex = {};
19
19
  let currentFuncIndex = importedFuncs.length;
20
20
  let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
21
21
 
22
- const debug = str => {
23
- const code = [];
24
-
25
- const logChar = n => {
26
- code.push(...number(n));
27
-
28
- code.push([ Opcodes.call, 0 ]);
29
- };
30
-
31
- for (let i = 0; i < str.length; i++) {
32
- logChar(str.charCodeAt(i));
33
- }
34
-
35
- logChar(10); // new line
36
-
37
- return code;
38
- };
39
-
40
22
  class TodoError extends Error {
41
23
  constructor(message) {
42
24
  super(message);
@@ -58,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
58
40
  }
59
41
  };
60
42
 
61
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
62
- const hasFuncWithName = name => {
63
- const func = funcs.find(x => x.name === name);
64
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
65
- };
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
66
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
67
49
  switch (decl.type) {
68
50
  case 'BinaryExpression':
@@ -76,10 +58,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
76
58
 
77
59
  case 'ArrowFunctionExpression':
78
60
  case 'FunctionDeclaration':
61
+ case 'FunctionExpression':
79
62
  const func = generateFunc(scope, decl);
80
63
 
81
64
  if (decl.type.endsWith('Expression')) {
82
- return number(func.index);
65
+ return number(func.index - importedFuncs.length);
83
66
  }
84
67
 
85
68
  return [];
@@ -157,24 +140,24 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
157
140
  case 'ArrayExpression':
158
141
  return generateArray(scope, decl, global, name);
159
142
 
143
+ case 'ObjectExpression':
144
+ return generateObject(scope, decl, global, name);
145
+
160
146
  case 'MemberExpression':
161
147
  return generateMember(scope, decl, global, name);
162
148
 
163
149
  case 'ExportNamedDeclaration':
164
- // hack to flag new func for export
165
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
166
151
  generate(scope, decl.declaration);
167
152
 
168
- if (funcsBefore !== funcs.length) {
169
- // new func added
170
- const newFunc = funcs[funcs.length - 1];
171
- newFunc.export = true;
172
- }
173
-
174
- // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
175
156
 
176
- // const newFunc = funcs[funcs.length - 1];
177
- // newFunc.export = true;
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
160
+ }
178
161
 
179
162
  return [];
180
163
 
@@ -210,7 +193,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
210
193
  if (!Array.isArray(inst)) inst = [ inst ];
211
194
  const immediates = asm.slice(1).map(x => {
212
195
  const int = parseInt(x);
213
- if (Number.isNaN(int)) return scope.locals[x]?.idx;
196
+ if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
214
197
  return int;
215
198
  });
216
199
 
@@ -222,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
222
205
 
223
206
  __Porffor_bs: str => [
224
207
  ...makeString(scope, str, global, name, true),
225
-
226
- ...(name ? setType(scope, name, TYPES.bytestring) : [
227
- ...number(TYPES.bytestring, Valtype.i32),
228
- ...setLastType(scope)
229
- ])
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
230
209
  ],
231
210
  __Porffor_s: str => [
232
211
  ...makeString(scope, str, global, name, false),
233
-
234
- ...(name ? setType(scope, name, TYPES.string) : [
235
- ...number(TYPES.string, Valtype.i32),
236
- ...setLastType(scope)
237
- ])
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
238
213
  ],
239
214
  };
240
215
 
@@ -336,10 +311,10 @@ const generateIdent = (scope, decl) => {
336
311
 
337
312
  if (local?.idx === undefined) {
338
313
  // no local var with name
339
- if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
340
- if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
341
-
342
314
  if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
315
+
316
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
317
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
343
318
  }
344
319
 
345
320
  if (local?.idx === undefined && rawName.startsWith('__')) {
@@ -373,9 +348,7 @@ const generateReturn = (scope, decl) => {
373
348
 
374
349
  return [
375
350
  ...generate(scope, decl.argument),
376
- ...(scope.returnType != null ? [] : [
377
- ...getNodeType(scope, decl.argument)
378
- ]),
351
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
379
352
  [ Opcodes.return ]
380
353
  ];
381
354
  };
@@ -390,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
390
363
  };
391
364
 
392
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
393
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
394
367
 
395
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
396
369
  const checks = {
@@ -407,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
407
380
 
408
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
409
382
  // (like if we are in an if condition - very common)
410
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
411
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
412
385
 
413
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
414
387
 
415
388
  if (canInt) {
416
389
  // remove int -> float conversions from left and right
@@ -424,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
424
397
  [ Opcodes.if, Valtype.i32 ],
425
398
  ...right,
426
399
  // note type
427
- ...rightType,
428
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
429
401
  [ Opcodes.else ],
430
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
431
403
  // note type
432
- ...leftType,
433
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
434
405
  [ Opcodes.end ],
435
406
  Opcodes.i32_from
436
407
  ];
@@ -443,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
443
414
  [ Opcodes.if, valtypeBinary ],
444
415
  ...right,
445
416
  // note type
446
- ...rightType,
447
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
448
418
  [ Opcodes.else ],
449
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
450
420
  // note type
451
- ...leftType,
452
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
453
422
  [ Opcodes.end ]
454
423
  ];
455
424
  };
@@ -477,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
477
446
  ...number(0, Valtype.i32), // base 0 for store later
478
447
 
479
448
  ...number(pointer, Valtype.i32),
480
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
481
450
  [ Opcodes.local_tee, leftLength ],
482
451
 
483
452
  [ Opcodes.local_get, rightPointer ],
484
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
485
454
  [ Opcodes.local_tee, rightLength ],
486
455
 
487
456
  [ Opcodes.i32_add ],
@@ -537,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
537
506
  ...number(0, Valtype.i32), // base 0 for store later
538
507
 
539
508
  [ Opcodes.local_get, leftPointer ],
540
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
541
510
  [ Opcodes.local_tee, leftLength ],
542
511
 
543
512
  [ Opcodes.local_get, rightPointer ],
544
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
545
514
  [ Opcodes.local_tee, rightLength ],
546
515
 
547
516
  [ Opcodes.i32_add ],
@@ -619,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
619
588
 
620
589
  // get lengths
621
590
  [ Opcodes.local_get, leftPointer ],
622
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
623
592
  [ Opcodes.local_tee, leftLength ],
624
593
 
625
594
  [ Opcodes.local_get, rightPointer ],
626
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
627
596
 
628
597
  // fast path: check leftLength != rightLength
629
598
  [ Opcodes.i32_ne ],
@@ -679,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
679
648
  [ Opcodes.i32_add ],
680
649
  [ Opcodes.local_tee, index ],
681
650
 
682
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
683
652
  [ Opcodes.local_get, indexEnd ],
684
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
685
654
  [ Opcodes.br_if, 0 ],
686
655
  [ Opcodes.end ],
687
656
 
@@ -699,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
699
668
  ];
700
669
  };
701
670
 
702
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
671
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
704
673
  ...wasm,
705
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
706
675
  ];
707
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
708
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
709
680
  const useTmp = knownType(scope, type) == null;
710
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
711
682
 
712
- const def = [
713
- // if value != 0
714
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
715
688
 
716
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
717
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
718
691
 
719
- /* Opcodes.eqz,
720
- [ Opcodes.i32_eqz ],
721
- Opcodes.i32_from */
722
- ];
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
723
709
 
724
710
  return [
725
711
  ...wasm,
726
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
727
713
 
728
714
  ...typeSwitch(scope, type, {
729
- // [TYPES.number]: def,
730
- [TYPES.array]: [
731
- // arrays are always truthy
732
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
733
- ],
734
715
  [TYPES.string]: [
735
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
736
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -766,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
766
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
767
748
 
768
749
  ...typeSwitch(scope, type, {
769
- [TYPES.array]: [
770
- // arrays are always truthy
771
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
772
- ],
773
750
  [TYPES.string]: [
774
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
775
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -858,31 +835,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
858
835
 
859
836
  // if strict (in)equal check types match
860
837
  if (strictOp) {
861
- // startOut.push(
862
- // ...leftType,
863
- // ...rightType,
864
- // [ Opcodes.i32_eq ]
865
- // );
866
-
867
- // endOut.push(
868
- // [ Opcodes.i32_and ]
869
- // );
870
-
871
- // startOut.push(
872
- // [ Opcodes.block, Valtype.i32 ],
873
- // ...leftType,
874
- // ...rightType,
875
- // [ Opcodes.i32_ne ],
876
- // [ Opcodes.if, Blocktype.void ],
877
- // ...number(op === '===' ? 0 : 1, Valtype.i32),
878
- // [ Opcodes.br, 1 ],
879
- // [ Opcodes.end ]
880
- // );
881
-
882
- // endOut.push(
883
- // [ Opcodes.end ]
884
- // );
885
-
886
838
  endOut.push(
887
839
  ...leftType,
888
840
  ...rightType,
@@ -1038,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
1038
990
  // if both are true
1039
991
  [ Opcodes.i32_and ],
1040
992
  [ Opcodes.if, Blocktype.void ],
1041
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
1042
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1043
995
  [ Opcodes.br, 1 ],
1044
996
  [ Opcodes.end ],
@@ -1087,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1087
1039
  return out;
1088
1040
  };
1089
1041
 
1090
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1091
- return func({ name, params, locals, returns, localInd }, {
1092
- TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1093
- builtin: name => {
1094
- let idx = funcIndex[name] ?? importedFuncs[name];
1095
- if (idx === undefined && builtinFuncs[name]) {
1096
- includeBuiltin(null, name);
1097
- idx = funcIndex[name];
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1044
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1098
1050
  }
1099
1051
 
1100
1052
  return idx;
@@ -1102,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1102
1054
  });
1103
1055
  };
1104
1056
 
1105
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1106
1058
  const existing = funcs.find(x => x.name === name);
1107
1059
  if (existing) return existing;
1108
1060
 
@@ -1120,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1120
1072
  data.push(copy);
1121
1073
  }
1122
1074
 
1123
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType: returnType ?? TYPES.number,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1124
1091
 
1125
1092
  let baseGlobalIdx, i = 0;
1126
1093
  for (const type of globalTypes) {
@@ -1139,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1139
1106
  }
1140
1107
  }
1141
1108
 
1142
- const func = {
1143
- name,
1144
- params,
1145
- locals,
1146
- returns,
1147
- returnType: returnType ?? TYPES.number,
1148
- wasm,
1149
- internal: true,
1150
- index: currentFuncIndex++
1151
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1152
1115
 
1153
- funcs.push(func);
1154
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1155
1117
 
1156
1118
  return func;
1157
1119
  };
@@ -1243,12 +1205,13 @@ const getLastType = scope => {
1243
1205
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1244
1206
  };
1245
1207
 
1246
- const setLastType = scope => {
1247
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1248
- };
1208
+ const setLastType = (scope, type = []) => [
1209
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1210
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1211
+ ];
1249
1212
 
1250
1213
  const getNodeType = (scope, node) => {
1251
- const inner = () => {
1214
+ const ret = (() => {
1252
1215
  if (node.type === 'Literal') {
1253
1216
  if (node.regex) return TYPES.regexp;
1254
1217
 
@@ -1291,7 +1254,6 @@ const getNodeType = (scope, node) => {
1291
1254
  const func = funcs.find(x => x.name === name);
1292
1255
 
1293
1256
  if (func) {
1294
- // console.log(scope, func, func.returnType);
1295
1257
  if (func.returnType) return func.returnType;
1296
1258
  }
1297
1259
 
@@ -1307,7 +1269,17 @@ const getNodeType = (scope, node) => {
1307
1269
 
1308
1270
  const func = spl[spl.length - 1];
1309
1271
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1310
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1311
1283
  }
1312
1284
 
1313
1285
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1402,22 +1374,27 @@ const getNodeType = (scope, node) => {
1402
1374
  }
1403
1375
 
1404
1376
  if (node.type === 'MemberExpression') {
1405
- // hack: if something.name, string type
1406
- if (node.property.name === 'name') {
1407
- if (hasFuncWithName(node.object.name)) {
1408
- return TYPES.bytestring;
1409
- } else {
1410
- return TYPES.undefined;
1411
- }
1377
+ const name = node.property.name;
1378
+
1379
+ if (name === 'length') {
1380
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1381
+ if (Prefs.fastLength) return TYPES.number;
1412
1382
  }
1413
1383
 
1414
- // hack: if something.length, number type
1415
- if (node.property.name === 'length') return TYPES.number;
1416
1384
 
1417
- // ts hack
1418
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1419
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1420
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1385
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1386
+ if (objectKnownType != null) {
1387
+ if (name === 'length') {
1388
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1389
+ else return TYPES.undefined;
1390
+ }
1391
+
1392
+ if (node.computed) {
1393
+ if (objectKnownType === TYPES.string) return TYPES.string;
1394
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1395
+ if (objectKnownType === TYPES.array) return TYPES.number;
1396
+ }
1397
+ }
1421
1398
 
1422
1399
  if (scope.locals['#last_type']) return getLastType(scope);
1423
1400
 
@@ -1435,10 +1412,8 @@ const getNodeType = (scope, node) => {
1435
1412
  // presume
1436
1413
  // todo: warn here?
1437
1414
  return TYPES.number;
1438
- };
1415
+ })();
1439
1416
 
1440
- const ret = inner();
1441
- // console.trace(node, ret);
1442
1417
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1443
1418
  return ret;
1444
1419
  };
@@ -1490,17 +1465,16 @@ const countLeftover = wasm => {
1490
1465
  else if (inst[0] === Opcodes.return) count = 0;
1491
1466
  else if (inst[0] === Opcodes.call) {
1492
1467
  let func = funcs.find(x => x.index === inst[1]);
1493
- if (inst[1] === -1) {
1494
- // todo: count for calling self
1495
- } else if (!func && inst[1] < importedFuncs.length) {
1496
- count -= importedFuncs[inst[1]].params;
1497
- count += importedFuncs[inst[1]].returns;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1498
1471
  } else {
1499
- if (func) {
1500
- count -= func.params.length;
1501
- } else count--;
1502
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1503
1473
  }
1474
+ } else if (inst[0] === Opcodes.call_indirect) {
1475
+ count--; // funcidx
1476
+ count -= inst[1] * 2; // params * 2 (typed)
1477
+ count += 2; // fixed return (value, type)
1504
1478
  } else count--;
1505
1479
 
1506
1480
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1518,7 +1492,7 @@ const disposeLeftover = wasm => {
1518
1492
  const generateExp = (scope, decl) => {
1519
1493
  const expression = decl.expression;
1520
1494
 
1521
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1522
1496
  disposeLeftover(out);
1523
1497
 
1524
1498
  return out;
@@ -1577,15 +1551,6 @@ const RTArrayUtil = {
1577
1551
  };
1578
1552
 
1579
1553
  const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1580
- /* const callee = decl.callee;
1581
- const args = decl.arguments;
1582
-
1583
- return [
1584
- ...generate(args),
1585
- ...generate(callee),
1586
- Opcodes.call_indirect,
1587
- ]; */
1588
-
1589
1554
  let name = mapName(decl.callee.name);
1590
1555
  if (isFuncType(decl.callee.type)) { // iife
1591
1556
  const func = generateFunc(scope, decl.callee);
@@ -1618,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1618
1583
  out.splice(out.length - 1, 1);
1619
1584
 
1620
1585
  const finalStatement = parsed.body[parsed.body.length - 1];
1621
- out.push(
1622
- ...getNodeType(scope, finalStatement),
1623
- ...setLastType(scope)
1624
- );
1586
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1625
1587
  } else if (countLeftover(out) === 0) {
1626
1588
  out.push(...number(UNDEFINED));
1627
- out.push(
1628
- ...number(TYPES.undefined, Valtype.i32),
1629
- ...setLastType(scope)
1630
- );
1589
+ out.push(...setLastType(scope, TYPES.undefined));
1631
1590
  }
1632
1591
 
1633
1592
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1663,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1663
1622
 
1664
1623
  if (!funcIndex[rhemynName]) {
1665
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1666
1626
 
1667
1627
  funcIndex[func.name] = func.index;
1668
1628
  funcs.push(func);
@@ -1679,8 +1639,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1679
1639
  [ Opcodes.call, idx ],
1680
1640
  Opcodes.i32_from_u,
1681
1641
 
1682
- ...number(TYPES.boolean, Valtype.i32),
1683
- ...setLastType(scope)
1642
+ ...setLastType(scope, TYPES.boolean)
1684
1643
  ];
1685
1644
  }
1686
1645
 
@@ -1752,15 +1711,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1752
1711
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1753
1712
  protoBC[x] = [
1754
1713
  ...RTArrayUtil.getLength(getPointer),
1755
-
1756
- ...number(TYPES.number, Valtype.i32),
1757
- ...setLastType(scope)
1714
+ ...setLastType(scope, TYPES.number)
1758
1715
  ];
1759
1716
  continue;
1760
1717
  }
1761
1718
 
1762
- // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1763
- // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1764
1719
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1765
1720
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1766
1721
 
@@ -1775,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1775
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1776
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1777
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1778
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1779
1734
  return makeArray(scope, {
1780
1735
  rawElements: new Array(length)
1781
1736
  }, _global, _name, true, itemType);
@@ -1789,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1789
1744
  protoBC[x] = [
1790
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1791
1746
  ...protoOut,
1792
-
1793
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1794
- ...setLastType(scope),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1795
1748
  [ Opcodes.end ]
1796
1749
  ];
1797
1750
  }
@@ -1837,31 +1790,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1837
1790
 
1838
1791
  includeBuiltin(scope, name);
1839
1792
  idx = funcIndex[name];
1840
-
1841
- // infer arguments types from builtins params
1842
- // const func = funcs.find(x => x.name === name);
1843
- // for (let i = 0; i < decl.arguments.length; i++) {
1844
- // const arg = decl.arguments[i];
1845
- // if (!arg.name) continue;
1846
-
1847
- // const local = scope.locals[arg.name];
1848
- // if (!local) continue;
1849
-
1850
- // local.type = func.params[i];
1851
- // if (local.type === Valtype.v128) {
1852
- // // specify vec subtype inferred from last vec type in function name
1853
- // local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1854
- // }
1855
- // }
1856
1793
  }
1857
1794
 
1858
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1859
1796
 
1860
- if (idx === undefined && name === scope.name) {
1861
- // hack: calling self, func generator will fix later
1862
- idx = -1;
1863
- }
1864
-
1865
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1866
1798
  const wasmOps = {
1867
1799
  // pointer, align, offset
@@ -1909,15 +1841,144 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1909
1841
  }
1910
1842
 
1911
1843
  if (idx === undefined) {
1912
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1844
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1845
+ const [ local, global ] = lookupName(scope, name);
1846
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1847
+
1848
+ // todo: only works when function uses typedParams and typedReturns
1849
+
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1857
+
1858
+ funcs.table = true;
1859
+ scope.table = true;
1860
+
1861
+ let args = decl.arguments;
1862
+ let out = [];
1863
+
1864
+ let locals = [];
1865
+
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1868
+
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1873
+
1874
+ for (let i = 0; i < args.length; i++) {
1875
+ const arg = args[i];
1876
+ out = out.concat(generate(scope, arg));
1877
+
1878
+ if (valtypeBinary !== Valtype.i32 && (
1879
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1880
+ (importedFuncs[name] && name.startsWith('profile'))
1881
+ )) {
1882
+ out.push(Opcodes.i32_to);
1883
+ }
1884
+
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1897
+ }
1898
+ }
1899
+
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1911
+ }
1912
+
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1950
+ }
1951
+
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1954
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1955
+ [TYPES.function]: [
1956
+ ...out,
1957
+
1958
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1959
+ Opcodes.i32_to_u,
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1970
+ ],
1971
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1972
+ });
1973
+ }
1974
+
1913
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1914
1976
  }
1915
1977
 
1916
- const func = funcs.find(x => x.index === idx);
1917
-
1918
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
1919
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1920
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1921
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1922
1983
 
1923
1984
  let args = decl.arguments;
@@ -1938,11 +1999,17 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1938
1999
  const arg = args[i];
1939
2000
  out = out.concat(generate(scope, arg));
1940
2001
 
1941
- if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1942
- out.push(Opcodes.i32_to);
2002
+ // todo: this should be used instead of the too many args thing above (by removing that)
2003
+ if (i >= paramCount) {
2004
+ // over param count of func, drop arg
2005
+ out.push([ Opcodes.drop ]);
2006
+ continue;
1943
2007
  }
1944
2008
 
1945
- if (importedFuncs[name] && name.startsWith('profile')) {
2009
+ if (valtypeBinary !== Valtype.i32 && (
2010
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
2011
+ (importedFuncs[name] && name.startsWith('profile'))
2012
+ )) {
1946
2013
  out.push(Opcodes.i32_to);
1947
2014
  }
1948
2015
 
@@ -1987,6 +2054,11 @@ const generateNew = (scope, decl, _global, _name) => {
1987
2054
  }, _global, _name);
1988
2055
  }
1989
2056
 
2057
+ if (
2058
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2059
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2060
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2061
+
1990
2062
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1991
2063
 
1992
2064
  return generateCall(scope, decl, _global, _name);
@@ -2012,8 +2084,11 @@ const knownType = (scope, type) => {
2012
2084
  const idx = type[0][1];
2013
2085
 
2014
2086
  // type idx = var idx + 1
2015
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
2016
- if (v.metadata?.type != null) return v.metadata.type;
2087
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2088
+ if (name) {
2089
+ const local = scope.locals[name];
2090
+ if (local.metadata?.type != null) return v.metadata.type;
2091
+ }
2017
2092
  }
2018
2093
 
2019
2094
  return null;
@@ -2048,16 +2123,17 @@ const brTable = (input, bc, returns) => {
2048
2123
  }
2049
2124
 
2050
2125
  for (let i = 0; i < count; i++) {
2051
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2052
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
2053
2129
  }
2054
2130
 
2055
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
2056
2132
  const offset = Math.min(...nums);
2057
2133
  const max = Math.max(...nums);
2058
2134
 
2059
2135
  const table = [];
2060
- let br = 1;
2136
+ let br = 0;
2061
2137
 
2062
2138
  for (let i = offset; i <= max; i++) {
2063
2139
  // if branch for this num, go to that block
@@ -2097,10 +2173,9 @@ const brTable = (input, bc, returns) => {
2097
2173
  br--;
2098
2174
  }
2099
2175
 
2100
- return [
2101
- ...out,
2102
- [ Opcodes.end, 'br table end' ]
2103
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
2104
2179
  };
2105
2180
 
2106
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2144,6 +2219,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2144
2219
  return out;
2145
2220
  };
2146
2221
 
2222
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2223
+ const out = [];
2224
+
2225
+ for (let i = 0; i < types.length; i++) {
2226
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2227
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2228
+ }
2229
+
2230
+ return out;
2231
+ };
2232
+
2147
2233
  const allocVar = (scope, name, global = false, type = true) => {
2148
2234
  const target = global ? globals : scope.locals;
2149
2235
 
@@ -2160,7 +2246,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2160
2246
 
2161
2247
  if (type) {
2162
2248
  let typeIdx = global ? globalInd++ : scope.localInd++;
2163
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2249
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2164
2250
  }
2165
2251
 
2166
2252
  return idx;
@@ -2178,7 +2264,6 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
2178
2264
  const typeAnnoToPorfType = x => {
2179
2265
  if (!x) return null;
2180
2266
  if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2181
- if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
2182
2267
 
2183
2268
  switch (x) {
2184
2269
  case 'i32':
@@ -2219,9 +2304,8 @@ const generateVar = (scope, decl) => {
2219
2304
 
2220
2305
  const topLevel = scope.name === 'main';
2221
2306
 
2222
- // global variable if in top scope (main) and var ..., or if wanted
2223
- const global = topLevel || decl._bare; // decl.kind === 'var';
2224
- const target = global ? globals : scope.locals;
2307
+ // global variable if in top scope (main) or if internally wanted
2308
+ const global = topLevel || decl._bare;
2225
2309
 
2226
2310
  for (const x of decl.declarations) {
2227
2311
  const name = mapName(x.id.name);
@@ -2235,7 +2319,6 @@ const generateVar = (scope, decl) => {
2235
2319
  continue;
2236
2320
  }
2237
2321
 
2238
- // console.log(name);
2239
2322
  if (topLevel && builtinVars[name]) {
2240
2323
  // cannot redeclare
2241
2324
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -2255,6 +2338,22 @@ const generateVar = (scope, decl) => {
2255
2338
  }
2256
2339
 
2257
2340
  if (x.init) {
2341
+ // if (isFuncType(x.init.type)) {
2342
+ // // let a = function () { ... }
2343
+ // x.init.id = { name };
2344
+
2345
+ // const func = generateFunc(scope, x.init);
2346
+
2347
+ // out.push(
2348
+ // ...number(func.index - importedFuncs.length),
2349
+ // [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
2350
+
2351
+ // ...setType(scope, name, TYPES.function)
2352
+ // );
2353
+
2354
+ // continue;
2355
+ // }
2356
+
2258
2357
  const generated = generate(scope, x.init, global, name);
2259
2358
  if (scope.arrays?.get(name) != null) {
2260
2359
  // hack to set local as pointer before
@@ -2266,6 +2365,7 @@ const generateVar = (scope, decl) => {
2266
2365
  out = out.concat(generated);
2267
2366
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2268
2367
  }
2368
+
2269
2369
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2270
2370
  }
2271
2371
 
@@ -2279,6 +2379,7 @@ const generateVar = (scope, decl) => {
2279
2379
  // todo: optimize this func for valueUnused
2280
2380
  const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2281
2381
  const { type, name } = decl.left;
2382
+ const [ local, isGlobal ] = lookupName(scope, name);
2282
2383
 
2283
2384
  if (type === 'ObjectPattern') {
2284
2385
  // hack: ignore object parts of `var a = {} = 2`
@@ -2288,8 +2389,18 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2288
2389
  if (isFuncType(decl.right.type)) {
2289
2390
  // hack for a = function () { ... }
2290
2391
  decl.right.id = { name };
2291
- generateFunc(scope, decl.right);
2292
- return [];
2392
+
2393
+ const func = generateFunc(scope, decl.right);
2394
+
2395
+ return [
2396
+ ...number(func.index - importedFuncs.length),
2397
+ ...(local != null ? [
2398
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2399
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2400
+
2401
+ ...setType(scope, name, TYPES.function)
2402
+ ] : [])
2403
+ ];
2293
2404
  }
2294
2405
 
2295
2406
  const op = decl.operator.slice(0, -1) || '=';
@@ -2348,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2348
2459
  Opcodes.i32_to_u,
2349
2460
 
2350
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2351
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2352
2463
  [ Opcodes.i32_mul ],
2353
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2354
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2355
2466
 
2356
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2357
2468
  [ Opcodes.local_get, pointerTmp ],
2358
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2359
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2360
2474
  [ Opcodes.local_tee, newValueTmp ],
2361
2475
 
2362
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2363
2477
  ],
2364
2478
 
2365
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2388,8 +2502,6 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2388
2502
 
2389
2503
  if (!name) return todo(scope, 'destructuring is not supported yet', true);
2390
2504
 
2391
- const [ local, isGlobal ] = lookupName(scope, name);
2392
-
2393
2505
  if (local === undefined) {
2394
2506
  // todo: this should be a sloppy mode only thing
2395
2507
 
@@ -2469,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2469
2581
  ];
2470
2582
 
2471
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === "UnaryExpression" && arg.operator === "!") {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2472
2589
  // !=
2473
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2474
2591
 
@@ -2538,6 +2655,7 @@ const generateUnary = (scope, decl) => {
2538
2655
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2539
2656
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2540
2657
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2658
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2541
2659
 
2542
2660
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2543
2661
 
@@ -2614,21 +2732,16 @@ const generateConditional = (scope, decl) => {
2614
2732
  out.push([ Opcodes.if, valtypeBinary ]);
2615
2733
  depth.push('if');
2616
2734
 
2617
- out.push(...generate(scope, decl.consequent));
2618
-
2619
- // note type
2620
2735
  out.push(
2621
- ...getNodeType(scope, decl.consequent),
2622
- ...setLastType(scope)
2736
+ ...generate(scope, decl.consequent),
2737
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2623
2738
  );
2624
2739
 
2625
2740
  out.push([ Opcodes.else ]);
2626
- out.push(...generate(scope, decl.alternate));
2627
2741
 
2628
- // note type
2629
2742
  out.push(
2630
- ...getNodeType(scope, decl.alternate),
2631
- ...setLastType(scope)
2743
+ ...generate(scope, decl.alternate),
2744
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2632
2745
  );
2633
2746
 
2634
2747
  out.push([ Opcodes.end ]);
@@ -2776,12 +2889,15 @@ const generateForOf = (scope, decl) => {
2776
2889
  // todo: optimize away counter and use end pointer
2777
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2778
2891
  [TYPES.array]: [
2779
- ...setType(scope, leftName, TYPES.number),
2780
-
2781
2892
  [ Opcodes.loop, Blocktype.void ],
2782
2893
 
2783
2894
  [ Opcodes.local_get, pointer ],
2784
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2785
2901
 
2786
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2787
2903
 
@@ -2790,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2790
2906
  ...generate(scope, decl.body),
2791
2907
  [ Opcodes.end ],
2792
2908
 
2793
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2794
2910
  [ Opcodes.local_get, pointer ],
2795
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2796
2912
  [ Opcodes.i32_add ],
2797
2913
  [ Opcodes.local_set, pointer ],
2798
2914
 
@@ -2908,6 +3024,44 @@ const generateForOf = (scope, decl) => {
2908
3024
  [ Opcodes.end ],
2909
3025
  [ Opcodes.end ]
2910
3026
  ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
2911
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2912
3066
  }, Blocktype.void));
2913
3067
 
@@ -3009,14 +3163,18 @@ const generateThrow = (scope, decl) => {
3009
3163
  };
3010
3164
 
3011
3165
  const generateTry = (scope, decl) => {
3012
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
3013
3168
 
3014
3169
  const out = [];
3015
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
3016
3173
  out.push([ Opcodes.try, Blocktype.void ]);
3017
3174
  depth.push('try');
3018
3175
 
3019
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
3020
3178
 
3021
3179
  if (decl.handler) {
3022
3180
  depth.pop();
@@ -3024,6 +3182,7 @@ const generateTry = (scope, decl) => {
3024
3182
 
3025
3183
  out.push([ Opcodes.catch_all ]);
3026
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
3027
3186
  }
3028
3187
 
3029
3188
  out.push([ Opcodes.end ]);
@@ -3036,13 +3195,6 @@ const generateEmpty = (scope, decl) => {
3036
3195
  return [];
3037
3196
  };
3038
3197
 
3039
- const generateAssignPat = (scope, decl) => {
3040
- // TODO
3041
- // if identifier declared, use that
3042
- // else, use default (right)
3043
- return todo(scope, 'assignment pattern (optional arg)');
3044
- };
3045
-
3046
3198
  let pages = new Map();
3047
3199
  const allocPage = (scope, reason, type) => {
3048
3200
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -3116,7 +3268,7 @@ const getAllocType = itemType => {
3116
3268
  }
3117
3269
  };
3118
3270
 
3119
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3120
3272
  const out = [];
3121
3273
 
3122
3274
  scope.arrays ??= new Map();
@@ -3128,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3128
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3129
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3130
3282
 
3131
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3132
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
3133
3290
  }
3134
3291
 
3135
3292
  const pointer = scope.arrays.get(name);
@@ -3179,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3179
3336
 
3180
3337
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3181
3338
 
3182
- // store length as 0th array
3339
+ // store length
3183
3340
  out.push(
3184
3341
  ...pointerWasm,
3185
3342
  ...number(length, Valtype.i32),
@@ -3187,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3187
3344
  );
3188
3345
 
3189
3346
  const storeOp = StoreOps[itemType];
3190
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3191
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
3192
3349
  if (elements[i] == null) continue;
3193
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3194
3352
  out.push(
3195
3353
  ...pointerWasm,
3196
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3197
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
3198
3361
  );
3199
3362
  }
3200
3363
 
@@ -3204,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3204
3367
  return [ out, pointer ];
3205
3368
  };
3206
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
3207
3429
  const byteStringable = str => {
3208
3430
  if (!Prefs.bytestring) return false;
3209
3431
 
@@ -3232,14 +3454,39 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3232
3454
  };
3233
3455
 
3234
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3235
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3236
3458
  };
3237
3459
 
3238
- export const generateMember = (scope, decl, _global, _name) => {
3460
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3461
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3462
+
3463
+ return [
3464
+ ...number(1),
3465
+ ...setLastType(scope, TYPES.object)
3466
+ ];
3467
+ };
3468
+
3469
+ const withType = (scope, wasm, type) => [
3470
+ ...wasm,
3471
+ ...setLastType(scope, type)
3472
+ ];
3473
+
3474
+ const generateMember = (scope, decl, _global, _name) => {
3239
3475
  const name = decl.object.name;
3240
- const pointer = scope.arrays?.get(name);
3241
3476
 
3242
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3477
+ // hack: process.argv[n]
3478
+ if (name === '__process_argv') {
3479
+ const setPointer = scope.arrays?.get(_name);
3480
+
3481
+ return [
3482
+ ...number(decl.property.value - 1),
3483
+ ...(setPointer ? number(setPointer) : makeArray(scope, { elements: [] }, undefined, undefined, true, 'i8')[0]),
3484
+ [ Opcodes.call, importedFuncs.__Porffor_readArgv ]
3485
+ ];
3486
+ }
3487
+
3488
+ const pointer = scope.arrays?.get(name);
3489
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3243
3490
 
3244
3491
  // hack: .name
3245
3492
  if (decl.property.name === 'name') {
@@ -3249,9 +3496,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3496
  // eg: __String_prototype_toLowerCase -> toLowerCase
3250
3497
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3251
3498
 
3252
- return makeString(scope, nameProp, _global, _name, true);
3499
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3253
3500
  } else {
3254
- return generate(scope, DEFAULT_VALUE);
3501
+ return withType(scope, number(0), TYPES.undefined);
3255
3502
  }
3256
3503
  }
3257
3504
 
@@ -3259,9 +3506,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3259
3506
  if (decl.property.name === 'length') {
3260
3507
  const func = funcs.find(x => x.name === name);
3261
3508
  if (func) {
3262
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3263
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3264
- return number(typedParams ? func.params.length / 2 : func.params.length);
3509
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3510
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3265
3511
  }
3266
3512
 
3267
3513
  if (builtinFuncs[name + '$constructor']) {
@@ -3271,24 +3517,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3271
3517
  const constructorFunc = builtinFuncs[name + '$constructor'];
3272
3518
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3273
3519
 
3274
- return number(Math.max(regularParams, constructorParams));
3520
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3275
3521
  }
3276
3522
 
3277
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3278
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3279
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3523
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3524
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3525
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3526
+
3527
+ if (Prefs.fastLength) {
3528
+ // presume valid length object
3529
+ return [
3530
+ ...(aotPointer ? number(0, Valtype.i32) : [
3531
+ ...generate(scope, decl.object),
3532
+ Opcodes.i32_to_u
3533
+ ]),
3534
+
3535
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3536
+ Opcodes.i32_from_u
3537
+ ];
3538
+ }
3539
+
3540
+ const type = getNodeType(scope, decl.object);
3541
+ const known = knownType(scope, type);
3542
+ if (known != null) {
3543
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3544
+ ...(aotPointer ? number(0, Valtype.i32) : [
3545
+ ...generate(scope, decl.object),
3546
+ Opcodes.i32_to_u
3547
+ ]),
3548
+
3549
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3550
+ Opcodes.i32_from_u
3551
+ ];
3552
+
3553
+ return number(0);
3554
+ }
3280
3555
 
3281
3556
  return [
3282
- ...(aotPointer ? number(0, Valtype.i32) : [
3283
- ...generate(scope, decl.object),
3284
- Opcodes.i32_to_u
3285
- ]),
3557
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3558
+ [ Opcodes.if, valtypeBinary ],
3559
+ ...(aotPointer ? number(0, Valtype.i32) : [
3560
+ ...generate(scope, decl.object),
3561
+ Opcodes.i32_to_u
3562
+ ]),
3563
+
3564
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3565
+ Opcodes.i32_from_u,
3286
3566
 
3287
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3288
- Opcodes.i32_from_u
3567
+ ...setLastType(scope, TYPES.number),
3568
+ [ Opcodes.else ],
3569
+ ...number(0),
3570
+ ...setLastType(scope, TYPES.undefined),
3571
+ [ Opcodes.end ]
3289
3572
  ];
3290
3573
  }
3291
3574
 
3575
+ // todo: generate this array procedurally during builtinFuncs creation
3576
+ if (['size', 'description'].includes(decl.property.name)) {
3577
+ const bc = {};
3578
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3579
+
3580
+ if (cands.length > 0) {
3581
+ for (const x of cands) {
3582
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3583
+ if (type == null) continue;
3584
+
3585
+ bc[type] = generateCall(scope, {
3586
+ callee: {
3587
+ type: 'Identifier',
3588
+ name: x
3589
+ },
3590
+ arguments: [ decl.object ],
3591
+ _protoInternalCall: true
3592
+ });
3593
+ }
3594
+ }
3595
+
3596
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3597
+ ...bc,
3598
+ default: withType(scope, number(0), TYPES.undefined)
3599
+ }, valtypeBinary);
3600
+ }
3601
+
3292
3602
  const object = generate(scope, decl.object);
3293
3603
  const property = generate(scope, decl.property);
3294
3604
 
@@ -3303,24 +3613,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3303
3613
 
3304
3614
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3305
3615
  [TYPES.array]: [
3306
- // get index as valtype
3307
- ...property,
3308
-
3309
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3310
- Opcodes.i32_to_u,
3311
- ...number(ValtypeSize[valtype], Valtype.i32),
3312
- [ Opcodes.i32_mul ],
3313
-
3314
- ...(aotPointer ? [] : [
3315
- ...object,
3316
- Opcodes.i32_to_u,
3317
- [ Opcodes.i32_add ]
3318
- ]),
3319
-
3320
- // read from memory
3321
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3322
-
3323
- ...number(TYPES.number, Valtype.i32),
3616
+ ...loadArray(scope, object, property, aotPointer),
3324
3617
  ...setLastType(scope)
3325
3618
  ],
3326
3619
 
@@ -3351,9 +3644,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3351
3644
 
3352
3645
  // return new string (page)
3353
3646
  ...number(newPointer),
3354
-
3355
- ...number(TYPES.string, Valtype.i32),
3356
- ...setLastType(scope)
3647
+ ...setLastType(scope, TYPES.string)
3357
3648
  ],
3358
3649
  [TYPES.bytestring]: [
3359
3650
  // setup new/out array
@@ -3379,9 +3670,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3379
3670
 
3380
3671
  // return new string (page)
3381
3672
  ...number(newPointer),
3382
-
3383
- ...number(TYPES.bytestring, Valtype.i32),
3384
- ...setLastType(scope)
3673
+ ...setLastType(scope, TYPES.bytestring)
3385
3674
  ],
3386
3675
 
3387
3676
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3406,7 +3695,7 @@ const objectHack = node => {
3406
3695
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3407
3696
 
3408
3697
  // if .name or .length, give up (hack within a hack!)
3409
- if (['name', 'length'].includes(node.property.name)) {
3698
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3410
3699
  node.object = objectHack(node.object);
3411
3700
  return;
3412
3701
  }
@@ -3443,30 +3732,39 @@ const generateFunc = (scope, decl) => {
3443
3732
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3444
3733
  const params = decl.params ?? [];
3445
3734
 
3446
- // const innerScope = { ...scope };
3447
3735
  // TODO: share scope/locals between !!!
3448
- const innerScope = {
3736
+ const func = {
3449
3737
  locals: {},
3450
3738
  localInd: 0,
3451
3739
  // value, type
3452
3740
  returns: [ valtypeBinary, Valtype.i32 ],
3453
3741
  throws: false,
3454
- name
3742
+ name,
3743
+ index: currentFuncIndex++
3455
3744
  };
3456
3745
 
3457
3746
  if (typedInput && decl.returnType) {
3458
- innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3459
- innerScope.returns = [ valtypeBinary ];
3747
+ const { type } = extractTypeAnnotation(decl.returnType);
3748
+ // if (type != null && !Prefs.indirectCalls) {
3749
+ if (type != null) {
3750
+ func.returnType = type;
3751
+ func.returns = [ valtypeBinary ];
3752
+ }
3460
3753
  }
3461
3754
 
3462
3755
  for (let i = 0; i < params.length; i++) {
3463
- allocVar(innerScope, params[i].name, false);
3756
+ const name = params[i].name;
3757
+ // if (name == null) return todo('non-identifier args are not supported');
3758
+
3759
+ allocVar(func, name, false);
3464
3760
 
3465
3761
  if (typedInput && params[i].typeAnnotation) {
3466
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3762
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3467
3763
  }
3468
3764
  }
3469
3765
 
3766
+ func.params = Object.values(func.locals).map(x => x.type);
3767
+
3470
3768
  let body = objectHack(decl.body);
3471
3769
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3472
3770
  // hack: () => 0 -> () => return 0
@@ -3476,37 +3774,23 @@ const generateFunc = (scope, decl) => {
3476
3774
  };
3477
3775
  }
3478
3776
 
3479
- const wasm = generate(innerScope, body);
3480
- const func = {
3481
- name,
3482
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3483
- index: currentFuncIndex++,
3484
- ...innerScope
3485
- };
3486
3777
  funcIndex[name] = func.index;
3778
+ funcs.push(func);
3487
3779
 
3488
- if (name === 'main') func.gotLastType = true;
3780
+ const wasm = generate(func, body);
3781
+ func.wasm = wasm;
3489
3782
 
3490
- // quick hack fixes
3491
- for (const inst of wasm) {
3492
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3493
- inst[1] = func.index;
3494
- }
3495
- }
3783
+ if (name === 'main') func.gotLastType = true;
3496
3784
 
3497
3785
  // add end return if not found
3498
3786
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3499
3787
  wasm.push(
3500
3788
  ...number(0),
3501
- ...number(TYPES.undefined, Valtype.i32),
3789
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3502
3790
  [ Opcodes.return ]
3503
3791
  );
3504
3792
  }
3505
3793
 
3506
- func.wasm = wasm;
3507
-
3508
- funcs.push(func);
3509
-
3510
3794
  return func;
3511
3795
  };
3512
3796
 
@@ -3610,7 +3894,7 @@ const internalConstrs = {
3610
3894
  generate: (scope, decl) => {
3611
3895
  // todo: boolean object when used as constructor
3612
3896
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3613
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3897
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3614
3898
  },
3615
3899
  type: TYPES.boolean,
3616
3900
  length: 1
@@ -3671,8 +3955,10 @@ const internalConstrs = {
3671
3955
  }),
3672
3956
 
3673
3957
  // print space
3674
- ...number(32),
3675
- [ Opcodes.call, importedFuncs.printChar ]
3958
+ ...(i !== decl.arguments.length - 1 ? [
3959
+ ...number(32),
3960
+ [ Opcodes.call, importedFuncs.printChar ]
3961
+ ] : [])
3676
3962
  );
3677
3963
  }
3678
3964
 
@@ -3682,6 +3968,8 @@ const internalConstrs = {
3682
3968
  [ Opcodes.call, importedFuncs.printChar ]
3683
3969
  );
3684
3970
 
3971
+ out.push(...number(UNDEFINED));
3972
+
3685
3973
  return out;
3686
3974
  },
3687
3975
  type: TYPES.undefined,
@@ -3751,9 +4039,8 @@ export default program => {
3751
4039
 
3752
4040
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3753
4041
 
3754
- generateFunc(scope, program);
4042
+ const main = generateFunc(scope, program);
3755
4043
 
3756
- const main = funcs[funcs.length - 1];
3757
4044
  main.export = true;
3758
4045
  main.returns = [ valtypeBinary, Valtype.i32 ];
3759
4046
 
@@ -3780,7 +4067,7 @@ export default program => {
3780
4067
  }
3781
4068
 
3782
4069
  // if blank main func and other exports, remove it
3783
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4070
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3784
4071
 
3785
4072
  return { funcs, globals, tags, exceptions, pages, data };
3786
4073
  };