porffor 0.2.0-dcc06c8 → 0.2.0-de394c3

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 (56) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +113 -62
  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 +1370 -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 +454 -243
  21. package/compiler/{codeGen.js → codegen.js} +833 -290
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +1259 -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/fib.js +7 -0
  36. package/package.json +9 -5
  37. package/porf +2 -0
  38. package/rhemyn/compile.js +3 -2
  39. package/rhemyn/parse.js +323 -320
  40. package/rhemyn/test/parse.js +58 -58
  41. package/runner/compare.js +34 -34
  42. package/runner/debug.js +122 -0
  43. package/runner/index.js +48 -9
  44. package/runner/profiler.js +102 -0
  45. package/runner/repl.js +40 -7
  46. package/runner/sizes.js +37 -37
  47. package/demo.js +0 -3
  48. package/demo.ts +0 -1
  49. package/filesize.cmd +0 -2
  50. package/hello +0 -0
  51. package/runner/info.js +0 -89
  52. package/runner/profile.js +0 -46
  53. package/runner/results.json +0 -1
  54. package/runner/transform.js +0 -15
  55. package/tmp.c +0 -152
  56. 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,31 @@ 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
+ type: 'Identifier',
1701
+ name: x
1702
+ },
1703
+ arguments: [ target, ...decl.arguments ],
1704
+ _protoInternalCall: true
1705
+ });
1706
+ }
1707
+ }
1708
+
1538
1709
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1539
1710
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1540
1711
  return acc;
1541
1712
  }, {});
1542
1713
 
1543
- // no prototype function candidates, ignore
1544
1714
  if (Object.keys(protoCands).length > 0) {
1545
1715
  // use local for cached i32 length as commonly used
1546
1716
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1558,7 +1728,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1558
1728
 
1559
1729
  let allOptUnused = true;
1560
1730
  let lengthI32CacheUsed = false;
1561
- const protoBC = {};
1562
1731
  for (const x in protoCands) {
1563
1732
  const protoFunc = protoCands[x];
1564
1733
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1566,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1566
1735
  ...RTArrayUtil.getLength(getPointer),
1567
1736
 
1568
1737
  ...number(TYPES.number, Valtype.i32),
1569
- setLastType(scope)
1738
+ ...setLastType(scope)
1570
1739
  ];
1571
1740
  continue;
1572
1741
  }
@@ -1603,7 +1772,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1603
1772
  ...protoOut,
1604
1773
 
1605
1774
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1606
- setLastType(scope),
1775
+ ...setLastType(scope),
1607
1776
  [ Opcodes.end ]
1608
1777
  ];
1609
1778
  }
@@ -1629,10 +1798,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1629
1798
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1630
1799
  ];
1631
1800
  }
1801
+
1802
+ if (Object.keys(protoBC).length > 0) {
1803
+ return typeSwitch(scope, getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary);
1809
+ }
1632
1810
  }
1633
1811
 
1634
1812
  // TODO: only allows callee as literal
1635
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1813
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1636
1814
 
1637
1815
  let idx = funcIndex[name] ?? importedFuncs[name];
1638
1816
  if (idx === undefined && builtinFuncs[name]) {
@@ -1665,16 +1843,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1665
1843
  idx = -1;
1666
1844
  }
1667
1845
 
1846
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1847
+ const wasmOps = {
1848
+ // pointer, align, offset
1849
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1850
+ // pointer, value, align, offset
1851
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1852
+ // pointer, align, offset
1853
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1854
+ // pointer, value, align, offset
1855
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1856
+ // pointer, align, offset
1857
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1858
+ // pointer, value, align, offset
1859
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1860
+
1861
+ // pointer, align, offset
1862
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1863
+ // pointer, value, align, offset
1864
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1865
+
1866
+ // value
1867
+ i32_const: { imms: 1, args: [], returns: 1 },
1868
+
1869
+ // a, b
1870
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1871
+ };
1872
+
1873
+ const opName = name.slice('__Porffor_wasm_'.length);
1874
+
1875
+ if (wasmOps[opName]) {
1876
+ const op = wasmOps[opName];
1877
+
1878
+ const argOut = [];
1879
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1880
+ ...generate(scope, decl.arguments[i]),
1881
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1882
+ );
1883
+
1884
+ // literals only
1885
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1886
+
1887
+ return [
1888
+ ...argOut,
1889
+ [ Opcodes[opName], ...imms ],
1890
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1891
+ ];
1892
+ }
1893
+ }
1894
+
1668
1895
  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`);
1896
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1897
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1671
1898
  }
1672
1899
 
1673
1900
  const func = funcs.find(x => x.index === idx);
1674
1901
 
1675
1902
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1676
1903
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1677
- const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
1904
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1678
1905
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1679
1906
 
1680
1907
  let args = decl.arguments;
@@ -1691,8 +1918,18 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1691
1918
  if (func && func.throws) scope.throws = true;
1692
1919
 
1693
1920
  let out = [];
1694
- for (const arg of args) {
1921
+ for (let i = 0; i < args.length; i++) {
1922
+ const arg = args[i];
1695
1923
  out = out.concat(generate(scope, arg));
1924
+
1925
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1926
+ out.push(Opcodes.i32_to);
1927
+ }
1928
+
1929
+ if (importedFuncs[name] && name.startsWith('profile')) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1696
1933
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1697
1934
  }
1698
1935
 
@@ -1708,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1708
1945
  // ...number(type, Valtype.i32),
1709
1946
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1710
1947
  // );
1711
- } else out.push(setLastType(scope));
1948
+ } else out.push(...setLastType(scope));
1949
+
1950
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1951
+ out.push(Opcodes.i32_from);
1952
+ }
1712
1953
 
1713
1954
  return out;
1714
1955
  };
@@ -1716,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1957
  const generateNew = (scope, decl, _global, _name) => {
1717
1958
  // hack: basically treat this as a normal call for builtins for now
1718
1959
  const name = mapName(decl.callee.name);
1960
+
1719
1961
  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)})`);
1962
+
1963
+ if (builtinFuncs[name + '$constructor']) {
1964
+ // custom ...$constructor override builtin func
1965
+ return generateCall(scope, {
1966
+ ...decl,
1967
+ callee: {
1968
+ type: 'Identifier',
1969
+ name: name + '$constructor'
1970
+ }
1971
+ }, _global, _name);
1972
+ }
1973
+
1974
+ 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
1975
 
1722
1976
  return generateCall(scope, decl, _global, _name);
1723
1977
  };
@@ -1851,8 +2105,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1851
2105
  [ Opcodes.block, returns ]
1852
2106
  ];
1853
2107
 
1854
- // todo: use br_table?
1855
-
1856
2108
  for (const x in bc) {
1857
2109
  if (x === 'default') continue;
1858
2110
 
@@ -1908,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1908
2160
  };
1909
2161
 
1910
2162
  const typeAnnoToPorfType = x => {
1911
- if (TYPES[x]) return TYPES[x];
1912
- if (TYPES['_' + x]) return TYPES['_' + x];
2163
+ if (!x) return null;
2164
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2165
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1913
2166
 
1914
2167
  switch (x) {
1915
2168
  case 'i32':
2169
+ case 'i64':
2170
+ case 'f64':
1916
2171
  return TYPES.number;
1917
2172
  }
1918
2173
 
@@ -1923,7 +2178,7 @@ const extractTypeAnnotation = decl => {
1923
2178
  let a = decl;
1924
2179
  while (a.typeAnnotation) a = a.typeAnnotation;
1925
2180
 
1926
- let type, elementType;
2181
+ let type = null, elementType = null;
1927
2182
  if (a.typeName) {
1928
2183
  type = a.typeName.name;
1929
2184
  } else if (a.type.endsWith('Keyword')) {
@@ -1954,7 +2209,7 @@ const generateVar = (scope, decl) => {
1954
2209
  for (const x of decl.declarations) {
1955
2210
  const name = mapName(x.id.name);
1956
2211
 
1957
- if (!name) return todo('destructuring is not supported yet');
2212
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1958
2213
 
1959
2214
  if (x.init && isFuncType(x.init.type)) {
1960
2215
  // hack for let a = function () { ... }
@@ -1971,9 +2226,10 @@ const generateVar = (scope, decl) => {
1971
2226
  continue; // always ignore
1972
2227
  }
1973
2228
 
1974
- let idx = allocVar(scope, name, global, !x.id.typeAnnotation);
2229
+ const typed = typedInput && x.id.typeAnnotation;
2230
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1975
2231
 
1976
- if (typedInput && x.id.typeAnnotation) {
2232
+ if (typed) {
1977
2233
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1978
2234
  }
1979
2235
 
@@ -1991,7 +2247,8 @@ const generateVar = (scope, decl) => {
1991
2247
  return out;
1992
2248
  };
1993
2249
 
1994
- const generateAssign = (scope, decl) => {
2250
+ // todo: optimize this func for valueUnused
2251
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1995
2252
  const { type, name } = decl.left;
1996
2253
 
1997
2254
  if (type === 'ObjectPattern') {
@@ -2009,9 +2266,9 @@ const generateAssign = (scope, decl) => {
2009
2266
  // hack: .length setter
2010
2267
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
2011
2268
  const name = decl.left.object.name;
2012
- const pointer = arrays.get(name);
2269
+ const pointer = scope.arrays?.get(name);
2013
2270
 
2014
- const aotPointer = pointer != null;
2271
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2015
2272
 
2016
2273
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2017
2274
 
@@ -2036,9 +2293,9 @@ const generateAssign = (scope, decl) => {
2036
2293
  // arr[i]
2037
2294
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2038
2295
  const name = decl.left.object.name;
2039
- const pointer = arrays.get(name);
2296
+ const pointer = scope.arrays?.get(name);
2040
2297
 
2041
- const aotPointer = pointer != null;
2298
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2042
2299
 
2043
2300
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2044
2301
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2094,7 +2351,7 @@ const generateAssign = (scope, decl) => {
2094
2351
  ];
2095
2352
  }
2096
2353
 
2097
- if (!name) return todo('destructuring is not supported yet');
2354
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2098
2355
 
2099
2356
  const [ local, isGlobal ] = lookupName(scope, name);
2100
2357
 
@@ -2142,9 +2399,7 @@ const generateAssign = (scope, decl) => {
2142
2399
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2143
2400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2144
2401
 
2145
- getLastType(scope),
2146
- // hack: type is idx+1
2147
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2402
+ ...setType(scope, name, getLastType(scope))
2148
2403
  ];
2149
2404
  }
2150
2405
 
@@ -2155,9 +2410,7 @@ const generateAssign = (scope, decl) => {
2155
2410
 
2156
2411
  // todo: string concat types
2157
2412
 
2158
- // hack: type is idx+1
2159
- ...number(TYPES.number, Valtype.i32),
2160
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2413
+ ...setType(scope, name, TYPES.number)
2161
2414
  ];
2162
2415
  };
2163
2416
 
@@ -2203,7 +2456,7 @@ const generateUnary = (scope, decl) => {
2203
2456
  return out;
2204
2457
  }
2205
2458
 
2206
- case 'delete':
2459
+ case 'delete': {
2207
2460
  let toReturn = true, toGenerate = true;
2208
2461
 
2209
2462
  if (decl.argument.type === 'Identifier') {
@@ -2225,9 +2478,26 @@ const generateUnary = (scope, decl) => {
2225
2478
 
2226
2479
  out.push(...number(toReturn ? 1 : 0));
2227
2480
  return out;
2481
+ }
2482
+
2483
+ case 'typeof': {
2484
+ let overrideType, toGenerate = true;
2485
+
2486
+ if (decl.argument.type === 'Identifier') {
2487
+ const out = generateIdent(scope, decl.argument);
2488
+
2489
+ // if ReferenceError (undeclared var), ignore and return undefined
2490
+ if (out[1]) {
2491
+ // does not exist (2 ops from throw)
2492
+ overrideType = number(TYPES.undefined, Valtype.i32);
2493
+ toGenerate = false;
2494
+ }
2495
+ }
2496
+
2497
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2498
+ disposeLeftover(out);
2228
2499
 
2229
- case 'typeof':
2230
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2500
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2231
2501
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2232
2502
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2233
2503
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2238,27 +2508,30 @@ const generateUnary = (scope, decl) => {
2238
2508
 
2239
2509
  // object and internal types
2240
2510
  default: makeString(scope, 'object', false, '#typeof_result'),
2241
- });
2511
+ }));
2512
+
2513
+ return out;
2514
+ }
2242
2515
 
2243
2516
  default:
2244
- return todo(`unary operator ${decl.operator} not implemented yet`);
2517
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2245
2518
  }
2246
2519
  };
2247
2520
 
2248
- const generateUpdate = (scope, decl) => {
2521
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2249
2522
  const { name } = decl.argument;
2250
2523
 
2251
2524
  const [ local, isGlobal ] = lookupName(scope, name);
2252
2525
 
2253
2526
  if (local === undefined) {
2254
- return todo(`update expression with undefined variable`);
2527
+ return todo(scope, `update expression with undefined variable`, true);
2255
2528
  }
2256
2529
 
2257
2530
  const idx = local.idx;
2258
2531
  const out = [];
2259
2532
 
2260
2533
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2261
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2534
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2262
2535
 
2263
2536
  switch (decl.operator) {
2264
2537
  case '++':
@@ -2271,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
2271
2544
  }
2272
2545
 
2273
2546
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2274
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2547
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2275
2548
 
2276
2549
  return out;
2277
2550
  };
@@ -2311,7 +2584,7 @@ const generateConditional = (scope, decl) => {
2311
2584
  // note type
2312
2585
  out.push(
2313
2586
  ...getNodeType(scope, decl.consequent),
2314
- setLastType(scope)
2587
+ ...setLastType(scope)
2315
2588
  );
2316
2589
 
2317
2590
  out.push([ Opcodes.else ]);
@@ -2320,7 +2593,7 @@ const generateConditional = (scope, decl) => {
2320
2593
  // note type
2321
2594
  out.push(
2322
2595
  ...getNodeType(scope, decl.alternate),
2323
- setLastType(scope)
2596
+ ...setLastType(scope)
2324
2597
  );
2325
2598
 
2326
2599
  out.push([ Opcodes.end ]);
@@ -2334,7 +2607,7 @@ const generateFor = (scope, decl) => {
2334
2607
  const out = [];
2335
2608
 
2336
2609
  if (decl.init) {
2337
- out.push(...generate(scope, decl.init));
2610
+ out.push(...generate(scope, decl.init, false, undefined, true));
2338
2611
  disposeLeftover(out);
2339
2612
  }
2340
2613
 
@@ -2352,7 +2625,7 @@ const generateFor = (scope, decl) => {
2352
2625
  out.push(...generate(scope, decl.body));
2353
2626
  out.push([ Opcodes.end ]);
2354
2627
 
2355
- if (decl.update) out.push(...generate(scope, decl.update));
2628
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2356
2629
 
2357
2630
  out.push([ Opcodes.br, 1 ]);
2358
2631
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2380,6 +2653,36 @@ const generateWhile = (scope, decl) => {
2380
2653
  return out;
2381
2654
  };
2382
2655
 
2656
+ const generateDoWhile = (scope, decl) => {
2657
+ const out = [];
2658
+
2659
+ out.push([ Opcodes.loop, Blocktype.void ]);
2660
+ depth.push('dowhile');
2661
+
2662
+ // block for break (includes all)
2663
+ out.push([ Opcodes.block, Blocktype.void ]);
2664
+ depth.push('block');
2665
+
2666
+ // block for continue
2667
+ // includes body but not test+loop so we can exit body at anytime
2668
+ // and still test+loop after
2669
+ out.push([ Opcodes.block, Blocktype.void ]);
2670
+ depth.push('block');
2671
+
2672
+ out.push(...generate(scope, decl.body));
2673
+
2674
+ out.push([ Opcodes.end ]);
2675
+ depth.pop();
2676
+
2677
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2678
+ out.push([ Opcodes.br_if, 1 ]);
2679
+
2680
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2681
+ depth.pop(); depth.pop();
2682
+
2683
+ return out;
2684
+ };
2685
+
2383
2686
  const generateForOf = (scope, decl) => {
2384
2687
  const out = [];
2385
2688
 
@@ -2416,7 +2719,10 @@ const generateForOf = (scope, decl) => {
2416
2719
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2417
2720
  }
2418
2721
 
2722
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2723
+
2419
2724
  const [ local, isGlobal ] = lookupName(scope, leftName);
2725
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2420
2726
 
2421
2727
  depth.push('block');
2422
2728
  depth.push('block');
@@ -2425,6 +2731,7 @@ const generateForOf = (scope, decl) => {
2425
2731
  // hack: this is naughty and will break things!
2426
2732
  let newOut = number(0, Valtype.f64), newPointer = -1;
2427
2733
  if (pages.hasAnyString) {
2734
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2428
2735
  0, [ newOut, newPointer ] = makeArray(scope, {
2429
2736
  rawElements: new Array(1)
2430
2737
  }, isGlobal, leftName, true, 'i16');
@@ -2516,6 +2823,56 @@ const generateForOf = (scope, decl) => {
2516
2823
  [ Opcodes.end ],
2517
2824
  [ Opcodes.end ]
2518
2825
  ],
2826
+ [TYPES._bytestring]: [
2827
+ ...setType(scope, leftName, TYPES._bytestring),
2828
+
2829
+ [ Opcodes.loop, Blocktype.void ],
2830
+
2831
+ // setup new/out array
2832
+ ...newOut,
2833
+ [ Opcodes.drop ],
2834
+
2835
+ ...number(0, Valtype.i32), // base 0 for store after
2836
+
2837
+ // load current string ind {arg}
2838
+ [ Opcodes.local_get, pointer ],
2839
+ [ Opcodes.local_get, counter ],
2840
+ [ Opcodes.i32_add ],
2841
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2842
+
2843
+ // store to new string ind 0
2844
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2845
+
2846
+ // return new string (page)
2847
+ ...number(newPointer),
2848
+
2849
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2850
+
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ [ Opcodes.block, Blocktype.void ],
2853
+ ...generate(scope, decl.body),
2854
+ [ Opcodes.end ],
2855
+
2856
+ // increment iter pointer
2857
+ // [ Opcodes.local_get, pointer ],
2858
+ // ...number(1, Valtype.i32),
2859
+ // [ Opcodes.i32_add ],
2860
+ // [ Opcodes.local_set, pointer ],
2861
+
2862
+ // increment counter by 1
2863
+ [ Opcodes.local_get, counter ],
2864
+ ...number(1, Valtype.i32),
2865
+ [ Opcodes.i32_add ],
2866
+ [ Opcodes.local_tee, counter ],
2867
+
2868
+ // loop if counter != length
2869
+ [ Opcodes.local_get, length ],
2870
+ [ Opcodes.i32_ne ],
2871
+ [ Opcodes.br_if, 1 ],
2872
+
2873
+ [ Opcodes.end ],
2874
+ [ Opcodes.end ]
2875
+ ],
2519
2876
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2520
2877
  }, Blocktype.void));
2521
2878
 
@@ -2526,28 +2883,65 @@ const generateForOf = (scope, decl) => {
2526
2883
  return out;
2527
2884
  };
2528
2885
 
2886
+ // find the nearest loop in depth map by type
2529
2887
  const getNearestLoop = () => {
2530
2888
  for (let i = depth.length - 1; i >= 0; i--) {
2531
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2889
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2532
2890
  }
2533
2891
 
2534
2892
  return -1;
2535
2893
  };
2536
2894
 
2537
2895
  const generateBreak = (scope, decl) => {
2538
- const nearestLoop = depth.length - getNearestLoop();
2896
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2897
+ const type = depth[target];
2898
+
2899
+ // different loop types have different branch offsets
2900
+ // as they have different wasm block/loop/if structures
2901
+ // we need to use the right offset by type to branch to the one we want
2902
+ // for a break: exit the loop without executing anything else inside it
2903
+ const offset = ({
2904
+ for: 2, // loop > if (wanted branch) > block (we are here)
2905
+ while: 2, // loop > if (wanted branch) (we are here)
2906
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2907
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2908
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2909
+ })[type];
2910
+
2539
2911
  return [
2540
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2912
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2541
2913
  ];
2542
2914
  };
2543
2915
 
2544
2916
  const generateContinue = (scope, decl) => {
2545
- const nearestLoop = depth.length - getNearestLoop();
2917
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2918
+ const type = depth[target];
2919
+
2920
+ // different loop types have different branch offsets
2921
+ // as they have different wasm block/loop/if structures
2922
+ // we need to use the right offset by type to branch to the one we want
2923
+ // for a continue: do test for the loop, and then loop depending on that success
2924
+ const offset = ({
2925
+ for: 3, // loop (wanted branch) > if > block (we are here)
2926
+ while: 1, // loop (wanted branch) > if (we are here)
2927
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2928
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2929
+ })[type];
2930
+
2546
2931
  return [
2547
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2932
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2548
2933
  ];
2549
2934
  };
2550
2935
 
2936
+ const generateLabel = (scope, decl) => {
2937
+ scope.labels ??= new Map();
2938
+
2939
+ const name = decl.label.name;
2940
+ scope.labels.set(name, depth.length);
2941
+
2942
+ return generate(scope, decl.body);
2943
+ };
2944
+
2551
2945
  const generateThrow = (scope, decl) => {
2552
2946
  scope.throws = true;
2553
2947
 
@@ -2580,7 +2974,7 @@ const generateThrow = (scope, decl) => {
2580
2974
  };
2581
2975
 
2582
2976
  const generateTry = (scope, decl) => {
2583
- if (decl.finalizer) return todo('try finally not implemented yet');
2977
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2584
2978
 
2585
2979
  const out = [];
2586
2980
 
@@ -2611,7 +3005,7 @@ const generateAssignPat = (scope, decl) => {
2611
3005
  // TODO
2612
3006
  // if identifier declared, use that
2613
3007
  // else, use default (right)
2614
- return todo('assignment pattern (optional arg)');
3008
+ return todo(scope, 'assignment pattern (optional arg)');
2615
3009
  };
2616
3010
 
2617
3011
  let pages = new Map();
@@ -2690,16 +3084,20 @@ const getAllocType = itemType => {
2690
3084
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2691
3085
  const out = [];
2692
3086
 
3087
+ scope.arrays ??= new Map();
3088
+
2693
3089
  let firstAssign = false;
2694
- if (!arrays.has(name) || name === '$undeclared') {
3090
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2695
3091
  firstAssign = true;
2696
3092
 
2697
3093
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2698
3094
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2699
- arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3095
+
3096
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3097
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2700
3098
  }
2701
3099
 
2702
- const pointer = arrays.get(name);
3100
+ const pointer = scope.arrays.get(name);
2703
3101
 
2704
3102
  const useRawElements = !!decl.rawElements;
2705
3103
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2707,22 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2707
3105
  const valtype = itemTypeToValtype[itemType];
2708
3106
  const length = elements.length;
2709
3107
 
2710
- if (firstAssign && useRawElements) {
2711
- let bytes = compileBytes(length, 'i32');
3108
+ if (firstAssign && useRawElements && !Prefs.noData) {
3109
+ // if length is 0 memory/data will just be 0000... anyway
3110
+ if (length !== 0) {
3111
+ let bytes = compileBytes(length, 'i32');
2712
3112
 
2713
- if (!initEmpty) for (let i = 0; i < length; i++) {
2714
- if (elements[i] == null) continue;
3113
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3114
+ if (elements[i] == null) continue;
2715
3115
 
2716
- bytes.push(...compileBytes(elements[i], itemType));
2717
- }
3116
+ bytes.push(...compileBytes(elements[i], itemType));
3117
+ }
2718
3118
 
2719
- const ind = data.push({
2720
- offset: pointer,
2721
- bytes
2722
- }) - 1;
3119
+ const ind = data.push({
3120
+ offset: pointer,
3121
+ bytes
3122
+ }) - 1;
2723
3123
 
2724
- scope.data ??= [];
2725
- scope.data.push(ind);
3124
+ scope.data ??= [];
3125
+ scope.data.push(ind);
3126
+ }
2726
3127
 
2727
3128
  // local value as pointer
2728
3129
  out.push(...number(pointer));
@@ -2782,20 +3183,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2782
3183
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2783
3184
  };
2784
3185
 
2785
- let arrays = new Map();
2786
3186
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2787
3187
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2788
3188
  };
2789
3189
 
2790
3190
  export const generateMember = (scope, decl, _global, _name) => {
2791
3191
  const name = decl.object.name;
2792
- const pointer = arrays.get(name);
3192
+ const pointer = scope.arrays?.get(name);
2793
3193
 
2794
- const aotPointer = pointer != null;
3194
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2795
3195
 
2796
3196
  // hack: .length
2797
3197
  if (decl.property.name === 'length') {
2798
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3198
+ const func = funcs.find(x => x.name === name);
3199
+ if (func) {
3200
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3201
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3202
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3203
+ }
3204
+
3205
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3206
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3207
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3208
+
2799
3209
  return [
2800
3210
  ...(aotPointer ? number(0, Valtype.i32) : [
2801
3211
  ...generate(scope, decl.object),
@@ -2839,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2839
3249
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2840
3250
 
2841
3251
  ...number(TYPES.number, Valtype.i32),
2842
- setLastType(scope)
3252
+ ...setLastType(scope)
2843
3253
  ],
2844
3254
 
2845
3255
  [TYPES.string]: [
@@ -2871,7 +3281,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2871
3281
  ...number(newPointer),
2872
3282
 
2873
3283
  ...number(TYPES.string, Valtype.i32),
2874
- setLastType(scope)
3284
+ ...setLastType(scope)
2875
3285
  ],
2876
3286
  [TYPES._bytestring]: [
2877
3287
  // setup new/out array
@@ -2890,19 +3300,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2890
3300
  ]),
2891
3301
 
2892
3302
  // load current string ind {arg}
2893
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3303
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2894
3304
 
2895
3305
  // store to new string ind 0
2896
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3306
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2897
3307
 
2898
3308
  // return new string (page)
2899
3309
  ...number(newPointer),
2900
3310
 
2901
3311
  ...number(TYPES._bytestring, Valtype.i32),
2902
- setLastType(scope)
3312
+ ...setLastType(scope)
2903
3313
  ],
2904
3314
 
2905
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3315
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2906
3316
  });
2907
3317
  };
2908
3318
 
@@ -2912,28 +3322,36 @@ const objectHack = node => {
2912
3322
  if (!node) return node;
2913
3323
 
2914
3324
  if (node.type === 'MemberExpression') {
2915
- if (node.computed || node.optional) return node;
3325
+ const out = (() => {
3326
+ if (node.computed || node.optional) return;
2916
3327
 
2917
- let objectName = node.object.name;
3328
+ let objectName = node.object.name;
2918
3329
 
2919
- // if object is not identifier or another member exp, give up
2920
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3330
+ // if object is not identifier or another member exp, give up
3331
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3332
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2921
3333
 
2922
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3334
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2923
3335
 
2924
- // if .length, give up (hack within a hack!)
2925
- if (node.property.name === 'length') return node;
3336
+ // if .length, give up (hack within a hack!)
3337
+ if (node.property.name === 'length') {
3338
+ node.object = objectHack(node.object);
3339
+ return;
3340
+ }
2926
3341
 
2927
- // no object name, give up
2928
- if (!objectName) return node;
3342
+ // no object name, give up
3343
+ if (!objectName) return;
2929
3344
 
2930
- const name = '__' + objectName + '_' + node.property.name;
2931
- if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3345
+ const name = '__' + objectName + '_' + node.property.name;
3346
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2932
3347
 
2933
- return {
2934
- type: 'Identifier',
2935
- name
2936
- };
3348
+ return {
3349
+ type: 'Identifier',
3350
+ name
3351
+ };
3352
+ })();
3353
+
3354
+ if (out) return out;
2937
3355
  }
2938
3356
 
2939
3357
  for (const x in node) {
@@ -2947,8 +3365,8 @@ const objectHack = node => {
2947
3365
  };
2948
3366
 
2949
3367
  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');
3368
+ if (decl.async) return todo(scope, 'async functions are not supported');
3369
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2952
3370
 
2953
3371
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2954
3372
  const params = decl.params ?? [];
@@ -2964,6 +3382,11 @@ const generateFunc = (scope, decl) => {
2964
3382
  name
2965
3383
  };
2966
3384
 
3385
+ if (typedInput && decl.returnType) {
3386
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3387
+ innerScope.returns = [ valtypeBinary ];
3388
+ }
3389
+
2967
3390
  for (let i = 0; i < params.length; i++) {
2968
3391
  allocVar(innerScope, params[i].name, false);
2969
3392
 
@@ -2990,6 +3413,8 @@ const generateFunc = (scope, decl) => {
2990
3413
  };
2991
3414
  funcIndex[name] = func.index;
2992
3415
 
3416
+ if (name === 'main') func.gotLastType = true;
3417
+
2993
3418
  // quick hack fixes
2994
3419
  for (const inst of wasm) {
2995
3420
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3024,16 +3449,6 @@ const generateCode = (scope, decl) => {
3024
3449
  };
3025
3450
 
3026
3451
  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
3452
  Array: {
3038
3453
  generate: (scope, decl, global, name) => {
3039
3454
  // new Array(i0, i1, ...)
@@ -3051,7 +3466,7 @@ const internalConstrs = {
3051
3466
 
3052
3467
  // todo: check in wasm instead of here
3053
3468
  const literalValue = arg.value ?? 0;
3054
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3469
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3055
3470
 
3056
3471
  return [
3057
3472
  ...number(0, Valtype.i32),
@@ -3062,7 +3477,8 @@ const internalConstrs = {
3062
3477
  ...number(pointer)
3063
3478
  ];
3064
3479
  },
3065
- type: TYPES._array
3480
+ type: TYPES._array,
3481
+ length: 1
3066
3482
  },
3067
3483
 
3068
3484
  __Array_of: {
@@ -3074,7 +3490,131 @@ const internalConstrs = {
3074
3490
  }, global, name);
3075
3491
  },
3076
3492
  type: TYPES._array,
3493
+ notConstr: true,
3494
+ length: 0
3495
+ },
3496
+
3497
+ __Porffor_fastOr: {
3498
+ generate: (scope, decl) => {
3499
+ const out = [];
3500
+
3501
+ for (let i = 0; i < decl.arguments.length; i++) {
3502
+ out.push(
3503
+ ...generate(scope, decl.arguments[i]),
3504
+ Opcodes.i32_to_u,
3505
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3506
+ );
3507
+ }
3508
+
3509
+ out.push(Opcodes.i32_from_u);
3510
+
3511
+ return out;
3512
+ },
3513
+ type: TYPES.boolean,
3514
+ notConstr: true
3515
+ },
3516
+
3517
+ __Porffor_fastAnd: {
3518
+ generate: (scope, decl) => {
3519
+ const out = [];
3520
+
3521
+ for (let i = 0; i < decl.arguments.length; i++) {
3522
+ out.push(
3523
+ ...generate(scope, decl.arguments[i]),
3524
+ Opcodes.i32_to_u,
3525
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3526
+ );
3527
+ }
3528
+
3529
+ out.push(Opcodes.i32_from_u);
3530
+
3531
+ return out;
3532
+ },
3533
+ type: TYPES.boolean,
3077
3534
  notConstr: true
3535
+ },
3536
+
3537
+ Boolean: {
3538
+ generate: (scope, decl) => {
3539
+ // todo: boolean object when used as constructor
3540
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3541
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3542
+ },
3543
+ type: TYPES.boolean,
3544
+ length: 1
3545
+ },
3546
+
3547
+ __Math_max: {
3548
+ generate: (scope, decl) => {
3549
+ const out = [
3550
+ ...number(-Infinity)
3551
+ ];
3552
+
3553
+ for (let i = 0; i < decl.arguments.length; i++) {
3554
+ out.push(
3555
+ ...generate(scope, decl.arguments[i]),
3556
+ [ Opcodes.f64_max ]
3557
+ );
3558
+ }
3559
+
3560
+ return out;
3561
+ },
3562
+ type: TYPES.number,
3563
+ notConstr: true,
3564
+ length: 2
3565
+ },
3566
+
3567
+ __Math_min: {
3568
+ generate: (scope, decl) => {
3569
+ const out = [
3570
+ ...number(Infinity)
3571
+ ];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ [ Opcodes.f64_min ]
3577
+ );
3578
+ }
3579
+
3580
+ return out;
3581
+ },
3582
+ type: TYPES.number,
3583
+ notConstr: true,
3584
+ length: 2
3585
+ },
3586
+
3587
+ __console_log: {
3588
+ generate: (scope, decl) => {
3589
+ const out = [];
3590
+
3591
+ for (let i = 0; i < decl.arguments.length; i++) {
3592
+ out.push(
3593
+ ...generateCall(scope, {
3594
+ callee: {
3595
+ type: 'Identifier',
3596
+ name: '__Porffor_print'
3597
+ },
3598
+ arguments: [ decl.arguments[i] ]
3599
+ }),
3600
+
3601
+ // print space
3602
+ ...number(32),
3603
+ [ Opcodes.call, importedFuncs.printChar ]
3604
+ );
3605
+ }
3606
+
3607
+ // print newline
3608
+ out.push(
3609
+ ...number(10),
3610
+ [ Opcodes.call, importedFuncs.printChar ]
3611
+ );
3612
+
3613
+ return out;
3614
+ },
3615
+ type: TYPES.undefined,
3616
+ notConstr: true,
3617
+ length: 0
3078
3618
  }
3079
3619
  };
3080
3620
 
@@ -3103,7 +3643,6 @@ export default program => {
3103
3643
  funcs = [];
3104
3644
  funcIndex = {};
3105
3645
  depth = [];
3106
- arrays = new Map();
3107
3646
  pages = new Map();
3108
3647
  data = [];
3109
3648
  currentFuncIndex = importedFuncs.length;
@@ -3117,6 +3656,10 @@ export default program => {
3117
3656
 
3118
3657
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3119
3658
 
3659
+ globalThis.pageSize = PageSize;
3660
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3661
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3662
+
3120
3663
  // set generic opcodes for current valtype
3121
3664
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3122
3665
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3125,10 +3668,10 @@ export default program => {
3125
3668
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3126
3669
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3127
3670
 
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];
3671
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3672
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3673
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3674
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3132
3675
 
3133
3676
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3134
3677
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3141,10 +3684,6 @@ export default program => {
3141
3684
 
3142
3685
  program.id = { name: 'main' };
3143
3686
 
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
3687
  const scope = {
3149
3688
  locals: {},
3150
3689
  localInd: 0
@@ -3155,7 +3694,7 @@ export default program => {
3155
3694
  body: program.body
3156
3695
  };
3157
3696
 
3158
- if (Prefs.astLog) console.log(program.body.body);
3697
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3159
3698
 
3160
3699
  generateFunc(scope, program);
3161
3700
 
@@ -3172,7 +3711,11 @@ export default program => {
3172
3711
  }
3173
3712
 
3174
3713
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3175
- main.returns = [];
3714
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3715
+ main.wasm.splice(main.wasm.length - 1, 1);
3716
+ } else {
3717
+ main.returns = [];
3718
+ }
3176
3719
  }
3177
3720
 
3178
3721
  if (lastInst[0] === Opcodes.call) {