porffor 0.2.0-6aff0fa → 0.2.0-75bc012

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CONTRIBUTING.md +256 -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/boolean.ts +20 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +2070 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/int.ts +147 -0
  17. package/compiler/builtins/number.ts +534 -0
  18. package/compiler/builtins/porffor.d.ts +43 -7
  19. package/compiler/builtins/string.ts +1080 -0
  20. package/compiler/builtins/tostring.ts +25 -0
  21. package/compiler/builtins.js +398 -115
  22. package/compiler/{codeGen.js → codegen.js} +856 -323
  23. package/compiler/decompile.js +0 -1
  24. package/compiler/embedding.js +22 -22
  25. package/compiler/encoding.js +108 -10
  26. package/compiler/generated_builtins.js +1504 -2
  27. package/compiler/index.js +16 -14
  28. package/compiler/log.js +2 -2
  29. package/compiler/opt.js +23 -22
  30. package/compiler/parse.js +30 -22
  31. package/compiler/precompile.js +26 -27
  32. package/compiler/prefs.js +7 -6
  33. package/compiler/prototype.js +16 -32
  34. package/compiler/types.js +37 -0
  35. package/compiler/wasmSpec.js +14 -1
  36. package/compiler/wrap.js +41 -44
  37. package/package.json +9 -5
  38. package/porf +2 -0
  39. package/rhemyn/compile.js +44 -26
  40. package/rhemyn/parse.js +322 -320
  41. package/rhemyn/test/parse.js +58 -58
  42. package/runner/compare.js +34 -34
  43. package/runner/debug.js +122 -0
  44. package/runner/index.js +69 -12
  45. package/runner/profiler.js +45 -26
  46. package/runner/repl.js +42 -9
  47. package/runner/sizes.js +37 -37
  48. package/runner/info.js +0 -89
  49. package/runner/transform.js +0 -15
  50. package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
10
  import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
11
12
 
12
13
  let globals = {};
13
14
  let globalInd = 0;
@@ -24,38 +25,44 @@ 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';
62
+ const hasFuncWithName = name => {
63
+ const func = funcs.find(x => x.name === name);
64
+ return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
65
+ };
59
66
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
60
67
  switch (decl.type) {
61
68
  case 'BinaryExpression':
@@ -105,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
112
  return generateUnary(scope, decl);
106
113
 
107
114
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
115
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
116
 
110
117
  case 'IfStatement':
111
118
  return generateIf(scope, decl);
@@ -116,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
123
  case 'WhileStatement':
117
124
  return generateWhile(scope, decl);
118
125
 
126
+ case 'DoWhileStatement':
127
+ return generateDoWhile(scope, decl);
128
+
119
129
  case 'ForOfStatement':
120
130
  return generateForOf(scope, decl);
121
131
 
@@ -125,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
135
  case 'ContinueStatement':
126
136
  return generateContinue(scope, decl);
127
137
 
138
+ case 'LabeledStatement':
139
+ return generateLabel(scope, decl);
140
+
128
141
  case 'EmptyStatement':
129
142
  return generateEmpty(scope, decl);
130
143
 
@@ -138,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
151
  return generateTry(scope, decl);
139
152
 
140
153
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
154
+ // todo: hook into terminal debugger
142
155
  return [];
143
156
 
144
157
  case 'ArrayExpression':
@@ -152,10 +165,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
165
  const funcsBefore = funcs.length;
153
166
  generate(scope, decl.declaration);
154
167
 
155
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
168
+ if (funcsBefore !== funcs.length) {
169
+ // new func added
170
+ const newFunc = funcs[funcs.length - 1];
171
+ newFunc.export = true;
172
+ }
156
173
 
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
174
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
175
+
176
+ // const newFunc = funcs[funcs.length - 1];
177
+ // newFunc.export = true;
159
178
 
160
179
  return [];
161
180
 
@@ -186,7 +205,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
186
205
  }
187
206
 
188
207
  let inst = Opcodes[asm[0].replace('.', '_')];
189
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
208
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
209
 
191
210
  if (!Array.isArray(inst)) inst = [ inst ];
192
211
  const immediates = asm.slice(1).map(x => {
@@ -195,40 +214,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
195
214
  return int;
196
215
  });
197
216
 
198
- out.push([ ...inst, ...immediates ]);
217
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
199
218
  }
200
219
 
201
220
  return out;
202
221
  },
203
222
 
204
223
  __Porffor_bs: str => [
205
- ...makeString(scope, str, undefined, undefined, true),
224
+ ...makeString(scope, str, global, name, true),
206
225
 
207
- ...number(TYPES._bytestring, Valtype.i32),
208
- setLastType(scope)
226
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
227
+ ...number(TYPES.bytestring, Valtype.i32),
228
+ ...setLastType(scope)
229
+ ])
209
230
  ],
210
231
  __Porffor_s: str => [
211
- ...makeString(scope, str, undefined, undefined, false),
232
+ ...makeString(scope, str, global, name, false),
212
233
 
213
- ...number(TYPES.string, Valtype.i32),
214
- setLastType(scope)
234
+ ...(name ? setType(scope, name, TYPES.string) : [
235
+ ...number(TYPES.string, Valtype.i32),
236
+ ...setLastType(scope)
237
+ ])
215
238
  ],
216
239
  };
217
240
 
218
- const name = decl.tag.name;
241
+ const func = decl.tag.name;
219
242
  // hack for inline asm
220
- if (!funcs[name]) return todo('tagged template expressions not implemented');
243
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
244
 
222
245
  const { quasis, expressions } = decl.quasi;
223
246
  let str = quasis[0].value.raw;
224
247
 
225
248
  for (let i = 0; i < expressions.length; i++) {
226
249
  const e = expressions[i];
227
- str += lookupName(scope, e.name)[0].idx;
250
+ if (!e.name) {
251
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
252
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
253
+ } else todo(scope, 'unsupported expression in intrinsic');
254
+ } else str += lookupName(scope, e.name)[0].idx;
255
+
228
256
  str += quasis[i + 1].value.raw;
229
257
  }
230
258
 
231
- return funcs[name](str);
259
+ return funcs[func](str);
232
260
  }
233
261
 
234
262
  default:
@@ -238,7 +266,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
238
266
  return [];
239
267
  }
240
268
 
241
- return todo(`no generation for ${decl.type}!`);
269
+ return todo(scope, `no generation for ${decl.type}!`);
242
270
  }
243
271
  };
244
272
 
@@ -266,7 +294,7 @@ const lookupName = (scope, _name) => {
266
294
  return [ undefined, undefined ];
267
295
  };
268
296
 
269
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
297
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
270
298
  ...generateThrow(scope, {
271
299
  argument: {
272
300
  type: 'NewExpression',
@@ -293,7 +321,7 @@ const generateIdent = (scope, decl) => {
293
321
 
294
322
  let wasm = builtinVars[name];
295
323
  if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
- return wasm;
324
+ return wasm.slice();
297
325
  }
298
326
 
299
327
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -301,6 +329,11 @@ const generateIdent = (scope, decl) => {
301
329
  return number(1);
302
330
  }
303
331
 
332
+ if (isExistingProtoFunc(name)) {
333
+ // todo: return an actual something
334
+ return number(1);
335
+ }
336
+
304
337
  if (local?.idx === undefined) {
305
338
  // no local var with name
306
339
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -331,14 +364,18 @@ const generateReturn = (scope, decl) => {
331
364
  // just bare "return"
332
365
  return [
333
366
  ...number(UNDEFINED), // "undefined" if func returns
334
- ...number(TYPES.undefined, Valtype.i32), // type undefined
367
+ ...(scope.returnType != null ? [] : [
368
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
369
+ ]),
335
370
  [ Opcodes.return ]
336
371
  ];
337
372
  }
338
373
 
339
374
  return [
340
375
  ...generate(scope, decl.argument),
341
- ...getNodeType(scope, decl.argument),
376
+ ...(scope.returnType != null ? [] : [
377
+ ...getNodeType(scope, decl.argument)
378
+ ]),
342
379
  [ Opcodes.return ]
343
380
  ];
344
381
  };
@@ -352,7 +389,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
352
389
  return idx;
353
390
  };
354
391
 
355
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
392
+ 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);
356
394
 
357
395
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
358
396
  const checks = {
@@ -361,7 +399,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
361
399
  '??': nullish
362
400
  };
363
401
 
364
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
402
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
365
403
 
366
404
  // generic structure for {a} OP {b}
367
405
  // -->
@@ -369,8 +407,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
369
407
 
370
408
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
371
409
  // (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]);
410
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
411
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
374
412
 
375
413
  const canInt = leftIsInt && rightIsInt;
376
414
 
@@ -387,12 +425,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
387
425
  ...right,
388
426
  // note type
389
427
  ...rightType,
390
- setLastType(scope),
428
+ ...setLastType(scope),
391
429
  [ Opcodes.else ],
392
430
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
393
431
  // note type
394
432
  ...leftType,
395
- setLastType(scope),
433
+ ...setLastType(scope),
396
434
  [ Opcodes.end ],
397
435
  Opcodes.i32_from
398
436
  ];
@@ -406,17 +444,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
406
444
  ...right,
407
445
  // note type
408
446
  ...rightType,
409
- setLastType(scope),
447
+ ...setLastType(scope),
410
448
  [ Opcodes.else ],
411
449
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
412
450
  // note type
413
451
  ...leftType,
414
- setLastType(scope),
452
+ ...setLastType(scope),
415
453
  [ Opcodes.end ]
416
454
  ];
417
455
  };
418
456
 
419
- const concatStrings = (scope, left, right, global, name, assign) => {
457
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
420
458
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
421
459
  // todo: convert left and right to strings if not
422
460
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -426,8 +464,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
426
464
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
427
465
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
428
466
 
429
- if (assign) {
430
- const pointer = arrays.get(name ?? '$undeclared');
467
+ if (assign && Prefs.aotPointerOpt) {
468
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
431
469
 
432
470
  return [
433
471
  // setup right
@@ -452,11 +490,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
452
490
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
453
491
 
454
492
  // copy right
455
- // dst = out pointer + length size + current length * i16 size
493
+ // dst = out pointer + length size + current length * sizeof valtype
456
494
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
457
495
 
458
496
  [ Opcodes.local_get, leftLength ],
459
- ...number(ValtypeSize.i16, Valtype.i32),
497
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
460
498
  [ Opcodes.i32_mul ],
461
499
  [ Opcodes.i32_add ],
462
500
 
@@ -465,9 +503,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
465
503
  ...number(ValtypeSize.i32, Valtype.i32),
466
504
  [ Opcodes.i32_add ],
467
505
 
468
- // size = right length * i16 size
506
+ // size = right length * sizeof valtype
469
507
  [ Opcodes.local_get, rightLength ],
470
- ...number(ValtypeSize.i16, Valtype.i32),
508
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
471
509
  [ Opcodes.i32_mul ],
472
510
 
473
511
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -525,11 +563,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
525
563
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
526
564
 
527
565
  // copy right
528
- // dst = out pointer + length size + left length * i16 size
566
+ // dst = out pointer + length size + left length * sizeof valtype
529
567
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
530
568
 
531
569
  [ Opcodes.local_get, leftLength ],
532
- ...number(ValtypeSize.i16, Valtype.i32),
570
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
533
571
  [ Opcodes.i32_mul ],
534
572
  [ Opcodes.i32_add ],
535
573
 
@@ -538,9 +576,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
538
576
  ...number(ValtypeSize.i32, Valtype.i32),
539
577
  [ Opcodes.i32_add ],
540
578
 
541
- // size = right length * i16 size
579
+ // size = right length * sizeof valtype
542
580
  [ Opcodes.local_get, rightLength ],
543
- ...number(ValtypeSize.i16, Valtype.i32),
581
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
544
582
  [ Opcodes.i32_mul ],
545
583
 
546
584
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -550,7 +588,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
550
588
  ];
551
589
  };
552
590
 
553
- const compareStrings = (scope, left, right) => {
591
+ const compareStrings = (scope, left, right, bytestrings = false) => {
554
592
  // todo: this should be rewritten into a func
555
593
  // todo: convert left and right to strings if not
556
594
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -559,7 +597,6 @@ const compareStrings = (scope, left, right) => {
559
597
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
560
598
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
561
599
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
562
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
563
600
 
564
601
  const index = localTmp(scope, 'compare_index', Valtype.i32);
565
602
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -587,7 +624,6 @@ const compareStrings = (scope, left, right) => {
587
624
 
588
625
  [ Opcodes.local_get, rightPointer ],
589
626
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
590
- [ Opcodes.local_tee, rightLength ],
591
627
 
592
628
  // fast path: check leftLength != rightLength
593
629
  [ Opcodes.i32_ne ],
@@ -602,11 +638,13 @@ const compareStrings = (scope, left, right) => {
602
638
  ...number(0, Valtype.i32),
603
639
  [ Opcodes.local_set, index ],
604
640
 
605
- // setup index end as length * sizeof i16 (2)
641
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
606
642
  // we do this instead of having to do mul/div each iter for perf™
607
643
  [ Opcodes.local_get, leftLength ],
608
- ...number(ValtypeSize.i16, Valtype.i32),
609
- [ Opcodes.i32_mul ],
644
+ ...(bytestrings ? [] : [
645
+ ...number(ValtypeSize.i16, Valtype.i32),
646
+ [ Opcodes.i32_mul ],
647
+ ]),
610
648
  [ Opcodes.local_set, indexEnd ],
611
649
 
612
650
  // iterate over each char and check if eq
@@ -616,13 +654,17 @@ const compareStrings = (scope, left, right) => {
616
654
  [ Opcodes.local_get, index ],
617
655
  [ Opcodes.local_get, leftPointer ],
618
656
  [ Opcodes.i32_add ],
619
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
657
+ bytestrings ?
658
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
659
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
620
660
 
621
661
  // fetch right
622
662
  [ Opcodes.local_get, index ],
623
663
  [ Opcodes.local_get, rightPointer ],
624
664
  [ Opcodes.i32_add ],
625
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
665
+ bytestrings ?
666
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
667
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
626
668
 
627
669
  // not equal, "return" false
628
670
  [ Opcodes.i32_ne ],
@@ -631,13 +673,13 @@ const compareStrings = (scope, left, right) => {
631
673
  [ Opcodes.br, 2 ],
632
674
  [ Opcodes.end ],
633
675
 
634
- // index += sizeof i16 (2)
676
+ // index += sizeof valtype (1 for bytestring, 2 for string)
635
677
  [ Opcodes.local_get, index ],
636
- ...number(ValtypeSize.i16, Valtype.i32),
678
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
637
679
  [ Opcodes.i32_add ],
638
680
  [ Opcodes.local_tee, index ],
639
681
 
640
- // if index != index end (length * sizeof 16), loop
682
+ // if index != index end (length * sizeof valtype), loop
641
683
  [ Opcodes.local_get, indexEnd ],
642
684
  [ Opcodes.i32_ne ],
643
685
  [ Opcodes.br_if, 0 ],
@@ -658,13 +700,14 @@ const compareStrings = (scope, left, right) => {
658
700
  };
659
701
 
660
702
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
661
- if (isIntOp(wasm[wasm.length - 1])) return [
703
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
662
704
  ...wasm,
663
705
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
664
706
  ];
707
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
665
708
 
666
709
  const useTmp = knownType(scope, type) == null;
667
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
710
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
668
711
 
669
712
  const def = [
670
713
  // if value != 0
@@ -684,7 +727,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
684
727
 
685
728
  ...typeSwitch(scope, type, {
686
729
  // [TYPES.number]: def,
687
- [TYPES._array]: [
730
+ [TYPES.array]: [
688
731
  // arrays are always truthy
689
732
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
690
733
  ],
@@ -700,7 +743,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
743
  [ Opcodes.i32_eqz ], */
701
744
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
702
745
  ],
703
- [TYPES._bytestring]: [ // duplicate of string
746
+ [TYPES.bytestring]: [ // duplicate of string
704
747
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
705
748
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
706
749
 
@@ -716,14 +759,14 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
716
759
 
717
760
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
718
761
  const useTmp = knownType(scope, type) == null;
719
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
762
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
720
763
 
721
764
  return [
722
765
  ...wasm,
723
766
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
724
767
 
725
768
  ...typeSwitch(scope, type, {
726
- [TYPES._array]: [
769
+ [TYPES.array]: [
727
770
  // arrays are always truthy
728
771
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
729
772
  ],
@@ -738,7 +781,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
738
781
  [ Opcodes.i32_eqz ],
739
782
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
740
783
  ],
741
- [TYPES._bytestring]: [ // duplicate of string
784
+ [TYPES.bytestring]: [ // duplicate of string
742
785
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
743
786
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
744
787
 
@@ -762,7 +805,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
762
805
 
763
806
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
764
807
  const useTmp = knownType(scope, type) == null;
765
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
808
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
766
809
 
767
810
  return [
768
811
  ...wasm,
@@ -856,11 +899,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
856
899
  // todo: if equality op and an operand is undefined, return false
857
900
  // todo: niche null hell with 0
858
901
 
859
- // todo: this should be dynamic but for now only static
860
902
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
861
903
  if (op === '+') {
904
+ // todo: this should be dynamic too but for now only static
862
905
  // string concat (a + b)
863
- return concatStrings(scope, left, right, _global, _name, assign);
906
+ return concatStrings(scope, left, right, _global, _name, assign, false);
864
907
  }
865
908
 
866
909
  // not an equality op, NaN
@@ -883,6 +926,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
883
926
  }
884
927
  }
885
928
 
929
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
930
+ if (op === '+') {
931
+ // todo: this should be dynamic too but for now only static
932
+ // string concat (a + b)
933
+ return concatStrings(scope, left, right, _global, _name, assign, true);
934
+ }
935
+
936
+ // not an equality op, NaN
937
+ if (!eqOp) return number(NaN);
938
+
939
+ // else leave bool ops
940
+ // todo: convert string to number if string and number/bool
941
+ // todo: string (>|>=|<|<=) string
942
+
943
+ // string comparison
944
+ if (op === '===' || op === '==') {
945
+ return compareStrings(scope, left, right, true);
946
+ }
947
+
948
+ if (op === '!==' || op === '!=') {
949
+ return [
950
+ ...compareStrings(scope, left, right, true),
951
+ [ Opcodes.i32_eqz ]
952
+ ];
953
+ }
954
+ }
955
+
886
956
  let ops = operatorOpcode[valtype][op];
887
957
 
888
958
  // some complex ops are implemented as builtin funcs
@@ -898,23 +968,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
898
968
  ]);
899
969
  }
900
970
 
901
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
971
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
902
972
 
903
973
  if (!Array.isArray(ops)) ops = [ ops ];
904
974
  ops = [ ops ];
905
975
 
906
976
  let tmpLeft, tmpRight;
907
977
  // 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
- }
978
+ // todo: intelligent partial skip later
979
+ // if neither known are string, stop this madness
980
+ // we already do known checks earlier, so don't need to recheck
914
981
 
982
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
915
983
  tmpLeft = localTmp(scope, '__tmpop_left');
916
984
  tmpRight = localTmp(scope, '__tmpop_right');
917
985
 
986
+ // returns false for one string, one not - but more ops/slower
987
+ // ops.unshift(...stringOnly([
988
+ // // if left is string
989
+ // ...leftType,
990
+ // ...number(TYPES.string, Valtype.i32),
991
+ // [ Opcodes.i32_eq ],
992
+
993
+ // // if right is string
994
+ // ...rightType,
995
+ // ...number(TYPES.string, Valtype.i32),
996
+ // [ Opcodes.i32_eq ],
997
+
998
+ // // if either are true
999
+ // [ Opcodes.i32_or ],
1000
+ // [ Opcodes.if, Blocktype.void ],
1001
+
1002
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
1003
+ // // if left is not string
1004
+ // ...leftType,
1005
+ // ...number(TYPES.string, Valtype.i32),
1006
+ // [ Opcodes.i32_ne ],
1007
+
1008
+ // // if right is not string
1009
+ // ...rightType,
1010
+ // ...number(TYPES.string, Valtype.i32),
1011
+ // [ Opcodes.i32_ne ],
1012
+
1013
+ // // if either are true
1014
+ // [ Opcodes.i32_or ],
1015
+ // [ Opcodes.if, Blocktype.void ],
1016
+ // ...number(0, Valtype.i32),
1017
+ // [ Opcodes.br, 2 ],
1018
+ // [ Opcodes.end ],
1019
+
1020
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1021
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1022
+ // [ Opcodes.br, 1 ],
1023
+ // [ Opcodes.end ],
1024
+ // ]));
1025
+
1026
+ // does not handle one string, one not (such cases go past)
918
1027
  ops.unshift(...stringOnly([
919
1028
  // if left is string
920
1029
  ...leftType,
@@ -926,30 +1035,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
926
1035
  ...number(TYPES.string, Valtype.i32),
927
1036
  [ Opcodes.i32_eq ],
928
1037
 
929
- // if either are true
930
- [ Opcodes.i32_or ],
1038
+ // if both are true
1039
+ [ Opcodes.i32_and ],
931
1040
  [ Opcodes.if, Blocktype.void ],
1041
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1042
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1043
+ [ Opcodes.br, 1 ],
1044
+ [ Opcodes.end ],
932
1045
 
933
- // todo: convert non-strings to strings, for now fail immediately if one is not
934
- // if left is not string
1046
+ // if left is bytestring
935
1047
  ...leftType,
936
- ...number(TYPES.string, Valtype.i32),
937
- [ Opcodes.i32_ne ],
1048
+ ...number(TYPES.bytestring, Valtype.i32),
1049
+ [ Opcodes.i32_eq ],
938
1050
 
939
- // if right is not string
1051
+ // if right is bytestring
940
1052
  ...rightType,
941
- ...number(TYPES.string, Valtype.i32),
942
- [ Opcodes.i32_ne ],
1053
+ ...number(TYPES.bytestring, Valtype.i32),
1054
+ [ Opcodes.i32_eq ],
943
1055
 
944
- // if either are true
945
- [ Opcodes.i32_or ],
1056
+ // if both are true
1057
+ [ Opcodes.i32_and ],
946
1058
  [ 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 ] ]),
1059
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
953
1060
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
954
1061
  [ Opcodes.br, 1 ],
955
1062
  [ Opcodes.end ],
@@ -961,7 +1068,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
961
1068
  // endOut.push(stringOnly([ Opcodes.end ]));
962
1069
  endOut.unshift(stringOnly([ Opcodes.end ]));
963
1070
  // }
964
- })();
1071
+ }
965
1072
 
966
1073
  return finalize([
967
1074
  ...left,
@@ -1037,7 +1144,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1037
1144
  params,
1038
1145
  locals,
1039
1146
  returns,
1040
- returnType: TYPES[returnType ?? 'number'],
1147
+ returnType: returnType ?? TYPES.number,
1041
1148
  wasm,
1042
1149
  internal: true,
1043
1150
  index: currentFuncIndex++
@@ -1060,6 +1167,7 @@ const generateLogicExp = (scope, decl) => {
1060
1167
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1061
1168
  };
1062
1169
 
1170
+ // potential future ideas for nan boxing (unused):
1063
1171
  // T = JS type, V = value/pointer
1064
1172
  // 0bTTT
1065
1173
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1073,7 +1181,6 @@ const generateLogicExp = (scope, decl) => {
1073
1181
  // js type: 4 bits
1074
1182
  // internal type: ? bits
1075
1183
  // pointer: 32 bits
1076
-
1077
1184
  // generic
1078
1185
  // 1 23 4 5
1079
1186
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1083,40 +1190,18 @@ const generateLogicExp = (scope, decl) => {
1083
1190
  // 4: internal type
1084
1191
  // 5: pointer
1085
1192
 
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'
1193
+ const isExistingProtoFunc = name => {
1194
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1195
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1196
+
1197
+ return false;
1115
1198
  };
1116
1199
 
1117
1200
  const getType = (scope, _name) => {
1118
1201
  const name = mapName(_name);
1119
1202
 
1203
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1204
+
1120
1205
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1121
1206
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1122
1207
 
@@ -1124,11 +1209,10 @@ const getType = (scope, _name) => {
1124
1209
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1125
1210
 
1126
1211
  let type = TYPES.undefined;
1127
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1212
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1128
1213
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1129
1214
 
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;
1215
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1132
1216
 
1133
1217
  return number(type, Valtype.i32);
1134
1218
  };
@@ -1151,23 +1235,24 @@ const setType = (scope, _name, type) => {
1151
1235
  ];
1152
1236
 
1153
1237
  // throw new Error('could not find var');
1238
+ return [];
1154
1239
  };
1155
1240
 
1156
1241
  const getLastType = scope => {
1157
1242
  scope.gotLastType = true;
1158
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1243
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1159
1244
  };
1160
1245
 
1161
1246
  const setLastType = scope => {
1162
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1247
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1163
1248
  };
1164
1249
 
1165
1250
  const getNodeType = (scope, node) => {
1166
1251
  const inner = () => {
1167
1252
  if (node.type === 'Literal') {
1168
- if (node.regex) return TYPES._regexp;
1253
+ if (node.regex) return TYPES.regexp;
1169
1254
 
1170
- if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1255
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1171
1256
 
1172
1257
  return TYPES[typeof node.value];
1173
1258
  }
@@ -1184,13 +1269,25 @@ const getNodeType = (scope, node) => {
1184
1269
  const name = node.callee.name;
1185
1270
  if (!name) {
1186
1271
  // iife
1187
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1272
+ if (scope.locals['#last_type']) return getLastType(scope);
1188
1273
 
1189
1274
  // presume
1190
1275
  // todo: warn here?
1191
1276
  return TYPES.number;
1192
1277
  }
1193
1278
 
1279
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1280
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1281
+ if (scope.locals['#last_type']) return getLastType(scope);
1282
+
1283
+ // presume
1284
+ // todo: warn here?
1285
+ return TYPES.number;
1286
+ }
1287
+
1288
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1289
+ }
1290
+
1194
1291
  const func = funcs.find(x => x.name === name);
1195
1292
 
1196
1293
  if (func) {
@@ -1198,7 +1295,7 @@ const getNodeType = (scope, node) => {
1198
1295
  if (func.returnType) return func.returnType;
1199
1296
  }
1200
1297
 
1201
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1298
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1202
1299
  if (internalConstrs[name]) return internalConstrs[name].type;
1203
1300
 
1204
1301
  // check if this is a prototype function
@@ -1209,7 +1306,7 @@ const getNodeType = (scope, node) => {
1209
1306
  const spl = name.slice(2).split('_');
1210
1307
 
1211
1308
  const func = spl[spl.length - 1];
1212
- const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1309
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1213
1310
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1214
1311
  }
1215
1312
 
@@ -1218,7 +1315,7 @@ const getNodeType = (scope, node) => {
1218
1315
  return TYPES.number;
1219
1316
  }
1220
1317
 
1221
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1318
+ if (scope.locals['#last_type']) return getLastType(scope);
1222
1319
 
1223
1320
  // presume
1224
1321
  // todo: warn here?
@@ -1261,7 +1358,7 @@ const getNodeType = (scope, node) => {
1261
1358
  }
1262
1359
 
1263
1360
  if (node.type === 'ArrayExpression') {
1264
- return TYPES._array;
1361
+ return TYPES.array;
1265
1362
  }
1266
1363
 
1267
1364
  if (node.type === 'BinaryExpression') {
@@ -1273,6 +1370,7 @@ const getNodeType = (scope, node) => {
1273
1370
 
1274
1371
  // todo: this should be dynamic but for now only static
1275
1372
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1373
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1276
1374
 
1277
1375
  return TYPES.number;
1278
1376
 
@@ -1298,26 +1396,41 @@ const getNodeType = (scope, node) => {
1298
1396
  if (node.operator === '!') return TYPES.boolean;
1299
1397
  if (node.operator === 'void') return TYPES.undefined;
1300
1398
  if (node.operator === 'delete') return TYPES.boolean;
1301
- if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1399
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1302
1400
 
1303
1401
  return TYPES.number;
1304
1402
  }
1305
1403
 
1306
1404
  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
+ }
1412
+ }
1413
+
1307
1414
  // hack: if something.length, number type
1308
1415
  if (node.property.name === 'length') return TYPES.number;
1309
1416
 
1310
1417
  // ts hack
1311
1418
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1312
- if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
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;
1313
1421
 
1314
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1422
+ if (scope.locals['#last_type']) return getLastType(scope);
1315
1423
 
1316
1424
  // presume
1317
1425
  return TYPES.number;
1318
1426
  }
1319
1427
 
1320
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1428
+ if (node.type === 'TaggedTemplateExpression') {
1429
+ // hack
1430
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1431
+ }
1432
+
1433
+ if (scope.locals['#last_type']) return getLastType(scope);
1321
1434
 
1322
1435
  // presume
1323
1436
  // todo: warn here?
@@ -1350,7 +1463,7 @@ const generateLiteral = (scope, decl, global, name) => {
1350
1463
  return makeString(scope, decl.value, global, name);
1351
1464
 
1352
1465
  default:
1353
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1466
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1354
1467
  }
1355
1468
  };
1356
1469
 
@@ -1359,6 +1472,8 @@ const countLeftover = wasm => {
1359
1472
 
1360
1473
  for (let i = 0; i < wasm.length; i++) {
1361
1474
  const inst = wasm[i];
1475
+ if (inst[0] == null) continue;
1476
+
1362
1477
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1363
1478
  if (inst[0] === Opcodes.if) count--;
1364
1479
  if (inst[1] !== Blocktype.void) count++;
@@ -1369,16 +1484,23 @@ const countLeftover = wasm => {
1369
1484
  if (depth === 0)
1370
1485
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1371
1486
  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++;
1487
+ 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
1488
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1374
1489
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1375
1490
  else if (inst[0] === Opcodes.return) count = 0;
1376
1491
  else if (inst[0] === Opcodes.call) {
1377
1492
  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;
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;
1498
+ } else {
1499
+ if (func) {
1500
+ count -= func.params.length;
1501
+ } else count--;
1502
+ if (func) count += func.returns.length;
1503
+ }
1382
1504
  } else count--;
1383
1505
 
1384
1506
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1470,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1470
1592
  name = func.name;
1471
1593
  }
1472
1594
 
1473
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1595
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1474
1596
  // literal eval hack
1475
- const code = decl.arguments[0].value;
1476
- const parsed = parse(code, []);
1597
+ const code = decl.arguments[0]?.value ?? '';
1598
+
1599
+ let parsed;
1600
+ try {
1601
+ parsed = parse(code, []);
1602
+ } catch (e) {
1603
+ if (e.name === 'SyntaxError') {
1604
+ // throw syntax errors of evals at runtime instead
1605
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1606
+ }
1607
+
1608
+ throw e;
1609
+ }
1477
1610
 
1478
1611
  const out = generate(scope, {
1479
1612
  type: 'BlockStatement',
@@ -1487,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1487
1620
  const finalStatement = parsed.body[parsed.body.length - 1];
1488
1621
  out.push(
1489
1622
  ...getNodeType(scope, finalStatement),
1490
- setLastType(scope)
1623
+ ...setLastType(scope)
1491
1624
  );
1492
1625
  } else if (countLeftover(out) === 0) {
1493
1626
  out.push(...number(UNDEFINED));
1494
1627
  out.push(
1495
1628
  ...number(TYPES.undefined, Valtype.i32),
1496
- setLastType(scope)
1629
+ ...setLastType(scope)
1497
1630
  );
1498
1631
  }
1499
1632
 
@@ -1515,6 +1648,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1648
 
1516
1649
  target = { ...decl.callee };
1517
1650
  target.name = spl.slice(0, -1).join('_');
1651
+
1652
+ // failed to lookup name, abort
1653
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1518
1654
  }
1519
1655
 
1520
1656
  // literal.func()
@@ -1522,22 +1658,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1522
1658
  // megahack for /regex/.func()
1523
1659
  const funcName = decl.callee.property.name;
1524
1660
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1525
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1661
+ const regex = decl.callee.object.regex.pattern;
1662
+ const rhemynName = `regex_${funcName}_${regex}`;
1526
1663
 
1527
- funcIndex[func.name] = func.index;
1528
- funcs.push(func);
1664
+ if (!funcIndex[rhemynName]) {
1665
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1666
+
1667
+ funcIndex[func.name] = func.index;
1668
+ funcs.push(func);
1669
+ }
1529
1670
 
1671
+ const idx = funcIndex[rhemynName];
1530
1672
  return [
1531
1673
  // make string arg
1532
1674
  ...generate(scope, decl.arguments[0]),
1675
+ Opcodes.i32_to_u,
1676
+ ...getNodeType(scope, decl.arguments[0]),
1533
1677
 
1534
1678
  // call regex func
1535
- Opcodes.i32_to_u,
1536
- [ Opcodes.call, func.index ],
1679
+ [ Opcodes.call, idx ],
1537
1680
  Opcodes.i32_from_u,
1538
1681
 
1539
1682
  ...number(TYPES.boolean, Valtype.i32),
1540
- setLastType(scope)
1683
+ ...setLastType(scope)
1541
1684
  ];
1542
1685
  }
1543
1686
 
@@ -1562,12 +1705,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1562
1705
  // }
1563
1706
 
1564
1707
  if (protoName) {
1708
+ const protoBC = {};
1709
+
1710
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1711
+
1712
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1713
+ for (const x of builtinProtoCands) {
1714
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1715
+ if (type == null) continue;
1716
+
1717
+ protoBC[type] = generateCall(scope, {
1718
+ callee: {
1719
+ type: 'Identifier',
1720
+ name: x
1721
+ },
1722
+ arguments: [ target, ...decl.arguments ],
1723
+ _protoInternalCall: true
1724
+ });
1725
+ }
1726
+ }
1727
+
1565
1728
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1566
1729
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1567
1730
  return acc;
1568
1731
  }, {});
1569
1732
 
1570
- // no prototype function candidates, ignore
1571
1733
  if (Object.keys(protoCands).length > 0) {
1572
1734
  // use local for cached i32 length as commonly used
1573
1735
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1585,7 +1747,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1585
1747
 
1586
1748
  let allOptUnused = true;
1587
1749
  let lengthI32CacheUsed = false;
1588
- const protoBC = {};
1589
1750
  for (const x in protoCands) {
1590
1751
  const protoFunc = protoCands[x];
1591
1752
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1593,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1593
1754
  ...RTArrayUtil.getLength(getPointer),
1594
1755
 
1595
1756
  ...number(TYPES.number, Valtype.i32),
1596
- setLastType(scope)
1757
+ ...setLastType(scope)
1597
1758
  ];
1598
1759
  continue;
1599
1760
  }
@@ -1630,7 +1791,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1630
1791
  ...protoOut,
1631
1792
 
1632
1793
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1633
- setLastType(scope),
1794
+ ...setLastType(scope),
1634
1795
  [ Opcodes.end ]
1635
1796
  ];
1636
1797
  }
@@ -1656,10 +1817,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1656
1817
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1657
1818
  ];
1658
1819
  }
1820
+
1821
+ if (Object.keys(protoBC).length > 0) {
1822
+ return typeSwitch(scope, getNodeType(scope, target), {
1823
+ ...protoBC,
1824
+
1825
+ // TODO: error better
1826
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1827
+ }, valtypeBinary);
1828
+ }
1659
1829
  }
1660
1830
 
1661
1831
  // TODO: only allows callee as literal
1662
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1832
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1663
1833
 
1664
1834
  let idx = funcIndex[name] ?? importedFuncs[name];
1665
1835
  if (idx === undefined && builtinFuncs[name]) {
@@ -1669,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1669
1839
  idx = funcIndex[name];
1670
1840
 
1671
1841
  // 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
- }
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
+ // }
1686
1856
  }
1687
1857
 
1688
1858
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1695,9 +1865,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1865
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1696
1866
  const wasmOps = {
1697
1867
  // pointer, align, offset
1698
- i32_load8_u: { imms: 2, args: 1 },
1868
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1869
+ // pointer, value, align, offset
1870
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1871
+ // pointer, align, offset
1872
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1873
+ // pointer, value, align, offset
1874
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1875
+ // pointer, align, offset
1876
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1877
+ // pointer, value, align, offset
1878
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1879
+
1880
+ // pointer, align, offset
1881
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1699
1882
  // pointer, value, align, offset
1700
- i32_store8: { imms: 2, args: 2 },
1883
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1884
+
1885
+ // value
1886
+ i32_const: { imms: 1, args: [], returns: 1 },
1701
1887
  };
1702
1888
 
1703
1889
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1706,28 +1892,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1706
1892
  const op = wasmOps[opName];
1707
1893
 
1708
1894
  const argOut = [];
1709
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1895
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1896
+ ...generate(scope, decl.arguments[i]),
1897
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1898
+ );
1710
1899
 
1711
1900
  // literals only
1712
- const imms = decl.arguments.slice(op.args).map(x => x.value);
1901
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1713
1902
 
1714
1903
  return [
1715
1904
  ...argOut,
1716
- [ Opcodes[opName], ...imms ]
1905
+ [ Opcodes[opName], ...imms ],
1906
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1717
1907
  ];
1718
1908
  }
1719
1909
  }
1720
1910
 
1721
1911
  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`);
1912
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1913
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1724
1914
  }
1725
1915
 
1726
1916
  const func = funcs.find(x => x.index === idx);
1727
1917
 
1728
1918
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1729
1919
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1730
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1920
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1731
1921
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1732
1922
 
1733
1923
  let args = decl.arguments;
@@ -1748,7 +1938,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1748
1938
  const arg = args[i];
1749
1939
  out = out.concat(generate(scope, arg));
1750
1940
 
1751
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1941
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1942
+ out.push(Opcodes.i32_to);
1943
+ }
1944
+
1945
+ if (importedFuncs[name] && name.startsWith('profile')) {
1752
1946
  out.push(Opcodes.i32_to);
1753
1947
  }
1754
1948
 
@@ -1767,9 +1961,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1767
1961
  // ...number(type, Valtype.i32),
1768
1962
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1769
1963
  // );
1770
- } else out.push(setLastType(scope));
1964
+ } else out.push(...setLastType(scope));
1771
1965
 
1772
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1966
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
1967
  out.push(Opcodes.i32_from);
1774
1968
  }
1775
1969
 
@@ -1779,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1779
1973
  const generateNew = (scope, decl, _global, _name) => {
1780
1974
  // hack: basically treat this as a normal call for builtins for now
1781
1975
  const name = mapName(decl.callee.name);
1976
+
1782
1977
  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)})`);
1978
+
1979
+ if (builtinFuncs[name + '$constructor']) {
1980
+ // custom ...$constructor override builtin func
1981
+ return generateCall(scope, {
1982
+ ...decl,
1983
+ callee: {
1984
+ type: 'Identifier',
1985
+ name: name + '$constructor'
1986
+ }
1987
+ }, _global, _name);
1988
+ }
1989
+
1990
+ 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
1991
 
1785
1992
  return generateCall(scope, decl, _global, _name);
1786
1993
  };
@@ -1897,7 +2104,7 @@ const brTable = (input, bc, returns) => {
1897
2104
  };
1898
2105
 
1899
2106
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1900
- if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2107
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
1901
2108
 
1902
2109
  const known = knownType(scope, type);
1903
2110
  if (known != null) {
@@ -1914,8 +2121,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1914
2121
  [ Opcodes.block, returns ]
1915
2122
  ];
1916
2123
 
1917
- // todo: use br_table?
1918
-
1919
2124
  for (const x in bc) {
1920
2125
  if (x === 'default') continue;
1921
2126
 
@@ -1971,12 +2176,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1971
2176
  };
1972
2177
 
1973
2178
  const typeAnnoToPorfType = x => {
1974
- if (TYPES[x]) return TYPES[x];
1975
- if (TYPES['_' + x]) return TYPES['_' + x];
2179
+ if (!x) return null;
2180
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2181
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1976
2182
 
1977
2183
  switch (x) {
1978
2184
  case 'i32':
1979
2185
  case 'i64':
2186
+ case 'f64':
1980
2187
  return TYPES.number;
1981
2188
  }
1982
2189
 
@@ -1987,7 +2194,7 @@ const extractTypeAnnotation = decl => {
1987
2194
  let a = decl;
1988
2195
  while (a.typeAnnotation) a = a.typeAnnotation;
1989
2196
 
1990
- let type, elementType;
2197
+ let type = null, elementType = null;
1991
2198
  if (a.typeName) {
1992
2199
  type = a.typeName.name;
1993
2200
  } else if (a.type.endsWith('Keyword')) {
@@ -2000,7 +2207,7 @@ const extractTypeAnnotation = decl => {
2000
2207
  const typeName = type;
2001
2208
  type = typeAnnoToPorfType(type);
2002
2209
 
2003
- if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2210
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
2004
2211
 
2005
2212
  // if (decl.name) console.log(decl.name, { type, elementType });
2006
2213
 
@@ -2014,11 +2221,12 @@ const generateVar = (scope, decl) => {
2014
2221
 
2015
2222
  // global variable if in top scope (main) and var ..., or if wanted
2016
2223
  const global = topLevel || decl._bare; // decl.kind === 'var';
2224
+ const target = global ? globals : scope.locals;
2017
2225
 
2018
2226
  for (const x of decl.declarations) {
2019
2227
  const name = mapName(x.id.name);
2020
2228
 
2021
- if (!name) return todo('destructuring is not supported yet');
2229
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2022
2230
 
2023
2231
  if (x.init && isFuncType(x.init.type)) {
2024
2232
  // hack for let a = function () { ... }
@@ -2035,16 +2243,29 @@ const generateVar = (scope, decl) => {
2035
2243
  continue; // always ignore
2036
2244
  }
2037
2245
 
2038
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2246
+ // // generate init before allocating var
2247
+ // let generated;
2248
+ // if (x.init) generated = generate(scope, x.init, global, name);
2249
+
2250
+ const typed = typedInput && x.id.typeAnnotation;
2251
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2039
2252
 
2040
- if (typedInput && x.id.typeAnnotation) {
2253
+ if (typed) {
2041
2254
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2042
2255
  }
2043
2256
 
2044
2257
  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 ]);
2258
+ const generated = generate(scope, x.init, global, name);
2259
+ if (scope.arrays?.get(name) != null) {
2260
+ // hack to set local as pointer before
2261
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2262
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2263
+ generated.pop();
2264
+ out = out.concat(generated);
2265
+ } else {
2266
+ out = out.concat(generated);
2267
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2268
+ }
2048
2269
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2049
2270
  }
2050
2271
 
@@ -2055,7 +2276,8 @@ const generateVar = (scope, decl) => {
2055
2276
  return out;
2056
2277
  };
2057
2278
 
2058
- const generateAssign = (scope, decl) => {
2279
+ // todo: optimize this func for valueUnused
2280
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2059
2281
  const { type, name } = decl.left;
2060
2282
 
2061
2283
  if (type === 'ObjectPattern') {
@@ -2070,22 +2292,30 @@ const generateAssign = (scope, decl) => {
2070
2292
  return [];
2071
2293
  }
2072
2294
 
2295
+ const op = decl.operator.slice(0, -1) || '=';
2296
+
2073
2297
  // hack: .length setter
2074
2298
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2075
2299
  const name = decl.left.object.name;
2076
- const pointer = arrays.get(name);
2300
+ const pointer = scope.arrays?.get(name);
2077
2301
 
2078
- const aotPointer = pointer != null;
2302
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2079
2303
 
2080
2304
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2305
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2081
2306
 
2082
2307
  return [
2083
2308
  ...(aotPointer ? number(0, Valtype.i32) : [
2084
2309
  ...generate(scope, decl.left.object),
2085
2310
  Opcodes.i32_to_u
2086
2311
  ]),
2312
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2087
2313
 
2088
- ...generate(scope, decl.right),
2314
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2315
+ [ Opcodes.local_get, pointerTmp ],
2316
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2317
+ Opcodes.i32_from_u
2318
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2089
2319
  [ Opcodes.local_tee, newValueTmp ],
2090
2320
 
2091
2321
  Opcodes.i32_to_u,
@@ -2095,21 +2325,19 @@ const generateAssign = (scope, decl) => {
2095
2325
  ];
2096
2326
  }
2097
2327
 
2098
- const op = decl.operator.slice(0, -1) || '=';
2099
-
2100
2328
  // arr[i]
2101
2329
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2102
2330
  const name = decl.left.object.name;
2103
- const pointer = arrays.get(name);
2331
+ const pointer = scope.arrays?.get(name);
2104
2332
 
2105
- const aotPointer = pointer != null;
2333
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2106
2334
 
2107
2335
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2108
2336
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2109
2337
 
2110
2338
  return [
2111
2339
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2112
- [TYPES._array]: [
2340
+ [TYPES.array]: [
2113
2341
  ...(aotPointer ? [] : [
2114
2342
  ...generate(scope, decl.left.object),
2115
2343
  Opcodes.i32_to_u
@@ -2158,7 +2386,7 @@ const generateAssign = (scope, decl) => {
2158
2386
  ];
2159
2387
  }
2160
2388
 
2161
- if (!name) return todo('destructuring is not supported yet');
2389
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2162
2390
 
2163
2391
  const [ local, isGlobal ] = lookupName(scope, name);
2164
2392
 
@@ -2263,7 +2491,7 @@ const generateUnary = (scope, decl) => {
2263
2491
  return out;
2264
2492
  }
2265
2493
 
2266
- case 'delete':
2494
+ case 'delete': {
2267
2495
  let toReturn = true, toGenerate = true;
2268
2496
 
2269
2497
  if (decl.argument.type === 'Identifier') {
@@ -2285,40 +2513,60 @@ const generateUnary = (scope, decl) => {
2285
2513
 
2286
2514
  out.push(...number(toReturn ? 1 : 0));
2287
2515
  return out;
2516
+ }
2517
+
2518
+ case 'typeof': {
2519
+ let overrideType, toGenerate = true;
2520
+
2521
+ if (decl.argument.type === 'Identifier') {
2522
+ const out = generateIdent(scope, decl.argument);
2523
+
2524
+ // if ReferenceError (undeclared var), ignore and return undefined
2525
+ if (out[1]) {
2526
+ // does not exist (2 ops from throw)
2527
+ overrideType = number(TYPES.undefined, Valtype.i32);
2528
+ toGenerate = false;
2529
+ }
2530
+ }
2288
2531
 
2289
- case 'typeof':
2290
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2532
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2533
+ disposeLeftover(out);
2534
+
2535
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2291
2536
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2292
2537
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2293
2538
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2294
2539
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2295
2540
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2296
2541
 
2297
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2542
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2298
2543
 
2299
2544
  // object and internal types
2300
2545
  default: makeString(scope, 'object', false, '#typeof_result'),
2301
- });
2546
+ }));
2547
+
2548
+ return out;
2549
+ }
2302
2550
 
2303
2551
  default:
2304
- return todo(`unary operator ${decl.operator} not implemented yet`);
2552
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2305
2553
  }
2306
2554
  };
2307
2555
 
2308
- const generateUpdate = (scope, decl) => {
2556
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2309
2557
  const { name } = decl.argument;
2310
2558
 
2311
2559
  const [ local, isGlobal ] = lookupName(scope, name);
2312
2560
 
2313
2561
  if (local === undefined) {
2314
- return todo(`update expression with undefined variable`);
2562
+ return todo(scope, `update expression with undefined variable`, true);
2315
2563
  }
2316
2564
 
2317
2565
  const idx = local.idx;
2318
2566
  const out = [];
2319
2567
 
2320
2568
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2321
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2569
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2322
2570
 
2323
2571
  switch (decl.operator) {
2324
2572
  case '++':
@@ -2331,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
2331
2579
  }
2332
2580
 
2333
2581
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2334
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2582
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2335
2583
 
2336
2584
  return out;
2337
2585
  };
@@ -2371,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2371
2619
  // note type
2372
2620
  out.push(
2373
2621
  ...getNodeType(scope, decl.consequent),
2374
- setLastType(scope)
2622
+ ...setLastType(scope)
2375
2623
  );
2376
2624
 
2377
2625
  out.push([ Opcodes.else ]);
@@ -2380,7 +2628,7 @@ const generateConditional = (scope, decl) => {
2380
2628
  // note type
2381
2629
  out.push(
2382
2630
  ...getNodeType(scope, decl.alternate),
2383
- setLastType(scope)
2631
+ ...setLastType(scope)
2384
2632
  );
2385
2633
 
2386
2634
  out.push([ Opcodes.end ]);
@@ -2394,7 +2642,7 @@ const generateFor = (scope, decl) => {
2394
2642
  const out = [];
2395
2643
 
2396
2644
  if (decl.init) {
2397
- out.push(...generate(scope, decl.init));
2645
+ out.push(...generate(scope, decl.init, false, undefined, true));
2398
2646
  disposeLeftover(out);
2399
2647
  }
2400
2648
 
@@ -2412,7 +2660,7 @@ const generateFor = (scope, decl) => {
2412
2660
  out.push(...generate(scope, decl.body));
2413
2661
  out.push([ Opcodes.end ]);
2414
2662
 
2415
- if (decl.update) out.push(...generate(scope, decl.update));
2663
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2416
2664
 
2417
2665
  out.push([ Opcodes.br, 1 ]);
2418
2666
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2440,6 +2688,36 @@ const generateWhile = (scope, decl) => {
2440
2688
  return out;
2441
2689
  };
2442
2690
 
2691
+ const generateDoWhile = (scope, decl) => {
2692
+ const out = [];
2693
+
2694
+ out.push([ Opcodes.loop, Blocktype.void ]);
2695
+ depth.push('dowhile');
2696
+
2697
+ // block for break (includes all)
2698
+ out.push([ Opcodes.block, Blocktype.void ]);
2699
+ depth.push('block');
2700
+
2701
+ // block for continue
2702
+ // includes body but not test+loop so we can exit body at anytime
2703
+ // and still test+loop after
2704
+ out.push([ Opcodes.block, Blocktype.void ]);
2705
+ depth.push('block');
2706
+
2707
+ out.push(...generate(scope, decl.body));
2708
+
2709
+ out.push([ Opcodes.end ]);
2710
+ depth.pop();
2711
+
2712
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2713
+ out.push([ Opcodes.br_if, 1 ]);
2714
+
2715
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2716
+ depth.pop(); depth.pop();
2717
+
2718
+ return out;
2719
+ };
2720
+
2443
2721
  const generateForOf = (scope, decl) => {
2444
2722
  const out = [];
2445
2723
 
@@ -2476,7 +2754,10 @@ const generateForOf = (scope, decl) => {
2476
2754
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2477
2755
  }
2478
2756
 
2757
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2758
+
2479
2759
  const [ local, isGlobal ] = lookupName(scope, leftName);
2760
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2480
2761
 
2481
2762
  depth.push('block');
2482
2763
  depth.push('block');
@@ -2485,6 +2766,7 @@ const generateForOf = (scope, decl) => {
2485
2766
  // hack: this is naughty and will break things!
2486
2767
  let newOut = number(0, Valtype.f64), newPointer = -1;
2487
2768
  if (pages.hasAnyString) {
2769
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2488
2770
  0, [ newOut, newPointer ] = makeArray(scope, {
2489
2771
  rawElements: new Array(1)
2490
2772
  }, isGlobal, leftName, true, 'i16');
@@ -2493,7 +2775,7 @@ const generateForOf = (scope, decl) => {
2493
2775
  // set type for local
2494
2776
  // todo: optimize away counter and use end pointer
2495
2777
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2496
- [TYPES._array]: [
2778
+ [TYPES.array]: [
2497
2779
  ...setType(scope, leftName, TYPES.number),
2498
2780
 
2499
2781
  [ Opcodes.loop, Blocktype.void ],
@@ -2576,6 +2858,56 @@ const generateForOf = (scope, decl) => {
2576
2858
  [ Opcodes.end ],
2577
2859
  [ Opcodes.end ]
2578
2860
  ],
2861
+ [TYPES.bytestring]: [
2862
+ ...setType(scope, leftName, TYPES.bytestring),
2863
+
2864
+ [ Opcodes.loop, Blocktype.void ],
2865
+
2866
+ // setup new/out array
2867
+ ...newOut,
2868
+ [ Opcodes.drop ],
2869
+
2870
+ ...number(0, Valtype.i32), // base 0 for store after
2871
+
2872
+ // load current string ind {arg}
2873
+ [ Opcodes.local_get, pointer ],
2874
+ [ Opcodes.local_get, counter ],
2875
+ [ Opcodes.i32_add ],
2876
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2877
+
2878
+ // store to new string ind 0
2879
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2880
+
2881
+ // return new string (page)
2882
+ ...number(newPointer),
2883
+
2884
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2885
+
2886
+ [ Opcodes.block, Blocktype.void ],
2887
+ [ Opcodes.block, Blocktype.void ],
2888
+ ...generate(scope, decl.body),
2889
+ [ Opcodes.end ],
2890
+
2891
+ // increment iter pointer
2892
+ // [ Opcodes.local_get, pointer ],
2893
+ // ...number(1, Valtype.i32),
2894
+ // [ Opcodes.i32_add ],
2895
+ // [ Opcodes.local_set, pointer ],
2896
+
2897
+ // increment counter by 1
2898
+ [ Opcodes.local_get, counter ],
2899
+ ...number(1, Valtype.i32),
2900
+ [ Opcodes.i32_add ],
2901
+ [ Opcodes.local_tee, counter ],
2902
+
2903
+ // loop if counter != length
2904
+ [ Opcodes.local_get, length ],
2905
+ [ Opcodes.i32_ne ],
2906
+ [ Opcodes.br_if, 1 ],
2907
+
2908
+ [ Opcodes.end ],
2909
+ [ Opcodes.end ]
2910
+ ],
2579
2911
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2580
2912
  }, Blocktype.void));
2581
2913
 
@@ -2586,28 +2918,65 @@ const generateForOf = (scope, decl) => {
2586
2918
  return out;
2587
2919
  };
2588
2920
 
2921
+ // find the nearest loop in depth map by type
2589
2922
  const getNearestLoop = () => {
2590
2923
  for (let i = depth.length - 1; i >= 0; i--) {
2591
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2924
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2592
2925
  }
2593
2926
 
2594
2927
  return -1;
2595
2928
  };
2596
2929
 
2597
2930
  const generateBreak = (scope, decl) => {
2598
- const nearestLoop = depth.length - getNearestLoop();
2931
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2932
+ const type = depth[target];
2933
+
2934
+ // different loop types have different branch offsets
2935
+ // as they have different wasm block/loop/if structures
2936
+ // we need to use the right offset by type to branch to the one we want
2937
+ // for a break: exit the loop without executing anything else inside it
2938
+ const offset = ({
2939
+ for: 2, // loop > if (wanted branch) > block (we are here)
2940
+ while: 2, // loop > if (wanted branch) (we are here)
2941
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2942
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2943
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2944
+ })[type];
2945
+
2599
2946
  return [
2600
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2947
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2601
2948
  ];
2602
2949
  };
2603
2950
 
2604
2951
  const generateContinue = (scope, decl) => {
2605
- const nearestLoop = depth.length - getNearestLoop();
2952
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2953
+ const type = depth[target];
2954
+
2955
+ // different loop types have different branch offsets
2956
+ // as they have different wasm block/loop/if structures
2957
+ // we need to use the right offset by type to branch to the one we want
2958
+ // for a continue: do test for the loop, and then loop depending on that success
2959
+ const offset = ({
2960
+ for: 3, // loop (wanted branch) > if > block (we are here)
2961
+ while: 1, // loop (wanted branch) > if (we are here)
2962
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2963
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2964
+ })[type];
2965
+
2606
2966
  return [
2607
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2967
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2608
2968
  ];
2609
2969
  };
2610
2970
 
2971
+ const generateLabel = (scope, decl) => {
2972
+ scope.labels ??= new Map();
2973
+
2974
+ const name = decl.label.name;
2975
+ scope.labels.set(name, depth.length);
2976
+
2977
+ return generate(scope, decl.body);
2978
+ };
2979
+
2611
2980
  const generateThrow = (scope, decl) => {
2612
2981
  scope.throws = true;
2613
2982
 
@@ -2640,7 +3009,7 @@ const generateThrow = (scope, decl) => {
2640
3009
  };
2641
3010
 
2642
3011
  const generateTry = (scope, decl) => {
2643
- if (decl.finalizer) return todo('try finally not implemented yet');
3012
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2644
3013
 
2645
3014
  const out = [];
2646
3015
 
@@ -2671,7 +3040,7 @@ const generateAssignPat = (scope, decl) => {
2671
3040
  // TODO
2672
3041
  // if identifier declared, use that
2673
3042
  // else, use default (right)
2674
- return todo('assignment pattern (optional arg)');
3043
+ return todo(scope, 'assignment pattern (optional arg)');
2675
3044
  };
2676
3045
 
2677
3046
  let pages = new Map();
@@ -2750,16 +3119,22 @@ const getAllocType = itemType => {
2750
3119
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2751
3120
  const out = [];
2752
3121
 
3122
+ scope.arrays ??= new Map();
3123
+
2753
3124
  let firstAssign = false;
2754
- if (!arrays.has(name) || name === '$undeclared') {
3125
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2755
3126
  firstAssign = true;
2756
3127
 
2757
3128
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2758
3129
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2759
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3130
+
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);
2760
3133
  }
2761
3134
 
2762
- const pointer = arrays.get(name);
3135
+ const pointer = scope.arrays.get(name);
3136
+
3137
+ const local = global ? globals[name] : scope.locals[name];
2763
3138
 
2764
3139
  const useRawElements = !!decl.rawElements;
2765
3140
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2793,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2793
3168
  return [ out, pointer ];
2794
3169
  }
2795
3170
 
3171
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3172
+ if (pointerTmp != null) {
3173
+ out.push(
3174
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3175
+ Opcodes.i32_to_u,
3176
+ [ Opcodes.local_set, pointerTmp ]
3177
+ );
3178
+ }
3179
+
3180
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3181
+
2796
3182
  // store length as 0th array
2797
3183
  out.push(
2798
- ...number(0, Valtype.i32),
3184
+ ...pointerWasm,
2799
3185
  ...number(length, Valtype.i32),
2800
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3186
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2801
3187
  );
2802
3188
 
2803
3189
  const storeOp = StoreOps[itemType];
@@ -2806,14 +3192,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2806
3192
  if (elements[i] == null) continue;
2807
3193
 
2808
3194
  out.push(
2809
- ...number(0, Valtype.i32),
3195
+ ...pointerWasm,
2810
3196
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2811
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3197
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2812
3198
  );
2813
3199
  }
2814
3200
 
2815
3201
  // local value as pointer
2816
- out.push(...number(pointer));
3202
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2817
3203
 
2818
3204
  return [ out, pointer ];
2819
3205
  };
@@ -2845,20 +3231,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2845
3231
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2846
3232
  };
2847
3233
 
2848
- let arrays = new Map();
2849
3234
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2850
3235
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2851
3236
  };
2852
3237
 
2853
3238
  export const generateMember = (scope, decl, _global, _name) => {
2854
3239
  const name = decl.object.name;
2855
- const pointer = arrays.get(name);
3240
+ const pointer = scope.arrays?.get(name);
3241
+
3242
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2856
3243
 
2857
- const aotPointer = pointer != null;
3244
+ // hack: .name
3245
+ if (decl.property.name === 'name') {
3246
+ if (hasFuncWithName(name)) {
3247
+ let nameProp = name;
3248
+
3249
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3250
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3251
+
3252
+ return makeString(scope, nameProp, _global, _name, true);
3253
+ } else {
3254
+ return generate(scope, DEFAULT_VALUE);
3255
+ }
3256
+ }
2858
3257
 
2859
3258
  // hack: .length
2860
3259
  if (decl.property.name === 'length') {
2861
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3260
+ const func = funcs.find(x => x.name === name);
3261
+ 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);
3265
+ }
3266
+
3267
+ if (builtinFuncs[name + '$constructor']) {
3268
+ const regularFunc = builtinFuncs[name];
3269
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3270
+
3271
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3272
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3273
+
3274
+ return number(Math.max(regularParams, constructorParams));
3275
+ }
3276
+
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);
3280
+
2862
3281
  return [
2863
3282
  ...(aotPointer ? number(0, Valtype.i32) : [
2864
3283
  ...generate(scope, decl.object),
@@ -2883,7 +3302,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2883
3302
  }
2884
3303
 
2885
3304
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2886
- [TYPES._array]: [
3305
+ [TYPES.array]: [
2887
3306
  // get index as valtype
2888
3307
  ...property,
2889
3308
 
@@ -2902,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2902
3321
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2903
3322
 
2904
3323
  ...number(TYPES.number, Valtype.i32),
2905
- setLastType(scope)
3324
+ ...setLastType(scope)
2906
3325
  ],
2907
3326
 
2908
3327
  [TYPES.string]: [
@@ -2934,9 +3353,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2934
3353
  ...number(newPointer),
2935
3354
 
2936
3355
  ...number(TYPES.string, Valtype.i32),
2937
- setLastType(scope)
3356
+ ...setLastType(scope)
2938
3357
  ],
2939
- [TYPES._bytestring]: [
3358
+ [TYPES.bytestring]: [
2940
3359
  // setup new/out array
2941
3360
  ...newOut,
2942
3361
  [ Opcodes.drop ],
@@ -2953,19 +3372,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2953
3372
  ]),
2954
3373
 
2955
3374
  // load current string ind {arg}
2956
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3375
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2957
3376
 
2958
3377
  // store to new string ind 0
2959
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3378
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2960
3379
 
2961
3380
  // return new string (page)
2962
3381
  ...number(newPointer),
2963
3382
 
2964
- ...number(TYPES._bytestring, Valtype.i32),
2965
- setLastType(scope)
3383
+ ...number(TYPES.bytestring, Valtype.i32),
3384
+ ...setLastType(scope)
2966
3385
  ],
2967
3386
 
2968
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3387
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2969
3388
  });
2970
3389
  };
2971
3390
 
@@ -2975,28 +3394,36 @@ const objectHack = node => {
2975
3394
  if (!node) return node;
2976
3395
 
2977
3396
  if (node.type === 'MemberExpression') {
2978
- if (node.computed || node.optional) return node;
3397
+ const out = (() => {
3398
+ if (node.computed || node.optional) return;
2979
3399
 
2980
- let objectName = node.object.name;
3400
+ let objectName = node.object.name;
2981
3401
 
2982
- // if object is not identifier or another member exp, give up
2983
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3402
+ // if object is not identifier or another member exp, give up
3403
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3404
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2984
3405
 
2985
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3406
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2986
3407
 
2987
- // if .length, give up (hack within a hack!)
2988
- if (node.property.name === 'length') return node;
3408
+ // if .name or .length, give up (hack within a hack!)
3409
+ if (['name', 'length'].includes(node.property.name)) {
3410
+ node.object = objectHack(node.object);
3411
+ return;
3412
+ }
2989
3413
 
2990
- // no object name, give up
2991
- if (!objectName) return node;
3414
+ // no object name, give up
3415
+ if (!objectName) return;
2992
3416
 
2993
- const name = '__' + objectName + '_' + node.property.name;
2994
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3417
+ const name = '__' + objectName + '_' + node.property.name;
3418
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2995
3419
 
2996
- return {
2997
- type: 'Identifier',
2998
- name
2999
- };
3420
+ return {
3421
+ type: 'Identifier',
3422
+ name
3423
+ };
3424
+ })();
3425
+
3426
+ if (out) return out;
3000
3427
  }
3001
3428
 
3002
3429
  for (const x in node) {
@@ -3010,8 +3437,8 @@ const objectHack = node => {
3010
3437
  };
3011
3438
 
3012
3439
  const generateFunc = (scope, decl) => {
3013
- if (decl.async) return todo('async functions are not supported');
3014
- if (decl.generator) return todo('generator functions are not supported');
3440
+ if (decl.async) return todo(scope, 'async functions are not supported');
3441
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3015
3442
 
3016
3443
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3017
3444
  const params = decl.params ?? [];
@@ -3027,6 +3454,11 @@ const generateFunc = (scope, decl) => {
3027
3454
  name
3028
3455
  };
3029
3456
 
3457
+ if (typedInput && decl.returnType) {
3458
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3459
+ innerScope.returns = [ valtypeBinary ];
3460
+ }
3461
+
3030
3462
  for (let i = 0; i < params.length; i++) {
3031
3463
  allocVar(innerScope, params[i].name, false);
3032
3464
 
@@ -3089,16 +3521,6 @@ const generateCode = (scope, decl) => {
3089
3521
  };
3090
3522
 
3091
3523
  const internalConstrs = {
3092
- Boolean: {
3093
- generate: (scope, decl) => {
3094
- if (decl.arguments.length === 0) return number(0);
3095
-
3096
- // should generate/run all args
3097
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3098
- },
3099
- type: TYPES.boolean
3100
- },
3101
-
3102
3524
  Array: {
3103
3525
  generate: (scope, decl, global, name) => {
3104
3526
  // new Array(i0, i1, ...)
@@ -3116,7 +3538,7 @@ const internalConstrs = {
3116
3538
 
3117
3539
  // todo: check in wasm instead of here
3118
3540
  const literalValue = arg.value ?? 0;
3119
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3541
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3120
3542
 
3121
3543
  return [
3122
3544
  ...number(0, Valtype.i32),
@@ -3127,7 +3549,8 @@ const internalConstrs = {
3127
3549
  ...number(pointer)
3128
3550
  ];
3129
3551
  },
3130
- type: TYPES._array
3552
+ type: TYPES.array,
3553
+ length: 1
3131
3554
  },
3132
3555
 
3133
3556
  __Array_of: {
@@ -3138,27 +3561,134 @@ const internalConstrs = {
3138
3561
  elements: decl.arguments
3139
3562
  }, global, name);
3140
3563
  },
3141
- type: TYPES._array,
3564
+ type: TYPES.array,
3565
+ notConstr: true,
3566
+ length: 0
3567
+ },
3568
+
3569
+ __Porffor_fastOr: {
3570
+ generate: (scope, decl) => {
3571
+ const out = [];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ Opcodes.i32_to_u,
3577
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3578
+ );
3579
+ }
3580
+
3581
+ out.push(Opcodes.i32_from_u);
3582
+
3583
+ return out;
3584
+ },
3585
+ type: TYPES.boolean,
3142
3586
  notConstr: true
3143
- }
3144
- };
3587
+ },
3145
3588
 
3146
- // const _ = Array.prototype.push;
3147
- // Array.prototype.push = function (a) {
3148
- // const check = arr => {
3149
- // for (const x of arr) {
3150
- // if (x === undefined) {
3151
- // console.trace(arr);
3152
- // process.exit();
3153
- // }
3154
- // if (Array.isArray(x)) check(x);
3155
- // }
3156
- // };
3157
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
3158
- // // if (Array.isArray(a)) check(a);
3589
+ __Porffor_fastAnd: {
3590
+ generate: (scope, decl) => {
3591
+ const out = [];
3592
+
3593
+ for (let i = 0; i < decl.arguments.length; i++) {
3594
+ out.push(
3595
+ ...generate(scope, decl.arguments[i]),
3596
+ Opcodes.i32_to_u,
3597
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3598
+ );
3599
+ }
3600
+
3601
+ out.push(Opcodes.i32_from_u);
3159
3602
 
3160
- // return _.apply(this, arguments);
3161
- // };
3603
+ return out;
3604
+ },
3605
+ type: TYPES.boolean,
3606
+ notConstr: true
3607
+ },
3608
+
3609
+ Boolean: {
3610
+ generate: (scope, decl) => {
3611
+ // todo: boolean object when used as constructor
3612
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3613
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3614
+ },
3615
+ type: TYPES.boolean,
3616
+ length: 1
3617
+ },
3618
+
3619
+ __Math_max: {
3620
+ generate: (scope, decl) => {
3621
+ const out = [
3622
+ ...number(-Infinity)
3623
+ ];
3624
+
3625
+ for (let i = 0; i < decl.arguments.length; i++) {
3626
+ out.push(
3627
+ ...generate(scope, decl.arguments[i]),
3628
+ [ Opcodes.f64_max ]
3629
+ );
3630
+ }
3631
+
3632
+ return out;
3633
+ },
3634
+ type: TYPES.number,
3635
+ notConstr: true,
3636
+ length: 2
3637
+ },
3638
+
3639
+ __Math_min: {
3640
+ generate: (scope, decl) => {
3641
+ const out = [
3642
+ ...number(Infinity)
3643
+ ];
3644
+
3645
+ for (let i = 0; i < decl.arguments.length; i++) {
3646
+ out.push(
3647
+ ...generate(scope, decl.arguments[i]),
3648
+ [ Opcodes.f64_min ]
3649
+ );
3650
+ }
3651
+
3652
+ return out;
3653
+ },
3654
+ type: TYPES.number,
3655
+ notConstr: true,
3656
+ length: 2
3657
+ },
3658
+
3659
+ __console_log: {
3660
+ generate: (scope, decl) => {
3661
+ const out = [];
3662
+
3663
+ for (let i = 0; i < decl.arguments.length; i++) {
3664
+ out.push(
3665
+ ...generateCall(scope, {
3666
+ callee: {
3667
+ type: 'Identifier',
3668
+ name: '__Porffor_print'
3669
+ },
3670
+ arguments: [ decl.arguments[i] ]
3671
+ }),
3672
+
3673
+ // print space
3674
+ ...number(32),
3675
+ [ Opcodes.call, importedFuncs.printChar ]
3676
+ );
3677
+ }
3678
+
3679
+ // print newline
3680
+ out.push(
3681
+ ...number(10),
3682
+ [ Opcodes.call, importedFuncs.printChar ]
3683
+ );
3684
+
3685
+ return out;
3686
+ },
3687
+ type: TYPES.undefined,
3688
+ notConstr: true,
3689
+ length: 0
3690
+ }
3691
+ };
3162
3692
 
3163
3693
  export default program => {
3164
3694
  globals = {};
@@ -3168,20 +3698,23 @@ export default program => {
3168
3698
  funcs = [];
3169
3699
  funcIndex = {};
3170
3700
  depth = [];
3171
- arrays = new Map();
3172
3701
  pages = new Map();
3173
3702
  data = [];
3174
3703
  currentFuncIndex = importedFuncs.length;
3175
3704
 
3176
3705
  globalThis.valtype = 'f64';
3177
3706
 
3178
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3707
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3179
3708
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3180
3709
 
3181
3710
  globalThis.valtypeBinary = Valtype[valtype];
3182
3711
 
3183
3712
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3184
3713
 
3714
+ globalThis.pageSize = PageSize;
3715
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3716
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3717
+
3185
3718
  // set generic opcodes for current valtype
3186
3719
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3187
3720
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3190,10 +3723,10 @@ export default program => {
3190
3723
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3191
3724
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3192
3725
 
3193
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3194
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3195
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3196
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3726
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3727
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3728
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3729
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3197
3730
 
3198
3731
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3199
3732
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3206,10 +3739,6 @@ export default program => {
3206
3739
 
3207
3740
  program.id = { name: 'main' };
3208
3741
 
3209
- globalThis.pageSize = PageSize;
3210
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3211
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3212
-
3213
3742
  const scope = {
3214
3743
  locals: {},
3215
3744
  localInd: 0
@@ -3220,7 +3749,7 @@ export default program => {
3220
3749
  body: program.body
3221
3750
  };
3222
3751
 
3223
- if (Prefs.astLog) console.log(program.body.body);
3752
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3224
3753
 
3225
3754
  generateFunc(scope, program);
3226
3755
 
@@ -3237,7 +3766,11 @@ export default program => {
3237
3766
  }
3238
3767
 
3239
3768
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3240
- main.returns = [];
3769
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3770
+ main.wasm.splice(main.wasm.length - 1, 1);
3771
+ } else {
3772
+ main.returns = [];
3773
+ }
3241
3774
  }
3242
3775
 
3243
3776
  if (lastInst[0] === Opcodes.call) {