porffor 0.2.0-fde989a → 0.14.0-0d97d1e6a

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 (61) 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} +95 -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/symbol.ts +61 -0
  25. package/compiler/builtins.js +440 -285
  26. package/compiler/{codeGen.js → codegen.js} +1116 -489
  27. package/compiler/decompile.js +3 -4
  28. package/compiler/embedding.js +22 -22
  29. package/compiler/encoding.js +94 -10
  30. package/compiler/expression.js +1 -1
  31. package/compiler/generated_builtins.js +1670 -0
  32. package/compiler/index.js +27 -43
  33. package/compiler/log.js +6 -3
  34. package/compiler/opt.js +55 -41
  35. package/compiler/parse.js +38 -30
  36. package/compiler/precompile.js +120 -0
  37. package/compiler/prefs.js +31 -0
  38. package/compiler/prototype.js +31 -46
  39. package/compiler/types.js +38 -0
  40. package/compiler/wasmSpec.js +33 -8
  41. package/compiler/wrap.js +107 -71
  42. package/package.json +9 -5
  43. package/porf +2 -0
  44. package/rhemyn/compile.js +46 -27
  45. package/rhemyn/parse.js +322 -320
  46. package/rhemyn/test/parse.js +58 -58
  47. package/runner/compare.js +33 -34
  48. package/runner/debug.js +117 -0
  49. package/runner/index.js +78 -11
  50. package/runner/profiler.js +75 -0
  51. package/runner/repl.js +40 -13
  52. package/runner/sizes.js +37 -37
  53. package/runner/version.js +10 -8
  54. package/compiler/builtins/base64.js +0 -92
  55. package/filesize.cmd +0 -2
  56. package/runner/info.js +0 -89
  57. package/runner/profile.js +0 -46
  58. package/runner/results.json +0 -1
  59. package/runner/transform.js +0 -15
  60. package/tmp.c +0 -661
  61. 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));
35
-
36
- return code;
37
- };
33
+ case 'runtime':
34
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
38
35
 
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':
@@ -68,10 +58,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
68
58
 
69
59
  case 'ArrowFunctionExpression':
70
60
  case 'FunctionDeclaration':
61
+ case 'FunctionExpression':
71
62
  const func = generateFunc(scope, decl);
72
63
 
73
64
  if (decl.type.endsWith('Expression')) {
74
- return number(func.index);
65
+ return number(func.index - importedFuncs.length);
75
66
  }
76
67
 
77
68
  return [];
@@ -104,7 +95,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
95
  return generateUnary(scope, decl);
105
96
 
106
97
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
98
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
99
 
109
100
  case 'IfStatement':
110
101
  return generateIf(scope, decl);
@@ -115,6 +106,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
106
  case 'WhileStatement':
116
107
  return generateWhile(scope, decl);
117
108
 
109
+ case 'DoWhileStatement':
110
+ return generateDoWhile(scope, decl);
111
+
118
112
  case 'ForOfStatement':
119
113
  return generateForOf(scope, decl);
120
114
 
@@ -124,6 +118,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
118
  case 'ContinueStatement':
125
119
  return generateContinue(scope, decl);
126
120
 
121
+ case 'LabeledStatement':
122
+ return generateLabel(scope, decl);
123
+
127
124
  case 'EmptyStatement':
128
125
  return generateEmpty(scope, decl);
129
126
 
@@ -137,7 +134,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
134
  return generateTry(scope, decl);
138
135
 
139
136
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
137
+ // todo: hook into terminal debugger
141
138
  return [];
142
139
 
143
140
  case 'ArrayExpression':
@@ -151,16 +148,17 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
151
148
  const funcsBefore = funcs.length;
152
149
  generate(scope, decl.declaration);
153
150
 
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;
151
+ if (funcsBefore !== funcs.length) {
152
+ // new func added
153
+ const newFunc = funcs[funcs.length - 1];
154
+ newFunc.export = true;
155
+ }
158
156
 
159
157
  return [];
160
158
 
161
159
  case 'TaggedTemplateExpression': {
162
160
  const funcs = {
163
- __Porffor_asm: str => {
161
+ __Porffor_wasm: str => {
164
162
  let out = [];
165
163
 
166
164
  for (const line of str.split('\n')) {
@@ -168,8 +166,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
166
  if (asm[0] === '') continue; // blank
169
167
 
170
168
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
169
+ const [ name, type ] = asm.slice(1);
170
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
171
  continue;
174
172
  }
175
173
 
@@ -179,52 +177,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
177
  }
180
178
 
181
179
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
180
+ allocPage(scope, 'asm instrinsic');
183
181
  // todo: add to store/load offset insts
184
182
  continue;
185
183
  }
186
184
 
187
185
  let inst = Opcodes[asm[0].replace('.', '_')];
188
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
186
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
187
 
190
188
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
189
+ const immediates = asm.slice(1).map(x => {
190
+ const int = parseInt(x);
191
+ if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
192
+ return int;
193
+ });
192
194
 
193
- out.push([ ...inst, ...immediates ]);
195
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
196
  }
195
197
 
196
198
  return out;
197
199
  },
198
200
 
199
201
  __Porffor_bs: str => [
200
- ...makeString(scope, str, undefined, undefined, true),
202
+ ...makeString(scope, str, global, name, true),
201
203
 
202
- ...number(TYPES._bytestring, Valtype.i32),
203
- setLastType(scope)
204
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
205
+ ...number(TYPES.bytestring, Valtype.i32),
206
+ ...setLastType(scope)
207
+ ])
204
208
  ],
205
209
  __Porffor_s: str => [
206
- ...makeString(scope, str, undefined, undefined, false),
210
+ ...makeString(scope, str, global, name, false),
207
211
 
208
- ...number(TYPES.string, Valtype.i32),
209
- setLastType(scope)
212
+ ...(name ? setType(scope, name, TYPES.string) : [
213
+ ...number(TYPES.string, Valtype.i32),
214
+ ...setLastType(scope)
215
+ ])
210
216
  ],
211
217
  };
212
218
 
213
- const name = decl.tag.name;
219
+ const func = decl.tag.name;
214
220
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
221
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
222
+
223
+ const { quasis, expressions } = decl.quasi;
224
+ let str = quasis[0].value.raw;
225
+
226
+ for (let i = 0; i < expressions.length; i++) {
227
+ const e = expressions[i];
228
+ if (!e.name) {
229
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
230
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
231
+ } else todo(scope, 'unsupported expression in intrinsic');
232
+ } else str += lookupName(scope, e.name)[0].idx;
233
+
234
+ str += quasis[i + 1].value.raw;
235
+ }
216
236
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
237
+ return funcs[func](str);
219
238
  }
220
239
 
221
240
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
241
+ // ignore typescript nodes
242
+ if (decl.type.startsWith('TS') ||
243
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
244
  return [];
225
245
  }
226
246
 
227
- return todo(`no generation for ${decl.type}!`);
247
+ return todo(scope, `no generation for ${decl.type}!`);
228
248
  }
229
249
  };
230
250
 
@@ -252,7 +272,7 @@ const lookupName = (scope, _name) => {
252
272
  return [ undefined, undefined ];
253
273
  };
254
274
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
275
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
276
  ...generateThrow(scope, {
257
277
  argument: {
258
278
  type: 'NewExpression',
@@ -276,7 +296,10 @@ const generateIdent = (scope, decl) => {
276
296
 
277
297
  if (Object.hasOwn(builtinVars, name)) {
278
298
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
299
+
300
+ let wasm = builtinVars[name];
301
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
302
+ return wasm.slice();
280
303
  }
281
304
 
282
305
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -284,12 +307,17 @@ const generateIdent = (scope, decl) => {
284
307
  return number(1);
285
308
  }
286
309
 
310
+ if (isExistingProtoFunc(name)) {
311
+ // todo: return an actual something
312
+ return number(1);
313
+ }
314
+
287
315
  if (local?.idx === undefined) {
288
316
  // no local var with name
289
- if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
290
- if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
-
292
317
  if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
318
+
319
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
320
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
293
321
  }
294
322
 
295
323
  if (local?.idx === undefined && rawName.startsWith('__')) {
@@ -314,14 +342,16 @@ const generateReturn = (scope, decl) => {
314
342
  // just bare "return"
315
343
  return [
316
344
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
345
+ ...(scope.returnType != null ? [] : [
346
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
347
+ ]),
318
348
  [ Opcodes.return ]
319
349
  ];
320
350
  }
321
351
 
322
352
  return [
323
353
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
354
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
325
355
  [ Opcodes.return ]
326
356
  ];
327
357
  };
@@ -335,7 +365,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
365
  return idx;
336
366
  };
337
367
 
338
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
368
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
369
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
339
370
 
340
371
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
372
  const checks = {
@@ -344,7 +375,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
375
  '??': nullish
345
376
  };
346
377
 
347
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
378
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
348
379
 
349
380
  // generic structure for {a} OP {b}
350
381
  // -->
@@ -352,8 +383,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
383
 
353
384
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
385
  // (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]);
386
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
387
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
388
 
358
389
  const canInt = leftIsInt && rightIsInt;
359
390
 
@@ -370,12 +401,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
401
  ...right,
371
402
  // note type
372
403
  ...rightType,
373
- setLastType(scope),
404
+ ...setLastType(scope),
374
405
  [ Opcodes.else ],
375
406
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
407
  // note type
377
408
  ...leftType,
378
- setLastType(scope),
409
+ ...setLastType(scope),
379
410
  [ Opcodes.end ],
380
411
  Opcodes.i32_from
381
412
  ];
@@ -389,17 +420,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
420
  ...right,
390
421
  // note type
391
422
  ...rightType,
392
- setLastType(scope),
423
+ ...setLastType(scope),
393
424
  [ Opcodes.else ],
394
425
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
426
  // note type
396
427
  ...leftType,
397
- setLastType(scope),
428
+ ...setLastType(scope),
398
429
  [ Opcodes.end ]
399
430
  ];
400
431
  };
401
432
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
433
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
434
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
435
  // todo: convert left and right to strings if not
405
436
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +440,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
440
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
441
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
442
 
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');
443
+ if (assign && Prefs.aotPointerOpt) {
444
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
445
 
418
446
  return [
419
447
  // setup right
@@ -438,11 +466,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
466
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
467
 
440
468
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
469
+ // dst = out pointer + length size + current length * sizeof valtype
442
470
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
471
 
444
472
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
473
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
474
  [ Opcodes.i32_mul ],
447
475
  [ Opcodes.i32_add ],
448
476
 
@@ -451,9 +479,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
479
  ...number(ValtypeSize.i32, Valtype.i32),
452
480
  [ Opcodes.i32_add ],
453
481
 
454
- // size = right length * i16 size
482
+ // size = right length * sizeof valtype
455
483
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
484
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
485
  [ Opcodes.i32_mul ],
458
486
 
459
487
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +539,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
539
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
540
 
513
541
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
542
+ // dst = out pointer + length size + left length * sizeof valtype
515
543
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
544
 
517
545
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
546
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
547
  [ Opcodes.i32_mul ],
520
548
  [ Opcodes.i32_add ],
521
549
 
@@ -524,9 +552,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
552
  ...number(ValtypeSize.i32, Valtype.i32),
525
553
  [ Opcodes.i32_add ],
526
554
 
527
- // size = right length * i16 size
555
+ // size = right length * sizeof valtype
528
556
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
557
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
558
  [ Opcodes.i32_mul ],
531
559
 
532
560
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +564,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
564
  ];
537
565
  };
538
566
 
539
- const compareStrings = (scope, left, right) => {
567
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
568
  // todo: this should be rewritten into a func
541
569
  // todo: convert left and right to strings if not
542
570
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +573,6 @@ const compareStrings = (scope, left, right) => {
545
573
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
574
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
575
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
576
 
550
577
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
578
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +600,6 @@ const compareStrings = (scope, left, right) => {
573
600
 
574
601
  [ Opcodes.local_get, rightPointer ],
575
602
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
603
 
578
604
  // fast path: check leftLength != rightLength
579
605
  [ Opcodes.i32_ne ],
@@ -588,11 +614,13 @@ const compareStrings = (scope, left, right) => {
588
614
  ...number(0, Valtype.i32),
589
615
  [ Opcodes.local_set, index ],
590
616
 
591
- // setup index end as length * sizeof i16 (2)
617
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
618
  // we do this instead of having to do mul/div each iter for perf™
593
619
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
620
+ ...(bytestrings ? [] : [
621
+ ...number(ValtypeSize.i16, Valtype.i32),
622
+ [ Opcodes.i32_mul ],
623
+ ]),
596
624
  [ Opcodes.local_set, indexEnd ],
597
625
 
598
626
  // iterate over each char and check if eq
@@ -602,13 +630,17 @@ const compareStrings = (scope, left, right) => {
602
630
  [ Opcodes.local_get, index ],
603
631
  [ Opcodes.local_get, leftPointer ],
604
632
  [ Opcodes.i32_add ],
605
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
633
+ bytestrings ?
634
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
635
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
606
636
 
607
637
  // fetch right
608
638
  [ Opcodes.local_get, index ],
609
639
  [ Opcodes.local_get, rightPointer ],
610
640
  [ Opcodes.i32_add ],
611
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
641
+ bytestrings ?
642
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
643
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
612
644
 
613
645
  // not equal, "return" false
614
646
  [ Opcodes.i32_ne ],
@@ -617,13 +649,13 @@ const compareStrings = (scope, left, right) => {
617
649
  [ Opcodes.br, 2 ],
618
650
  [ Opcodes.end ],
619
651
 
620
- // index += sizeof i16 (2)
652
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
653
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
654
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
655
  [ Opcodes.i32_add ],
624
656
  [ Opcodes.local_tee, index ],
625
657
 
626
- // if index != index end (length * sizeof 16), loop
658
+ // if index != index end (length * sizeof valtype), loop
627
659
  [ Opcodes.local_get, indexEnd ],
628
660
  [ Opcodes.i32_ne ],
629
661
  [ Opcodes.br_if, 0 ],
@@ -644,16 +676,18 @@ const compareStrings = (scope, left, right) => {
644
676
  };
645
677
 
646
678
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
679
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
680
  ...wasm,
649
681
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
682
  ];
683
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
684
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
685
+ const useTmp = knownType(scope, type) == null;
686
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
687
 
654
688
  const def = [
655
689
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
690
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
691
 
658
692
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
693
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,16 +699,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
699
 
666
700
  return [
667
701
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
702
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
703
 
670
704
  ...typeSwitch(scope, type, {
671
705
  // [TYPES.number]: def,
672
- [TYPES._array]: [
706
+ [TYPES.array]: [
673
707
  // arrays are always truthy
674
708
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
709
  ],
676
710
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
711
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
712
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
713
 
680
714
  // get length
@@ -685,8 +719,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
719
  [ Opcodes.i32_eqz ], */
686
720
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
721
  ],
688
- [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
722
+ [TYPES.bytestring]: [ // duplicate of string
723
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
724
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
725
 
692
726
  // get length
@@ -700,18 +734,20 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
734
  };
701
735
 
702
736
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
737
+ const useTmp = knownType(scope, type) == null;
738
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
739
+
704
740
  return [
705
741
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
742
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
743
 
708
744
  ...typeSwitch(scope, type, {
709
- [TYPES._array]: [
745
+ [TYPES.array]: [
710
746
  // arrays are always truthy
711
747
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
748
  ],
713
749
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
750
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
751
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
752
 
717
753
  // get length
@@ -721,8 +757,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
721
757
  [ Opcodes.i32_eqz ],
722
758
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
759
  ],
724
- [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
760
+ [TYPES.bytestring]: [ // duplicate of string
761
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
762
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
763
 
728
764
  // get length
@@ -734,7 +770,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
770
  ],
735
771
  default: [
736
772
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
773
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
774
 
739
775
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
776
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +780,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
780
  };
745
781
 
746
782
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
783
+ const useTmp = knownType(scope, type) == null;
784
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
785
+
748
786
  return [
749
787
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
788
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
789
 
752
790
  ...typeSwitch(scope, type, {
753
791
  [TYPES.undefined]: [
@@ -756,7 +794,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
794
  ],
757
795
  [TYPES.object]: [
758
796
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
797
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
798
 
761
799
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
800
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,39 +823,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
823
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
824
  }
787
825
 
826
+ const knownLeft = knownType(scope, leftType);
827
+ const knownRight = knownType(scope, rightType);
828
+
788
829
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
830
  const strictOp = op === '===' || op === '!==';
790
831
 
791
832
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
833
+ const finalize = out => startOut.concat(out, endOut);
793
834
 
794
835
  // if strict (in)equal check types match
795
836
  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
837
  endOut.push(
822
838
  ...leftType,
823
839
  ...rightType,
@@ -834,31 +850,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
850
  // todo: if equality op and an operand is undefined, return false
835
851
  // todo: niche null hell with 0
836
852
 
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
- // }
853
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
854
+ if (op === '+') {
855
+ // todo: this should be dynamic too but for now only static
856
+ // string concat (a + b)
857
+ return concatStrings(scope, left, right, _global, _name, assign, false);
858
+ }
859
+
860
+ // not an equality op, NaN
861
+ if (!eqOp) return number(NaN);
862
+
863
+ // else leave bool ops
864
+ // todo: convert string to number if string and number/bool
865
+ // todo: string (>|>=|<|<=) string
866
+
867
+ // string comparison
868
+ if (op === '===' || op === '==') {
869
+ return compareStrings(scope, left, right);
870
+ }
871
+
872
+ if (op === '!==' || op === '!=') {
873
+ return [
874
+ ...compareStrings(scope, left, right),
875
+ [ Opcodes.i32_eqz ]
876
+ ];
877
+ }
878
+ }
879
+
880
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
881
+ if (op === '+') {
882
+ // todo: this should be dynamic too but for now only static
883
+ // string concat (a + b)
884
+ return concatStrings(scope, left, right, _global, _name, assign, true);
885
+ }
886
+
887
+ // not an equality op, NaN
888
+ if (!eqOp) return number(NaN);
889
+
890
+ // else leave bool ops
891
+ // todo: convert string to number if string and number/bool
892
+ // todo: string (>|>=|<|<=) string
893
+
894
+ // string comparison
895
+ if (op === '===' || op === '==') {
896
+ return compareStrings(scope, left, right, true);
897
+ }
898
+
899
+ if (op === '!==' || op === '!=') {
900
+ return [
901
+ ...compareStrings(scope, left, right, true),
902
+ [ Opcodes.i32_eqz ]
903
+ ];
904
+ }
905
+ }
862
906
 
863
907
  let ops = operatorOpcode[valtype][op];
864
908
 
@@ -868,33 +912,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
912
  includeBuiltin(scope, builtinName);
869
913
  const idx = funcIndex[builtinName];
870
914
 
871
- return finalise([
915
+ return finalize([
872
916
  ...left,
873
917
  ...right,
874
918
  [ Opcodes.call, idx ]
875
919
  ]);
876
920
  }
877
921
 
878
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
922
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
879
923
 
880
924
  if (!Array.isArray(ops)) ops = [ ops ];
881
925
  ops = [ ops ];
882
926
 
883
927
  let tmpLeft, tmpRight;
884
928
  // 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
- }
929
+ // todo: intelligent partial skip later
930
+ // if neither known are string, stop this madness
931
+ // we already do known checks earlier, so don't need to recheck
894
932
 
933
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
934
  tmpLeft = localTmp(scope, '__tmpop_left');
896
935
  tmpRight = localTmp(scope, '__tmpop_right');
897
936
 
937
+ // returns false for one string, one not - but more ops/slower
938
+ // ops.unshift(...stringOnly([
939
+ // // if left is string
940
+ // ...leftType,
941
+ // ...number(TYPES.string, Valtype.i32),
942
+ // [ Opcodes.i32_eq ],
943
+
944
+ // // if right is string
945
+ // ...rightType,
946
+ // ...number(TYPES.string, Valtype.i32),
947
+ // [ Opcodes.i32_eq ],
948
+
949
+ // // if either are true
950
+ // [ Opcodes.i32_or ],
951
+ // [ Opcodes.if, Blocktype.void ],
952
+
953
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
954
+ // // if left is not string
955
+ // ...leftType,
956
+ // ...number(TYPES.string, Valtype.i32),
957
+ // [ Opcodes.i32_ne ],
958
+
959
+ // // if right is not string
960
+ // ...rightType,
961
+ // ...number(TYPES.string, Valtype.i32),
962
+ // [ Opcodes.i32_ne ],
963
+
964
+ // // if either are true
965
+ // [ Opcodes.i32_or ],
966
+ // [ Opcodes.if, Blocktype.void ],
967
+ // ...number(0, Valtype.i32),
968
+ // [ Opcodes.br, 2 ],
969
+ // [ Opcodes.end ],
970
+
971
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
972
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
973
+ // [ Opcodes.br, 1 ],
974
+ // [ Opcodes.end ],
975
+ // ]));
976
+
977
+ // does not handle one string, one not (such cases go past)
898
978
  ops.unshift(...stringOnly([
899
979
  // if left is string
900
980
  ...leftType,
@@ -906,30 +986,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
986
  ...number(TYPES.string, Valtype.i32),
907
987
  [ Opcodes.i32_eq ],
908
988
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
989
+ // if both are true
990
+ [ Opcodes.i32_and ],
911
991
  [ Opcodes.if, Blocktype.void ],
992
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
994
+ [ Opcodes.br, 1 ],
995
+ [ Opcodes.end ],
912
996
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
997
+ // if left is bytestring
915
998
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
999
+ ...number(TYPES.bytestring, Valtype.i32),
1000
+ [ Opcodes.i32_eq ],
918
1001
 
919
- // if right is not string
1002
+ // if right is bytestring
920
1003
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1004
+ ...number(TYPES.bytestring, Valtype.i32),
1005
+ [ Opcodes.i32_eq ],
923
1006
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1007
+ // if both are true
1008
+ [ Opcodes.i32_and ],
926
1009
  [ 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 ] ]),
1010
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1011
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1012
  [ Opcodes.br, 1 ],
935
1013
  [ Opcodes.end ],
@@ -941,9 +1019,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1019
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1020
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1021
  // }
944
- })();
1022
+ }
945
1023
 
946
- return finalise([
1024
+ return finalize([
947
1025
  ...left,
948
1026
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1027
  ...right,
@@ -960,7 +1038,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1038
  return out;
961
1039
  };
962
1040
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1041
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1042
+ return func({ name, params, locals, returns, localInd }, {
1043
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1044
+ builtin: name => {
1045
+ let idx = funcIndex[name] ?? importedFuncs[name];
1046
+ if (idx === undefined && builtinFuncs[name]) {
1047
+ includeBuiltin(null, name);
1048
+ idx = funcIndex[name];
1049
+ }
1050
+
1051
+ return idx;
1052
+ }
1053
+ });
1054
+ };
1055
+
1056
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
964
1057
  const existing = funcs.find(x => x.name === name);
965
1058
  if (existing) return existing;
966
1059
 
@@ -972,18 +1065,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1065
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1066
  }
974
1067
 
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 });
1068
+ for (const x of _data) {
1069
+ const copy = { ...x };
1070
+ copy.offset += pages.size * pageSize;
1071
+ data.push(copy);
985
1072
  }
986
1073
 
1074
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+
987
1076
  let baseGlobalIdx, i = 0;
988
1077
  for (const type of globalTypes) {
989
1078
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1006,7 +1095,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1006
1095
  params,
1007
1096
  locals,
1008
1097
  returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1098
+ returnType: returnType ?? TYPES.number,
1010
1099
  wasm,
1011
1100
  internal: true,
1012
1101
  index: currentFuncIndex++
@@ -1029,6 +1118,7 @@ const generateLogicExp = (scope, decl) => {
1029
1118
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1119
  };
1031
1120
 
1121
+ // potential future ideas for nan boxing (unused):
1032
1122
  // T = JS type, V = value/pointer
1033
1123
  // 0bTTT
1034
1124
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1042,7 +1132,6 @@ const generateLogicExp = (scope, decl) => {
1042
1132
  // js type: 4 bits
1043
1133
  // internal type: ? bits
1044
1134
  // pointer: 32 bits
1045
-
1046
1135
  // generic
1047
1136
  // 1 23 4 5
1048
1137
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1052,49 +1141,29 @@ const generateLogicExp = (scope, decl) => {
1052
1141
  // 4: internal type
1053
1142
  // 5: pointer
1054
1143
 
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'
1144
+ const isExistingProtoFunc = name => {
1145
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1146
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1147
+
1148
+ return false;
1084
1149
  };
1085
1150
 
1086
1151
  const getType = (scope, _name) => {
1087
1152
  const name = mapName(_name);
1088
1153
 
1154
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1155
+
1156
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1157
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1158
+
1159
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1160
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1161
 
1092
1162
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1163
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1164
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1165
 
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;
1166
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1167
 
1099
1168
  return number(type, Valtype.i32);
1100
1169
  };
@@ -1117,23 +1186,24 @@ const setType = (scope, _name, type) => {
1117
1186
  ];
1118
1187
 
1119
1188
  // throw new Error('could not find var');
1189
+ return [];
1120
1190
  };
1121
1191
 
1122
1192
  const getLastType = scope => {
1123
1193
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1194
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1195
  };
1126
1196
 
1127
1197
  const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1198
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1129
1199
  };
1130
1200
 
1131
1201
  const getNodeType = (scope, node) => {
1132
- const inner = () => {
1202
+ const ret = (() => {
1133
1203
  if (node.type === 'Literal') {
1134
- if (node.regex) return TYPES._regexp;
1204
+ if (node.regex) return TYPES.regexp;
1135
1205
 
1136
- if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1206
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1137
1207
 
1138
1208
  return TYPES[typeof node.value];
1139
1209
  }
@@ -1150,21 +1220,32 @@ const getNodeType = (scope, node) => {
1150
1220
  const name = node.callee.name;
1151
1221
  if (!name) {
1152
1222
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1223
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1224
 
1155
1225
  // presume
1156
1226
  // todo: warn here?
1157
1227
  return TYPES.number;
1158
1228
  }
1159
1229
 
1230
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1231
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1232
+ if (scope.locals['#last_type']) return getLastType(scope);
1233
+
1234
+ // presume
1235
+ // todo: warn here?
1236
+ return TYPES.number;
1237
+ }
1238
+
1239
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1240
+ }
1241
+
1160
1242
  const func = funcs.find(x => x.name === name);
1161
1243
 
1162
1244
  if (func) {
1163
- // console.log(scope, func, func.returnType);
1164
1245
  if (func.returnType) return func.returnType;
1165
1246
  }
1166
1247
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1248
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1249
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1250
 
1170
1251
  // check if this is a prototype function
@@ -1175,11 +1256,16 @@ const getNodeType = (scope, node) => {
1175
1256
  const spl = name.slice(2).split('_');
1176
1257
 
1177
1258
  const func = spl[spl.length - 1];
1178
- const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1259
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1179
1260
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1261
  }
1181
1262
 
1182
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1263
+ if (name.startsWith('__Porffor_wasm_')) {
1264
+ // todo: return undefined for non-returning ops
1265
+ return TYPES.number;
1266
+ }
1267
+
1268
+ if (scope.locals['#last_type']) return getLastType(scope);
1183
1269
 
1184
1270
  // presume
1185
1271
  // todo: warn here?
@@ -1222,11 +1308,20 @@ const getNodeType = (scope, node) => {
1222
1308
  }
1223
1309
 
1224
1310
  if (node.type === 'ArrayExpression') {
1225
- return TYPES._array;
1311
+ return TYPES.array;
1226
1312
  }
1227
1313
 
1228
1314
  if (node.type === 'BinaryExpression') {
1229
1315
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1316
+ if (node.operator !== '+') return TYPES.number;
1317
+
1318
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1319
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1320
+
1321
+ // todo: this should be dynamic but for now only static
1322
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1323
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1324
+
1230
1325
  return TYPES.number;
1231
1326
 
1232
1327
  // todo: string concat types
@@ -1251,34 +1346,47 @@ const getNodeType = (scope, node) => {
1251
1346
  if (node.operator === '!') return TYPES.boolean;
1252
1347
  if (node.operator === 'void') return TYPES.undefined;
1253
1348
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1349
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1255
1350
 
1256
1351
  return TYPES.number;
1257
1352
  }
1258
1353
 
1259
1354
  if (node.type === 'MemberExpression') {
1355
+ // hack: if something.name, string type
1356
+ if (node.property.name === 'name') {
1357
+ if (hasFuncWithName(node.object.name)) {
1358
+ return TYPES.bytestring;
1359
+ } else {
1360
+ return TYPES.undefined;
1361
+ }
1362
+ }
1363
+
1260
1364
  // hack: if something.length, number type
1261
1365
  if (node.property.name === 'length') return TYPES.number;
1262
1366
 
1263
1367
  // ts hack
1264
1368
  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;
1369
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1370
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1266
1371
 
1267
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1372
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
1373
 
1269
1374
  // presume
1270
1375
  return TYPES.number;
1271
1376
  }
1272
1377
 
1273
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1378
+ if (node.type === 'TaggedTemplateExpression') {
1379
+ // hack
1380
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1381
+ }
1382
+
1383
+ if (scope.locals['#last_type']) return getLastType(scope);
1274
1384
 
1275
1385
  // presume
1276
1386
  // todo: warn here?
1277
1387
  return TYPES.number;
1278
- };
1388
+ })();
1279
1389
 
1280
- const ret = inner();
1281
- // console.trace(node, ret);
1282
1390
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1283
1391
  return ret;
1284
1392
  };
@@ -1303,7 +1411,7 @@ const generateLiteral = (scope, decl, global, name) => {
1303
1411
  return makeString(scope, decl.value, global, name);
1304
1412
 
1305
1413
  default:
1306
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1414
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1307
1415
  }
1308
1416
  };
1309
1417
 
@@ -1312,6 +1420,8 @@ const countLeftover = wasm => {
1312
1420
 
1313
1421
  for (let i = 0; i < wasm.length; i++) {
1314
1422
  const inst = wasm[i];
1423
+ if (inst[0] == null) continue;
1424
+
1315
1425
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1316
1426
  if (inst[0] === Opcodes.if) count--;
1317
1427
  if (inst[1] !== Blocktype.void) count++;
@@ -1320,18 +1430,29 @@ const countLeftover = wasm => {
1320
1430
  if (inst[0] === Opcodes.end) depth--;
1321
1431
 
1322
1432
  if (depth === 0)
1323
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1433
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1324
1434
  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++;
1435
+ 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
1436
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1327
1437
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1328
1438
  else if (inst[0] === Opcodes.return) count = 0;
1329
1439
  else if (inst[0] === Opcodes.call) {
1330
1440
  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;
1441
+ if (inst[1] === -1) {
1442
+ // todo: count for calling self
1443
+ } else if (!func && inst[1] < importedFuncs.length) {
1444
+ count -= importedFuncs[inst[1]].params;
1445
+ count += importedFuncs[inst[1]].returns;
1446
+ } else {
1447
+ if (func) {
1448
+ count -= func.params.length;
1449
+ } else count--;
1450
+ if (func) count += func.returns.length;
1451
+ }
1452
+ } else if (inst[0] === Opcodes.call_indirect) {
1453
+ count--; // funcidx
1454
+ count -= inst[1] * 2; // params * 2 (typed)
1455
+ count += 2; // fixed return (value, type)
1335
1456
  } else count--;
1336
1457
 
1337
1458
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1408,25 +1529,27 @@ const RTArrayUtil = {
1408
1529
  };
1409
1530
 
1410
1531
  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
1532
  let name = mapName(decl.callee.name);
1421
1533
  if (isFuncType(decl.callee.type)) { // iife
1422
1534
  const func = generateFunc(scope, decl.callee);
1423
1535
  name = func.name;
1424
1536
  }
1425
1537
 
1426
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1538
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1427
1539
  // literal eval hack
1428
- const code = decl.arguments[0].value;
1429
- const parsed = parse(code, []);
1540
+ const code = decl.arguments[0]?.value ?? '';
1541
+
1542
+ let parsed;
1543
+ try {
1544
+ parsed = parse(code, []);
1545
+ } catch (e) {
1546
+ if (e.name === 'SyntaxError') {
1547
+ // throw syntax errors of evals at runtime instead
1548
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1549
+ }
1550
+
1551
+ throw e;
1552
+ }
1430
1553
 
1431
1554
  const out = generate(scope, {
1432
1555
  type: 'BlockStatement',
@@ -1440,13 +1563,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1440
1563
  const finalStatement = parsed.body[parsed.body.length - 1];
1441
1564
  out.push(
1442
1565
  ...getNodeType(scope, finalStatement),
1443
- setLastType(scope)
1566
+ ...setLastType(scope)
1444
1567
  );
1445
1568
  } else if (countLeftover(out) === 0) {
1446
1569
  out.push(...number(UNDEFINED));
1447
1570
  out.push(
1448
1571
  ...number(TYPES.undefined, Valtype.i32),
1449
- setLastType(scope)
1572
+ ...setLastType(scope)
1450
1573
  );
1451
1574
  }
1452
1575
 
@@ -1468,6 +1591,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1468
1591
 
1469
1592
  target = { ...decl.callee };
1470
1593
  target.name = spl.slice(0, -1).join('_');
1594
+
1595
+ // failed to lookup name, abort
1596
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1471
1597
  }
1472
1598
 
1473
1599
  // literal.func()
@@ -1475,22 +1601,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1475
1601
  // megahack for /regex/.func()
1476
1602
  const funcName = decl.callee.property.name;
1477
1603
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1478
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1604
+ const regex = decl.callee.object.regex.pattern;
1605
+ const rhemynName = `regex_${funcName}_${regex}`;
1606
+
1607
+ if (!funcIndex[rhemynName]) {
1608
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1479
1609
 
1480
- funcIndex[func.name] = func.index;
1481
- funcs.push(func);
1610
+ funcIndex[func.name] = func.index;
1611
+ funcs.push(func);
1612
+ }
1482
1613
 
1614
+ const idx = funcIndex[rhemynName];
1483
1615
  return [
1484
1616
  // make string arg
1485
1617
  ...generate(scope, decl.arguments[0]),
1618
+ Opcodes.i32_to_u,
1619
+ ...getNodeType(scope, decl.arguments[0]),
1486
1620
 
1487
1621
  // call regex func
1488
- Opcodes.i32_to_u,
1489
- [ Opcodes.call, func.index ],
1622
+ [ Opcodes.call, idx ],
1490
1623
  Opcodes.i32_from_u,
1491
1624
 
1492
1625
  ...number(TYPES.boolean, Valtype.i32),
1493
- setLastType(scope)
1626
+ ...setLastType(scope)
1494
1627
  ];
1495
1628
  }
1496
1629
 
@@ -1515,12 +1648,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1648
  // }
1516
1649
 
1517
1650
  if (protoName) {
1651
+ const protoBC = {};
1652
+
1653
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1654
+
1655
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1656
+ for (const x of builtinProtoCands) {
1657
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1658
+ if (type == null) continue;
1659
+
1660
+ protoBC[type] = generateCall(scope, {
1661
+ callee: {
1662
+ type: 'Identifier',
1663
+ name: x
1664
+ },
1665
+ arguments: [ target, ...decl.arguments ],
1666
+ _protoInternalCall: true
1667
+ });
1668
+ }
1669
+ }
1670
+
1518
1671
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1519
1672
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1520
1673
  return acc;
1521
1674
  }, {});
1522
1675
 
1523
- // no prototype function candidates, ignore
1524
1676
  if (Object.keys(protoCands).length > 0) {
1525
1677
  // use local for cached i32 length as commonly used
1526
1678
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1538,7 +1690,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1538
1690
 
1539
1691
  let allOptUnused = true;
1540
1692
  let lengthI32CacheUsed = false;
1541
- const protoBC = {};
1542
1693
  for (const x in protoCands) {
1543
1694
  const protoFunc = protoCands[x];
1544
1695
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1546,13 +1697,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1546
1697
  ...RTArrayUtil.getLength(getPointer),
1547
1698
 
1548
1699
  ...number(TYPES.number, Valtype.i32),
1549
- setLastType(scope)
1700
+ ...setLastType(scope)
1550
1701
  ];
1551
1702
  continue;
1552
1703
  }
1553
1704
 
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
1705
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1557
1706
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1558
1707
 
@@ -1583,7 +1732,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1583
1732
  ...protoOut,
1584
1733
 
1585
1734
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1586
- setLastType(scope),
1735
+ ...setLastType(scope),
1587
1736
  [ Opcodes.end ]
1588
1737
  ];
1589
1738
  }
@@ -1609,10 +1758,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1758
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1610
1759
  ];
1611
1760
  }
1761
+
1762
+ if (Object.keys(protoBC).length > 0) {
1763
+ return typeSwitch(scope, getNodeType(scope, target), {
1764
+ ...protoBC,
1765
+
1766
+ // TODO: error better
1767
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1768
+ }, valtypeBinary);
1769
+ }
1612
1770
  }
1613
1771
 
1614
1772
  // TODO: only allows callee as literal
1615
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1773
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1616
1774
 
1617
1775
  let idx = funcIndex[name] ?? importedFuncs[name];
1618
1776
  if (idx === undefined && builtinFuncs[name]) {
@@ -1620,22 +1778,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1778
 
1621
1779
  includeBuiltin(scope, name);
1622
1780
  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
1781
  }
1640
1782
 
1641
1783
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1645,16 +1787,100 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1787
  idx = -1;
1646
1788
  }
1647
1789
 
1790
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1791
+ const wasmOps = {
1792
+ // pointer, align, offset
1793
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1794
+ // pointer, value, align, offset
1795
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1796
+ // pointer, align, offset
1797
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1798
+ // pointer, value, align, offset
1799
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1800
+ // pointer, align, offset
1801
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1802
+ // pointer, value, align, offset
1803
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1804
+
1805
+ // pointer, align, offset
1806
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1807
+ // pointer, value, align, offset
1808
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1809
+
1810
+ // value
1811
+ i32_const: { imms: 1, args: [], returns: 1 },
1812
+ };
1813
+
1814
+ const opName = name.slice('__Porffor_wasm_'.length);
1815
+
1816
+ if (wasmOps[opName]) {
1817
+ const op = wasmOps[opName];
1818
+
1819
+ const argOut = [];
1820
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1821
+ ...generate(scope, decl.arguments[i]),
1822
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1823
+ );
1824
+
1825
+ // literals only
1826
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1827
+
1828
+ return [
1829
+ ...argOut,
1830
+ [ Opcodes[opName], ...imms ],
1831
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1832
+ ];
1833
+ }
1834
+ }
1835
+
1648
1836
  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`);
1837
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1838
+ const [ local, global ] = lookupName(scope, name);
1839
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
+
1841
+ // todo: only works when:
1842
+ // 1. arg count matches arg count of function
1843
+ // 2. function uses typedParams and typedReturns
1844
+
1845
+ funcs.table = true;
1846
+
1847
+ let args = decl.arguments;
1848
+ let argWasm = [];
1849
+
1850
+ for (let i = 0; i < args.length; i++) {
1851
+ const arg = args[i];
1852
+ argWasm = argWasm.concat(generate(scope, arg));
1853
+
1854
+ if (valtypeBinary !== Valtype.i32 && (
1855
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1856
+ (importedFuncs[name] && name.startsWith('profile'))
1857
+ )) {
1858
+ argWasm.push(Opcodes.i32_to);
1859
+ }
1860
+
1861
+ argWasm = argWasm.concat(getNodeType(scope, arg));
1862
+ }
1863
+
1864
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1865
+ [TYPES.function]: [
1866
+ ...argWasm,
1867
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1868
+ Opcodes.i32_to_u,
1869
+ [ Opcodes.call_indirect, args.length, 0 ],
1870
+ ...setLastType(scope)
1871
+ ],
1872
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1873
+ });
1874
+ }
1875
+
1876
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1651
1877
  }
1652
1878
 
1653
1879
  const func = funcs.find(x => x.index === idx);
1654
1880
 
1655
1881
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1882
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1883
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1658
1884
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1885
 
1660
1886
  let args = decl.arguments;
@@ -1671,14 +1897,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1897
  if (func && func.throws) scope.throws = true;
1672
1898
 
1673
1899
  let out = [];
1674
- for (const arg of args) {
1900
+ for (let i = 0; i < args.length; i++) {
1901
+ const arg = args[i];
1675
1902
  out = out.concat(generate(scope, arg));
1903
+
1904
+ if (i >= paramCount) {
1905
+ // over param count of func, drop arg
1906
+ out.push([ Opcodes.drop ]);
1907
+ continue;
1908
+ }
1909
+
1910
+ if (valtypeBinary !== Valtype.i32 && (
1911
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1912
+ (importedFuncs[name] && name.startsWith('profile'))
1913
+ )) {
1914
+ out.push(Opcodes.i32_to);
1915
+ }
1916
+
1676
1917
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1918
  }
1678
1919
 
1679
1920
  out.push([ Opcodes.call, idx ]);
1680
1921
 
1681
- if (!typedReturn) {
1922
+ if (!typedReturns) {
1682
1923
  // let type;
1683
1924
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1925
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1688,7 +1929,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1688
1929
  // ...number(type, Valtype.i32),
1689
1930
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1690
1931
  // );
1691
- } else out.push(setLastType(scope));
1932
+ } else out.push(...setLastType(scope));
1933
+
1934
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1935
+ out.push(Opcodes.i32_from);
1936
+ }
1692
1937
 
1693
1938
  return out;
1694
1939
  };
@@ -1696,8 +1941,26 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1696
1941
  const generateNew = (scope, decl, _global, _name) => {
1697
1942
  // hack: basically treat this as a normal call for builtins for now
1698
1943
  const name = mapName(decl.callee.name);
1944
+
1699
1945
  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)})`);
1946
+
1947
+ if (builtinFuncs[name + '$constructor']) {
1948
+ // custom ...$constructor override builtin func
1949
+ return generateCall(scope, {
1950
+ ...decl,
1951
+ callee: {
1952
+ type: 'Identifier',
1953
+ name: name + '$constructor'
1954
+ }
1955
+ }, _global, _name);
1956
+ }
1957
+
1958
+ if (
1959
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
1960
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
1961
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
1962
+
1963
+ 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
1964
 
1702
1965
  return generateCall(scope, decl, _global, _name);
1703
1966
  };
@@ -1814,14 +2077,14 @@ const brTable = (input, bc, returns) => {
1814
2077
  };
1815
2078
 
1816
2079
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2080
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
1818
2081
 
1819
2082
  const known = knownType(scope, type);
1820
2083
  if (known != null) {
1821
2084
  return bc[known] ?? bc.default;
1822
2085
  }
1823
2086
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
2087
+ if (Prefs.typeswitchUseBrtable)
1825
2088
  return brTable(type, bc, returns);
1826
2089
 
1827
2090
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1831,8 +2094,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
2094
  [ Opcodes.block, returns ]
1832
2095
  ];
1833
2096
 
1834
- // todo: use br_table?
1835
-
1836
2097
  for (const x in bc) {
1837
2098
  if (x === 'default') continue;
1838
2099
 
@@ -1856,7 +2117,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
2117
  return out;
1857
2118
  };
1858
2119
 
1859
- const allocVar = (scope, name, global = false) => {
2120
+ const allocVar = (scope, name, global = false, type = true) => {
1860
2121
  const target = global ? globals : scope.locals;
1861
2122
 
1862
2123
  // already declared
@@ -1870,8 +2131,10 @@ const allocVar = (scope, name, global = false) => {
1870
2131
  let idx = global ? globalInd++ : scope.localInd++;
1871
2132
  target[name] = { idx, type: valtypeBinary };
1872
2133
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2134
+ if (type) {
2135
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2136
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2137
+ }
1875
2138
 
1876
2139
  return idx;
1877
2140
  };
@@ -1886,11 +2149,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1886
2149
  };
1887
2150
 
1888
2151
  const typeAnnoToPorfType = x => {
1889
- if (TYPES[x]) return TYPES[x];
1890
- if (TYPES['_' + x]) return TYPES['_' + x];
2152
+ if (!x) return null;
2153
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
1891
2154
 
1892
2155
  switch (x) {
1893
2156
  case 'i32':
2157
+ case 'i64':
2158
+ case 'f64':
1894
2159
  return TYPES.number;
1895
2160
  }
1896
2161
 
@@ -1901,7 +2166,7 @@ const extractTypeAnnotation = decl => {
1901
2166
  let a = decl;
1902
2167
  while (a.typeAnnotation) a = a.typeAnnotation;
1903
2168
 
1904
- let type, elementType;
2169
+ let type = null, elementType = null;
1905
2170
  if (a.typeName) {
1906
2171
  type = a.typeName.name;
1907
2172
  } else if (a.type.endsWith('Keyword')) {
@@ -1914,7 +2179,7 @@ const extractTypeAnnotation = decl => {
1914
2179
  const typeName = type;
1915
2180
  type = typeAnnoToPorfType(type);
1916
2181
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2182
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2183
 
1919
2184
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2185
 
@@ -1926,13 +2191,13 @@ const generateVar = (scope, decl) => {
1926
2191
 
1927
2192
  const topLevel = scope.name === 'main';
1928
2193
 
1929
- // global variable if in top scope (main) and var ..., or if wanted
1930
- const global = topLevel || decl._bare; // decl.kind === 'var';
2194
+ // global variable if in top scope (main) or if internally wanted
2195
+ const global = topLevel || decl._bare;
1931
2196
 
1932
2197
  for (const x of decl.declarations) {
1933
2198
  const name = mapName(x.id.name);
1934
2199
 
1935
- if (!name) return todo('destructuring is not supported yet');
2200
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1936
2201
 
1937
2202
  if (x.init && isFuncType(x.init.type)) {
1938
2203
  // hack for let a = function () { ... }
@@ -1941,7 +2206,6 @@ const generateVar = (scope, decl) => {
1941
2206
  continue;
1942
2207
  }
1943
2208
 
1944
- // console.log(name);
1945
2209
  if (topLevel && builtinVars[name]) {
1946
2210
  // cannot redeclare
1947
2211
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -1949,16 +2213,46 @@ const generateVar = (scope, decl) => {
1949
2213
  continue; // always ignore
1950
2214
  }
1951
2215
 
1952
- let idx = allocVar(scope, name, global);
2216
+ // // generate init before allocating var
2217
+ // let generated;
2218
+ // if (x.init) generated = generate(scope, x.init, global, name);
1953
2219
 
1954
- if (typedInput && x.id.typeAnnotation) {
2220
+ const typed = typedInput && x.id.typeAnnotation;
2221
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2222
+
2223
+ if (typed) {
1955
2224
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
2225
  }
1957
2226
 
1958
2227
  if (x.init) {
1959
- out = out.concat(generate(scope, x.init, global, name));
2228
+ // if (isFuncType(x.init.type)) {
2229
+ // // let a = function () { ... }
2230
+ // x.init.id = { name };
2231
+
2232
+ // const func = generateFunc(scope, x.init);
2233
+
2234
+ // out.push(
2235
+ // ...number(func.index - importedFuncs.length),
2236
+ // [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
2237
+
2238
+ // ...setType(scope, name, TYPES.function)
2239
+ // );
2240
+
2241
+ // continue;
2242
+ // }
2243
+
2244
+ const generated = generate(scope, x.init, global, name);
2245
+ if (scope.arrays?.get(name) != null) {
2246
+ // hack to set local as pointer before
2247
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2248
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2249
+ generated.pop();
2250
+ out = out.concat(generated);
2251
+ } else {
2252
+ out = out.concat(generated);
2253
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2254
+ }
1960
2255
 
1961
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1962
2256
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1963
2257
  }
1964
2258
 
@@ -1969,8 +2263,10 @@ const generateVar = (scope, decl) => {
1969
2263
  return out;
1970
2264
  };
1971
2265
 
1972
- const generateAssign = (scope, decl) => {
2266
+ // todo: optimize this func for valueUnused
2267
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1973
2268
  const { type, name } = decl.left;
2269
+ const [ local, isGlobal ] = lookupName(scope, name);
1974
2270
 
1975
2271
  if (type === 'ObjectPattern') {
1976
2272
  // hack: ignore object parts of `var a = {} = 2`
@@ -1980,26 +2276,44 @@ const generateAssign = (scope, decl) => {
1980
2276
  if (isFuncType(decl.right.type)) {
1981
2277
  // hack for a = function () { ... }
1982
2278
  decl.right.id = { name };
1983
- generateFunc(scope, decl.right);
1984
- return [];
2279
+
2280
+ const func = generateFunc(scope, decl.right);
2281
+
2282
+ return [
2283
+ ...number(func.index - importedFuncs.length),
2284
+ ...(local != null ? [
2285
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2286
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2287
+
2288
+ ...setType(scope, name, TYPES.function)
2289
+ ] : [])
2290
+ ];
1985
2291
  }
1986
2292
 
2293
+ const op = decl.operator.slice(0, -1) || '=';
2294
+
1987
2295
  // hack: .length setter
1988
2296
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1989
2297
  const name = decl.left.object.name;
1990
- const pointer = arrays.get(name);
2298
+ const pointer = scope.arrays?.get(name);
1991
2299
 
1992
- const aotPointer = pointer != null;
2300
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1993
2301
 
1994
2302
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2303
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1995
2304
 
1996
2305
  return [
1997
2306
  ...(aotPointer ? number(0, Valtype.i32) : [
1998
2307
  ...generate(scope, decl.left.object),
1999
2308
  Opcodes.i32_to_u
2000
2309
  ]),
2310
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2001
2311
 
2002
- ...generate(scope, decl.right),
2312
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2313
+ [ Opcodes.local_get, pointerTmp ],
2314
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2315
+ Opcodes.i32_from_u
2316
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2003
2317
  [ Opcodes.local_tee, newValueTmp ],
2004
2318
 
2005
2319
  Opcodes.i32_to_u,
@@ -2009,21 +2323,19 @@ const generateAssign = (scope, decl) => {
2009
2323
  ];
2010
2324
  }
2011
2325
 
2012
- const op = decl.operator.slice(0, -1) || '=';
2013
-
2014
2326
  // arr[i]
2015
2327
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2016
2328
  const name = decl.left.object.name;
2017
- const pointer = arrays.get(name);
2329
+ const pointer = scope.arrays?.get(name);
2018
2330
 
2019
- const aotPointer = pointer != null;
2331
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2020
2332
 
2021
2333
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2022
2334
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2023
2335
 
2024
2336
  return [
2025
2337
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2026
- [TYPES._array]: [
2338
+ [TYPES.array]: [
2027
2339
  ...(aotPointer ? [] : [
2028
2340
  ...generate(scope, decl.left.object),
2029
2341
  Opcodes.i32_to_u
@@ -2072,9 +2384,7 @@ const generateAssign = (scope, decl) => {
2072
2384
  ];
2073
2385
  }
2074
2386
 
2075
- if (!name) return todo('destructuring is not supported yet');
2076
-
2077
- const [ local, isGlobal ] = lookupName(scope, name);
2387
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2078
2388
 
2079
2389
  if (local === undefined) {
2080
2390
  // todo: this should be a sloppy mode only thing
@@ -2120,9 +2430,7 @@ const generateAssign = (scope, decl) => {
2120
2430
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2431
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2432
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2433
+ ...setType(scope, name, getLastType(scope))
2126
2434
  ];
2127
2435
  }
2128
2436
 
@@ -2133,9 +2441,7 @@ const generateAssign = (scope, decl) => {
2133
2441
 
2134
2442
  // todo: string concat types
2135
2443
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2444
+ ...setType(scope, name, TYPES.number)
2139
2445
  ];
2140
2446
  };
2141
2447
 
@@ -2181,7 +2487,7 @@ const generateUnary = (scope, decl) => {
2181
2487
  return out;
2182
2488
  }
2183
2489
 
2184
- case 'delete':
2490
+ case 'delete': {
2185
2491
  let toReturn = true, toGenerate = true;
2186
2492
 
2187
2493
  if (decl.argument.type === 'Identifier') {
@@ -2203,40 +2509,61 @@ const generateUnary = (scope, decl) => {
2203
2509
 
2204
2510
  out.push(...number(toReturn ? 1 : 0));
2205
2511
  return out;
2512
+ }
2513
+
2514
+ case 'typeof': {
2515
+ let overrideType, toGenerate = true;
2206
2516
 
2207
- case 'typeof':
2208
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2517
+ if (decl.argument.type === 'Identifier') {
2518
+ const out = generateIdent(scope, decl.argument);
2519
+
2520
+ // if ReferenceError (undeclared var), ignore and return undefined
2521
+ if (out[1]) {
2522
+ // does not exist (2 ops from throw)
2523
+ overrideType = number(TYPES.undefined, Valtype.i32);
2524
+ toGenerate = false;
2525
+ }
2526
+ }
2527
+
2528
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2529
+ disposeLeftover(out);
2530
+
2531
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2209
2532
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2210
2533
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2211
2534
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2212
2535
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2213
2536
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2537
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2214
2538
 
2215
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2539
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
2540
 
2217
2541
  // object and internal types
2218
2542
  default: makeString(scope, 'object', false, '#typeof_result'),
2219
- });
2543
+ }));
2544
+
2545
+ return out;
2546
+ }
2220
2547
 
2221
2548
  default:
2222
- return todo(`unary operator ${decl.operator} not implemented yet`);
2549
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2223
2550
  }
2224
2551
  };
2225
2552
 
2226
- const generateUpdate = (scope, decl) => {
2553
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2227
2554
  const { name } = decl.argument;
2228
2555
 
2229
2556
  const [ local, isGlobal ] = lookupName(scope, name);
2230
2557
 
2231
2558
  if (local === undefined) {
2232
- return todo(`update expression with undefined variable`);
2559
+ return todo(scope, `update expression with undefined variable`, true);
2233
2560
  }
2234
2561
 
2235
2562
  const idx = local.idx;
2236
2563
  const out = [];
2237
2564
 
2238
2565
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2239
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2566
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2240
2567
 
2241
2568
  switch (decl.operator) {
2242
2569
  case '++':
@@ -2249,7 +2576,7 @@ const generateUpdate = (scope, decl) => {
2249
2576
  }
2250
2577
 
2251
2578
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2252
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2579
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2253
2580
 
2254
2581
  return out;
2255
2582
  };
@@ -2289,7 +2616,7 @@ const generateConditional = (scope, decl) => {
2289
2616
  // note type
2290
2617
  out.push(
2291
2618
  ...getNodeType(scope, decl.consequent),
2292
- setLastType(scope)
2619
+ ...setLastType(scope)
2293
2620
  );
2294
2621
 
2295
2622
  out.push([ Opcodes.else ]);
@@ -2298,7 +2625,7 @@ const generateConditional = (scope, decl) => {
2298
2625
  // note type
2299
2626
  out.push(
2300
2627
  ...getNodeType(scope, decl.alternate),
2301
- setLastType(scope)
2628
+ ...setLastType(scope)
2302
2629
  );
2303
2630
 
2304
2631
  out.push([ Opcodes.end ]);
@@ -2312,7 +2639,7 @@ const generateFor = (scope, decl) => {
2312
2639
  const out = [];
2313
2640
 
2314
2641
  if (decl.init) {
2315
- out.push(...generate(scope, decl.init));
2642
+ out.push(...generate(scope, decl.init, false, undefined, true));
2316
2643
  disposeLeftover(out);
2317
2644
  }
2318
2645
 
@@ -2330,7 +2657,7 @@ const generateFor = (scope, decl) => {
2330
2657
  out.push(...generate(scope, decl.body));
2331
2658
  out.push([ Opcodes.end ]);
2332
2659
 
2333
- if (decl.update) out.push(...generate(scope, decl.update));
2660
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2334
2661
 
2335
2662
  out.push([ Opcodes.br, 1 ]);
2336
2663
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2358,6 +2685,36 @@ const generateWhile = (scope, decl) => {
2358
2685
  return out;
2359
2686
  };
2360
2687
 
2688
+ const generateDoWhile = (scope, decl) => {
2689
+ const out = [];
2690
+
2691
+ out.push([ Opcodes.loop, Blocktype.void ]);
2692
+ depth.push('dowhile');
2693
+
2694
+ // block for break (includes all)
2695
+ out.push([ Opcodes.block, Blocktype.void ]);
2696
+ depth.push('block');
2697
+
2698
+ // block for continue
2699
+ // includes body but not test+loop so we can exit body at anytime
2700
+ // and still test+loop after
2701
+ out.push([ Opcodes.block, Blocktype.void ]);
2702
+ depth.push('block');
2703
+
2704
+ out.push(...generate(scope, decl.body));
2705
+
2706
+ out.push([ Opcodes.end ]);
2707
+ depth.pop();
2708
+
2709
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2710
+ out.push([ Opcodes.br_if, 1 ]);
2711
+
2712
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2713
+ depth.pop(); depth.pop();
2714
+
2715
+ return out;
2716
+ };
2717
+
2361
2718
  const generateForOf = (scope, decl) => {
2362
2719
  const out = [];
2363
2720
 
@@ -2394,7 +2751,10 @@ const generateForOf = (scope, decl) => {
2394
2751
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
2752
  }
2396
2753
 
2754
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2755
+
2397
2756
  const [ local, isGlobal ] = lookupName(scope, leftName);
2757
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2398
2758
 
2399
2759
  depth.push('block');
2400
2760
  depth.push('block');
@@ -2403,6 +2763,7 @@ const generateForOf = (scope, decl) => {
2403
2763
  // hack: this is naughty and will break things!
2404
2764
  let newOut = number(0, Valtype.f64), newPointer = -1;
2405
2765
  if (pages.hasAnyString) {
2766
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2406
2767
  0, [ newOut, newPointer ] = makeArray(scope, {
2407
2768
  rawElements: new Array(1)
2408
2769
  }, isGlobal, leftName, true, 'i16');
@@ -2411,7 +2772,7 @@ const generateForOf = (scope, decl) => {
2411
2772
  // set type for local
2412
2773
  // todo: optimize away counter and use end pointer
2413
2774
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2414
- [TYPES._array]: [
2775
+ [TYPES.array]: [
2415
2776
  ...setType(scope, leftName, TYPES.number),
2416
2777
 
2417
2778
  [ Opcodes.loop, Blocktype.void ],
@@ -2494,6 +2855,56 @@ const generateForOf = (scope, decl) => {
2494
2855
  [ Opcodes.end ],
2495
2856
  [ Opcodes.end ]
2496
2857
  ],
2858
+ [TYPES.bytestring]: [
2859
+ ...setType(scope, leftName, TYPES.bytestring),
2860
+
2861
+ [ Opcodes.loop, Blocktype.void ],
2862
+
2863
+ // setup new/out array
2864
+ ...newOut,
2865
+ [ Opcodes.drop ],
2866
+
2867
+ ...number(0, Valtype.i32), // base 0 for store after
2868
+
2869
+ // load current string ind {arg}
2870
+ [ Opcodes.local_get, pointer ],
2871
+ [ Opcodes.local_get, counter ],
2872
+ [ Opcodes.i32_add ],
2873
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2874
+
2875
+ // store to new string ind 0
2876
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2877
+
2878
+ // return new string (page)
2879
+ ...number(newPointer),
2880
+
2881
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2882
+
2883
+ [ Opcodes.block, Blocktype.void ],
2884
+ [ Opcodes.block, Blocktype.void ],
2885
+ ...generate(scope, decl.body),
2886
+ [ Opcodes.end ],
2887
+
2888
+ // increment iter pointer
2889
+ // [ Opcodes.local_get, pointer ],
2890
+ // ...number(1, Valtype.i32),
2891
+ // [ Opcodes.i32_add ],
2892
+ // [ Opcodes.local_set, pointer ],
2893
+
2894
+ // increment counter by 1
2895
+ [ Opcodes.local_get, counter ],
2896
+ ...number(1, Valtype.i32),
2897
+ [ Opcodes.i32_add ],
2898
+ [ Opcodes.local_tee, counter ],
2899
+
2900
+ // loop if counter != length
2901
+ [ Opcodes.local_get, length ],
2902
+ [ Opcodes.i32_ne ],
2903
+ [ Opcodes.br_if, 1 ],
2904
+
2905
+ [ Opcodes.end ],
2906
+ [ Opcodes.end ]
2907
+ ],
2497
2908
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2498
2909
  }, Blocktype.void));
2499
2910
 
@@ -2504,28 +2915,65 @@ const generateForOf = (scope, decl) => {
2504
2915
  return out;
2505
2916
  };
2506
2917
 
2918
+ // find the nearest loop in depth map by type
2507
2919
  const getNearestLoop = () => {
2508
2920
  for (let i = depth.length - 1; i >= 0; i--) {
2509
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2921
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2510
2922
  }
2511
2923
 
2512
2924
  return -1;
2513
2925
  };
2514
2926
 
2515
2927
  const generateBreak = (scope, decl) => {
2516
- const nearestLoop = depth.length - getNearestLoop();
2928
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2929
+ const type = depth[target];
2930
+
2931
+ // different loop types have different branch offsets
2932
+ // as they have different wasm block/loop/if structures
2933
+ // we need to use the right offset by type to branch to the one we want
2934
+ // for a break: exit the loop without executing anything else inside it
2935
+ const offset = ({
2936
+ for: 2, // loop > if (wanted branch) > block (we are here)
2937
+ while: 2, // loop > if (wanted branch) (we are here)
2938
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2939
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2940
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2941
+ })[type];
2942
+
2517
2943
  return [
2518
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2944
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2519
2945
  ];
2520
2946
  };
2521
2947
 
2522
2948
  const generateContinue = (scope, decl) => {
2523
- const nearestLoop = depth.length - getNearestLoop();
2949
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2950
+ const type = depth[target];
2951
+
2952
+ // different loop types have different branch offsets
2953
+ // as they have different wasm block/loop/if structures
2954
+ // we need to use the right offset by type to branch to the one we want
2955
+ // for a continue: do test for the loop, and then loop depending on that success
2956
+ const offset = ({
2957
+ for: 3, // loop (wanted branch) > if > block (we are here)
2958
+ while: 1, // loop (wanted branch) > if (we are here)
2959
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2960
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2961
+ })[type];
2962
+
2524
2963
  return [
2525
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2964
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2526
2965
  ];
2527
2966
  };
2528
2967
 
2968
+ const generateLabel = (scope, decl) => {
2969
+ scope.labels ??= new Map();
2970
+
2971
+ const name = decl.label.name;
2972
+ scope.labels.set(name, depth.length);
2973
+
2974
+ return generate(scope, decl.body);
2975
+ };
2976
+
2529
2977
  const generateThrow = (scope, decl) => {
2530
2978
  scope.throws = true;
2531
2979
 
@@ -2546,6 +2994,9 @@ const generateThrow = (scope, decl) => {
2546
2994
  let exceptId = exceptions.push({ constructor, message }) - 1;
2547
2995
  let tagIdx = tags[0].idx;
2548
2996
 
2997
+ scope.exceptions ??= [];
2998
+ scope.exceptions.push(exceptId);
2999
+
2549
3000
  // todo: write a description of how this works lol
2550
3001
 
2551
3002
  return [
@@ -2555,7 +3006,7 @@ const generateThrow = (scope, decl) => {
2555
3006
  };
2556
3007
 
2557
3008
  const generateTry = (scope, decl) => {
2558
- if (decl.finalizer) return todo('try finally not implemented yet');
3009
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2559
3010
 
2560
3011
  const out = [];
2561
3012
 
@@ -2582,15 +3033,8 @@ const generateEmpty = (scope, decl) => {
2582
3033
  return [];
2583
3034
  };
2584
3035
 
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
3036
  let pages = new Map();
2593
- const allocPage = (reason, type) => {
3037
+ const allocPage = (scope, reason, type) => {
2594
3038
  if (pages.has(reason)) return pages.get(reason).ind;
2595
3039
 
2596
3040
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2601,16 +3045,20 @@ const allocPage = (reason, type) => {
2601
3045
  const ind = pages.size;
2602
3046
  pages.set(reason, { ind, type });
2603
3047
 
2604
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3048
+ scope.pages ??= new Map();
3049
+ scope.pages.set(reason, { ind, type });
3050
+
3051
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2605
3052
 
2606
3053
  return ind;
2607
3054
  };
2608
3055
 
3056
+ // todo: add scope.pages
2609
3057
  const freePage = reason => {
2610
3058
  const { ind } = pages.get(reason);
2611
3059
  pages.delete(reason);
2612
3060
 
2613
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3061
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2614
3062
 
2615
3063
  return ind;
2616
3064
  };
@@ -2661,16 +3109,22 @@ const getAllocType = itemType => {
2661
3109
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2662
3110
  const out = [];
2663
3111
 
3112
+ scope.arrays ??= new Map();
3113
+
2664
3114
  let firstAssign = false;
2665
- if (!arrays.has(name) || name === '$undeclared') {
3115
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2666
3116
  firstAssign = true;
2667
3117
 
2668
3118
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2669
3119
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2670
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3120
+
3121
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3122
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2671
3123
  }
2672
3124
 
2673
- const pointer = arrays.get(name);
3125
+ const pointer = scope.arrays.get(name);
3126
+
3127
+ const local = global ? globals[name] : scope.locals[name];
2674
3128
 
2675
3129
  const useRawElements = !!decl.rawElements;
2676
3130
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2678,19 +3132,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2678
3132
  const valtype = itemTypeToValtype[itemType];
2679
3133
  const length = elements.length;
2680
3134
 
2681
- if (firstAssign && useRawElements) {
2682
- let bytes = compileBytes(length, 'i32');
3135
+ if (firstAssign && useRawElements && !Prefs.noData) {
3136
+ // if length is 0 memory/data will just be 0000... anyway
3137
+ if (length !== 0) {
3138
+ let bytes = compileBytes(length, 'i32');
2683
3139
 
2684
- if (!initEmpty) for (let i = 0; i < length; i++) {
2685
- if (elements[i] == null) continue;
3140
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3141
+ if (elements[i] == null) continue;
2686
3142
 
2687
- bytes.push(...compileBytes(elements[i], itemType));
2688
- }
3143
+ bytes.push(...compileBytes(elements[i], itemType));
3144
+ }
2689
3145
 
2690
- data.push({
2691
- offset: pointer,
2692
- bytes
2693
- });
3146
+ const ind = data.push({
3147
+ offset: pointer,
3148
+ bytes
3149
+ }) - 1;
3150
+
3151
+ scope.data ??= [];
3152
+ scope.data.push(ind);
3153
+ }
2694
3154
 
2695
3155
  // local value as pointer
2696
3156
  out.push(...number(pointer));
@@ -2698,11 +3158,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2698
3158
  return [ out, pointer ];
2699
3159
  }
2700
3160
 
3161
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3162
+ if (pointerTmp != null) {
3163
+ out.push(
3164
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3165
+ Opcodes.i32_to_u,
3166
+ [ Opcodes.local_set, pointerTmp ]
3167
+ );
3168
+ }
3169
+
3170
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
+
2701
3172
  // store length as 0th array
2702
3173
  out.push(
2703
- ...number(0, Valtype.i32),
3174
+ ...pointerWasm,
2704
3175
  ...number(length, Valtype.i32),
2705
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3176
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2706
3177
  );
2707
3178
 
2708
3179
  const storeOp = StoreOps[itemType];
@@ -2711,20 +3182,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2711
3182
  if (elements[i] == null) continue;
2712
3183
 
2713
3184
  out.push(
2714
- ...number(0, Valtype.i32),
3185
+ ...pointerWasm,
2715
3186
  ...(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]) ]
3187
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2717
3188
  );
2718
3189
  }
2719
3190
 
2720
3191
  // local value as pointer
2721
- out.push(...number(pointer));
3192
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2722
3193
 
2723
3194
  return [ out, pointer ];
2724
3195
  };
2725
3196
 
2726
3197
  const byteStringable = str => {
2727
- if (!process.argv.includes('-bytestring')) return false;
3198
+ if (!Prefs.bytestring) return false;
2728
3199
 
2729
3200
  for (let i = 0; i < str.length; i++) {
2730
3201
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2735,7 +3206,7 @@ const byteStringable = str => {
2735
3206
 
2736
3207
  const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2737
3208
  const rawElements = new Array(str.length);
2738
- let byteStringable = process.argv.includes('-bytestring');
3209
+ let byteStringable = Prefs.bytestring;
2739
3210
  for (let i = 0; i < str.length; i++) {
2740
3211
  const c = str.charCodeAt(i);
2741
3212
  rawElements[i] = c;
@@ -2750,20 +3221,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2750
3221
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2751
3222
  };
2752
3223
 
2753
- let arrays = new Map();
2754
3224
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2755
3225
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2756
3226
  };
2757
3227
 
2758
3228
  export const generateMember = (scope, decl, _global, _name) => {
2759
3229
  const name = decl.object.name;
2760
- const pointer = arrays.get(name);
3230
+ const pointer = scope.arrays?.get(name);
3231
+
3232
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2761
3233
 
2762
- const aotPointer = pointer != null;
3234
+ // hack: .name
3235
+ if (decl.property.name === 'name') {
3236
+ if (hasFuncWithName(name)) {
3237
+ let nameProp = name;
3238
+
3239
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3240
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
+
3242
+ return makeString(scope, nameProp, _global, _name, true);
3243
+ } else {
3244
+ return generate(scope, DEFAULT_VALUE);
3245
+ }
3246
+ }
2763
3247
 
2764
3248
  // hack: .length
2765
3249
  if (decl.property.name === 'length') {
2766
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3250
+ const func = funcs.find(x => x.name === name);
3251
+ if (func) {
3252
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3253
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3254
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3255
+ }
3256
+
3257
+ if (builtinFuncs[name + '$constructor']) {
3258
+ const regularFunc = builtinFuncs[name];
3259
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3260
+
3261
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3262
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
+
3264
+ return number(Math.max(regularParams, constructorParams));
3265
+ }
3266
+
3267
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3268
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3269
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3270
+
2767
3271
  return [
2768
3272
  ...(aotPointer ? number(0, Valtype.i32) : [
2769
3273
  ...generate(scope, decl.object),
@@ -2788,7 +3292,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2788
3292
  }
2789
3293
 
2790
3294
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2791
- [TYPES._array]: [
3295
+ [TYPES.array]: [
2792
3296
  // get index as valtype
2793
3297
  ...property,
2794
3298
 
@@ -2807,7 +3311,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2807
3311
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2808
3312
 
2809
3313
  ...number(TYPES.number, Valtype.i32),
2810
- setLastType(scope)
3314
+ ...setLastType(scope)
2811
3315
  ],
2812
3316
 
2813
3317
  [TYPES.string]: [
@@ -2839,9 +3343,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2839
3343
  ...number(newPointer),
2840
3344
 
2841
3345
  ...number(TYPES.string, Valtype.i32),
2842
- setLastType(scope)
3346
+ ...setLastType(scope)
2843
3347
  ],
2844
- [TYPES._bytestring]: [
3348
+ [TYPES.bytestring]: [
2845
3349
  // setup new/out array
2846
3350
  ...newOut,
2847
3351
  [ Opcodes.drop ],
@@ -2858,19 +3362,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2858
3362
  ]),
2859
3363
 
2860
3364
  // load current string ind {arg}
2861
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3365
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
3366
 
2863
3367
  // store to new string ind 0
2864
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3368
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
3369
 
2866
3370
  // return new string (page)
2867
3371
  ...number(newPointer),
2868
3372
 
2869
- ...number(TYPES._bytestring, Valtype.i32),
2870
- setLastType(scope)
3373
+ ...number(TYPES.bytestring, Valtype.i32),
3374
+ ...setLastType(scope)
2871
3375
  ],
2872
3376
 
2873
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3377
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2874
3378
  });
2875
3379
  };
2876
3380
 
@@ -2880,28 +3384,36 @@ const objectHack = node => {
2880
3384
  if (!node) return node;
2881
3385
 
2882
3386
  if (node.type === 'MemberExpression') {
2883
- if (node.computed || node.optional) return node;
3387
+ const out = (() => {
3388
+ if (node.computed || node.optional) return;
2884
3389
 
2885
- let objectName = node.object.name;
3390
+ let objectName = node.object.name;
2886
3391
 
2887
- // if object is not identifier or another member exp, give up
2888
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3392
+ // if object is not identifier or another member exp, give up
3393
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3394
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2889
3395
 
2890
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3396
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2891
3397
 
2892
- // if .length, give up (hack within a hack!)
2893
- if (node.property.name === 'length') return node;
3398
+ // if .name or .length, give up (hack within a hack!)
3399
+ if (['name', 'length'].includes(node.property.name)) {
3400
+ node.object = objectHack(node.object);
3401
+ return;
3402
+ }
2894
3403
 
2895
- // no object name, give up
2896
- if (!objectName) return node;
3404
+ // no object name, give up
3405
+ if (!objectName) return;
2897
3406
 
2898
- const name = '__' + objectName + '_' + node.property.name;
2899
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3407
+ const name = '__' + objectName + '_' + node.property.name;
3408
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2900
3409
 
2901
- return {
2902
- type: 'Identifier',
2903
- name
2904
- };
3410
+ return {
3411
+ type: 'Identifier',
3412
+ name
3413
+ };
3414
+ })();
3415
+
3416
+ if (out) return out;
2905
3417
  }
2906
3418
 
2907
3419
  for (const x in node) {
@@ -2915,8 +3427,8 @@ const objectHack = node => {
2915
3427
  };
2916
3428
 
2917
3429
  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');
3430
+ if (decl.async) return todo(scope, 'async functions are not supported');
3431
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2920
3432
 
2921
3433
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2922
3434
  const params = decl.params ?? [];
@@ -2932,6 +3444,14 @@ const generateFunc = (scope, decl) => {
2932
3444
  name
2933
3445
  };
2934
3446
 
3447
+ if (typedInput && decl.returnType) {
3448
+ const { type } = extractTypeAnnotation(decl.returnType);
3449
+ if (type != null && !Prefs.indirectCalls) {
3450
+ innerScope.returnType = type;
3451
+ innerScope.returns = [ valtypeBinary ];
3452
+ }
3453
+ }
3454
+
2935
3455
  for (let i = 0; i < params.length; i++) {
2936
3456
  allocVar(innerScope, params[i].name, false);
2937
3457
 
@@ -2958,6 +3478,8 @@ const generateFunc = (scope, decl) => {
2958
3478
  };
2959
3479
  funcIndex[name] = func.index;
2960
3480
 
3481
+ if (name === 'main') func.gotLastType = true;
3482
+
2961
3483
  // quick hack fixes
2962
3484
  for (const inst of wasm) {
2963
3485
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2969,7 +3491,7 @@ const generateFunc = (scope, decl) => {
2969
3491
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2970
3492
  wasm.push(
2971
3493
  ...number(0),
2972
- ...number(TYPES.undefined, Valtype.i32),
3494
+ ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
2973
3495
  [ Opcodes.return ]
2974
3496
  );
2975
3497
  }
@@ -2992,16 +3514,6 @@ const generateCode = (scope, decl) => {
2992
3514
  };
2993
3515
 
2994
3516
  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
3517
  Array: {
3006
3518
  generate: (scope, decl, global, name) => {
3007
3519
  // new Array(i0, i1, ...)
@@ -3019,7 +3531,7 @@ const internalConstrs = {
3019
3531
 
3020
3532
  // todo: check in wasm instead of here
3021
3533
  const literalValue = arg.value ?? 0;
3022
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3534
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3023
3535
 
3024
3536
  return [
3025
3537
  ...number(0, Valtype.i32),
@@ -3030,7 +3542,8 @@ const internalConstrs = {
3030
3542
  ...number(pointer)
3031
3543
  ];
3032
3544
  },
3033
- type: TYPES._array
3545
+ type: TYPES.array,
3546
+ length: 1
3034
3547
  },
3035
3548
 
3036
3549
  __Array_of: {
@@ -3041,27 +3554,138 @@ const internalConstrs = {
3041
3554
  elements: decl.arguments
3042
3555
  }, global, name);
3043
3556
  },
3044
- type: TYPES._array,
3557
+ type: TYPES.array,
3558
+ notConstr: true,
3559
+ length: 0
3560
+ },
3561
+
3562
+ __Porffor_fastOr: {
3563
+ generate: (scope, decl) => {
3564
+ const out = [];
3565
+
3566
+ for (let i = 0; i < decl.arguments.length; i++) {
3567
+ out.push(
3568
+ ...generate(scope, decl.arguments[i]),
3569
+ Opcodes.i32_to_u,
3570
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3571
+ );
3572
+ }
3573
+
3574
+ out.push(Opcodes.i32_from_u);
3575
+
3576
+ return out;
3577
+ },
3578
+ type: TYPES.boolean,
3045
3579
  notConstr: true
3046
- }
3047
- };
3580
+ },
3581
+
3582
+ __Porffor_fastAnd: {
3583
+ generate: (scope, decl) => {
3584
+ const out = [];
3585
+
3586
+ for (let i = 0; i < decl.arguments.length; i++) {
3587
+ out.push(
3588
+ ...generate(scope, decl.arguments[i]),
3589
+ Opcodes.i32_to_u,
3590
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3591
+ );
3592
+ }
3593
+
3594
+ out.push(Opcodes.i32_from_u);
3595
+
3596
+ return out;
3597
+ },
3598
+ type: TYPES.boolean,
3599
+ notConstr: true
3600
+ },
3601
+
3602
+ Boolean: {
3603
+ generate: (scope, decl) => {
3604
+ // todo: boolean object when used as constructor
3605
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3606
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3607
+ },
3608
+ type: TYPES.boolean,
3609
+ length: 1
3610
+ },
3611
+
3612
+ __Math_max: {
3613
+ generate: (scope, decl) => {
3614
+ const out = [
3615
+ ...number(-Infinity)
3616
+ ];
3048
3617
 
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);
3618
+ for (let i = 0; i < decl.arguments.length; i++) {
3619
+ out.push(
3620
+ ...generate(scope, decl.arguments[i]),
3621
+ [ Opcodes.f64_max ]
3622
+ );
3623
+ }
3062
3624
 
3063
- // return _.apply(this, arguments);
3064
- // };
3625
+ return out;
3626
+ },
3627
+ type: TYPES.number,
3628
+ notConstr: true,
3629
+ length: 2
3630
+ },
3631
+
3632
+ __Math_min: {
3633
+ generate: (scope, decl) => {
3634
+ const out = [
3635
+ ...number(Infinity)
3636
+ ];
3637
+
3638
+ for (let i = 0; i < decl.arguments.length; i++) {
3639
+ out.push(
3640
+ ...generate(scope, decl.arguments[i]),
3641
+ [ Opcodes.f64_min ]
3642
+ );
3643
+ }
3644
+
3645
+ return out;
3646
+ },
3647
+ type: TYPES.number,
3648
+ notConstr: true,
3649
+ length: 2
3650
+ },
3651
+
3652
+ __console_log: {
3653
+ generate: (scope, decl) => {
3654
+ const out = [];
3655
+
3656
+ for (let i = 0; i < decl.arguments.length; i++) {
3657
+ out.push(
3658
+ ...generateCall(scope, {
3659
+ callee: {
3660
+ type: 'Identifier',
3661
+ name: '__Porffor_print'
3662
+ },
3663
+ arguments: [ decl.arguments[i] ]
3664
+ }),
3665
+
3666
+ // print space
3667
+ ...(i !== decl.arguments.length - 1 ? [
3668
+ ...number(32),
3669
+ [ Opcodes.call, importedFuncs.printChar ]
3670
+ ] : [])
3671
+ );
3672
+ }
3673
+
3674
+ // print newline
3675
+ out.push(
3676
+ ...number(10),
3677
+ [ Opcodes.call, importedFuncs.printChar ]
3678
+ );
3679
+
3680
+ out.push(...number(UNDEFINED));
3681
+
3682
+ return out;
3683
+ },
3684
+ type: TYPES.undefined,
3685
+ notConstr: true,
3686
+ length: 0
3687
+ }
3688
+ };
3065
3689
 
3066
3690
  export default program => {
3067
3691
  globals = {};
@@ -3071,20 +3695,23 @@ export default program => {
3071
3695
  funcs = [];
3072
3696
  funcIndex = {};
3073
3697
  depth = [];
3074
- arrays = new Map();
3075
3698
  pages = new Map();
3076
3699
  data = [];
3077
3700
  currentFuncIndex = importedFuncs.length;
3078
3701
 
3079
3702
  globalThis.valtype = 'f64';
3080
3703
 
3081
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3704
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3082
3705
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3083
3706
 
3084
3707
  globalThis.valtypeBinary = Valtype[valtype];
3085
3708
 
3086
3709
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3087
3710
 
3711
+ globalThis.pageSize = PageSize;
3712
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3713
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3714
+
3088
3715
  // set generic opcodes for current valtype
3089
3716
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3090
3717
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3093,10 +3720,10 @@ export default program => {
3093
3720
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3094
3721
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3095
3722
 
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];
3723
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3724
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3725
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3726
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3100
3727
 
3101
3728
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3102
3729
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3109,10 +3736,6 @@ export default program => {
3109
3736
 
3110
3737
  program.id = { name: 'main' };
3111
3738
 
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
3739
  const scope = {
3117
3740
  locals: {},
3118
3741
  localInd: 0
@@ -3123,7 +3746,7 @@ export default program => {
3123
3746
  body: program.body
3124
3747
  };
3125
3748
 
3126
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3749
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3127
3750
 
3128
3751
  generateFunc(scope, program);
3129
3752
 
@@ -3140,7 +3763,11 @@ export default program => {
3140
3763
  }
3141
3764
 
3142
3765
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3143
- main.returns = [];
3766
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3767
+ main.wasm.splice(main.wasm.length - 1, 1);
3768
+ } else {
3769
+ main.returns = [];
3770
+ }
3144
3771
  }
3145
3772
 
3146
3773
  if (lastInst[0] === Opcodes.call) {