porffor 0.2.0-5ac7ea0 → 0.2.0-5c24120

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 (51) 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 +29 -41
  14. package/compiler/builtins/date.ts +2070 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/function.ts +7 -0
  17. package/compiler/builtins/int.ts +147 -0
  18. package/compiler/builtins/number.ts +534 -0
  19. package/compiler/builtins/object.ts +6 -0
  20. package/compiler/builtins/porffor.d.ts +42 -9
  21. package/compiler/builtins/string.ts +1080 -0
  22. package/compiler/builtins.js +60 -87
  23. package/compiler/{codeGen.js → codegen.js} +848 -316
  24. package/compiler/decompile.js +0 -1
  25. package/compiler/embedding.js +22 -22
  26. package/compiler/encoding.js +108 -10
  27. package/compiler/generated_builtins.js +1499 -7
  28. package/compiler/index.js +16 -14
  29. package/compiler/log.js +2 -2
  30. package/compiler/opt.js +23 -22
  31. package/compiler/parse.js +30 -22
  32. package/compiler/precompile.js +26 -27
  33. package/compiler/prefs.js +7 -6
  34. package/compiler/prototype.js +16 -32
  35. package/compiler/types.js +37 -0
  36. package/compiler/wasmSpec.js +11 -1
  37. package/compiler/wrap.js +41 -44
  38. package/package.json +9 -5
  39. package/porf +2 -0
  40. package/rhemyn/compile.js +44 -26
  41. package/rhemyn/parse.js +322 -320
  42. package/rhemyn/test/parse.js +58 -58
  43. package/runner/compare.js +34 -34
  44. package/runner/debug.js +122 -0
  45. package/runner/index.js +69 -12
  46. package/runner/profiler.js +45 -26
  47. package/runner/repl.js +42 -9
  48. package/runner/sizes.js +37 -37
  49. package/runner/info.js +0 -89
  50. package/runner/transform.js +0 -15
  51. 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':
@@ -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
 
@@ -2071,22 +2292,30 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2071
2292
  return [];
2072
2293
  }
2073
2294
 
2295
+ const op = decl.operator.slice(0, -1) || '=';
2296
+
2074
2297
  // hack: .length setter
2075
2298
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2076
2299
  const name = decl.left.object.name;
2077
- const pointer = arrays.get(name);
2300
+ const pointer = scope.arrays?.get(name);
2078
2301
 
2079
- const aotPointer = pointer != null;
2302
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2080
2303
 
2081
2304
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2305
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2082
2306
 
2083
2307
  return [
2084
2308
  ...(aotPointer ? number(0, Valtype.i32) : [
2085
2309
  ...generate(scope, decl.left.object),
2086
2310
  Opcodes.i32_to_u
2087
2311
  ]),
2312
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2088
2313
 
2089
- ...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))),
2090
2319
  [ Opcodes.local_tee, newValueTmp ],
2091
2320
 
2092
2321
  Opcodes.i32_to_u,
@@ -2096,21 +2325,19 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2096
2325
  ];
2097
2326
  }
2098
2327
 
2099
- const op = decl.operator.slice(0, -1) || '=';
2100
-
2101
2328
  // arr[i]
2102
2329
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2103
2330
  const name = decl.left.object.name;
2104
- const pointer = arrays.get(name);
2331
+ const pointer = scope.arrays?.get(name);
2105
2332
 
2106
- const aotPointer = pointer != null;
2333
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2107
2334
 
2108
2335
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2109
2336
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2110
2337
 
2111
2338
  return [
2112
2339
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2113
- [TYPES._array]: [
2340
+ [TYPES.array]: [
2114
2341
  ...(aotPointer ? [] : [
2115
2342
  ...generate(scope, decl.left.object),
2116
2343
  Opcodes.i32_to_u
@@ -2159,7 +2386,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2159
2386
  ];
2160
2387
  }
2161
2388
 
2162
- if (!name) return todo('destructuring is not supported yet');
2389
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2163
2390
 
2164
2391
  const [ local, isGlobal ] = lookupName(scope, name);
2165
2392
 
@@ -2264,7 +2491,7 @@ const generateUnary = (scope, decl) => {
2264
2491
  return out;
2265
2492
  }
2266
2493
 
2267
- case 'delete':
2494
+ case 'delete': {
2268
2495
  let toReturn = true, toGenerate = true;
2269
2496
 
2270
2497
  if (decl.argument.type === 'Identifier') {
@@ -2286,23 +2513,43 @@ const generateUnary = (scope, decl) => {
2286
2513
 
2287
2514
  out.push(...number(toReturn ? 1 : 0));
2288
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
+ }
2289
2531
 
2290
- case 'typeof':
2291
- 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), {
2292
2536
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2293
2537
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2294
2538
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2295
2539
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2296
2540
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2297
2541
 
2298
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2542
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2299
2543
 
2300
2544
  // object and internal types
2301
2545
  default: makeString(scope, 'object', false, '#typeof_result'),
2302
- });
2546
+ }));
2547
+
2548
+ return out;
2549
+ }
2303
2550
 
2304
2551
  default:
2305
- return todo(`unary operator ${decl.operator} not implemented yet`);
2552
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2306
2553
  }
2307
2554
  };
2308
2555
 
@@ -2312,7 +2559,7 @@ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2312
2559
  const [ local, isGlobal ] = lookupName(scope, name);
2313
2560
 
2314
2561
  if (local === undefined) {
2315
- return todo(`update expression with undefined variable`);
2562
+ return todo(scope, `update expression with undefined variable`, true);
2316
2563
  }
2317
2564
 
2318
2565
  const idx = local.idx;
@@ -2372,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2372
2619
  // note type
2373
2620
  out.push(
2374
2621
  ...getNodeType(scope, decl.consequent),
2375
- setLastType(scope)
2622
+ ...setLastType(scope)
2376
2623
  );
2377
2624
 
2378
2625
  out.push([ Opcodes.else ]);
@@ -2381,7 +2628,7 @@ const generateConditional = (scope, decl) => {
2381
2628
  // note type
2382
2629
  out.push(
2383
2630
  ...getNodeType(scope, decl.alternate),
2384
- setLastType(scope)
2631
+ ...setLastType(scope)
2385
2632
  );
2386
2633
 
2387
2634
  out.push([ Opcodes.end ]);
@@ -2441,6 +2688,36 @@ const generateWhile = (scope, decl) => {
2441
2688
  return out;
2442
2689
  };
2443
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
+
2444
2721
  const generateForOf = (scope, decl) => {
2445
2722
  const out = [];
2446
2723
 
@@ -2477,7 +2754,10 @@ const generateForOf = (scope, decl) => {
2477
2754
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2478
2755
  }
2479
2756
 
2757
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2758
+
2480
2759
  const [ local, isGlobal ] = lookupName(scope, leftName);
2760
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2481
2761
 
2482
2762
  depth.push('block');
2483
2763
  depth.push('block');
@@ -2486,6 +2766,7 @@ const generateForOf = (scope, decl) => {
2486
2766
  // hack: this is naughty and will break things!
2487
2767
  let newOut = number(0, Valtype.f64), newPointer = -1;
2488
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?
2489
2770
  0, [ newOut, newPointer ] = makeArray(scope, {
2490
2771
  rawElements: new Array(1)
2491
2772
  }, isGlobal, leftName, true, 'i16');
@@ -2494,7 +2775,7 @@ const generateForOf = (scope, decl) => {
2494
2775
  // set type for local
2495
2776
  // todo: optimize away counter and use end pointer
2496
2777
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2497
- [TYPES._array]: [
2778
+ [TYPES.array]: [
2498
2779
  ...setType(scope, leftName, TYPES.number),
2499
2780
 
2500
2781
  [ Opcodes.loop, Blocktype.void ],
@@ -2577,6 +2858,56 @@ const generateForOf = (scope, decl) => {
2577
2858
  [ Opcodes.end ],
2578
2859
  [ Opcodes.end ]
2579
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
+ ],
2580
2911
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2581
2912
  }, Blocktype.void));
2582
2913
 
@@ -2587,28 +2918,65 @@ const generateForOf = (scope, decl) => {
2587
2918
  return out;
2588
2919
  };
2589
2920
 
2921
+ // find the nearest loop in depth map by type
2590
2922
  const getNearestLoop = () => {
2591
2923
  for (let i = depth.length - 1; i >= 0; i--) {
2592
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2924
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2593
2925
  }
2594
2926
 
2595
2927
  return -1;
2596
2928
  };
2597
2929
 
2598
2930
  const generateBreak = (scope, decl) => {
2599
- 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
+
2600
2946
  return [
2601
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2947
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2602
2948
  ];
2603
2949
  };
2604
2950
 
2605
2951
  const generateContinue = (scope, decl) => {
2606
- 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
+
2607
2966
  return [
2608
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2967
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2609
2968
  ];
2610
2969
  };
2611
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
+
2612
2980
  const generateThrow = (scope, decl) => {
2613
2981
  scope.throws = true;
2614
2982
 
@@ -2641,7 +3009,7 @@ const generateThrow = (scope, decl) => {
2641
3009
  };
2642
3010
 
2643
3011
  const generateTry = (scope, decl) => {
2644
- if (decl.finalizer) return todo('try finally not implemented yet');
3012
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2645
3013
 
2646
3014
  const out = [];
2647
3015
 
@@ -2672,7 +3040,7 @@ const generateAssignPat = (scope, decl) => {
2672
3040
  // TODO
2673
3041
  // if identifier declared, use that
2674
3042
  // else, use default (right)
2675
- return todo('assignment pattern (optional arg)');
3043
+ return todo(scope, 'assignment pattern (optional arg)');
2676
3044
  };
2677
3045
 
2678
3046
  let pages = new Map();
@@ -2751,16 +3119,22 @@ const getAllocType = itemType => {
2751
3119
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2752
3120
  const out = [];
2753
3121
 
3122
+ scope.arrays ??= new Map();
3123
+
2754
3124
  let firstAssign = false;
2755
- if (!arrays.has(name) || name === '$undeclared') {
3125
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2756
3126
  firstAssign = true;
2757
3127
 
2758
3128
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2759
3129
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2760
- 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);
2761
3133
  }
2762
3134
 
2763
- const pointer = arrays.get(name);
3135
+ const pointer = scope.arrays.get(name);
3136
+
3137
+ const local = global ? globals[name] : scope.locals[name];
2764
3138
 
2765
3139
  const useRawElements = !!decl.rawElements;
2766
3140
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2794,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2794
3168
  return [ out, pointer ];
2795
3169
  }
2796
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
+
2797
3182
  // store length as 0th array
2798
3183
  out.push(
2799
- ...number(0, Valtype.i32),
3184
+ ...pointerWasm,
2800
3185
  ...number(length, Valtype.i32),
2801
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3186
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2802
3187
  );
2803
3188
 
2804
3189
  const storeOp = StoreOps[itemType];
@@ -2807,14 +3192,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2807
3192
  if (elements[i] == null) continue;
2808
3193
 
2809
3194
  out.push(
2810
- ...number(0, Valtype.i32),
3195
+ ...pointerWasm,
2811
3196
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2812
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3197
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2813
3198
  );
2814
3199
  }
2815
3200
 
2816
3201
  // local value as pointer
2817
- out.push(...number(pointer));
3202
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2818
3203
 
2819
3204
  return [ out, pointer ];
2820
3205
  };
@@ -2846,20 +3231,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2846
3231
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2847
3232
  };
2848
3233
 
2849
- let arrays = new Map();
2850
3234
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2851
3235
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2852
3236
  };
2853
3237
 
2854
3238
  export const generateMember = (scope, decl, _global, _name) => {
2855
3239
  const name = decl.object.name;
2856
- const pointer = arrays.get(name);
3240
+ const pointer = scope.arrays?.get(name);
3241
+
3242
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2857
3243
 
2858
- 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
+ }
2859
3257
 
2860
3258
  // hack: .length
2861
3259
  if (decl.property.name === 'length') {
2862
- // 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
+
2863
3281
  return [
2864
3282
  ...(aotPointer ? number(0, Valtype.i32) : [
2865
3283
  ...generate(scope, decl.object),
@@ -2884,7 +3302,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2884
3302
  }
2885
3303
 
2886
3304
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2887
- [TYPES._array]: [
3305
+ [TYPES.array]: [
2888
3306
  // get index as valtype
2889
3307
  ...property,
2890
3308
 
@@ -2903,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2903
3321
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2904
3322
 
2905
3323
  ...number(TYPES.number, Valtype.i32),
2906
- setLastType(scope)
3324
+ ...setLastType(scope)
2907
3325
  ],
2908
3326
 
2909
3327
  [TYPES.string]: [
@@ -2935,9 +3353,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2935
3353
  ...number(newPointer),
2936
3354
 
2937
3355
  ...number(TYPES.string, Valtype.i32),
2938
- setLastType(scope)
3356
+ ...setLastType(scope)
2939
3357
  ],
2940
- [TYPES._bytestring]: [
3358
+ [TYPES.bytestring]: [
2941
3359
  // setup new/out array
2942
3360
  ...newOut,
2943
3361
  [ Opcodes.drop ],
@@ -2954,19 +3372,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2954
3372
  ]),
2955
3373
 
2956
3374
  // load current string ind {arg}
2957
- [ 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) ],
2958
3376
 
2959
3377
  // store to new string ind 0
2960
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3378
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2961
3379
 
2962
3380
  // return new string (page)
2963
3381
  ...number(newPointer),
2964
3382
 
2965
- ...number(TYPES._bytestring, Valtype.i32),
2966
- setLastType(scope)
3383
+ ...number(TYPES.bytestring, Valtype.i32),
3384
+ ...setLastType(scope)
2967
3385
  ],
2968
3386
 
2969
- 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)
2970
3388
  });
2971
3389
  };
2972
3390
 
@@ -2976,28 +3394,36 @@ const objectHack = node => {
2976
3394
  if (!node) return node;
2977
3395
 
2978
3396
  if (node.type === 'MemberExpression') {
2979
- if (node.computed || node.optional) return node;
3397
+ const out = (() => {
3398
+ if (node.computed || node.optional) return;
2980
3399
 
2981
- let objectName = node.object.name;
3400
+ let objectName = node.object.name;
2982
3401
 
2983
- // if object is not identifier or another member exp, give up
2984
- 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;
2985
3405
 
2986
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3406
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2987
3407
 
2988
- // if .length, give up (hack within a hack!)
2989
- 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
+ }
2990
3413
 
2991
- // no object name, give up
2992
- if (!objectName) return node;
3414
+ // no object name, give up
3415
+ if (!objectName) return;
2993
3416
 
2994
- const name = '__' + objectName + '_' + node.property.name;
2995
- 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}`);
2996
3419
 
2997
- return {
2998
- type: 'Identifier',
2999
- name
3000
- };
3420
+ return {
3421
+ type: 'Identifier',
3422
+ name
3423
+ };
3424
+ })();
3425
+
3426
+ if (out) return out;
3001
3427
  }
3002
3428
 
3003
3429
  for (const x in node) {
@@ -3011,8 +3437,8 @@ const objectHack = node => {
3011
3437
  };
3012
3438
 
3013
3439
  const generateFunc = (scope, decl) => {
3014
- if (decl.async) return todo('async functions are not supported');
3015
- if (decl.generator) return todo('generator functions are not supported');
3440
+ if (decl.async) return todo(scope, 'async functions are not supported');
3441
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3016
3442
 
3017
3443
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3018
3444
  const params = decl.params ?? [];
@@ -3028,6 +3454,11 @@ const generateFunc = (scope, decl) => {
3028
3454
  name
3029
3455
  };
3030
3456
 
3457
+ if (typedInput && decl.returnType) {
3458
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3459
+ innerScope.returns = [ valtypeBinary ];
3460
+ }
3461
+
3031
3462
  for (let i = 0; i < params.length; i++) {
3032
3463
  allocVar(innerScope, params[i].name, false);
3033
3464
 
@@ -3090,16 +3521,6 @@ const generateCode = (scope, decl) => {
3090
3521
  };
3091
3522
 
3092
3523
  const internalConstrs = {
3093
- Boolean: {
3094
- generate: (scope, decl) => {
3095
- if (decl.arguments.length === 0) return number(0);
3096
-
3097
- // should generate/run all args
3098
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3099
- },
3100
- type: TYPES.boolean
3101
- },
3102
-
3103
3524
  Array: {
3104
3525
  generate: (scope, decl, global, name) => {
3105
3526
  // new Array(i0, i1, ...)
@@ -3117,7 +3538,7 @@ const internalConstrs = {
3117
3538
 
3118
3539
  // todo: check in wasm instead of here
3119
3540
  const literalValue = arg.value ?? 0;
3120
- 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);
3121
3542
 
3122
3543
  return [
3123
3544
  ...number(0, Valtype.i32),
@@ -3128,7 +3549,8 @@ const internalConstrs = {
3128
3549
  ...number(pointer)
3129
3550
  ];
3130
3551
  },
3131
- type: TYPES._array
3552
+ type: TYPES.array,
3553
+ length: 1
3132
3554
  },
3133
3555
 
3134
3556
  __Array_of: {
@@ -3139,27 +3561,134 @@ const internalConstrs = {
3139
3561
  elements: decl.arguments
3140
3562
  }, global, name);
3141
3563
  },
3142
- 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,
3143
3586
  notConstr: true
3144
- }
3145
- };
3587
+ },
3146
3588
 
3147
- // const _ = Array.prototype.push;
3148
- // Array.prototype.push = function (a) {
3149
- // const check = arr => {
3150
- // for (const x of arr) {
3151
- // if (x === undefined) {
3152
- // console.trace(arr);
3153
- // process.exit();
3154
- // }
3155
- // if (Array.isArray(x)) check(x);
3156
- // }
3157
- // };
3158
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
3159
- // // 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);
3160
3602
 
3161
- // return _.apply(this, arguments);
3162
- // };
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
+ };
3163
3692
 
3164
3693
  export default program => {
3165
3694
  globals = {};
@@ -3169,20 +3698,23 @@ export default program => {
3169
3698
  funcs = [];
3170
3699
  funcIndex = {};
3171
3700
  depth = [];
3172
- arrays = new Map();
3173
3701
  pages = new Map();
3174
3702
  data = [];
3175
3703
  currentFuncIndex = importedFuncs.length;
3176
3704
 
3177
3705
  globalThis.valtype = 'f64';
3178
3706
 
3179
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3707
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3180
3708
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3181
3709
 
3182
3710
  globalThis.valtypeBinary = Valtype[valtype];
3183
3711
 
3184
3712
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3185
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
+
3186
3718
  // set generic opcodes for current valtype
3187
3719
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3188
3720
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3191,10 +3723,10 @@ export default program => {
3191
3723
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3192
3724
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3193
3725
 
3194
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3195
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3196
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3197
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
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];
3198
3730
 
3199
3731
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3200
3732
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3207,10 +3739,6 @@ export default program => {
3207
3739
 
3208
3740
  program.id = { name: 'main' };
3209
3741
 
3210
- globalThis.pageSize = PageSize;
3211
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3212
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3213
-
3214
3742
  const scope = {
3215
3743
  locals: {},
3216
3744
  localInd: 0
@@ -3221,7 +3749,7 @@ export default program => {
3221
3749
  body: program.body
3222
3750
  };
3223
3751
 
3224
- if (Prefs.astLog) console.log(program.body.body);
3752
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3225
3753
 
3226
3754
  generateFunc(scope, program);
3227
3755
 
@@ -3238,7 +3766,11 @@ export default program => {
3238
3766
  }
3239
3767
 
3240
3768
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3241
- 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
+ }
3242
3774
  }
3243
3775
 
3244
3776
  if (lastInst[0] === Opcodes.call) {