porffor 0.2.0-fde989a → 0.14.0-4057a18e9

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 (60) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +131 -86
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +216 -0
  7. package/compiler/2c.js +2 -53
  8. package/compiler/{sections.js → assemble.js} +84 -21
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/boolean.ts +18 -0
  14. package/compiler/builtins/crypto.ts +120 -0
  15. package/compiler/builtins/date.ts +2067 -0
  16. package/compiler/builtins/escape.ts +141 -0
  17. package/compiler/builtins/function.ts +5 -0
  18. package/compiler/builtins/int.ts +145 -0
  19. package/compiler/builtins/number.ts +529 -0
  20. package/compiler/builtins/object.ts +4 -0
  21. package/compiler/builtins/porffor.d.ts +60 -0
  22. package/compiler/builtins/set.ts +187 -0
  23. package/compiler/builtins/string.ts +1080 -0
  24. package/compiler/builtins.js +436 -283
  25. package/compiler/{codeGen.js → codegen.js} +1027 -482
  26. package/compiler/decompile.js +2 -3
  27. package/compiler/embedding.js +22 -22
  28. package/compiler/encoding.js +94 -10
  29. package/compiler/expression.js +1 -1
  30. package/compiler/generated_builtins.js +1625 -0
  31. package/compiler/index.js +25 -36
  32. package/compiler/log.js +6 -3
  33. package/compiler/opt.js +55 -41
  34. package/compiler/parse.js +38 -30
  35. package/compiler/precompile.js +120 -0
  36. package/compiler/prefs.js +27 -0
  37. package/compiler/prototype.js +31 -46
  38. package/compiler/types.js +38 -0
  39. package/compiler/wasmSpec.js +33 -8
  40. package/compiler/wrap.js +88 -70
  41. package/package.json +9 -5
  42. package/porf +2 -0
  43. package/rhemyn/compile.js +46 -27
  44. package/rhemyn/parse.js +322 -320
  45. package/rhemyn/test/parse.js +58 -58
  46. package/runner/compare.js +33 -34
  47. package/runner/debug.js +117 -0
  48. package/runner/index.js +78 -11
  49. package/runner/profiler.js +75 -0
  50. package/runner/repl.js +40 -13
  51. package/runner/sizes.js +37 -37
  52. package/runner/version.js +10 -8
  53. package/compiler/builtins/base64.js +0 -92
  54. package/filesize.cmd +0 -2
  55. package/runner/info.js +0 -89
  56. package/runner/profile.js +0 -46
  57. package/runner/results.json +0 -1
  58. package/runner/transform.js +0 -15
  59. package/tmp.c +0 -661
  60. package/util/enum.js +0 -20
@@ -1,12 +1,14 @@
1
- import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
- import { operatorOpcode } from "./expression.js";
4
- import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
- import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
- import { log } from "./log.js";
8
- import parse from "./parse.js";
9
- import * as Rhemyn from "../rhemyn/compile.js";
1
+ import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
3
+ import { operatorOpcode } from './expression.js';
4
+ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
+ import { PrototypeFuncs } from './prototype.js';
6
+ import { number } from './embedding.js';
7
+ import { TYPES, TYPE_NAMES } from './types.js';
8
+ import * as Rhemyn from '../rhemyn/compile.js';
9
+ import parse from './parse.js';
10
+ import { log } from './log.js';
11
+ import Prefs from './prefs.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -17,44 +19,32 @@ let funcIndex = {};
17
19
  let currentFuncIndex = importedFuncs.length;
18
20
  let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
19
21
 
20
- const debug = str => {
21
- const code = [];
22
-
23
- const logChar = n => {
24
- code.push(...number(n));
25
-
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
- };
29
-
30
- for (let i = 0; i < str.length; i++) {
31
- logChar(str.charCodeAt(i));
22
+ class TodoError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = 'TodoError';
32
26
  }
27
+ }
28
+ const todo = (scope, msg, expectsValue = undefined) => {
29
+ switch (Prefs.todoTime ?? 'runtime') {
30
+ case 'compile':
31
+ throw new TodoError(msg);
33
32
 
34
- logChar('\n'.charCodeAt(0));
33
+ case 'runtime':
34
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
35
35
 
36
- return code;
37
- };
38
-
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
36
+ // return [
37
+ // ...debug(`todo! ${msg}`),
38
+ // [ Opcodes.unreachable ]
39
+ // ];
45
40
  }
46
-
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
53
-
54
- return code;
55
41
  };
56
42
 
57
43
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
+ const hasFuncWithName = name => {
45
+ const func = funcs.find(x => x.name === name);
46
+ return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
+ };
58
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
49
  switch (decl.type) {
60
50
  case 'BinaryExpression':
@@ -104,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
94
  return generateUnary(scope, decl);
105
95
 
106
96
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
97
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
98
 
109
99
  case 'IfStatement':
110
100
  return generateIf(scope, decl);
@@ -115,6 +105,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
105
  case 'WhileStatement':
116
106
  return generateWhile(scope, decl);
117
107
 
108
+ case 'DoWhileStatement':
109
+ return generateDoWhile(scope, decl);
110
+
118
111
  case 'ForOfStatement':
119
112
  return generateForOf(scope, decl);
120
113
 
@@ -124,6 +117,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
117
  case 'ContinueStatement':
125
118
  return generateContinue(scope, decl);
126
119
 
120
+ case 'LabeledStatement':
121
+ return generateLabel(scope, decl);
122
+
127
123
  case 'EmptyStatement':
128
124
  return generateEmpty(scope, decl);
129
125
 
@@ -137,7 +133,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
133
  return generateTry(scope, decl);
138
134
 
139
135
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
136
+ // todo: hook into terminal debugger
141
137
  return [];
142
138
 
143
139
  case 'ArrayExpression':
@@ -151,16 +147,17 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
151
147
  const funcsBefore = funcs.length;
152
148
  generate(scope, decl.declaration);
153
149
 
154
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
155
-
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
150
+ if (funcsBefore !== funcs.length) {
151
+ // new func added
152
+ const newFunc = funcs[funcs.length - 1];
153
+ newFunc.export = true;
154
+ }
158
155
 
159
156
  return [];
160
157
 
161
158
  case 'TaggedTemplateExpression': {
162
159
  const funcs = {
163
- __Porffor_asm: str => {
160
+ __Porffor_wasm: str => {
164
161
  let out = [];
165
162
 
166
163
  for (const line of str.split('\n')) {
@@ -168,8 +165,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
165
  if (asm[0] === '') continue; // blank
169
166
 
170
167
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
168
+ const [ name, type ] = asm.slice(1);
169
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
170
  continue;
174
171
  }
175
172
 
@@ -179,52 +176,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
176
  }
180
177
 
181
178
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
179
+ allocPage(scope, 'asm instrinsic');
183
180
  // todo: add to store/load offset insts
184
181
  continue;
185
182
  }
186
183
 
187
184
  let inst = Opcodes[asm[0].replace('.', '_')];
188
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
185
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
186
 
190
187
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
188
+ const immediates = asm.slice(1).map(x => {
189
+ const int = parseInt(x);
190
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
191
+ return int;
192
+ });
192
193
 
193
- out.push([ ...inst, ...immediates ]);
194
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
195
  }
195
196
 
196
197
  return out;
197
198
  },
198
199
 
199
200
  __Porffor_bs: str => [
200
- ...makeString(scope, str, undefined, undefined, true),
201
+ ...makeString(scope, str, global, name, true),
201
202
 
202
- ...number(TYPES._bytestring, Valtype.i32),
203
- setLastType(scope)
203
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
204
+ ...number(TYPES.bytestring, Valtype.i32),
205
+ ...setLastType(scope)
206
+ ])
204
207
  ],
205
208
  __Porffor_s: str => [
206
- ...makeString(scope, str, undefined, undefined, false),
209
+ ...makeString(scope, str, global, name, false),
207
210
 
208
- ...number(TYPES.string, Valtype.i32),
209
- setLastType(scope)
211
+ ...(name ? setType(scope, name, TYPES.string) : [
212
+ ...number(TYPES.string, Valtype.i32),
213
+ ...setLastType(scope)
214
+ ])
210
215
  ],
211
216
  };
212
217
 
213
- const name = decl.tag.name;
218
+ const func = decl.tag.name;
214
219
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
220
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
221
+
222
+ const { quasis, expressions } = decl.quasi;
223
+ let str = quasis[0].value.raw;
224
+
225
+ for (let i = 0; i < expressions.length; i++) {
226
+ const e = expressions[i];
227
+ if (!e.name) {
228
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
229
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
230
+ } else todo(scope, 'unsupported expression in intrinsic');
231
+ } else str += lookupName(scope, e.name)[0].idx;
216
232
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
233
+ str += quasis[i + 1].value.raw;
234
+ }
235
+
236
+ return funcs[func](str);
219
237
  }
220
238
 
221
239
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
240
+ // ignore typescript nodes
241
+ if (decl.type.startsWith('TS') ||
242
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
243
  return [];
225
244
  }
226
245
 
227
- return todo(`no generation for ${decl.type}!`);
246
+ return todo(scope, `no generation for ${decl.type}!`);
228
247
  }
229
248
  };
230
249
 
@@ -252,7 +271,7 @@ const lookupName = (scope, _name) => {
252
271
  return [ undefined, undefined ];
253
272
  };
254
273
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
274
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
275
  ...generateThrow(scope, {
257
276
  argument: {
258
277
  type: 'NewExpression',
@@ -276,7 +295,10 @@ const generateIdent = (scope, decl) => {
276
295
 
277
296
  if (Object.hasOwn(builtinVars, name)) {
278
297
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
298
+
299
+ let wasm = builtinVars[name];
300
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
301
+ return wasm.slice();
280
302
  }
281
303
 
282
304
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -284,6 +306,11 @@ const generateIdent = (scope, decl) => {
284
306
  return number(1);
285
307
  }
286
308
 
309
+ if (isExistingProtoFunc(name)) {
310
+ // todo: return an actual something
311
+ return number(1);
312
+ }
313
+
287
314
  if (local?.idx === undefined) {
288
315
  // no local var with name
289
316
  if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
@@ -314,14 +341,18 @@ const generateReturn = (scope, decl) => {
314
341
  // just bare "return"
315
342
  return [
316
343
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
344
+ ...(scope.returnType != null ? [] : [
345
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
346
+ ]),
318
347
  [ Opcodes.return ]
319
348
  ];
320
349
  }
321
350
 
322
351
  return [
323
352
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
353
+ ...(scope.returnType != null ? [] : [
354
+ ...getNodeType(scope, decl.argument)
355
+ ]),
325
356
  [ Opcodes.return ]
326
357
  ];
327
358
  };
@@ -335,7 +366,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
366
  return idx;
336
367
  };
337
368
 
338
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
369
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
370
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
339
371
 
340
372
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
373
  const checks = {
@@ -344,7 +376,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
376
  '??': nullish
345
377
  };
346
378
 
347
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
379
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
348
380
 
349
381
  // generic structure for {a} OP {b}
350
382
  // -->
@@ -352,8 +384,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
384
 
353
385
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
386
  // (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]);
387
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
388
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
389
 
358
390
  const canInt = leftIsInt && rightIsInt;
359
391
 
@@ -370,12 +402,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
402
  ...right,
371
403
  // note type
372
404
  ...rightType,
373
- setLastType(scope),
405
+ ...setLastType(scope),
374
406
  [ Opcodes.else ],
375
407
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
408
  // note type
377
409
  ...leftType,
378
- setLastType(scope),
410
+ ...setLastType(scope),
379
411
  [ Opcodes.end ],
380
412
  Opcodes.i32_from
381
413
  ];
@@ -389,17 +421,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
421
  ...right,
390
422
  // note type
391
423
  ...rightType,
392
- setLastType(scope),
424
+ ...setLastType(scope),
393
425
  [ Opcodes.else ],
394
426
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
427
  // note type
396
428
  ...leftType,
397
- setLastType(scope),
429
+ ...setLastType(scope),
398
430
  [ Opcodes.end ]
399
431
  ];
400
432
  };
401
433
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
434
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
435
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
436
  // todo: convert left and right to strings if not
405
437
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +441,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
441
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
442
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
443
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
- if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
444
+ if (assign && Prefs.aotPointerOpt) {
445
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
446
 
418
447
  return [
419
448
  // setup right
@@ -438,11 +467,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
467
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
468
 
440
469
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
470
+ // dst = out pointer + length size + current length * sizeof valtype
442
471
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
472
 
444
473
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
474
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
475
  [ Opcodes.i32_mul ],
447
476
  [ Opcodes.i32_add ],
448
477
 
@@ -451,9 +480,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
480
  ...number(ValtypeSize.i32, Valtype.i32),
452
481
  [ Opcodes.i32_add ],
453
482
 
454
- // size = right length * i16 size
483
+ // size = right length * sizeof valtype
455
484
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
485
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
486
  [ Opcodes.i32_mul ],
458
487
 
459
488
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +540,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
540
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
541
 
513
542
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
543
+ // dst = out pointer + length size + left length * sizeof valtype
515
544
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
545
 
517
546
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
547
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
548
  [ Opcodes.i32_mul ],
520
549
  [ Opcodes.i32_add ],
521
550
 
@@ -524,9 +553,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
553
  ...number(ValtypeSize.i32, Valtype.i32),
525
554
  [ Opcodes.i32_add ],
526
555
 
527
- // size = right length * i16 size
556
+ // size = right length * sizeof valtype
528
557
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
558
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
559
  [ Opcodes.i32_mul ],
531
560
 
532
561
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +565,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
565
  ];
537
566
  };
538
567
 
539
- const compareStrings = (scope, left, right) => {
568
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
569
  // todo: this should be rewritten into a func
541
570
  // todo: convert left and right to strings if not
542
571
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +574,6 @@ const compareStrings = (scope, left, right) => {
545
574
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
575
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
576
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
577
 
550
578
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
579
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +601,6 @@ const compareStrings = (scope, left, right) => {
573
601
 
574
602
  [ Opcodes.local_get, rightPointer ],
575
603
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
604
 
578
605
  // fast path: check leftLength != rightLength
579
606
  [ Opcodes.i32_ne ],
@@ -588,11 +615,13 @@ const compareStrings = (scope, left, right) => {
588
615
  ...number(0, Valtype.i32),
589
616
  [ Opcodes.local_set, index ],
590
617
 
591
- // setup index end as length * sizeof i16 (2)
618
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
619
  // we do this instead of having to do mul/div each iter for perf™
593
620
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
621
+ ...(bytestrings ? [] : [
622
+ ...number(ValtypeSize.i16, Valtype.i32),
623
+ [ Opcodes.i32_mul ],
624
+ ]),
596
625
  [ Opcodes.local_set, indexEnd ],
597
626
 
598
627
  // iterate over each char and check if eq
@@ -602,13 +631,17 @@ const compareStrings = (scope, left, right) => {
602
631
  [ Opcodes.local_get, index ],
603
632
  [ Opcodes.local_get, leftPointer ],
604
633
  [ Opcodes.i32_add ],
605
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
634
+ bytestrings ?
635
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
636
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
606
637
 
607
638
  // fetch right
608
639
  [ Opcodes.local_get, index ],
609
640
  [ Opcodes.local_get, rightPointer ],
610
641
  [ Opcodes.i32_add ],
611
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
642
+ bytestrings ?
643
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
644
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
612
645
 
613
646
  // not equal, "return" false
614
647
  [ Opcodes.i32_ne ],
@@ -617,13 +650,13 @@ const compareStrings = (scope, left, right) => {
617
650
  [ Opcodes.br, 2 ],
618
651
  [ Opcodes.end ],
619
652
 
620
- // index += sizeof i16 (2)
653
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
654
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
655
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
656
  [ Opcodes.i32_add ],
624
657
  [ Opcodes.local_tee, index ],
625
658
 
626
- // if index != index end (length * sizeof 16), loop
659
+ // if index != index end (length * sizeof valtype), loop
627
660
  [ Opcodes.local_get, indexEnd ],
628
661
  [ Opcodes.i32_ne ],
629
662
  [ Opcodes.br_if, 0 ],
@@ -644,16 +677,18 @@ const compareStrings = (scope, left, right) => {
644
677
  };
645
678
 
646
679
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
680
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
681
  ...wasm,
649
682
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
683
  ];
684
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
685
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
686
+ const useTmp = knownType(scope, type) == null;
687
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
688
 
654
689
  const def = [
655
690
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
691
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
692
 
658
693
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
694
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,16 +700,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
700
 
666
701
  return [
667
702
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
703
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
704
 
670
705
  ...typeSwitch(scope, type, {
671
706
  // [TYPES.number]: def,
672
- [TYPES._array]: [
707
+ [TYPES.array]: [
673
708
  // arrays are always truthy
674
709
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
710
  ],
676
711
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
712
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
713
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
714
 
680
715
  // get length
@@ -685,8 +720,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
720
  [ Opcodes.i32_eqz ], */
686
721
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
722
  ],
688
- [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
723
+ [TYPES.bytestring]: [ // duplicate of string
724
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
725
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
726
 
692
727
  // get length
@@ -700,18 +735,20 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
735
  };
701
736
 
702
737
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
738
+ const useTmp = knownType(scope, type) == null;
739
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
740
+
704
741
  return [
705
742
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
744
 
708
745
  ...typeSwitch(scope, type, {
709
- [TYPES._array]: [
746
+ [TYPES.array]: [
710
747
  // arrays are always truthy
711
748
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
749
  ],
713
750
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
751
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
753
 
717
754
  // get length
@@ -721,8 +758,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
721
758
  [ Opcodes.i32_eqz ],
722
759
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
760
  ],
724
- [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
761
+ [TYPES.bytestring]: [ // duplicate of string
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
763
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
764
 
728
765
  // get length
@@ -734,7 +771,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
771
  ],
735
772
  default: [
736
773
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
774
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
775
 
739
776
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
777
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +781,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
781
  };
745
782
 
746
783
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
784
+ const useTmp = knownType(scope, type) == null;
785
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
786
+
748
787
  return [
749
788
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
789
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
790
 
752
791
  ...typeSwitch(scope, type, {
753
792
  [TYPES.undefined]: [
@@ -756,7 +795,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
795
  ],
757
796
  [TYPES.object]: [
758
797
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
798
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
799
 
761
800
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
801
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,39 +824,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
824
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
825
  }
787
826
 
827
+ const knownLeft = knownType(scope, leftType);
828
+ const knownRight = knownType(scope, rightType);
829
+
788
830
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
831
  const strictOp = op === '===' || op === '!==';
790
832
 
791
833
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
834
+ const finalize = out => startOut.concat(out, endOut);
793
835
 
794
836
  // if strict (in)equal check types match
795
837
  if (strictOp) {
796
- // startOut.push(
797
- // ...leftType,
798
- // ...rightType,
799
- // [ Opcodes.i32_eq ]
800
- // );
801
-
802
- // endOut.push(
803
- // [ Opcodes.i32_and ]
804
- // );
805
-
806
- // startOut.push(
807
- // [ Opcodes.block, Valtype.i32 ],
808
- // ...leftType,
809
- // ...rightType,
810
- // [ Opcodes.i32_ne ],
811
- // [ Opcodes.if, Blocktype.void ],
812
- // ...number(op === '===' ? 0 : 1, Valtype.i32),
813
- // [ Opcodes.br, 1 ],
814
- // [ Opcodes.end ]
815
- // );
816
-
817
- // endOut.push(
818
- // [ Opcodes.end ]
819
- // );
820
-
821
838
  endOut.push(
822
839
  ...leftType,
823
840
  ...rightType,
@@ -834,31 +851,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
851
  // todo: if equality op and an operand is undefined, return false
835
852
  // todo: niche null hell with 0
836
853
 
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
- // }
854
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
855
+ if (op === '+') {
856
+ // todo: this should be dynamic too but for now only static
857
+ // string concat (a + b)
858
+ return concatStrings(scope, left, right, _global, _name, assign, false);
859
+ }
860
+
861
+ // not an equality op, NaN
862
+ if (!eqOp) return number(NaN);
863
+
864
+ // else leave bool ops
865
+ // todo: convert string to number if string and number/bool
866
+ // todo: string (>|>=|<|<=) string
867
+
868
+ // string comparison
869
+ if (op === '===' || op === '==') {
870
+ return compareStrings(scope, left, right);
871
+ }
872
+
873
+ if (op === '!==' || op === '!=') {
874
+ return [
875
+ ...compareStrings(scope, left, right),
876
+ [ Opcodes.i32_eqz ]
877
+ ];
878
+ }
879
+ }
880
+
881
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
882
+ if (op === '+') {
883
+ // todo: this should be dynamic too but for now only static
884
+ // string concat (a + b)
885
+ return concatStrings(scope, left, right, _global, _name, assign, true);
886
+ }
887
+
888
+ // not an equality op, NaN
889
+ if (!eqOp) return number(NaN);
890
+
891
+ // else leave bool ops
892
+ // todo: convert string to number if string and number/bool
893
+ // todo: string (>|>=|<|<=) string
894
+
895
+ // string comparison
896
+ if (op === '===' || op === '==') {
897
+ return compareStrings(scope, left, right, true);
898
+ }
899
+
900
+ if (op === '!==' || op === '!=') {
901
+ return [
902
+ ...compareStrings(scope, left, right, true),
903
+ [ Opcodes.i32_eqz ]
904
+ ];
905
+ }
906
+ }
862
907
 
863
908
  let ops = operatorOpcode[valtype][op];
864
909
 
@@ -868,33 +913,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
913
  includeBuiltin(scope, builtinName);
869
914
  const idx = funcIndex[builtinName];
870
915
 
871
- return finalise([
916
+ return finalize([
872
917
  ...left,
873
918
  ...right,
874
919
  [ Opcodes.call, idx ]
875
920
  ]);
876
921
  }
877
922
 
878
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
923
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
879
924
 
880
925
  if (!Array.isArray(ops)) ops = [ ops ];
881
926
  ops = [ ops ];
882
927
 
883
928
  let tmpLeft, tmpRight;
884
929
  // 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
- }
930
+ // todo: intelligent partial skip later
931
+ // if neither known are string, stop this madness
932
+ // we already do known checks earlier, so don't need to recheck
894
933
 
934
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
935
  tmpLeft = localTmp(scope, '__tmpop_left');
896
936
  tmpRight = localTmp(scope, '__tmpop_right');
897
937
 
938
+ // returns false for one string, one not - but more ops/slower
939
+ // ops.unshift(...stringOnly([
940
+ // // if left is string
941
+ // ...leftType,
942
+ // ...number(TYPES.string, Valtype.i32),
943
+ // [ Opcodes.i32_eq ],
944
+
945
+ // // if right is string
946
+ // ...rightType,
947
+ // ...number(TYPES.string, Valtype.i32),
948
+ // [ Opcodes.i32_eq ],
949
+
950
+ // // if either are true
951
+ // [ Opcodes.i32_or ],
952
+ // [ Opcodes.if, Blocktype.void ],
953
+
954
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
955
+ // // if left is not string
956
+ // ...leftType,
957
+ // ...number(TYPES.string, Valtype.i32),
958
+ // [ Opcodes.i32_ne ],
959
+
960
+ // // if right is not string
961
+ // ...rightType,
962
+ // ...number(TYPES.string, Valtype.i32),
963
+ // [ Opcodes.i32_ne ],
964
+
965
+ // // if either are true
966
+ // [ Opcodes.i32_or ],
967
+ // [ Opcodes.if, Blocktype.void ],
968
+ // ...number(0, Valtype.i32),
969
+ // [ Opcodes.br, 2 ],
970
+ // [ Opcodes.end ],
971
+
972
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
973
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
974
+ // [ Opcodes.br, 1 ],
975
+ // [ Opcodes.end ],
976
+ // ]));
977
+
978
+ // does not handle one string, one not (such cases go past)
898
979
  ops.unshift(...stringOnly([
899
980
  // if left is string
900
981
  ...leftType,
@@ -906,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
987
  ...number(TYPES.string, Valtype.i32),
907
988
  [ Opcodes.i32_eq ],
908
989
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
990
+ // if both are true
991
+ [ Opcodes.i32_and ],
911
992
  [ Opcodes.if, Blocktype.void ],
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
994
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
995
+ [ Opcodes.br, 1 ],
996
+ [ Opcodes.end ],
912
997
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
998
+ // if left is bytestring
915
999
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
1000
+ ...number(TYPES.bytestring, Valtype.i32),
1001
+ [ Opcodes.i32_eq ],
918
1002
 
919
- // if right is not string
1003
+ // if right is bytestring
920
1004
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1005
+ ...number(TYPES.bytestring, Valtype.i32),
1006
+ [ Opcodes.i32_eq ],
923
1007
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1008
+ // if both are true
1009
+ [ Opcodes.i32_and ],
926
1010
  [ 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 ] ]),
1011
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1012
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1013
  [ Opcodes.br, 1 ],
935
1014
  [ Opcodes.end ],
@@ -941,9 +1020,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1020
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1021
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1022
  // }
944
- })();
1023
+ }
945
1024
 
946
- return finalise([
1025
+ return finalize([
947
1026
  ...left,
948
1027
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1028
  ...right,
@@ -960,7 +1039,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1039
  return out;
961
1040
  };
962
1041
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1042
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1043
+ return func({ name, params, locals, returns, localInd }, {
1044
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1045
+ builtin: name => {
1046
+ let idx = funcIndex[name] ?? importedFuncs[name];
1047
+ if (idx === undefined && builtinFuncs[name]) {
1048
+ includeBuiltin(null, name);
1049
+ idx = funcIndex[name];
1050
+ }
1051
+
1052
+ return idx;
1053
+ }
1054
+ });
1055
+ };
1056
+
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
964
1058
  const existing = funcs.find(x => x.name === name);
965
1059
  if (existing) return existing;
966
1060
 
@@ -972,18 +1066,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1066
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1067
  }
974
1068
 
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 });
1069
+ for (const x of _data) {
1070
+ const copy = { ...x };
1071
+ copy.offset += pages.size * pageSize;
1072
+ data.push(copy);
985
1073
  }
986
1074
 
1075
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1076
+
987
1077
  let baseGlobalIdx, i = 0;
988
1078
  for (const type of globalTypes) {
989
1079
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1006,7 +1096,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1006
1096
  params,
1007
1097
  locals,
1008
1098
  returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1099
+ returnType: returnType ?? TYPES.number,
1010
1100
  wasm,
1011
1101
  internal: true,
1012
1102
  index: currentFuncIndex++
@@ -1029,6 +1119,7 @@ const generateLogicExp = (scope, decl) => {
1029
1119
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1120
  };
1031
1121
 
1122
+ // potential future ideas for nan boxing (unused):
1032
1123
  // T = JS type, V = value/pointer
1033
1124
  // 0bTTT
1034
1125
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1042,7 +1133,6 @@ const generateLogicExp = (scope, decl) => {
1042
1133
  // js type: 4 bits
1043
1134
  // internal type: ? bits
1044
1135
  // pointer: 32 bits
1045
-
1046
1136
  // generic
1047
1137
  // 1 23 4 5
1048
1138
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1052,49 +1142,29 @@ const generateLogicExp = (scope, decl) => {
1052
1142
  // 4: internal type
1053
1143
  // 5: pointer
1054
1144
 
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'
1145
+ const isExistingProtoFunc = name => {
1146
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1147
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1148
+
1149
+ return false;
1084
1150
  };
1085
1151
 
1086
1152
  const getType = (scope, _name) => {
1087
1153
  const name = mapName(_name);
1088
1154
 
1155
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1156
+
1157
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1158
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1159
+
1160
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1161
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1162
 
1092
1163
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1164
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1165
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1166
 
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;
1167
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1168
 
1099
1169
  return number(type, Valtype.i32);
1100
1170
  };
@@ -1117,23 +1187,24 @@ const setType = (scope, _name, type) => {
1117
1187
  ];
1118
1188
 
1119
1189
  // throw new Error('could not find var');
1190
+ return [];
1120
1191
  };
1121
1192
 
1122
1193
  const getLastType = scope => {
1123
1194
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1195
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1196
  };
1126
1197
 
1127
1198
  const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1199
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1129
1200
  };
1130
1201
 
1131
1202
  const getNodeType = (scope, node) => {
1132
- const inner = () => {
1203
+ const ret = (() => {
1133
1204
  if (node.type === 'Literal') {
1134
- if (node.regex) return TYPES._regexp;
1205
+ if (node.regex) return TYPES.regexp;
1135
1206
 
1136
- if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1207
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1137
1208
 
1138
1209
  return TYPES[typeof node.value];
1139
1210
  }
@@ -1150,21 +1221,32 @@ const getNodeType = (scope, node) => {
1150
1221
  const name = node.callee.name;
1151
1222
  if (!name) {
1152
1223
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1224
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1225
 
1155
1226
  // presume
1156
1227
  // todo: warn here?
1157
1228
  return TYPES.number;
1158
1229
  }
1159
1230
 
1231
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1232
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1233
+ if (scope.locals['#last_type']) return getLastType(scope);
1234
+
1235
+ // presume
1236
+ // todo: warn here?
1237
+ return TYPES.number;
1238
+ }
1239
+
1240
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1241
+ }
1242
+
1160
1243
  const func = funcs.find(x => x.name === name);
1161
1244
 
1162
1245
  if (func) {
1163
- // console.log(scope, func, func.returnType);
1164
1246
  if (func.returnType) return func.returnType;
1165
1247
  }
1166
1248
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1249
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1250
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1251
 
1170
1252
  // check if this is a prototype function
@@ -1175,11 +1257,16 @@ const getNodeType = (scope, node) => {
1175
1257
  const spl = name.slice(2).split('_');
1176
1258
 
1177
1259
  const func = spl[spl.length - 1];
1178
- const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1260
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1179
1261
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1262
  }
1181
1263
 
1182
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1264
+ if (name.startsWith('__Porffor_wasm_')) {
1265
+ // todo: return undefined for non-returning ops
1266
+ return TYPES.number;
1267
+ }
1268
+
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1183
1270
 
1184
1271
  // presume
1185
1272
  // todo: warn here?
@@ -1222,11 +1309,20 @@ const getNodeType = (scope, node) => {
1222
1309
  }
1223
1310
 
1224
1311
  if (node.type === 'ArrayExpression') {
1225
- return TYPES._array;
1312
+ return TYPES.array;
1226
1313
  }
1227
1314
 
1228
1315
  if (node.type === 'BinaryExpression') {
1229
1316
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1317
+ if (node.operator !== '+') return TYPES.number;
1318
+
1319
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1320
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1321
+
1322
+ // todo: this should be dynamic but for now only static
1323
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1324
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1325
+
1230
1326
  return TYPES.number;
1231
1327
 
1232
1328
  // todo: string concat types
@@ -1251,34 +1347,47 @@ const getNodeType = (scope, node) => {
1251
1347
  if (node.operator === '!') return TYPES.boolean;
1252
1348
  if (node.operator === 'void') return TYPES.undefined;
1253
1349
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1350
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1255
1351
 
1256
1352
  return TYPES.number;
1257
1353
  }
1258
1354
 
1259
1355
  if (node.type === 'MemberExpression') {
1356
+ // hack: if something.name, string type
1357
+ if (node.property.name === 'name') {
1358
+ if (hasFuncWithName(node.object.name)) {
1359
+ return TYPES.bytestring;
1360
+ } else {
1361
+ return TYPES.undefined;
1362
+ }
1363
+ }
1364
+
1260
1365
  // hack: if something.length, number type
1261
1366
  if (node.property.name === 'length') return TYPES.number;
1262
1367
 
1263
1368
  // ts hack
1264
1369
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1265
- if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1370
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1371
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1266
1372
 
1267
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1373
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
1374
 
1269
1375
  // presume
1270
1376
  return TYPES.number;
1271
1377
  }
1272
1378
 
1273
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1379
+ if (node.type === 'TaggedTemplateExpression') {
1380
+ // hack
1381
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1382
+ }
1383
+
1384
+ if (scope.locals['#last_type']) return getLastType(scope);
1274
1385
 
1275
1386
  // presume
1276
1387
  // todo: warn here?
1277
1388
  return TYPES.number;
1278
- };
1389
+ })();
1279
1390
 
1280
- const ret = inner();
1281
- // console.trace(node, ret);
1282
1391
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1283
1392
  return ret;
1284
1393
  };
@@ -1303,7 +1412,7 @@ const generateLiteral = (scope, decl, global, name) => {
1303
1412
  return makeString(scope, decl.value, global, name);
1304
1413
 
1305
1414
  default:
1306
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1415
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1307
1416
  }
1308
1417
  };
1309
1418
 
@@ -1312,6 +1421,8 @@ const countLeftover = wasm => {
1312
1421
 
1313
1422
  for (let i = 0; i < wasm.length; i++) {
1314
1423
  const inst = wasm[i];
1424
+ if (inst[0] == null) continue;
1425
+
1315
1426
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1316
1427
  if (inst[0] === Opcodes.if) count--;
1317
1428
  if (inst[1] !== Blocktype.void) count++;
@@ -1320,18 +1431,25 @@ const countLeftover = wasm => {
1320
1431
  if (inst[0] === Opcodes.end) depth--;
1321
1432
 
1322
1433
  if (depth === 0)
1323
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1434
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1324
1435
  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
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1436
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1326
1437
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1327
1438
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1328
1439
  else if (inst[0] === Opcodes.return) count = 0;
1329
1440
  else if (inst[0] === Opcodes.call) {
1330
1441
  let func = funcs.find(x => x.index === inst[1]);
1331
- if (func) {
1332
- count -= func.params.length;
1333
- } else count--;
1334
- if (func) count += func.returns.length;
1442
+ if (inst[1] === -1) {
1443
+ // todo: count for calling self
1444
+ } else if (!func && inst[1] < importedFuncs.length) {
1445
+ count -= importedFuncs[inst[1]].params;
1446
+ count += importedFuncs[inst[1]].returns;
1447
+ } else {
1448
+ if (func) {
1449
+ count -= func.params.length;
1450
+ } else count--;
1451
+ if (func) count += func.returns.length;
1452
+ }
1335
1453
  } else count--;
1336
1454
 
1337
1455
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1408,25 +1526,27 @@ const RTArrayUtil = {
1408
1526
  };
1409
1527
 
1410
1528
  const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1411
- /* const callee = decl.callee;
1412
- const args = decl.arguments;
1413
-
1414
- return [
1415
- ...generate(args),
1416
- ...generate(callee),
1417
- Opcodes.call_indirect,
1418
- ]; */
1419
-
1420
1529
  let name = mapName(decl.callee.name);
1421
1530
  if (isFuncType(decl.callee.type)) { // iife
1422
1531
  const func = generateFunc(scope, decl.callee);
1423
1532
  name = func.name;
1424
1533
  }
1425
1534
 
1426
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1535
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1427
1536
  // literal eval hack
1428
- const code = decl.arguments[0].value;
1429
- const parsed = parse(code, []);
1537
+ const code = decl.arguments[0]?.value ?? '';
1538
+
1539
+ let parsed;
1540
+ try {
1541
+ parsed = parse(code, []);
1542
+ } catch (e) {
1543
+ if (e.name === 'SyntaxError') {
1544
+ // throw syntax errors of evals at runtime instead
1545
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1546
+ }
1547
+
1548
+ throw e;
1549
+ }
1430
1550
 
1431
1551
  const out = generate(scope, {
1432
1552
  type: 'BlockStatement',
@@ -1440,13 +1560,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1440
1560
  const finalStatement = parsed.body[parsed.body.length - 1];
1441
1561
  out.push(
1442
1562
  ...getNodeType(scope, finalStatement),
1443
- setLastType(scope)
1563
+ ...setLastType(scope)
1444
1564
  );
1445
1565
  } else if (countLeftover(out) === 0) {
1446
1566
  out.push(...number(UNDEFINED));
1447
1567
  out.push(
1448
1568
  ...number(TYPES.undefined, Valtype.i32),
1449
- setLastType(scope)
1569
+ ...setLastType(scope)
1450
1570
  );
1451
1571
  }
1452
1572
 
@@ -1468,6 +1588,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1468
1588
 
1469
1589
  target = { ...decl.callee };
1470
1590
  target.name = spl.slice(0, -1).join('_');
1591
+
1592
+ // failed to lookup name, abort
1593
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1471
1594
  }
1472
1595
 
1473
1596
  // literal.func()
@@ -1475,22 +1598,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1475
1598
  // megahack for /regex/.func()
1476
1599
  const funcName = decl.callee.property.name;
1477
1600
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1478
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1601
+ const regex = decl.callee.object.regex.pattern;
1602
+ const rhemynName = `regex_${funcName}_${regex}`;
1479
1603
 
1480
- funcIndex[func.name] = func.index;
1481
- funcs.push(func);
1604
+ if (!funcIndex[rhemynName]) {
1605
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1482
1606
 
1607
+ funcIndex[func.name] = func.index;
1608
+ funcs.push(func);
1609
+ }
1610
+
1611
+ const idx = funcIndex[rhemynName];
1483
1612
  return [
1484
1613
  // make string arg
1485
1614
  ...generate(scope, decl.arguments[0]),
1615
+ Opcodes.i32_to_u,
1616
+ ...getNodeType(scope, decl.arguments[0]),
1486
1617
 
1487
1618
  // call regex func
1488
- Opcodes.i32_to_u,
1489
- [ Opcodes.call, func.index ],
1619
+ [ Opcodes.call, idx ],
1490
1620
  Opcodes.i32_from_u,
1491
1621
 
1492
1622
  ...number(TYPES.boolean, Valtype.i32),
1493
- setLastType(scope)
1623
+ ...setLastType(scope)
1494
1624
  ];
1495
1625
  }
1496
1626
 
@@ -1515,12 +1645,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1645
  // }
1516
1646
 
1517
1647
  if (protoName) {
1648
+ const protoBC = {};
1649
+
1650
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1651
+
1652
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1653
+ for (const x of builtinProtoCands) {
1654
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1655
+ if (type == null) continue;
1656
+
1657
+ protoBC[type] = generateCall(scope, {
1658
+ callee: {
1659
+ type: 'Identifier',
1660
+ name: x
1661
+ },
1662
+ arguments: [ target, ...decl.arguments ],
1663
+ _protoInternalCall: true
1664
+ });
1665
+ }
1666
+ }
1667
+
1518
1668
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1519
1669
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1520
1670
  return acc;
1521
1671
  }, {});
1522
1672
 
1523
- // no prototype function candidates, ignore
1524
1673
  if (Object.keys(protoCands).length > 0) {
1525
1674
  // use local for cached i32 length as commonly used
1526
1675
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1538,7 +1687,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1538
1687
 
1539
1688
  let allOptUnused = true;
1540
1689
  let lengthI32CacheUsed = false;
1541
- const protoBC = {};
1542
1690
  for (const x in protoCands) {
1543
1691
  const protoFunc = protoCands[x];
1544
1692
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1546,13 +1694,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1546
1694
  ...RTArrayUtil.getLength(getPointer),
1547
1695
 
1548
1696
  ...number(TYPES.number, Valtype.i32),
1549
- setLastType(scope)
1697
+ ...setLastType(scope)
1550
1698
  ];
1551
1699
  continue;
1552
1700
  }
1553
1701
 
1554
- // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1555
- // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1556
1702
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1557
1703
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1558
1704
 
@@ -1583,7 +1729,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1583
1729
  ...protoOut,
1584
1730
 
1585
1731
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1586
- setLastType(scope),
1732
+ ...setLastType(scope),
1587
1733
  [ Opcodes.end ]
1588
1734
  ];
1589
1735
  }
@@ -1609,10 +1755,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1755
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1610
1756
  ];
1611
1757
  }
1758
+
1759
+ if (Object.keys(protoBC).length > 0) {
1760
+ return typeSwitch(scope, getNodeType(scope, target), {
1761
+ ...protoBC,
1762
+
1763
+ // TODO: error better
1764
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1765
+ }, valtypeBinary);
1766
+ }
1612
1767
  }
1613
1768
 
1614
1769
  // TODO: only allows callee as literal
1615
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1770
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1616
1771
 
1617
1772
  let idx = funcIndex[name] ?? importedFuncs[name];
1618
1773
  if (idx === undefined && builtinFuncs[name]) {
@@ -1620,22 +1775,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1775
 
1621
1776
  includeBuiltin(scope, name);
1622
1777
  idx = funcIndex[name];
1623
-
1624
- // infer arguments types from builtins params
1625
- const func = funcs.find(x => x.name === name);
1626
- for (let i = 0; i < decl.arguments.length; i++) {
1627
- const arg = decl.arguments[i];
1628
- if (!arg.name) continue;
1629
-
1630
- const local = scope.locals[arg.name];
1631
- if (!local) continue;
1632
-
1633
- local.type = func.params[i];
1634
- if (local.type === Valtype.v128) {
1635
- // specify vec subtype inferred from last vec type in function name
1636
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1637
- }
1638
- }
1639
1778
  }
1640
1779
 
1641
1780
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1645,16 +1784,62 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1784
  idx = -1;
1646
1785
  }
1647
1786
 
1787
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1788
+ const wasmOps = {
1789
+ // pointer, align, offset
1790
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1791
+ // pointer, value, align, offset
1792
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1793
+ // pointer, align, offset
1794
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1795
+ // pointer, value, align, offset
1796
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1797
+ // pointer, align, offset
1798
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1799
+ // pointer, value, align, offset
1800
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1801
+
1802
+ // pointer, align, offset
1803
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1804
+ // pointer, value, align, offset
1805
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1806
+
1807
+ // value
1808
+ i32_const: { imms: 1, args: [], returns: 1 },
1809
+ };
1810
+
1811
+ const opName = name.slice('__Porffor_wasm_'.length);
1812
+
1813
+ if (wasmOps[opName]) {
1814
+ const op = wasmOps[opName];
1815
+
1816
+ const argOut = [];
1817
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1818
+ ...generate(scope, decl.arguments[i]),
1819
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1820
+ );
1821
+
1822
+ // literals only
1823
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1824
+
1825
+ return [
1826
+ ...argOut,
1827
+ [ Opcodes[opName], ...imms ],
1828
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1829
+ ];
1830
+ }
1831
+ }
1832
+
1648
1833
  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`);
1834
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1835
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1651
1836
  }
1652
1837
 
1653
1838
  const func = funcs.find(x => x.index === idx);
1654
1839
 
1655
1840
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1841
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1842
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1658
1843
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1844
 
1660
1845
  let args = decl.arguments;
@@ -1671,14 +1856,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1856
  if (func && func.throws) scope.throws = true;
1672
1857
 
1673
1858
  let out = [];
1674
- for (const arg of args) {
1859
+ for (let i = 0; i < args.length; i++) {
1860
+ const arg = args[i];
1675
1861
  out = out.concat(generate(scope, arg));
1862
+
1863
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1864
+ out.push(Opcodes.i32_to);
1865
+ }
1866
+
1867
+ if (importedFuncs[name] && name.startsWith('profile')) {
1868
+ out.push(Opcodes.i32_to);
1869
+ }
1870
+
1676
1871
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1872
  }
1678
1873
 
1679
1874
  out.push([ Opcodes.call, idx ]);
1680
1875
 
1681
- if (!typedReturn) {
1876
+ if (!typedReturns) {
1682
1877
  // let type;
1683
1878
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1879
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1688,7 +1883,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1688
1883
  // ...number(type, Valtype.i32),
1689
1884
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1690
1885
  // );
1691
- } else out.push(setLastType(scope));
1886
+ } else out.push(...setLastType(scope));
1887
+
1888
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1889
+ out.push(Opcodes.i32_from);
1890
+ }
1692
1891
 
1693
1892
  return out;
1694
1893
  };
@@ -1696,8 +1895,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1696
1895
  const generateNew = (scope, decl, _global, _name) => {
1697
1896
  // hack: basically treat this as a normal call for builtins for now
1698
1897
  const name = mapName(decl.callee.name);
1898
+
1699
1899
  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)})`);
1900
+
1901
+ if (builtinFuncs[name + '$constructor']) {
1902
+ // custom ...$constructor override builtin func
1903
+ return generateCall(scope, {
1904
+ ...decl,
1905
+ callee: {
1906
+ type: 'Identifier',
1907
+ name: name + '$constructor'
1908
+ }
1909
+ }, _global, _name);
1910
+ }
1911
+
1912
+ 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
1913
 
1702
1914
  return generateCall(scope, decl, _global, _name);
1703
1915
  };
@@ -1814,14 +2026,14 @@ const brTable = (input, bc, returns) => {
1814
2026
  };
1815
2027
 
1816
2028
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2029
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
1818
2030
 
1819
2031
  const known = knownType(scope, type);
1820
2032
  if (known != null) {
1821
2033
  return bc[known] ?? bc.default;
1822
2034
  }
1823
2035
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
2036
+ if (Prefs.typeswitchUseBrtable)
1825
2037
  return brTable(type, bc, returns);
1826
2038
 
1827
2039
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1831,8 +2043,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
2043
  [ Opcodes.block, returns ]
1832
2044
  ];
1833
2045
 
1834
- // todo: use br_table?
1835
-
1836
2046
  for (const x in bc) {
1837
2047
  if (x === 'default') continue;
1838
2048
 
@@ -1856,7 +2066,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
2066
  return out;
1857
2067
  };
1858
2068
 
1859
- const allocVar = (scope, name, global = false) => {
2069
+ const allocVar = (scope, name, global = false, type = true) => {
1860
2070
  const target = global ? globals : scope.locals;
1861
2071
 
1862
2072
  // already declared
@@ -1870,8 +2080,10 @@ const allocVar = (scope, name, global = false) => {
1870
2080
  let idx = global ? globalInd++ : scope.localInd++;
1871
2081
  target[name] = { idx, type: valtypeBinary };
1872
2082
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2083
+ if (type) {
2084
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2085
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2086
+ }
1875
2087
 
1876
2088
  return idx;
1877
2089
  };
@@ -1886,11 +2098,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1886
2098
  };
1887
2099
 
1888
2100
  const typeAnnoToPorfType = x => {
1889
- if (TYPES[x]) return TYPES[x];
1890
- if (TYPES['_' + x]) return TYPES['_' + x];
2101
+ if (!x) return null;
2102
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
1891
2103
 
1892
2104
  switch (x) {
1893
2105
  case 'i32':
2106
+ case 'i64':
2107
+ case 'f64':
1894
2108
  return TYPES.number;
1895
2109
  }
1896
2110
 
@@ -1901,7 +2115,7 @@ const extractTypeAnnotation = decl => {
1901
2115
  let a = decl;
1902
2116
  while (a.typeAnnotation) a = a.typeAnnotation;
1903
2117
 
1904
- let type, elementType;
2118
+ let type = null, elementType = null;
1905
2119
  if (a.typeName) {
1906
2120
  type = a.typeName.name;
1907
2121
  } else if (a.type.endsWith('Keyword')) {
@@ -1914,7 +2128,7 @@ const extractTypeAnnotation = decl => {
1914
2128
  const typeName = type;
1915
2129
  type = typeAnnoToPorfType(type);
1916
2130
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2131
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2132
 
1919
2133
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2134
 
@@ -1926,13 +2140,13 @@ const generateVar = (scope, decl) => {
1926
2140
 
1927
2141
  const topLevel = scope.name === 'main';
1928
2142
 
1929
- // global variable if in top scope (main) and var ..., or if wanted
1930
- const global = topLevel || decl._bare; // decl.kind === 'var';
2143
+ // global variable if in top scope (main) or if internally wanted
2144
+ const global = topLevel || decl._bare;
1931
2145
 
1932
2146
  for (const x of decl.declarations) {
1933
2147
  const name = mapName(x.id.name);
1934
2148
 
1935
- if (!name) return todo('destructuring is not supported yet');
2149
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1936
2150
 
1937
2151
  if (x.init && isFuncType(x.init.type)) {
1938
2152
  // hack for let a = function () { ... }
@@ -1941,7 +2155,6 @@ const generateVar = (scope, decl) => {
1941
2155
  continue;
1942
2156
  }
1943
2157
 
1944
- // console.log(name);
1945
2158
  if (topLevel && builtinVars[name]) {
1946
2159
  // cannot redeclare
1947
2160
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -1949,16 +2162,29 @@ const generateVar = (scope, decl) => {
1949
2162
  continue; // always ignore
1950
2163
  }
1951
2164
 
1952
- let idx = allocVar(scope, name, global);
2165
+ // // generate init before allocating var
2166
+ // let generated;
2167
+ // if (x.init) generated = generate(scope, x.init, global, name);
1953
2168
 
1954
- if (typedInput && x.id.typeAnnotation) {
2169
+ const typed = typedInput && x.id.typeAnnotation;
2170
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2171
+
2172
+ if (typed) {
1955
2173
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
2174
  }
1957
2175
 
1958
2176
  if (x.init) {
1959
- out = out.concat(generate(scope, x.init, global, name));
1960
-
1961
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2177
+ const generated = generate(scope, x.init, global, name);
2178
+ if (scope.arrays?.get(name) != null) {
2179
+ // hack to set local as pointer before
2180
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2181
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2182
+ generated.pop();
2183
+ out = out.concat(generated);
2184
+ } else {
2185
+ out = out.concat(generated);
2186
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2187
+ }
1962
2188
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1963
2189
  }
1964
2190
 
@@ -1969,7 +2195,8 @@ const generateVar = (scope, decl) => {
1969
2195
  return out;
1970
2196
  };
1971
2197
 
1972
- const generateAssign = (scope, decl) => {
2198
+ // todo: optimize this func for valueUnused
2199
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1973
2200
  const { type, name } = decl.left;
1974
2201
 
1975
2202
  if (type === 'ObjectPattern') {
@@ -1984,22 +2211,30 @@ const generateAssign = (scope, decl) => {
1984
2211
  return [];
1985
2212
  }
1986
2213
 
2214
+ const op = decl.operator.slice(0, -1) || '=';
2215
+
1987
2216
  // hack: .length setter
1988
2217
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1989
2218
  const name = decl.left.object.name;
1990
- const pointer = arrays.get(name);
2219
+ const pointer = scope.arrays?.get(name);
1991
2220
 
1992
- const aotPointer = pointer != null;
2221
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1993
2222
 
1994
2223
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2224
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1995
2225
 
1996
2226
  return [
1997
2227
  ...(aotPointer ? number(0, Valtype.i32) : [
1998
2228
  ...generate(scope, decl.left.object),
1999
2229
  Opcodes.i32_to_u
2000
2230
  ]),
2231
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2001
2232
 
2002
- ...generate(scope, decl.right),
2233
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2234
+ [ Opcodes.local_get, pointerTmp ],
2235
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2236
+ Opcodes.i32_from_u
2237
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2003
2238
  [ Opcodes.local_tee, newValueTmp ],
2004
2239
 
2005
2240
  Opcodes.i32_to_u,
@@ -2009,21 +2244,19 @@ const generateAssign = (scope, decl) => {
2009
2244
  ];
2010
2245
  }
2011
2246
 
2012
- const op = decl.operator.slice(0, -1) || '=';
2013
-
2014
2247
  // arr[i]
2015
2248
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2016
2249
  const name = decl.left.object.name;
2017
- const pointer = arrays.get(name);
2250
+ const pointer = scope.arrays?.get(name);
2018
2251
 
2019
- const aotPointer = pointer != null;
2252
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2020
2253
 
2021
2254
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2022
2255
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2023
2256
 
2024
2257
  return [
2025
2258
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2026
- [TYPES._array]: [
2259
+ [TYPES.array]: [
2027
2260
  ...(aotPointer ? [] : [
2028
2261
  ...generate(scope, decl.left.object),
2029
2262
  Opcodes.i32_to_u
@@ -2072,7 +2305,7 @@ const generateAssign = (scope, decl) => {
2072
2305
  ];
2073
2306
  }
2074
2307
 
2075
- if (!name) return todo('destructuring is not supported yet');
2308
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2076
2309
 
2077
2310
  const [ local, isGlobal ] = lookupName(scope, name);
2078
2311
 
@@ -2120,9 +2353,7 @@ const generateAssign = (scope, decl) => {
2120
2353
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2354
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2355
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2356
+ ...setType(scope, name, getLastType(scope))
2126
2357
  ];
2127
2358
  }
2128
2359
 
@@ -2133,9 +2364,7 @@ const generateAssign = (scope, decl) => {
2133
2364
 
2134
2365
  // todo: string concat types
2135
2366
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2367
+ ...setType(scope, name, TYPES.number)
2139
2368
  ];
2140
2369
  };
2141
2370
 
@@ -2181,7 +2410,7 @@ const generateUnary = (scope, decl) => {
2181
2410
  return out;
2182
2411
  }
2183
2412
 
2184
- case 'delete':
2413
+ case 'delete': {
2185
2414
  let toReturn = true, toGenerate = true;
2186
2415
 
2187
2416
  if (decl.argument.type === 'Identifier') {
@@ -2203,40 +2432,60 @@ const generateUnary = (scope, decl) => {
2203
2432
 
2204
2433
  out.push(...number(toReturn ? 1 : 0));
2205
2434
  return out;
2435
+ }
2436
+
2437
+ case 'typeof': {
2438
+ let overrideType, toGenerate = true;
2439
+
2440
+ if (decl.argument.type === 'Identifier') {
2441
+ const out = generateIdent(scope, decl.argument);
2442
+
2443
+ // if ReferenceError (undeclared var), ignore and return undefined
2444
+ if (out[1]) {
2445
+ // does not exist (2 ops from throw)
2446
+ overrideType = number(TYPES.undefined, Valtype.i32);
2447
+ toGenerate = false;
2448
+ }
2449
+ }
2206
2450
 
2207
- case 'typeof':
2208
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2451
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2452
+ disposeLeftover(out);
2453
+
2454
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2209
2455
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2210
2456
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2211
2457
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2212
2458
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2213
2459
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2214
2460
 
2215
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2461
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
2462
 
2217
2463
  // object and internal types
2218
2464
  default: makeString(scope, 'object', false, '#typeof_result'),
2219
- });
2465
+ }));
2466
+
2467
+ return out;
2468
+ }
2220
2469
 
2221
2470
  default:
2222
- return todo(`unary operator ${decl.operator} not implemented yet`);
2471
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2223
2472
  }
2224
2473
  };
2225
2474
 
2226
- const generateUpdate = (scope, decl) => {
2475
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2227
2476
  const { name } = decl.argument;
2228
2477
 
2229
2478
  const [ local, isGlobal ] = lookupName(scope, name);
2230
2479
 
2231
2480
  if (local === undefined) {
2232
- return todo(`update expression with undefined variable`);
2481
+ return todo(scope, `update expression with undefined variable`, true);
2233
2482
  }
2234
2483
 
2235
2484
  const idx = local.idx;
2236
2485
  const out = [];
2237
2486
 
2238
2487
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2239
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2488
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2240
2489
 
2241
2490
  switch (decl.operator) {
2242
2491
  case '++':
@@ -2249,7 +2498,7 @@ const generateUpdate = (scope, decl) => {
2249
2498
  }
2250
2499
 
2251
2500
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2252
- 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 ]);
2253
2502
 
2254
2503
  return out;
2255
2504
  };
@@ -2289,7 +2538,7 @@ const generateConditional = (scope, decl) => {
2289
2538
  // note type
2290
2539
  out.push(
2291
2540
  ...getNodeType(scope, decl.consequent),
2292
- setLastType(scope)
2541
+ ...setLastType(scope)
2293
2542
  );
2294
2543
 
2295
2544
  out.push([ Opcodes.else ]);
@@ -2298,7 +2547,7 @@ const generateConditional = (scope, decl) => {
2298
2547
  // note type
2299
2548
  out.push(
2300
2549
  ...getNodeType(scope, decl.alternate),
2301
- setLastType(scope)
2550
+ ...setLastType(scope)
2302
2551
  );
2303
2552
 
2304
2553
  out.push([ Opcodes.end ]);
@@ -2312,7 +2561,7 @@ const generateFor = (scope, decl) => {
2312
2561
  const out = [];
2313
2562
 
2314
2563
  if (decl.init) {
2315
- out.push(...generate(scope, decl.init));
2564
+ out.push(...generate(scope, decl.init, false, undefined, true));
2316
2565
  disposeLeftover(out);
2317
2566
  }
2318
2567
 
@@ -2330,7 +2579,7 @@ const generateFor = (scope, decl) => {
2330
2579
  out.push(...generate(scope, decl.body));
2331
2580
  out.push([ Opcodes.end ]);
2332
2581
 
2333
- if (decl.update) out.push(...generate(scope, decl.update));
2582
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2334
2583
 
2335
2584
  out.push([ Opcodes.br, 1 ]);
2336
2585
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2358,6 +2607,36 @@ const generateWhile = (scope, decl) => {
2358
2607
  return out;
2359
2608
  };
2360
2609
 
2610
+ const generateDoWhile = (scope, decl) => {
2611
+ const out = [];
2612
+
2613
+ out.push([ Opcodes.loop, Blocktype.void ]);
2614
+ depth.push('dowhile');
2615
+
2616
+ // block for break (includes all)
2617
+ out.push([ Opcodes.block, Blocktype.void ]);
2618
+ depth.push('block');
2619
+
2620
+ // block for continue
2621
+ // includes body but not test+loop so we can exit body at anytime
2622
+ // and still test+loop after
2623
+ out.push([ Opcodes.block, Blocktype.void ]);
2624
+ depth.push('block');
2625
+
2626
+ out.push(...generate(scope, decl.body));
2627
+
2628
+ out.push([ Opcodes.end ]);
2629
+ depth.pop();
2630
+
2631
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2632
+ out.push([ Opcodes.br_if, 1 ]);
2633
+
2634
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2635
+ depth.pop(); depth.pop();
2636
+
2637
+ return out;
2638
+ };
2639
+
2361
2640
  const generateForOf = (scope, decl) => {
2362
2641
  const out = [];
2363
2642
 
@@ -2394,7 +2673,10 @@ const generateForOf = (scope, decl) => {
2394
2673
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
2674
  }
2396
2675
 
2676
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2677
+
2397
2678
  const [ local, isGlobal ] = lookupName(scope, leftName);
2679
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2398
2680
 
2399
2681
  depth.push('block');
2400
2682
  depth.push('block');
@@ -2403,6 +2685,7 @@ const generateForOf = (scope, decl) => {
2403
2685
  // hack: this is naughty and will break things!
2404
2686
  let newOut = number(0, Valtype.f64), newPointer = -1;
2405
2687
  if (pages.hasAnyString) {
2688
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2406
2689
  0, [ newOut, newPointer ] = makeArray(scope, {
2407
2690
  rawElements: new Array(1)
2408
2691
  }, isGlobal, leftName, true, 'i16');
@@ -2411,7 +2694,7 @@ const generateForOf = (scope, decl) => {
2411
2694
  // set type for local
2412
2695
  // todo: optimize away counter and use end pointer
2413
2696
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2414
- [TYPES._array]: [
2697
+ [TYPES.array]: [
2415
2698
  ...setType(scope, leftName, TYPES.number),
2416
2699
 
2417
2700
  [ Opcodes.loop, Blocktype.void ],
@@ -2494,6 +2777,56 @@ const generateForOf = (scope, decl) => {
2494
2777
  [ Opcodes.end ],
2495
2778
  [ Opcodes.end ]
2496
2779
  ],
2780
+ [TYPES.bytestring]: [
2781
+ ...setType(scope, leftName, TYPES.bytestring),
2782
+
2783
+ [ Opcodes.loop, Blocktype.void ],
2784
+
2785
+ // setup new/out array
2786
+ ...newOut,
2787
+ [ Opcodes.drop ],
2788
+
2789
+ ...number(0, Valtype.i32), // base 0 for store after
2790
+
2791
+ // load current string ind {arg}
2792
+ [ Opcodes.local_get, pointer ],
2793
+ [ Opcodes.local_get, counter ],
2794
+ [ Opcodes.i32_add ],
2795
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2796
+
2797
+ // store to new string ind 0
2798
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2799
+
2800
+ // return new string (page)
2801
+ ...number(newPointer),
2802
+
2803
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2804
+
2805
+ [ Opcodes.block, Blocktype.void ],
2806
+ [ Opcodes.block, Blocktype.void ],
2807
+ ...generate(scope, decl.body),
2808
+ [ Opcodes.end ],
2809
+
2810
+ // increment iter pointer
2811
+ // [ Opcodes.local_get, pointer ],
2812
+ // ...number(1, Valtype.i32),
2813
+ // [ Opcodes.i32_add ],
2814
+ // [ Opcodes.local_set, pointer ],
2815
+
2816
+ // increment counter by 1
2817
+ [ Opcodes.local_get, counter ],
2818
+ ...number(1, Valtype.i32),
2819
+ [ Opcodes.i32_add ],
2820
+ [ Opcodes.local_tee, counter ],
2821
+
2822
+ // loop if counter != length
2823
+ [ Opcodes.local_get, length ],
2824
+ [ Opcodes.i32_ne ],
2825
+ [ Opcodes.br_if, 1 ],
2826
+
2827
+ [ Opcodes.end ],
2828
+ [ Opcodes.end ]
2829
+ ],
2497
2830
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2498
2831
  }, Blocktype.void));
2499
2832
 
@@ -2504,28 +2837,65 @@ const generateForOf = (scope, decl) => {
2504
2837
  return out;
2505
2838
  };
2506
2839
 
2840
+ // find the nearest loop in depth map by type
2507
2841
  const getNearestLoop = () => {
2508
2842
  for (let i = depth.length - 1; i >= 0; i--) {
2509
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2843
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2510
2844
  }
2511
2845
 
2512
2846
  return -1;
2513
2847
  };
2514
2848
 
2515
2849
  const generateBreak = (scope, decl) => {
2516
- const nearestLoop = depth.length - getNearestLoop();
2850
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2851
+ const type = depth[target];
2852
+
2853
+ // different loop types have different branch offsets
2854
+ // as they have different wasm block/loop/if structures
2855
+ // we need to use the right offset by type to branch to the one we want
2856
+ // for a break: exit the loop without executing anything else inside it
2857
+ const offset = ({
2858
+ for: 2, // loop > if (wanted branch) > block (we are here)
2859
+ while: 2, // loop > if (wanted branch) (we are here)
2860
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2861
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2862
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2863
+ })[type];
2864
+
2517
2865
  return [
2518
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2866
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2519
2867
  ];
2520
2868
  };
2521
2869
 
2522
2870
  const generateContinue = (scope, decl) => {
2523
- const nearestLoop = depth.length - getNearestLoop();
2871
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2872
+ const type = depth[target];
2873
+
2874
+ // different loop types have different branch offsets
2875
+ // as they have different wasm block/loop/if structures
2876
+ // we need to use the right offset by type to branch to the one we want
2877
+ // for a continue: do test for the loop, and then loop depending on that success
2878
+ const offset = ({
2879
+ for: 3, // loop (wanted branch) > if > block (we are here)
2880
+ while: 1, // loop (wanted branch) > if (we are here)
2881
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2882
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2883
+ })[type];
2884
+
2524
2885
  return [
2525
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2886
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2526
2887
  ];
2527
2888
  };
2528
2889
 
2890
+ const generateLabel = (scope, decl) => {
2891
+ scope.labels ??= new Map();
2892
+
2893
+ const name = decl.label.name;
2894
+ scope.labels.set(name, depth.length);
2895
+
2896
+ return generate(scope, decl.body);
2897
+ };
2898
+
2529
2899
  const generateThrow = (scope, decl) => {
2530
2900
  scope.throws = true;
2531
2901
 
@@ -2546,6 +2916,9 @@ const generateThrow = (scope, decl) => {
2546
2916
  let exceptId = exceptions.push({ constructor, message }) - 1;
2547
2917
  let tagIdx = tags[0].idx;
2548
2918
 
2919
+ scope.exceptions ??= [];
2920
+ scope.exceptions.push(exceptId);
2921
+
2549
2922
  // todo: write a description of how this works lol
2550
2923
 
2551
2924
  return [
@@ -2555,7 +2928,7 @@ const generateThrow = (scope, decl) => {
2555
2928
  };
2556
2929
 
2557
2930
  const generateTry = (scope, decl) => {
2558
- if (decl.finalizer) return todo('try finally not implemented yet');
2931
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2559
2932
 
2560
2933
  const out = [];
2561
2934
 
@@ -2582,15 +2955,8 @@ const generateEmpty = (scope, decl) => {
2582
2955
  return [];
2583
2956
  };
2584
2957
 
2585
- const generateAssignPat = (scope, decl) => {
2586
- // TODO
2587
- // if identifier declared, use that
2588
- // else, use default (right)
2589
- return todo('assignment pattern (optional arg)');
2590
- };
2591
-
2592
2958
  let pages = new Map();
2593
- const allocPage = (reason, type) => {
2959
+ const allocPage = (scope, reason, type) => {
2594
2960
  if (pages.has(reason)) return pages.get(reason).ind;
2595
2961
 
2596
2962
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2601,16 +2967,20 @@ const allocPage = (reason, type) => {
2601
2967
  const ind = pages.size;
2602
2968
  pages.set(reason, { ind, type });
2603
2969
 
2604
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2970
+ scope.pages ??= new Map();
2971
+ scope.pages.set(reason, { ind, type });
2972
+
2973
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2605
2974
 
2606
2975
  return ind;
2607
2976
  };
2608
2977
 
2978
+ // todo: add scope.pages
2609
2979
  const freePage = reason => {
2610
2980
  const { ind } = pages.get(reason);
2611
2981
  pages.delete(reason);
2612
2982
 
2613
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2983
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2614
2984
 
2615
2985
  return ind;
2616
2986
  };
@@ -2661,16 +3031,22 @@ const getAllocType = itemType => {
2661
3031
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2662
3032
  const out = [];
2663
3033
 
3034
+ scope.arrays ??= new Map();
3035
+
2664
3036
  let firstAssign = false;
2665
- if (!arrays.has(name) || name === '$undeclared') {
3037
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2666
3038
  firstAssign = true;
2667
3039
 
2668
3040
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2669
3041
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2670
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3042
+
3043
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3044
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2671
3045
  }
2672
3046
 
2673
- const pointer = arrays.get(name);
3047
+ const pointer = scope.arrays.get(name);
3048
+
3049
+ const local = global ? globals[name] : scope.locals[name];
2674
3050
 
2675
3051
  const useRawElements = !!decl.rawElements;
2676
3052
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2678,19 +3054,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2678
3054
  const valtype = itemTypeToValtype[itemType];
2679
3055
  const length = elements.length;
2680
3056
 
2681
- if (firstAssign && useRawElements) {
2682
- let bytes = compileBytes(length, 'i32');
3057
+ if (firstAssign && useRawElements && !Prefs.noData) {
3058
+ // if length is 0 memory/data will just be 0000... anyway
3059
+ if (length !== 0) {
3060
+ let bytes = compileBytes(length, 'i32');
2683
3061
 
2684
- if (!initEmpty) for (let i = 0; i < length; i++) {
2685
- if (elements[i] == null) continue;
3062
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3063
+ if (elements[i] == null) continue;
2686
3064
 
2687
- bytes.push(...compileBytes(elements[i], itemType));
2688
- }
3065
+ bytes.push(...compileBytes(elements[i], itemType));
3066
+ }
2689
3067
 
2690
- data.push({
2691
- offset: pointer,
2692
- bytes
2693
- });
3068
+ const ind = data.push({
3069
+ offset: pointer,
3070
+ bytes
3071
+ }) - 1;
3072
+
3073
+ scope.data ??= [];
3074
+ scope.data.push(ind);
3075
+ }
2694
3076
 
2695
3077
  // local value as pointer
2696
3078
  out.push(...number(pointer));
@@ -2698,11 +3080,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2698
3080
  return [ out, pointer ];
2699
3081
  }
2700
3082
 
3083
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3084
+ if (pointerTmp != null) {
3085
+ out.push(
3086
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3087
+ Opcodes.i32_to_u,
3088
+ [ Opcodes.local_set, pointerTmp ]
3089
+ );
3090
+ }
3091
+
3092
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3093
+
2701
3094
  // store length as 0th array
2702
3095
  out.push(
2703
- ...number(0, Valtype.i32),
3096
+ ...pointerWasm,
2704
3097
  ...number(length, Valtype.i32),
2705
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3098
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2706
3099
  );
2707
3100
 
2708
3101
  const storeOp = StoreOps[itemType];
@@ -2711,20 +3104,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2711
3104
  if (elements[i] == null) continue;
2712
3105
 
2713
3106
  out.push(
2714
- ...number(0, Valtype.i32),
3107
+ ...pointerWasm,
2715
3108
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2716
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3109
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2717
3110
  );
2718
3111
  }
2719
3112
 
2720
3113
  // local value as pointer
2721
- out.push(...number(pointer));
3114
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2722
3115
 
2723
3116
  return [ out, pointer ];
2724
3117
  };
2725
3118
 
2726
3119
  const byteStringable = str => {
2727
- if (!process.argv.includes('-bytestring')) return false;
3120
+ if (!Prefs.bytestring) return false;
2728
3121
 
2729
3122
  for (let i = 0; i < str.length; i++) {
2730
3123
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2735,7 +3128,7 @@ const byteStringable = str => {
2735
3128
 
2736
3129
  const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2737
3130
  const rawElements = new Array(str.length);
2738
- let byteStringable = process.argv.includes('-bytestring');
3131
+ let byteStringable = Prefs.bytestring;
2739
3132
  for (let i = 0; i < str.length; i++) {
2740
3133
  const c = str.charCodeAt(i);
2741
3134
  rawElements[i] = c;
@@ -2750,20 +3143,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2750
3143
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2751
3144
  };
2752
3145
 
2753
- let arrays = new Map();
2754
3146
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2755
3147
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2756
3148
  };
2757
3149
 
2758
3150
  export const generateMember = (scope, decl, _global, _name) => {
2759
3151
  const name = decl.object.name;
2760
- const pointer = arrays.get(name);
3152
+ const pointer = scope.arrays?.get(name);
3153
+
3154
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
3155
+
3156
+ // hack: .name
3157
+ if (decl.property.name === 'name') {
3158
+ if (hasFuncWithName(name)) {
3159
+ let nameProp = name;
3160
+
3161
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3162
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
2761
3163
 
2762
- const aotPointer = pointer != null;
3164
+ return makeString(scope, nameProp, _global, _name, true);
3165
+ } else {
3166
+ return generate(scope, DEFAULT_VALUE);
3167
+ }
3168
+ }
2763
3169
 
2764
3170
  // hack: .length
2765
3171
  if (decl.property.name === 'length') {
2766
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3172
+ const func = funcs.find(x => x.name === name);
3173
+ if (func) {
3174
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3175
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3176
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3177
+ }
3178
+
3179
+ if (builtinFuncs[name + '$constructor']) {
3180
+ const regularFunc = builtinFuncs[name];
3181
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3182
+
3183
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3184
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3185
+
3186
+ return number(Math.max(regularParams, constructorParams));
3187
+ }
3188
+
3189
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3190
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3191
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3192
+
2767
3193
  return [
2768
3194
  ...(aotPointer ? number(0, Valtype.i32) : [
2769
3195
  ...generate(scope, decl.object),
@@ -2788,7 +3214,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2788
3214
  }
2789
3215
 
2790
3216
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2791
- [TYPES._array]: [
3217
+ [TYPES.array]: [
2792
3218
  // get index as valtype
2793
3219
  ...property,
2794
3220
 
@@ -2807,7 +3233,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2807
3233
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2808
3234
 
2809
3235
  ...number(TYPES.number, Valtype.i32),
2810
- setLastType(scope)
3236
+ ...setLastType(scope)
2811
3237
  ],
2812
3238
 
2813
3239
  [TYPES.string]: [
@@ -2839,9 +3265,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2839
3265
  ...number(newPointer),
2840
3266
 
2841
3267
  ...number(TYPES.string, Valtype.i32),
2842
- setLastType(scope)
3268
+ ...setLastType(scope)
2843
3269
  ],
2844
- [TYPES._bytestring]: [
3270
+ [TYPES.bytestring]: [
2845
3271
  // setup new/out array
2846
3272
  ...newOut,
2847
3273
  [ Opcodes.drop ],
@@ -2858,19 +3284,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2858
3284
  ]),
2859
3285
 
2860
3286
  // load current string ind {arg}
2861
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3287
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
3288
 
2863
3289
  // store to new string ind 0
2864
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3290
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
3291
 
2866
3292
  // return new string (page)
2867
3293
  ...number(newPointer),
2868
3294
 
2869
- ...number(TYPES._bytestring, Valtype.i32),
2870
- setLastType(scope)
3295
+ ...number(TYPES.bytestring, Valtype.i32),
3296
+ ...setLastType(scope)
2871
3297
  ],
2872
3298
 
2873
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3299
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2874
3300
  });
2875
3301
  };
2876
3302
 
@@ -2880,28 +3306,36 @@ const objectHack = node => {
2880
3306
  if (!node) return node;
2881
3307
 
2882
3308
  if (node.type === 'MemberExpression') {
2883
- if (node.computed || node.optional) return node;
3309
+ const out = (() => {
3310
+ if (node.computed || node.optional) return;
2884
3311
 
2885
- let objectName = node.object.name;
3312
+ let objectName = node.object.name;
2886
3313
 
2887
- // if object is not identifier or another member exp, give up
2888
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3314
+ // if object is not identifier or another member exp, give up
3315
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3316
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2889
3317
 
2890
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3318
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2891
3319
 
2892
- // if .length, give up (hack within a hack!)
2893
- if (node.property.name === 'length') return node;
3320
+ // if .name or .length, give up (hack within a hack!)
3321
+ if (['name', 'length'].includes(node.property.name)) {
3322
+ node.object = objectHack(node.object);
3323
+ return;
3324
+ }
2894
3325
 
2895
- // no object name, give up
2896
- if (!objectName) return node;
3326
+ // no object name, give up
3327
+ if (!objectName) return;
2897
3328
 
2898
- const name = '__' + objectName + '_' + node.property.name;
2899
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3329
+ const name = '__' + objectName + '_' + node.property.name;
3330
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2900
3331
 
2901
- return {
2902
- type: 'Identifier',
2903
- name
2904
- };
3332
+ return {
3333
+ type: 'Identifier',
3334
+ name
3335
+ };
3336
+ })();
3337
+
3338
+ if (out) return out;
2905
3339
  }
2906
3340
 
2907
3341
  for (const x in node) {
@@ -2915,8 +3349,8 @@ const objectHack = node => {
2915
3349
  };
2916
3350
 
2917
3351
  const generateFunc = (scope, decl) => {
2918
- if (decl.async) return todo('async functions are not supported');
2919
- if (decl.generator) return todo('generator functions are not supported');
3352
+ if (decl.async) return todo(scope, 'async functions are not supported');
3353
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2920
3354
 
2921
3355
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2922
3356
  const params = decl.params ?? [];
@@ -2932,6 +3366,14 @@ const generateFunc = (scope, decl) => {
2932
3366
  name
2933
3367
  };
2934
3368
 
3369
+ if (typedInput && decl.returnType) {
3370
+ const { type } = extractTypeAnnotation(decl.returnType);
3371
+ if (type != null) {
3372
+ innerScope.returnType = type;
3373
+ innerScope.returns = [ valtypeBinary ];
3374
+ }
3375
+ }
3376
+
2935
3377
  for (let i = 0; i < params.length; i++) {
2936
3378
  allocVar(innerScope, params[i].name, false);
2937
3379
 
@@ -2958,6 +3400,8 @@ const generateFunc = (scope, decl) => {
2958
3400
  };
2959
3401
  funcIndex[name] = func.index;
2960
3402
 
3403
+ if (name === 'main') func.gotLastType = true;
3404
+
2961
3405
  // quick hack fixes
2962
3406
  for (const inst of wasm) {
2963
3407
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2969,7 +3413,7 @@ const generateFunc = (scope, decl) => {
2969
3413
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2970
3414
  wasm.push(
2971
3415
  ...number(0),
2972
- ...number(TYPES.undefined, Valtype.i32),
3416
+ ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
2973
3417
  [ Opcodes.return ]
2974
3418
  );
2975
3419
  }
@@ -2992,16 +3436,6 @@ const generateCode = (scope, decl) => {
2992
3436
  };
2993
3437
 
2994
3438
  const internalConstrs = {
2995
- Boolean: {
2996
- generate: (scope, decl) => {
2997
- if (decl.arguments.length === 0) return number(0);
2998
-
2999
- // should generate/run all args
3000
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3001
- },
3002
- type: TYPES.boolean
3003
- },
3004
-
3005
3439
  Array: {
3006
3440
  generate: (scope, decl, global, name) => {
3007
3441
  // new Array(i0, i1, ...)
@@ -3019,7 +3453,7 @@ const internalConstrs = {
3019
3453
 
3020
3454
  // todo: check in wasm instead of here
3021
3455
  const literalValue = arg.value ?? 0;
3022
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3456
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3023
3457
 
3024
3458
  return [
3025
3459
  ...number(0, Valtype.i32),
@@ -3030,7 +3464,8 @@ const internalConstrs = {
3030
3464
  ...number(pointer)
3031
3465
  ];
3032
3466
  },
3033
- type: TYPES._array
3467
+ type: TYPES.array,
3468
+ length: 1
3034
3469
  },
3035
3470
 
3036
3471
  __Array_of: {
@@ -3041,27 +3476,134 @@ const internalConstrs = {
3041
3476
  elements: decl.arguments
3042
3477
  }, global, name);
3043
3478
  },
3044
- type: TYPES._array,
3479
+ type: TYPES.array,
3480
+ notConstr: true,
3481
+ length: 0
3482
+ },
3483
+
3484
+ __Porffor_fastOr: {
3485
+ generate: (scope, decl) => {
3486
+ const out = [];
3487
+
3488
+ for (let i = 0; i < decl.arguments.length; i++) {
3489
+ out.push(
3490
+ ...generate(scope, decl.arguments[i]),
3491
+ Opcodes.i32_to_u,
3492
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3493
+ );
3494
+ }
3495
+
3496
+ out.push(Opcodes.i32_from_u);
3497
+
3498
+ return out;
3499
+ },
3500
+ type: TYPES.boolean,
3045
3501
  notConstr: true
3046
- }
3047
- };
3502
+ },
3048
3503
 
3049
- // const _ = Array.prototype.push;
3050
- // Array.prototype.push = function (a) {
3051
- // const check = arr => {
3052
- // for (const x of arr) {
3053
- // if (x === undefined) {
3054
- // console.trace(arr);
3055
- // process.exit();
3056
- // }
3057
- // if (Array.isArray(x)) check(x);
3058
- // }
3059
- // };
3060
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
3061
- // // if (Array.isArray(a)) check(a);
3504
+ __Porffor_fastAnd: {
3505
+ generate: (scope, decl) => {
3506
+ const out = [];
3062
3507
 
3063
- // return _.apply(this, arguments);
3064
- // };
3508
+ for (let i = 0; i < decl.arguments.length; i++) {
3509
+ out.push(
3510
+ ...generate(scope, decl.arguments[i]),
3511
+ Opcodes.i32_to_u,
3512
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3513
+ );
3514
+ }
3515
+
3516
+ out.push(Opcodes.i32_from_u);
3517
+
3518
+ return out;
3519
+ },
3520
+ type: TYPES.boolean,
3521
+ notConstr: true
3522
+ },
3523
+
3524
+ Boolean: {
3525
+ generate: (scope, decl) => {
3526
+ // todo: boolean object when used as constructor
3527
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3528
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3529
+ },
3530
+ type: TYPES.boolean,
3531
+ length: 1
3532
+ },
3533
+
3534
+ __Math_max: {
3535
+ generate: (scope, decl) => {
3536
+ const out = [
3537
+ ...number(-Infinity)
3538
+ ];
3539
+
3540
+ for (let i = 0; i < decl.arguments.length; i++) {
3541
+ out.push(
3542
+ ...generate(scope, decl.arguments[i]),
3543
+ [ Opcodes.f64_max ]
3544
+ );
3545
+ }
3546
+
3547
+ return out;
3548
+ },
3549
+ type: TYPES.number,
3550
+ notConstr: true,
3551
+ length: 2
3552
+ },
3553
+
3554
+ __Math_min: {
3555
+ generate: (scope, decl) => {
3556
+ const out = [
3557
+ ...number(Infinity)
3558
+ ];
3559
+
3560
+ for (let i = 0; i < decl.arguments.length; i++) {
3561
+ out.push(
3562
+ ...generate(scope, decl.arguments[i]),
3563
+ [ Opcodes.f64_min ]
3564
+ );
3565
+ }
3566
+
3567
+ return out;
3568
+ },
3569
+ type: TYPES.number,
3570
+ notConstr: true,
3571
+ length: 2
3572
+ },
3573
+
3574
+ __console_log: {
3575
+ generate: (scope, decl) => {
3576
+ const out = [];
3577
+
3578
+ for (let i = 0; i < decl.arguments.length; i++) {
3579
+ out.push(
3580
+ ...generateCall(scope, {
3581
+ callee: {
3582
+ type: 'Identifier',
3583
+ name: '__Porffor_print'
3584
+ },
3585
+ arguments: [ decl.arguments[i] ]
3586
+ }),
3587
+
3588
+ // print space
3589
+ ...number(32),
3590
+ [ Opcodes.call, importedFuncs.printChar ]
3591
+ );
3592
+ }
3593
+
3594
+ // print newline
3595
+ out.push(
3596
+ ...number(10),
3597
+ [ Opcodes.call, importedFuncs.printChar ]
3598
+ );
3599
+
3600
+ return out;
3601
+ },
3602
+ type: TYPES.undefined,
3603
+ notConstr: true,
3604
+ length: 0
3605
+ }
3606
+ };
3065
3607
 
3066
3608
  export default program => {
3067
3609
  globals = {};
@@ -3071,20 +3613,23 @@ export default program => {
3071
3613
  funcs = [];
3072
3614
  funcIndex = {};
3073
3615
  depth = [];
3074
- arrays = new Map();
3075
3616
  pages = new Map();
3076
3617
  data = [];
3077
3618
  currentFuncIndex = importedFuncs.length;
3078
3619
 
3079
3620
  globalThis.valtype = 'f64';
3080
3621
 
3081
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3622
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3082
3623
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3083
3624
 
3084
3625
  globalThis.valtypeBinary = Valtype[valtype];
3085
3626
 
3086
3627
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3087
3628
 
3629
+ globalThis.pageSize = PageSize;
3630
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3631
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3632
+
3088
3633
  // set generic opcodes for current valtype
3089
3634
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3090
3635
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3093,10 +3638,10 @@ export default program => {
3093
3638
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3094
3639
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3095
3640
 
3096
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3097
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3098
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3099
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3641
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3642
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3643
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3644
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3100
3645
 
3101
3646
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3102
3647
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3109,10 +3654,6 @@ export default program => {
3109
3654
 
3110
3655
  program.id = { name: 'main' };
3111
3656
 
3112
- globalThis.pageSize = PageSize;
3113
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3114
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3115
-
3116
3657
  const scope = {
3117
3658
  locals: {},
3118
3659
  localInd: 0
@@ -3123,7 +3664,7 @@ export default program => {
3123
3664
  body: program.body
3124
3665
  };
3125
3666
 
3126
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3667
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3127
3668
 
3128
3669
  generateFunc(scope, program);
3129
3670
 
@@ -3140,7 +3681,11 @@ export default program => {
3140
3681
  }
3141
3682
 
3142
3683
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3143
- main.returns = [];
3684
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3685
+ main.wasm.splice(main.wasm.length - 1, 1);
3686
+ } else {
3687
+ main.returns = [];
3688
+ }
3144
3689
  }
3145
3690
 
3146
3691
  if (lastInst[0] === Opcodes.call) {