porffor 0.2.0-4b72c49 → 0.2.0-4d189b5

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 (53) hide show
  1. package/CONTRIBUTING.md +248 -0
  2. package/LICENSE +20 -20
  3. package/README.md +129 -84
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +1 -1
  8. package/compiler/{sections.js → assemble.js} +59 -12
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +7 -84
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +2071 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/int.ts +147 -0
  17. package/compiler/builtins/number.ts +527 -0
  18. package/compiler/builtins/porffor.d.ts +43 -7
  19. package/compiler/builtins/string.ts +1055 -0
  20. package/compiler/builtins/tostring.ts +45 -0
  21. package/compiler/builtins.js +403 -110
  22. package/compiler/{codeGen.js → codegen.js} +820 -309
  23. package/compiler/decompile.js +0 -1
  24. package/compiler/embedding.js +22 -22
  25. package/compiler/encoding.js +108 -10
  26. package/compiler/generated_builtins.js +1470 -4
  27. package/compiler/index.js +16 -14
  28. package/compiler/log.js +6 -3
  29. package/compiler/opt.js +23 -22
  30. package/compiler/parse.js +30 -22
  31. package/compiler/precompile.js +25 -26
  32. package/compiler/prefs.js +7 -6
  33. package/compiler/prototype.js +2 -18
  34. package/compiler/types.js +37 -0
  35. package/compiler/wasmSpec.js +18 -6
  36. package/compiler/wrap.js +51 -47
  37. package/package.json +9 -5
  38. package/porf +2 -0
  39. package/rhemyn/compile.js +44 -26
  40. package/rhemyn/parse.js +322 -320
  41. package/rhemyn/test/parse.js +58 -58
  42. package/runner/compare.js +34 -34
  43. package/runner/debug.js +122 -0
  44. package/runner/index.js +74 -11
  45. package/runner/profiler.js +102 -0
  46. package/runner/repl.js +42 -9
  47. package/runner/sizes.js +37 -37
  48. package/demo.js +0 -15
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
10
  import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
11
12
 
12
13
  let globals = {};
13
14
  let globalInd = 0;
@@ -24,35 +25,37 @@ const debug = str => {
24
25
  const logChar = n => {
25
26
  code.push(...number(n));
26
27
 
27
- code.push(Opcodes.call);
28
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
29
29
  };
30
30
 
31
31
  for (let i = 0; i < str.length; i++) {
32
32
  logChar(str.charCodeAt(i));
33
33
  }
34
34
 
35
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
36
36
 
37
37
  return code;
38
38
  };
39
39
 
40
- const todo = msg => {
41
- class TodoError extends Error {
42
- constructor(message) {
43
- super(message);
44
- this.name = 'TodoError';
45
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
46
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
47
50
 
48
- throw new TodoError(`todo: ${msg}`);
49
-
50
- const code = [];
51
-
52
- code.push(...debug(`todo! ` + msg));
53
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
54
53
 
55
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
56
59
  };
57
60
 
58
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -105,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
108
  return generateUnary(scope, decl);
106
109
 
107
110
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
112
 
110
113
  case 'IfStatement':
111
114
  return generateIf(scope, decl);
@@ -116,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
119
  case 'WhileStatement':
117
120
  return generateWhile(scope, decl);
118
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
119
125
  case 'ForOfStatement':
120
126
  return generateForOf(scope, decl);
121
127
 
@@ -125,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
131
  case 'ContinueStatement':
126
132
  return generateContinue(scope, decl);
127
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
128
137
  case 'EmptyStatement':
129
138
  return generateEmpty(scope, decl);
130
139
 
@@ -138,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
147
  return generateTry(scope, decl);
139
148
 
140
149
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
142
151
  return [];
143
152
 
144
153
  case 'ArrayExpression':
@@ -152,10 +161,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
161
  const funcsBefore = funcs.length;
153
162
  generate(scope, decl.declaration);
154
163
 
155
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
156
171
 
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
159
174
 
160
175
  return [];
161
176
 
@@ -186,7 +201,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
186
201
  }
187
202
 
188
203
  let inst = Opcodes[asm[0].replace('.', '_')];
189
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
204
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
205
 
191
206
  if (!Array.isArray(inst)) inst = [ inst ];
192
207
  const immediates = asm.slice(1).map(x => {
@@ -195,40 +210,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
195
210
  return int;
196
211
  });
197
212
 
198
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
199
214
  }
200
215
 
201
216
  return out;
202
217
  },
203
218
 
204
219
  __Porffor_bs: str => [
205
- ...makeString(scope, str, undefined, undefined, true),
220
+ ...makeString(scope, str, global, name, true),
206
221
 
207
- ...number(TYPES._bytestring, Valtype.i32),
208
- setLastType(scope)
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
209
226
  ],
210
227
  __Porffor_s: str => [
211
- ...makeString(scope, str, undefined, undefined, false),
228
+ ...makeString(scope, str, global, name, false),
212
229
 
213
- ...number(TYPES.string, Valtype.i32),
214
- setLastType(scope)
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
215
234
  ],
216
235
  };
217
236
 
218
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
219
238
  // hack for inline asm
220
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
240
 
222
241
  const { quasis, expressions } = decl.quasi;
223
242
  let str = quasis[0].value.raw;
224
243
 
225
244
  for (let i = 0; i < expressions.length; i++) {
226
245
  const e = expressions[i];
227
- str += lookupName(scope, e.name)[0].idx;
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
228
252
  str += quasis[i + 1].value.raw;
229
253
  }
230
254
 
231
- return funcs[name](str);
255
+ return funcs[func](str);
232
256
  }
233
257
 
234
258
  default:
@@ -238,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
238
262
  return [];
239
263
  }
240
264
 
241
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
242
266
  }
243
267
  };
244
268
 
@@ -266,7 +290,7 @@ const lookupName = (scope, _name) => {
266
290
  return [ undefined, undefined ];
267
291
  };
268
292
 
269
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
270
294
  ...generateThrow(scope, {
271
295
  argument: {
272
296
  type: 'NewExpression',
@@ -290,7 +314,10 @@ const generateIdent = (scope, decl) => {
290
314
 
291
315
  if (Object.hasOwn(builtinVars, name)) {
292
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
293
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
294
321
  }
295
322
 
296
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -298,6 +325,11 @@ const generateIdent = (scope, decl) => {
298
325
  return number(1);
299
326
  }
300
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
301
333
  if (local?.idx === undefined) {
302
334
  // no local var with name
303
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -328,14 +360,18 @@ const generateReturn = (scope, decl) => {
328
360
  // just bare "return"
329
361
  return [
330
362
  ...number(UNDEFINED), // "undefined" if func returns
331
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
332
366
  [ Opcodes.return ]
333
367
  ];
334
368
  }
335
369
 
336
370
  return [
337
371
  ...generate(scope, decl.argument),
338
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
339
375
  [ Opcodes.return ]
340
376
  ];
341
377
  };
@@ -349,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
349
385
  return idx;
350
386
  };
351
387
 
352
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
353
390
 
354
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
355
392
  const checks = {
@@ -358,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
358
395
  '??': nullish
359
396
  };
360
397
 
361
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
362
399
 
363
400
  // generic structure for {a} OP {b}
364
401
  // -->
@@ -366,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
366
403
 
367
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
368
405
  // (like if we are in an if condition - very common)
369
- const leftIsInt = isIntOp(left[left.length - 1]);
370
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
371
408
 
372
409
  const canInt = leftIsInt && rightIsInt;
373
410
 
@@ -384,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
421
  ...right,
385
422
  // note type
386
423
  ...rightType,
387
- setLastType(scope),
424
+ ...setLastType(scope),
388
425
  [ Opcodes.else ],
389
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
390
427
  // note type
391
428
  ...leftType,
392
- setLastType(scope),
429
+ ...setLastType(scope),
393
430
  [ Opcodes.end ],
394
431
  Opcodes.i32_from
395
432
  ];
@@ -403,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
403
440
  ...right,
404
441
  // note type
405
442
  ...rightType,
406
- setLastType(scope),
443
+ ...setLastType(scope),
407
444
  [ Opcodes.else ],
408
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
409
446
  // note type
410
447
  ...leftType,
411
- setLastType(scope),
448
+ ...setLastType(scope),
412
449
  [ Opcodes.end ]
413
450
  ];
414
451
  };
415
452
 
416
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
417
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
418
455
  // todo: convert left and right to strings if not
419
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -423,8 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
423
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
424
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
425
462
 
426
- if (assign) {
427
- const pointer = arrays.get(name ?? '$undeclared');
463
+ if (assign && Prefs.aotPointerOpt) {
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
428
465
 
429
466
  return [
430
467
  // setup right
@@ -449,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
449
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
450
487
 
451
488
  // copy right
452
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
453
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
454
491
 
455
492
  [ Opcodes.local_get, leftLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
494
  [ Opcodes.i32_mul ],
458
495
  [ Opcodes.i32_add ],
459
496
 
@@ -462,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
462
499
  ...number(ValtypeSize.i32, Valtype.i32),
463
500
  [ Opcodes.i32_add ],
464
501
 
465
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
466
503
  [ Opcodes.local_get, rightLength ],
467
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
468
505
  [ Opcodes.i32_mul ],
469
506
 
470
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -522,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
522
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
523
560
 
524
561
  // copy right
525
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
526
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
527
564
 
528
565
  [ Opcodes.local_get, leftLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
567
  [ Opcodes.i32_mul ],
531
568
  [ Opcodes.i32_add ],
532
569
 
@@ -535,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
535
572
  ...number(ValtypeSize.i32, Valtype.i32),
536
573
  [ Opcodes.i32_add ],
537
574
 
538
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
539
576
  [ Opcodes.local_get, rightLength ],
540
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
541
578
  [ Opcodes.i32_mul ],
542
579
 
543
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -547,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
547
584
  ];
548
585
  };
549
586
 
550
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
551
588
  // todo: this should be rewritten into a func
552
589
  // todo: convert left and right to strings if not
553
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -556,7 +593,6 @@ const compareStrings = (scope, left, right) => {
556
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
557
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
558
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
559
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
560
596
 
561
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
562
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -584,7 +620,6 @@ const compareStrings = (scope, left, right) => {
584
620
 
585
621
  [ Opcodes.local_get, rightPointer ],
586
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
587
- [ Opcodes.local_tee, rightLength ],
588
623
 
589
624
  // fast path: check leftLength != rightLength
590
625
  [ Opcodes.i32_ne ],
@@ -599,11 +634,13 @@ const compareStrings = (scope, left, right) => {
599
634
  ...number(0, Valtype.i32),
600
635
  [ Opcodes.local_set, index ],
601
636
 
602
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
603
638
  // we do this instead of having to do mul/div each iter for perf™
604
639
  [ Opcodes.local_get, leftLength ],
605
- ...number(ValtypeSize.i16, Valtype.i32),
606
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
607
644
  [ Opcodes.local_set, indexEnd ],
608
645
 
609
646
  // iterate over each char and check if eq
@@ -613,13 +650,17 @@ const compareStrings = (scope, left, right) => {
613
650
  [ Opcodes.local_get, index ],
614
651
  [ Opcodes.local_get, leftPointer ],
615
652
  [ Opcodes.i32_add ],
616
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
617
656
 
618
657
  // fetch right
619
658
  [ Opcodes.local_get, index ],
620
659
  [ Opcodes.local_get, rightPointer ],
621
660
  [ Opcodes.i32_add ],
622
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
623
664
 
624
665
  // not equal, "return" false
625
666
  [ Opcodes.i32_ne ],
@@ -628,13 +669,13 @@ const compareStrings = (scope, left, right) => {
628
669
  [ Opcodes.br, 2 ],
629
670
  [ Opcodes.end ],
630
671
 
631
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
632
673
  [ Opcodes.local_get, index ],
633
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
634
675
  [ Opcodes.i32_add ],
635
676
  [ Opcodes.local_tee, index ],
636
677
 
637
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
638
679
  [ Opcodes.local_get, indexEnd ],
639
680
  [ Opcodes.i32_ne ],
640
681
  [ Opcodes.br_if, 0 ],
@@ -655,13 +696,14 @@ const compareStrings = (scope, left, right) => {
655
696
  };
656
697
 
657
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
658
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
659
700
  ...wasm,
660
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
661
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
662
704
 
663
705
  const useTmp = knownType(scope, type) == null;
664
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
665
707
 
666
708
  const def = [
667
709
  // if value != 0
@@ -713,7 +755,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
713
755
 
714
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
715
757
  const useTmp = knownType(scope, type) == null;
716
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
717
759
 
718
760
  return [
719
761
  ...wasm,
@@ -759,7 +801,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
759
801
 
760
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
761
803
  const useTmp = knownType(scope, type) == null;
762
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
763
805
 
764
806
  return [
765
807
  ...wasm,
@@ -853,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
853
895
  // todo: if equality op and an operand is undefined, return false
854
896
  // todo: niche null hell with 0
855
897
 
856
- // todo: this should be dynamic but for now only static
857
898
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
858
899
  if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
859
901
  // string concat (a + b)
860
- return concatStrings(scope, left, right, _global, _name, assign);
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
861
903
  }
862
904
 
863
905
  // not an equality op, NaN
@@ -880,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
880
922
  }
881
923
  }
882
924
 
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
951
+
883
952
  let ops = operatorOpcode[valtype][op];
884
953
 
885
954
  // some complex ops are implemented as builtin funcs
@@ -895,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
895
964
  ]);
896
965
  }
897
966
 
898
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
899
968
 
900
969
  if (!Array.isArray(ops)) ops = [ ops ];
901
970
  ops = [ ops ];
902
971
 
903
972
  let tmpLeft, tmpRight;
904
973
  // if equal op, check if strings for compareStrings
905
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
906
- // todo: intelligent partial skip later
907
- // if neither known are string, stop this madness
908
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
909
- return;
910
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
911
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
912
979
  tmpLeft = localTmp(scope, '__tmpop_left');
913
980
  tmpRight = localTmp(scope, '__tmpop_right');
914
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
915
1023
  ops.unshift(...stringOnly([
916
1024
  // if left is string
917
1025
  ...leftType,
@@ -923,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
923
1031
  ...number(TYPES.string, Valtype.i32),
924
1032
  [ Opcodes.i32_eq ],
925
1033
 
926
- // if either are true
927
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
928
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
929
1041
 
930
- // todo: convert non-strings to strings, for now fail immediately if one is not
931
- // if left is not string
1042
+ // if left is bytestring
932
1043
  ...leftType,
933
- ...number(TYPES.string, Valtype.i32),
934
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
935
1046
 
936
- // if right is not string
1047
+ // if right is bytestring
937
1048
  ...rightType,
938
- ...number(TYPES.string, Valtype.i32),
939
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
940
1051
 
941
- // if either are true
942
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
943
1054
  [ Opcodes.if, Blocktype.void ],
944
- ...number(0, Valtype.i32),
945
- [ Opcodes.br, 2 ],
946
- [ Opcodes.end ],
947
-
948
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
949
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
950
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
951
1057
  [ Opcodes.br, 1 ],
952
1058
  [ Opcodes.end ],
@@ -958,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
958
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
959
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
960
1066
  // }
961
- })();
1067
+ }
962
1068
 
963
1069
  return finalize([
964
1070
  ...left,
@@ -977,6 +1083,21 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
977
1083
  return out;
978
1084
  };
979
1085
 
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
980
1101
  const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
981
1102
  const existing = funcs.find(x => x.name === name);
982
1103
  if (existing) return existing;
@@ -995,28 +1116,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
995
1116
  data.push(copy);
996
1117
  }
997
1118
 
998
- if (typeof wasm === 'function') {
999
- const scope = {
1000
- name,
1001
- params,
1002
- locals,
1003
- returns,
1004
- localInd: allLocals.length,
1005
- };
1006
-
1007
- wasm = wasm(scope, {
1008
- TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1009
- builtin: name => {
1010
- let idx = funcIndex[name] ?? importedFuncs[name];
1011
- if (idx === undefined && builtinFuncs[name]) {
1012
- includeBuiltin(scope, name);
1013
- idx = funcIndex[name];
1014
- }
1015
-
1016
- return idx;
1017
- }
1018
- });
1019
- }
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1020
1120
 
1021
1121
  let baseGlobalIdx, i = 0;
1022
1122
  for (const type of globalTypes) {
@@ -1040,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1040
1140
  params,
1041
1141
  locals,
1042
1142
  returns,
1043
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1044
1144
  wasm,
1045
1145
  internal: true,
1046
1146
  index: currentFuncIndex++
@@ -1063,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1063
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1064
1164
  };
1065
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1066
1167
  // T = JS type, V = value/pointer
1067
1168
  // 0bTTT
1068
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1086,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
1086
1187
  // 4: internal type
1087
1188
  // 5: pointer
1088
1189
 
1089
- const TYPES = {
1090
- number: 0x00,
1091
- boolean: 0x01,
1092
- string: 0x02,
1093
- undefined: 0x03,
1094
- object: 0x04,
1095
- function: 0x05,
1096
- symbol: 0x06,
1097
- bigint: 0x07,
1098
-
1099
- // these are not "typeof" types but tracked internally
1100
- _array: 0x10,
1101
- _regexp: 0x11,
1102
- _bytestring: 0x12
1103
- };
1104
-
1105
- const TYPE_NAMES = {
1106
- [TYPES.number]: 'Number',
1107
- [TYPES.boolean]: 'Boolean',
1108
- [TYPES.string]: 'String',
1109
- [TYPES.undefined]: 'undefined',
1110
- [TYPES.object]: 'Object',
1111
- [TYPES.function]: 'Function',
1112
- [TYPES.symbol]: 'Symbol',
1113
- [TYPES.bigint]: 'BigInt',
1114
-
1115
- [TYPES._array]: 'Array',
1116
- [TYPES._regexp]: 'RegExp',
1117
- [TYPES._bytestring]: 'ByteString'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1118
1195
  };
1119
1196
 
1120
1197
  const getType = (scope, _name) => {
1121
1198
  const name = mapName(_name);
1122
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1123
1202
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1124
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1125
1204
 
@@ -1127,11 +1206,10 @@ const getType = (scope, _name) => {
1127
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1128
1207
 
1129
1208
  let type = TYPES.undefined;
1130
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1131
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1132
1211
 
1133
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1134
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1135
1213
 
1136
1214
  return number(type, Valtype.i32);
1137
1215
  };
@@ -1154,15 +1232,16 @@ const setType = (scope, _name, type) => {
1154
1232
  ];
1155
1233
 
1156
1234
  // throw new Error('could not find var');
1235
+ return [];
1157
1236
  };
1158
1237
 
1159
1238
  const getLastType = scope => {
1160
1239
  scope.gotLastType = true;
1161
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1162
1241
  };
1163
1242
 
1164
1243
  const setLastType = scope => {
1165
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1166
1245
  };
1167
1246
 
1168
1247
  const getNodeType = (scope, node) => {
@@ -1187,13 +1266,25 @@ const getNodeType = (scope, node) => {
1187
1266
  const name = node.callee.name;
1188
1267
  if (!name) {
1189
1268
  // iife
1190
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1191
1270
 
1192
1271
  // presume
1193
1272
  // todo: warn here?
1194
1273
  return TYPES.number;
1195
1274
  }
1196
1275
 
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1197
1288
  const func = funcs.find(x => x.name === name);
1198
1289
 
1199
1290
  if (func) {
@@ -1201,7 +1292,7 @@ const getNodeType = (scope, node) => {
1201
1292
  if (func.returnType) return func.returnType;
1202
1293
  }
1203
1294
 
1204
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1205
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1206
1297
 
1207
1298
  // check if this is a prototype function
@@ -1221,7 +1312,7 @@ const getNodeType = (scope, node) => {
1221
1312
  return TYPES.number;
1222
1313
  }
1223
1314
 
1224
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1225
1316
 
1226
1317
  // presume
1227
1318
  // todo: warn here?
@@ -1276,6 +1367,7 @@ const getNodeType = (scope, node) => {
1276
1367
 
1277
1368
  // todo: this should be dynamic but for now only static
1278
1369
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1279
1371
 
1280
1372
  return TYPES.number;
1281
1373
 
@@ -1312,15 +1404,21 @@ const getNodeType = (scope, node) => {
1312
1404
 
1313
1405
  // ts hack
1314
1406
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1315
1408
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1316
1409
 
1317
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1318
1411
 
1319
1412
  // presume
1320
1413
  return TYPES.number;
1321
1414
  }
1322
1415
 
1323
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1324
1422
 
1325
1423
  // presume
1326
1424
  // todo: warn here?
@@ -1353,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1353
1451
  return makeString(scope, decl.value, global, name);
1354
1452
 
1355
1453
  default:
1356
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1357
1455
  }
1358
1456
  };
1359
1457
 
@@ -1362,6 +1460,8 @@ const countLeftover = wasm => {
1362
1460
 
1363
1461
  for (let i = 0; i < wasm.length; i++) {
1364
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1365
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1366
1466
  if (inst[0] === Opcodes.if) count--;
1367
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1370,18 +1470,25 @@ const countLeftover = wasm => {
1370
1470
  if (inst[0] === Opcodes.end) depth--;
1371
1471
 
1372
1472
  if (depth === 0)
1373
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1374
1474
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1375
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1376
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1377
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1378
1478
  else if (inst[0] === Opcodes.return) count = 0;
1379
1479
  else if (inst[0] === Opcodes.call) {
1380
1480
  let func = funcs.find(x => x.index === inst[1]);
1381
- if (func) {
1382
- count -= func.params.length;
1383
- } else count--;
1384
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1385
1492
  } else count--;
1386
1493
 
1387
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1473,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1473
1580
  name = func.name;
1474
1581
  }
1475
1582
 
1476
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1477
1584
  // literal eval hack
1478
- const code = decl.arguments[0].value;
1479
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1480
1598
 
1481
1599
  const out = generate(scope, {
1482
1600
  type: 'BlockStatement',
@@ -1490,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1490
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1491
1609
  out.push(
1492
1610
  ...getNodeType(scope, finalStatement),
1493
- setLastType(scope)
1611
+ ...setLastType(scope)
1494
1612
  );
1495
1613
  } else if (countLeftover(out) === 0) {
1496
1614
  out.push(...number(UNDEFINED));
1497
1615
  out.push(
1498
1616
  ...number(TYPES.undefined, Valtype.i32),
1499
- setLastType(scope)
1617
+ ...setLastType(scope)
1500
1618
  );
1501
1619
  }
1502
1620
 
@@ -1518,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1518
1636
 
1519
1637
  target = { ...decl.callee };
1520
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1521
1642
  }
1522
1643
 
1523
1644
  // literal.func()
@@ -1525,22 +1646,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1525
1646
  // megahack for /regex/.func()
1526
1647
  const funcName = decl.callee.property.name;
1527
1648
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1528
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1649
+ const regex = decl.callee.object.regex.pattern;
1650
+ const rhemynName = `regex_${funcName}_${regex}`;
1529
1651
 
1530
- funcIndex[func.name] = func.index;
1531
- funcs.push(func);
1652
+ if (!funcIndex[rhemynName]) {
1653
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1532
1654
 
1655
+ funcIndex[func.name] = func.index;
1656
+ funcs.push(func);
1657
+ }
1658
+
1659
+ const idx = funcIndex[rhemynName];
1533
1660
  return [
1534
1661
  // make string arg
1535
1662
  ...generate(scope, decl.arguments[0]),
1663
+ Opcodes.i32_to_u,
1664
+ ...getNodeType(scope, decl.arguments[0]),
1536
1665
 
1537
1666
  // call regex func
1538
- Opcodes.i32_to_u,
1539
- [ Opcodes.call, func.index ],
1667
+ [ Opcodes.call, idx ],
1540
1668
  Opcodes.i32_from_u,
1541
1669
 
1542
1670
  ...number(TYPES.boolean, Valtype.i32),
1543
- setLastType(scope)
1671
+ ...setLastType(scope)
1544
1672
  ];
1545
1673
  }
1546
1674
 
@@ -1565,12 +1693,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1565
1693
  // }
1566
1694
 
1567
1695
  if (protoName) {
1696
+ const protoBC = {};
1697
+
1698
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1699
+
1700
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1701
+ for (const x of builtinProtoCands) {
1702
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1703
+ if (type == null) continue;
1704
+
1705
+ protoBC[type] = generateCall(scope, {
1706
+ callee: {
1707
+ type: 'Identifier',
1708
+ name: x
1709
+ },
1710
+ arguments: [ target, ...decl.arguments ],
1711
+ _protoInternalCall: true
1712
+ });
1713
+ }
1714
+ }
1715
+
1568
1716
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1569
1717
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1570
1718
  return acc;
1571
1719
  }, {});
1572
1720
 
1573
- // no prototype function candidates, ignore
1574
1721
  if (Object.keys(protoCands).length > 0) {
1575
1722
  // use local for cached i32 length as commonly used
1576
1723
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1588,7 +1735,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1588
1735
 
1589
1736
  let allOptUnused = true;
1590
1737
  let lengthI32CacheUsed = false;
1591
- const protoBC = {};
1592
1738
  for (const x in protoCands) {
1593
1739
  const protoFunc = protoCands[x];
1594
1740
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1596,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1596
1742
  ...RTArrayUtil.getLength(getPointer),
1597
1743
 
1598
1744
  ...number(TYPES.number, Valtype.i32),
1599
- setLastType(scope)
1745
+ ...setLastType(scope)
1600
1746
  ];
1601
1747
  continue;
1602
1748
  }
@@ -1633,7 +1779,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1633
1779
  ...protoOut,
1634
1780
 
1635
1781
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1636
- setLastType(scope),
1782
+ ...setLastType(scope),
1637
1783
  [ Opcodes.end ]
1638
1784
  ];
1639
1785
  }
@@ -1659,10 +1805,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1659
1805
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1660
1806
  ];
1661
1807
  }
1808
+
1809
+ if (Object.keys(protoBC).length > 0) {
1810
+ return typeSwitch(scope, getNodeType(scope, target), {
1811
+ ...protoBC,
1812
+
1813
+ // TODO: error better
1814
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1815
+ }, valtypeBinary);
1816
+ }
1662
1817
  }
1663
1818
 
1664
1819
  // TODO: only allows callee as literal
1665
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1820
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1666
1821
 
1667
1822
  let idx = funcIndex[name] ?? importedFuncs[name];
1668
1823
  if (idx === undefined && builtinFuncs[name]) {
@@ -1672,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1672
1827
  idx = funcIndex[name];
1673
1828
 
1674
1829
  // infer arguments types from builtins params
1675
- const func = funcs.find(x => x.name === name);
1676
- for (let i = 0; i < decl.arguments.length; i++) {
1677
- const arg = decl.arguments[i];
1678
- if (!arg.name) continue;
1679
-
1680
- const local = scope.locals[arg.name];
1681
- if (!local) continue;
1682
-
1683
- local.type = func.params[i];
1684
- if (local.type === Valtype.v128) {
1685
- // specify vec subtype inferred from last vec type in function name
1686
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1687
- }
1688
- }
1830
+ // const func = funcs.find(x => x.name === name);
1831
+ // for (let i = 0; i < decl.arguments.length; i++) {
1832
+ // const arg = decl.arguments[i];
1833
+ // if (!arg.name) continue;
1834
+
1835
+ // const local = scope.locals[arg.name];
1836
+ // if (!local) continue;
1837
+
1838
+ // local.type = func.params[i];
1839
+ // if (local.type === Valtype.v128) {
1840
+ // // specify vec subtype inferred from last vec type in function name
1841
+ // local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1842
+ // }
1843
+ // }
1689
1844
  }
1690
1845
 
1691
1846
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1698,9 +1853,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1698
1853
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1699
1854
  const wasmOps = {
1700
1855
  // pointer, align, offset
1701
- i32_load8_u: { imms: 2, args: 1 },
1856
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1702
1857
  // pointer, value, align, offset
1703
- i32_store8: { imms: 2, args: 2 },
1858
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1859
+ // pointer, align, offset
1860
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1861
+ // pointer, value, align, offset
1862
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1863
+ // pointer, align, offset
1864
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1865
+ // pointer, value, align, offset
1866
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1867
+
1868
+ // pointer, align, offset
1869
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1870
+ // pointer, value, align, offset
1871
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1872
+
1873
+ // value
1874
+ i32_const: { imms: 1, args: [], returns: 1 },
1704
1875
  };
1705
1876
 
1706
1877
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1709,28 +1880,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1709
1880
  const op = wasmOps[opName];
1710
1881
 
1711
1882
  const argOut = [];
1712
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1883
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1884
+ ...generate(scope, decl.arguments[i]),
1885
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1886
+ );
1713
1887
 
1714
1888
  // literals only
1715
- const imms = decl.arguments.slice(op.args).map(x => x.value);
1889
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1716
1890
 
1717
1891
  return [
1718
1892
  ...argOut,
1719
- [ Opcodes[opName], ...imms ]
1893
+ [ Opcodes[opName], ...imms ],
1894
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1720
1895
  ];
1721
1896
  }
1722
1897
  }
1723
1898
 
1724
1899
  if (idx === undefined) {
1725
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1726
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1900
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1901
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1727
1902
  }
1728
1903
 
1729
1904
  const func = funcs.find(x => x.index === idx);
1730
1905
 
1731
1906
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1732
1907
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1733
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1908
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1734
1909
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1735
1910
 
1736
1911
  let args = decl.arguments;
@@ -1751,7 +1926,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1751
1926
  const arg = args[i];
1752
1927
  out = out.concat(generate(scope, arg));
1753
1928
 
1754
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1929
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1933
+ if (importedFuncs[name] && name.startsWith('profile')) {
1755
1934
  out.push(Opcodes.i32_to);
1756
1935
  }
1757
1936
 
@@ -1770,9 +1949,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1770
1949
  // ...number(type, Valtype.i32),
1771
1950
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1772
1951
  // );
1773
- } else out.push(setLastType(scope));
1952
+ } else out.push(...setLastType(scope));
1774
1953
 
1775
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1954
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1776
1955
  out.push(Opcodes.i32_from);
1777
1956
  }
1778
1957
 
@@ -1782,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1782
1961
  const generateNew = (scope, decl, _global, _name) => {
1783
1962
  // hack: basically treat this as a normal call for builtins for now
1784
1963
  const name = mapName(decl.callee.name);
1964
+
1785
1965
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1786
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1966
+
1967
+ if (builtinFuncs[name + '$constructor']) {
1968
+ // custom ...$constructor override builtin func
1969
+ return generateCall(scope, {
1970
+ ...decl,
1971
+ callee: {
1972
+ type: 'Identifier',
1973
+ name: name + '$constructor'
1974
+ }
1975
+ }, _global, _name);
1976
+ }
1977
+
1978
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1787
1979
 
1788
1980
  return generateCall(scope, decl, _global, _name);
1789
1981
  };
@@ -1917,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1917
2109
  [ Opcodes.block, returns ]
1918
2110
  ];
1919
2111
 
1920
- // todo: use br_table?
1921
-
1922
2112
  for (const x in bc) {
1923
2113
  if (x === 'default') continue;
1924
2114
 
@@ -1974,12 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1974
2164
  };
1975
2165
 
1976
2166
  const typeAnnoToPorfType = x => {
1977
- if (TYPES[x]) return TYPES[x];
1978
- if (TYPES['_' + x]) return TYPES['_' + x];
2167
+ if (!x) return null;
2168
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2169
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1979
2170
 
1980
2171
  switch (x) {
1981
2172
  case 'i32':
1982
2173
  case 'i64':
2174
+ case 'f64':
1983
2175
  return TYPES.number;
1984
2176
  }
1985
2177
 
@@ -1990,7 +2182,7 @@ const extractTypeAnnotation = decl => {
1990
2182
  let a = decl;
1991
2183
  while (a.typeAnnotation) a = a.typeAnnotation;
1992
2184
 
1993
- let type, elementType;
2185
+ let type = null, elementType = null;
1994
2186
  if (a.typeName) {
1995
2187
  type = a.typeName.name;
1996
2188
  } else if (a.type.endsWith('Keyword')) {
@@ -2017,11 +2209,12 @@ const generateVar = (scope, decl) => {
2017
2209
 
2018
2210
  // global variable if in top scope (main) and var ..., or if wanted
2019
2211
  const global = topLevel || decl._bare; // decl.kind === 'var';
2212
+ const target = global ? globals : scope.locals;
2020
2213
 
2021
2214
  for (const x of decl.declarations) {
2022
2215
  const name = mapName(x.id.name);
2023
2216
 
2024
- if (!name) return todo('destructuring is not supported yet');
2217
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2025
2218
 
2026
2219
  if (x.init && isFuncType(x.init.type)) {
2027
2220
  // hack for let a = function () { ... }
@@ -2038,16 +2231,29 @@ const generateVar = (scope, decl) => {
2038
2231
  continue; // always ignore
2039
2232
  }
2040
2233
 
2041
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2234
+ // // generate init before allocating var
2235
+ // let generated;
2236
+ // if (x.init) generated = generate(scope, x.init, global, name);
2237
+
2238
+ const typed = typedInput && x.id.typeAnnotation;
2239
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2042
2240
 
2043
- if (typedInput && x.id.typeAnnotation) {
2241
+ if (typed) {
2044
2242
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2045
2243
  }
2046
2244
 
2047
2245
  if (x.init) {
2048
- out = out.concat(generate(scope, x.init, global, name));
2049
-
2050
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2246
+ const generated = generate(scope, x.init, global, name);
2247
+ if (scope.arrays?.get(name) != null) {
2248
+ // hack to set local as pointer before
2249
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2250
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2251
+ generated.pop();
2252
+ out = out.concat(generated);
2253
+ } else {
2254
+ out = out.concat(generated);
2255
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2256
+ }
2051
2257
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2052
2258
  }
2053
2259
 
@@ -2058,7 +2264,8 @@ const generateVar = (scope, decl) => {
2058
2264
  return out;
2059
2265
  };
2060
2266
 
2061
- const generateAssign = (scope, decl) => {
2267
+ // todo: optimize this func for valueUnused
2268
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2062
2269
  const { type, name } = decl.left;
2063
2270
 
2064
2271
  if (type === 'ObjectPattern') {
@@ -2073,22 +2280,30 @@ const generateAssign = (scope, decl) => {
2073
2280
  return [];
2074
2281
  }
2075
2282
 
2283
+ const op = decl.operator.slice(0, -1) || '=';
2284
+
2076
2285
  // hack: .length setter
2077
2286
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2078
2287
  const name = decl.left.object.name;
2079
- const pointer = arrays.get(name);
2288
+ const pointer = scope.arrays?.get(name);
2080
2289
 
2081
- const aotPointer = pointer != null;
2290
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2082
2291
 
2083
2292
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2293
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2084
2294
 
2085
2295
  return [
2086
2296
  ...(aotPointer ? number(0, Valtype.i32) : [
2087
2297
  ...generate(scope, decl.left.object),
2088
2298
  Opcodes.i32_to_u
2089
2299
  ]),
2300
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2090
2301
 
2091
- ...generate(scope, decl.right),
2302
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2303
+ [ Opcodes.local_get, pointerTmp ],
2304
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2305
+ Opcodes.i32_from_u
2306
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2092
2307
  [ Opcodes.local_tee, newValueTmp ],
2093
2308
 
2094
2309
  Opcodes.i32_to_u,
@@ -2098,14 +2313,12 @@ const generateAssign = (scope, decl) => {
2098
2313
  ];
2099
2314
  }
2100
2315
 
2101
- const op = decl.operator.slice(0, -1) || '=';
2102
-
2103
2316
  // arr[i]
2104
2317
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2105
2318
  const name = decl.left.object.name;
2106
- const pointer = arrays.get(name);
2319
+ const pointer = scope.arrays?.get(name);
2107
2320
 
2108
- const aotPointer = pointer != null;
2321
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2109
2322
 
2110
2323
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2111
2324
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2161,7 +2374,7 @@ const generateAssign = (scope, decl) => {
2161
2374
  ];
2162
2375
  }
2163
2376
 
2164
- if (!name) return todo('destructuring is not supported yet');
2377
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2165
2378
 
2166
2379
  const [ local, isGlobal ] = lookupName(scope, name);
2167
2380
 
@@ -2266,7 +2479,7 @@ const generateUnary = (scope, decl) => {
2266
2479
  return out;
2267
2480
  }
2268
2481
 
2269
- case 'delete':
2482
+ case 'delete': {
2270
2483
  let toReturn = true, toGenerate = true;
2271
2484
 
2272
2485
  if (decl.argument.type === 'Identifier') {
@@ -2288,9 +2501,26 @@ const generateUnary = (scope, decl) => {
2288
2501
 
2289
2502
  out.push(...number(toReturn ? 1 : 0));
2290
2503
  return out;
2504
+ }
2291
2505
 
2292
- case 'typeof':
2293
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2506
+ case 'typeof': {
2507
+ let overrideType, toGenerate = true;
2508
+
2509
+ if (decl.argument.type === 'Identifier') {
2510
+ const out = generateIdent(scope, decl.argument);
2511
+
2512
+ // if ReferenceError (undeclared var), ignore and return undefined
2513
+ if (out[1]) {
2514
+ // does not exist (2 ops from throw)
2515
+ overrideType = number(TYPES.undefined, Valtype.i32);
2516
+ toGenerate = false;
2517
+ }
2518
+ }
2519
+
2520
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2521
+ disposeLeftover(out);
2522
+
2523
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2294
2524
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2295
2525
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2296
2526
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2301,27 +2531,30 @@ const generateUnary = (scope, decl) => {
2301
2531
 
2302
2532
  // object and internal types
2303
2533
  default: makeString(scope, 'object', false, '#typeof_result'),
2304
- });
2534
+ }));
2535
+
2536
+ return out;
2537
+ }
2305
2538
 
2306
2539
  default:
2307
- return todo(`unary operator ${decl.operator} not implemented yet`);
2540
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2308
2541
  }
2309
2542
  };
2310
2543
 
2311
- const generateUpdate = (scope, decl) => {
2544
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2312
2545
  const { name } = decl.argument;
2313
2546
 
2314
2547
  const [ local, isGlobal ] = lookupName(scope, name);
2315
2548
 
2316
2549
  if (local === undefined) {
2317
- return todo(`update expression with undefined variable`);
2550
+ return todo(scope, `update expression with undefined variable`, true);
2318
2551
  }
2319
2552
 
2320
2553
  const idx = local.idx;
2321
2554
  const out = [];
2322
2555
 
2323
2556
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2324
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2557
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2325
2558
 
2326
2559
  switch (decl.operator) {
2327
2560
  case '++':
@@ -2334,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
2334
2567
  }
2335
2568
 
2336
2569
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2337
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2570
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2338
2571
 
2339
2572
  return out;
2340
2573
  };
@@ -2374,7 +2607,7 @@ const generateConditional = (scope, decl) => {
2374
2607
  // note type
2375
2608
  out.push(
2376
2609
  ...getNodeType(scope, decl.consequent),
2377
- setLastType(scope)
2610
+ ...setLastType(scope)
2378
2611
  );
2379
2612
 
2380
2613
  out.push([ Opcodes.else ]);
@@ -2383,7 +2616,7 @@ const generateConditional = (scope, decl) => {
2383
2616
  // note type
2384
2617
  out.push(
2385
2618
  ...getNodeType(scope, decl.alternate),
2386
- setLastType(scope)
2619
+ ...setLastType(scope)
2387
2620
  );
2388
2621
 
2389
2622
  out.push([ Opcodes.end ]);
@@ -2397,7 +2630,7 @@ const generateFor = (scope, decl) => {
2397
2630
  const out = [];
2398
2631
 
2399
2632
  if (decl.init) {
2400
- out.push(...generate(scope, decl.init));
2633
+ out.push(...generate(scope, decl.init, false, undefined, true));
2401
2634
  disposeLeftover(out);
2402
2635
  }
2403
2636
 
@@ -2415,7 +2648,7 @@ const generateFor = (scope, decl) => {
2415
2648
  out.push(...generate(scope, decl.body));
2416
2649
  out.push([ Opcodes.end ]);
2417
2650
 
2418
- if (decl.update) out.push(...generate(scope, decl.update));
2651
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2419
2652
 
2420
2653
  out.push([ Opcodes.br, 1 ]);
2421
2654
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2443,6 +2676,36 @@ const generateWhile = (scope, decl) => {
2443
2676
  return out;
2444
2677
  };
2445
2678
 
2679
+ const generateDoWhile = (scope, decl) => {
2680
+ const out = [];
2681
+
2682
+ out.push([ Opcodes.loop, Blocktype.void ]);
2683
+ depth.push('dowhile');
2684
+
2685
+ // block for break (includes all)
2686
+ out.push([ Opcodes.block, Blocktype.void ]);
2687
+ depth.push('block');
2688
+
2689
+ // block for continue
2690
+ // includes body but not test+loop so we can exit body at anytime
2691
+ // and still test+loop after
2692
+ out.push([ Opcodes.block, Blocktype.void ]);
2693
+ depth.push('block');
2694
+
2695
+ out.push(...generate(scope, decl.body));
2696
+
2697
+ out.push([ Opcodes.end ]);
2698
+ depth.pop();
2699
+
2700
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2701
+ out.push([ Opcodes.br_if, 1 ]);
2702
+
2703
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2704
+ depth.pop(); depth.pop();
2705
+
2706
+ return out;
2707
+ };
2708
+
2446
2709
  const generateForOf = (scope, decl) => {
2447
2710
  const out = [];
2448
2711
 
@@ -2479,7 +2742,10 @@ const generateForOf = (scope, decl) => {
2479
2742
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2480
2743
  }
2481
2744
 
2745
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2746
+
2482
2747
  const [ local, isGlobal ] = lookupName(scope, leftName);
2748
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2483
2749
 
2484
2750
  depth.push('block');
2485
2751
  depth.push('block');
@@ -2488,6 +2754,7 @@ const generateForOf = (scope, decl) => {
2488
2754
  // hack: this is naughty and will break things!
2489
2755
  let newOut = number(0, Valtype.f64), newPointer = -1;
2490
2756
  if (pages.hasAnyString) {
2757
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2491
2758
  0, [ newOut, newPointer ] = makeArray(scope, {
2492
2759
  rawElements: new Array(1)
2493
2760
  }, isGlobal, leftName, true, 'i16');
@@ -2579,6 +2846,56 @@ const generateForOf = (scope, decl) => {
2579
2846
  [ Opcodes.end ],
2580
2847
  [ Opcodes.end ]
2581
2848
  ],
2849
+ [TYPES._bytestring]: [
2850
+ ...setType(scope, leftName, TYPES._bytestring),
2851
+
2852
+ [ Opcodes.loop, Blocktype.void ],
2853
+
2854
+ // setup new/out array
2855
+ ...newOut,
2856
+ [ Opcodes.drop ],
2857
+
2858
+ ...number(0, Valtype.i32), // base 0 for store after
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.local_get, pointer ],
2862
+ [ Opcodes.local_get, counter ],
2863
+ [ Opcodes.i32_add ],
2864
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2865
+
2866
+ // store to new string ind 0
2867
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2868
+
2869
+ // return new string (page)
2870
+ ...number(newPointer),
2871
+
2872
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2873
+
2874
+ [ Opcodes.block, Blocktype.void ],
2875
+ [ Opcodes.block, Blocktype.void ],
2876
+ ...generate(scope, decl.body),
2877
+ [ Opcodes.end ],
2878
+
2879
+ // increment iter pointer
2880
+ // [ Opcodes.local_get, pointer ],
2881
+ // ...number(1, Valtype.i32),
2882
+ // [ Opcodes.i32_add ],
2883
+ // [ Opcodes.local_set, pointer ],
2884
+
2885
+ // increment counter by 1
2886
+ [ Opcodes.local_get, counter ],
2887
+ ...number(1, Valtype.i32),
2888
+ [ Opcodes.i32_add ],
2889
+ [ Opcodes.local_tee, counter ],
2890
+
2891
+ // loop if counter != length
2892
+ [ Opcodes.local_get, length ],
2893
+ [ Opcodes.i32_ne ],
2894
+ [ Opcodes.br_if, 1 ],
2895
+
2896
+ [ Opcodes.end ],
2897
+ [ Opcodes.end ]
2898
+ ],
2582
2899
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2583
2900
  }, Blocktype.void));
2584
2901
 
@@ -2589,28 +2906,65 @@ const generateForOf = (scope, decl) => {
2589
2906
  return out;
2590
2907
  };
2591
2908
 
2909
+ // find the nearest loop in depth map by type
2592
2910
  const getNearestLoop = () => {
2593
2911
  for (let i = depth.length - 1; i >= 0; i--) {
2594
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2912
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2595
2913
  }
2596
2914
 
2597
2915
  return -1;
2598
2916
  };
2599
2917
 
2600
2918
  const generateBreak = (scope, decl) => {
2601
- const nearestLoop = depth.length - getNearestLoop();
2919
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2920
+ const type = depth[target];
2921
+
2922
+ // different loop types have different branch offsets
2923
+ // as they have different wasm block/loop/if structures
2924
+ // we need to use the right offset by type to branch to the one we want
2925
+ // for a break: exit the loop without executing anything else inside it
2926
+ const offset = ({
2927
+ for: 2, // loop > if (wanted branch) > block (we are here)
2928
+ while: 2, // loop > if (wanted branch) (we are here)
2929
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2930
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2931
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2932
+ })[type];
2933
+
2602
2934
  return [
2603
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2935
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2604
2936
  ];
2605
2937
  };
2606
2938
 
2607
2939
  const generateContinue = (scope, decl) => {
2608
- const nearestLoop = depth.length - getNearestLoop();
2940
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2941
+ const type = depth[target];
2942
+
2943
+ // different loop types have different branch offsets
2944
+ // as they have different wasm block/loop/if structures
2945
+ // we need to use the right offset by type to branch to the one we want
2946
+ // for a continue: do test for the loop, and then loop depending on that success
2947
+ const offset = ({
2948
+ for: 3, // loop (wanted branch) > if > block (we are here)
2949
+ while: 1, // loop (wanted branch) > if (we are here)
2950
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2951
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2952
+ })[type];
2953
+
2609
2954
  return [
2610
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2955
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2611
2956
  ];
2612
2957
  };
2613
2958
 
2959
+ const generateLabel = (scope, decl) => {
2960
+ scope.labels ??= new Map();
2961
+
2962
+ const name = decl.label.name;
2963
+ scope.labels.set(name, depth.length);
2964
+
2965
+ return generate(scope, decl.body);
2966
+ };
2967
+
2614
2968
  const generateThrow = (scope, decl) => {
2615
2969
  scope.throws = true;
2616
2970
 
@@ -2643,7 +2997,7 @@ const generateThrow = (scope, decl) => {
2643
2997
  };
2644
2998
 
2645
2999
  const generateTry = (scope, decl) => {
2646
- if (decl.finalizer) return todo('try finally not implemented yet');
3000
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2647
3001
 
2648
3002
  const out = [];
2649
3003
 
@@ -2674,7 +3028,7 @@ const generateAssignPat = (scope, decl) => {
2674
3028
  // TODO
2675
3029
  // if identifier declared, use that
2676
3030
  // else, use default (right)
2677
- return todo('assignment pattern (optional arg)');
3031
+ return todo(scope, 'assignment pattern (optional arg)');
2678
3032
  };
2679
3033
 
2680
3034
  let pages = new Map();
@@ -2753,16 +3107,22 @@ const getAllocType = itemType => {
2753
3107
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2754
3108
  const out = [];
2755
3109
 
3110
+ scope.arrays ??= new Map();
3111
+
2756
3112
  let firstAssign = false;
2757
- if (!arrays.has(name) || name === '$undeclared') {
3113
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2758
3114
  firstAssign = true;
2759
3115
 
2760
3116
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2761
3117
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2762
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3118
+
3119
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3120
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2763
3121
  }
2764
3122
 
2765
- const pointer = arrays.get(name);
3123
+ const pointer = scope.arrays.get(name);
3124
+
3125
+ const local = global ? globals[name] : scope.locals[name];
2766
3126
 
2767
3127
  const useRawElements = !!decl.rawElements;
2768
3128
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2796,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2796
3156
  return [ out, pointer ];
2797
3157
  }
2798
3158
 
3159
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3160
+ if (pointerTmp != null) {
3161
+ out.push(
3162
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3163
+ Opcodes.i32_to_u,
3164
+ [ Opcodes.local_set, pointerTmp ]
3165
+ );
3166
+ }
3167
+
3168
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3169
+
2799
3170
  // store length as 0th array
2800
3171
  out.push(
2801
- ...number(0, Valtype.i32),
3172
+ ...pointerWasm,
2802
3173
  ...number(length, Valtype.i32),
2803
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3174
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2804
3175
  );
2805
3176
 
2806
3177
  const storeOp = StoreOps[itemType];
@@ -2809,14 +3180,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2809
3180
  if (elements[i] == null) continue;
2810
3181
 
2811
3182
  out.push(
2812
- ...number(0, Valtype.i32),
3183
+ ...pointerWasm,
2813
3184
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2814
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3185
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2815
3186
  );
2816
3187
  }
2817
3188
 
2818
3189
  // local value as pointer
2819
- out.push(...number(pointer));
3190
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2820
3191
 
2821
3192
  return [ out, pointer ];
2822
3193
  };
@@ -2848,20 +3219,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2848
3219
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2849
3220
  };
2850
3221
 
2851
- let arrays = new Map();
2852
3222
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2853
3223
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2854
3224
  };
2855
3225
 
2856
3226
  export const generateMember = (scope, decl, _global, _name) => {
2857
3227
  const name = decl.object.name;
2858
- const pointer = arrays.get(name);
3228
+ const pointer = scope.arrays?.get(name);
2859
3229
 
2860
- const aotPointer = pointer != null;
3230
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2861
3231
 
2862
3232
  // hack: .length
2863
3233
  if (decl.property.name === 'length') {
2864
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3234
+ const func = funcs.find(x => x.name === name);
3235
+ if (func) {
3236
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3237
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3238
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3239
+ }
3240
+
3241
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3242
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3243
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3244
+
2865
3245
  return [
2866
3246
  ...(aotPointer ? number(0, Valtype.i32) : [
2867
3247
  ...generate(scope, decl.object),
@@ -2905,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2905
3285
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2906
3286
 
2907
3287
  ...number(TYPES.number, Valtype.i32),
2908
- setLastType(scope)
3288
+ ...setLastType(scope)
2909
3289
  ],
2910
3290
 
2911
3291
  [TYPES.string]: [
@@ -2937,7 +3317,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2937
3317
  ...number(newPointer),
2938
3318
 
2939
3319
  ...number(TYPES.string, Valtype.i32),
2940
- setLastType(scope)
3320
+ ...setLastType(scope)
2941
3321
  ],
2942
3322
  [TYPES._bytestring]: [
2943
3323
  // setup new/out array
@@ -2956,19 +3336,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2956
3336
  ]),
2957
3337
 
2958
3338
  // load current string ind {arg}
2959
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3339
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2960
3340
 
2961
3341
  // store to new string ind 0
2962
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3342
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2963
3343
 
2964
3344
  // return new string (page)
2965
3345
  ...number(newPointer),
2966
3346
 
2967
3347
  ...number(TYPES._bytestring, Valtype.i32),
2968
- setLastType(scope)
3348
+ ...setLastType(scope)
2969
3349
  ],
2970
3350
 
2971
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3351
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2972
3352
  });
2973
3353
  };
2974
3354
 
@@ -2978,28 +3358,36 @@ const objectHack = node => {
2978
3358
  if (!node) return node;
2979
3359
 
2980
3360
  if (node.type === 'MemberExpression') {
2981
- if (node.computed || node.optional) return node;
3361
+ const out = (() => {
3362
+ if (node.computed || node.optional) return;
2982
3363
 
2983
- let objectName = node.object.name;
3364
+ let objectName = node.object.name;
2984
3365
 
2985
- // if object is not identifier or another member exp, give up
2986
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3366
+ // if object is not identifier or another member exp, give up
3367
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3368
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2987
3369
 
2988
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3370
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2989
3371
 
2990
- // if .length, give up (hack within a hack!)
2991
- if (node.property.name === 'length') return node;
3372
+ // if .length, give up (hack within a hack!)
3373
+ if (node.property.name === 'length') {
3374
+ node.object = objectHack(node.object);
3375
+ return;
3376
+ }
2992
3377
 
2993
- // no object name, give up
2994
- if (!objectName) return node;
3378
+ // no object name, give up
3379
+ if (!objectName) return;
2995
3380
 
2996
- const name = '__' + objectName + '_' + node.property.name;
2997
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3381
+ const name = '__' + objectName + '_' + node.property.name;
3382
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2998
3383
 
2999
- return {
3000
- type: 'Identifier',
3001
- name
3002
- };
3384
+ return {
3385
+ type: 'Identifier',
3386
+ name
3387
+ };
3388
+ })();
3389
+
3390
+ if (out) return out;
3003
3391
  }
3004
3392
 
3005
3393
  for (const x in node) {
@@ -3013,8 +3401,8 @@ const objectHack = node => {
3013
3401
  };
3014
3402
 
3015
3403
  const generateFunc = (scope, decl) => {
3016
- if (decl.async) return todo('async functions are not supported');
3017
- if (decl.generator) return todo('generator functions are not supported');
3404
+ if (decl.async) return todo(scope, 'async functions are not supported');
3405
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3018
3406
 
3019
3407
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3020
3408
  const params = decl.params ?? [];
@@ -3030,6 +3418,11 @@ const generateFunc = (scope, decl) => {
3030
3418
  name
3031
3419
  };
3032
3420
 
3421
+ if (typedInput && decl.returnType) {
3422
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3423
+ innerScope.returns = [ valtypeBinary ];
3424
+ }
3425
+
3033
3426
  for (let i = 0; i < params.length; i++) {
3034
3427
  allocVar(innerScope, params[i].name, false);
3035
3428
 
@@ -3092,16 +3485,6 @@ const generateCode = (scope, decl) => {
3092
3485
  };
3093
3486
 
3094
3487
  const internalConstrs = {
3095
- Boolean: {
3096
- generate: (scope, decl) => {
3097
- if (decl.arguments.length === 0) return number(0);
3098
-
3099
- // should generate/run all args
3100
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3101
- },
3102
- type: TYPES.boolean
3103
- },
3104
-
3105
3488
  Array: {
3106
3489
  generate: (scope, decl, global, name) => {
3107
3490
  // new Array(i0, i1, ...)
@@ -3119,7 +3502,7 @@ const internalConstrs = {
3119
3502
 
3120
3503
  // todo: check in wasm instead of here
3121
3504
  const literalValue = arg.value ?? 0;
3122
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3505
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3123
3506
 
3124
3507
  return [
3125
3508
  ...number(0, Valtype.i32),
@@ -3130,7 +3513,8 @@ const internalConstrs = {
3130
3513
  ...number(pointer)
3131
3514
  ];
3132
3515
  },
3133
- type: TYPES._array
3516
+ type: TYPES._array,
3517
+ length: 1
3134
3518
  },
3135
3519
 
3136
3520
  __Array_of: {
@@ -3142,7 +3526,131 @@ const internalConstrs = {
3142
3526
  }, global, name);
3143
3527
  },
3144
3528
  type: TYPES._array,
3529
+ notConstr: true,
3530
+ length: 0
3531
+ },
3532
+
3533
+ __Porffor_fastOr: {
3534
+ generate: (scope, decl) => {
3535
+ const out = [];
3536
+
3537
+ for (let i = 0; i < decl.arguments.length; i++) {
3538
+ out.push(
3539
+ ...generate(scope, decl.arguments[i]),
3540
+ Opcodes.i32_to_u,
3541
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3542
+ );
3543
+ }
3544
+
3545
+ out.push(Opcodes.i32_from_u);
3546
+
3547
+ return out;
3548
+ },
3549
+ type: TYPES.boolean,
3550
+ notConstr: true
3551
+ },
3552
+
3553
+ __Porffor_fastAnd: {
3554
+ generate: (scope, decl) => {
3555
+ const out = [];
3556
+
3557
+ for (let i = 0; i < decl.arguments.length; i++) {
3558
+ out.push(
3559
+ ...generate(scope, decl.arguments[i]),
3560
+ Opcodes.i32_to_u,
3561
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3562
+ );
3563
+ }
3564
+
3565
+ out.push(Opcodes.i32_from_u);
3566
+
3567
+ return out;
3568
+ },
3569
+ type: TYPES.boolean,
3145
3570
  notConstr: true
3571
+ },
3572
+
3573
+ Boolean: {
3574
+ generate: (scope, decl) => {
3575
+ // todo: boolean object when used as constructor
3576
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3577
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3578
+ },
3579
+ type: TYPES.boolean,
3580
+ length: 1
3581
+ },
3582
+
3583
+ __Math_max: {
3584
+ generate: (scope, decl) => {
3585
+ const out = [
3586
+ ...number(-Infinity)
3587
+ ];
3588
+
3589
+ for (let i = 0; i < decl.arguments.length; i++) {
3590
+ out.push(
3591
+ ...generate(scope, decl.arguments[i]),
3592
+ [ Opcodes.f64_max ]
3593
+ );
3594
+ }
3595
+
3596
+ return out;
3597
+ },
3598
+ type: TYPES.number,
3599
+ notConstr: true,
3600
+ length: 2
3601
+ },
3602
+
3603
+ __Math_min: {
3604
+ generate: (scope, decl) => {
3605
+ const out = [
3606
+ ...number(Infinity)
3607
+ ];
3608
+
3609
+ for (let i = 0; i < decl.arguments.length; i++) {
3610
+ out.push(
3611
+ ...generate(scope, decl.arguments[i]),
3612
+ [ Opcodes.f64_min ]
3613
+ );
3614
+ }
3615
+
3616
+ return out;
3617
+ },
3618
+ type: TYPES.number,
3619
+ notConstr: true,
3620
+ length: 2
3621
+ },
3622
+
3623
+ __console_log: {
3624
+ generate: (scope, decl) => {
3625
+ const out = [];
3626
+
3627
+ for (let i = 0; i < decl.arguments.length; i++) {
3628
+ out.push(
3629
+ ...generateCall(scope, {
3630
+ callee: {
3631
+ type: 'Identifier',
3632
+ name: '__Porffor_print'
3633
+ },
3634
+ arguments: [ decl.arguments[i] ]
3635
+ }),
3636
+
3637
+ // print space
3638
+ ...number(32),
3639
+ [ Opcodes.call, importedFuncs.printChar ]
3640
+ );
3641
+ }
3642
+
3643
+ // print newline
3644
+ out.push(
3645
+ ...number(10),
3646
+ [ Opcodes.call, importedFuncs.printChar ]
3647
+ );
3648
+
3649
+ return out;
3650
+ },
3651
+ type: TYPES.undefined,
3652
+ notConstr: true,
3653
+ length: 0
3146
3654
  }
3147
3655
  };
3148
3656
 
@@ -3171,20 +3679,23 @@ export default program => {
3171
3679
  funcs = [];
3172
3680
  funcIndex = {};
3173
3681
  depth = [];
3174
- arrays = new Map();
3175
3682
  pages = new Map();
3176
3683
  data = [];
3177
3684
  currentFuncIndex = importedFuncs.length;
3178
3685
 
3179
3686
  globalThis.valtype = 'f64';
3180
3687
 
3181
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3688
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3182
3689
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3183
3690
 
3184
3691
  globalThis.valtypeBinary = Valtype[valtype];
3185
3692
 
3186
3693
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3187
3694
 
3695
+ globalThis.pageSize = PageSize;
3696
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3697
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3698
+
3188
3699
  // set generic opcodes for current valtype
3189
3700
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3190
3701
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3193,10 +3704,10 @@ export default program => {
3193
3704
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3194
3705
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3195
3706
 
3196
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3197
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3198
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3199
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3707
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3708
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3709
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3710
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3200
3711
 
3201
3712
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3202
3713
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3209,10 +3720,6 @@ export default program => {
3209
3720
 
3210
3721
  program.id = { name: 'main' };
3211
3722
 
3212
- globalThis.pageSize = PageSize;
3213
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3214
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3215
-
3216
3723
  const scope = {
3217
3724
  locals: {},
3218
3725
  localInd: 0
@@ -3223,7 +3730,7 @@ export default program => {
3223
3730
  body: program.body
3224
3731
  };
3225
3732
 
3226
- if (Prefs.astLog) console.log(program.body.body);
3733
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3227
3734
 
3228
3735
  generateFunc(scope, program);
3229
3736
 
@@ -3240,7 +3747,11 @@ export default program => {
3240
3747
  }
3241
3748
 
3242
3749
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3243
- main.returns = [];
3750
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3751
+ main.wasm.splice(main.wasm.length - 1, 1);
3752
+ } else {
3753
+ main.returns = [];
3754
+ }
3244
3755
  }
3245
3756
 
3246
3757
  if (lastInst[0] === Opcodes.call) {