porffor 0.2.0-5ac7ea0 → 0.2.0-5ad562e

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 (49) hide show
  1. package/CONTRIBUTING.md +255 -0
  2. package/LICENSE +20 -20
  3. package/README.md +115 -82
  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} +59 -12
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +18 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +7 -84
  12. package/compiler/builtins/crypto.ts +29 -41
  13. package/compiler/builtins/date.ts +2071 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +42 -9
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +58 -85
  21. package/compiler/{codeGen.js → codegen.js} +792 -279
  22. package/compiler/decompile.js +0 -1
  23. package/compiler/embedding.js +22 -22
  24. package/compiler/encoding.js +108 -10
  25. package/compiler/generated_builtins.js +1463 -7
  26. package/compiler/index.js +16 -14
  27. package/compiler/log.js +2 -2
  28. package/compiler/opt.js +23 -22
  29. package/compiler/parse.js +30 -22
  30. package/compiler/precompile.js +25 -26
  31. package/compiler/prefs.js +7 -6
  32. package/compiler/prototype.js +2 -18
  33. package/compiler/types.js +37 -0
  34. package/compiler/wasmSpec.js +11 -1
  35. package/compiler/wrap.js +41 -44
  36. package/package.json +9 -5
  37. package/porf +2 -0
  38. package/rhemyn/compile.js +44 -26
  39. package/rhemyn/parse.js +322 -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 +69 -12
  44. package/runner/profiler.js +45 -26
  45. package/runner/repl.js +42 -9
  46. package/runner/sizes.js +37 -37
  47. package/runner/info.js +0 -89
  48. package/runner/transform.js +0 -15
  49. 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';
@@ -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
 
@@ -186,7 +201,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
186
201
  }
187
202
 
188
203
  let inst = Opcodes[asm[0].replace('.', '_')];
189
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
204
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
205
 
191
206
  if (!Array.isArray(inst)) inst = [ inst ];
192
207
  const immediates = asm.slice(1).map(x => {
@@ -195,40 +210,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
195
210
  return int;
196
211
  });
197
212
 
198
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
199
214
  }
200
215
 
201
216
  return out;
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?
@@ -426,8 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
426
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
427
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
428
462
 
429
- if (assign) {
430
- const pointer = arrays.get(name ?? '$undeclared');
463
+ if (assign && Prefs.aotPointerOpt) {
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,13 +1266,25 @@ 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?
1191
1273
  return TYPES.number;
1192
1274
  }
1193
1275
 
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1194
1288
  const func = funcs.find(x => x.name === name);
1195
1289
 
1196
1290
  if (func) {
@@ -1198,7 +1292,7 @@ const getNodeType = (scope, node) => {
1198
1292
  if (func.returnType) return func.returnType;
1199
1293
  }
1200
1294
 
1201
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1202
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1203
1297
 
1204
1298
  // check if this is a prototype function
@@ -1218,7 +1312,7 @@ const getNodeType = (scope, node) => {
1218
1312
  return TYPES.number;
1219
1313
  }
1220
1314
 
1221
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1222
1316
 
1223
1317
  // presume
1224
1318
  // todo: warn here?
@@ -1273,6 +1367,7 @@ const getNodeType = (scope, node) => {
1273
1367
 
1274
1368
  // todo: this should be dynamic but for now only static
1275
1369
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1276
1371
 
1277
1372
  return TYPES.number;
1278
1373
 
@@ -1309,15 +1404,21 @@ const getNodeType = (scope, node) => {
1309
1404
 
1310
1405
  // ts hack
1311
1406
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1312
1408
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1313
1409
 
1314
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1315
1411
 
1316
1412
  // presume
1317
1413
  return TYPES.number;
1318
1414
  }
1319
1415
 
1320
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1321
1422
 
1322
1423
  // presume
1323
1424
  // todo: warn here?
@@ -1350,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1350
1451
  return makeString(scope, decl.value, global, name);
1351
1452
 
1352
1453
  default:
1353
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1354
1455
  }
1355
1456
  };
1356
1457
 
@@ -1359,6 +1460,8 @@ const countLeftover = wasm => {
1359
1460
 
1360
1461
  for (let i = 0; i < wasm.length; i++) {
1361
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1362
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1363
1466
  if (inst[0] === Opcodes.if) count--;
1364
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1369,16 +1472,23 @@ const countLeftover = wasm => {
1369
1472
  if (depth === 0)
1370
1473
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1371
1474
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1372
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1373
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1374
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1375
1478
  else if (inst[0] === Opcodes.return) count = 0;
1376
1479
  else if (inst[0] === Opcodes.call) {
1377
1480
  let func = funcs.find(x => x.index === inst[1]);
1378
- if (func) {
1379
- count -= func.params.length;
1380
- } else count--;
1381
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1382
1492
  } else count--;
1383
1493
 
1384
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1470,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1470
1580
  name = func.name;
1471
1581
  }
1472
1582
 
1473
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1474
1584
  // literal eval hack
1475
- const code = decl.arguments[0].value;
1476
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1477
1598
 
1478
1599
  const out = generate(scope, {
1479
1600
  type: 'BlockStatement',
@@ -1487,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1487
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1488
1609
  out.push(
1489
1610
  ...getNodeType(scope, finalStatement),
1490
- setLastType(scope)
1611
+ ...setLastType(scope)
1491
1612
  );
1492
1613
  } else if (countLeftover(out) === 0) {
1493
1614
  out.push(...number(UNDEFINED));
1494
1615
  out.push(
1495
1616
  ...number(TYPES.undefined, Valtype.i32),
1496
- setLastType(scope)
1617
+ ...setLastType(scope)
1497
1618
  );
1498
1619
  }
1499
1620
 
@@ -1515,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1636
 
1516
1637
  target = { ...decl.callee };
1517
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1518
1642
  }
1519
1643
 
1520
1644
  // literal.func()
@@ -1522,22 +1646,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1522
1646
  // megahack for /regex/.func()
1523
1647
  const funcName = decl.callee.property.name;
1524
1648
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1525
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1649
+ const regex = decl.callee.object.regex.pattern;
1650
+ const rhemynName = `regex_${funcName}_${regex}`;
1526
1651
 
1527
- funcIndex[func.name] = func.index;
1528
- funcs.push(func);
1652
+ if (!funcIndex[rhemynName]) {
1653
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1654
+
1655
+ funcIndex[func.name] = func.index;
1656
+ funcs.push(func);
1657
+ }
1529
1658
 
1659
+ const idx = funcIndex[rhemynName];
1530
1660
  return [
1531
1661
  // make string arg
1532
1662
  ...generate(scope, decl.arguments[0]),
1663
+ Opcodes.i32_to_u,
1664
+ ...getNodeType(scope, decl.arguments[0]),
1533
1665
 
1534
1666
  // call regex func
1535
- Opcodes.i32_to_u,
1536
- [ Opcodes.call, func.index ],
1667
+ [ Opcodes.call, idx ],
1537
1668
  Opcodes.i32_from_u,
1538
1669
 
1539
1670
  ...number(TYPES.boolean, Valtype.i32),
1540
- setLastType(scope)
1671
+ ...setLastType(scope)
1541
1672
  ];
1542
1673
  }
1543
1674
 
@@ -1562,12 +1693,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1562
1693
  // }
1563
1694
 
1564
1695
  if (protoName) {
1696
+ const protoBC = {};
1697
+
1698
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1699
+
1700
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1701
+ for (const x of builtinProtoCands) {
1702
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1703
+ if (type == null) continue;
1704
+
1705
+ protoBC[type] = generateCall(scope, {
1706
+ callee: {
1707
+ type: 'Identifier',
1708
+ name: x
1709
+ },
1710
+ arguments: [ target, ...decl.arguments ],
1711
+ _protoInternalCall: true
1712
+ });
1713
+ }
1714
+ }
1715
+
1565
1716
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1566
1717
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1567
1718
  return acc;
1568
1719
  }, {});
1569
1720
 
1570
- // no prototype function candidates, ignore
1571
1721
  if (Object.keys(protoCands).length > 0) {
1572
1722
  // use local for cached i32 length as commonly used
1573
1723
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1585,7 +1735,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1585
1735
 
1586
1736
  let allOptUnused = true;
1587
1737
  let lengthI32CacheUsed = false;
1588
- const protoBC = {};
1589
1738
  for (const x in protoCands) {
1590
1739
  const protoFunc = protoCands[x];
1591
1740
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1593,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1593
1742
  ...RTArrayUtil.getLength(getPointer),
1594
1743
 
1595
1744
  ...number(TYPES.number, Valtype.i32),
1596
- setLastType(scope)
1745
+ ...setLastType(scope)
1597
1746
  ];
1598
1747
  continue;
1599
1748
  }
@@ -1630,7 +1779,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1630
1779
  ...protoOut,
1631
1780
 
1632
1781
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1633
- setLastType(scope),
1782
+ ...setLastType(scope),
1634
1783
  [ Opcodes.end ]
1635
1784
  ];
1636
1785
  }
@@ -1656,10 +1805,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1656
1805
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1657
1806
  ];
1658
1807
  }
1808
+
1809
+ if (Object.keys(protoBC).length > 0) {
1810
+ return typeSwitch(scope, getNodeType(scope, target), {
1811
+ ...protoBC,
1812
+
1813
+ // TODO: error better
1814
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1815
+ }, valtypeBinary);
1816
+ }
1659
1817
  }
1660
1818
 
1661
1819
  // TODO: only allows callee as literal
1662
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1820
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1663
1821
 
1664
1822
  let idx = funcIndex[name] ?? importedFuncs[name];
1665
1823
  if (idx === undefined && builtinFuncs[name]) {
@@ -1669,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1669
1827
  idx = funcIndex[name];
1670
1828
 
1671
1829
  // infer arguments types from builtins params
1672
- const func = funcs.find(x => x.name === name);
1673
- for (let i = 0; i < decl.arguments.length; i++) {
1674
- const arg = decl.arguments[i];
1675
- if (!arg.name) continue;
1676
-
1677
- const local = scope.locals[arg.name];
1678
- if (!local) continue;
1679
-
1680
- local.type = func.params[i];
1681
- if (local.type === Valtype.v128) {
1682
- // specify vec subtype inferred from last vec type in function name
1683
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1684
- }
1685
- }
1830
+ // const func = funcs.find(x => x.name === name);
1831
+ // for (let i = 0; i < decl.arguments.length; i++) {
1832
+ // const arg = decl.arguments[i];
1833
+ // if (!arg.name) continue;
1834
+
1835
+ // const local = scope.locals[arg.name];
1836
+ // if (!local) continue;
1837
+
1838
+ // local.type = func.params[i];
1839
+ // if (local.type === Valtype.v128) {
1840
+ // // specify vec subtype inferred from last vec type in function name
1841
+ // local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1842
+ // }
1843
+ // }
1686
1844
  }
1687
1845
 
1688
1846
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1695,9 +1853,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1853
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1696
1854
  const wasmOps = {
1697
1855
  // pointer, align, offset
1698
- i32_load8_u: { imms: 2, args: 1 },
1856
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1857
+ // pointer, value, align, offset
1858
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1859
+ // pointer, align, offset
1860
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1861
+ // pointer, value, align, offset
1862
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1863
+ // pointer, align, offset
1864
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1865
+ // pointer, value, align, offset
1866
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1867
+
1868
+ // pointer, align, offset
1869
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1699
1870
  // pointer, value, align, offset
1700
- i32_store8: { imms: 2, args: 2 },
1871
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1872
+
1873
+ // value
1874
+ i32_const: { imms: 1, args: [], returns: 1 },
1701
1875
  };
1702
1876
 
1703
1877
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1706,28 +1880,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1706
1880
  const op = wasmOps[opName];
1707
1881
 
1708
1882
  const argOut = [];
1709
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1883
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1884
+ ...generate(scope, decl.arguments[i]),
1885
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1886
+ );
1710
1887
 
1711
1888
  // literals only
1712
- const imms = decl.arguments.slice(op.args).map(x => x.value);
1889
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1713
1890
 
1714
1891
  return [
1715
1892
  ...argOut,
1716
- [ Opcodes[opName], ...imms ]
1893
+ [ Opcodes[opName], ...imms ],
1894
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1717
1895
  ];
1718
1896
  }
1719
1897
  }
1720
1898
 
1721
1899
  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`);
1900
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1901
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1724
1902
  }
1725
1903
 
1726
1904
  const func = funcs.find(x => x.index === idx);
1727
1905
 
1728
1906
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1729
1907
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1730
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1908
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1731
1909
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1732
1910
 
1733
1911
  let args = decl.arguments;
@@ -1748,7 +1926,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1748
1926
  const arg = args[i];
1749
1927
  out = out.concat(generate(scope, arg));
1750
1928
 
1751
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1929
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1933
+ if (importedFuncs[name] && name.startsWith('profile')) {
1752
1934
  out.push(Opcodes.i32_to);
1753
1935
  }
1754
1936
 
@@ -1767,9 +1949,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1767
1949
  // ...number(type, Valtype.i32),
1768
1950
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1769
1951
  // );
1770
- } else out.push(setLastType(scope));
1952
+ } else out.push(...setLastType(scope));
1771
1953
 
1772
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1954
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
1955
  out.push(Opcodes.i32_from);
1774
1956
  }
1775
1957
 
@@ -1779,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1779
1961
  const generateNew = (scope, decl, _global, _name) => {
1780
1962
  // hack: basically treat this as a normal call for builtins for now
1781
1963
  const name = mapName(decl.callee.name);
1964
+
1782
1965
  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)})`);
1966
+
1967
+ if (builtinFuncs[name + '$constructor']) {
1968
+ // custom ...$constructor override builtin func
1969
+ return generateCall(scope, {
1970
+ ...decl,
1971
+ callee: {
1972
+ type: 'Identifier',
1973
+ name: name + '$constructor'
1974
+ }
1975
+ }, _global, _name);
1976
+ }
1977
+
1978
+ 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
1979
 
1785
1980
  return generateCall(scope, decl, _global, _name);
1786
1981
  };
@@ -1914,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1914
2109
  [ Opcodes.block, returns ]
1915
2110
  ];
1916
2111
 
1917
- // todo: use br_table?
1918
-
1919
2112
  for (const x in bc) {
1920
2113
  if (x === 'default') continue;
1921
2114
 
@@ -1971,12 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1971
2164
  };
1972
2165
 
1973
2166
  const typeAnnoToPorfType = x => {
1974
- if (TYPES[x]) return TYPES[x];
1975
- if (TYPES['_' + x]) return TYPES['_' + x];
2167
+ if (!x) return null;
2168
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2169
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1976
2170
 
1977
2171
  switch (x) {
1978
2172
  case 'i32':
1979
2173
  case 'i64':
2174
+ case 'f64':
1980
2175
  return TYPES.number;
1981
2176
  }
1982
2177
 
@@ -1987,7 +2182,7 @@ const extractTypeAnnotation = decl => {
1987
2182
  let a = decl;
1988
2183
  while (a.typeAnnotation) a = a.typeAnnotation;
1989
2184
 
1990
- let type, elementType;
2185
+ let type = null, elementType = null;
1991
2186
  if (a.typeName) {
1992
2187
  type = a.typeName.name;
1993
2188
  } else if (a.type.endsWith('Keyword')) {
@@ -2014,11 +2209,12 @@ const generateVar = (scope, decl) => {
2014
2209
 
2015
2210
  // global variable if in top scope (main) and var ..., or if wanted
2016
2211
  const global = topLevel || decl._bare; // decl.kind === 'var';
2212
+ const target = global ? globals : scope.locals;
2017
2213
 
2018
2214
  for (const x of decl.declarations) {
2019
2215
  const name = mapName(x.id.name);
2020
2216
 
2021
- if (!name) return todo('destructuring is not supported yet');
2217
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2022
2218
 
2023
2219
  if (x.init && isFuncType(x.init.type)) {
2024
2220
  // hack for let a = function () { ... }
@@ -2035,16 +2231,29 @@ const generateVar = (scope, decl) => {
2035
2231
  continue; // always ignore
2036
2232
  }
2037
2233
 
2038
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2234
+ // // generate init before allocating var
2235
+ // let generated;
2236
+ // if (x.init) generated = generate(scope, x.init, global, name);
2237
+
2238
+ const typed = typedInput && x.id.typeAnnotation;
2239
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2039
2240
 
2040
- if (typedInput && x.id.typeAnnotation) {
2241
+ if (typed) {
2041
2242
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2042
2243
  }
2043
2244
 
2044
2245
  if (x.init) {
2045
- out = out.concat(generate(scope, x.init, global, name));
2046
-
2047
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2246
+ const generated = generate(scope, x.init, global, name);
2247
+ if (scope.arrays?.get(name) != null) {
2248
+ // hack to set local as pointer before
2249
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2250
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2251
+ generated.pop();
2252
+ out = out.concat(generated);
2253
+ } else {
2254
+ out = out.concat(generated);
2255
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2256
+ }
2048
2257
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2049
2258
  }
2050
2259
 
@@ -2071,22 +2280,30 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2071
2280
  return [];
2072
2281
  }
2073
2282
 
2283
+ const op = decl.operator.slice(0, -1) || '=';
2284
+
2074
2285
  // hack: .length setter
2075
2286
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2076
2287
  const name = decl.left.object.name;
2077
- const pointer = arrays.get(name);
2288
+ const pointer = scope.arrays?.get(name);
2078
2289
 
2079
- const aotPointer = pointer != null;
2290
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2080
2291
 
2081
2292
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2293
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2082
2294
 
2083
2295
  return [
2084
2296
  ...(aotPointer ? number(0, Valtype.i32) : [
2085
2297
  ...generate(scope, decl.left.object),
2086
2298
  Opcodes.i32_to_u
2087
2299
  ]),
2300
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2088
2301
 
2089
- ...generate(scope, decl.right),
2302
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2303
+ [ Opcodes.local_get, pointerTmp ],
2304
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2305
+ Opcodes.i32_from_u
2306
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2090
2307
  [ Opcodes.local_tee, newValueTmp ],
2091
2308
 
2092
2309
  Opcodes.i32_to_u,
@@ -2096,14 +2313,12 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2096
2313
  ];
2097
2314
  }
2098
2315
 
2099
- const op = decl.operator.slice(0, -1) || '=';
2100
-
2101
2316
  // arr[i]
2102
2317
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2103
2318
  const name = decl.left.object.name;
2104
- const pointer = arrays.get(name);
2319
+ const pointer = scope.arrays?.get(name);
2105
2320
 
2106
- const aotPointer = pointer != null;
2321
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2107
2322
 
2108
2323
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2109
2324
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2159,7 +2374,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2159
2374
  ];
2160
2375
  }
2161
2376
 
2162
- if (!name) return todo('destructuring is not supported yet');
2377
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2163
2378
 
2164
2379
  const [ local, isGlobal ] = lookupName(scope, name);
2165
2380
 
@@ -2264,7 +2479,7 @@ const generateUnary = (scope, decl) => {
2264
2479
  return out;
2265
2480
  }
2266
2481
 
2267
- case 'delete':
2482
+ case 'delete': {
2268
2483
  let toReturn = true, toGenerate = true;
2269
2484
 
2270
2485
  if (decl.argument.type === 'Identifier') {
@@ -2286,9 +2501,26 @@ const generateUnary = (scope, decl) => {
2286
2501
 
2287
2502
  out.push(...number(toReturn ? 1 : 0));
2288
2503
  return out;
2504
+ }
2505
+
2506
+ case 'typeof': {
2507
+ let overrideType, toGenerate = true;
2508
+
2509
+ if (decl.argument.type === 'Identifier') {
2510
+ const out = generateIdent(scope, decl.argument);
2511
+
2512
+ // if ReferenceError (undeclared var), ignore and return undefined
2513
+ if (out[1]) {
2514
+ // does not exist (2 ops from throw)
2515
+ overrideType = number(TYPES.undefined, Valtype.i32);
2516
+ toGenerate = false;
2517
+ }
2518
+ }
2519
+
2520
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2521
+ disposeLeftover(out);
2289
2522
 
2290
- case 'typeof':
2291
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2523
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2292
2524
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2293
2525
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2294
2526
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2299,10 +2531,13 @@ const generateUnary = (scope, decl) => {
2299
2531
 
2300
2532
  // object and internal types
2301
2533
  default: makeString(scope, 'object', false, '#typeof_result'),
2302
- });
2534
+ }));
2535
+
2536
+ return out;
2537
+ }
2303
2538
 
2304
2539
  default:
2305
- return todo(`unary operator ${decl.operator} not implemented yet`);
2540
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2306
2541
  }
2307
2542
  };
2308
2543
 
@@ -2312,7 +2547,7 @@ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2312
2547
  const [ local, isGlobal ] = lookupName(scope, name);
2313
2548
 
2314
2549
  if (local === undefined) {
2315
- return todo(`update expression with undefined variable`);
2550
+ return todo(scope, `update expression with undefined variable`, true);
2316
2551
  }
2317
2552
 
2318
2553
  const idx = local.idx;
@@ -2372,7 +2607,7 @@ const generateConditional = (scope, decl) => {
2372
2607
  // note type
2373
2608
  out.push(
2374
2609
  ...getNodeType(scope, decl.consequent),
2375
- setLastType(scope)
2610
+ ...setLastType(scope)
2376
2611
  );
2377
2612
 
2378
2613
  out.push([ Opcodes.else ]);
@@ -2381,7 +2616,7 @@ const generateConditional = (scope, decl) => {
2381
2616
  // note type
2382
2617
  out.push(
2383
2618
  ...getNodeType(scope, decl.alternate),
2384
- setLastType(scope)
2619
+ ...setLastType(scope)
2385
2620
  );
2386
2621
 
2387
2622
  out.push([ Opcodes.end ]);
@@ -2441,6 +2676,36 @@ const generateWhile = (scope, decl) => {
2441
2676
  return out;
2442
2677
  };
2443
2678
 
2679
+ const generateDoWhile = (scope, decl) => {
2680
+ const out = [];
2681
+
2682
+ out.push([ Opcodes.loop, Blocktype.void ]);
2683
+ depth.push('dowhile');
2684
+
2685
+ // block for break (includes all)
2686
+ out.push([ Opcodes.block, Blocktype.void ]);
2687
+ depth.push('block');
2688
+
2689
+ // block for continue
2690
+ // includes body but not test+loop so we can exit body at anytime
2691
+ // and still test+loop after
2692
+ out.push([ Opcodes.block, Blocktype.void ]);
2693
+ depth.push('block');
2694
+
2695
+ out.push(...generate(scope, decl.body));
2696
+
2697
+ out.push([ Opcodes.end ]);
2698
+ depth.pop();
2699
+
2700
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2701
+ out.push([ Opcodes.br_if, 1 ]);
2702
+
2703
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2704
+ depth.pop(); depth.pop();
2705
+
2706
+ return out;
2707
+ };
2708
+
2444
2709
  const generateForOf = (scope, decl) => {
2445
2710
  const out = [];
2446
2711
 
@@ -2477,7 +2742,10 @@ const generateForOf = (scope, decl) => {
2477
2742
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2478
2743
  }
2479
2744
 
2745
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2746
+
2480
2747
  const [ local, isGlobal ] = lookupName(scope, leftName);
2748
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2481
2749
 
2482
2750
  depth.push('block');
2483
2751
  depth.push('block');
@@ -2486,6 +2754,7 @@ const generateForOf = (scope, decl) => {
2486
2754
  // hack: this is naughty and will break things!
2487
2755
  let newOut = number(0, Valtype.f64), newPointer = -1;
2488
2756
  if (pages.hasAnyString) {
2757
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2489
2758
  0, [ newOut, newPointer ] = makeArray(scope, {
2490
2759
  rawElements: new Array(1)
2491
2760
  }, isGlobal, leftName, true, 'i16');
@@ -2577,6 +2846,56 @@ const generateForOf = (scope, decl) => {
2577
2846
  [ Opcodes.end ],
2578
2847
  [ Opcodes.end ]
2579
2848
  ],
2849
+ [TYPES._bytestring]: [
2850
+ ...setType(scope, leftName, TYPES._bytestring),
2851
+
2852
+ [ Opcodes.loop, Blocktype.void ],
2853
+
2854
+ // setup new/out array
2855
+ ...newOut,
2856
+ [ Opcodes.drop ],
2857
+
2858
+ ...number(0, Valtype.i32), // base 0 for store after
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.local_get, pointer ],
2862
+ [ Opcodes.local_get, counter ],
2863
+ [ Opcodes.i32_add ],
2864
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2865
+
2866
+ // store to new string ind 0
2867
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2868
+
2869
+ // return new string (page)
2870
+ ...number(newPointer),
2871
+
2872
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2873
+
2874
+ [ Opcodes.block, Blocktype.void ],
2875
+ [ Opcodes.block, Blocktype.void ],
2876
+ ...generate(scope, decl.body),
2877
+ [ Opcodes.end ],
2878
+
2879
+ // increment iter pointer
2880
+ // [ Opcodes.local_get, pointer ],
2881
+ // ...number(1, Valtype.i32),
2882
+ // [ Opcodes.i32_add ],
2883
+ // [ Opcodes.local_set, pointer ],
2884
+
2885
+ // increment counter by 1
2886
+ [ Opcodes.local_get, counter ],
2887
+ ...number(1, Valtype.i32),
2888
+ [ Opcodes.i32_add ],
2889
+ [ Opcodes.local_tee, counter ],
2890
+
2891
+ // loop if counter != length
2892
+ [ Opcodes.local_get, length ],
2893
+ [ Opcodes.i32_ne ],
2894
+ [ Opcodes.br_if, 1 ],
2895
+
2896
+ [ Opcodes.end ],
2897
+ [ Opcodes.end ]
2898
+ ],
2580
2899
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2581
2900
  }, Blocktype.void));
2582
2901
 
@@ -2587,28 +2906,65 @@ const generateForOf = (scope, decl) => {
2587
2906
  return out;
2588
2907
  };
2589
2908
 
2909
+ // find the nearest loop in depth map by type
2590
2910
  const getNearestLoop = () => {
2591
2911
  for (let i = depth.length - 1; i >= 0; i--) {
2592
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2912
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2593
2913
  }
2594
2914
 
2595
2915
  return -1;
2596
2916
  };
2597
2917
 
2598
2918
  const generateBreak = (scope, decl) => {
2599
- const nearestLoop = depth.length - getNearestLoop();
2919
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2920
+ const type = depth[target];
2921
+
2922
+ // different loop types have different branch offsets
2923
+ // as they have different wasm block/loop/if structures
2924
+ // we need to use the right offset by type to branch to the one we want
2925
+ // for a break: exit the loop without executing anything else inside it
2926
+ const offset = ({
2927
+ for: 2, // loop > if (wanted branch) > block (we are here)
2928
+ while: 2, // loop > if (wanted branch) (we are here)
2929
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2930
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2931
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2932
+ })[type];
2933
+
2600
2934
  return [
2601
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2935
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2602
2936
  ];
2603
2937
  };
2604
2938
 
2605
2939
  const generateContinue = (scope, decl) => {
2606
- const nearestLoop = depth.length - getNearestLoop();
2940
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2941
+ const type = depth[target];
2942
+
2943
+ // different loop types have different branch offsets
2944
+ // as they have different wasm block/loop/if structures
2945
+ // we need to use the right offset by type to branch to the one we want
2946
+ // for a continue: do test for the loop, and then loop depending on that success
2947
+ const offset = ({
2948
+ for: 3, // loop (wanted branch) > if > block (we are here)
2949
+ while: 1, // loop (wanted branch) > if (we are here)
2950
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2951
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2952
+ })[type];
2953
+
2607
2954
  return [
2608
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2955
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2609
2956
  ];
2610
2957
  };
2611
2958
 
2959
+ const generateLabel = (scope, decl) => {
2960
+ scope.labels ??= new Map();
2961
+
2962
+ const name = decl.label.name;
2963
+ scope.labels.set(name, depth.length);
2964
+
2965
+ return generate(scope, decl.body);
2966
+ };
2967
+
2612
2968
  const generateThrow = (scope, decl) => {
2613
2969
  scope.throws = true;
2614
2970
 
@@ -2641,7 +2997,7 @@ const generateThrow = (scope, decl) => {
2641
2997
  };
2642
2998
 
2643
2999
  const generateTry = (scope, decl) => {
2644
- if (decl.finalizer) return todo('try finally not implemented yet');
3000
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2645
3001
 
2646
3002
  const out = [];
2647
3003
 
@@ -2672,7 +3028,7 @@ const generateAssignPat = (scope, decl) => {
2672
3028
  // TODO
2673
3029
  // if identifier declared, use that
2674
3030
  // else, use default (right)
2675
- return todo('assignment pattern (optional arg)');
3031
+ return todo(scope, 'assignment pattern (optional arg)');
2676
3032
  };
2677
3033
 
2678
3034
  let pages = new Map();
@@ -2751,16 +3107,22 @@ const getAllocType = itemType => {
2751
3107
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2752
3108
  const out = [];
2753
3109
 
3110
+ scope.arrays ??= new Map();
3111
+
2754
3112
  let firstAssign = false;
2755
- if (!arrays.has(name) || name === '$undeclared') {
3113
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2756
3114
  firstAssign = true;
2757
3115
 
2758
3116
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2759
3117
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2760
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3118
+
3119
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3120
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2761
3121
  }
2762
3122
 
2763
- const pointer = arrays.get(name);
3123
+ const pointer = scope.arrays.get(name);
3124
+
3125
+ const local = global ? globals[name] : scope.locals[name];
2764
3126
 
2765
3127
  const useRawElements = !!decl.rawElements;
2766
3128
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2794,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2794
3156
  return [ out, pointer ];
2795
3157
  }
2796
3158
 
3159
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3160
+ if (pointerTmp != null) {
3161
+ out.push(
3162
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3163
+ Opcodes.i32_to_u,
3164
+ [ Opcodes.local_set, pointerTmp ]
3165
+ );
3166
+ }
3167
+
3168
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3169
+
2797
3170
  // store length as 0th array
2798
3171
  out.push(
2799
- ...number(0, Valtype.i32),
3172
+ ...pointerWasm,
2800
3173
  ...number(length, Valtype.i32),
2801
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3174
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2802
3175
  );
2803
3176
 
2804
3177
  const storeOp = StoreOps[itemType];
@@ -2807,14 +3180,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2807
3180
  if (elements[i] == null) continue;
2808
3181
 
2809
3182
  out.push(
2810
- ...number(0, Valtype.i32),
3183
+ ...pointerWasm,
2811
3184
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2812
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3185
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2813
3186
  );
2814
3187
  }
2815
3188
 
2816
3189
  // local value as pointer
2817
- out.push(...number(pointer));
3190
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2818
3191
 
2819
3192
  return [ out, pointer ];
2820
3193
  };
@@ -2846,20 +3219,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2846
3219
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2847
3220
  };
2848
3221
 
2849
- let arrays = new Map();
2850
3222
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2851
3223
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2852
3224
  };
2853
3225
 
2854
3226
  export const generateMember = (scope, decl, _global, _name) => {
2855
3227
  const name = decl.object.name;
2856
- const pointer = arrays.get(name);
3228
+ const pointer = scope.arrays?.get(name);
2857
3229
 
2858
- const aotPointer = pointer != null;
3230
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2859
3231
 
2860
3232
  // hack: .length
2861
3233
  if (decl.property.name === 'length') {
2862
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3234
+ const func = funcs.find(x => x.name === name);
3235
+ if (func) {
3236
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3237
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3238
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3239
+ }
3240
+
3241
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3242
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3243
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3244
+
2863
3245
  return [
2864
3246
  ...(aotPointer ? number(0, Valtype.i32) : [
2865
3247
  ...generate(scope, decl.object),
@@ -2903,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2903
3285
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2904
3286
 
2905
3287
  ...number(TYPES.number, Valtype.i32),
2906
- setLastType(scope)
3288
+ ...setLastType(scope)
2907
3289
  ],
2908
3290
 
2909
3291
  [TYPES.string]: [
@@ -2935,7 +3317,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2935
3317
  ...number(newPointer),
2936
3318
 
2937
3319
  ...number(TYPES.string, Valtype.i32),
2938
- setLastType(scope)
3320
+ ...setLastType(scope)
2939
3321
  ],
2940
3322
  [TYPES._bytestring]: [
2941
3323
  // setup new/out array
@@ -2954,19 +3336,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2954
3336
  ]),
2955
3337
 
2956
3338
  // load current string ind {arg}
2957
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3339
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2958
3340
 
2959
3341
  // store to new string ind 0
2960
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3342
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2961
3343
 
2962
3344
  // return new string (page)
2963
3345
  ...number(newPointer),
2964
3346
 
2965
3347
  ...number(TYPES._bytestring, Valtype.i32),
2966
- setLastType(scope)
3348
+ ...setLastType(scope)
2967
3349
  ],
2968
3350
 
2969
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3351
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2970
3352
  });
2971
3353
  };
2972
3354
 
@@ -2976,28 +3358,36 @@ const objectHack = node => {
2976
3358
  if (!node) return node;
2977
3359
 
2978
3360
  if (node.type === 'MemberExpression') {
2979
- if (node.computed || node.optional) return node;
3361
+ const out = (() => {
3362
+ if (node.computed || node.optional) return;
2980
3363
 
2981
- let objectName = node.object.name;
3364
+ let objectName = node.object.name;
2982
3365
 
2983
- // if object is not identifier or another member exp, give up
2984
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3366
+ // if object is not identifier or another member exp, give up
3367
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3368
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2985
3369
 
2986
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3370
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2987
3371
 
2988
- // if .length, give up (hack within a hack!)
2989
- if (node.property.name === 'length') return node;
3372
+ // if .length, give up (hack within a hack!)
3373
+ if (node.property.name === 'length') {
3374
+ node.object = objectHack(node.object);
3375
+ return;
3376
+ }
2990
3377
 
2991
- // no object name, give up
2992
- if (!objectName) return node;
3378
+ // no object name, give up
3379
+ if (!objectName) return;
2993
3380
 
2994
- const name = '__' + objectName + '_' + node.property.name;
2995
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3381
+ const name = '__' + objectName + '_' + node.property.name;
3382
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2996
3383
 
2997
- return {
2998
- type: 'Identifier',
2999
- name
3000
- };
3384
+ return {
3385
+ type: 'Identifier',
3386
+ name
3387
+ };
3388
+ })();
3389
+
3390
+ if (out) return out;
3001
3391
  }
3002
3392
 
3003
3393
  for (const x in node) {
@@ -3011,8 +3401,8 @@ const objectHack = node => {
3011
3401
  };
3012
3402
 
3013
3403
  const generateFunc = (scope, decl) => {
3014
- if (decl.async) return todo('async functions are not supported');
3015
- if (decl.generator) return todo('generator functions are not supported');
3404
+ if (decl.async) return todo(scope, 'async functions are not supported');
3405
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3016
3406
 
3017
3407
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3018
3408
  const params = decl.params ?? [];
@@ -3028,6 +3418,11 @@ const generateFunc = (scope, decl) => {
3028
3418
  name
3029
3419
  };
3030
3420
 
3421
+ if (typedInput && decl.returnType) {
3422
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3423
+ innerScope.returns = [ valtypeBinary ];
3424
+ }
3425
+
3031
3426
  for (let i = 0; i < params.length; i++) {
3032
3427
  allocVar(innerScope, params[i].name, false);
3033
3428
 
@@ -3090,16 +3485,6 @@ const generateCode = (scope, decl) => {
3090
3485
  };
3091
3486
 
3092
3487
  const internalConstrs = {
3093
- Boolean: {
3094
- generate: (scope, decl) => {
3095
- if (decl.arguments.length === 0) return number(0);
3096
-
3097
- // should generate/run all args
3098
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3099
- },
3100
- type: TYPES.boolean
3101
- },
3102
-
3103
3488
  Array: {
3104
3489
  generate: (scope, decl, global, name) => {
3105
3490
  // new Array(i0, i1, ...)
@@ -3117,7 +3502,7 @@ const internalConstrs = {
3117
3502
 
3118
3503
  // todo: check in wasm instead of here
3119
3504
  const literalValue = arg.value ?? 0;
3120
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3505
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3121
3506
 
3122
3507
  return [
3123
3508
  ...number(0, Valtype.i32),
@@ -3128,7 +3513,8 @@ const internalConstrs = {
3128
3513
  ...number(pointer)
3129
3514
  ];
3130
3515
  },
3131
- type: TYPES._array
3516
+ type: TYPES._array,
3517
+ length: 1
3132
3518
  },
3133
3519
 
3134
3520
  __Array_of: {
@@ -3140,7 +3526,131 @@ const internalConstrs = {
3140
3526
  }, global, name);
3141
3527
  },
3142
3528
  type: TYPES._array,
3529
+ notConstr: true,
3530
+ length: 0
3531
+ },
3532
+
3533
+ __Porffor_fastOr: {
3534
+ generate: (scope, decl) => {
3535
+ const out = [];
3536
+
3537
+ for (let i = 0; i < decl.arguments.length; i++) {
3538
+ out.push(
3539
+ ...generate(scope, decl.arguments[i]),
3540
+ Opcodes.i32_to_u,
3541
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3542
+ );
3543
+ }
3544
+
3545
+ out.push(Opcodes.i32_from_u);
3546
+
3547
+ return out;
3548
+ },
3549
+ type: TYPES.boolean,
3550
+ notConstr: true
3551
+ },
3552
+
3553
+ __Porffor_fastAnd: {
3554
+ generate: (scope, decl) => {
3555
+ const out = [];
3556
+
3557
+ for (let i = 0; i < decl.arguments.length; i++) {
3558
+ out.push(
3559
+ ...generate(scope, decl.arguments[i]),
3560
+ Opcodes.i32_to_u,
3561
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3562
+ );
3563
+ }
3564
+
3565
+ out.push(Opcodes.i32_from_u);
3566
+
3567
+ return out;
3568
+ },
3569
+ type: TYPES.boolean,
3143
3570
  notConstr: true
3571
+ },
3572
+
3573
+ Boolean: {
3574
+ generate: (scope, decl) => {
3575
+ // todo: boolean object when used as constructor
3576
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3577
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3578
+ },
3579
+ type: TYPES.boolean,
3580
+ length: 1
3581
+ },
3582
+
3583
+ __Math_max: {
3584
+ generate: (scope, decl) => {
3585
+ const out = [
3586
+ ...number(-Infinity)
3587
+ ];
3588
+
3589
+ for (let i = 0; i < decl.arguments.length; i++) {
3590
+ out.push(
3591
+ ...generate(scope, decl.arguments[i]),
3592
+ [ Opcodes.f64_max ]
3593
+ );
3594
+ }
3595
+
3596
+ return out;
3597
+ },
3598
+ type: TYPES.number,
3599
+ notConstr: true,
3600
+ length: 2
3601
+ },
3602
+
3603
+ __Math_min: {
3604
+ generate: (scope, decl) => {
3605
+ const out = [
3606
+ ...number(Infinity)
3607
+ ];
3608
+
3609
+ for (let i = 0; i < decl.arguments.length; i++) {
3610
+ out.push(
3611
+ ...generate(scope, decl.arguments[i]),
3612
+ [ Opcodes.f64_min ]
3613
+ );
3614
+ }
3615
+
3616
+ return out;
3617
+ },
3618
+ type: TYPES.number,
3619
+ notConstr: true,
3620
+ length: 2
3621
+ },
3622
+
3623
+ __console_log: {
3624
+ generate: (scope, decl) => {
3625
+ const out = [];
3626
+
3627
+ for (let i = 0; i < decl.arguments.length; i++) {
3628
+ out.push(
3629
+ ...generateCall(scope, {
3630
+ callee: {
3631
+ type: 'Identifier',
3632
+ name: '__Porffor_print'
3633
+ },
3634
+ arguments: [ decl.arguments[i] ]
3635
+ }),
3636
+
3637
+ // print space
3638
+ ...number(32),
3639
+ [ Opcodes.call, importedFuncs.printChar ]
3640
+ );
3641
+ }
3642
+
3643
+ // print newline
3644
+ out.push(
3645
+ ...number(10),
3646
+ [ Opcodes.call, importedFuncs.printChar ]
3647
+ );
3648
+
3649
+ return out;
3650
+ },
3651
+ type: TYPES.undefined,
3652
+ notConstr: true,
3653
+ length: 0
3144
3654
  }
3145
3655
  };
3146
3656
 
@@ -3169,20 +3679,23 @@ export default program => {
3169
3679
  funcs = [];
3170
3680
  funcIndex = {};
3171
3681
  depth = [];
3172
- arrays = new Map();
3173
3682
  pages = new Map();
3174
3683
  data = [];
3175
3684
  currentFuncIndex = importedFuncs.length;
3176
3685
 
3177
3686
  globalThis.valtype = 'f64';
3178
3687
 
3179
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3688
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3180
3689
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3181
3690
 
3182
3691
  globalThis.valtypeBinary = Valtype[valtype];
3183
3692
 
3184
3693
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3185
3694
 
3695
+ globalThis.pageSize = PageSize;
3696
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3697
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3698
+
3186
3699
  // set generic opcodes for current valtype
3187
3700
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3188
3701
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3191,10 +3704,10 @@ export default program => {
3191
3704
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3192
3705
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3193
3706
 
3194
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3195
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3196
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3197
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3707
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3708
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3709
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3710
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3198
3711
 
3199
3712
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3200
3713
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3207,10 +3720,6 @@ export default program => {
3207
3720
 
3208
3721
  program.id = { name: 'main' };
3209
3722
 
3210
- globalThis.pageSize = PageSize;
3211
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3212
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3213
-
3214
3723
  const scope = {
3215
3724
  locals: {},
3216
3725
  localInd: 0
@@ -3221,7 +3730,7 @@ export default program => {
3221
3730
  body: program.body
3222
3731
  };
3223
3732
 
3224
- if (Prefs.astLog) console.log(program.body.body);
3733
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3225
3734
 
3226
3735
  generateFunc(scope, program);
3227
3736
 
@@ -3238,7 +3747,11 @@ export default program => {
3238
3747
  }
3239
3748
 
3240
3749
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3241
- main.returns = [];
3750
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3751
+ main.wasm.splice(main.wasm.length - 1, 1);
3752
+ } else {
3753
+ main.returns = [];
3754
+ }
3242
3755
  }
3243
3756
 
3244
3757
  if (lastInst[0] === Opcodes.call) {