porffor 0.2.0-dcc06c8 → 0.2.0-e04e26f

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 (55) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +63 -44
  3. package/asur/README.md +2 -0
  4. package/asur/index.js +1262 -0
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +1 -1
  7. package/compiler/{sections.js → assemble.js} +58 -11
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +103 -40
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/date.ts +1128 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +33 -1
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +452 -238
  21. package/compiler/{codeGen.js → codegen.js} +799 -290
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +1133 -0
  25. package/compiler/index.js +16 -14
  26. package/compiler/log.js +6 -3
  27. package/compiler/opt.js +23 -22
  28. package/compiler/parse.js +31 -25
  29. package/compiler/precompile.js +66 -22
  30. package/compiler/prefs.js +5 -1
  31. package/compiler/prototype.js +4 -20
  32. package/compiler/types.js +37 -0
  33. package/compiler/wasmSpec.js +28 -8
  34. package/compiler/wrap.js +51 -47
  35. package/package.json +9 -5
  36. package/porf +2 -0
  37. package/rhemyn/compile.js +3 -2
  38. package/rhemyn/parse.js +323 -320
  39. package/rhemyn/test/parse.js +58 -58
  40. package/runner/compare.js +34 -34
  41. package/runner/debug.js +122 -0
  42. package/runner/index.js +31 -9
  43. package/runner/profiler.js +102 -0
  44. package/runner/repl.js +40 -7
  45. package/runner/sizes.js +37 -37
  46. package/demo.js +0 -3
  47. package/demo.ts +0 -1
  48. package/filesize.cmd +0 -2
  49. package/hello +0 -0
  50. package/runner/info.js +0 -89
  51. package/runner/profile.js +0 -46
  52. package/runner/results.json +0 -1
  53. package/runner/transform.js +0 -15
  54. package/tmp.c +0 -152
  55. package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
10
  import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
11
12
 
12
13
  let globals = {};
13
14
  let globalInd = 0;
@@ -24,35 +25,37 @@ const debug = str => {
24
25
  const logChar = n => {
25
26
  code.push(...number(n));
26
27
 
27
- code.push(Opcodes.call);
28
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
29
29
  };
30
30
 
31
31
  for (let i = 0; i < str.length; i++) {
32
32
  logChar(str.charCodeAt(i));
33
33
  }
34
34
 
35
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
36
36
 
37
37
  return code;
38
38
  };
39
39
 
40
- const todo = msg => {
41
- class TodoError extends Error {
42
- constructor(message) {
43
- super(message);
44
- this.name = 'TodoError';
45
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
46
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
47
50
 
48
- throw new TodoError(`todo: ${msg}`);
49
-
50
- const code = [];
51
-
52
- code.push(...debug(`todo! ` + msg));
53
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
54
53
 
55
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
56
59
  };
57
60
 
58
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -105,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
105
108
  return generateUnary(scope, decl);
106
109
 
107
110
  case 'UpdateExpression':
108
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
109
112
 
110
113
  case 'IfStatement':
111
114
  return generateIf(scope, decl);
@@ -116,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
116
119
  case 'WhileStatement':
117
120
  return generateWhile(scope, decl);
118
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
119
125
  case 'ForOfStatement':
120
126
  return generateForOf(scope, decl);
121
127
 
@@ -125,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
125
131
  case 'ContinueStatement':
126
132
  return generateContinue(scope, decl);
127
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
128
137
  case 'EmptyStatement':
129
138
  return generateEmpty(scope, decl);
130
139
 
@@ -138,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
138
147
  return generateTry(scope, decl);
139
148
 
140
149
  case 'DebuggerStatement':
141
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
142
151
  return [];
143
152
 
144
153
  case 'ArrayExpression':
@@ -152,10 +161,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
152
161
  const funcsBefore = funcs.length;
153
162
  generate(scope, decl.declaration);
154
163
 
155
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
156
171
 
157
- const newFunc = funcs[funcs.length - 1];
158
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
159
174
 
160
175
  return [];
161
176
 
@@ -169,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
169
184
  if (asm[0] === '') continue; // blank
170
185
 
171
186
  if (asm[0] === 'local') {
172
- const [ name, idx, type ] = asm.slice(1);
173
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
174
189
  continue;
175
190
  }
176
191
 
@@ -189,43 +204,55 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
189
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
190
205
 
191
206
  if (!Array.isArray(inst)) inst = [ inst ];
192
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
193
212
 
194
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
195
214
  }
196
215
 
197
216
  return out;
198
217
  },
199
218
 
200
219
  __Porffor_bs: str => [
201
- ...makeString(scope, str, undefined, undefined, true),
220
+ ...makeString(scope, str, global, name, true),
202
221
 
203
- ...number(TYPES._bytestring, Valtype.i32),
204
- setLastType(scope)
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
205
226
  ],
206
227
  __Porffor_s: str => [
207
- ...makeString(scope, str, undefined, undefined, false),
228
+ ...makeString(scope, str, global, name, false),
208
229
 
209
- ...number(TYPES.string, Valtype.i32),
210
- setLastType(scope)
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
211
234
  ],
212
235
  };
213
236
 
214
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
215
238
  // hack for inline asm
216
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
217
240
 
218
241
  const { quasis, expressions } = decl.quasi;
219
242
  let str = quasis[0].value.raw;
220
243
 
221
244
  for (let i = 0; i < expressions.length; i++) {
222
245
  const e = expressions[i];
223
- str += lookupName(scope, e.name)[0];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
224
251
 
225
252
  str += quasis[i + 1].value.raw;
226
253
  }
227
254
 
228
- return funcs[name](str);
255
+ return funcs[func](str);
229
256
  }
230
257
 
231
258
  default:
@@ -235,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
235
262
  return [];
236
263
  }
237
264
 
238
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
239
266
  }
240
267
  };
241
268
 
@@ -263,7 +290,7 @@ const lookupName = (scope, _name) => {
263
290
  return [ undefined, undefined ];
264
291
  };
265
292
 
266
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
267
294
  ...generateThrow(scope, {
268
295
  argument: {
269
296
  type: 'NewExpression',
@@ -287,7 +314,10 @@ const generateIdent = (scope, decl) => {
287
314
 
288
315
  if (Object.hasOwn(builtinVars, name)) {
289
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
290
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
291
321
  }
292
322
 
293
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -295,6 +325,11 @@ const generateIdent = (scope, decl) => {
295
325
  return number(1);
296
326
  }
297
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
298
333
  if (local?.idx === undefined) {
299
334
  // no local var with name
300
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -325,14 +360,18 @@ const generateReturn = (scope, decl) => {
325
360
  // just bare "return"
326
361
  return [
327
362
  ...number(UNDEFINED), // "undefined" if func returns
328
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
329
366
  [ Opcodes.return ]
330
367
  ];
331
368
  }
332
369
 
333
370
  return [
334
371
  ...generate(scope, decl.argument),
335
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
336
375
  [ Opcodes.return ]
337
376
  ];
338
377
  };
@@ -346,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
346
385
  return idx;
347
386
  };
348
387
 
349
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
350
390
 
351
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
392
  const checks = {
@@ -355,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
355
395
  '??': nullish
356
396
  };
357
397
 
358
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
359
399
 
360
400
  // generic structure for {a} OP {b}
361
401
  // -->
@@ -363,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
363
403
 
364
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
365
405
  // (like if we are in an if condition - very common)
366
- const leftIsInt = isIntOp(left[left.length - 1]);
367
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
368
408
 
369
409
  const canInt = leftIsInt && rightIsInt;
370
410
 
@@ -381,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
381
421
  ...right,
382
422
  // note type
383
423
  ...rightType,
384
- setLastType(scope),
424
+ ...setLastType(scope),
385
425
  [ Opcodes.else ],
386
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
387
427
  // note type
388
428
  ...leftType,
389
- setLastType(scope),
429
+ ...setLastType(scope),
390
430
  [ Opcodes.end ],
391
431
  Opcodes.i32_from
392
432
  ];
@@ -400,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
400
440
  ...right,
401
441
  // note type
402
442
  ...rightType,
403
- setLastType(scope),
443
+ ...setLastType(scope),
404
444
  [ Opcodes.else ],
405
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
406
446
  // note type
407
447
  ...leftType,
408
- setLastType(scope),
448
+ ...setLastType(scope),
409
449
  [ Opcodes.end ]
410
450
  ];
411
451
  };
412
452
 
413
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
414
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
415
455
  // todo: convert left and right to strings if not
416
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -421,7 +461,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
421
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
422
462
 
423
463
  if (assign) {
424
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
425
465
 
426
466
  return [
427
467
  // setup right
@@ -446,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
446
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
447
487
 
448
488
  // copy right
449
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
450
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
451
491
 
452
492
  [ Opcodes.local_get, leftLength ],
453
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
454
494
  [ Opcodes.i32_mul ],
455
495
  [ Opcodes.i32_add ],
456
496
 
@@ -459,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
459
499
  ...number(ValtypeSize.i32, Valtype.i32),
460
500
  [ Opcodes.i32_add ],
461
501
 
462
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
463
503
  [ Opcodes.local_get, rightLength ],
464
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
465
505
  [ Opcodes.i32_mul ],
466
506
 
467
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -519,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
519
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
520
560
 
521
561
  // copy right
522
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
523
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
524
564
 
525
565
  [ Opcodes.local_get, leftLength ],
526
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
527
567
  [ Opcodes.i32_mul ],
528
568
  [ Opcodes.i32_add ],
529
569
 
@@ -532,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
532
572
  ...number(ValtypeSize.i32, Valtype.i32),
533
573
  [ Opcodes.i32_add ],
534
574
 
535
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
536
576
  [ Opcodes.local_get, rightLength ],
537
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
538
578
  [ Opcodes.i32_mul ],
539
579
 
540
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -544,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
544
584
  ];
545
585
  };
546
586
 
547
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
548
588
  // todo: this should be rewritten into a func
549
589
  // todo: convert left and right to strings if not
550
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -553,7 +593,6 @@ const compareStrings = (scope, left, right) => {
553
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
554
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
555
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
556
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
557
596
 
558
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
559
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -581,7 +620,6 @@ const compareStrings = (scope, left, right) => {
581
620
 
582
621
  [ Opcodes.local_get, rightPointer ],
583
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
584
- [ Opcodes.local_tee, rightLength ],
585
623
 
586
624
  // fast path: check leftLength != rightLength
587
625
  [ Opcodes.i32_ne ],
@@ -596,11 +634,13 @@ const compareStrings = (scope, left, right) => {
596
634
  ...number(0, Valtype.i32),
597
635
  [ Opcodes.local_set, index ],
598
636
 
599
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
600
638
  // we do this instead of having to do mul/div each iter for perf™
601
639
  [ Opcodes.local_get, leftLength ],
602
- ...number(ValtypeSize.i16, Valtype.i32),
603
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
604
644
  [ Opcodes.local_set, indexEnd ],
605
645
 
606
646
  // iterate over each char and check if eq
@@ -610,13 +650,17 @@ const compareStrings = (scope, left, right) => {
610
650
  [ Opcodes.local_get, index ],
611
651
  [ Opcodes.local_get, leftPointer ],
612
652
  [ Opcodes.i32_add ],
613
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
614
656
 
615
657
  // fetch right
616
658
  [ Opcodes.local_get, index ],
617
659
  [ Opcodes.local_get, rightPointer ],
618
660
  [ Opcodes.i32_add ],
619
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
620
664
 
621
665
  // not equal, "return" false
622
666
  [ Opcodes.i32_ne ],
@@ -625,13 +669,13 @@ const compareStrings = (scope, left, right) => {
625
669
  [ Opcodes.br, 2 ],
626
670
  [ Opcodes.end ],
627
671
 
628
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
629
673
  [ Opcodes.local_get, index ],
630
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
631
675
  [ Opcodes.i32_add ],
632
676
  [ Opcodes.local_tee, index ],
633
677
 
634
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
635
679
  [ Opcodes.local_get, indexEnd ],
636
680
  [ Opcodes.i32_ne ],
637
681
  [ Opcodes.br_if, 0 ],
@@ -652,16 +696,18 @@ const compareStrings = (scope, left, right) => {
652
696
  };
653
697
 
654
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
655
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
656
700
  ...wasm,
657
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
658
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
659
704
 
660
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
661
707
 
662
708
  const def = [
663
709
  // if value != 0
664
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
665
711
 
666
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
667
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -673,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
673
719
 
674
720
  return [
675
721
  ...wasm,
676
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
677
723
 
678
724
  ...typeSwitch(scope, type, {
679
725
  // [TYPES.number]: def,
@@ -682,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
682
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
683
729
  ],
684
730
  [TYPES.string]: [
685
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
686
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
687
733
 
688
734
  // get length
@@ -694,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
694
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
695
741
  ],
696
742
  [TYPES._bytestring]: [ // duplicate of string
697
- [ Opcodes.local_get, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
744
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
699
745
 
700
746
  // get length
@@ -708,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
708
754
  };
709
755
 
710
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
712
760
  return [
713
761
  ...wasm,
714
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
715
763
 
716
764
  ...typeSwitch(scope, type, {
717
765
  [TYPES._array]: [
@@ -719,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
720
768
  ],
721
769
  [TYPES.string]: [
722
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
723
771
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
724
772
 
725
773
  // get length
@@ -730,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
730
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
731
779
  ],
732
780
  [TYPES._bytestring]: [ // duplicate of string
733
- [ Opcodes.local_get, tmp ],
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
734
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
735
783
 
736
784
  // get length
@@ -742,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
742
790
  ],
743
791
  default: [
744
792
  // if value == 0
745
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
746
794
 
747
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
748
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -752,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
752
800
  };
753
801
 
754
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
755
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
756
806
  return [
757
807
  ...wasm,
758
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
759
809
 
760
810
  ...typeSwitch(scope, type, {
761
811
  [TYPES.undefined]: [
@@ -764,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
764
814
  ],
765
815
  [TYPES.object]: [
766
816
  // object, null if == 0
767
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
768
818
 
769
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
770
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -845,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
845
895
  // todo: if equality op and an operand is undefined, return false
846
896
  // todo: niche null hell with 0
847
897
 
848
- // todo: this should be dynamic but for now only static
849
898
  if (knownLeft === TYPES.string || knownRight === TYPES.string) {
850
899
  if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
851
901
  // string concat (a + b)
852
- return concatStrings(scope, left, right, _global, _name, assign);
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
853
903
  }
854
904
 
855
905
  // not an equality op, NaN
@@ -872,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
872
922
  }
873
923
  }
874
924
 
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
951
+
875
952
  let ops = operatorOpcode[valtype][op];
876
953
 
877
954
  // some complex ops are implemented as builtin funcs
@@ -887,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
887
964
  ]);
888
965
  }
889
966
 
890
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
891
968
 
892
969
  if (!Array.isArray(ops)) ops = [ ops ];
893
970
  ops = [ ops ];
894
971
 
895
972
  let tmpLeft, tmpRight;
896
973
  // if equal op, check if strings for compareStrings
897
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
898
- // todo: intelligent partial skip later
899
- // if neither known are string, stop this madness
900
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
901
- return;
902
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
903
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
904
979
  tmpLeft = localTmp(scope, '__tmpop_left');
905
980
  tmpRight = localTmp(scope, '__tmpop_right');
906
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
907
1023
  ops.unshift(...stringOnly([
908
1024
  // if left is string
909
1025
  ...leftType,
@@ -915,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
915
1031
  ...number(TYPES.string, Valtype.i32),
916
1032
  [ Opcodes.i32_eq ],
917
1033
 
918
- // if either are true
919
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
920
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
921
1041
 
922
- // todo: convert non-strings to strings, for now fail immediately if one is not
923
- // if left is not string
1042
+ // if left is bytestring
924
1043
  ...leftType,
925
- ...number(TYPES.string, Valtype.i32),
926
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
927
1046
 
928
- // if right is not string
1047
+ // if right is bytestring
929
1048
  ...rightType,
930
- ...number(TYPES.string, Valtype.i32),
931
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
932
1051
 
933
- // if either are true
934
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
935
1054
  [ Opcodes.if, Blocktype.void ],
936
- ...number(0, Valtype.i32),
937
- [ Opcodes.br, 2 ],
938
- [ Opcodes.end ],
939
-
940
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
941
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
942
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
943
1057
  [ Opcodes.br, 1 ],
944
1058
  [ Opcodes.end ],
@@ -950,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
950
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
951
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
952
1066
  // }
953
- })();
1067
+ }
954
1068
 
955
1069
  return finalize([
956
1070
  ...left,
@@ -969,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
969
1083
  return out;
970
1084
  };
971
1085
 
972
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
973
1102
  const existing = funcs.find(x => x.name === name);
974
1103
  if (existing) return existing;
975
1104
 
@@ -981,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
981
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
982
1111
  }
983
1112
 
984
- if (typeof wasm === 'function') {
985
- const scope = {
986
- name,
987
- params,
988
- locals,
989
- returns,
990
- localInd: allLocals.length,
991
- };
992
-
993
- wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
994
1117
  }
995
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
996
1121
  let baseGlobalIdx, i = 0;
997
1122
  for (const type of globalTypes) {
998
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1015,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1015
1140
  params,
1016
1141
  locals,
1017
1142
  returns,
1018
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1019
1144
  wasm,
1020
1145
  internal: true,
1021
1146
  index: currentFuncIndex++
@@ -1038,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1038
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1039
1164
  };
1040
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1041
1167
  // T = JS type, V = value/pointer
1042
1168
  // 0bTTT
1043
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1061,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
1061
1187
  // 4: internal type
1062
1188
  // 5: pointer
1063
1189
 
1064
- const TYPES = {
1065
- number: 0x00,
1066
- boolean: 0x01,
1067
- string: 0x02,
1068
- undefined: 0x03,
1069
- object: 0x04,
1070
- function: 0x05,
1071
- symbol: 0x06,
1072
- bigint: 0x07,
1073
-
1074
- // these are not "typeof" types but tracked internally
1075
- _array: 0x10,
1076
- _regexp: 0x11,
1077
- _bytestring: 0x12
1078
- };
1079
-
1080
- const TYPE_NAMES = {
1081
- [TYPES.number]: 'Number',
1082
- [TYPES.boolean]: 'Boolean',
1083
- [TYPES.string]: 'String',
1084
- [TYPES.undefined]: 'undefined',
1085
- [TYPES.object]: 'Object',
1086
- [TYPES.function]: 'Function',
1087
- [TYPES.symbol]: 'Symbol',
1088
- [TYPES.bigint]: 'BigInt',
1089
-
1090
- [TYPES._array]: 'Array',
1091
- [TYPES._regexp]: 'RegExp',
1092
- [TYPES._bytestring]: 'ByteString'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1093
1195
  };
1094
1196
 
1095
1197
  const getType = (scope, _name) => {
1096
1198
  const name = mapName(_name);
1097
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1098
1202
  if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1099
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1100
1204
 
@@ -1102,11 +1206,10 @@ const getType = (scope, _name) => {
1102
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1103
1207
 
1104
1208
  let type = TYPES.undefined;
1105
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1106
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1107
1211
 
1108
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1109
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1110
1213
 
1111
1214
  return number(type, Valtype.i32);
1112
1215
  };
@@ -1129,15 +1232,16 @@ const setType = (scope, _name, type) => {
1129
1232
  ];
1130
1233
 
1131
1234
  // throw new Error('could not find var');
1235
+ return [];
1132
1236
  };
1133
1237
 
1134
1238
  const getLastType = scope => {
1135
1239
  scope.gotLastType = true;
1136
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1137
1241
  };
1138
1242
 
1139
1243
  const setLastType = scope => {
1140
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1141
1245
  };
1142
1246
 
1143
1247
  const getNodeType = (scope, node) => {
@@ -1162,13 +1266,25 @@ const getNodeType = (scope, node) => {
1162
1266
  const name = node.callee.name;
1163
1267
  if (!name) {
1164
1268
  // iife
1165
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1166
1270
 
1167
1271
  // presume
1168
1272
  // todo: warn here?
1169
1273
  return TYPES.number;
1170
1274
  }
1171
1275
 
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1172
1288
  const func = funcs.find(x => x.name === name);
1173
1289
 
1174
1290
  if (func) {
@@ -1176,7 +1292,7 @@ const getNodeType = (scope, node) => {
1176
1292
  if (func.returnType) return func.returnType;
1177
1293
  }
1178
1294
 
1179
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1180
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1181
1297
 
1182
1298
  // check if this is a prototype function
@@ -1191,7 +1307,12 @@ const getNodeType = (scope, node) => {
1191
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1192
1308
  }
1193
1309
 
1194
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1195
1316
 
1196
1317
  // presume
1197
1318
  // todo: warn here?
@@ -1246,6 +1367,7 @@ const getNodeType = (scope, node) => {
1246
1367
 
1247
1368
  // todo: this should be dynamic but for now only static
1248
1369
  if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1249
1371
 
1250
1372
  return TYPES.number;
1251
1373
 
@@ -1282,15 +1404,21 @@ const getNodeType = (scope, node) => {
1282
1404
 
1283
1405
  // ts hack
1284
1406
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1285
1408
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1286
1409
 
1287
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1288
1411
 
1289
1412
  // presume
1290
1413
  return TYPES.number;
1291
1414
  }
1292
1415
 
1293
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1294
1422
 
1295
1423
  // presume
1296
1424
  // todo: warn here?
@@ -1323,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1323
1451
  return makeString(scope, decl.value, global, name);
1324
1452
 
1325
1453
  default:
1326
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1327
1455
  }
1328
1456
  };
1329
1457
 
@@ -1332,6 +1460,8 @@ const countLeftover = wasm => {
1332
1460
 
1333
1461
  for (let i = 0; i < wasm.length; i++) {
1334
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1335
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1336
1466
  if (inst[0] === Opcodes.if) count--;
1337
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1340,18 +1470,25 @@ const countLeftover = wasm => {
1340
1470
  if (inst[0] === Opcodes.end) depth--;
1341
1471
 
1342
1472
  if (depth === 0)
1343
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1344
1474
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1345
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1346
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1347
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1348
1478
  else if (inst[0] === Opcodes.return) count = 0;
1349
1479
  else if (inst[0] === Opcodes.call) {
1350
1480
  let func = funcs.find(x => x.index === inst[1]);
1351
- if (func) {
1352
- count -= func.params.length;
1353
- } else count--;
1354
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1355
1492
  } else count--;
1356
1493
 
1357
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1443,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1443
1580
  name = func.name;
1444
1581
  }
1445
1582
 
1446
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1447
1584
  // literal eval hack
1448
- const code = decl.arguments[0].value;
1449
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1450
1598
 
1451
1599
  const out = generate(scope, {
1452
1600
  type: 'BlockStatement',
@@ -1460,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1460
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1461
1609
  out.push(
1462
1610
  ...getNodeType(scope, finalStatement),
1463
- setLastType(scope)
1611
+ ...setLastType(scope)
1464
1612
  );
1465
1613
  } else if (countLeftover(out) === 0) {
1466
1614
  out.push(...number(UNDEFINED));
1467
1615
  out.push(
1468
1616
  ...number(TYPES.undefined, Valtype.i32),
1469
- setLastType(scope)
1617
+ ...setLastType(scope)
1470
1618
  );
1471
1619
  }
1472
1620
 
@@ -1488,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1488
1636
 
1489
1637
  target = { ...decl.callee };
1490
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1491
1642
  }
1492
1643
 
1493
1644
  // literal.func()
@@ -1510,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1510
1661
  Opcodes.i32_from_u,
1511
1662
 
1512
1663
  ...number(TYPES.boolean, Valtype.i32),
1513
- setLastType(scope)
1664
+ ...setLastType(scope)
1514
1665
  ];
1515
1666
  }
1516
1667
 
@@ -1535,12 +1686,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1535
1686
  // }
1536
1687
 
1537
1688
  if (protoName) {
1689
+ const protoBC = {};
1690
+
1691
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1692
+
1693
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1694
+ for (const x of builtinProtoCands) {
1695
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1696
+ if (type == null) continue;
1697
+
1698
+ protoBC[type] = generateCall(scope, {
1699
+ callee: {
1700
+ name: x
1701
+ },
1702
+ arguments: [ target, ...decl.arguments ],
1703
+ _protoInternalCall: true
1704
+ });
1705
+ }
1706
+ }
1707
+
1538
1708
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1539
1709
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1540
1710
  return acc;
1541
1711
  }, {});
1542
1712
 
1543
- // no prototype function candidates, ignore
1544
1713
  if (Object.keys(protoCands).length > 0) {
1545
1714
  // use local for cached i32 length as commonly used
1546
1715
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1558,7 +1727,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1558
1727
 
1559
1728
  let allOptUnused = true;
1560
1729
  let lengthI32CacheUsed = false;
1561
- const protoBC = {};
1562
1730
  for (const x in protoCands) {
1563
1731
  const protoFunc = protoCands[x];
1564
1732
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1566,7 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1566
1734
  ...RTArrayUtil.getLength(getPointer),
1567
1735
 
1568
1736
  ...number(TYPES.number, Valtype.i32),
1569
- setLastType(scope)
1737
+ ...setLastType(scope)
1570
1738
  ];
1571
1739
  continue;
1572
1740
  }
@@ -1603,7 +1771,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1603
1771
  ...protoOut,
1604
1772
 
1605
1773
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1606
- setLastType(scope),
1774
+ ...setLastType(scope),
1607
1775
  [ Opcodes.end ]
1608
1776
  ];
1609
1777
  }
@@ -1629,10 +1797,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1629
1797
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1630
1798
  ];
1631
1799
  }
1800
+
1801
+ if (Object.keys(protoBC).length > 0) {
1802
+ return typeSwitch(scope, getNodeType(scope, target), {
1803
+ ...protoBC,
1804
+
1805
+ // TODO: error better
1806
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1807
+ }, valtypeBinary);
1808
+ }
1632
1809
  }
1633
1810
 
1634
1811
  // TODO: only allows callee as literal
1635
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1812
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1636
1813
 
1637
1814
  let idx = funcIndex[name] ?? importedFuncs[name];
1638
1815
  if (idx === undefined && builtinFuncs[name]) {
@@ -1665,16 +1842,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1665
1842
  idx = -1;
1666
1843
  }
1667
1844
 
1845
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1846
+ const wasmOps = {
1847
+ // pointer, align, offset
1848
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1849
+ // pointer, value, align, offset
1850
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1851
+ // pointer, align, offset
1852
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1853
+ // pointer, value, align, offset
1854
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1855
+ // pointer, align, offset
1856
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1857
+ // pointer, value, align, offset
1858
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1859
+
1860
+ // pointer, align, offset
1861
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1862
+ // pointer, value, align, offset
1863
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1864
+
1865
+ // value
1866
+ i32_const: { imms: 1, args: [], returns: 1 },
1867
+
1868
+ // a, b
1869
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1870
+ };
1871
+
1872
+ const opName = name.slice('__Porffor_wasm_'.length);
1873
+
1874
+ if (wasmOps[opName]) {
1875
+ const op = wasmOps[opName];
1876
+
1877
+ const argOut = [];
1878
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1879
+ ...generate(scope, decl.arguments[i]),
1880
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1881
+ );
1882
+
1883
+ // literals only
1884
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1885
+
1886
+ return [
1887
+ ...argOut,
1888
+ [ Opcodes[opName], ...imms ],
1889
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1890
+ ];
1891
+ }
1892
+ }
1893
+
1668
1894
  if (idx === undefined) {
1669
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1670
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1895
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1896
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1671
1897
  }
1672
1898
 
1673
1899
  const func = funcs.find(x => x.index === idx);
1674
1900
 
1675
1901
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1676
1902
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1677
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1903
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1678
1904
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1679
1905
 
1680
1906
  let args = decl.arguments;
@@ -1691,8 +1917,18 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1691
1917
  if (func && func.throws) scope.throws = true;
1692
1918
 
1693
1919
  let out = [];
1694
- for (const arg of args) {
1920
+ for (let i = 0; i < args.length; i++) {
1921
+ const arg = args[i];
1695
1922
  out = out.concat(generate(scope, arg));
1923
+
1924
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1925
+ out.push(Opcodes.i32_to);
1926
+ }
1927
+
1928
+ if (importedFuncs[name] && name.startsWith('profile')) {
1929
+ out.push(Opcodes.i32_to);
1930
+ }
1931
+
1696
1932
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1697
1933
  }
1698
1934
 
@@ -1708,7 +1944,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1708
1944
  // ...number(type, Valtype.i32),
1709
1945
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1710
1946
  // );
1711
- } else out.push(setLastType(scope));
1947
+ } else out.push(...setLastType(scope));
1948
+
1949
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1950
+ out.push(Opcodes.i32_from);
1951
+ }
1712
1952
 
1713
1953
  return out;
1714
1954
  };
@@ -1716,8 +1956,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1956
  const generateNew = (scope, decl, _global, _name) => {
1717
1957
  // hack: basically treat this as a normal call for builtins for now
1718
1958
  const name = mapName(decl.callee.name);
1959
+
1719
1960
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1720
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1961
+
1962
+ if (builtinFuncs[name + '$constructor']) {
1963
+ // custom ...$constructor override builtin func
1964
+ return generateCall(scope, {
1965
+ ...decl,
1966
+ callee: {
1967
+ type: 'Identifier',
1968
+ name: name + '$constructor'
1969
+ }
1970
+ }, _global, _name);
1971
+ }
1972
+
1973
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1721
1974
 
1722
1975
  return generateCall(scope, decl, _global, _name);
1723
1976
  };
@@ -1851,8 +2104,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1851
2104
  [ Opcodes.block, returns ]
1852
2105
  ];
1853
2106
 
1854
- // todo: use br_table?
1855
-
1856
2107
  for (const x in bc) {
1857
2108
  if (x === 'default') continue;
1858
2109
 
@@ -1908,11 +2159,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1908
2159
  };
1909
2160
 
1910
2161
  const typeAnnoToPorfType = x => {
1911
- if (TYPES[x]) return TYPES[x];
1912
- if (TYPES['_' + x]) return TYPES['_' + x];
2162
+ if (!x) return null;
2163
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2164
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1913
2165
 
1914
2166
  switch (x) {
1915
2167
  case 'i32':
2168
+ case 'i64':
2169
+ case 'f64':
1916
2170
  return TYPES.number;
1917
2171
  }
1918
2172
 
@@ -1923,7 +2177,7 @@ const extractTypeAnnotation = decl => {
1923
2177
  let a = decl;
1924
2178
  while (a.typeAnnotation) a = a.typeAnnotation;
1925
2179
 
1926
- let type, elementType;
2180
+ let type = null, elementType = null;
1927
2181
  if (a.typeName) {
1928
2182
  type = a.typeName.name;
1929
2183
  } else if (a.type.endsWith('Keyword')) {
@@ -1954,7 +2208,7 @@ const generateVar = (scope, decl) => {
1954
2208
  for (const x of decl.declarations) {
1955
2209
  const name = mapName(x.id.name);
1956
2210
 
1957
- if (!name) return todo('destructuring is not supported yet');
2211
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1958
2212
 
1959
2213
  if (x.init && isFuncType(x.init.type)) {
1960
2214
  // hack for let a = function () { ... }
@@ -1971,9 +2225,10 @@ const generateVar = (scope, decl) => {
1971
2225
  continue; // always ignore
1972
2226
  }
1973
2227
 
1974
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2228
+ const typed = typedInput && x.id.typeAnnotation;
2229
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1975
2230
 
1976
- if (typedInput && x.id.typeAnnotation) {
2231
+ if (typed) {
1977
2232
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1978
2233
  }
1979
2234
 
@@ -1991,7 +2246,8 @@ const generateVar = (scope, decl) => {
1991
2246
  return out;
1992
2247
  };
1993
2248
 
1994
- const generateAssign = (scope, decl) => {
2249
+ // todo: optimize this func for valueUnused
2250
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1995
2251
  const { type, name } = decl.left;
1996
2252
 
1997
2253
  if (type === 'ObjectPattern') {
@@ -2009,9 +2265,9 @@ const generateAssign = (scope, decl) => {
2009
2265
  // hack: .length setter
2010
2266
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2011
2267
  const name = decl.left.object.name;
2012
- const pointer = arrays.get(name);
2268
+ const pointer = scope.arrays?.get(name);
2013
2269
 
2014
- const aotPointer = pointer != null;
2270
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2015
2271
 
2016
2272
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2017
2273
 
@@ -2036,9 +2292,9 @@ const generateAssign = (scope, decl) => {
2036
2292
  // arr[i]
2037
2293
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2038
2294
  const name = decl.left.object.name;
2039
- const pointer = arrays.get(name);
2295
+ const pointer = scope.arrays?.get(name);
2040
2296
 
2041
- const aotPointer = pointer != null;
2297
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2042
2298
 
2043
2299
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2044
2300
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2094,7 +2350,7 @@ const generateAssign = (scope, decl) => {
2094
2350
  ];
2095
2351
  }
2096
2352
 
2097
- if (!name) return todo('destructuring is not supported yet');
2353
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2098
2354
 
2099
2355
  const [ local, isGlobal ] = lookupName(scope, name);
2100
2356
 
@@ -2142,9 +2398,7 @@ const generateAssign = (scope, decl) => {
2142
2398
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2143
2399
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2144
2400
 
2145
- getLastType(scope),
2146
- // hack: type is idx+1
2147
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2401
+ ...setType(scope, name, getLastType(scope))
2148
2402
  ];
2149
2403
  }
2150
2404
 
@@ -2155,9 +2409,7 @@ const generateAssign = (scope, decl) => {
2155
2409
 
2156
2410
  // todo: string concat types
2157
2411
 
2158
- // hack: type is idx+1
2159
- ...number(TYPES.number, Valtype.i32),
2160
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2412
+ ...setType(scope, name, TYPES.number)
2161
2413
  ];
2162
2414
  };
2163
2415
 
@@ -2203,7 +2455,7 @@ const generateUnary = (scope, decl) => {
2203
2455
  return out;
2204
2456
  }
2205
2457
 
2206
- case 'delete':
2458
+ case 'delete': {
2207
2459
  let toReturn = true, toGenerate = true;
2208
2460
 
2209
2461
  if (decl.argument.type === 'Identifier') {
@@ -2225,9 +2477,26 @@ const generateUnary = (scope, decl) => {
2225
2477
 
2226
2478
  out.push(...number(toReturn ? 1 : 0));
2227
2479
  return out;
2480
+ }
2481
+
2482
+ case 'typeof': {
2483
+ let overrideType, toGenerate = true;
2228
2484
 
2229
- case 'typeof':
2230
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2485
+ if (decl.argument.type === 'Identifier') {
2486
+ const out = generateIdent(scope, decl.argument);
2487
+
2488
+ // if ReferenceError (undeclared var), ignore and return undefined
2489
+ if (out[1]) {
2490
+ // does not exist (2 ops from throw)
2491
+ overrideType = number(TYPES.undefined, Valtype.i32);
2492
+ toGenerate = false;
2493
+ }
2494
+ }
2495
+
2496
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2497
+ disposeLeftover(out);
2498
+
2499
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2231
2500
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2232
2501
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2233
2502
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2238,27 +2507,30 @@ const generateUnary = (scope, decl) => {
2238
2507
 
2239
2508
  // object and internal types
2240
2509
  default: makeString(scope, 'object', false, '#typeof_result'),
2241
- });
2510
+ }));
2511
+
2512
+ return out;
2513
+ }
2242
2514
 
2243
2515
  default:
2244
- return todo(`unary operator ${decl.operator} not implemented yet`);
2516
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2245
2517
  }
2246
2518
  };
2247
2519
 
2248
- const generateUpdate = (scope, decl) => {
2520
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2249
2521
  const { name } = decl.argument;
2250
2522
 
2251
2523
  const [ local, isGlobal ] = lookupName(scope, name);
2252
2524
 
2253
2525
  if (local === undefined) {
2254
- return todo(`update expression with undefined variable`);
2526
+ return todo(scope, `update expression with undefined variable`, true);
2255
2527
  }
2256
2528
 
2257
2529
  const idx = local.idx;
2258
2530
  const out = [];
2259
2531
 
2260
2532
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2261
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2533
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2262
2534
 
2263
2535
  switch (decl.operator) {
2264
2536
  case '++':
@@ -2271,7 +2543,7 @@ const generateUpdate = (scope, decl) => {
2271
2543
  }
2272
2544
 
2273
2545
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2274
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2546
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2275
2547
 
2276
2548
  return out;
2277
2549
  };
@@ -2311,7 +2583,7 @@ const generateConditional = (scope, decl) => {
2311
2583
  // note type
2312
2584
  out.push(
2313
2585
  ...getNodeType(scope, decl.consequent),
2314
- setLastType(scope)
2586
+ ...setLastType(scope)
2315
2587
  );
2316
2588
 
2317
2589
  out.push([ Opcodes.else ]);
@@ -2320,7 +2592,7 @@ const generateConditional = (scope, decl) => {
2320
2592
  // note type
2321
2593
  out.push(
2322
2594
  ...getNodeType(scope, decl.alternate),
2323
- setLastType(scope)
2595
+ ...setLastType(scope)
2324
2596
  );
2325
2597
 
2326
2598
  out.push([ Opcodes.end ]);
@@ -2334,7 +2606,7 @@ const generateFor = (scope, decl) => {
2334
2606
  const out = [];
2335
2607
 
2336
2608
  if (decl.init) {
2337
- out.push(...generate(scope, decl.init));
2609
+ out.push(...generate(scope, decl.init, false, undefined, true));
2338
2610
  disposeLeftover(out);
2339
2611
  }
2340
2612
 
@@ -2352,7 +2624,7 @@ const generateFor = (scope, decl) => {
2352
2624
  out.push(...generate(scope, decl.body));
2353
2625
  out.push([ Opcodes.end ]);
2354
2626
 
2355
- if (decl.update) out.push(...generate(scope, decl.update));
2627
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2356
2628
 
2357
2629
  out.push([ Opcodes.br, 1 ]);
2358
2630
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2380,6 +2652,36 @@ const generateWhile = (scope, decl) => {
2380
2652
  return out;
2381
2653
  };
2382
2654
 
2655
+ const generateDoWhile = (scope, decl) => {
2656
+ const out = [];
2657
+
2658
+ out.push([ Opcodes.loop, Blocktype.void ]);
2659
+ depth.push('dowhile');
2660
+
2661
+ // block for break (includes all)
2662
+ out.push([ Opcodes.block, Blocktype.void ]);
2663
+ depth.push('block');
2664
+
2665
+ // block for continue
2666
+ // includes body but not test+loop so we can exit body at anytime
2667
+ // and still test+loop after
2668
+ out.push([ Opcodes.block, Blocktype.void ]);
2669
+ depth.push('block');
2670
+
2671
+ out.push(...generate(scope, decl.body));
2672
+
2673
+ out.push([ Opcodes.end ]);
2674
+ depth.pop();
2675
+
2676
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2677
+ out.push([ Opcodes.br_if, 1 ]);
2678
+
2679
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2680
+ depth.pop(); depth.pop();
2681
+
2682
+ return out;
2683
+ };
2684
+
2383
2685
  const generateForOf = (scope, decl) => {
2384
2686
  const out = [];
2385
2687
 
@@ -2416,7 +2718,10 @@ const generateForOf = (scope, decl) => {
2416
2718
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2417
2719
  }
2418
2720
 
2721
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2722
+
2419
2723
  const [ local, isGlobal ] = lookupName(scope, leftName);
2724
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2420
2725
 
2421
2726
  depth.push('block');
2422
2727
  depth.push('block');
@@ -2425,6 +2730,7 @@ const generateForOf = (scope, decl) => {
2425
2730
  // hack: this is naughty and will break things!
2426
2731
  let newOut = number(0, Valtype.f64), newPointer = -1;
2427
2732
  if (pages.hasAnyString) {
2733
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2428
2734
  0, [ newOut, newPointer ] = makeArray(scope, {
2429
2735
  rawElements: new Array(1)
2430
2736
  }, isGlobal, leftName, true, 'i16');
@@ -2516,6 +2822,56 @@ const generateForOf = (scope, decl) => {
2516
2822
  [ Opcodes.end ],
2517
2823
  [ Opcodes.end ]
2518
2824
  ],
2825
+ [TYPES._bytestring]: [
2826
+ ...setType(scope, leftName, TYPES._bytestring),
2827
+
2828
+ [ Opcodes.loop, Blocktype.void ],
2829
+
2830
+ // setup new/out array
2831
+ ...newOut,
2832
+ [ Opcodes.drop ],
2833
+
2834
+ ...number(0, Valtype.i32), // base 0 for store after
2835
+
2836
+ // load current string ind {arg}
2837
+ [ Opcodes.local_get, pointer ],
2838
+ [ Opcodes.local_get, counter ],
2839
+ [ Opcodes.i32_add ],
2840
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2841
+
2842
+ // store to new string ind 0
2843
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2844
+
2845
+ // return new string (page)
2846
+ ...number(newPointer),
2847
+
2848
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2849
+
2850
+ [ Opcodes.block, Blocktype.void ],
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ ...generate(scope, decl.body),
2853
+ [ Opcodes.end ],
2854
+
2855
+ // increment iter pointer
2856
+ // [ Opcodes.local_get, pointer ],
2857
+ // ...number(1, Valtype.i32),
2858
+ // [ Opcodes.i32_add ],
2859
+ // [ Opcodes.local_set, pointer ],
2860
+
2861
+ // increment counter by 1
2862
+ [ Opcodes.local_get, counter ],
2863
+ ...number(1, Valtype.i32),
2864
+ [ Opcodes.i32_add ],
2865
+ [ Opcodes.local_tee, counter ],
2866
+
2867
+ // loop if counter != length
2868
+ [ Opcodes.local_get, length ],
2869
+ [ Opcodes.i32_ne ],
2870
+ [ Opcodes.br_if, 1 ],
2871
+
2872
+ [ Opcodes.end ],
2873
+ [ Opcodes.end ]
2874
+ ],
2519
2875
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2520
2876
  }, Blocktype.void));
2521
2877
 
@@ -2526,28 +2882,65 @@ const generateForOf = (scope, decl) => {
2526
2882
  return out;
2527
2883
  };
2528
2884
 
2885
+ // find the nearest loop in depth map by type
2529
2886
  const getNearestLoop = () => {
2530
2887
  for (let i = depth.length - 1; i >= 0; i--) {
2531
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2888
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2532
2889
  }
2533
2890
 
2534
2891
  return -1;
2535
2892
  };
2536
2893
 
2537
2894
  const generateBreak = (scope, decl) => {
2538
- const nearestLoop = depth.length - getNearestLoop();
2895
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2896
+ const type = depth[target];
2897
+
2898
+ // different loop types have different branch offsets
2899
+ // as they have different wasm block/loop/if structures
2900
+ // we need to use the right offset by type to branch to the one we want
2901
+ // for a break: exit the loop without executing anything else inside it
2902
+ const offset = ({
2903
+ for: 2, // loop > if (wanted branch) > block (we are here)
2904
+ while: 2, // loop > if (wanted branch) (we are here)
2905
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2906
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2907
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2908
+ })[type];
2909
+
2539
2910
  return [
2540
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2911
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2541
2912
  ];
2542
2913
  };
2543
2914
 
2544
2915
  const generateContinue = (scope, decl) => {
2545
- const nearestLoop = depth.length - getNearestLoop();
2916
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2917
+ const type = depth[target];
2918
+
2919
+ // different loop types have different branch offsets
2920
+ // as they have different wasm block/loop/if structures
2921
+ // we need to use the right offset by type to branch to the one we want
2922
+ // for a continue: do test for the loop, and then loop depending on that success
2923
+ const offset = ({
2924
+ for: 3, // loop (wanted branch) > if > block (we are here)
2925
+ while: 1, // loop (wanted branch) > if (we are here)
2926
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2927
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2928
+ })[type];
2929
+
2546
2930
  return [
2547
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2931
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2548
2932
  ];
2549
2933
  };
2550
2934
 
2935
+ const generateLabel = (scope, decl) => {
2936
+ scope.labels ??= new Map();
2937
+
2938
+ const name = decl.label.name;
2939
+ scope.labels.set(name, depth.length);
2940
+
2941
+ return generate(scope, decl.body);
2942
+ };
2943
+
2551
2944
  const generateThrow = (scope, decl) => {
2552
2945
  scope.throws = true;
2553
2946
 
@@ -2580,7 +2973,7 @@ const generateThrow = (scope, decl) => {
2580
2973
  };
2581
2974
 
2582
2975
  const generateTry = (scope, decl) => {
2583
- if (decl.finalizer) return todo('try finally not implemented yet');
2976
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2584
2977
 
2585
2978
  const out = [];
2586
2979
 
@@ -2611,7 +3004,7 @@ const generateAssignPat = (scope, decl) => {
2611
3004
  // TODO
2612
3005
  // if identifier declared, use that
2613
3006
  // else, use default (right)
2614
- return todo('assignment pattern (optional arg)');
3007
+ return todo(scope, 'assignment pattern (optional arg)');
2615
3008
  };
2616
3009
 
2617
3010
  let pages = new Map();
@@ -2690,16 +3083,20 @@ const getAllocType = itemType => {
2690
3083
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2691
3084
  const out = [];
2692
3085
 
3086
+ scope.arrays ??= new Map();
3087
+
2693
3088
  let firstAssign = false;
2694
- if (!arrays.has(name) || name === '$undeclared') {
3089
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2695
3090
  firstAssign = true;
2696
3091
 
2697
3092
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2698
3093
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2699
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3094
+
3095
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3096
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2700
3097
  }
2701
3098
 
2702
- const pointer = arrays.get(name);
3099
+ const pointer = scope.arrays.get(name);
2703
3100
 
2704
3101
  const useRawElements = !!decl.rawElements;
2705
3102
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2707,22 +3104,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2707
3104
  const valtype = itemTypeToValtype[itemType];
2708
3105
  const length = elements.length;
2709
3106
 
2710
- if (firstAssign && useRawElements) {
2711
- let bytes = compileBytes(length, 'i32');
3107
+ if (firstAssign && useRawElements && !Prefs.noData) {
3108
+ // if length is 0 memory/data will just be 0000... anyway
3109
+ if (length !== 0) {
3110
+ let bytes = compileBytes(length, 'i32');
2712
3111
 
2713
- if (!initEmpty) for (let i = 0; i < length; i++) {
2714
- if (elements[i] == null) continue;
3112
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3113
+ if (elements[i] == null) continue;
2715
3114
 
2716
- bytes.push(...compileBytes(elements[i], itemType));
2717
- }
3115
+ bytes.push(...compileBytes(elements[i], itemType));
3116
+ }
2718
3117
 
2719
- const ind = data.push({
2720
- offset: pointer,
2721
- bytes
2722
- }) - 1;
3118
+ const ind = data.push({
3119
+ offset: pointer,
3120
+ bytes
3121
+ }) - 1;
2723
3122
 
2724
- scope.data ??= [];
2725
- scope.data.push(ind);
3123
+ scope.data ??= [];
3124
+ scope.data.push(ind);
3125
+ }
2726
3126
 
2727
3127
  // local value as pointer
2728
3128
  out.push(...number(pointer));
@@ -2782,20 +3182,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2782
3182
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2783
3183
  };
2784
3184
 
2785
- let arrays = new Map();
2786
3185
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2787
3186
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2788
3187
  };
2789
3188
 
2790
3189
  export const generateMember = (scope, decl, _global, _name) => {
2791
3190
  const name = decl.object.name;
2792
- const pointer = arrays.get(name);
3191
+ const pointer = scope.arrays?.get(name);
2793
3192
 
2794
- const aotPointer = pointer != null;
3193
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2795
3194
 
2796
3195
  // hack: .length
2797
3196
  if (decl.property.name === 'length') {
2798
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3197
+ const func = funcs.find(x => x.name === name);
3198
+ if (func) {
3199
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3200
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3201
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3202
+ }
3203
+
3204
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3205
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3206
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3207
+
2799
3208
  return [
2800
3209
  ...(aotPointer ? number(0, Valtype.i32) : [
2801
3210
  ...generate(scope, decl.object),
@@ -2839,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2839
3248
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2840
3249
 
2841
3250
  ...number(TYPES.number, Valtype.i32),
2842
- setLastType(scope)
3251
+ ...setLastType(scope)
2843
3252
  ],
2844
3253
 
2845
3254
  [TYPES.string]: [
@@ -2871,7 +3280,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2871
3280
  ...number(newPointer),
2872
3281
 
2873
3282
  ...number(TYPES.string, Valtype.i32),
2874
- setLastType(scope)
3283
+ ...setLastType(scope)
2875
3284
  ],
2876
3285
  [TYPES._bytestring]: [
2877
3286
  // setup new/out array
@@ -2890,19 +3299,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2890
3299
  ]),
2891
3300
 
2892
3301
  // load current string ind {arg}
2893
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3302
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2894
3303
 
2895
3304
  // store to new string ind 0
2896
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3305
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2897
3306
 
2898
3307
  // return new string (page)
2899
3308
  ...number(newPointer),
2900
3309
 
2901
3310
  ...number(TYPES._bytestring, Valtype.i32),
2902
- setLastType(scope)
3311
+ ...setLastType(scope)
2903
3312
  ],
2904
3313
 
2905
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3314
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2906
3315
  });
2907
3316
  };
2908
3317
 
@@ -2912,28 +3321,36 @@ const objectHack = node => {
2912
3321
  if (!node) return node;
2913
3322
 
2914
3323
  if (node.type === 'MemberExpression') {
2915
- if (node.computed || node.optional) return node;
3324
+ const out = (() => {
3325
+ if (node.computed || node.optional) return;
2916
3326
 
2917
- let objectName = node.object.name;
3327
+ let objectName = node.object.name;
2918
3328
 
2919
- // if object is not identifier or another member exp, give up
2920
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3329
+ // if object is not identifier or another member exp, give up
3330
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3331
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2921
3332
 
2922
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3333
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2923
3334
 
2924
- // if .length, give up (hack within a hack!)
2925
- if (node.property.name === 'length') return node;
3335
+ // if .length, give up (hack within a hack!)
3336
+ if (node.property.name === 'length') {
3337
+ node.object = objectHack(node.object);
3338
+ return;
3339
+ }
2926
3340
 
2927
- // no object name, give up
2928
- if (!objectName) return node;
3341
+ // no object name, give up
3342
+ if (!objectName) return;
2929
3343
 
2930
- const name = '__' + objectName + '_' + node.property.name;
2931
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3344
+ const name = '__' + objectName + '_' + node.property.name;
3345
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2932
3346
 
2933
- return {
2934
- type: 'Identifier',
2935
- name
2936
- };
3347
+ return {
3348
+ type: 'Identifier',
3349
+ name
3350
+ };
3351
+ })();
3352
+
3353
+ if (out) return out;
2937
3354
  }
2938
3355
 
2939
3356
  for (const x in node) {
@@ -2947,8 +3364,8 @@ const objectHack = node => {
2947
3364
  };
2948
3365
 
2949
3366
  const generateFunc = (scope, decl) => {
2950
- if (decl.async) return todo('async functions are not supported');
2951
- if (decl.generator) return todo('generator functions are not supported');
3367
+ if (decl.async) return todo(scope, 'async functions are not supported');
3368
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2952
3369
 
2953
3370
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2954
3371
  const params = decl.params ?? [];
@@ -2964,6 +3381,11 @@ const generateFunc = (scope, decl) => {
2964
3381
  name
2965
3382
  };
2966
3383
 
3384
+ if (typedInput && decl.returnType) {
3385
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3386
+ innerScope.returns = [ valtypeBinary ];
3387
+ }
3388
+
2967
3389
  for (let i = 0; i < params.length; i++) {
2968
3390
  allocVar(innerScope, params[i].name, false);
2969
3391
 
@@ -2990,6 +3412,8 @@ const generateFunc = (scope, decl) => {
2990
3412
  };
2991
3413
  funcIndex[name] = func.index;
2992
3414
 
3415
+ if (name === 'main') func.gotLastType = true;
3416
+
2993
3417
  // quick hack fixes
2994
3418
  for (const inst of wasm) {
2995
3419
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3024,16 +3448,6 @@ const generateCode = (scope, decl) => {
3024
3448
  };
3025
3449
 
3026
3450
  const internalConstrs = {
3027
- Boolean: {
3028
- generate: (scope, decl) => {
3029
- if (decl.arguments.length === 0) return number(0);
3030
-
3031
- // should generate/run all args
3032
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3033
- },
3034
- type: TYPES.boolean
3035
- },
3036
-
3037
3451
  Array: {
3038
3452
  generate: (scope, decl, global, name) => {
3039
3453
  // new Array(i0, i1, ...)
@@ -3051,7 +3465,7 @@ const internalConstrs = {
3051
3465
 
3052
3466
  // todo: check in wasm instead of here
3053
3467
  const literalValue = arg.value ?? 0;
3054
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3468
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3055
3469
 
3056
3470
  return [
3057
3471
  ...number(0, Valtype.i32),
@@ -3062,7 +3476,8 @@ const internalConstrs = {
3062
3476
  ...number(pointer)
3063
3477
  ];
3064
3478
  },
3065
- type: TYPES._array
3479
+ type: TYPES._array,
3480
+ length: 1
3066
3481
  },
3067
3482
 
3068
3483
  __Array_of: {
@@ -3074,7 +3489,98 @@ const internalConstrs = {
3074
3489
  }, global, name);
3075
3490
  },
3076
3491
  type: TYPES._array,
3492
+ notConstr: true,
3493
+ length: 0
3494
+ },
3495
+
3496
+ __Porffor_fastOr: {
3497
+ generate: (scope, decl) => {
3498
+ const out = [];
3499
+
3500
+ for (let i = 0; i < decl.arguments.length; i++) {
3501
+ out.push(
3502
+ ...generate(scope, decl.arguments[i]),
3503
+ Opcodes.i32_to_u,
3504
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3505
+ );
3506
+ }
3507
+
3508
+ out.push(Opcodes.i32_from_u);
3509
+
3510
+ return out;
3511
+ },
3512
+ type: TYPES.boolean,
3513
+ notConstr: true
3514
+ },
3515
+
3516
+ __Porffor_fastAnd: {
3517
+ generate: (scope, decl) => {
3518
+ const out = [];
3519
+
3520
+ for (let i = 0; i < decl.arguments.length; i++) {
3521
+ out.push(
3522
+ ...generate(scope, decl.arguments[i]),
3523
+ Opcodes.i32_to_u,
3524
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3525
+ );
3526
+ }
3527
+
3528
+ out.push(Opcodes.i32_from_u);
3529
+
3530
+ return out;
3531
+ },
3532
+ type: TYPES.boolean,
3077
3533
  notConstr: true
3534
+ },
3535
+
3536
+ Boolean: {
3537
+ generate: (scope, decl) => {
3538
+ // todo: boolean object when used as constructor
3539
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3540
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3541
+ },
3542
+ type: TYPES.boolean,
3543
+ length: 1
3544
+ },
3545
+
3546
+ __Math_max: {
3547
+ generate: (scope, decl) => {
3548
+ const out = [
3549
+ ...number(-Infinity)
3550
+ ];
3551
+
3552
+ for (let i = 0; i < decl.arguments.length; i++) {
3553
+ out.push(
3554
+ ...generate(scope, decl.arguments[i]),
3555
+ [ Opcodes.f64_max ]
3556
+ );
3557
+ }
3558
+
3559
+ return out;
3560
+ },
3561
+ type: TYPES.number,
3562
+ notConstr: true,
3563
+ length: 2
3564
+ },
3565
+
3566
+ __Math_min: {
3567
+ generate: (scope, decl) => {
3568
+ const out = [
3569
+ ...number(Infinity)
3570
+ ];
3571
+
3572
+ for (let i = 0; i < decl.arguments.length; i++) {
3573
+ out.push(
3574
+ ...generate(scope, decl.arguments[i]),
3575
+ [ Opcodes.f64_min ]
3576
+ );
3577
+ }
3578
+
3579
+ return out;
3580
+ },
3581
+ type: TYPES.number,
3582
+ notConstr: true,
3583
+ length: 2
3078
3584
  }
3079
3585
  };
3080
3586
 
@@ -3103,7 +3609,6 @@ export default program => {
3103
3609
  funcs = [];
3104
3610
  funcIndex = {};
3105
3611
  depth = [];
3106
- arrays = new Map();
3107
3612
  pages = new Map();
3108
3613
  data = [];
3109
3614
  currentFuncIndex = importedFuncs.length;
@@ -3117,6 +3622,10 @@ export default program => {
3117
3622
 
3118
3623
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3119
3624
 
3625
+ globalThis.pageSize = PageSize;
3626
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3627
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3628
+
3120
3629
  // set generic opcodes for current valtype
3121
3630
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3122
3631
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3125,10 +3634,10 @@ export default program => {
3125
3634
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3126
3635
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3127
3636
 
3128
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3129
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3130
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3131
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3637
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3638
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3639
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3640
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3132
3641
 
3133
3642
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3134
3643
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3141,10 +3650,6 @@ export default program => {
3141
3650
 
3142
3651
  program.id = { name: 'main' };
3143
3652
 
3144
- globalThis.pageSize = PageSize;
3145
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3146
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3147
-
3148
3653
  const scope = {
3149
3654
  locals: {},
3150
3655
  localInd: 0
@@ -3155,7 +3660,7 @@ export default program => {
3155
3660
  body: program.body
3156
3661
  };
3157
3662
 
3158
- if (Prefs.astLog) console.log(program.body.body);
3663
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3159
3664
 
3160
3665
  generateFunc(scope, program);
3161
3666
 
@@ -3172,7 +3677,11 @@ export default program => {
3172
3677
  }
3173
3678
 
3174
3679
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3175
- main.returns = [];
3680
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3681
+ main.wasm.splice(main.wasm.length - 1, 1);
3682
+ } else {
3683
+ main.returns = [];
3684
+ }
3176
3685
  }
3177
3686
 
3178
3687
  if (lastInst[0] === Opcodes.call) {