porffor 0.2.0-1afe9b8 → 0.2.0-22b3092

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.vscode/launch.json +18 -0
  2. package/LICENSE +20 -20
  3. package/README.md +121 -83
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +317 -72
  8. package/compiler/{sections.js → assemble.js} +62 -14
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +151 -0
  13. package/compiler/builtins/crypto.ts +120 -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 +445 -249
  21. package/compiler/{codeGen.js → codegen.js} +853 -334
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +695 -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 +27 -8
  34. package/compiler/wrap.js +46 -44
  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/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
  47. package/compiler/builtins/base64.js +0 -92
  48. package/r.js +0 -4
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/util/enum.js +0 -20
@@ -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
213
  out.push([ ...inst, ...immediates ]);
194
214
  }
195
215
 
196
216
  return out;
197
- }
198
- }
217
+ },
218
+
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
221
+
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),
199
229
 
200
- const name = decl.tag.name;
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;
203
243
 
204
- const str = decl.quasi.quasis[0].value.raw;
205
- return funcs[name](str);
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
+ }
254
+
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,7 +1266,7 @@ 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?
@@ -1151,7 +1280,7 @@ const getNodeType = (scope, node) => {
1151
1280
  if (func.returnType) return func.returnType;
1152
1281
  }
1153
1282
 
1154
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1283
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1155
1284
  if (internalConstrs[name]) return internalConstrs[name].type;
1156
1285
 
1157
1286
  // check if this is a prototype function
@@ -1166,7 +1295,12 @@ const getNodeType = (scope, node) => {
1166
1295
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1167
1296
  }
1168
1297
 
1169
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1298
+ if (name.startsWith('__Porffor_wasm_')) {
1299
+ // todo: return undefined for non-returning ops
1300
+ return TYPES.number;
1301
+ }
1302
+
1303
+ if (scope.locals['#last_type']) return getLastType(scope);
1170
1304
 
1171
1305
  // presume
1172
1306
  // todo: warn here?
@@ -1214,6 +1348,15 @@ const getNodeType = (scope, node) => {
1214
1348
 
1215
1349
  if (node.type === 'BinaryExpression') {
1216
1350
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1351
+ if (node.operator !== '+') return TYPES.number;
1352
+
1353
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1354
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1355
+
1356
+ // todo: this should be dynamic but for now only static
1357
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1358
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1359
+
1217
1360
  return TYPES.number;
1218
1361
 
1219
1362
  // todo: string concat types
@@ -1238,7 +1381,7 @@ const getNodeType = (scope, node) => {
1238
1381
  if (node.operator === '!') return TYPES.boolean;
1239
1382
  if (node.operator === 'void') return TYPES.undefined;
1240
1383
  if (node.operator === 'delete') return TYPES.boolean;
1241
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1384
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1242
1385
 
1243
1386
  return TYPES.number;
1244
1387
  }
@@ -1249,15 +1392,21 @@ const getNodeType = (scope, node) => {
1249
1392
 
1250
1393
  // ts hack
1251
1394
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1395
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1252
1396
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1253
1397
 
1254
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1398
+ if (scope.locals['#last_type']) return getLastType(scope);
1255
1399
 
1256
1400
  // presume
1257
1401
  return TYPES.number;
1258
1402
  }
1259
1403
 
1260
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1404
+ if (node.type === 'TaggedTemplateExpression') {
1405
+ // hack
1406
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1407
+ }
1408
+
1409
+ if (scope.locals['#last_type']) return getLastType(scope);
1261
1410
 
1262
1411
  // presume
1263
1412
  // todo: warn here?
@@ -1290,7 +1439,7 @@ const generateLiteral = (scope, decl, global, name) => {
1290
1439
  return makeString(scope, decl.value, global, name);
1291
1440
 
1292
1441
  default:
1293
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1442
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1294
1443
  }
1295
1444
  };
1296
1445
 
@@ -1299,6 +1448,8 @@ const countLeftover = wasm => {
1299
1448
 
1300
1449
  for (let i = 0; i < wasm.length; i++) {
1301
1450
  const inst = wasm[i];
1451
+ if (inst[0] == null) continue;
1452
+
1302
1453
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1303
1454
  if (inst[0] === Opcodes.if) count--;
1304
1455
  if (inst[1] !== Blocktype.void) count++;
@@ -1307,7 +1458,7 @@ const countLeftover = wasm => {
1307
1458
  if (inst[0] === Opcodes.end) depth--;
1308
1459
 
1309
1460
  if (depth === 0)
1310
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1461
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1311
1462
  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
1463
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1313
1464
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
@@ -1410,10 +1561,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1410
1561
  name = func.name;
1411
1562
  }
1412
1563
 
1413
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1564
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1414
1565
  // literal eval hack
1415
- const code = decl.arguments[0].value;
1416
- const parsed = parse(code, []);
1566
+ const code = decl.arguments[0]?.value ?? '';
1567
+
1568
+ let parsed;
1569
+ try {
1570
+ parsed = parse(code, []);
1571
+ } catch (e) {
1572
+ if (e.name === 'SyntaxError') {
1573
+ // throw syntax errors of evals at runtime instead
1574
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1575
+ }
1576
+
1577
+ throw e;
1578
+ }
1417
1579
 
1418
1580
  const out = generate(scope, {
1419
1581
  type: 'BlockStatement',
@@ -1427,13 +1589,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1427
1589
  const finalStatement = parsed.body[parsed.body.length - 1];
1428
1590
  out.push(
1429
1591
  ...getNodeType(scope, finalStatement),
1430
- setLastType(scope)
1592
+ ...setLastType(scope)
1431
1593
  );
1432
1594
  } else if (countLeftover(out) === 0) {
1433
1595
  out.push(...number(UNDEFINED));
1434
1596
  out.push(
1435
1597
  ...number(TYPES.undefined, Valtype.i32),
1436
- setLastType(scope)
1598
+ ...setLastType(scope)
1437
1599
  );
1438
1600
  }
1439
1601
 
@@ -1455,6 +1617,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1455
1617
 
1456
1618
  target = { ...decl.callee };
1457
1619
  target.name = spl.slice(0, -1).join('_');
1620
+
1621
+ // failed to lookup name, abort
1622
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1458
1623
  }
1459
1624
 
1460
1625
  // literal.func()
@@ -1477,7 +1642,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1477
1642
  Opcodes.i32_from_u,
1478
1643
 
1479
1644
  ...number(TYPES.boolean, Valtype.i32),
1480
- setLastType(scope)
1645
+ ...setLastType(scope)
1481
1646
  ];
1482
1647
  }
1483
1648
 
@@ -1502,12 +1667,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1502
1667
  // }
1503
1668
 
1504
1669
  if (protoName) {
1670
+ const protoBC = {};
1671
+
1672
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1673
+
1674
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1675
+ for (const x of builtinProtoCands) {
1676
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1677
+ if (type == null) continue;
1678
+
1679
+ protoBC[type] = generateCall(scope, {
1680
+ callee: {
1681
+ name: x
1682
+ },
1683
+ arguments: [ target, ...decl.arguments ],
1684
+ _protoInternalCall: true
1685
+ });
1686
+ }
1687
+ }
1688
+
1505
1689
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1506
1690
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1507
1691
  return acc;
1508
1692
  }, {});
1509
1693
 
1510
- // no prototype function candidates, ignore
1511
1694
  if (Object.keys(protoCands).length > 0) {
1512
1695
  // use local for cached i32 length as commonly used
1513
1696
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1525,7 +1708,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1525
1708
 
1526
1709
  let allOptUnused = true;
1527
1710
  let lengthI32CacheUsed = false;
1528
- const protoBC = {};
1529
1711
  for (const x in protoCands) {
1530
1712
  const protoFunc = protoCands[x];
1531
1713
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1533,7 +1715,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1533
1715
  ...RTArrayUtil.getLength(getPointer),
1534
1716
 
1535
1717
  ...number(TYPES.number, Valtype.i32),
1536
- setLastType(scope)
1718
+ ...setLastType(scope)
1537
1719
  ];
1538
1720
  continue;
1539
1721
  }
@@ -1570,7 +1752,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1570
1752
  ...protoOut,
1571
1753
 
1572
1754
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1573
- setLastType(scope),
1755
+ ...setLastType(scope),
1574
1756
  [ Opcodes.end ]
1575
1757
  ];
1576
1758
  }
@@ -1596,10 +1778,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1596
1778
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1597
1779
  ];
1598
1780
  }
1781
+
1782
+ if (Object.keys(protoBC).length > 0) {
1783
+ return typeSwitch(scope, getNodeType(scope, target), {
1784
+ ...protoBC,
1785
+
1786
+ // TODO: error better
1787
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1788
+ }, valtypeBinary);
1789
+ }
1599
1790
  }
1600
1791
 
1601
1792
  // TODO: only allows callee as literal
1602
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1793
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1603
1794
 
1604
1795
  let idx = funcIndex[name] ?? importedFuncs[name];
1605
1796
  if (idx === undefined && builtinFuncs[name]) {
@@ -1632,16 +1823,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1632
1823
  idx = -1;
1633
1824
  }
1634
1825
 
1826
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1827
+ const wasmOps = {
1828
+ // pointer, align, offset
1829
+ i32_load: { imms: 2, args: 1, returns: 1 },
1830
+ // pointer, value, align, offset
1831
+ i32_store: { imms: 2, args: 2, returns: 0 },
1832
+ // pointer, align, offset
1833
+ i32_load8_u: { imms: 2, args: 1, returns: 1 },
1834
+ // pointer, value, align, offset
1835
+ i32_store8: { imms: 2, args: 2, returns: 0 },
1836
+ // pointer, align, offset
1837
+ i32_load16_u: { imms: 2, args: 1, returns: 1 },
1838
+ // pointer, value, align, offset
1839
+ i32_store16: { imms: 2, args: 2, returns: 0 },
1840
+
1841
+ // pointer, align, offset
1842
+ f64_load: { imms: 2, args: 1, returns: 1 },
1843
+ // pointer, value, align, offset
1844
+ f64_store: { imms: 2, args: 2, returns: 0 },
1845
+
1846
+ // value
1847
+ i32_const: { imms: 1, args: 0, returns: 1 },
1848
+
1849
+ // a, b
1850
+ i32_or: { imms: 0, args: 2, returns: 1 },
1851
+ };
1852
+
1853
+ const opName = name.slice('__Porffor_wasm_'.length);
1854
+
1855
+ if (wasmOps[opName]) {
1856
+ const op = wasmOps[opName];
1857
+
1858
+ const argOut = [];
1859
+ for (let i = 0; i < op.args; i++) argOut.push(
1860
+ ...generate(scope, decl.arguments[i]),
1861
+ Opcodes.i32_to
1862
+ );
1863
+
1864
+ // literals only
1865
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1866
+
1867
+ return [
1868
+ ...argOut,
1869
+ [ Opcodes[opName], ...imms ],
1870
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1871
+ ];
1872
+ }
1873
+ }
1874
+
1635
1875
  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`);
1876
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1877
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1638
1878
  }
1639
1879
 
1640
1880
  const func = funcs.find(x => x.index === idx);
1641
1881
 
1642
1882
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1643
1883
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1644
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1884
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1645
1885
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1646
1886
 
1647
1887
  let args = decl.arguments;
@@ -1658,14 +1898,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1658
1898
  if (func && func.throws) scope.throws = true;
1659
1899
 
1660
1900
  let out = [];
1661
- for (const arg of args) {
1901
+ for (let i = 0; i < args.length; i++) {
1902
+ const arg = args[i];
1662
1903
  out = out.concat(generate(scope, arg));
1904
+
1905
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1906
+ out.push(Opcodes.i32_to);
1907
+ }
1908
+
1909
+ if (importedFuncs[name] && name.startsWith('profile')) {
1910
+ out.push(Opcodes.i32_to);
1911
+ }
1912
+
1663
1913
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1664
1914
  }
1665
1915
 
1666
1916
  out.push([ Opcodes.call, idx ]);
1667
1917
 
1668
- if (!typedReturn) {
1918
+ if (!typedReturns) {
1669
1919
  // let type;
1670
1920
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1671
1921
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1675,7 +1925,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1675
1925
  // ...number(type, Valtype.i32),
1676
1926
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1677
1927
  // );
1678
- } else out.push(setLastType(scope));
1928
+ } else out.push(...setLastType(scope));
1929
+
1930
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1931
+ out.push(Opcodes.i32_from);
1932
+ }
1679
1933
 
1680
1934
  return out;
1681
1935
  };
@@ -1684,7 +1938,7 @@ const generateNew = (scope, decl, _global, _name) => {
1684
1938
  // hack: basically treat this as a normal call for builtins for now
1685
1939
  const name = mapName(decl.callee.name);
1686
1940
  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)})`);
1941
+ 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
1942
 
1689
1943
  return generateCall(scope, decl, _global, _name);
1690
1944
  };
@@ -1801,14 +2055,14 @@ const brTable = (input, bc, returns) => {
1801
2055
  };
1802
2056
 
1803
2057
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1804
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2058
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1805
2059
 
1806
2060
  const known = knownType(scope, type);
1807
2061
  if (known != null) {
1808
2062
  return bc[known] ?? bc.default;
1809
2063
  }
1810
2064
 
1811
- if (process.argv.includes('-typeswitch-use-brtable'))
2065
+ if (Prefs.typeswitchUseBrtable)
1812
2066
  return brTable(type, bc, returns);
1813
2067
 
1814
2068
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1818,8 +2072,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1818
2072
  [ Opcodes.block, returns ]
1819
2073
  ];
1820
2074
 
1821
- // todo: use br_table?
1822
-
1823
2075
  for (const x in bc) {
1824
2076
  if (x === 'default') continue;
1825
2077
 
@@ -1843,7 +2095,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1843
2095
  return out;
1844
2096
  };
1845
2097
 
1846
- const allocVar = (scope, name, global = false) => {
2098
+ const allocVar = (scope, name, global = false, type = true) => {
1847
2099
  const target = global ? globals : scope.locals;
1848
2100
 
1849
2101
  // already declared
@@ -1857,8 +2109,10 @@ const allocVar = (scope, name, global = false) => {
1857
2109
  let idx = global ? globalInd++ : scope.localInd++;
1858
2110
  target[name] = { idx, type: valtypeBinary };
1859
2111
 
1860
- let typeIdx = global ? globalInd++ : scope.localInd++;
1861
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2112
+ if (type) {
2113
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2114
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2115
+ }
1862
2116
 
1863
2117
  return idx;
1864
2118
  };
@@ -1873,11 +2127,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1873
2127
  };
1874
2128
 
1875
2129
  const typeAnnoToPorfType = x => {
1876
- if (TYPES[x]) return TYPES[x];
1877
- if (TYPES['_' + x]) return TYPES['_' + x];
2130
+ if (!x) return null;
2131
+ if (TYPES[x] != null) return TYPES[x];
2132
+ if (TYPES['_' + x] != null) return TYPES['_' + x];
1878
2133
 
1879
2134
  switch (x) {
1880
2135
  case 'i32':
2136
+ case 'i64':
2137
+ case 'f64':
1881
2138
  return TYPES.number;
1882
2139
  }
1883
2140
 
@@ -1888,7 +2145,7 @@ const extractTypeAnnotation = decl => {
1888
2145
  let a = decl;
1889
2146
  while (a.typeAnnotation) a = a.typeAnnotation;
1890
2147
 
1891
- let type, elementType;
2148
+ let type = null, elementType = null;
1892
2149
  if (a.typeName) {
1893
2150
  type = a.typeName.name;
1894
2151
  } else if (a.type.endsWith('Keyword')) {
@@ -1901,7 +2158,7 @@ const extractTypeAnnotation = decl => {
1901
2158
  const typeName = type;
1902
2159
  type = typeAnnoToPorfType(type);
1903
2160
 
1904
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2161
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1905
2162
 
1906
2163
  // if (decl.name) console.log(decl.name, { type, elementType });
1907
2164
 
@@ -1919,7 +2176,7 @@ const generateVar = (scope, decl) => {
1919
2176
  for (const x of decl.declarations) {
1920
2177
  const name = mapName(x.id.name);
1921
2178
 
1922
- if (!name) return todo('destructuring is not supported yet');
2179
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1923
2180
 
1924
2181
  if (x.init && isFuncType(x.init.type)) {
1925
2182
  // hack for let a = function () { ... }
@@ -1936,9 +2193,10 @@ const generateVar = (scope, decl) => {
1936
2193
  continue; // always ignore
1937
2194
  }
1938
2195
 
1939
- let idx = allocVar(scope, name, global);
2196
+ const typed = typedInput && x.id.typeAnnotation;
2197
+ let idx = allocVar(scope, name, global, !typed);
1940
2198
 
1941
- if (typedInput && x.id.typeAnnotation) {
2199
+ if (typed) {
1942
2200
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1943
2201
  }
1944
2202
 
@@ -1956,7 +2214,8 @@ const generateVar = (scope, decl) => {
1956
2214
  return out;
1957
2215
  };
1958
2216
 
1959
- const generateAssign = (scope, decl) => {
2217
+ // todo: optimize this func for valueUnused
2218
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1960
2219
  const { type, name } = decl.left;
1961
2220
 
1962
2221
  if (type === 'ObjectPattern') {
@@ -1974,9 +2233,9 @@ const generateAssign = (scope, decl) => {
1974
2233
  // hack: .length setter
1975
2234
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1976
2235
  const name = decl.left.object.name;
1977
- const pointer = arrays.get(name);
2236
+ const pointer = scope.arrays?.get(name);
1978
2237
 
1979
- const aotPointer = pointer != null;
2238
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1980
2239
 
1981
2240
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1982
2241
 
@@ -2001,9 +2260,9 @@ const generateAssign = (scope, decl) => {
2001
2260
  // arr[i]
2002
2261
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2003
2262
  const name = decl.left.object.name;
2004
- const pointer = arrays.get(name);
2263
+ const pointer = scope.arrays?.get(name);
2005
2264
 
2006
- const aotPointer = pointer != null;
2265
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2007
2266
 
2008
2267
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2009
2268
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2059,7 +2318,7 @@ const generateAssign = (scope, decl) => {
2059
2318
  ];
2060
2319
  }
2061
2320
 
2062
- if (!name) return todo('destructuring is not supported yet');
2321
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2063
2322
 
2064
2323
  const [ local, isGlobal ] = lookupName(scope, name);
2065
2324
 
@@ -2107,9 +2366,7 @@ const generateAssign = (scope, decl) => {
2107
2366
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2108
2367
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2109
2368
 
2110
- getLastType(scope),
2111
- // hack: type is idx+1
2112
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2369
+ ...setType(scope, name, getLastType(scope))
2113
2370
  ];
2114
2371
  }
2115
2372
 
@@ -2120,9 +2377,7 @@ const generateAssign = (scope, decl) => {
2120
2377
 
2121
2378
  // todo: string concat types
2122
2379
 
2123
- // hack: type is idx+1
2124
- ...number(TYPES.number, Valtype.i32),
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2380
+ ...setType(scope, name, TYPES.number)
2126
2381
  ];
2127
2382
  };
2128
2383
 
@@ -2168,7 +2423,7 @@ const generateUnary = (scope, decl) => {
2168
2423
  return out;
2169
2424
  }
2170
2425
 
2171
- case 'delete':
2426
+ case 'delete': {
2172
2427
  let toReturn = true, toGenerate = true;
2173
2428
 
2174
2429
  if (decl.argument.type === 'Identifier') {
@@ -2190,9 +2445,26 @@ const generateUnary = (scope, decl) => {
2190
2445
 
2191
2446
  out.push(...number(toReturn ? 1 : 0));
2192
2447
  return out;
2448
+ }
2449
+
2450
+ case 'typeof': {
2451
+ let overrideType, toGenerate = true;
2452
+
2453
+ if (decl.argument.type === 'Identifier') {
2454
+ const out = generateIdent(scope, decl.argument);
2455
+
2456
+ // if ReferenceError (undeclared var), ignore and return undefined
2457
+ if (out[1]) {
2458
+ // does not exist (2 ops from throw)
2459
+ overrideType = number(TYPES.undefined, Valtype.i32);
2460
+ toGenerate = false;
2461
+ }
2462
+ }
2193
2463
 
2194
- case 'typeof':
2195
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2464
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2465
+ disposeLeftover(out);
2466
+
2467
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2196
2468
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2197
2469
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2198
2470
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2203,27 +2475,30 @@ const generateUnary = (scope, decl) => {
2203
2475
 
2204
2476
  // object and internal types
2205
2477
  default: makeString(scope, 'object', false, '#typeof_result'),
2206
- });
2478
+ }));
2479
+
2480
+ return out;
2481
+ }
2207
2482
 
2208
2483
  default:
2209
- return todo(`unary operator ${decl.operator} not implemented yet`);
2484
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2210
2485
  }
2211
2486
  };
2212
2487
 
2213
- const generateUpdate = (scope, decl) => {
2488
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2214
2489
  const { name } = decl.argument;
2215
2490
 
2216
2491
  const [ local, isGlobal ] = lookupName(scope, name);
2217
2492
 
2218
2493
  if (local === undefined) {
2219
- return todo(`update expression with undefined variable`);
2494
+ return todo(scope, `update expression with undefined variable`, true);
2220
2495
  }
2221
2496
 
2222
2497
  const idx = local.idx;
2223
2498
  const out = [];
2224
2499
 
2225
2500
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2226
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2501
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2227
2502
 
2228
2503
  switch (decl.operator) {
2229
2504
  case '++':
@@ -2236,7 +2511,7 @@ const generateUpdate = (scope, decl) => {
2236
2511
  }
2237
2512
 
2238
2513
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2239
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2514
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2240
2515
 
2241
2516
  return out;
2242
2517
  };
@@ -2276,7 +2551,7 @@ const generateConditional = (scope, decl) => {
2276
2551
  // note type
2277
2552
  out.push(
2278
2553
  ...getNodeType(scope, decl.consequent),
2279
- setLastType(scope)
2554
+ ...setLastType(scope)
2280
2555
  );
2281
2556
 
2282
2557
  out.push([ Opcodes.else ]);
@@ -2285,7 +2560,7 @@ const generateConditional = (scope, decl) => {
2285
2560
  // note type
2286
2561
  out.push(
2287
2562
  ...getNodeType(scope, decl.alternate),
2288
- setLastType(scope)
2563
+ ...setLastType(scope)
2289
2564
  );
2290
2565
 
2291
2566
  out.push([ Opcodes.end ]);
@@ -2299,7 +2574,7 @@ const generateFor = (scope, decl) => {
2299
2574
  const out = [];
2300
2575
 
2301
2576
  if (decl.init) {
2302
- out.push(...generate(scope, decl.init));
2577
+ out.push(...generate(scope, decl.init, false, undefined, true));
2303
2578
  disposeLeftover(out);
2304
2579
  }
2305
2580
 
@@ -2317,7 +2592,7 @@ const generateFor = (scope, decl) => {
2317
2592
  out.push(...generate(scope, decl.body));
2318
2593
  out.push([ Opcodes.end ]);
2319
2594
 
2320
- if (decl.update) out.push(...generate(scope, decl.update));
2595
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2321
2596
 
2322
2597
  out.push([ Opcodes.br, 1 ]);
2323
2598
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2345,6 +2620,36 @@ const generateWhile = (scope, decl) => {
2345
2620
  return out;
2346
2621
  };
2347
2622
 
2623
+ const generateDoWhile = (scope, decl) => {
2624
+ const out = [];
2625
+
2626
+ out.push([ Opcodes.loop, Blocktype.void ]);
2627
+ depth.push('dowhile');
2628
+
2629
+ // block for break (includes all)
2630
+ out.push([ Opcodes.block, Blocktype.void ]);
2631
+ depth.push('block');
2632
+
2633
+ // block for continue
2634
+ // includes body but not test+loop so we can exit body at anytime
2635
+ // and still test+loop after
2636
+ out.push([ Opcodes.block, Blocktype.void ]);
2637
+ depth.push('block');
2638
+
2639
+ out.push(...generate(scope, decl.body));
2640
+
2641
+ out.push([ Opcodes.end ]);
2642
+ depth.pop();
2643
+
2644
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2645
+ out.push([ Opcodes.br_if, 1 ]);
2646
+
2647
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2648
+ depth.pop(); depth.pop();
2649
+
2650
+ return out;
2651
+ };
2652
+
2348
2653
  const generateForOf = (scope, decl) => {
2349
2654
  const out = [];
2350
2655
 
@@ -2381,7 +2686,10 @@ const generateForOf = (scope, decl) => {
2381
2686
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2382
2687
  }
2383
2688
 
2689
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2690
+
2384
2691
  const [ local, isGlobal ] = lookupName(scope, leftName);
2692
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2385
2693
 
2386
2694
  depth.push('block');
2387
2695
  depth.push('block');
@@ -2390,6 +2698,7 @@ const generateForOf = (scope, decl) => {
2390
2698
  // hack: this is naughty and will break things!
2391
2699
  let newOut = number(0, Valtype.f64), newPointer = -1;
2392
2700
  if (pages.hasAnyString) {
2701
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2393
2702
  0, [ newOut, newPointer ] = makeArray(scope, {
2394
2703
  rawElements: new Array(1)
2395
2704
  }, isGlobal, leftName, true, 'i16');
@@ -2481,6 +2790,56 @@ const generateForOf = (scope, decl) => {
2481
2790
  [ Opcodes.end ],
2482
2791
  [ Opcodes.end ]
2483
2792
  ],
2793
+ [TYPES._bytestring]: [
2794
+ ...setType(scope, leftName, TYPES._bytestring),
2795
+
2796
+ [ Opcodes.loop, Blocktype.void ],
2797
+
2798
+ // setup new/out array
2799
+ ...newOut,
2800
+ [ Opcodes.drop ],
2801
+
2802
+ ...number(0, Valtype.i32), // base 0 for store after
2803
+
2804
+ // load current string ind {arg}
2805
+ [ Opcodes.local_get, pointer ],
2806
+ [ Opcodes.local_get, counter ],
2807
+ [ Opcodes.i32_add ],
2808
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2809
+
2810
+ // store to new string ind 0
2811
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2812
+
2813
+ // return new string (page)
2814
+ ...number(newPointer),
2815
+
2816
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2817
+
2818
+ [ Opcodes.block, Blocktype.void ],
2819
+ [ Opcodes.block, Blocktype.void ],
2820
+ ...generate(scope, decl.body),
2821
+ [ Opcodes.end ],
2822
+
2823
+ // increment iter pointer
2824
+ // [ Opcodes.local_get, pointer ],
2825
+ // ...number(1, Valtype.i32),
2826
+ // [ Opcodes.i32_add ],
2827
+ // [ Opcodes.local_set, pointer ],
2828
+
2829
+ // increment counter by 1
2830
+ [ Opcodes.local_get, counter ],
2831
+ ...number(1, Valtype.i32),
2832
+ [ Opcodes.i32_add ],
2833
+ [ Opcodes.local_tee, counter ],
2834
+
2835
+ // loop if counter != length
2836
+ [ Opcodes.local_get, length ],
2837
+ [ Opcodes.i32_ne ],
2838
+ [ Opcodes.br_if, 1 ],
2839
+
2840
+ [ Opcodes.end ],
2841
+ [ Opcodes.end ]
2842
+ ],
2484
2843
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2485
2844
  }, Blocktype.void));
2486
2845
 
@@ -2491,28 +2850,65 @@ const generateForOf = (scope, decl) => {
2491
2850
  return out;
2492
2851
  };
2493
2852
 
2853
+ // find the nearest loop in depth map by type
2494
2854
  const getNearestLoop = () => {
2495
2855
  for (let i = depth.length - 1; i >= 0; i--) {
2496
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2856
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2497
2857
  }
2498
2858
 
2499
2859
  return -1;
2500
2860
  };
2501
2861
 
2502
2862
  const generateBreak = (scope, decl) => {
2503
- const nearestLoop = depth.length - getNearestLoop();
2863
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2864
+ const type = depth[target];
2865
+
2866
+ // different loop types have different branch offsets
2867
+ // as they have different wasm block/loop/if structures
2868
+ // we need to use the right offset by type to branch to the one we want
2869
+ // for a break: exit the loop without executing anything else inside it
2870
+ const offset = ({
2871
+ for: 2, // loop > if (wanted branch) > block (we are here)
2872
+ while: 2, // loop > if (wanted branch) (we are here)
2873
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2874
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2875
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2876
+ })[type];
2877
+
2504
2878
  return [
2505
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2879
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2506
2880
  ];
2507
2881
  };
2508
2882
 
2509
2883
  const generateContinue = (scope, decl) => {
2510
- const nearestLoop = depth.length - getNearestLoop();
2884
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2885
+ const type = depth[target];
2886
+
2887
+ // different loop types have different branch offsets
2888
+ // as they have different wasm block/loop/if structures
2889
+ // we need to use the right offset by type to branch to the one we want
2890
+ // for a continue: do test for the loop, and then loop depending on that success
2891
+ const offset = ({
2892
+ for: 3, // loop (wanted branch) > if > block (we are here)
2893
+ while: 1, // loop (wanted branch) > if (we are here)
2894
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2895
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2896
+ })[type];
2897
+
2511
2898
  return [
2512
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2899
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2513
2900
  ];
2514
2901
  };
2515
2902
 
2903
+ const generateLabel = (scope, decl) => {
2904
+ scope.labels ??= new Map();
2905
+
2906
+ const name = decl.label.name;
2907
+ scope.labels.set(name, depth.length);
2908
+
2909
+ return generate(scope, decl.body);
2910
+ };
2911
+
2516
2912
  const generateThrow = (scope, decl) => {
2517
2913
  scope.throws = true;
2518
2914
 
@@ -2533,6 +2929,9 @@ const generateThrow = (scope, decl) => {
2533
2929
  let exceptId = exceptions.push({ constructor, message }) - 1;
2534
2930
  let tagIdx = tags[0].idx;
2535
2931
 
2932
+ scope.exceptions ??= [];
2933
+ scope.exceptions.push(exceptId);
2934
+
2536
2935
  // todo: write a description of how this works lol
2537
2936
 
2538
2937
  return [
@@ -2542,7 +2941,7 @@ const generateThrow = (scope, decl) => {
2542
2941
  };
2543
2942
 
2544
2943
  const generateTry = (scope, decl) => {
2545
- if (decl.finalizer) return todo('try finally not implemented yet');
2944
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2546
2945
 
2547
2946
  const out = [];
2548
2947
 
@@ -2573,11 +2972,11 @@ const generateAssignPat = (scope, decl) => {
2573
2972
  // TODO
2574
2973
  // if identifier declared, use that
2575
2974
  // else, use default (right)
2576
- return todo('assignment pattern (optional arg)');
2975
+ return todo(scope, 'assignment pattern (optional arg)');
2577
2976
  };
2578
2977
 
2579
2978
  let pages = new Map();
2580
- const allocPage = (reason, type) => {
2979
+ const allocPage = (scope, reason, type) => {
2581
2980
  if (pages.has(reason)) return pages.get(reason).ind;
2582
2981
 
2583
2982
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2588,16 +2987,20 @@ const allocPage = (reason, type) => {
2588
2987
  const ind = pages.size;
2589
2988
  pages.set(reason, { ind, type });
2590
2989
 
2591
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2990
+ scope.pages ??= new Map();
2991
+ scope.pages.set(reason, { ind, type });
2992
+
2993
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2592
2994
 
2593
2995
  return ind;
2594
2996
  };
2595
2997
 
2998
+ // todo: add scope.pages
2596
2999
  const freePage = reason => {
2597
3000
  const { ind } = pages.get(reason);
2598
3001
  pages.delete(reason);
2599
3002
 
2600
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3003
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2601
3004
 
2602
3005
  return ind;
2603
3006
  };
@@ -2623,15 +3026,14 @@ const StoreOps = {
2623
3026
 
2624
3027
  let data = [];
2625
3028
 
2626
- const compileBytes = (val, itemType, signed = true) => {
3029
+ const compileBytes = (val, itemType) => {
2627
3030
  // todo: this is a mess and needs confirming / ????
2628
3031
  switch (itemType) {
2629
3032
  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));
3033
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3034
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3035
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3036
+ // todo: i64
2635
3037
 
2636
3038
  case 'f64': return ieee754_binary64(val);
2637
3039
  }
@@ -2649,16 +3051,20 @@ const getAllocType = itemType => {
2649
3051
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2650
3052
  const out = [];
2651
3053
 
3054
+ scope.arrays ??= new Map();
3055
+
2652
3056
  let firstAssign = false;
2653
- if (!arrays.has(name) || name === '$undeclared') {
3057
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2654
3058
  firstAssign = true;
2655
3059
 
2656
3060
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2657
3061
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2658
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3062
+
3063
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3064
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2659
3065
  }
2660
3066
 
2661
- const pointer = arrays.get(name);
3067
+ const pointer = scope.arrays.get(name);
2662
3068
 
2663
3069
  const useRawElements = !!decl.rawElements;
2664
3070
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2666,19 +3072,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
3072
  const valtype = itemTypeToValtype[itemType];
2667
3073
  const length = elements.length;
2668
3074
 
2669
- if (firstAssign && useRawElements) {
2670
- let bytes = compileBytes(length, 'i32');
3075
+ if (firstAssign && useRawElements && !Prefs.noData) {
3076
+ // if length is 0 memory/data will just be 0000... anyway
3077
+ if (length !== 0) {
3078
+ let bytes = compileBytes(length, 'i32');
2671
3079
 
2672
- if (!initEmpty) for (let i = 0; i < length; i++) {
2673
- if (elements[i] == null) continue;
3080
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3081
+ if (elements[i] == null) continue;
2674
3082
 
2675
- bytes.push(...compileBytes(elements[i], itemType));
2676
- }
3083
+ bytes.push(...compileBytes(elements[i], itemType));
3084
+ }
2677
3085
 
2678
- data.push({
2679
- offset: pointer,
2680
- bytes
2681
- });
3086
+ const ind = data.push({
3087
+ offset: pointer,
3088
+ bytes
3089
+ }) - 1;
3090
+
3091
+ scope.data ??= [];
3092
+ scope.data.push(ind);
3093
+ }
2682
3094
 
2683
3095
  // local value as pointer
2684
3096
  out.push(...number(pointer));
@@ -2712,7 +3124,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2712
3124
  };
2713
3125
 
2714
3126
  const byteStringable = str => {
2715
- if (!process.argv.includes('-bytestring')) return false;
3127
+ if (!Prefs.bytestring) return false;
2716
3128
 
2717
3129
  for (let i = 0; i < str.length; i++) {
2718
3130
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2721,9 +3133,9 @@ const byteStringable = str => {
2721
3133
  return true;
2722
3134
  };
2723
3135
 
2724
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3136
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2725
3137
  const rawElements = new Array(str.length);
2726
- let byteStringable = process.argv.includes('-bytestring');
3138
+ let byteStringable = Prefs.bytestring;
2727
3139
  for (let i = 0; i < str.length; i++) {
2728
3140
  const c = str.charCodeAt(i);
2729
3141
  rawElements[i] = c;
@@ -2731,25 +3143,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2731
3143
  if (byteStringable && c > 0xFF) byteStringable = false;
2732
3144
  }
2733
3145
 
3146
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3147
+
2734
3148
  return makeArray(scope, {
2735
3149
  rawElements
2736
3150
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2737
3151
  };
2738
3152
 
2739
- let arrays = new Map();
2740
3153
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2741
3154
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2742
3155
  };
2743
3156
 
2744
3157
  export const generateMember = (scope, decl, _global, _name) => {
2745
3158
  const name = decl.object.name;
2746
- const pointer = arrays.get(name);
3159
+ const pointer = scope.arrays?.get(name);
2747
3160
 
2748
- const aotPointer = pointer != null;
3161
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2749
3162
 
2750
3163
  // hack: .length
2751
3164
  if (decl.property.name === 'length') {
2752
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3165
+ const func = funcs.find(x => x.name === name);
3166
+ if (func) {
3167
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3168
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3169
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3170
+ }
3171
+
3172
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3173
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3174
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3175
+
2753
3176
  return [
2754
3177
  ...(aotPointer ? number(0, Valtype.i32) : [
2755
3178
  ...generate(scope, decl.object),
@@ -2793,7 +3216,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2793
3216
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2794
3217
 
2795
3218
  ...number(TYPES.number, Valtype.i32),
2796
- setLastType(scope)
3219
+ ...setLastType(scope)
2797
3220
  ],
2798
3221
 
2799
3222
  [TYPES.string]: [
@@ -2825,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2825
3248
  ...number(newPointer),
2826
3249
 
2827
3250
  ...number(TYPES.string, Valtype.i32),
2828
- setLastType(scope)
3251
+ ...setLastType(scope)
2829
3252
  ],
2830
3253
  [TYPES._bytestring]: [
2831
3254
  // setup new/out array
@@ -2844,19 +3267,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2844
3267
  ]),
2845
3268
 
2846
3269
  // load current string ind {arg}
2847
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3270
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2848
3271
 
2849
3272
  // store to new string ind 0
2850
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3273
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2851
3274
 
2852
3275
  // return new string (page)
2853
3276
  ...number(newPointer),
2854
3277
 
2855
3278
  ...number(TYPES._bytestring, Valtype.i32),
2856
- setLastType(scope)
3279
+ ...setLastType(scope)
2857
3280
  ],
2858
3281
 
2859
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3282
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2860
3283
  });
2861
3284
  };
2862
3285
 
@@ -2866,28 +3289,36 @@ const objectHack = node => {
2866
3289
  if (!node) return node;
2867
3290
 
2868
3291
  if (node.type === 'MemberExpression') {
2869
- if (node.computed || node.optional) return node;
3292
+ const out = (() => {
3293
+ if (node.computed || node.optional) return;
2870
3294
 
2871
- let objectName = node.object.name;
3295
+ let objectName = node.object.name;
2872
3296
 
2873
- // if object is not identifier or another member exp, give up
2874
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3297
+ // if object is not identifier or another member exp, give up
3298
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3299
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2875
3300
 
2876
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3301
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2877
3302
 
2878
- // if .length, give up (hack within a hack!)
2879
- if (node.property.name === 'length') return node;
3303
+ // if .length, give up (hack within a hack!)
3304
+ if (node.property.name === 'length') {
3305
+ node.object = objectHack(node.object);
3306
+ return;
3307
+ }
2880
3308
 
2881
- // no object name, give up
2882
- if (!objectName) return node;
3309
+ // no object name, give up
3310
+ if (!objectName) return;
2883
3311
 
2884
- const name = '__' + objectName + '_' + node.property.name;
2885
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3312
+ const name = '__' + objectName + '_' + node.property.name;
3313
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2886
3314
 
2887
- return {
2888
- type: 'Identifier',
2889
- name
2890
- };
3315
+ return {
3316
+ type: 'Identifier',
3317
+ name
3318
+ };
3319
+ })();
3320
+
3321
+ if (out) return out;
2891
3322
  }
2892
3323
 
2893
3324
  for (const x in node) {
@@ -2901,8 +3332,8 @@ const objectHack = node => {
2901
3332
  };
2902
3333
 
2903
3334
  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');
3335
+ if (decl.async) return todo(scope, 'async functions are not supported');
3336
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2906
3337
 
2907
3338
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2908
3339
  const params = decl.params ?? [];
@@ -2918,6 +3349,11 @@ const generateFunc = (scope, decl) => {
2918
3349
  name
2919
3350
  };
2920
3351
 
3352
+ if (typedInput && decl.returnType) {
3353
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3354
+ innerScope.returns = [ valtypeBinary ];
3355
+ }
3356
+
2921
3357
  for (let i = 0; i < params.length; i++) {
2922
3358
  allocVar(innerScope, params[i].name, false);
2923
3359
 
@@ -2944,6 +3380,8 @@ const generateFunc = (scope, decl) => {
2944
3380
  };
2945
3381
  funcIndex[name] = func.index;
2946
3382
 
3383
+ if (name === 'main') func.gotLastType = true;
3384
+
2947
3385
  // quick hack fixes
2948
3386
  for (const inst of wasm) {
2949
3387
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2978,16 +3416,6 @@ const generateCode = (scope, decl) => {
2978
3416
  };
2979
3417
 
2980
3418
  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
3419
  Array: {
2992
3420
  generate: (scope, decl, global, name) => {
2993
3421
  // new Array(i0, i1, ...)
@@ -3005,7 +3433,7 @@ const internalConstrs = {
3005
3433
 
3006
3434
  // todo: check in wasm instead of here
3007
3435
  const literalValue = arg.value ?? 0;
3008
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3436
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3009
3437
 
3010
3438
  return [
3011
3439
  ...number(0, Valtype.i32),
@@ -3016,7 +3444,8 @@ const internalConstrs = {
3016
3444
  ...number(pointer)
3017
3445
  ];
3018
3446
  },
3019
- type: TYPES._array
3447
+ type: TYPES._array,
3448
+ length: 1
3020
3449
  },
3021
3450
 
3022
3451
  __Array_of: {
@@ -3028,7 +3457,94 @@ const internalConstrs = {
3028
3457
  }, global, name);
3029
3458
  },
3030
3459
  type: TYPES._array,
3460
+ notConstr: true,
3461
+ length: 0
3462
+ },
3463
+
3464
+ __Porffor_fastOr: {
3465
+ generate: (scope, decl) => {
3466
+ const out = [];
3467
+
3468
+ for (let i = 0; i < decl.arguments.length; i++) {
3469
+ out.push(
3470
+ ...generate(scope, decl.arguments[i]),
3471
+ Opcodes.i32_to_u,
3472
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3473
+ );
3474
+ }
3475
+
3476
+ return out;
3477
+ },
3478
+ type: TYPES.boolean,
3031
3479
  notConstr: true
3480
+ },
3481
+
3482
+ __Porffor_fastAnd: {
3483
+ generate: (scope, decl) => {
3484
+ const out = [];
3485
+
3486
+ for (let i = 0; i < decl.arguments.length; i++) {
3487
+ out.push(
3488
+ ...generate(scope, decl.arguments[i]),
3489
+ Opcodes.i32_to_u,
3490
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3491
+ );
3492
+ }
3493
+
3494
+ return out;
3495
+ },
3496
+ type: TYPES.boolean,
3497
+ notConstr: true
3498
+ },
3499
+
3500
+ Boolean: {
3501
+ generate: (scope, decl) => {
3502
+ // todo: boolean object when used as constructor
3503
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3504
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3505
+ },
3506
+ type: TYPES.boolean,
3507
+ length: 1
3508
+ },
3509
+
3510
+ __Math_max: {
3511
+ generate: (scope, decl) => {
3512
+ const out = [
3513
+ ...number(-Infinity)
3514
+ ];
3515
+
3516
+ for (let i = 0; i < decl.arguments.length; i++) {
3517
+ out.push(
3518
+ ...generate(scope, decl.arguments[i]),
3519
+ [ Opcodes.f64_max ]
3520
+ );
3521
+ }
3522
+
3523
+ return out;
3524
+ },
3525
+ type: TYPES.number,
3526
+ notConstr: true,
3527
+ length: 2
3528
+ },
3529
+
3530
+ __Math_min: {
3531
+ generate: (scope, decl) => {
3532
+ const out = [
3533
+ ...number(Infinity)
3534
+ ];
3535
+
3536
+ for (let i = 0; i < decl.arguments.length; i++) {
3537
+ out.push(
3538
+ ...generate(scope, decl.arguments[i]),
3539
+ [ Opcodes.f64_min ]
3540
+ );
3541
+ }
3542
+
3543
+ return out;
3544
+ },
3545
+ type: TYPES.number,
3546
+ notConstr: true,
3547
+ length: 2
3032
3548
  }
3033
3549
  };
3034
3550
 
@@ -3057,7 +3573,6 @@ export default program => {
3057
3573
  funcs = [];
3058
3574
  funcIndex = {};
3059
3575
  depth = [];
3060
- arrays = new Map();
3061
3576
  pages = new Map();
3062
3577
  data = [];
3063
3578
  currentFuncIndex = importedFuncs.length;
@@ -3071,6 +3586,10 @@ export default program => {
3071
3586
 
3072
3587
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3073
3588
 
3589
+ globalThis.pageSize = PageSize;
3590
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3591
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3592
+
3074
3593
  // set generic opcodes for current valtype
3075
3594
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3076
3595
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3079,10 +3598,10 @@ export default program => {
3079
3598
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3080
3599
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3081
3600
 
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];
3601
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3602
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3603
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3604
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3086
3605
 
3087
3606
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3088
3607
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3095,10 +3614,6 @@ export default program => {
3095
3614
 
3096
3615
  program.id = { name: 'main' };
3097
3616
 
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
3617
  const scope = {
3103
3618
  locals: {},
3104
3619
  localInd: 0
@@ -3109,7 +3624,7 @@ export default program => {
3109
3624
  body: program.body
3110
3625
  };
3111
3626
 
3112
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3627
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3113
3628
 
3114
3629
  generateFunc(scope, program);
3115
3630
 
@@ -3126,7 +3641,11 @@ export default program => {
3126
3641
  }
3127
3642
 
3128
3643
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3129
- main.returns = [];
3644
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3645
+ main.wasm.splice(main.wasm.length - 1, 1);
3646
+ } else {
3647
+ main.returns = [];
3648
+ }
3130
3649
  }
3131
3650
 
3132
3651
  if (lastInst[0] === Opcodes.call) {