porffor 0.2.0-6aff0fa → 0.2.0-6bc63ef

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 (54) 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 +216 -0
  6. package/compiler/2c.js +2 -53
  7. package/compiler/{sections.js → assemble.js} +60 -14
  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 +18 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +2067 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/function.ts +5 -0
  17. package/compiler/builtins/int.ts +145 -0
  18. package/compiler/builtins/number.ts +529 -0
  19. package/compiler/builtins/object.ts +4 -0
  20. package/compiler/builtins/porffor.d.ts +44 -7
  21. package/compiler/builtins/set.ts +187 -0
  22. package/compiler/builtins/string.ts +1080 -0
  23. package/compiler/builtins.js +400 -120
  24. package/compiler/{codeGen.js → codegen.js} +850 -402
  25. package/compiler/decompile.js +2 -3
  26. package/compiler/embedding.js +22 -22
  27. package/compiler/encoding.js +94 -10
  28. package/compiler/expression.js +1 -1
  29. package/compiler/generated_builtins.js +1613 -3
  30. package/compiler/index.js +16 -16
  31. package/compiler/log.js +2 -2
  32. package/compiler/opt.js +28 -27
  33. package/compiler/parse.js +36 -30
  34. package/compiler/precompile.js +37 -46
  35. package/compiler/prefs.js +7 -6
  36. package/compiler/prototype.js +20 -36
  37. package/compiler/types.js +38 -0
  38. package/compiler/wasmSpec.js +14 -1
  39. package/compiler/wrap.js +79 -69
  40. package/package.json +9 -5
  41. package/porf +2 -0
  42. package/rhemyn/compile.js +44 -26
  43. package/rhemyn/parse.js +322 -320
  44. package/rhemyn/test/parse.js +58 -58
  45. package/runner/compare.js +33 -34
  46. package/runner/debug.js +117 -0
  47. package/runner/index.js +69 -12
  48. package/runner/profiler.js +22 -30
  49. package/runner/repl.js +40 -13
  50. package/runner/sizes.js +37 -37
  51. package/runner/version.js +3 -3
  52. package/runner/info.js +0 -89
  53. package/runner/transform.js +0 -15
  54. package/util/enum.js +0 -20
@@ -1,12 +1,13 @@
1
- import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
- import { operatorOpcode } from "./expression.js";
4
- import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
- import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
- import { log } from "./log.js";
8
- import parse from "./parse.js";
9
- import * as Rhemyn from "../rhemyn/compile.js";
1
+ import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
3
+ import { operatorOpcode } from './expression.js';
4
+ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
+ import { PrototypeFuncs } from './prototype.js';
6
+ import { number } from './embedding.js';
7
+ import { TYPES, TYPE_NAMES } from './types.js';
8
+ import * as Rhemyn from '../rhemyn/compile.js';
9
+ import parse from './parse.js';
10
+ import { log } from './log.js';
10
11
  import Prefs from './prefs.js';
11
12
 
12
13
  let globals = {};
@@ -18,44 +19,32 @@ let funcIndex = {};
18
19
  let currentFuncIndex = importedFuncs.length;
19
20
  let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
20
21
 
21
- const debug = str => {
22
- const code = [];
23
-
24
- const logChar = n => {
25
- code.push(...number(n));
26
-
27
- code.push(Opcodes.call);
28
- code.push(...unsignedLEB128(0));
29
- };
30
-
31
- for (let i = 0; i < str.length; i++) {
32
- logChar(str.charCodeAt(i));
22
+ class TodoError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = 'TodoError';
33
26
  }
27
+ }
28
+ const todo = (scope, msg, expectsValue = undefined) => {
29
+ switch (Prefs.todoTime ?? 'runtime') {
30
+ case 'compile':
31
+ throw new TodoError(msg);
34
32
 
35
- logChar('\n'.charCodeAt(0));
36
-
37
- return code;
38
- };
33
+ case 'runtime':
34
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
39
35
 
40
- const todo = msg => {
41
- class TodoError extends Error {
42
- constructor(message) {
43
- super(message);
44
- this.name = 'TodoError';
45
- }
36
+ // return [
37
+ // ...debug(`todo! ${msg}`),
38
+ // [ Opcodes.unreachable ]
39
+ // ];
46
40
  }
47
-
48
- throw new TodoError(`todo: ${msg}`);
49
-
50
- const code = [];
51
-
52
- code.push(...debug(`todo! ` + msg));
53
- code.push(Opcodes.unreachable);
54
-
55
- return code;
56
41
  };
57
42
 
58
43
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
+ const hasFuncWithName = name => {
45
+ const func = funcs.find(x => x.name === name);
46
+ return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
+ };
59
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
60
49
  switch (decl.type) {
61
50
  case 'BinaryExpression':
@@ -105,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
94
  return generateUnary(scope, decl);
106
95
 
107
96
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
97
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
98
 
110
99
  case 'IfStatement':
111
100
  return generateIf(scope, decl);
@@ -116,6 +105,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
105
  case 'WhileStatement':
117
106
  return generateWhile(scope, decl);
118
107
 
108
+ case 'DoWhileStatement':
109
+ return generateDoWhile(scope, decl);
110
+
119
111
  case 'ForOfStatement':
120
112
  return generateForOf(scope, decl);
121
113
 
@@ -125,6 +117,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
117
  case 'ContinueStatement':
126
118
  return generateContinue(scope, decl);
127
119
 
120
+ case 'LabeledStatement':
121
+ return generateLabel(scope, decl);
122
+
128
123
  case 'EmptyStatement':
129
124
  return generateEmpty(scope, decl);
130
125
 
@@ -138,7 +133,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
133
  return generateTry(scope, decl);
139
134
 
140
135
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
136
+ // todo: hook into terminal debugger
142
137
  return [];
143
138
 
144
139
  case 'ArrayExpression':
@@ -152,10 +147,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
147
  const funcsBefore = funcs.length;
153
148
  generate(scope, decl.declaration);
154
149
 
155
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
156
-
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
150
+ if (funcsBefore !== funcs.length) {
151
+ // new func added
152
+ const newFunc = funcs[funcs.length - 1];
153
+ newFunc.export = true;
154
+ }
159
155
 
160
156
  return [];
161
157
 
@@ -186,7 +182,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
186
182
  }
187
183
 
188
184
  let inst = Opcodes[asm[0].replace('.', '_')];
189
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
185
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
186
 
191
187
  if (!Array.isArray(inst)) inst = [ inst ];
192
188
  const immediates = asm.slice(1).map(x => {
@@ -195,40 +191,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
195
191
  return int;
196
192
  });
197
193
 
198
- out.push([ ...inst, ...immediates ]);
194
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
199
195
  }
200
196
 
201
197
  return out;
202
198
  },
203
199
 
204
200
  __Porffor_bs: str => [
205
- ...makeString(scope, str, undefined, undefined, true),
201
+ ...makeString(scope, str, global, name, true),
206
202
 
207
- ...number(TYPES._bytestring, Valtype.i32),
208
- setLastType(scope)
203
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
204
+ ...number(TYPES.bytestring, Valtype.i32),
205
+ ...setLastType(scope)
206
+ ])
209
207
  ],
210
208
  __Porffor_s: str => [
211
- ...makeString(scope, str, undefined, undefined, false),
209
+ ...makeString(scope, str, global, name, false),
212
210
 
213
- ...number(TYPES.string, Valtype.i32),
214
- setLastType(scope)
211
+ ...(name ? setType(scope, name, TYPES.string) : [
212
+ ...number(TYPES.string, Valtype.i32),
213
+ ...setLastType(scope)
214
+ ])
215
215
  ],
216
216
  };
217
217
 
218
- const name = decl.tag.name;
218
+ const func = decl.tag.name;
219
219
  // hack for inline asm
220
- if (!funcs[name]) return todo('tagged template expressions not implemented');
220
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
221
 
222
222
  const { quasis, expressions } = decl.quasi;
223
223
  let str = quasis[0].value.raw;
224
224
 
225
225
  for (let i = 0; i < expressions.length; i++) {
226
226
  const e = expressions[i];
227
- str += lookupName(scope, e.name)[0].idx;
227
+ if (!e.name) {
228
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
229
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
230
+ } else todo(scope, 'unsupported expression in intrinsic');
231
+ } else str += lookupName(scope, e.name)[0].idx;
232
+
228
233
  str += quasis[i + 1].value.raw;
229
234
  }
230
235
 
231
- return funcs[name](str);
236
+ return funcs[func](str);
232
237
  }
233
238
 
234
239
  default:
@@ -238,7 +243,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
238
243
  return [];
239
244
  }
240
245
 
241
- return todo(`no generation for ${decl.type}!`);
246
+ return todo(scope, `no generation for ${decl.type}!`);
242
247
  }
243
248
  };
244
249
 
@@ -266,7 +271,7 @@ const lookupName = (scope, _name) => {
266
271
  return [ undefined, undefined ];
267
272
  };
268
273
 
269
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
274
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
270
275
  ...generateThrow(scope, {
271
276
  argument: {
272
277
  type: 'NewExpression',
@@ -293,7 +298,7 @@ const generateIdent = (scope, decl) => {
293
298
 
294
299
  let wasm = builtinVars[name];
295
300
  if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
296
- return wasm;
301
+ return wasm.slice();
297
302
  }
298
303
 
299
304
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -301,6 +306,11 @@ const generateIdent = (scope, decl) => {
301
306
  return number(1);
302
307
  }
303
308
 
309
+ if (isExistingProtoFunc(name)) {
310
+ // todo: return an actual something
311
+ return number(1);
312
+ }
313
+
304
314
  if (local?.idx === undefined) {
305
315
  // no local var with name
306
316
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -331,14 +341,18 @@ const generateReturn = (scope, decl) => {
331
341
  // just bare "return"
332
342
  return [
333
343
  ...number(UNDEFINED), // "undefined" if func returns
334
- ...number(TYPES.undefined, Valtype.i32), // type undefined
344
+ ...(scope.returnType != null ? [] : [
345
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
346
+ ]),
335
347
  [ Opcodes.return ]
336
348
  ];
337
349
  }
338
350
 
339
351
  return [
340
352
  ...generate(scope, decl.argument),
341
- ...getNodeType(scope, decl.argument),
353
+ ...(scope.returnType != null ? [] : [
354
+ ...getNodeType(scope, decl.argument)
355
+ ]),
342
356
  [ Opcodes.return ]
343
357
  ];
344
358
  };
@@ -352,7 +366,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
352
366
  return idx;
353
367
  };
354
368
 
355
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
369
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
370
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
356
371
 
357
372
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
358
373
  const checks = {
@@ -361,7 +376,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
361
376
  '??': nullish
362
377
  };
363
378
 
364
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
379
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
365
380
 
366
381
  // generic structure for {a} OP {b}
367
382
  // -->
@@ -369,8 +384,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
369
384
 
370
385
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
371
386
  // (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]);
387
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
388
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
374
389
 
375
390
  const canInt = leftIsInt && rightIsInt;
376
391
 
@@ -387,12 +402,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
387
402
  ...right,
388
403
  // note type
389
404
  ...rightType,
390
- setLastType(scope),
405
+ ...setLastType(scope),
391
406
  [ Opcodes.else ],
392
407
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
393
408
  // note type
394
409
  ...leftType,
395
- setLastType(scope),
410
+ ...setLastType(scope),
396
411
  [ Opcodes.end ],
397
412
  Opcodes.i32_from
398
413
  ];
@@ -406,17 +421,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
406
421
  ...right,
407
422
  // note type
408
423
  ...rightType,
409
- setLastType(scope),
424
+ ...setLastType(scope),
410
425
  [ Opcodes.else ],
411
426
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
412
427
  // note type
413
428
  ...leftType,
414
- setLastType(scope),
429
+ ...setLastType(scope),
415
430
  [ Opcodes.end ]
416
431
  ];
417
432
  };
418
433
 
419
- const concatStrings = (scope, left, right, global, name, assign) => {
434
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
420
435
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
421
436
  // todo: convert left and right to strings if not
422
437
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -426,8 +441,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
426
441
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
427
442
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
428
443
 
429
- if (assign) {
430
- const pointer = arrays.get(name ?? '$undeclared');
444
+ if (assign && Prefs.aotPointerOpt) {
445
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
431
446
 
432
447
  return [
433
448
  // setup right
@@ -452,11 +467,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
452
467
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
453
468
 
454
469
  // copy right
455
- // dst = out pointer + length size + current length * i16 size
470
+ // dst = out pointer + length size + current length * sizeof valtype
456
471
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
457
472
 
458
473
  [ Opcodes.local_get, leftLength ],
459
- ...number(ValtypeSize.i16, Valtype.i32),
474
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
460
475
  [ Opcodes.i32_mul ],
461
476
  [ Opcodes.i32_add ],
462
477
 
@@ -465,9 +480,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
465
480
  ...number(ValtypeSize.i32, Valtype.i32),
466
481
  [ Opcodes.i32_add ],
467
482
 
468
- // size = right length * i16 size
483
+ // size = right length * sizeof valtype
469
484
  [ Opcodes.local_get, rightLength ],
470
- ...number(ValtypeSize.i16, Valtype.i32),
485
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
471
486
  [ Opcodes.i32_mul ],
472
487
 
473
488
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -525,11 +540,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
525
540
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
526
541
 
527
542
  // copy right
528
- // dst = out pointer + length size + left length * i16 size
543
+ // dst = out pointer + length size + left length * sizeof valtype
529
544
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
530
545
 
531
546
  [ Opcodes.local_get, leftLength ],
532
- ...number(ValtypeSize.i16, Valtype.i32),
547
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
533
548
  [ Opcodes.i32_mul ],
534
549
  [ Opcodes.i32_add ],
535
550
 
@@ -538,9 +553,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
538
553
  ...number(ValtypeSize.i32, Valtype.i32),
539
554
  [ Opcodes.i32_add ],
540
555
 
541
- // size = right length * i16 size
556
+ // size = right length * sizeof valtype
542
557
  [ Opcodes.local_get, rightLength ],
543
- ...number(ValtypeSize.i16, Valtype.i32),
558
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
544
559
  [ Opcodes.i32_mul ],
545
560
 
546
561
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -550,7 +565,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
550
565
  ];
551
566
  };
552
567
 
553
- const compareStrings = (scope, left, right) => {
568
+ const compareStrings = (scope, left, right, bytestrings = false) => {
554
569
  // todo: this should be rewritten into a func
555
570
  // todo: convert left and right to strings if not
556
571
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -559,7 +574,6 @@ const compareStrings = (scope, left, right) => {
559
574
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
560
575
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
561
576
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
562
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
563
577
 
564
578
  const index = localTmp(scope, 'compare_index', Valtype.i32);
565
579
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -587,7 +601,6 @@ const compareStrings = (scope, left, right) => {
587
601
 
588
602
  [ Opcodes.local_get, rightPointer ],
589
603
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
590
- [ Opcodes.local_tee, rightLength ],
591
604
 
592
605
  // fast path: check leftLength != rightLength
593
606
  [ Opcodes.i32_ne ],
@@ -602,11 +615,13 @@ const compareStrings = (scope, left, right) => {
602
615
  ...number(0, Valtype.i32),
603
616
  [ Opcodes.local_set, index ],
604
617
 
605
- // setup index end as length * sizeof i16 (2)
618
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
606
619
  // we do this instead of having to do mul/div each iter for perf™
607
620
  [ Opcodes.local_get, leftLength ],
608
- ...number(ValtypeSize.i16, Valtype.i32),
609
- [ Opcodes.i32_mul ],
621
+ ...(bytestrings ? [] : [
622
+ ...number(ValtypeSize.i16, Valtype.i32),
623
+ [ Opcodes.i32_mul ],
624
+ ]),
610
625
  [ Opcodes.local_set, indexEnd ],
611
626
 
612
627
  // iterate over each char and check if eq
@@ -616,13 +631,17 @@ const compareStrings = (scope, left, right) => {
616
631
  [ Opcodes.local_get, index ],
617
632
  [ Opcodes.local_get, leftPointer ],
618
633
  [ Opcodes.i32_add ],
619
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
634
+ bytestrings ?
635
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
636
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
620
637
 
621
638
  // fetch right
622
639
  [ Opcodes.local_get, index ],
623
640
  [ Opcodes.local_get, rightPointer ],
624
641
  [ Opcodes.i32_add ],
625
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
642
+ bytestrings ?
643
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
644
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
626
645
 
627
646
  // not equal, "return" false
628
647
  [ Opcodes.i32_ne ],
@@ -631,13 +650,13 @@ const compareStrings = (scope, left, right) => {
631
650
  [ Opcodes.br, 2 ],
632
651
  [ Opcodes.end ],
633
652
 
634
- // index += sizeof i16 (2)
653
+ // index += sizeof valtype (1 for bytestring, 2 for string)
635
654
  [ Opcodes.local_get, index ],
636
- ...number(ValtypeSize.i16, Valtype.i32),
655
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
637
656
  [ Opcodes.i32_add ],
638
657
  [ Opcodes.local_tee, index ],
639
658
 
640
- // if index != index end (length * sizeof 16), loop
659
+ // if index != index end (length * sizeof valtype), loop
641
660
  [ Opcodes.local_get, indexEnd ],
642
661
  [ Opcodes.i32_ne ],
643
662
  [ Opcodes.br_if, 0 ],
@@ -658,13 +677,14 @@ const compareStrings = (scope, left, right) => {
658
677
  };
659
678
 
660
679
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
661
- if (isIntOp(wasm[wasm.length - 1])) return [
680
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
662
681
  ...wasm,
663
682
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
664
683
  ];
684
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
665
685
 
666
686
  const useTmp = knownType(scope, type) == null;
667
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
687
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
668
688
 
669
689
  const def = [
670
690
  // if value != 0
@@ -684,7 +704,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
684
704
 
685
705
  ...typeSwitch(scope, type, {
686
706
  // [TYPES.number]: def,
687
- [TYPES._array]: [
707
+ [TYPES.array]: [
688
708
  // arrays are always truthy
689
709
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
690
710
  ],
@@ -700,7 +720,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
720
  [ Opcodes.i32_eqz ], */
701
721
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
702
722
  ],
703
- [TYPES._bytestring]: [ // duplicate of string
723
+ [TYPES.bytestring]: [ // duplicate of string
704
724
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
705
725
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
706
726
 
@@ -716,14 +736,14 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
716
736
 
717
737
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
718
738
  const useTmp = knownType(scope, type) == null;
719
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
739
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
720
740
 
721
741
  return [
722
742
  ...wasm,
723
743
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
724
744
 
725
745
  ...typeSwitch(scope, type, {
726
- [TYPES._array]: [
746
+ [TYPES.array]: [
727
747
  // arrays are always truthy
728
748
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
729
749
  ],
@@ -738,7 +758,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
738
758
  [ Opcodes.i32_eqz ],
739
759
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
740
760
  ],
741
- [TYPES._bytestring]: [ // duplicate of string
761
+ [TYPES.bytestring]: [ // duplicate of string
742
762
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
743
763
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
744
764
 
@@ -762,7 +782,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
762
782
 
763
783
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
764
784
  const useTmp = knownType(scope, type) == null;
765
- const tmp = useTmp && localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
785
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
766
786
 
767
787
  return [
768
788
  ...wasm,
@@ -815,31 +835,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
815
835
 
816
836
  // if strict (in)equal check types match
817
837
  if (strictOp) {
818
- // startOut.push(
819
- // ...leftType,
820
- // ...rightType,
821
- // [ Opcodes.i32_eq ]
822
- // );
823
-
824
- // endOut.push(
825
- // [ Opcodes.i32_and ]
826
- // );
827
-
828
- // startOut.push(
829
- // [ Opcodes.block, Valtype.i32 ],
830
- // ...leftType,
831
- // ...rightType,
832
- // [ Opcodes.i32_ne ],
833
- // [ Opcodes.if, Blocktype.void ],
834
- // ...number(op === '===' ? 0 : 1, Valtype.i32),
835
- // [ Opcodes.br, 1 ],
836
- // [ Opcodes.end ]
837
- // );
838
-
839
- // endOut.push(
840
- // [ Opcodes.end ]
841
- // );
842
-
843
838
  endOut.push(
844
839
  ...leftType,
845
840
  ...rightType,
@@ -856,11 +851,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
856
851
  // todo: if equality op and an operand is undefined, return false
857
852
  // todo: niche null hell with 0
858
853
 
859
- // todo: this should be dynamic but for now only static
860
854
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
861
855
  if (op === '+') {
856
+ // todo: this should be dynamic too but for now only static
862
857
  // string concat (a + b)
863
- return concatStrings(scope, left, right, _global, _name, assign);
858
+ return concatStrings(scope, left, right, _global, _name, assign, false);
864
859
  }
865
860
 
866
861
  // not an equality op, NaN
@@ -883,6 +878,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
883
878
  }
884
879
  }
885
880
 
881
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
882
+ if (op === '+') {
883
+ // todo: this should be dynamic too but for now only static
884
+ // string concat (a + b)
885
+ return concatStrings(scope, left, right, _global, _name, assign, true);
886
+ }
887
+
888
+ // not an equality op, NaN
889
+ if (!eqOp) return number(NaN);
890
+
891
+ // else leave bool ops
892
+ // todo: convert string to number if string and number/bool
893
+ // todo: string (>|>=|<|<=) string
894
+
895
+ // string comparison
896
+ if (op === '===' || op === '==') {
897
+ return compareStrings(scope, left, right, true);
898
+ }
899
+
900
+ if (op === '!==' || op === '!=') {
901
+ return [
902
+ ...compareStrings(scope, left, right, true),
903
+ [ Opcodes.i32_eqz ]
904
+ ];
905
+ }
906
+ }
907
+
886
908
  let ops = operatorOpcode[valtype][op];
887
909
 
888
910
  // some complex ops are implemented as builtin funcs
@@ -898,23 +920,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
898
920
  ]);
899
921
  }
900
922
 
901
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
923
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
902
924
 
903
925
  if (!Array.isArray(ops)) ops = [ ops ];
904
926
  ops = [ ops ];
905
927
 
906
928
  let tmpLeft, tmpRight;
907
929
  // 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
- }
930
+ // todo: intelligent partial skip later
931
+ // if neither known are string, stop this madness
932
+ // we already do known checks earlier, so don't need to recheck
914
933
 
934
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
915
935
  tmpLeft = localTmp(scope, '__tmpop_left');
916
936
  tmpRight = localTmp(scope, '__tmpop_right');
917
937
 
938
+ // returns false for one string, one not - but more ops/slower
939
+ // ops.unshift(...stringOnly([
940
+ // // if left is string
941
+ // ...leftType,
942
+ // ...number(TYPES.string, Valtype.i32),
943
+ // [ Opcodes.i32_eq ],
944
+
945
+ // // if right is string
946
+ // ...rightType,
947
+ // ...number(TYPES.string, Valtype.i32),
948
+ // [ Opcodes.i32_eq ],
949
+
950
+ // // if either are true
951
+ // [ Opcodes.i32_or ],
952
+ // [ Opcodes.if, Blocktype.void ],
953
+
954
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
955
+ // // if left is not string
956
+ // ...leftType,
957
+ // ...number(TYPES.string, Valtype.i32),
958
+ // [ Opcodes.i32_ne ],
959
+
960
+ // // if right is not string
961
+ // ...rightType,
962
+ // ...number(TYPES.string, Valtype.i32),
963
+ // [ Opcodes.i32_ne ],
964
+
965
+ // // if either are true
966
+ // [ Opcodes.i32_or ],
967
+ // [ Opcodes.if, Blocktype.void ],
968
+ // ...number(0, Valtype.i32),
969
+ // [ Opcodes.br, 2 ],
970
+ // [ Opcodes.end ],
971
+
972
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
973
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
974
+ // [ Opcodes.br, 1 ],
975
+ // [ Opcodes.end ],
976
+ // ]));
977
+
978
+ // does not handle one string, one not (such cases go past)
918
979
  ops.unshift(...stringOnly([
919
980
  // if left is string
920
981
  ...leftType,
@@ -926,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
926
987
  ...number(TYPES.string, Valtype.i32),
927
988
  [ Opcodes.i32_eq ],
928
989
 
929
- // if either are true
930
- [ Opcodes.i32_or ],
990
+ // if both are true
991
+ [ Opcodes.i32_and ],
931
992
  [ Opcodes.if, Blocktype.void ],
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
994
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
995
+ [ Opcodes.br, 1 ],
996
+ [ Opcodes.end ],
932
997
 
933
- // todo: convert non-strings to strings, for now fail immediately if one is not
934
- // if left is not string
998
+ // if left is bytestring
935
999
  ...leftType,
936
- ...number(TYPES.string, Valtype.i32),
937
- [ Opcodes.i32_ne ],
1000
+ ...number(TYPES.bytestring, Valtype.i32),
1001
+ [ Opcodes.i32_eq ],
938
1002
 
939
- // if right is not string
1003
+ // if right is bytestring
940
1004
  ...rightType,
941
- ...number(TYPES.string, Valtype.i32),
942
- [ Opcodes.i32_ne ],
1005
+ ...number(TYPES.bytestring, Valtype.i32),
1006
+ [ Opcodes.i32_eq ],
943
1007
 
944
- // if either are true
945
- [ Opcodes.i32_or ],
1008
+ // if both are true
1009
+ [ Opcodes.i32_and ],
946
1010
  [ 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 ] ]),
1011
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
953
1012
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
954
1013
  [ Opcodes.br, 1 ],
955
1014
  [ Opcodes.end ],
@@ -961,7 +1020,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
961
1020
  // endOut.push(stringOnly([ Opcodes.end ]));
962
1021
  endOut.unshift(stringOnly([ Opcodes.end ]));
963
1022
  // }
964
- })();
1023
+ }
965
1024
 
966
1025
  return finalize([
967
1026
  ...left,
@@ -982,7 +1041,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
982
1041
 
983
1042
  const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
984
1043
  return func({ name, params, locals, returns, localInd }, {
985
- TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1044
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
986
1045
  builtin: name => {
987
1046
  let idx = funcIndex[name] ?? importedFuncs[name];
988
1047
  if (idx === undefined && builtinFuncs[name]) {
@@ -1037,7 +1096,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1037
1096
  params,
1038
1097
  locals,
1039
1098
  returns,
1040
- returnType: TYPES[returnType ?? 'number'],
1099
+ returnType: returnType ?? TYPES.number,
1041
1100
  wasm,
1042
1101
  internal: true,
1043
1102
  index: currentFuncIndex++
@@ -1060,6 +1119,7 @@ const generateLogicExp = (scope, decl) => {
1060
1119
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1061
1120
  };
1062
1121
 
1122
+ // potential future ideas for nan boxing (unused):
1063
1123
  // T = JS type, V = value/pointer
1064
1124
  // 0bTTT
1065
1125
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1073,7 +1133,6 @@ const generateLogicExp = (scope, decl) => {
1073
1133
  // js type: 4 bits
1074
1134
  // internal type: ? bits
1075
1135
  // pointer: 32 bits
1076
-
1077
1136
  // generic
1078
1137
  // 1 23 4 5
1079
1138
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1083,40 +1142,18 @@ const generateLogicExp = (scope, decl) => {
1083
1142
  // 4: internal type
1084
1143
  // 5: pointer
1085
1144
 
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'
1145
+ const isExistingProtoFunc = name => {
1146
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1147
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1148
+
1149
+ return false;
1115
1150
  };
1116
1151
 
1117
1152
  const getType = (scope, _name) => {
1118
1153
  const name = mapName(_name);
1119
1154
 
1155
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1156
+
1120
1157
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1121
1158
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1122
1159
 
@@ -1124,11 +1161,10 @@ const getType = (scope, _name) => {
1124
1161
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1125
1162
 
1126
1163
  let type = TYPES.undefined;
1127
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1164
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1128
1165
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1129
1166
 
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;
1167
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1132
1168
 
1133
1169
  return number(type, Valtype.i32);
1134
1170
  };
@@ -1151,23 +1187,24 @@ const setType = (scope, _name, type) => {
1151
1187
  ];
1152
1188
 
1153
1189
  // throw new Error('could not find var');
1190
+ return [];
1154
1191
  };
1155
1192
 
1156
1193
  const getLastType = scope => {
1157
1194
  scope.gotLastType = true;
1158
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1195
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1159
1196
  };
1160
1197
 
1161
1198
  const setLastType = scope => {
1162
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1199
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1163
1200
  };
1164
1201
 
1165
1202
  const getNodeType = (scope, node) => {
1166
- const inner = () => {
1203
+ const ret = (() => {
1167
1204
  if (node.type === 'Literal') {
1168
- if (node.regex) return TYPES._regexp;
1205
+ if (node.regex) return TYPES.regexp;
1169
1206
 
1170
- if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1207
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1171
1208
 
1172
1209
  return TYPES[typeof node.value];
1173
1210
  }
@@ -1184,21 +1221,32 @@ const getNodeType = (scope, node) => {
1184
1221
  const name = node.callee.name;
1185
1222
  if (!name) {
1186
1223
  // iife
1187
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1224
+ if (scope.locals['#last_type']) return getLastType(scope);
1188
1225
 
1189
1226
  // presume
1190
1227
  // todo: warn here?
1191
1228
  return TYPES.number;
1192
1229
  }
1193
1230
 
1231
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1232
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1233
+ if (scope.locals['#last_type']) return getLastType(scope);
1234
+
1235
+ // presume
1236
+ // todo: warn here?
1237
+ return TYPES.number;
1238
+ }
1239
+
1240
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1241
+ }
1242
+
1194
1243
  const func = funcs.find(x => x.name === name);
1195
1244
 
1196
1245
  if (func) {
1197
- // console.log(scope, func, func.returnType);
1198
1246
  if (func.returnType) return func.returnType;
1199
1247
  }
1200
1248
 
1201
- if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1249
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1202
1250
  if (internalConstrs[name]) return internalConstrs[name].type;
1203
1251
 
1204
1252
  // check if this is a prototype function
@@ -1209,7 +1257,7 @@ const getNodeType = (scope, node) => {
1209
1257
  const spl = name.slice(2).split('_');
1210
1258
 
1211
1259
  const func = spl[spl.length - 1];
1212
- const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1260
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1213
1261
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1214
1262
  }
1215
1263
 
@@ -1218,7 +1266,7 @@ const getNodeType = (scope, node) => {
1218
1266
  return TYPES.number;
1219
1267
  }
1220
1268
 
1221
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1222
1270
 
1223
1271
  // presume
1224
1272
  // todo: warn here?
@@ -1261,7 +1309,7 @@ const getNodeType = (scope, node) => {
1261
1309
  }
1262
1310
 
1263
1311
  if (node.type === 'ArrayExpression') {
1264
- return TYPES._array;
1312
+ return TYPES.array;
1265
1313
  }
1266
1314
 
1267
1315
  if (node.type === 'BinaryExpression') {
@@ -1273,6 +1321,7 @@ const getNodeType = (scope, node) => {
1273
1321
 
1274
1322
  // todo: this should be dynamic but for now only static
1275
1323
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1324
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1276
1325
 
1277
1326
  return TYPES.number;
1278
1327
 
@@ -1298,34 +1347,47 @@ const getNodeType = (scope, node) => {
1298
1347
  if (node.operator === '!') return TYPES.boolean;
1299
1348
  if (node.operator === 'void') return TYPES.undefined;
1300
1349
  if (node.operator === 'delete') return TYPES.boolean;
1301
- if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1350
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1302
1351
 
1303
1352
  return TYPES.number;
1304
1353
  }
1305
1354
 
1306
1355
  if (node.type === 'MemberExpression') {
1356
+ // hack: if something.name, string type
1357
+ if (node.property.name === 'name') {
1358
+ if (hasFuncWithName(node.object.name)) {
1359
+ return TYPES.bytestring;
1360
+ } else {
1361
+ return TYPES.undefined;
1362
+ }
1363
+ }
1364
+
1307
1365
  // hack: if something.length, number type
1308
1366
  if (node.property.name === 'length') return TYPES.number;
1309
1367
 
1310
1368
  // ts hack
1311
1369
  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;
1370
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1371
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1313
1372
 
1314
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1373
+ if (scope.locals['#last_type']) return getLastType(scope);
1315
1374
 
1316
1375
  // presume
1317
1376
  return TYPES.number;
1318
1377
  }
1319
1378
 
1320
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1379
+ if (node.type === 'TaggedTemplateExpression') {
1380
+ // hack
1381
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1382
+ }
1383
+
1384
+ if (scope.locals['#last_type']) return getLastType(scope);
1321
1385
 
1322
1386
  // presume
1323
1387
  // todo: warn here?
1324
1388
  return TYPES.number;
1325
- };
1389
+ })();
1326
1390
 
1327
- const ret = inner();
1328
- // console.trace(node, ret);
1329
1391
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1330
1392
  return ret;
1331
1393
  };
@@ -1350,7 +1412,7 @@ const generateLiteral = (scope, decl, global, name) => {
1350
1412
  return makeString(scope, decl.value, global, name);
1351
1413
 
1352
1414
  default:
1353
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1415
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1354
1416
  }
1355
1417
  };
1356
1418
 
@@ -1359,6 +1421,8 @@ const countLeftover = wasm => {
1359
1421
 
1360
1422
  for (let i = 0; i < wasm.length; i++) {
1361
1423
  const inst = wasm[i];
1424
+ if (inst[0] == null) continue;
1425
+
1362
1426
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1363
1427
  if (inst[0] === Opcodes.if) count--;
1364
1428
  if (inst[1] !== Blocktype.void) count++;
@@ -1369,16 +1433,23 @@ const countLeftover = wasm => {
1369
1433
  if (depth === 0)
1370
1434
  if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1371
1435
  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++;
1436
+ 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
1437
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1374
1438
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1375
1439
  else if (inst[0] === Opcodes.return) count = 0;
1376
1440
  else if (inst[0] === Opcodes.call) {
1377
1441
  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;
1442
+ if (inst[1] === -1) {
1443
+ // todo: count for calling self
1444
+ } else if (!func && inst[1] < importedFuncs.length) {
1445
+ count -= importedFuncs[inst[1]].params;
1446
+ count += importedFuncs[inst[1]].returns;
1447
+ } else {
1448
+ if (func) {
1449
+ count -= func.params.length;
1450
+ } else count--;
1451
+ if (func) count += func.returns.length;
1452
+ }
1382
1453
  } else count--;
1383
1454
 
1384
1455
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1455,25 +1526,27 @@ const RTArrayUtil = {
1455
1526
  };
1456
1527
 
1457
1528
  const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1458
- /* const callee = decl.callee;
1459
- const args = decl.arguments;
1460
-
1461
- return [
1462
- ...generate(args),
1463
- ...generate(callee),
1464
- Opcodes.call_indirect,
1465
- ]; */
1466
-
1467
1529
  let name = mapName(decl.callee.name);
1468
1530
  if (isFuncType(decl.callee.type)) { // iife
1469
1531
  const func = generateFunc(scope, decl.callee);
1470
1532
  name = func.name;
1471
1533
  }
1472
1534
 
1473
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1535
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1474
1536
  // literal eval hack
1475
- const code = decl.arguments[0].value;
1476
- const parsed = parse(code, []);
1537
+ const code = decl.arguments[0]?.value ?? '';
1538
+
1539
+ let parsed;
1540
+ try {
1541
+ parsed = parse(code, []);
1542
+ } catch (e) {
1543
+ if (e.name === 'SyntaxError') {
1544
+ // throw syntax errors of evals at runtime instead
1545
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1546
+ }
1547
+
1548
+ throw e;
1549
+ }
1477
1550
 
1478
1551
  const out = generate(scope, {
1479
1552
  type: 'BlockStatement',
@@ -1487,13 +1560,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1487
1560
  const finalStatement = parsed.body[parsed.body.length - 1];
1488
1561
  out.push(
1489
1562
  ...getNodeType(scope, finalStatement),
1490
- setLastType(scope)
1563
+ ...setLastType(scope)
1491
1564
  );
1492
1565
  } else if (countLeftover(out) === 0) {
1493
1566
  out.push(...number(UNDEFINED));
1494
1567
  out.push(
1495
1568
  ...number(TYPES.undefined, Valtype.i32),
1496
- setLastType(scope)
1569
+ ...setLastType(scope)
1497
1570
  );
1498
1571
  }
1499
1572
 
@@ -1515,6 +1588,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1588
 
1516
1589
  target = { ...decl.callee };
1517
1590
  target.name = spl.slice(0, -1).join('_');
1591
+
1592
+ // failed to lookup name, abort
1593
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1518
1594
  }
1519
1595
 
1520
1596
  // literal.func()
@@ -1522,22 +1598,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1522
1598
  // megahack for /regex/.func()
1523
1599
  const funcName = decl.callee.property.name;
1524
1600
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1525
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1601
+ const regex = decl.callee.object.regex.pattern;
1602
+ const rhemynName = `regex_${funcName}_${regex}`;
1526
1603
 
1527
- funcIndex[func.name] = func.index;
1528
- funcs.push(func);
1604
+ if (!funcIndex[rhemynName]) {
1605
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1529
1606
 
1607
+ funcIndex[func.name] = func.index;
1608
+ funcs.push(func);
1609
+ }
1610
+
1611
+ const idx = funcIndex[rhemynName];
1530
1612
  return [
1531
1613
  // make string arg
1532
1614
  ...generate(scope, decl.arguments[0]),
1615
+ Opcodes.i32_to_u,
1616
+ ...getNodeType(scope, decl.arguments[0]),
1533
1617
 
1534
1618
  // call regex func
1535
- Opcodes.i32_to_u,
1536
- [ Opcodes.call, func.index ],
1619
+ [ Opcodes.call, idx ],
1537
1620
  Opcodes.i32_from_u,
1538
1621
 
1539
1622
  ...number(TYPES.boolean, Valtype.i32),
1540
- setLastType(scope)
1623
+ ...setLastType(scope)
1541
1624
  ];
1542
1625
  }
1543
1626
 
@@ -1562,12 +1645,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1562
1645
  // }
1563
1646
 
1564
1647
  if (protoName) {
1648
+ const protoBC = {};
1649
+
1650
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1651
+
1652
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1653
+ for (const x of builtinProtoCands) {
1654
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1655
+ if (type == null) continue;
1656
+
1657
+ protoBC[type] = generateCall(scope, {
1658
+ callee: {
1659
+ type: 'Identifier',
1660
+ name: x
1661
+ },
1662
+ arguments: [ target, ...decl.arguments ],
1663
+ _protoInternalCall: true
1664
+ });
1665
+ }
1666
+ }
1667
+
1565
1668
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1566
1669
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1567
1670
  return acc;
1568
1671
  }, {});
1569
1672
 
1570
- // no prototype function candidates, ignore
1571
1673
  if (Object.keys(protoCands).length > 0) {
1572
1674
  // use local for cached i32 length as commonly used
1573
1675
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1585,7 +1687,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1585
1687
 
1586
1688
  let allOptUnused = true;
1587
1689
  let lengthI32CacheUsed = false;
1588
- const protoBC = {};
1589
1690
  for (const x in protoCands) {
1590
1691
  const protoFunc = protoCands[x];
1591
1692
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1593,13 +1694,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1593
1694
  ...RTArrayUtil.getLength(getPointer),
1594
1695
 
1595
1696
  ...number(TYPES.number, Valtype.i32),
1596
- setLastType(scope)
1697
+ ...setLastType(scope)
1597
1698
  ];
1598
1699
  continue;
1599
1700
  }
1600
1701
 
1601
- // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1602
- // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1603
1702
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1604
1703
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1605
1704
 
@@ -1630,7 +1729,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1630
1729
  ...protoOut,
1631
1730
 
1632
1731
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1633
- setLastType(scope),
1732
+ ...setLastType(scope),
1634
1733
  [ Opcodes.end ]
1635
1734
  ];
1636
1735
  }
@@ -1656,10 +1755,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1656
1755
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1657
1756
  ];
1658
1757
  }
1758
+
1759
+ if (Object.keys(protoBC).length > 0) {
1760
+ return typeSwitch(scope, getNodeType(scope, target), {
1761
+ ...protoBC,
1762
+
1763
+ // TODO: error better
1764
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1765
+ }, valtypeBinary);
1766
+ }
1659
1767
  }
1660
1768
 
1661
1769
  // TODO: only allows callee as literal
1662
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1770
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1663
1771
 
1664
1772
  let idx = funcIndex[name] ?? importedFuncs[name];
1665
1773
  if (idx === undefined && builtinFuncs[name]) {
@@ -1667,22 +1775,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1667
1775
 
1668
1776
  includeBuiltin(scope, name);
1669
1777
  idx = funcIndex[name];
1670
-
1671
- // 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
- }
1686
1778
  }
1687
1779
 
1688
1780
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1695,9 +1787,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1787
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1696
1788
  const wasmOps = {
1697
1789
  // pointer, align, offset
1698
- i32_load8_u: { imms: 2, args: 1 },
1790
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1699
1791
  // pointer, value, align, offset
1700
- i32_store8: { imms: 2, args: 2 },
1792
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1793
+ // pointer, align, offset
1794
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1795
+ // pointer, value, align, offset
1796
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1797
+ // pointer, align, offset
1798
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1799
+ // pointer, value, align, offset
1800
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1801
+
1802
+ // pointer, align, offset
1803
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1804
+ // pointer, value, align, offset
1805
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1806
+
1807
+ // value
1808
+ i32_const: { imms: 1, args: [], returns: 1 },
1701
1809
  };
1702
1810
 
1703
1811
  const opName = name.slice('__Porffor_wasm_'.length);
@@ -1706,28 +1814,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1706
1814
  const op = wasmOps[opName];
1707
1815
 
1708
1816
  const argOut = [];
1709
- for (let i = 0; i < op.args; i++) argOut.push(...generate(scope, decl.arguments[i]));
1817
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1818
+ ...generate(scope, decl.arguments[i]),
1819
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1820
+ );
1710
1821
 
1711
1822
  // literals only
1712
- const imms = decl.arguments.slice(op.args).map(x => x.value);
1823
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1713
1824
 
1714
1825
  return [
1715
1826
  ...argOut,
1716
- [ Opcodes[opName], ...imms ]
1827
+ [ Opcodes[opName], ...imms ],
1828
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1717
1829
  ];
1718
1830
  }
1719
1831
  }
1720
1832
 
1721
1833
  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`);
1834
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1835
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1724
1836
  }
1725
1837
 
1726
1838
  const func = funcs.find(x => x.index === idx);
1727
1839
 
1728
1840
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1729
1841
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1730
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1842
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1731
1843
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1732
1844
 
1733
1845
  let args = decl.arguments;
@@ -1748,7 +1860,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1748
1860
  const arg = args[i];
1749
1861
  out = out.concat(generate(scope, arg));
1750
1862
 
1751
- if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1863
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1864
+ out.push(Opcodes.i32_to);
1865
+ }
1866
+
1867
+ if (importedFuncs[name] && name.startsWith('profile')) {
1752
1868
  out.push(Opcodes.i32_to);
1753
1869
  }
1754
1870
 
@@ -1767,9 +1883,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1767
1883
  // ...number(type, Valtype.i32),
1768
1884
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1769
1885
  // );
1770
- } else out.push(setLastType(scope));
1886
+ } else out.push(...setLastType(scope));
1771
1887
 
1772
- if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1888
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1773
1889
  out.push(Opcodes.i32_from);
1774
1890
  }
1775
1891
 
@@ -1779,8 +1895,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1779
1895
  const generateNew = (scope, decl, _global, _name) => {
1780
1896
  // hack: basically treat this as a normal call for builtins for now
1781
1897
  const name = mapName(decl.callee.name);
1898
+
1782
1899
  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)})`);
1900
+
1901
+ if (builtinFuncs[name + '$constructor']) {
1902
+ // custom ...$constructor override builtin func
1903
+ return generateCall(scope, {
1904
+ ...decl,
1905
+ callee: {
1906
+ type: 'Identifier',
1907
+ name: name + '$constructor'
1908
+ }
1909
+ }, _global, _name);
1910
+ }
1911
+
1912
+ 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
1913
 
1785
1914
  return generateCall(scope, decl, _global, _name);
1786
1915
  };
@@ -1897,7 +2026,7 @@ const brTable = (input, bc, returns) => {
1897
2026
  };
1898
2027
 
1899
2028
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1900
- if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2029
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
1901
2030
 
1902
2031
  const known = knownType(scope, type);
1903
2032
  if (known != null) {
@@ -1914,8 +2043,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1914
2043
  [ Opcodes.block, returns ]
1915
2044
  ];
1916
2045
 
1917
- // todo: use br_table?
1918
-
1919
2046
  for (const x in bc) {
1920
2047
  if (x === 'default') continue;
1921
2048
 
@@ -1971,12 +2098,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1971
2098
  };
1972
2099
 
1973
2100
  const typeAnnoToPorfType = x => {
1974
- if (TYPES[x]) return TYPES[x];
1975
- if (TYPES['_' + x]) return TYPES['_' + x];
2101
+ if (!x) return null;
2102
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
1976
2103
 
1977
2104
  switch (x) {
1978
2105
  case 'i32':
1979
2106
  case 'i64':
2107
+ case 'f64':
1980
2108
  return TYPES.number;
1981
2109
  }
1982
2110
 
@@ -1987,7 +2115,7 @@ const extractTypeAnnotation = decl => {
1987
2115
  let a = decl;
1988
2116
  while (a.typeAnnotation) a = a.typeAnnotation;
1989
2117
 
1990
- let type, elementType;
2118
+ let type = null, elementType = null;
1991
2119
  if (a.typeName) {
1992
2120
  type = a.typeName.name;
1993
2121
  } else if (a.type.endsWith('Keyword')) {
@@ -2000,7 +2128,7 @@ const extractTypeAnnotation = decl => {
2000
2128
  const typeName = type;
2001
2129
  type = typeAnnoToPorfType(type);
2002
2130
 
2003
- if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2131
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
2004
2132
 
2005
2133
  // if (decl.name) console.log(decl.name, { type, elementType });
2006
2134
 
@@ -2012,13 +2140,13 @@ const generateVar = (scope, decl) => {
2012
2140
 
2013
2141
  const topLevel = scope.name === 'main';
2014
2142
 
2015
- // global variable if in top scope (main) and var ..., or if wanted
2016
- const global = topLevel || decl._bare; // decl.kind === 'var';
2143
+ // global variable if in top scope (main) or if internally wanted
2144
+ const global = topLevel || decl._bare;
2017
2145
 
2018
2146
  for (const x of decl.declarations) {
2019
2147
  const name = mapName(x.id.name);
2020
2148
 
2021
- if (!name) return todo('destructuring is not supported yet');
2149
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2022
2150
 
2023
2151
  if (x.init && isFuncType(x.init.type)) {
2024
2152
  // hack for let a = function () { ... }
@@ -2027,7 +2155,6 @@ const generateVar = (scope, decl) => {
2027
2155
  continue;
2028
2156
  }
2029
2157
 
2030
- // console.log(name);
2031
2158
  if (topLevel && builtinVars[name]) {
2032
2159
  // cannot redeclare
2033
2160
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -2035,16 +2162,29 @@ const generateVar = (scope, decl) => {
2035
2162
  continue; // always ignore
2036
2163
  }
2037
2164
 
2038
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2165
+ // // generate init before allocating var
2166
+ // let generated;
2167
+ // if (x.init) generated = generate(scope, x.init, global, name);
2168
+
2169
+ const typed = typedInput && x.id.typeAnnotation;
2170
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2039
2171
 
2040
- if (typedInput && x.id.typeAnnotation) {
2172
+ if (typed) {
2041
2173
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2042
2174
  }
2043
2175
 
2044
2176
  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 ]);
2177
+ const generated = generate(scope, x.init, global, name);
2178
+ if (scope.arrays?.get(name) != null) {
2179
+ // hack to set local as pointer before
2180
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2181
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2182
+ generated.pop();
2183
+ out = out.concat(generated);
2184
+ } else {
2185
+ out = out.concat(generated);
2186
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2187
+ }
2048
2188
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2049
2189
  }
2050
2190
 
@@ -2055,7 +2195,8 @@ const generateVar = (scope, decl) => {
2055
2195
  return out;
2056
2196
  };
2057
2197
 
2058
- const generateAssign = (scope, decl) => {
2198
+ // todo: optimize this func for valueUnused
2199
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2059
2200
  const { type, name } = decl.left;
2060
2201
 
2061
2202
  if (type === 'ObjectPattern') {
@@ -2070,22 +2211,30 @@ const generateAssign = (scope, decl) => {
2070
2211
  return [];
2071
2212
  }
2072
2213
 
2214
+ const op = decl.operator.slice(0, -1) || '=';
2215
+
2073
2216
  // hack: .length setter
2074
2217
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2075
2218
  const name = decl.left.object.name;
2076
- const pointer = arrays.get(name);
2219
+ const pointer = scope.arrays?.get(name);
2077
2220
 
2078
- const aotPointer = pointer != null;
2221
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2079
2222
 
2080
2223
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2224
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2081
2225
 
2082
2226
  return [
2083
2227
  ...(aotPointer ? number(0, Valtype.i32) : [
2084
2228
  ...generate(scope, decl.left.object),
2085
2229
  Opcodes.i32_to_u
2086
2230
  ]),
2231
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2087
2232
 
2088
- ...generate(scope, decl.right),
2233
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2234
+ [ Opcodes.local_get, pointerTmp ],
2235
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2236
+ Opcodes.i32_from_u
2237
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2089
2238
  [ Opcodes.local_tee, newValueTmp ],
2090
2239
 
2091
2240
  Opcodes.i32_to_u,
@@ -2095,21 +2244,19 @@ const generateAssign = (scope, decl) => {
2095
2244
  ];
2096
2245
  }
2097
2246
 
2098
- const op = decl.operator.slice(0, -1) || '=';
2099
-
2100
2247
  // arr[i]
2101
2248
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2102
2249
  const name = decl.left.object.name;
2103
- const pointer = arrays.get(name);
2250
+ const pointer = scope.arrays?.get(name);
2104
2251
 
2105
- const aotPointer = pointer != null;
2252
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2106
2253
 
2107
2254
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2108
2255
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2109
2256
 
2110
2257
  return [
2111
2258
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2112
- [TYPES._array]: [
2259
+ [TYPES.array]: [
2113
2260
  ...(aotPointer ? [] : [
2114
2261
  ...generate(scope, decl.left.object),
2115
2262
  Opcodes.i32_to_u
@@ -2158,7 +2305,7 @@ const generateAssign = (scope, decl) => {
2158
2305
  ];
2159
2306
  }
2160
2307
 
2161
- if (!name) return todo('destructuring is not supported yet');
2308
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2162
2309
 
2163
2310
  const [ local, isGlobal ] = lookupName(scope, name);
2164
2311
 
@@ -2263,7 +2410,7 @@ const generateUnary = (scope, decl) => {
2263
2410
  return out;
2264
2411
  }
2265
2412
 
2266
- case 'delete':
2413
+ case 'delete': {
2267
2414
  let toReturn = true, toGenerate = true;
2268
2415
 
2269
2416
  if (decl.argument.type === 'Identifier') {
@@ -2285,40 +2432,60 @@ const generateUnary = (scope, decl) => {
2285
2432
 
2286
2433
  out.push(...number(toReturn ? 1 : 0));
2287
2434
  return out;
2435
+ }
2288
2436
 
2289
- case 'typeof':
2290
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2437
+ case 'typeof': {
2438
+ let overrideType, toGenerate = true;
2439
+
2440
+ if (decl.argument.type === 'Identifier') {
2441
+ const out = generateIdent(scope, decl.argument);
2442
+
2443
+ // if ReferenceError (undeclared var), ignore and return undefined
2444
+ if (out[1]) {
2445
+ // does not exist (2 ops from throw)
2446
+ overrideType = number(TYPES.undefined, Valtype.i32);
2447
+ toGenerate = false;
2448
+ }
2449
+ }
2450
+
2451
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2452
+ disposeLeftover(out);
2453
+
2454
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2291
2455
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2292
2456
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2293
2457
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2294
2458
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2295
2459
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2296
2460
 
2297
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2461
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2298
2462
 
2299
2463
  // object and internal types
2300
2464
  default: makeString(scope, 'object', false, '#typeof_result'),
2301
- });
2465
+ }));
2466
+
2467
+ return out;
2468
+ }
2302
2469
 
2303
2470
  default:
2304
- return todo(`unary operator ${decl.operator} not implemented yet`);
2471
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2305
2472
  }
2306
2473
  };
2307
2474
 
2308
- const generateUpdate = (scope, decl) => {
2475
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2309
2476
  const { name } = decl.argument;
2310
2477
 
2311
2478
  const [ local, isGlobal ] = lookupName(scope, name);
2312
2479
 
2313
2480
  if (local === undefined) {
2314
- return todo(`update expression with undefined variable`);
2481
+ return todo(scope, `update expression with undefined variable`, true);
2315
2482
  }
2316
2483
 
2317
2484
  const idx = local.idx;
2318
2485
  const out = [];
2319
2486
 
2320
2487
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2321
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2488
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2322
2489
 
2323
2490
  switch (decl.operator) {
2324
2491
  case '++':
@@ -2331,7 +2498,7 @@ const generateUpdate = (scope, decl) => {
2331
2498
  }
2332
2499
 
2333
2500
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2334
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2501
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2335
2502
 
2336
2503
  return out;
2337
2504
  };
@@ -2371,7 +2538,7 @@ const generateConditional = (scope, decl) => {
2371
2538
  // note type
2372
2539
  out.push(
2373
2540
  ...getNodeType(scope, decl.consequent),
2374
- setLastType(scope)
2541
+ ...setLastType(scope)
2375
2542
  );
2376
2543
 
2377
2544
  out.push([ Opcodes.else ]);
@@ -2380,7 +2547,7 @@ const generateConditional = (scope, decl) => {
2380
2547
  // note type
2381
2548
  out.push(
2382
2549
  ...getNodeType(scope, decl.alternate),
2383
- setLastType(scope)
2550
+ ...setLastType(scope)
2384
2551
  );
2385
2552
 
2386
2553
  out.push([ Opcodes.end ]);
@@ -2394,7 +2561,7 @@ const generateFor = (scope, decl) => {
2394
2561
  const out = [];
2395
2562
 
2396
2563
  if (decl.init) {
2397
- out.push(...generate(scope, decl.init));
2564
+ out.push(...generate(scope, decl.init, false, undefined, true));
2398
2565
  disposeLeftover(out);
2399
2566
  }
2400
2567
 
@@ -2412,7 +2579,7 @@ const generateFor = (scope, decl) => {
2412
2579
  out.push(...generate(scope, decl.body));
2413
2580
  out.push([ Opcodes.end ]);
2414
2581
 
2415
- if (decl.update) out.push(...generate(scope, decl.update));
2582
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2416
2583
 
2417
2584
  out.push([ Opcodes.br, 1 ]);
2418
2585
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2440,6 +2607,36 @@ const generateWhile = (scope, decl) => {
2440
2607
  return out;
2441
2608
  };
2442
2609
 
2610
+ const generateDoWhile = (scope, decl) => {
2611
+ const out = [];
2612
+
2613
+ out.push([ Opcodes.loop, Blocktype.void ]);
2614
+ depth.push('dowhile');
2615
+
2616
+ // block for break (includes all)
2617
+ out.push([ Opcodes.block, Blocktype.void ]);
2618
+ depth.push('block');
2619
+
2620
+ // block for continue
2621
+ // includes body but not test+loop so we can exit body at anytime
2622
+ // and still test+loop after
2623
+ out.push([ Opcodes.block, Blocktype.void ]);
2624
+ depth.push('block');
2625
+
2626
+ out.push(...generate(scope, decl.body));
2627
+
2628
+ out.push([ Opcodes.end ]);
2629
+ depth.pop();
2630
+
2631
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2632
+ out.push([ Opcodes.br_if, 1 ]);
2633
+
2634
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2635
+ depth.pop(); depth.pop();
2636
+
2637
+ return out;
2638
+ };
2639
+
2443
2640
  const generateForOf = (scope, decl) => {
2444
2641
  const out = [];
2445
2642
 
@@ -2476,7 +2673,10 @@ const generateForOf = (scope, decl) => {
2476
2673
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2477
2674
  }
2478
2675
 
2676
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2677
+
2479
2678
  const [ local, isGlobal ] = lookupName(scope, leftName);
2679
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2480
2680
 
2481
2681
  depth.push('block');
2482
2682
  depth.push('block');
@@ -2485,6 +2685,7 @@ const generateForOf = (scope, decl) => {
2485
2685
  // hack: this is naughty and will break things!
2486
2686
  let newOut = number(0, Valtype.f64), newPointer = -1;
2487
2687
  if (pages.hasAnyString) {
2688
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2488
2689
  0, [ newOut, newPointer ] = makeArray(scope, {
2489
2690
  rawElements: new Array(1)
2490
2691
  }, isGlobal, leftName, true, 'i16');
@@ -2493,7 +2694,7 @@ const generateForOf = (scope, decl) => {
2493
2694
  // set type for local
2494
2695
  // todo: optimize away counter and use end pointer
2495
2696
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2496
- [TYPES._array]: [
2697
+ [TYPES.array]: [
2497
2698
  ...setType(scope, leftName, TYPES.number),
2498
2699
 
2499
2700
  [ Opcodes.loop, Blocktype.void ],
@@ -2576,6 +2777,56 @@ const generateForOf = (scope, decl) => {
2576
2777
  [ Opcodes.end ],
2577
2778
  [ Opcodes.end ]
2578
2779
  ],
2780
+ [TYPES.bytestring]: [
2781
+ ...setType(scope, leftName, TYPES.bytestring),
2782
+
2783
+ [ Opcodes.loop, Blocktype.void ],
2784
+
2785
+ // setup new/out array
2786
+ ...newOut,
2787
+ [ Opcodes.drop ],
2788
+
2789
+ ...number(0, Valtype.i32), // base 0 for store after
2790
+
2791
+ // load current string ind {arg}
2792
+ [ Opcodes.local_get, pointer ],
2793
+ [ Opcodes.local_get, counter ],
2794
+ [ Opcodes.i32_add ],
2795
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2796
+
2797
+ // store to new string ind 0
2798
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2799
+
2800
+ // return new string (page)
2801
+ ...number(newPointer),
2802
+
2803
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2804
+
2805
+ [ Opcodes.block, Blocktype.void ],
2806
+ [ Opcodes.block, Blocktype.void ],
2807
+ ...generate(scope, decl.body),
2808
+ [ Opcodes.end ],
2809
+
2810
+ // increment iter pointer
2811
+ // [ Opcodes.local_get, pointer ],
2812
+ // ...number(1, Valtype.i32),
2813
+ // [ Opcodes.i32_add ],
2814
+ // [ Opcodes.local_set, pointer ],
2815
+
2816
+ // increment counter by 1
2817
+ [ Opcodes.local_get, counter ],
2818
+ ...number(1, Valtype.i32),
2819
+ [ Opcodes.i32_add ],
2820
+ [ Opcodes.local_tee, counter ],
2821
+
2822
+ // loop if counter != length
2823
+ [ Opcodes.local_get, length ],
2824
+ [ Opcodes.i32_ne ],
2825
+ [ Opcodes.br_if, 1 ],
2826
+
2827
+ [ Opcodes.end ],
2828
+ [ Opcodes.end ]
2829
+ ],
2579
2830
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2580
2831
  }, Blocktype.void));
2581
2832
 
@@ -2586,28 +2837,65 @@ const generateForOf = (scope, decl) => {
2586
2837
  return out;
2587
2838
  };
2588
2839
 
2840
+ // find the nearest loop in depth map by type
2589
2841
  const getNearestLoop = () => {
2590
2842
  for (let i = depth.length - 1; i >= 0; i--) {
2591
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2843
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2592
2844
  }
2593
2845
 
2594
2846
  return -1;
2595
2847
  };
2596
2848
 
2597
2849
  const generateBreak = (scope, decl) => {
2598
- const nearestLoop = depth.length - getNearestLoop();
2850
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2851
+ const type = depth[target];
2852
+
2853
+ // different loop types have different branch offsets
2854
+ // as they have different wasm block/loop/if structures
2855
+ // we need to use the right offset by type to branch to the one we want
2856
+ // for a break: exit the loop without executing anything else inside it
2857
+ const offset = ({
2858
+ for: 2, // loop > if (wanted branch) > block (we are here)
2859
+ while: 2, // loop > if (wanted branch) (we are here)
2860
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2861
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2862
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2863
+ })[type];
2864
+
2599
2865
  return [
2600
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2866
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2601
2867
  ];
2602
2868
  };
2603
2869
 
2604
2870
  const generateContinue = (scope, decl) => {
2605
- const nearestLoop = depth.length - getNearestLoop();
2871
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2872
+ const type = depth[target];
2873
+
2874
+ // different loop types have different branch offsets
2875
+ // as they have different wasm block/loop/if structures
2876
+ // we need to use the right offset by type to branch to the one we want
2877
+ // for a continue: do test for the loop, and then loop depending on that success
2878
+ const offset = ({
2879
+ for: 3, // loop (wanted branch) > if > block (we are here)
2880
+ while: 1, // loop (wanted branch) > if (we are here)
2881
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2882
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2883
+ })[type];
2884
+
2606
2885
  return [
2607
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2886
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2608
2887
  ];
2609
2888
  };
2610
2889
 
2890
+ const generateLabel = (scope, decl) => {
2891
+ scope.labels ??= new Map();
2892
+
2893
+ const name = decl.label.name;
2894
+ scope.labels.set(name, depth.length);
2895
+
2896
+ return generate(scope, decl.body);
2897
+ };
2898
+
2611
2899
  const generateThrow = (scope, decl) => {
2612
2900
  scope.throws = true;
2613
2901
 
@@ -2640,7 +2928,7 @@ const generateThrow = (scope, decl) => {
2640
2928
  };
2641
2929
 
2642
2930
  const generateTry = (scope, decl) => {
2643
- if (decl.finalizer) return todo('try finally not implemented yet');
2931
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2644
2932
 
2645
2933
  const out = [];
2646
2934
 
@@ -2667,13 +2955,6 @@ const generateEmpty = (scope, decl) => {
2667
2955
  return [];
2668
2956
  };
2669
2957
 
2670
- const generateAssignPat = (scope, decl) => {
2671
- // TODO
2672
- // if identifier declared, use that
2673
- // else, use default (right)
2674
- return todo('assignment pattern (optional arg)');
2675
- };
2676
-
2677
2958
  let pages = new Map();
2678
2959
  const allocPage = (scope, reason, type) => {
2679
2960
  if (pages.has(reason)) return pages.get(reason).ind;
@@ -2750,16 +3031,22 @@ const getAllocType = itemType => {
2750
3031
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2751
3032
  const out = [];
2752
3033
 
3034
+ scope.arrays ??= new Map();
3035
+
2753
3036
  let firstAssign = false;
2754
- if (!arrays.has(name) || name === '$undeclared') {
3037
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2755
3038
  firstAssign = true;
2756
3039
 
2757
3040
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2758
3041
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2759
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3042
+
3043
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3044
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2760
3045
  }
2761
3046
 
2762
- const pointer = arrays.get(name);
3047
+ const pointer = scope.arrays.get(name);
3048
+
3049
+ const local = global ? globals[name] : scope.locals[name];
2763
3050
 
2764
3051
  const useRawElements = !!decl.rawElements;
2765
3052
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2793,11 +3080,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2793
3080
  return [ out, pointer ];
2794
3081
  }
2795
3082
 
3083
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3084
+ if (pointerTmp != null) {
3085
+ out.push(
3086
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3087
+ Opcodes.i32_to_u,
3088
+ [ Opcodes.local_set, pointerTmp ]
3089
+ );
3090
+ }
3091
+
3092
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3093
+
2796
3094
  // store length as 0th array
2797
3095
  out.push(
2798
- ...number(0, Valtype.i32),
3096
+ ...pointerWasm,
2799
3097
  ...number(length, Valtype.i32),
2800
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3098
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2801
3099
  );
2802
3100
 
2803
3101
  const storeOp = StoreOps[itemType];
@@ -2806,14 +3104,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2806
3104
  if (elements[i] == null) continue;
2807
3105
 
2808
3106
  out.push(
2809
- ...number(0, Valtype.i32),
3107
+ ...pointerWasm,
2810
3108
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2811
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3109
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2812
3110
  );
2813
3111
  }
2814
3112
 
2815
3113
  // local value as pointer
2816
- out.push(...number(pointer));
3114
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2817
3115
 
2818
3116
  return [ out, pointer ];
2819
3117
  };
@@ -2845,20 +3143,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2845
3143
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2846
3144
  };
2847
3145
 
2848
- let arrays = new Map();
2849
3146
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2850
3147
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2851
3148
  };
2852
3149
 
2853
3150
  export const generateMember = (scope, decl, _global, _name) => {
2854
3151
  const name = decl.object.name;
2855
- const pointer = arrays.get(name);
3152
+ const pointer = scope.arrays?.get(name);
2856
3153
 
2857
- const aotPointer = pointer != null;
3154
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
3155
+
3156
+ // hack: .name
3157
+ if (decl.property.name === 'name') {
3158
+ if (hasFuncWithName(name)) {
3159
+ let nameProp = name;
3160
+
3161
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3162
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3163
+
3164
+ return makeString(scope, nameProp, _global, _name, true);
3165
+ } else {
3166
+ return generate(scope, DEFAULT_VALUE);
3167
+ }
3168
+ }
2858
3169
 
2859
3170
  // hack: .length
2860
3171
  if (decl.property.name === 'length') {
2861
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3172
+ const func = funcs.find(x => x.name === name);
3173
+ if (func) {
3174
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3175
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3176
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3177
+ }
3178
+
3179
+ if (builtinFuncs[name + '$constructor']) {
3180
+ const regularFunc = builtinFuncs[name];
3181
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3182
+
3183
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3184
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3185
+
3186
+ return number(Math.max(regularParams, constructorParams));
3187
+ }
3188
+
3189
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3190
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3191
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3192
+
2862
3193
  return [
2863
3194
  ...(aotPointer ? number(0, Valtype.i32) : [
2864
3195
  ...generate(scope, decl.object),
@@ -2883,7 +3214,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2883
3214
  }
2884
3215
 
2885
3216
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2886
- [TYPES._array]: [
3217
+ [TYPES.array]: [
2887
3218
  // get index as valtype
2888
3219
  ...property,
2889
3220
 
@@ -2902,7 +3233,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2902
3233
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2903
3234
 
2904
3235
  ...number(TYPES.number, Valtype.i32),
2905
- setLastType(scope)
3236
+ ...setLastType(scope)
2906
3237
  ],
2907
3238
 
2908
3239
  [TYPES.string]: [
@@ -2934,9 +3265,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2934
3265
  ...number(newPointer),
2935
3266
 
2936
3267
  ...number(TYPES.string, Valtype.i32),
2937
- setLastType(scope)
3268
+ ...setLastType(scope)
2938
3269
  ],
2939
- [TYPES._bytestring]: [
3270
+ [TYPES.bytestring]: [
2940
3271
  // setup new/out array
2941
3272
  ...newOut,
2942
3273
  [ Opcodes.drop ],
@@ -2953,19 +3284,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2953
3284
  ]),
2954
3285
 
2955
3286
  // load current string ind {arg}
2956
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3287
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2957
3288
 
2958
3289
  // store to new string ind 0
2959
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3290
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2960
3291
 
2961
3292
  // return new string (page)
2962
3293
  ...number(newPointer),
2963
3294
 
2964
- ...number(TYPES._bytestring, Valtype.i32),
2965
- setLastType(scope)
3295
+ ...number(TYPES.bytestring, Valtype.i32),
3296
+ ...setLastType(scope)
2966
3297
  ],
2967
3298
 
2968
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3299
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2969
3300
  });
2970
3301
  };
2971
3302
 
@@ -2975,28 +3306,36 @@ const objectHack = node => {
2975
3306
  if (!node) return node;
2976
3307
 
2977
3308
  if (node.type === 'MemberExpression') {
2978
- if (node.computed || node.optional) return node;
3309
+ const out = (() => {
3310
+ if (node.computed || node.optional) return;
2979
3311
 
2980
- let objectName = node.object.name;
3312
+ let objectName = node.object.name;
2981
3313
 
2982
- // if object is not identifier or another member exp, give up
2983
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3314
+ // if object is not identifier or another member exp, give up
3315
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3316
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2984
3317
 
2985
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3318
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2986
3319
 
2987
- // if .length, give up (hack within a hack!)
2988
- if (node.property.name === 'length') return node;
3320
+ // if .name or .length, give up (hack within a hack!)
3321
+ if (['name', 'length'].includes(node.property.name)) {
3322
+ node.object = objectHack(node.object);
3323
+ return;
3324
+ }
2989
3325
 
2990
- // no object name, give up
2991
- if (!objectName) return node;
3326
+ // no object name, give up
3327
+ if (!objectName) return;
2992
3328
 
2993
- const name = '__' + objectName + '_' + node.property.name;
2994
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3329
+ const name = '__' + objectName + '_' + node.property.name;
3330
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2995
3331
 
2996
- return {
2997
- type: 'Identifier',
2998
- name
2999
- };
3332
+ return {
3333
+ type: 'Identifier',
3334
+ name
3335
+ };
3336
+ })();
3337
+
3338
+ if (out) return out;
3000
3339
  }
3001
3340
 
3002
3341
  for (const x in node) {
@@ -3010,8 +3349,8 @@ const objectHack = node => {
3010
3349
  };
3011
3350
 
3012
3351
  const generateFunc = (scope, decl) => {
3013
- if (decl.async) return todo('async functions are not supported');
3014
- if (decl.generator) return todo('generator functions are not supported');
3352
+ if (decl.async) return todo(scope, 'async functions are not supported');
3353
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
3015
3354
 
3016
3355
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3017
3356
  const params = decl.params ?? [];
@@ -3027,6 +3366,14 @@ const generateFunc = (scope, decl) => {
3027
3366
  name
3028
3367
  };
3029
3368
 
3369
+ if (typedInput && decl.returnType) {
3370
+ const { type } = extractTypeAnnotation(decl.returnType);
3371
+ if (type != null) {
3372
+ innerScope.returnType = type;
3373
+ innerScope.returns = [ valtypeBinary ];
3374
+ }
3375
+ }
3376
+
3030
3377
  for (let i = 0; i < params.length; i++) {
3031
3378
  allocVar(innerScope, params[i].name, false);
3032
3379
 
@@ -3066,7 +3413,7 @@ const generateFunc = (scope, decl) => {
3066
3413
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3067
3414
  wasm.push(
3068
3415
  ...number(0),
3069
- ...number(TYPES.undefined, Valtype.i32),
3416
+ ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3070
3417
  [ Opcodes.return ]
3071
3418
  );
3072
3419
  }
@@ -3089,16 +3436,6 @@ const generateCode = (scope, decl) => {
3089
3436
  };
3090
3437
 
3091
3438
  const internalConstrs = {
3092
- Boolean: {
3093
- generate: (scope, decl) => {
3094
- if (decl.arguments.length === 0) return number(0);
3095
-
3096
- // should generate/run all args
3097
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3098
- },
3099
- type: TYPES.boolean
3100
- },
3101
-
3102
3439
  Array: {
3103
3440
  generate: (scope, decl, global, name) => {
3104
3441
  // new Array(i0, i1, ...)
@@ -3116,7 +3453,7 @@ const internalConstrs = {
3116
3453
 
3117
3454
  // todo: check in wasm instead of here
3118
3455
  const literalValue = arg.value ?? 0;
3119
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3456
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3120
3457
 
3121
3458
  return [
3122
3459
  ...number(0, Valtype.i32),
@@ -3127,7 +3464,8 @@ const internalConstrs = {
3127
3464
  ...number(pointer)
3128
3465
  ];
3129
3466
  },
3130
- type: TYPES._array
3467
+ type: TYPES.array,
3468
+ length: 1
3131
3469
  },
3132
3470
 
3133
3471
  __Array_of: {
@@ -3138,27 +3476,134 @@ const internalConstrs = {
3138
3476
  elements: decl.arguments
3139
3477
  }, global, name);
3140
3478
  },
3141
- type: TYPES._array,
3479
+ type: TYPES.array,
3480
+ notConstr: true,
3481
+ length: 0
3482
+ },
3483
+
3484
+ __Porffor_fastOr: {
3485
+ generate: (scope, decl) => {
3486
+ const out = [];
3487
+
3488
+ for (let i = 0; i < decl.arguments.length; i++) {
3489
+ out.push(
3490
+ ...generate(scope, decl.arguments[i]),
3491
+ Opcodes.i32_to_u,
3492
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3493
+ );
3494
+ }
3495
+
3496
+ out.push(Opcodes.i32_from_u);
3497
+
3498
+ return out;
3499
+ },
3500
+ type: TYPES.boolean,
3142
3501
  notConstr: true
3143
- }
3144
- };
3502
+ },
3503
+
3504
+ __Porffor_fastAnd: {
3505
+ generate: (scope, decl) => {
3506
+ const out = [];
3507
+
3508
+ for (let i = 0; i < decl.arguments.length; i++) {
3509
+ out.push(
3510
+ ...generate(scope, decl.arguments[i]),
3511
+ Opcodes.i32_to_u,
3512
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3513
+ );
3514
+ }
3515
+
3516
+ out.push(Opcodes.i32_from_u);
3145
3517
 
3146
- // const _ = Array.prototype.push;
3147
- // Array.prototype.push = function (a) {
3148
- // const check = arr => {
3149
- // for (const x of arr) {
3150
- // if (x === undefined) {
3151
- // console.trace(arr);
3152
- // process.exit();
3153
- // }
3154
- // if (Array.isArray(x)) check(x);
3155
- // }
3156
- // };
3157
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
3158
- // // if (Array.isArray(a)) check(a);
3518
+ return out;
3519
+ },
3520
+ type: TYPES.boolean,
3521
+ notConstr: true
3522
+ },
3523
+
3524
+ Boolean: {
3525
+ generate: (scope, decl) => {
3526
+ // todo: boolean object when used as constructor
3527
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3528
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3529
+ },
3530
+ type: TYPES.boolean,
3531
+ length: 1
3532
+ },
3159
3533
 
3160
- // return _.apply(this, arguments);
3161
- // };
3534
+ __Math_max: {
3535
+ generate: (scope, decl) => {
3536
+ const out = [
3537
+ ...number(-Infinity)
3538
+ ];
3539
+
3540
+ for (let i = 0; i < decl.arguments.length; i++) {
3541
+ out.push(
3542
+ ...generate(scope, decl.arguments[i]),
3543
+ [ Opcodes.f64_max ]
3544
+ );
3545
+ }
3546
+
3547
+ return out;
3548
+ },
3549
+ type: TYPES.number,
3550
+ notConstr: true,
3551
+ length: 2
3552
+ },
3553
+
3554
+ __Math_min: {
3555
+ generate: (scope, decl) => {
3556
+ const out = [
3557
+ ...number(Infinity)
3558
+ ];
3559
+
3560
+ for (let i = 0; i < decl.arguments.length; i++) {
3561
+ out.push(
3562
+ ...generate(scope, decl.arguments[i]),
3563
+ [ Opcodes.f64_min ]
3564
+ );
3565
+ }
3566
+
3567
+ return out;
3568
+ },
3569
+ type: TYPES.number,
3570
+ notConstr: true,
3571
+ length: 2
3572
+ },
3573
+
3574
+ __console_log: {
3575
+ generate: (scope, decl) => {
3576
+ const out = [];
3577
+
3578
+ for (let i = 0; i < decl.arguments.length; i++) {
3579
+ out.push(
3580
+ ...generateCall(scope, {
3581
+ callee: {
3582
+ type: 'Identifier',
3583
+ name: '__Porffor_print'
3584
+ },
3585
+ arguments: [ decl.arguments[i] ]
3586
+ }),
3587
+
3588
+ // print space
3589
+ ...number(32),
3590
+ [ Opcodes.call, importedFuncs.printChar ]
3591
+ );
3592
+ }
3593
+
3594
+ // print newline
3595
+ out.push(
3596
+ ...number(10),
3597
+ [ Opcodes.call, importedFuncs.printChar ]
3598
+ );
3599
+
3600
+ return out;
3601
+ },
3602
+ type: TYPES.undefined,
3603
+ notConstr: true,
3604
+ length: 0
3605
+ }
3606
+ };
3162
3607
 
3163
3608
  export default program => {
3164
3609
  globals = {};
@@ -3168,20 +3613,23 @@ export default program => {
3168
3613
  funcs = [];
3169
3614
  funcIndex = {};
3170
3615
  depth = [];
3171
- arrays = new Map();
3172
3616
  pages = new Map();
3173
3617
  data = [];
3174
3618
  currentFuncIndex = importedFuncs.length;
3175
3619
 
3176
3620
  globalThis.valtype = 'f64';
3177
3621
 
3178
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3622
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3179
3623
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3180
3624
 
3181
3625
  globalThis.valtypeBinary = Valtype[valtype];
3182
3626
 
3183
3627
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3184
3628
 
3629
+ globalThis.pageSize = PageSize;
3630
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3631
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3632
+
3185
3633
  // set generic opcodes for current valtype
3186
3634
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3187
3635
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3190,10 +3638,10 @@ export default program => {
3190
3638
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3191
3639
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3192
3640
 
3193
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3194
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3195
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3196
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3641
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3642
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3643
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3644
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3197
3645
 
3198
3646
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3199
3647
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3206,10 +3654,6 @@ export default program => {
3206
3654
 
3207
3655
  program.id = { name: 'main' };
3208
3656
 
3209
- globalThis.pageSize = PageSize;
3210
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3211
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3212
-
3213
3657
  const scope = {
3214
3658
  locals: {},
3215
3659
  localInd: 0
@@ -3220,7 +3664,7 @@ export default program => {
3220
3664
  body: program.body
3221
3665
  };
3222
3666
 
3223
- if (Prefs.astLog) console.log(program.body.body);
3667
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3224
3668
 
3225
3669
  generateFunc(scope, program);
3226
3670
 
@@ -3237,7 +3681,11 @@ export default program => {
3237
3681
  }
3238
3682
 
3239
3683
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3240
- main.returns = [];
3684
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3685
+ main.wasm.splice(main.wasm.length - 1, 1);
3686
+ } else {
3687
+ main.returns = [];
3688
+ }
3241
3689
  }
3242
3690
 
3243
3691
  if (lastInst[0] === Opcodes.call) {