porffor 0.2.0-1afe9b8 → 0.2.0-219912f

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 (52) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +123 -84
  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 +317 -72
  7. package/compiler/{sections.js → assemble.js} +63 -15
  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 +151 -0
  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 +42 -0
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +457 -254
  21. package/compiler/{codeGen.js → codegen.js} +929 -340
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +1262 -0
  25. package/compiler/index.js +36 -34
  26. package/compiler/log.js +6 -3
  27. package/compiler/opt.js +50 -36
  28. package/compiler/parse.js +35 -27
  29. package/compiler/precompile.js +123 -0
  30. package/compiler/prefs.js +26 -0
  31. package/compiler/prototype.js +13 -28
  32. package/compiler/types.js +37 -0
  33. package/compiler/wasmSpec.js +28 -8
  34. package/compiler/wrap.js +51 -46
  35. package/package.json +9 -5
  36. package/porf +4 -0
  37. package/rhemyn/compile.js +5 -3
  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 +49 -10
  43. package/runner/profiler.js +102 -0
  44. package/runner/repl.js +40 -7
  45. package/runner/sizes.js +37 -37
  46. package/compiler/builtins/base64.js +0 -92
  47. package/r.js +0 -4
  48. package/runner/info.js +0 -89
  49. package/runner/profile.js +0 -46
  50. package/runner/results.json +0 -1
  51. package/runner/transform.js +0 -15
  52. package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,35 +25,37 @@ const debug = str => {
23
25
  const logChar = n => {
24
26
  code.push(...number(n));
25
27
 
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
28
29
  };
29
30
 
30
31
  for (let i = 0; i < str.length; i++) {
31
32
  logChar(str.charCodeAt(i));
32
33
  }
33
34
 
34
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
35
36
 
36
37
  return code;
37
38
  };
38
39
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
45
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
46
50
 
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
53
53
 
54
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
55
59
  };
56
60
 
57
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
108
  return generateUnary(scope, decl);
105
109
 
106
110
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
112
 
109
113
  case 'IfStatement':
110
114
  return generateIf(scope, decl);
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
119
  case 'WhileStatement':
116
120
  return generateWhile(scope, decl);
117
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
118
125
  case 'ForOfStatement':
119
126
  return generateForOf(scope, decl);
120
127
 
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
131
  case 'ContinueStatement':
125
132
  return generateContinue(scope, decl);
126
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
127
137
  case 'EmptyStatement':
128
138
  return generateEmpty(scope, decl);
129
139
 
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
147
  return generateTry(scope, decl);
138
148
 
139
149
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
141
151
  return [];
142
152
 
143
153
  case 'ArrayExpression':
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
151
161
  const funcsBefore = funcs.length;
152
162
  generate(scope, decl.declaration);
153
163
 
154
- 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');
155
171
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
158
174
 
159
175
  return [];
160
176
 
161
177
  case 'TaggedTemplateExpression': {
162
178
  const funcs = {
163
- asm: str => {
179
+ __Porffor_wasm: str => {
164
180
  let out = [];
165
181
 
166
182
  for (const line of str.split('\n')) {
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
184
  if (asm[0] === '') continue; // blank
169
185
 
170
186
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- 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] };
173
189
  continue;
174
190
  }
175
191
 
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
195
  }
180
196
 
181
197
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
183
199
  // todo: add to store/load offset insts
184
200
  continue;
185
201
  }
@@ -188,30 +204,65 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
188
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
205
 
190
206
  if (!Array.isArray(inst)) inst = [ inst ];
191
- 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
+ });
192
212
 
193
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
214
  }
195
215
 
196
216
  return out;
197
- }
198
- }
217
+ },
218
+
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
199
221
 
200
- const name = decl.tag.name;
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
229
+
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
236
+
237
+ const func = decl.tag.name;
201
238
  // hack for inline asm
202
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
203
254
 
204
- const str = decl.quasi.quasis[0].value.raw;
205
- return funcs[name](str);
255
+ return funcs[func](str);
206
256
  }
207
257
 
208
258
  default:
209
- if (decl.type.startsWith('TS')) {
210
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
211
262
  return [];
212
263
  }
213
264
 
214
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
215
266
  }
216
267
  };
217
268
 
@@ -239,7 +290,7 @@ const lookupName = (scope, _name) => {
239
290
  return [ undefined, undefined ];
240
291
  };
241
292
 
242
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
243
294
  ...generateThrow(scope, {
244
295
  argument: {
245
296
  type: 'NewExpression',
@@ -263,7 +314,10 @@ const generateIdent = (scope, decl) => {
263
314
 
264
315
  if (Object.hasOwn(builtinVars, name)) {
265
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
266
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
267
321
  }
268
322
 
269
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -271,6 +325,11 @@ const generateIdent = (scope, decl) => {
271
325
  return number(1);
272
326
  }
273
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
274
333
  if (local?.idx === undefined) {
275
334
  // no local var with name
276
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -301,14 +360,18 @@ const generateReturn = (scope, decl) => {
301
360
  // just bare "return"
302
361
  return [
303
362
  ...number(UNDEFINED), // "undefined" if func returns
304
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
305
366
  [ Opcodes.return ]
306
367
  ];
307
368
  }
308
369
 
309
370
  return [
310
371
  ...generate(scope, decl.argument),
311
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
312
375
  [ Opcodes.return ]
313
376
  ];
314
377
  };
@@ -322,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
322
385
  return idx;
323
386
  };
324
387
 
325
- 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);
326
390
 
327
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
328
392
  const checks = {
@@ -331,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
331
395
  '??': nullish
332
396
  };
333
397
 
334
- 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);
335
399
 
336
400
  // generic structure for {a} OP {b}
337
401
  // -->
@@ -339,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
339
403
 
340
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
341
405
  // (like if we are in an if condition - very common)
342
- const leftIsInt = isIntOp(left[left.length - 1]);
343
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
344
408
 
345
409
  const canInt = leftIsInt && rightIsInt;
346
410
 
@@ -357,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
357
421
  ...right,
358
422
  // note type
359
423
  ...rightType,
360
- setLastType(scope),
424
+ ...setLastType(scope),
361
425
  [ Opcodes.else ],
362
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
363
427
  // note type
364
428
  ...leftType,
365
- setLastType(scope),
429
+ ...setLastType(scope),
366
430
  [ Opcodes.end ],
367
431
  Opcodes.i32_from
368
432
  ];
@@ -376,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
376
440
  ...right,
377
441
  // note type
378
442
  ...rightType,
379
- setLastType(scope),
443
+ ...setLastType(scope),
380
444
  [ Opcodes.else ],
381
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
382
446
  // note type
383
447
  ...leftType,
384
- setLastType(scope),
448
+ ...setLastType(scope),
385
449
  [ Opcodes.end ]
386
450
  ];
387
451
  };
388
452
 
389
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
390
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
391
455
  // todo: convert left and right to strings if not
392
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -396,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
396
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
397
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
398
462
 
399
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
400
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
401
-
402
463
  if (assign) {
403
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
404
465
 
405
466
  return [
406
467
  // setup right
@@ -425,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
425
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
426
487
 
427
488
  // copy right
428
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
429
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
430
491
 
431
492
  [ Opcodes.local_get, leftLength ],
432
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
433
494
  [ Opcodes.i32_mul ],
434
495
  [ Opcodes.i32_add ],
435
496
 
@@ -438,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
499
  ...number(ValtypeSize.i32, Valtype.i32),
439
500
  [ Opcodes.i32_add ],
440
501
 
441
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
442
503
  [ Opcodes.local_get, rightLength ],
443
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
444
505
  [ Opcodes.i32_mul ],
445
506
 
446
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -498,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
498
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
499
560
 
500
561
  // copy right
501
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
502
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
503
564
 
504
565
  [ Opcodes.local_get, leftLength ],
505
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
506
567
  [ Opcodes.i32_mul ],
507
568
  [ Opcodes.i32_add ],
508
569
 
@@ -511,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
572
  ...number(ValtypeSize.i32, Valtype.i32),
512
573
  [ Opcodes.i32_add ],
513
574
 
514
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
515
576
  [ Opcodes.local_get, rightLength ],
516
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
517
578
  [ Opcodes.i32_mul ],
518
579
 
519
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -523,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
523
584
  ];
524
585
  };
525
586
 
526
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
527
588
  // todo: this should be rewritten into a func
528
589
  // todo: convert left and right to strings if not
529
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -532,7 +593,6 @@ const compareStrings = (scope, left, right) => {
532
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
533
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
534
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
535
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
536
596
 
537
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
538
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -560,7 +620,6 @@ const compareStrings = (scope, left, right) => {
560
620
 
561
621
  [ Opcodes.local_get, rightPointer ],
562
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
563
- [ Opcodes.local_tee, rightLength ],
564
623
 
565
624
  // fast path: check leftLength != rightLength
566
625
  [ Opcodes.i32_ne ],
@@ -575,11 +634,13 @@ const compareStrings = (scope, left, right) => {
575
634
  ...number(0, Valtype.i32),
576
635
  [ Opcodes.local_set, index ],
577
636
 
578
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
579
638
  // we do this instead of having to do mul/div each iter for perf™
580
639
  [ Opcodes.local_get, leftLength ],
581
- ...number(ValtypeSize.i16, Valtype.i32),
582
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
583
644
  [ Opcodes.local_set, indexEnd ],
584
645
 
585
646
  // iterate over each char and check if eq
@@ -589,13 +650,17 @@ const compareStrings = (scope, left, right) => {
589
650
  [ Opcodes.local_get, index ],
590
651
  [ Opcodes.local_get, leftPointer ],
591
652
  [ Opcodes.i32_add ],
592
- [ 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 ],
593
656
 
594
657
  // fetch right
595
658
  [ Opcodes.local_get, index ],
596
659
  [ Opcodes.local_get, rightPointer ],
597
660
  [ Opcodes.i32_add ],
598
- [ 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 ],
599
664
 
600
665
  // not equal, "return" false
601
666
  [ Opcodes.i32_ne ],
@@ -604,13 +669,13 @@ const compareStrings = (scope, left, right) => {
604
669
  [ Opcodes.br, 2 ],
605
670
  [ Opcodes.end ],
606
671
 
607
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
608
673
  [ Opcodes.local_get, index ],
609
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
610
675
  [ Opcodes.i32_add ],
611
676
  [ Opcodes.local_tee, index ],
612
677
 
613
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
614
679
  [ Opcodes.local_get, indexEnd ],
615
680
  [ Opcodes.i32_ne ],
616
681
  [ Opcodes.br_if, 0 ],
@@ -631,16 +696,18 @@ const compareStrings = (scope, left, right) => {
631
696
  };
632
697
 
633
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
634
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
635
700
  ...wasm,
636
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
637
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
638
704
 
639
- 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);
640
707
 
641
708
  const def = [
642
709
  // if value != 0
643
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
644
711
 
645
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
646
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -652,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
652
719
 
653
720
  return [
654
721
  ...wasm,
655
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
656
723
 
657
724
  ...typeSwitch(scope, type, {
658
725
  // [TYPES.number]: def,
@@ -661,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
661
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
662
729
  ],
663
730
  [TYPES.string]: [
664
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
665
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
666
733
 
667
734
  // get length
@@ -673,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
673
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
674
741
  ],
675
742
  [TYPES._bytestring]: [ // duplicate of string
676
- [ Opcodes.local_get, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
677
744
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
678
745
 
679
746
  // get length
@@ -687,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
687
754
  };
688
755
 
689
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
690
- 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
+
691
760
  return [
692
761
  ...wasm,
693
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
694
763
 
695
764
  ...typeSwitch(scope, type, {
696
765
  [TYPES._array]: [
@@ -698,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
698
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
699
768
  ],
700
769
  [TYPES.string]: [
701
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
702
771
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
703
772
 
704
773
  // get length
@@ -709,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
710
779
  ],
711
780
  [TYPES._bytestring]: [ // duplicate of string
712
- [ Opcodes.local_get, tmp ],
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
714
783
 
715
784
  // get length
@@ -721,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
721
790
  ],
722
791
  default: [
723
792
  // if value == 0
724
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
725
794
 
726
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
727
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -731,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
731
800
  };
732
801
 
733
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
734
- 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
+
735
806
  return [
736
807
  ...wasm,
737
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
738
809
 
739
810
  ...typeSwitch(scope, type, {
740
811
  [TYPES.undefined]: [
@@ -743,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
743
814
  ],
744
815
  [TYPES.object]: [
745
816
  // object, null if == 0
746
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
747
818
 
748
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
749
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -772,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
772
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
773
844
  }
774
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
775
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
776
850
  const strictOp = op === '===' || op === '!==';
777
851
 
778
852
  const startOut = [], endOut = [];
779
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
780
854
 
781
855
  // if strict (in)equal check types match
782
856
  if (strictOp) {
@@ -821,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
821
895
  // todo: if equality op and an operand is undefined, return false
822
896
  // todo: niche null hell with 0
823
897
 
824
- // if (leftType === TYPES.string || rightType === TYPES.string) {
825
- // if (op === '+') {
826
- // // string concat (a + b)
827
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
828
- // }
829
-
830
- // // not an equality op, NaN
831
- // if (!eqOp) return finalise(number(NaN));
832
-
833
- // // else leave bool ops
834
- // // todo: convert string to number if string and number/bool
835
- // // todo: string (>|>=|<|<=) string
836
-
837
- // // string comparison
838
- // if (op === '===' || op === '==') {
839
- // return finalise(compareStrings(scope, left, right));
840
- // }
841
-
842
- // if (op === '!==' || op === '!=') {
843
- // return finalise([
844
- // ...compareStrings(scope, left, right),
845
- // [ Opcodes.i32_eqz ]
846
- // ]);
847
- // }
848
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
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
+ }
849
951
 
850
952
  let ops = operatorOpcode[valtype][op];
851
953
 
@@ -855,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
855
957
  includeBuiltin(scope, builtinName);
856
958
  const idx = funcIndex[builtinName];
857
959
 
858
- return finalise([
960
+ return finalize([
859
961
  ...left,
860
962
  ...right,
861
963
  [ Opcodes.call, idx ]
862
964
  ]);
863
965
  }
864
966
 
865
- 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);
866
968
 
867
969
  if (!Array.isArray(ops)) ops = [ ops ];
868
970
  ops = [ ops ];
869
971
 
870
972
  let tmpLeft, tmpRight;
871
973
  // if equal op, check if strings for compareStrings
872
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
873
- const knownLeft = knownType(scope, leftType);
874
- const knownRight = knownType(scope, rightType);
875
-
876
- // todo: intelligent partial skip later
877
- // if neither known are string, stop this madness
878
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
879
- return;
880
- }
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
881
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
882
979
  tmpLeft = localTmp(scope, '__tmpop_left');
883
980
  tmpRight = localTmp(scope, '__tmpop_right');
884
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)
885
1023
  ops.unshift(...stringOnly([
886
1024
  // if left is string
887
1025
  ...leftType,
@@ -893,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
893
1031
  ...number(TYPES.string, Valtype.i32),
894
1032
  [ Opcodes.i32_eq ],
895
1033
 
896
- // if either are true
897
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
898
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 ],
899
1041
 
900
- // todo: convert non-strings to strings, for now fail immediately if one is not
901
- // if left is not string
1042
+ // if left is bytestring
902
1043
  ...leftType,
903
- ...number(TYPES.string, Valtype.i32),
904
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
905
1046
 
906
- // if right is not string
1047
+ // if right is bytestring
907
1048
  ...rightType,
908
- ...number(TYPES.string, Valtype.i32),
909
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
910
1051
 
911
- // if either are true
912
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
913
1054
  [ Opcodes.if, Blocktype.void ],
914
- ...number(0, Valtype.i32),
915
- [ Opcodes.br, 1 ],
916
- [ Opcodes.end ],
917
-
918
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
919
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
920
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
921
1057
  [ Opcodes.br, 1 ],
922
1058
  [ Opcodes.end ],
@@ -928,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
928
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
929
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
930
1066
  // }
931
- })();
1067
+ }
932
1068
 
933
- return finalise([
1069
+ return finalize([
934
1070
  ...left,
935
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
936
1072
  ...right,
@@ -947,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
947
1083
  return out;
948
1084
  };
949
1085
 
950
- 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 = [] }) => {
951
1102
  const existing = funcs.find(x => x.name === name);
952
1103
  if (existing) return existing;
953
1104
 
@@ -959,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
959
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
960
1111
  }
961
1112
 
962
- if (typeof wasm === 'function') {
963
- const scope = {
964
- name,
965
- params,
966
- locals,
967
- returns,
968
- localInd: allLocals.length,
969
- };
970
-
971
- 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);
972
1117
  }
973
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
974
1121
  let baseGlobalIdx, i = 0;
975
1122
  for (const type of globalTypes) {
976
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -993,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
993
1140
  params,
994
1141
  locals,
995
1142
  returns,
996
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
997
1144
  wasm,
998
1145
  internal: true,
999
1146
  index: currentFuncIndex++
@@ -1016,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1016
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1017
1164
  };
1018
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1019
1167
  // T = JS type, V = value/pointer
1020
1168
  // 0bTTT
1021
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1039,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1039
1187
  // 4: internal type
1040
1188
  // 5: pointer
1041
1189
 
1042
- const TYPES = {
1043
- number: 0x00,
1044
- boolean: 0x01,
1045
- string: 0x02,
1046
- undefined: 0x03,
1047
- object: 0x04,
1048
- function: 0x05,
1049
- symbol: 0x06,
1050
- bigint: 0x07,
1051
-
1052
- // these are not "typeof" types but tracked internally
1053
- _array: 0x10,
1054
- _regexp: 0x11,
1055
- _bytestring: 0x12
1056
- };
1057
-
1058
- const TYPE_NAMES = {
1059
- [TYPES.number]: 'Number',
1060
- [TYPES.boolean]: 'Boolean',
1061
- [TYPES.string]: 'String',
1062
- [TYPES.undefined]: 'undefined',
1063
- [TYPES.object]: 'Object',
1064
- [TYPES.function]: 'Function',
1065
- [TYPES.symbol]: 'Symbol',
1066
- [TYPES.bigint]: 'BigInt',
1067
-
1068
- [TYPES._array]: 'Array',
1069
- [TYPES._regexp]: 'RegExp',
1070
- [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;
1071
1195
  };
1072
1196
 
1073
1197
  const getType = (scope, _name) => {
1074
1198
  const name = mapName(_name);
1075
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1076
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1077
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1078
1207
 
1079
1208
  let type = TYPES.undefined;
1080
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1081
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1082
1211
 
1083
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1084
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1085
1213
 
1086
1214
  return number(type, Valtype.i32);
1087
1215
  };
@@ -1104,15 +1232,16 @@ const setType = (scope, _name, type) => {
1104
1232
  ];
1105
1233
 
1106
1234
  // throw new Error('could not find var');
1235
+ return [];
1107
1236
  };
1108
1237
 
1109
1238
  const getLastType = scope => {
1110
1239
  scope.gotLastType = true;
1111
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1112
1241
  };
1113
1242
 
1114
1243
  const setLastType = scope => {
1115
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1116
1245
  };
1117
1246
 
1118
1247
  const getNodeType = (scope, node) => {
@@ -1137,13 +1266,25 @@ const getNodeType = (scope, node) => {
1137
1266
  const name = node.callee.name;
1138
1267
  if (!name) {
1139
1268
  // iife
1140
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1141
1270
 
1142
1271
  // presume
1143
1272
  // todo: warn here?
1144
1273
  return TYPES.number;
1145
1274
  }
1146
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
+
1147
1288
  const func = funcs.find(x => x.name === name);
1148
1289
 
1149
1290
  if (func) {
@@ -1151,7 +1292,7 @@ const getNodeType = (scope, node) => {
1151
1292
  if (func.returnType) return func.returnType;
1152
1293
  }
1153
1294
 
1154
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1155
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1156
1297
 
1157
1298
  // check if this is a prototype function
@@ -1166,7 +1307,12 @@ const getNodeType = (scope, node) => {
1166
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1167
1308
  }
1168
1309
 
1169
- 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);
1170
1316
 
1171
1317
  // presume
1172
1318
  // todo: warn here?
@@ -1214,6 +1360,15 @@ const getNodeType = (scope, node) => {
1214
1360
 
1215
1361
  if (node.type === 'BinaryExpression') {
1216
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1217
1372
  return TYPES.number;
1218
1373
 
1219
1374
  // todo: string concat types
@@ -1238,7 +1393,7 @@ const getNodeType = (scope, node) => {
1238
1393
  if (node.operator === '!') return TYPES.boolean;
1239
1394
  if (node.operator === 'void') return TYPES.undefined;
1240
1395
  if (node.operator === 'delete') return TYPES.boolean;
1241
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1242
1397
 
1243
1398
  return TYPES.number;
1244
1399
  }
@@ -1249,15 +1404,21 @@ const getNodeType = (scope, node) => {
1249
1404
 
1250
1405
  // ts hack
1251
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;
1252
1408
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1253
1409
 
1254
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1255
1411
 
1256
1412
  // presume
1257
1413
  return TYPES.number;
1258
1414
  }
1259
1415
 
1260
- 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);
1261
1422
 
1262
1423
  // presume
1263
1424
  // todo: warn here?
@@ -1290,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1290
1451
  return makeString(scope, decl.value, global, name);
1291
1452
 
1292
1453
  default:
1293
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1294
1455
  }
1295
1456
  };
1296
1457
 
@@ -1299,6 +1460,8 @@ const countLeftover = wasm => {
1299
1460
 
1300
1461
  for (let i = 0; i < wasm.length; i++) {
1301
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1302
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1303
1466
  if (inst[0] === Opcodes.if) count--;
1304
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1307,18 +1470,25 @@ const countLeftover = wasm => {
1307
1470
  if (inst[0] === Opcodes.end) depth--;
1308
1471
 
1309
1472
  if (depth === 0)
1310
- 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--;
1311
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)) {}
1312
- 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++;
1313
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1314
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1315
1478
  else if (inst[0] === Opcodes.return) count = 0;
1316
1479
  else if (inst[0] === Opcodes.call) {
1317
1480
  let func = funcs.find(x => x.index === inst[1]);
1318
- if (func) {
1319
- count -= func.params.length;
1320
- } else count--;
1321
- 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
+ }
1322
1492
  } else count--;
1323
1493
 
1324
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1410,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1410
1580
  name = func.name;
1411
1581
  }
1412
1582
 
1413
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1414
1584
  // literal eval hack
1415
- const code = decl.arguments[0].value;
1416
- 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
+ }
1417
1598
 
1418
1599
  const out = generate(scope, {
1419
1600
  type: 'BlockStatement',
@@ -1427,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1427
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1428
1609
  out.push(
1429
1610
  ...getNodeType(scope, finalStatement),
1430
- setLastType(scope)
1611
+ ...setLastType(scope)
1431
1612
  );
1432
1613
  } else if (countLeftover(out) === 0) {
1433
1614
  out.push(...number(UNDEFINED));
1434
1615
  out.push(
1435
1616
  ...number(TYPES.undefined, Valtype.i32),
1436
- setLastType(scope)
1617
+ ...setLastType(scope)
1437
1618
  );
1438
1619
  }
1439
1620
 
@@ -1455,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1455
1636
 
1456
1637
  target = { ...decl.callee };
1457
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1458
1642
  }
1459
1643
 
1460
1644
  // literal.func()
@@ -1477,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1477
1661
  Opcodes.i32_from_u,
1478
1662
 
1479
1663
  ...number(TYPES.boolean, Valtype.i32),
1480
- setLastType(scope)
1664
+ ...setLastType(scope)
1481
1665
  ];
1482
1666
  }
1483
1667
 
@@ -1502,12 +1686,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1502
1686
  // }
1503
1687
 
1504
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
+
1505
1709
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1506
1710
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1507
1711
  return acc;
1508
1712
  }, {});
1509
1713
 
1510
- // no prototype function candidates, ignore
1511
1714
  if (Object.keys(protoCands).length > 0) {
1512
1715
  // use local for cached i32 length as commonly used
1513
1716
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1525,7 +1728,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1525
1728
 
1526
1729
  let allOptUnused = true;
1527
1730
  let lengthI32CacheUsed = false;
1528
- const protoBC = {};
1529
1731
  for (const x in protoCands) {
1530
1732
  const protoFunc = protoCands[x];
1531
1733
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1533,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1533
1735
  ...RTArrayUtil.getLength(getPointer),
1534
1736
 
1535
1737
  ...number(TYPES.number, Valtype.i32),
1536
- setLastType(scope)
1738
+ ...setLastType(scope)
1537
1739
  ];
1538
1740
  continue;
1539
1741
  }
@@ -1570,7 +1772,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1570
1772
  ...protoOut,
1571
1773
 
1572
1774
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1573
- setLastType(scope),
1775
+ ...setLastType(scope),
1574
1776
  [ Opcodes.end ]
1575
1777
  ];
1576
1778
  }
@@ -1596,10 +1798,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1596
1798
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1597
1799
  ];
1598
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
+ }
1599
1810
  }
1600
1811
 
1601
1812
  // TODO: only allows callee as literal
1602
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1813
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1603
1814
 
1604
1815
  let idx = funcIndex[name] ?? importedFuncs[name];
1605
1816
  if (idx === undefined && builtinFuncs[name]) {
@@ -1632,16 +1843,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1632
1843
  idx = -1;
1633
1844
  }
1634
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
+
1635
1895
  if (idx === undefined) {
1636
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1637
- 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);
1638
1898
  }
1639
1899
 
1640
1900
  const func = funcs.find(x => x.index === idx);
1641
1901
 
1642
1902
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1643
1903
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1644
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1904
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1645
1905
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1646
1906
 
1647
1907
  let args = decl.arguments;
@@ -1658,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1658
1918
  if (func && func.throws) scope.throws = true;
1659
1919
 
1660
1920
  let out = [];
1661
- for (const arg of args) {
1921
+ for (let i = 0; i < args.length; i++) {
1922
+ const arg = args[i];
1662
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
+
1663
1933
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1664
1934
  }
1665
1935
 
1666
1936
  out.push([ Opcodes.call, idx ]);
1667
1937
 
1668
- if (!typedReturn) {
1938
+ if (!typedReturns) {
1669
1939
  // let type;
1670
1940
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1671
1941
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1675,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1675
1945
  // ...number(type, Valtype.i32),
1676
1946
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1677
1947
  // );
1678
- } 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
+ }
1679
1953
 
1680
1954
  return out;
1681
1955
  };
@@ -1683,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1683
1957
  const generateNew = (scope, decl, _global, _name) => {
1684
1958
  // hack: basically treat this as a normal call for builtins for now
1685
1959
  const name = mapName(decl.callee.name);
1960
+
1686
1961
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1687
- 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)})`);
1688
1975
 
1689
1976
  return generateCall(scope, decl, _global, _name);
1690
1977
  };
@@ -1801,14 +2088,14 @@ const brTable = (input, bc, returns) => {
1801
2088
  };
1802
2089
 
1803
2090
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1804
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2091
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1805
2092
 
1806
2093
  const known = knownType(scope, type);
1807
2094
  if (known != null) {
1808
2095
  return bc[known] ?? bc.default;
1809
2096
  }
1810
2097
 
1811
- if (process.argv.includes('-typeswitch-use-brtable'))
2098
+ if (Prefs.typeswitchUseBrtable)
1812
2099
  return brTable(type, bc, returns);
1813
2100
 
1814
2101
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1818,8 +2105,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1818
2105
  [ Opcodes.block, returns ]
1819
2106
  ];
1820
2107
 
1821
- // todo: use br_table?
1822
-
1823
2108
  for (const x in bc) {
1824
2109
  if (x === 'default') continue;
1825
2110
 
@@ -1843,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1843
2128
  return out;
1844
2129
  };
1845
2130
 
1846
- const allocVar = (scope, name, global = false) => {
2131
+ const allocVar = (scope, name, global = false, type = true) => {
1847
2132
  const target = global ? globals : scope.locals;
1848
2133
 
1849
2134
  // already declared
@@ -1857,8 +2142,10 @@ const allocVar = (scope, name, global = false) => {
1857
2142
  let idx = global ? globalInd++ : scope.localInd++;
1858
2143
  target[name] = { idx, type: valtypeBinary };
1859
2144
 
1860
- let typeIdx = global ? globalInd++ : scope.localInd++;
1861
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2145
+ if (type) {
2146
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2147
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2148
+ }
1862
2149
 
1863
2150
  return idx;
1864
2151
  };
@@ -1873,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1873
2160
  };
1874
2161
 
1875
2162
  const typeAnnoToPorfType = x => {
1876
- if (TYPES[x]) return TYPES[x];
1877
- 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()];
1878
2166
 
1879
2167
  switch (x) {
1880
2168
  case 'i32':
2169
+ case 'i64':
2170
+ case 'f64':
1881
2171
  return TYPES.number;
1882
2172
  }
1883
2173
 
@@ -1888,7 +2178,7 @@ const extractTypeAnnotation = decl => {
1888
2178
  let a = decl;
1889
2179
  while (a.typeAnnotation) a = a.typeAnnotation;
1890
2180
 
1891
- let type, elementType;
2181
+ let type = null, elementType = null;
1892
2182
  if (a.typeName) {
1893
2183
  type = a.typeName.name;
1894
2184
  } else if (a.type.endsWith('Keyword')) {
@@ -1901,7 +2191,7 @@ const extractTypeAnnotation = decl => {
1901
2191
  const typeName = type;
1902
2192
  type = typeAnnoToPorfType(type);
1903
2193
 
1904
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2194
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1905
2195
 
1906
2196
  // if (decl.name) console.log(decl.name, { type, elementType });
1907
2197
 
@@ -1919,7 +2209,7 @@ const generateVar = (scope, decl) => {
1919
2209
  for (const x of decl.declarations) {
1920
2210
  const name = mapName(x.id.name);
1921
2211
 
1922
- if (!name) return todo('destructuring is not supported yet');
2212
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1923
2213
 
1924
2214
  if (x.init && isFuncType(x.init.type)) {
1925
2215
  // hack for let a = function () { ... }
@@ -1936,9 +2226,10 @@ const generateVar = (scope, decl) => {
1936
2226
  continue; // always ignore
1937
2227
  }
1938
2228
 
1939
- let idx = allocVar(scope, name, global);
2229
+ const typed = typedInput && x.id.typeAnnotation;
2230
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1940
2231
 
1941
- if (typedInput && x.id.typeAnnotation) {
2232
+ if (typed) {
1942
2233
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1943
2234
  }
1944
2235
 
@@ -1956,7 +2247,8 @@ const generateVar = (scope, decl) => {
1956
2247
  return out;
1957
2248
  };
1958
2249
 
1959
- const generateAssign = (scope, decl) => {
2250
+ // todo: optimize this func for valueUnused
2251
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1960
2252
  const { type, name } = decl.left;
1961
2253
 
1962
2254
  if (type === 'ObjectPattern') {
@@ -1974,9 +2266,9 @@ const generateAssign = (scope, decl) => {
1974
2266
  // hack: .length setter
1975
2267
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1976
2268
  const name = decl.left.object.name;
1977
- const pointer = arrays.get(name);
2269
+ const pointer = scope.arrays?.get(name);
1978
2270
 
1979
- const aotPointer = pointer != null;
2271
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1980
2272
 
1981
2273
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1982
2274
 
@@ -2001,9 +2293,9 @@ const generateAssign = (scope, decl) => {
2001
2293
  // arr[i]
2002
2294
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2003
2295
  const name = decl.left.object.name;
2004
- const pointer = arrays.get(name);
2296
+ const pointer = scope.arrays?.get(name);
2005
2297
 
2006
- const aotPointer = pointer != null;
2298
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2007
2299
 
2008
2300
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2009
2301
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2059,7 +2351,7 @@ const generateAssign = (scope, decl) => {
2059
2351
  ];
2060
2352
  }
2061
2353
 
2062
- if (!name) return todo('destructuring is not supported yet');
2354
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2063
2355
 
2064
2356
  const [ local, isGlobal ] = lookupName(scope, name);
2065
2357
 
@@ -2107,9 +2399,7 @@ const generateAssign = (scope, decl) => {
2107
2399
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2108
2400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2109
2401
 
2110
- getLastType(scope),
2111
- // hack: type is idx+1
2112
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2402
+ ...setType(scope, name, getLastType(scope))
2113
2403
  ];
2114
2404
  }
2115
2405
 
@@ -2120,9 +2410,7 @@ const generateAssign = (scope, decl) => {
2120
2410
 
2121
2411
  // todo: string concat types
2122
2412
 
2123
- // hack: type is idx+1
2124
- ...number(TYPES.number, Valtype.i32),
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2413
+ ...setType(scope, name, TYPES.number)
2126
2414
  ];
2127
2415
  };
2128
2416
 
@@ -2168,7 +2456,7 @@ const generateUnary = (scope, decl) => {
2168
2456
  return out;
2169
2457
  }
2170
2458
 
2171
- case 'delete':
2459
+ case 'delete': {
2172
2460
  let toReturn = true, toGenerate = true;
2173
2461
 
2174
2462
  if (decl.argument.type === 'Identifier') {
@@ -2190,9 +2478,26 @@ const generateUnary = (scope, decl) => {
2190
2478
 
2191
2479
  out.push(...number(toReturn ? 1 : 0));
2192
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
+ }
2193
2496
 
2194
- case 'typeof':
2195
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2497
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2498
+ disposeLeftover(out);
2499
+
2500
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2196
2501
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2197
2502
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2198
2503
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2203,27 +2508,30 @@ const generateUnary = (scope, decl) => {
2203
2508
 
2204
2509
  // object and internal types
2205
2510
  default: makeString(scope, 'object', false, '#typeof_result'),
2206
- });
2511
+ }));
2512
+
2513
+ return out;
2514
+ }
2207
2515
 
2208
2516
  default:
2209
- return todo(`unary operator ${decl.operator} not implemented yet`);
2517
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2210
2518
  }
2211
2519
  };
2212
2520
 
2213
- const generateUpdate = (scope, decl) => {
2521
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2214
2522
  const { name } = decl.argument;
2215
2523
 
2216
2524
  const [ local, isGlobal ] = lookupName(scope, name);
2217
2525
 
2218
2526
  if (local === undefined) {
2219
- return todo(`update expression with undefined variable`);
2527
+ return todo(scope, `update expression with undefined variable`, true);
2220
2528
  }
2221
2529
 
2222
2530
  const idx = local.idx;
2223
2531
  const out = [];
2224
2532
 
2225
2533
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2226
- 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 ]);
2227
2535
 
2228
2536
  switch (decl.operator) {
2229
2537
  case '++':
@@ -2236,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
2236
2544
  }
2237
2545
 
2238
2546
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2239
- 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 ]);
2240
2548
 
2241
2549
  return out;
2242
2550
  };
@@ -2276,7 +2584,7 @@ const generateConditional = (scope, decl) => {
2276
2584
  // note type
2277
2585
  out.push(
2278
2586
  ...getNodeType(scope, decl.consequent),
2279
- setLastType(scope)
2587
+ ...setLastType(scope)
2280
2588
  );
2281
2589
 
2282
2590
  out.push([ Opcodes.else ]);
@@ -2285,7 +2593,7 @@ const generateConditional = (scope, decl) => {
2285
2593
  // note type
2286
2594
  out.push(
2287
2595
  ...getNodeType(scope, decl.alternate),
2288
- setLastType(scope)
2596
+ ...setLastType(scope)
2289
2597
  );
2290
2598
 
2291
2599
  out.push([ Opcodes.end ]);
@@ -2299,7 +2607,7 @@ const generateFor = (scope, decl) => {
2299
2607
  const out = [];
2300
2608
 
2301
2609
  if (decl.init) {
2302
- out.push(...generate(scope, decl.init));
2610
+ out.push(...generate(scope, decl.init, false, undefined, true));
2303
2611
  disposeLeftover(out);
2304
2612
  }
2305
2613
 
@@ -2317,7 +2625,7 @@ const generateFor = (scope, decl) => {
2317
2625
  out.push(...generate(scope, decl.body));
2318
2626
  out.push([ Opcodes.end ]);
2319
2627
 
2320
- if (decl.update) out.push(...generate(scope, decl.update));
2628
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2321
2629
 
2322
2630
  out.push([ Opcodes.br, 1 ]);
2323
2631
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2345,6 +2653,36 @@ const generateWhile = (scope, decl) => {
2345
2653
  return out;
2346
2654
  };
2347
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
+
2348
2686
  const generateForOf = (scope, decl) => {
2349
2687
  const out = [];
2350
2688
 
@@ -2381,7 +2719,10 @@ const generateForOf = (scope, decl) => {
2381
2719
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2382
2720
  }
2383
2721
 
2722
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2723
+
2384
2724
  const [ local, isGlobal ] = lookupName(scope, leftName);
2725
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2385
2726
 
2386
2727
  depth.push('block');
2387
2728
  depth.push('block');
@@ -2390,6 +2731,7 @@ const generateForOf = (scope, decl) => {
2390
2731
  // hack: this is naughty and will break things!
2391
2732
  let newOut = number(0, Valtype.f64), newPointer = -1;
2392
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?
2393
2735
  0, [ newOut, newPointer ] = makeArray(scope, {
2394
2736
  rawElements: new Array(1)
2395
2737
  }, isGlobal, leftName, true, 'i16');
@@ -2481,6 +2823,56 @@ const generateForOf = (scope, decl) => {
2481
2823
  [ Opcodes.end ],
2482
2824
  [ Opcodes.end ]
2483
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
+ ],
2484
2876
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2485
2877
  }, Blocktype.void));
2486
2878
 
@@ -2491,28 +2883,65 @@ const generateForOf = (scope, decl) => {
2491
2883
  return out;
2492
2884
  };
2493
2885
 
2886
+ // find the nearest loop in depth map by type
2494
2887
  const getNearestLoop = () => {
2495
2888
  for (let i = depth.length - 1; i >= 0; i--) {
2496
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2889
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2497
2890
  }
2498
2891
 
2499
2892
  return -1;
2500
2893
  };
2501
2894
 
2502
2895
  const generateBreak = (scope, decl) => {
2503
- 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
+
2504
2911
  return [
2505
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2912
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2506
2913
  ];
2507
2914
  };
2508
2915
 
2509
2916
  const generateContinue = (scope, decl) => {
2510
- 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
+
2511
2931
  return [
2512
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2932
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2513
2933
  ];
2514
2934
  };
2515
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
+
2516
2945
  const generateThrow = (scope, decl) => {
2517
2946
  scope.throws = true;
2518
2947
 
@@ -2533,6 +2962,9 @@ const generateThrow = (scope, decl) => {
2533
2962
  let exceptId = exceptions.push({ constructor, message }) - 1;
2534
2963
  let tagIdx = tags[0].idx;
2535
2964
 
2965
+ scope.exceptions ??= [];
2966
+ scope.exceptions.push(exceptId);
2967
+
2536
2968
  // todo: write a description of how this works lol
2537
2969
 
2538
2970
  return [
@@ -2542,7 +2974,7 @@ const generateThrow = (scope, decl) => {
2542
2974
  };
2543
2975
 
2544
2976
  const generateTry = (scope, decl) => {
2545
- if (decl.finalizer) return todo('try finally not implemented yet');
2977
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2546
2978
 
2547
2979
  const out = [];
2548
2980
 
@@ -2573,11 +3005,11 @@ const generateAssignPat = (scope, decl) => {
2573
3005
  // TODO
2574
3006
  // if identifier declared, use that
2575
3007
  // else, use default (right)
2576
- return todo('assignment pattern (optional arg)');
3008
+ return todo(scope, 'assignment pattern (optional arg)');
2577
3009
  };
2578
3010
 
2579
3011
  let pages = new Map();
2580
- const allocPage = (reason, type) => {
3012
+ const allocPage = (scope, reason, type) => {
2581
3013
  if (pages.has(reason)) return pages.get(reason).ind;
2582
3014
 
2583
3015
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2588,16 +3020,20 @@ const allocPage = (reason, type) => {
2588
3020
  const ind = pages.size;
2589
3021
  pages.set(reason, { ind, type });
2590
3022
 
2591
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3023
+ scope.pages ??= new Map();
3024
+ scope.pages.set(reason, { ind, type });
3025
+
3026
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2592
3027
 
2593
3028
  return ind;
2594
3029
  };
2595
3030
 
3031
+ // todo: add scope.pages
2596
3032
  const freePage = reason => {
2597
3033
  const { ind } = pages.get(reason);
2598
3034
  pages.delete(reason);
2599
3035
 
2600
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3036
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2601
3037
 
2602
3038
  return ind;
2603
3039
  };
@@ -2623,15 +3059,14 @@ const StoreOps = {
2623
3059
 
2624
3060
  let data = [];
2625
3061
 
2626
- const compileBytes = (val, itemType, signed = true) => {
3062
+ const compileBytes = (val, itemType) => {
2627
3063
  // todo: this is a mess and needs confirming / ????
2628
3064
  switch (itemType) {
2629
3065
  case 'i8': return [ val % 256 ];
2630
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2631
-
2632
- case 'i32':
2633
- case 'i64':
2634
- return enforceFourBytes(signedLEB128(val));
3066
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3067
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3068
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3069
+ // todo: i64
2635
3070
 
2636
3071
  case 'f64': return ieee754_binary64(val);
2637
3072
  }
@@ -2649,16 +3084,20 @@ const getAllocType = itemType => {
2649
3084
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2650
3085
  const out = [];
2651
3086
 
3087
+ scope.arrays ??= new Map();
3088
+
2652
3089
  let firstAssign = false;
2653
- if (!arrays.has(name) || name === '$undeclared') {
3090
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2654
3091
  firstAssign = true;
2655
3092
 
2656
3093
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2657
3094
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2658
- arrays.set(name, allocPage(`${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);
2659
3098
  }
2660
3099
 
2661
- const pointer = arrays.get(name);
3100
+ const pointer = scope.arrays.get(name);
2662
3101
 
2663
3102
  const useRawElements = !!decl.rawElements;
2664
3103
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2666,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
3105
  const valtype = itemTypeToValtype[itemType];
2667
3106
  const length = elements.length;
2668
3107
 
2669
- if (firstAssign && useRawElements) {
2670
- 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');
2671
3112
 
2672
- if (!initEmpty) for (let i = 0; i < length; i++) {
2673
- if (elements[i] == null) continue;
3113
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3114
+ if (elements[i] == null) continue;
2674
3115
 
2675
- bytes.push(...compileBytes(elements[i], itemType));
2676
- }
3116
+ bytes.push(...compileBytes(elements[i], itemType));
3117
+ }
2677
3118
 
2678
- data.push({
2679
- offset: pointer,
2680
- bytes
2681
- });
3119
+ const ind = data.push({
3120
+ offset: pointer,
3121
+ bytes
3122
+ }) - 1;
3123
+
3124
+ scope.data ??= [];
3125
+ scope.data.push(ind);
3126
+ }
2682
3127
 
2683
3128
  // local value as pointer
2684
3129
  out.push(...number(pointer));
@@ -2712,7 +3157,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2712
3157
  };
2713
3158
 
2714
3159
  const byteStringable = str => {
2715
- if (!process.argv.includes('-bytestring')) return false;
3160
+ if (!Prefs.bytestring) return false;
2716
3161
 
2717
3162
  for (let i = 0; i < str.length; i++) {
2718
3163
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2721,9 +3166,9 @@ const byteStringable = str => {
2721
3166
  return true;
2722
3167
  };
2723
3168
 
2724
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3169
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2725
3170
  const rawElements = new Array(str.length);
2726
- let byteStringable = process.argv.includes('-bytestring');
3171
+ let byteStringable = Prefs.bytestring;
2727
3172
  for (let i = 0; i < str.length; i++) {
2728
3173
  const c = str.charCodeAt(i);
2729
3174
  rawElements[i] = c;
@@ -2731,25 +3176,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2731
3176
  if (byteStringable && c > 0xFF) byteStringable = false;
2732
3177
  }
2733
3178
 
3179
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3180
+
2734
3181
  return makeArray(scope, {
2735
3182
  rawElements
2736
3183
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2737
3184
  };
2738
3185
 
2739
- let arrays = new Map();
2740
3186
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2741
3187
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2742
3188
  };
2743
3189
 
2744
3190
  export const generateMember = (scope, decl, _global, _name) => {
2745
3191
  const name = decl.object.name;
2746
- const pointer = arrays.get(name);
3192
+ const pointer = scope.arrays?.get(name);
2747
3193
 
2748
- const aotPointer = pointer != null;
3194
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2749
3195
 
2750
3196
  // hack: .length
2751
3197
  if (decl.property.name === 'length') {
2752
- // 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
+
2753
3209
  return [
2754
3210
  ...(aotPointer ? number(0, Valtype.i32) : [
2755
3211
  ...generate(scope, decl.object),
@@ -2793,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2793
3249
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2794
3250
 
2795
3251
  ...number(TYPES.number, Valtype.i32),
2796
- setLastType(scope)
3252
+ ...setLastType(scope)
2797
3253
  ],
2798
3254
 
2799
3255
  [TYPES.string]: [
@@ -2825,7 +3281,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2825
3281
  ...number(newPointer),
2826
3282
 
2827
3283
  ...number(TYPES.string, Valtype.i32),
2828
- setLastType(scope)
3284
+ ...setLastType(scope)
2829
3285
  ],
2830
3286
  [TYPES._bytestring]: [
2831
3287
  // setup new/out array
@@ -2844,19 +3300,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2844
3300
  ]),
2845
3301
 
2846
3302
  // load current string ind {arg}
2847
- [ 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) ],
2848
3304
 
2849
3305
  // store to new string ind 0
2850
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3306
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2851
3307
 
2852
3308
  // return new string (page)
2853
3309
  ...number(newPointer),
2854
3310
 
2855
3311
  ...number(TYPES._bytestring, Valtype.i32),
2856
- setLastType(scope)
3312
+ ...setLastType(scope)
2857
3313
  ],
2858
3314
 
2859
- 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)
2860
3316
  });
2861
3317
  };
2862
3318
 
@@ -2866,28 +3322,36 @@ const objectHack = node => {
2866
3322
  if (!node) return node;
2867
3323
 
2868
3324
  if (node.type === 'MemberExpression') {
2869
- if (node.computed || node.optional) return node;
3325
+ const out = (() => {
3326
+ if (node.computed || node.optional) return;
3327
+
3328
+ let objectName = node.object.name;
2870
3329
 
2871
- let objectName = node.object.name;
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;
2872
3333
 
2873
- // if object is not identifier or another member exp, give up
2874
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3334
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2875
3335
 
2876
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3336
+ // if .length, give up (hack within a hack!)
3337
+ if (node.property.name === 'length') {
3338
+ node.object = objectHack(node.object);
3339
+ return;
3340
+ }
2877
3341
 
2878
- // if .length, give up (hack within a hack!)
2879
- if (node.property.name === 'length') return node;
3342
+ // no object name, give up
3343
+ if (!objectName) return;
2880
3344
 
2881
- // no object name, give up
2882
- if (!objectName) return node;
3345
+ const name = '__' + objectName + '_' + node.property.name;
3346
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2883
3347
 
2884
- const name = '__' + objectName + '_' + node.property.name;
2885
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3348
+ return {
3349
+ type: 'Identifier',
3350
+ name
3351
+ };
3352
+ })();
2886
3353
 
2887
- return {
2888
- type: 'Identifier',
2889
- name
2890
- };
3354
+ if (out) return out;
2891
3355
  }
2892
3356
 
2893
3357
  for (const x in node) {
@@ -2901,8 +3365,8 @@ const objectHack = node => {
2901
3365
  };
2902
3366
 
2903
3367
  const generateFunc = (scope, decl) => {
2904
- if (decl.async) return todo('async functions are not supported');
2905
- 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');
2906
3370
 
2907
3371
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2908
3372
  const params = decl.params ?? [];
@@ -2918,6 +3382,11 @@ const generateFunc = (scope, decl) => {
2918
3382
  name
2919
3383
  };
2920
3384
 
3385
+ if (typedInput && decl.returnType) {
3386
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3387
+ innerScope.returns = [ valtypeBinary ];
3388
+ }
3389
+
2921
3390
  for (let i = 0; i < params.length; i++) {
2922
3391
  allocVar(innerScope, params[i].name, false);
2923
3392
 
@@ -2944,6 +3413,8 @@ const generateFunc = (scope, decl) => {
2944
3413
  };
2945
3414
  funcIndex[name] = func.index;
2946
3415
 
3416
+ if (name === 'main') func.gotLastType = true;
3417
+
2947
3418
  // quick hack fixes
2948
3419
  for (const inst of wasm) {
2949
3420
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2978,16 +3449,6 @@ const generateCode = (scope, decl) => {
2978
3449
  };
2979
3450
 
2980
3451
  const internalConstrs = {
2981
- Boolean: {
2982
- generate: (scope, decl) => {
2983
- if (decl.arguments.length === 0) return number(0);
2984
-
2985
- // should generate/run all args
2986
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
2987
- },
2988
- type: TYPES.boolean
2989
- },
2990
-
2991
3452
  Array: {
2992
3453
  generate: (scope, decl, global, name) => {
2993
3454
  // new Array(i0, i1, ...)
@@ -3005,7 +3466,7 @@ const internalConstrs = {
3005
3466
 
3006
3467
  // todo: check in wasm instead of here
3007
3468
  const literalValue = arg.value ?? 0;
3008
- 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);
3009
3470
 
3010
3471
  return [
3011
3472
  ...number(0, Valtype.i32),
@@ -3016,7 +3477,8 @@ const internalConstrs = {
3016
3477
  ...number(pointer)
3017
3478
  ];
3018
3479
  },
3019
- type: TYPES._array
3480
+ type: TYPES._array,
3481
+ length: 1
3020
3482
  },
3021
3483
 
3022
3484
  __Array_of: {
@@ -3028,7 +3490,131 @@ const internalConstrs = {
3028
3490
  }, global, name);
3029
3491
  },
3030
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,
3031
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
3032
3618
  }
3033
3619
  };
3034
3620
 
@@ -3057,7 +3643,6 @@ export default program => {
3057
3643
  funcs = [];
3058
3644
  funcIndex = {};
3059
3645
  depth = [];
3060
- arrays = new Map();
3061
3646
  pages = new Map();
3062
3647
  data = [];
3063
3648
  currentFuncIndex = importedFuncs.length;
@@ -3071,6 +3656,10 @@ export default program => {
3071
3656
 
3072
3657
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3073
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
+
3074
3663
  // set generic opcodes for current valtype
3075
3664
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3076
3665
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3079,10 +3668,10 @@ export default program => {
3079
3668
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3080
3669
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3081
3670
 
3082
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3083
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3084
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3085
- 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];
3086
3675
 
3087
3676
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3088
3677
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3095,10 +3684,6 @@ export default program => {
3095
3684
 
3096
3685
  program.id = { name: 'main' };
3097
3686
 
3098
- globalThis.pageSize = PageSize;
3099
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3100
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3101
-
3102
3687
  const scope = {
3103
3688
  locals: {},
3104
3689
  localInd: 0
@@ -3109,7 +3694,7 @@ export default program => {
3109
3694
  body: program.body
3110
3695
  };
3111
3696
 
3112
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3697
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3113
3698
 
3114
3699
  generateFunc(scope, program);
3115
3700
 
@@ -3126,7 +3711,11 @@ export default program => {
3126
3711
  }
3127
3712
 
3128
3713
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3129
- 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
+ }
3130
3719
  }
3131
3720
 
3132
3721
  if (lastInst[0] === Opcodes.call) {