porffor 0.2.0-623cdf0 → 0.2.0-69d30a8

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 +12 -12
  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 +443 -273
  21. package/compiler/{codeGen.js → codegen.js} +841 -337
  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 +25 -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 +35 -9
  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/filesize.cmd +0 -2
  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
- __Porffor_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,7 +204,11 @@ 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
  }
@@ -197,34 +217,52 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
197
217
  },
198
218
 
199
219
  __Porffor_bs: str => [
200
- ...makeString(scope, str, undefined, undefined, true),
220
+ ...makeString(scope, str, global, name, true),
201
221
 
202
- ...number(TYPES._bytestring, Valtype.i32),
203
- setLastType(scope)
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
204
226
  ],
205
227
  __Porffor_s: str => [
206
- ...makeString(scope, str, undefined, undefined, false),
228
+ ...makeString(scope, str, global, name, false),
207
229
 
208
- ...number(TYPES.string, Valtype.i32),
209
- setLastType(scope)
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
210
234
  ],
211
235
  };
212
236
 
213
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
214
238
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
216
254
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
255
+ return funcs[func](str);
219
256
  }
220
257
 
221
258
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
262
  return [];
225
263
  }
226
264
 
227
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
228
266
  }
229
267
  };
230
268
 
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
252
290
  return [ undefined, undefined ];
253
291
  };
254
292
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
294
  ...generateThrow(scope, {
257
295
  argument: {
258
296
  type: 'NewExpression',
@@ -276,7 +314,10 @@ const generateIdent = (scope, decl) => {
276
314
 
277
315
  if (Object.hasOwn(builtinVars, name)) {
278
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
280
321
  }
281
322
 
282
323
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -284,6 +325,11 @@ const generateIdent = (scope, decl) => {
284
325
  return number(1);
285
326
  }
286
327
 
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
287
333
  if (local?.idx === undefined) {
288
334
  // no local var with name
289
335
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
314
360
  // just bare "return"
315
361
  return [
316
362
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
318
366
  [ Opcodes.return ]
319
367
  ];
320
368
  }
321
369
 
322
370
  return [
323
371
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
325
375
  [ Opcodes.return ]
326
376
  ];
327
377
  };
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
385
  return idx;
336
386
  };
337
387
 
338
- 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);
339
390
 
340
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
392
  const checks = {
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
395
  '??': nullish
345
396
  };
346
397
 
347
- 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);
348
399
 
349
400
  // generic structure for {a} OP {b}
350
401
  // -->
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
403
 
353
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
405
  // (like if we are in an if condition - very common)
355
- const leftIsInt = isIntOp(left[left.length - 1]);
356
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
408
 
358
409
  const canInt = leftIsInt && rightIsInt;
359
410
 
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
421
  ...right,
371
422
  // note type
372
423
  ...rightType,
373
- setLastType(scope),
424
+ ...setLastType(scope),
374
425
  [ Opcodes.else ],
375
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
427
  // note type
377
428
  ...leftType,
378
- setLastType(scope),
429
+ ...setLastType(scope),
379
430
  [ Opcodes.end ],
380
431
  Opcodes.i32_from
381
432
  ];
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
440
  ...right,
390
441
  // note type
391
442
  ...rightType,
392
- setLastType(scope),
443
+ ...setLastType(scope),
393
444
  [ Opcodes.else ],
394
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
446
  // note type
396
447
  ...leftType,
397
- setLastType(scope),
448
+ ...setLastType(scope),
398
449
  [ Opcodes.end ]
399
450
  ];
400
451
  };
401
452
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
455
  // todo: convert left and right to strings if not
405
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
462
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
463
  if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
465
 
418
466
  return [
419
467
  // setup right
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
487
 
440
488
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
442
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
491
 
444
492
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
494
  [ Opcodes.i32_mul ],
447
495
  [ Opcodes.i32_add ],
448
496
 
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
499
  ...number(ValtypeSize.i32, Valtype.i32),
452
500
  [ Opcodes.i32_add ],
453
501
 
454
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
455
503
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
505
  [ Opcodes.i32_mul ],
458
506
 
459
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
560
 
513
561
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
515
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
564
 
517
565
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
567
  [ Opcodes.i32_mul ],
520
568
  [ Opcodes.i32_add ],
521
569
 
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
572
  ...number(ValtypeSize.i32, Valtype.i32),
525
573
  [ Opcodes.i32_add ],
526
574
 
527
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
528
576
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
578
  [ Opcodes.i32_mul ],
531
579
 
532
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
584
  ];
537
585
  };
538
586
 
539
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
588
  // todo: this should be rewritten into a func
541
589
  // todo: convert left and right to strings if not
542
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
545
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
596
 
550
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
573
620
 
574
621
  [ Opcodes.local_get, rightPointer ],
575
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
623
 
578
624
  // fast path: check leftLength != rightLength
579
625
  [ Opcodes.i32_ne ],
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
588
634
  ...number(0, Valtype.i32),
589
635
  [ Opcodes.local_set, index ],
590
636
 
591
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
638
  // we do this instead of having to do mul/div each iter for perf™
593
639
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
596
644
  [ Opcodes.local_set, indexEnd ],
597
645
 
598
646
  // iterate over each char and check if eq
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
602
650
  [ Opcodes.local_get, index ],
603
651
  [ Opcodes.local_get, leftPointer ],
604
652
  [ Opcodes.i32_add ],
605
- [ 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 ],
606
656
 
607
657
  // fetch right
608
658
  [ Opcodes.local_get, index ],
609
659
  [ Opcodes.local_get, rightPointer ],
610
660
  [ Opcodes.i32_add ],
611
- [ 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 ],
612
664
 
613
665
  // not equal, "return" false
614
666
  [ Opcodes.i32_ne ],
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
617
669
  [ Opcodes.br, 2 ],
618
670
  [ Opcodes.end ],
619
671
 
620
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
673
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
675
  [ Opcodes.i32_add ],
624
676
  [ Opcodes.local_tee, index ],
625
677
 
626
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
627
679
  [ Opcodes.local_get, indexEnd ],
628
680
  [ Opcodes.i32_ne ],
629
681
  [ Opcodes.br_if, 0 ],
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
644
696
  };
645
697
 
646
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
700
  ...wasm,
649
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
704
 
652
- 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);
653
707
 
654
708
  const def = [
655
709
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
711
 
658
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
719
 
666
720
  return [
667
721
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
723
 
670
724
  ...typeSwitch(scope, type, {
671
725
  // [TYPES.number]: def,
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
729
  ],
676
730
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
733
 
680
734
  // get length
@@ -686,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
686
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
741
  ],
688
742
  [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
744
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
745
 
692
746
  // get length
@@ -700,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
754
  };
701
755
 
702
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- 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
+
704
760
  return [
705
761
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
763
 
708
764
  ...typeSwitch(scope, type, {
709
765
  [TYPES._array]: [
@@ -711,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
768
  ],
713
769
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
771
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
772
 
717
773
  // get length
@@ -722,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
722
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
779
  ],
724
780
  [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
783
 
728
784
  // get length
@@ -734,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
790
  ],
735
791
  default: [
736
792
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
794
 
739
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
800
  };
745
801
 
746
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- 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
+
748
806
  return [
749
807
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
809
 
752
810
  ...typeSwitch(scope, type, {
753
811
  [TYPES.undefined]: [
@@ -756,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
814
  ],
757
815
  [TYPES.object]: [
758
816
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
818
 
761
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
844
  }
787
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
788
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
850
  const strictOp = op === '===' || op === '!==';
790
851
 
791
852
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
793
854
 
794
855
  // if strict (in)equal check types match
795
856
  if (strictOp) {
@@ -834,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
895
  // todo: if equality op and an operand is undefined, return false
835
896
  // todo: niche null hell with 0
836
897
 
837
- // if (leftType === TYPES.string || rightType === TYPES.string) {
838
- // if (op === '+') {
839
- // // string concat (a + b)
840
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
841
- // }
842
-
843
- // // not an equality op, NaN
844
- // if (!eqOp) return finalise(number(NaN));
845
-
846
- // // else leave bool ops
847
- // // todo: convert string to number if string and number/bool
848
- // // todo: string (>|>=|<|<=) string
849
-
850
- // // string comparison
851
- // if (op === '===' || op === '==') {
852
- // return finalise(compareStrings(scope, left, right));
853
- // }
854
-
855
- // if (op === '!==' || op === '!=') {
856
- // return finalise([
857
- // ...compareStrings(scope, left, right),
858
- // [ Opcodes.i32_eqz ]
859
- // ]);
860
- // }
861
- // }
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
+ }
862
951
 
863
952
  let ops = operatorOpcode[valtype][op];
864
953
 
@@ -868,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
957
  includeBuiltin(scope, builtinName);
869
958
  const idx = funcIndex[builtinName];
870
959
 
871
- return finalise([
960
+ return finalize([
872
961
  ...left,
873
962
  ...right,
874
963
  [ Opcodes.call, idx ]
875
964
  ]);
876
965
  }
877
966
 
878
- 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);
879
968
 
880
969
  if (!Array.isArray(ops)) ops = [ ops ];
881
970
  ops = [ ops ];
882
971
 
883
972
  let tmpLeft, tmpRight;
884
973
  // if equal op, check if strings for compareStrings
885
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
- const knownLeft = knownType(scope, leftType);
887
- const knownRight = knownType(scope, rightType);
888
-
889
- // todo: intelligent partial skip later
890
- // if neither known are string, stop this madness
891
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
892
- return;
893
- }
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
894
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
979
  tmpLeft = localTmp(scope, '__tmpop_left');
896
980
  tmpRight = localTmp(scope, '__tmpop_right');
897
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)
898
1023
  ops.unshift(...stringOnly([
899
1024
  // if left is string
900
1025
  ...leftType,
@@ -906,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
1031
  ...number(TYPES.string, Valtype.i32),
907
1032
  [ Opcodes.i32_eq ],
908
1033
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
911
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 ],
912
1041
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
1042
+ // if left is bytestring
915
1043
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
918
1046
 
919
- // if right is not string
1047
+ // if right is bytestring
920
1048
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
923
1051
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
926
1054
  [ Opcodes.if, Blocktype.void ],
927
- ...number(0, Valtype.i32),
928
- [ Opcodes.br, 2 ],
929
- [ Opcodes.end ],
930
-
931
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
932
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1057
  [ Opcodes.br, 1 ],
935
1058
  [ Opcodes.end ],
@@ -941,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1066
  // }
944
- })();
1067
+ }
945
1068
 
946
- return finalise([
1069
+ return finalize([
947
1070
  ...left,
948
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1072
  ...right,
@@ -960,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1083
  return out;
961
1084
  };
962
1085
 
963
- 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 = [] }) => {
964
1102
  const existing = funcs.find(x => x.name === name);
965
1103
  if (existing) return existing;
966
1104
 
@@ -972,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1111
  }
974
1112
 
975
- if (typeof wasm === 'function') {
976
- const scope = {
977
- name,
978
- params,
979
- locals,
980
- returns,
981
- localInd: allLocals.length,
982
- };
983
-
984
- 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);
985
1117
  }
986
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
987
1121
  let baseGlobalIdx, i = 0;
988
1122
  for (const type of globalTypes) {
989
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1006,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1006
1140
  params,
1007
1141
  locals,
1008
1142
  returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1010
1144
  wasm,
1011
1145
  internal: true,
1012
1146
  index: currentFuncIndex++
@@ -1029,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1029
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1164
  };
1031
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1032
1167
  // T = JS type, V = value/pointer
1033
1168
  // 0bTTT
1034
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1052,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1052
1187
  // 4: internal type
1053
1188
  // 5: pointer
1054
1189
 
1055
- const TYPES = {
1056
- number: 0x00,
1057
- boolean: 0x01,
1058
- string: 0x02,
1059
- undefined: 0x03,
1060
- object: 0x04,
1061
- function: 0x05,
1062
- symbol: 0x06,
1063
- bigint: 0x07,
1064
-
1065
- // these are not "typeof" types but tracked internally
1066
- _array: 0x10,
1067
- _regexp: 0x11,
1068
- _bytestring: 0x12
1069
- };
1070
-
1071
- const TYPE_NAMES = {
1072
- [TYPES.number]: 'Number',
1073
- [TYPES.boolean]: 'Boolean',
1074
- [TYPES.string]: 'String',
1075
- [TYPES.undefined]: 'undefined',
1076
- [TYPES.object]: 'Object',
1077
- [TYPES.function]: 'Function',
1078
- [TYPES.symbol]: 'Symbol',
1079
- [TYPES.bigint]: 'BigInt',
1080
-
1081
- [TYPES._array]: 'Array',
1082
- [TYPES._regexp]: 'RegExp',
1083
- [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;
1084
1195
  };
1085
1196
 
1086
1197
  const getType = (scope, _name) => {
1087
1198
  const name = mapName(_name);
1088
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);
1089
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);
1090
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1207
 
1092
1208
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1211
 
1096
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1097
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1213
 
1099
1214
  return number(type, Valtype.i32);
1100
1215
  };
@@ -1117,15 +1232,16 @@ const setType = (scope, _name, type) => {
1117
1232
  ];
1118
1233
 
1119
1234
  // throw new Error('could not find var');
1235
+ return [];
1120
1236
  };
1121
1237
 
1122
1238
  const getLastType = scope => {
1123
1239
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1241
  };
1126
1242
 
1127
1243
  const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1129
1245
  };
1130
1246
 
1131
1247
  const getNodeType = (scope, node) => {
@@ -1150,7 +1266,7 @@ const getNodeType = (scope, node) => {
1150
1266
  const name = node.callee.name;
1151
1267
  if (!name) {
1152
1268
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1270
 
1155
1271
  // presume
1156
1272
  // todo: warn here?
@@ -1164,7 +1280,7 @@ const getNodeType = (scope, node) => {
1164
1280
  if (func.returnType) return func.returnType;
1165
1281
  }
1166
1282
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1283
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1284
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1285
 
1170
1286
  // check if this is a prototype function
@@ -1179,7 +1295,12 @@ const getNodeType = (scope, node) => {
1179
1295
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1296
  }
1181
1297
 
1182
- 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);
1183
1304
 
1184
1305
  // presume
1185
1306
  // todo: warn here?
@@ -1227,6 +1348,15 @@ const getNodeType = (scope, node) => {
1227
1348
 
1228
1349
  if (node.type === 'BinaryExpression') {
1229
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
+
1230
1360
  return TYPES.number;
1231
1361
 
1232
1362
  // todo: string concat types
@@ -1251,7 +1381,7 @@ const getNodeType = (scope, node) => {
1251
1381
  if (node.operator === '!') return TYPES.boolean;
1252
1382
  if (node.operator === 'void') return TYPES.undefined;
1253
1383
  if (node.operator === 'delete') return TYPES.boolean;
1254
- 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;
1255
1385
 
1256
1386
  return TYPES.number;
1257
1387
  }
@@ -1262,15 +1392,21 @@ const getNodeType = (scope, node) => {
1262
1392
 
1263
1393
  // ts hack
1264
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;
1265
1396
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1266
1397
 
1267
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1398
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
1399
 
1269
1400
  // presume
1270
1401
  return TYPES.number;
1271
1402
  }
1272
1403
 
1273
- 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);
1274
1410
 
1275
1411
  // presume
1276
1412
  // todo: warn here?
@@ -1303,7 +1439,7 @@ const generateLiteral = (scope, decl, global, name) => {
1303
1439
  return makeString(scope, decl.value, global, name);
1304
1440
 
1305
1441
  default:
1306
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1442
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1307
1443
  }
1308
1444
  };
1309
1445
 
@@ -1312,6 +1448,8 @@ const countLeftover = wasm => {
1312
1448
 
1313
1449
  for (let i = 0; i < wasm.length; i++) {
1314
1450
  const inst = wasm[i];
1451
+ if (inst[0] == null) continue;
1452
+
1315
1453
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1316
1454
  if (inst[0] === Opcodes.if) count--;
1317
1455
  if (inst[1] !== Blocktype.void) count++;
@@ -1320,7 +1458,7 @@ const countLeftover = wasm => {
1320
1458
  if (inst[0] === Opcodes.end) depth--;
1321
1459
 
1322
1460
  if (depth === 0)
1323
- 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--;
1324
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)) {}
1325
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++;
1326
1464
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
@@ -1423,10 +1561,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1423
1561
  name = func.name;
1424
1562
  }
1425
1563
 
1426
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1564
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1427
1565
  // literal eval hack
1428
- const code = decl.arguments[0].value;
1429
- 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
+ }
1430
1579
 
1431
1580
  const out = generate(scope, {
1432
1581
  type: 'BlockStatement',
@@ -1440,13 +1589,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1440
1589
  const finalStatement = parsed.body[parsed.body.length - 1];
1441
1590
  out.push(
1442
1591
  ...getNodeType(scope, finalStatement),
1443
- setLastType(scope)
1592
+ ...setLastType(scope)
1444
1593
  );
1445
1594
  } else if (countLeftover(out) === 0) {
1446
1595
  out.push(...number(UNDEFINED));
1447
1596
  out.push(
1448
1597
  ...number(TYPES.undefined, Valtype.i32),
1449
- setLastType(scope)
1598
+ ...setLastType(scope)
1450
1599
  );
1451
1600
  }
1452
1601
 
@@ -1468,6 +1617,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1468
1617
 
1469
1618
  target = { ...decl.callee };
1470
1619
  target.name = spl.slice(0, -1).join('_');
1620
+
1621
+ // failed to lookup name, abort
1622
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1471
1623
  }
1472
1624
 
1473
1625
  // literal.func()
@@ -1490,7 +1642,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1490
1642
  Opcodes.i32_from_u,
1491
1643
 
1492
1644
  ...number(TYPES.boolean, Valtype.i32),
1493
- setLastType(scope)
1645
+ ...setLastType(scope)
1494
1646
  ];
1495
1647
  }
1496
1648
 
@@ -1515,12 +1667,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1667
  // }
1516
1668
 
1517
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
+
1518
1689
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1519
1690
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1520
1691
  return acc;
1521
1692
  }, {});
1522
1693
 
1523
- // no prototype function candidates, ignore
1524
1694
  if (Object.keys(protoCands).length > 0) {
1525
1695
  // use local for cached i32 length as commonly used
1526
1696
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1538,7 +1708,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1538
1708
 
1539
1709
  let allOptUnused = true;
1540
1710
  let lengthI32CacheUsed = false;
1541
- const protoBC = {};
1542
1711
  for (const x in protoCands) {
1543
1712
  const protoFunc = protoCands[x];
1544
1713
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1546,7 +1715,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1546
1715
  ...RTArrayUtil.getLength(getPointer),
1547
1716
 
1548
1717
  ...number(TYPES.number, Valtype.i32),
1549
- setLastType(scope)
1718
+ ...setLastType(scope)
1550
1719
  ];
1551
1720
  continue;
1552
1721
  }
@@ -1583,7 +1752,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1583
1752
  ...protoOut,
1584
1753
 
1585
1754
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1586
- setLastType(scope),
1755
+ ...setLastType(scope),
1587
1756
  [ Opcodes.end ]
1588
1757
  ];
1589
1758
  }
@@ -1609,10 +1778,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1778
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1610
1779
  ];
1611
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
+ }
1612
1790
  }
1613
1791
 
1614
1792
  // TODO: only allows callee as literal
1615
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1793
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1616
1794
 
1617
1795
  let idx = funcIndex[name] ?? importedFuncs[name];
1618
1796
  if (idx === undefined && builtinFuncs[name]) {
@@ -1645,16 +1823,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1823
  idx = -1;
1646
1824
  }
1647
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
+
1648
1875
  if (idx === undefined) {
1649
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1650
- 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);
1651
1878
  }
1652
1879
 
1653
1880
  const func = funcs.find(x => x.index === idx);
1654
1881
 
1655
1882
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1883
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1884
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1658
1885
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1886
 
1660
1887
  let args = decl.arguments;
@@ -1671,14 +1898,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1898
  if (func && func.throws) scope.throws = true;
1672
1899
 
1673
1900
  let out = [];
1674
- for (const arg of args) {
1901
+ for (let i = 0; i < args.length; i++) {
1902
+ const arg = args[i];
1675
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
+
1676
1913
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1914
  }
1678
1915
 
1679
1916
  out.push([ Opcodes.call, idx ]);
1680
1917
 
1681
- if (!typedReturn) {
1918
+ if (!typedReturns) {
1682
1919
  // let type;
1683
1920
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1921
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1688,7 +1925,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1688
1925
  // ...number(type, Valtype.i32),
1689
1926
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1690
1927
  // );
1691
- } 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
+ }
1692
1933
 
1693
1934
  return out;
1694
1935
  };
@@ -1697,7 +1938,7 @@ const generateNew = (scope, decl, _global, _name) => {
1697
1938
  // hack: basically treat this as a normal call for builtins for now
1698
1939
  const name = mapName(decl.callee.name);
1699
1940
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1700
- 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)})`);
1701
1942
 
1702
1943
  return generateCall(scope, decl, _global, _name);
1703
1944
  };
@@ -1814,14 +2055,14 @@ const brTable = (input, bc, returns) => {
1814
2055
  };
1815
2056
 
1816
2057
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2058
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1818
2059
 
1819
2060
  const known = knownType(scope, type);
1820
2061
  if (known != null) {
1821
2062
  return bc[known] ?? bc.default;
1822
2063
  }
1823
2064
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
2065
+ if (Prefs.typeswitchUseBrtable)
1825
2066
  return brTable(type, bc, returns);
1826
2067
 
1827
2068
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1831,8 +2072,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
2072
  [ Opcodes.block, returns ]
1832
2073
  ];
1833
2074
 
1834
- // todo: use br_table?
1835
-
1836
2075
  for (const x in bc) {
1837
2076
  if (x === 'default') continue;
1838
2077
 
@@ -1856,7 +2095,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
2095
  return out;
1857
2096
  };
1858
2097
 
1859
- const allocVar = (scope, name, global = false) => {
2098
+ const allocVar = (scope, name, global = false, type = true) => {
1860
2099
  const target = global ? globals : scope.locals;
1861
2100
 
1862
2101
  // already declared
@@ -1870,8 +2109,10 @@ const allocVar = (scope, name, global = false) => {
1870
2109
  let idx = global ? globalInd++ : scope.localInd++;
1871
2110
  target[name] = { idx, type: valtypeBinary };
1872
2111
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- 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
+ }
1875
2116
 
1876
2117
  return idx;
1877
2118
  };
@@ -1886,11 +2127,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1886
2127
  };
1887
2128
 
1888
2129
  const typeAnnoToPorfType = x => {
1889
- if (TYPES[x]) return TYPES[x];
1890
- 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];
1891
2133
 
1892
2134
  switch (x) {
1893
2135
  case 'i32':
2136
+ case 'i64':
2137
+ case 'f64':
1894
2138
  return TYPES.number;
1895
2139
  }
1896
2140
 
@@ -1901,7 +2145,7 @@ const extractTypeAnnotation = decl => {
1901
2145
  let a = decl;
1902
2146
  while (a.typeAnnotation) a = a.typeAnnotation;
1903
2147
 
1904
- let type, elementType;
2148
+ let type = null, elementType = null;
1905
2149
  if (a.typeName) {
1906
2150
  type = a.typeName.name;
1907
2151
  } else if (a.type.endsWith('Keyword')) {
@@ -1914,7 +2158,7 @@ const extractTypeAnnotation = decl => {
1914
2158
  const typeName = type;
1915
2159
  type = typeAnnoToPorfType(type);
1916
2160
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2161
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2162
 
1919
2163
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2164
 
@@ -1932,7 +2176,7 @@ const generateVar = (scope, decl) => {
1932
2176
  for (const x of decl.declarations) {
1933
2177
  const name = mapName(x.id.name);
1934
2178
 
1935
- if (!name) return todo('destructuring is not supported yet');
2179
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1936
2180
 
1937
2181
  if (x.init && isFuncType(x.init.type)) {
1938
2182
  // hack for let a = function () { ... }
@@ -1949,9 +2193,10 @@ const generateVar = (scope, decl) => {
1949
2193
  continue; // always ignore
1950
2194
  }
1951
2195
 
1952
- let idx = allocVar(scope, name, global);
2196
+ const typed = typedInput && x.id.typeAnnotation;
2197
+ let idx = allocVar(scope, name, global, !typed);
1953
2198
 
1954
- if (typedInput && x.id.typeAnnotation) {
2199
+ if (typed) {
1955
2200
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
2201
  }
1957
2202
 
@@ -1969,7 +2214,8 @@ const generateVar = (scope, decl) => {
1969
2214
  return out;
1970
2215
  };
1971
2216
 
1972
- const generateAssign = (scope, decl) => {
2217
+ // todo: optimize this func for valueUnused
2218
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1973
2219
  const { type, name } = decl.left;
1974
2220
 
1975
2221
  if (type === 'ObjectPattern') {
@@ -1987,9 +2233,9 @@ const generateAssign = (scope, decl) => {
1987
2233
  // hack: .length setter
1988
2234
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1989
2235
  const name = decl.left.object.name;
1990
- const pointer = arrays.get(name);
2236
+ const pointer = scope.arrays?.get(name);
1991
2237
 
1992
- const aotPointer = pointer != null;
2238
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1993
2239
 
1994
2240
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1995
2241
 
@@ -2014,9 +2260,9 @@ const generateAssign = (scope, decl) => {
2014
2260
  // arr[i]
2015
2261
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2016
2262
  const name = decl.left.object.name;
2017
- const pointer = arrays.get(name);
2263
+ const pointer = scope.arrays?.get(name);
2018
2264
 
2019
- const aotPointer = pointer != null;
2265
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2020
2266
 
2021
2267
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2022
2268
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2072,7 +2318,7 @@ const generateAssign = (scope, decl) => {
2072
2318
  ];
2073
2319
  }
2074
2320
 
2075
- if (!name) return todo('destructuring is not supported yet');
2321
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2076
2322
 
2077
2323
  const [ local, isGlobal ] = lookupName(scope, name);
2078
2324
 
@@ -2120,9 +2366,7 @@ const generateAssign = (scope, decl) => {
2120
2366
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2367
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2368
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2369
+ ...setType(scope, name, getLastType(scope))
2126
2370
  ];
2127
2371
  }
2128
2372
 
@@ -2133,9 +2377,7 @@ const generateAssign = (scope, decl) => {
2133
2377
 
2134
2378
  // todo: string concat types
2135
2379
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2380
+ ...setType(scope, name, TYPES.number)
2139
2381
  ];
2140
2382
  };
2141
2383
 
@@ -2181,7 +2423,7 @@ const generateUnary = (scope, decl) => {
2181
2423
  return out;
2182
2424
  }
2183
2425
 
2184
- case 'delete':
2426
+ case 'delete': {
2185
2427
  let toReturn = true, toGenerate = true;
2186
2428
 
2187
2429
  if (decl.argument.type === 'Identifier') {
@@ -2203,9 +2445,26 @@ const generateUnary = (scope, decl) => {
2203
2445
 
2204
2446
  out.push(...number(toReturn ? 1 : 0));
2205
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
+ }
2206
2463
 
2207
- case 'typeof':
2208
- 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), {
2209
2468
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2210
2469
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2211
2470
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2216,27 +2475,30 @@ const generateUnary = (scope, decl) => {
2216
2475
 
2217
2476
  // object and internal types
2218
2477
  default: makeString(scope, 'object', false, '#typeof_result'),
2219
- });
2478
+ }));
2479
+
2480
+ return out;
2481
+ }
2220
2482
 
2221
2483
  default:
2222
- return todo(`unary operator ${decl.operator} not implemented yet`);
2484
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2223
2485
  }
2224
2486
  };
2225
2487
 
2226
- const generateUpdate = (scope, decl) => {
2488
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2227
2489
  const { name } = decl.argument;
2228
2490
 
2229
2491
  const [ local, isGlobal ] = lookupName(scope, name);
2230
2492
 
2231
2493
  if (local === undefined) {
2232
- return todo(`update expression with undefined variable`);
2494
+ return todo(scope, `update expression with undefined variable`, true);
2233
2495
  }
2234
2496
 
2235
2497
  const idx = local.idx;
2236
2498
  const out = [];
2237
2499
 
2238
2500
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2239
- 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 ]);
2240
2502
 
2241
2503
  switch (decl.operator) {
2242
2504
  case '++':
@@ -2249,7 +2511,7 @@ const generateUpdate = (scope, decl) => {
2249
2511
  }
2250
2512
 
2251
2513
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2252
- 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 ]);
2253
2515
 
2254
2516
  return out;
2255
2517
  };
@@ -2289,7 +2551,7 @@ const generateConditional = (scope, decl) => {
2289
2551
  // note type
2290
2552
  out.push(
2291
2553
  ...getNodeType(scope, decl.consequent),
2292
- setLastType(scope)
2554
+ ...setLastType(scope)
2293
2555
  );
2294
2556
 
2295
2557
  out.push([ Opcodes.else ]);
@@ -2298,7 +2560,7 @@ const generateConditional = (scope, decl) => {
2298
2560
  // note type
2299
2561
  out.push(
2300
2562
  ...getNodeType(scope, decl.alternate),
2301
- setLastType(scope)
2563
+ ...setLastType(scope)
2302
2564
  );
2303
2565
 
2304
2566
  out.push([ Opcodes.end ]);
@@ -2312,7 +2574,7 @@ const generateFor = (scope, decl) => {
2312
2574
  const out = [];
2313
2575
 
2314
2576
  if (decl.init) {
2315
- out.push(...generate(scope, decl.init));
2577
+ out.push(...generate(scope, decl.init, false, undefined, true));
2316
2578
  disposeLeftover(out);
2317
2579
  }
2318
2580
 
@@ -2330,7 +2592,7 @@ const generateFor = (scope, decl) => {
2330
2592
  out.push(...generate(scope, decl.body));
2331
2593
  out.push([ Opcodes.end ]);
2332
2594
 
2333
- if (decl.update) out.push(...generate(scope, decl.update));
2595
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2334
2596
 
2335
2597
  out.push([ Opcodes.br, 1 ]);
2336
2598
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2358,6 +2620,36 @@ const generateWhile = (scope, decl) => {
2358
2620
  return out;
2359
2621
  };
2360
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
+
2361
2653
  const generateForOf = (scope, decl) => {
2362
2654
  const out = [];
2363
2655
 
@@ -2394,7 +2686,10 @@ const generateForOf = (scope, decl) => {
2394
2686
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
2687
  }
2396
2688
 
2689
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2690
+
2397
2691
  const [ local, isGlobal ] = lookupName(scope, leftName);
2692
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2398
2693
 
2399
2694
  depth.push('block');
2400
2695
  depth.push('block');
@@ -2403,6 +2698,7 @@ const generateForOf = (scope, decl) => {
2403
2698
  // hack: this is naughty and will break things!
2404
2699
  let newOut = number(0, Valtype.f64), newPointer = -1;
2405
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?
2406
2702
  0, [ newOut, newPointer ] = makeArray(scope, {
2407
2703
  rawElements: new Array(1)
2408
2704
  }, isGlobal, leftName, true, 'i16');
@@ -2494,6 +2790,56 @@ const generateForOf = (scope, decl) => {
2494
2790
  [ Opcodes.end ],
2495
2791
  [ Opcodes.end ]
2496
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
+ ],
2497
2843
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2498
2844
  }, Blocktype.void));
2499
2845
 
@@ -2504,28 +2850,65 @@ const generateForOf = (scope, decl) => {
2504
2850
  return out;
2505
2851
  };
2506
2852
 
2853
+ // find the nearest loop in depth map by type
2507
2854
  const getNearestLoop = () => {
2508
2855
  for (let i = depth.length - 1; i >= 0; i--) {
2509
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2856
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2510
2857
  }
2511
2858
 
2512
2859
  return -1;
2513
2860
  };
2514
2861
 
2515
2862
  const generateBreak = (scope, decl) => {
2516
- 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
+
2517
2878
  return [
2518
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2879
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2519
2880
  ];
2520
2881
  };
2521
2882
 
2522
2883
  const generateContinue = (scope, decl) => {
2523
- 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
+
2524
2898
  return [
2525
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2899
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2526
2900
  ];
2527
2901
  };
2528
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
+
2529
2912
  const generateThrow = (scope, decl) => {
2530
2913
  scope.throws = true;
2531
2914
 
@@ -2546,6 +2929,9 @@ const generateThrow = (scope, decl) => {
2546
2929
  let exceptId = exceptions.push({ constructor, message }) - 1;
2547
2930
  let tagIdx = tags[0].idx;
2548
2931
 
2932
+ scope.exceptions ??= [];
2933
+ scope.exceptions.push(exceptId);
2934
+
2549
2935
  // todo: write a description of how this works lol
2550
2936
 
2551
2937
  return [
@@ -2555,7 +2941,7 @@ const generateThrow = (scope, decl) => {
2555
2941
  };
2556
2942
 
2557
2943
  const generateTry = (scope, decl) => {
2558
- if (decl.finalizer) return todo('try finally not implemented yet');
2944
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2559
2945
 
2560
2946
  const out = [];
2561
2947
 
@@ -2586,11 +2972,11 @@ const generateAssignPat = (scope, decl) => {
2586
2972
  // TODO
2587
2973
  // if identifier declared, use that
2588
2974
  // else, use default (right)
2589
- return todo('assignment pattern (optional arg)');
2975
+ return todo(scope, 'assignment pattern (optional arg)');
2590
2976
  };
2591
2977
 
2592
2978
  let pages = new Map();
2593
- const allocPage = (reason, type) => {
2979
+ const allocPage = (scope, reason, type) => {
2594
2980
  if (pages.has(reason)) return pages.get(reason).ind;
2595
2981
 
2596
2982
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2601,16 +2987,20 @@ const allocPage = (reason, type) => {
2601
2987
  const ind = pages.size;
2602
2988
  pages.set(reason, { ind, type });
2603
2989
 
2604
- 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})`);
2605
2994
 
2606
2995
  return ind;
2607
2996
  };
2608
2997
 
2998
+ // todo: add scope.pages
2609
2999
  const freePage = reason => {
2610
3000
  const { ind } = pages.get(reason);
2611
3001
  pages.delete(reason);
2612
3002
 
2613
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3003
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2614
3004
 
2615
3005
  return ind;
2616
3006
  };
@@ -2636,15 +3026,14 @@ const StoreOps = {
2636
3026
 
2637
3027
  let data = [];
2638
3028
 
2639
- const compileBytes = (val, itemType, signed = true) => {
3029
+ const compileBytes = (val, itemType) => {
2640
3030
  // todo: this is a mess and needs confirming / ????
2641
3031
  switch (itemType) {
2642
3032
  case 'i8': return [ val % 256 ];
2643
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2644
-
2645
- case 'i32':
2646
- case 'i64':
2647
- 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
2648
3037
 
2649
3038
  case 'f64': return ieee754_binary64(val);
2650
3039
  }
@@ -2662,16 +3051,20 @@ const getAllocType = itemType => {
2662
3051
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2663
3052
  const out = [];
2664
3053
 
3054
+ scope.arrays ??= new Map();
3055
+
2665
3056
  let firstAssign = false;
2666
- if (!arrays.has(name) || name === '$undeclared') {
3057
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2667
3058
  firstAssign = true;
2668
3059
 
2669
3060
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2670
3061
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2671
- 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);
2672
3065
  }
2673
3066
 
2674
- const pointer = arrays.get(name);
3067
+ const pointer = scope.arrays.get(name);
2675
3068
 
2676
3069
  const useRawElements = !!decl.rawElements;
2677
3070
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2679,19 +3072,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2679
3072
  const valtype = itemTypeToValtype[itemType];
2680
3073
  const length = elements.length;
2681
3074
 
2682
- if (firstAssign && useRawElements) {
2683
- 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');
2684
3079
 
2685
- if (!initEmpty) for (let i = 0; i < length; i++) {
2686
- if (elements[i] == null) continue;
3080
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3081
+ if (elements[i] == null) continue;
2687
3082
 
2688
- bytes.push(...compileBytes(elements[i], itemType));
2689
- }
3083
+ bytes.push(...compileBytes(elements[i], itemType));
3084
+ }
2690
3085
 
2691
- data.push({
2692
- offset: pointer,
2693
- bytes
2694
- });
3086
+ const ind = data.push({
3087
+ offset: pointer,
3088
+ bytes
3089
+ }) - 1;
3090
+
3091
+ scope.data ??= [];
3092
+ scope.data.push(ind);
3093
+ }
2695
3094
 
2696
3095
  // local value as pointer
2697
3096
  out.push(...number(pointer));
@@ -2725,7 +3124,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2725
3124
  };
2726
3125
 
2727
3126
  const byteStringable = str => {
2728
- if (!process.argv.includes('-bytestring')) return false;
3127
+ if (!Prefs.bytestring) return false;
2729
3128
 
2730
3129
  for (let i = 0; i < str.length; i++) {
2731
3130
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2736,7 +3135,7 @@ const byteStringable = str => {
2736
3135
 
2737
3136
  const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2738
3137
  const rawElements = new Array(str.length);
2739
- let byteStringable = process.argv.includes('-bytestring');
3138
+ let byteStringable = Prefs.bytestring;
2740
3139
  for (let i = 0; i < str.length; i++) {
2741
3140
  const c = str.charCodeAt(i);
2742
3141
  rawElements[i] = c;
@@ -2751,20 +3150,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2751
3150
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2752
3151
  };
2753
3152
 
2754
- let arrays = new Map();
2755
3153
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2756
3154
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2757
3155
  };
2758
3156
 
2759
3157
  export const generateMember = (scope, decl, _global, _name) => {
2760
3158
  const name = decl.object.name;
2761
- const pointer = arrays.get(name);
3159
+ const pointer = scope.arrays?.get(name);
2762
3160
 
2763
- const aotPointer = pointer != null;
3161
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2764
3162
 
2765
3163
  // hack: .length
2766
3164
  if (decl.property.name === 'length') {
2767
- // 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
+
2768
3176
  return [
2769
3177
  ...(aotPointer ? number(0, Valtype.i32) : [
2770
3178
  ...generate(scope, decl.object),
@@ -2808,7 +3216,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2808
3216
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2809
3217
 
2810
3218
  ...number(TYPES.number, Valtype.i32),
2811
- setLastType(scope)
3219
+ ...setLastType(scope)
2812
3220
  ],
2813
3221
 
2814
3222
  [TYPES.string]: [
@@ -2840,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2840
3248
  ...number(newPointer),
2841
3249
 
2842
3250
  ...number(TYPES.string, Valtype.i32),
2843
- setLastType(scope)
3251
+ ...setLastType(scope)
2844
3252
  ],
2845
3253
  [TYPES._bytestring]: [
2846
3254
  // setup new/out array
@@ -2859,19 +3267,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2859
3267
  ]),
2860
3268
 
2861
3269
  // load current string ind {arg}
2862
- [ 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) ],
2863
3271
 
2864
3272
  // store to new string ind 0
2865
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3273
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2866
3274
 
2867
3275
  // return new string (page)
2868
3276
  ...number(newPointer),
2869
3277
 
2870
3278
  ...number(TYPES._bytestring, Valtype.i32),
2871
- setLastType(scope)
3279
+ ...setLastType(scope)
2872
3280
  ],
2873
3281
 
2874
- 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)
2875
3283
  });
2876
3284
  };
2877
3285
 
@@ -2881,28 +3289,36 @@ const objectHack = node => {
2881
3289
  if (!node) return node;
2882
3290
 
2883
3291
  if (node.type === 'MemberExpression') {
2884
- if (node.computed || node.optional) return node;
3292
+ const out = (() => {
3293
+ if (node.computed || node.optional) return;
2885
3294
 
2886
- let objectName = node.object.name;
3295
+ let objectName = node.object.name;
2887
3296
 
2888
- // if object is not identifier or another member exp, give up
2889
- 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;
2890
3300
 
2891
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3301
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2892
3302
 
2893
- // if .length, give up (hack within a hack!)
2894
- 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
+ }
2895
3308
 
2896
- // no object name, give up
2897
- if (!objectName) return node;
3309
+ // no object name, give up
3310
+ if (!objectName) return;
2898
3311
 
2899
- const name = '__' + objectName + '_' + node.property.name;
2900
- 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}`);
2901
3314
 
2902
- return {
2903
- type: 'Identifier',
2904
- name
2905
- };
3315
+ return {
3316
+ type: 'Identifier',
3317
+ name
3318
+ };
3319
+ })();
3320
+
3321
+ if (out) return out;
2906
3322
  }
2907
3323
 
2908
3324
  for (const x in node) {
@@ -2916,8 +3332,8 @@ const objectHack = node => {
2916
3332
  };
2917
3333
 
2918
3334
  const generateFunc = (scope, decl) => {
2919
- if (decl.async) return todo('async functions are not supported');
2920
- 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');
2921
3337
 
2922
3338
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2923
3339
  const params = decl.params ?? [];
@@ -2933,6 +3349,11 @@ const generateFunc = (scope, decl) => {
2933
3349
  name
2934
3350
  };
2935
3351
 
3352
+ if (typedInput && decl.returnType) {
3353
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3354
+ innerScope.returns = [ valtypeBinary ];
3355
+ }
3356
+
2936
3357
  for (let i = 0; i < params.length; i++) {
2937
3358
  allocVar(innerScope, params[i].name, false);
2938
3359
 
@@ -2959,6 +3380,8 @@ const generateFunc = (scope, decl) => {
2959
3380
  };
2960
3381
  funcIndex[name] = func.index;
2961
3382
 
3383
+ if (name === 'main') func.gotLastType = true;
3384
+
2962
3385
  // quick hack fixes
2963
3386
  for (const inst of wasm) {
2964
3387
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2993,16 +3416,6 @@ const generateCode = (scope, decl) => {
2993
3416
  };
2994
3417
 
2995
3418
  const internalConstrs = {
2996
- Boolean: {
2997
- generate: (scope, decl) => {
2998
- if (decl.arguments.length === 0) return number(0);
2999
-
3000
- // should generate/run all args
3001
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3002
- },
3003
- type: TYPES.boolean
3004
- },
3005
-
3006
3419
  Array: {
3007
3420
  generate: (scope, decl, global, name) => {
3008
3421
  // new Array(i0, i1, ...)
@@ -3020,7 +3433,7 @@ const internalConstrs = {
3020
3433
 
3021
3434
  // todo: check in wasm instead of here
3022
3435
  const literalValue = arg.value ?? 0;
3023
- 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);
3024
3437
 
3025
3438
  return [
3026
3439
  ...number(0, Valtype.i32),
@@ -3031,7 +3444,8 @@ const internalConstrs = {
3031
3444
  ...number(pointer)
3032
3445
  ];
3033
3446
  },
3034
- type: TYPES._array
3447
+ type: TYPES._array,
3448
+ length: 1
3035
3449
  },
3036
3450
 
3037
3451
  __Array_of: {
@@ -3043,7 +3457,94 @@ const internalConstrs = {
3043
3457
  }, global, name);
3044
3458
  },
3045
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,
3046
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
3047
3548
  }
3048
3549
  };
3049
3550
 
@@ -3072,7 +3573,6 @@ export default program => {
3072
3573
  funcs = [];
3073
3574
  funcIndex = {};
3074
3575
  depth = [];
3075
- arrays = new Map();
3076
3576
  pages = new Map();
3077
3577
  data = [];
3078
3578
  currentFuncIndex = importedFuncs.length;
@@ -3086,6 +3586,10 @@ export default program => {
3086
3586
 
3087
3587
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3088
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
+
3089
3593
  // set generic opcodes for current valtype
3090
3594
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3091
3595
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3094,10 +3598,10 @@ export default program => {
3094
3598
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3095
3599
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3096
3600
 
3097
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3098
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3099
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3100
- 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];
3101
3605
 
3102
3606
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3103
3607
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3110,10 +3614,6 @@ export default program => {
3110
3614
 
3111
3615
  program.id = { name: 'main' };
3112
3616
 
3113
- globalThis.pageSize = PageSize;
3114
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3115
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3116
-
3117
3617
  const scope = {
3118
3618
  locals: {},
3119
3619
  localInd: 0
@@ -3124,7 +3624,7 @@ export default program => {
3124
3624
  body: program.body
3125
3625
  };
3126
3626
 
3127
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3627
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3128
3628
 
3129
3629
  generateFunc(scope, program);
3130
3630
 
@@ -3141,7 +3641,11 @@ export default program => {
3141
3641
  }
3142
3642
 
3143
3643
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3144
- 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
+ }
3145
3649
  }
3146
3650
 
3147
3651
  if (lastInst[0] === Opcodes.call) {