porffor 0.2.0-30ecc4a → 0.2.0-371da1e

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 (51) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +121 -83
  3. package/asur/README.md +2 -0
  4. package/asur/index.js +1262 -0
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +317 -72
  7. package/compiler/{sections.js → assemble.js} +63 -15
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +151 -0
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/date.ts +9 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +42 -0
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +468 -264
  21. package/compiler/{codeGen.js → codegen.js} +920 -372
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +722 -0
  25. package/compiler/index.js +36 -34
  26. package/compiler/log.js +6 -3
  27. package/compiler/opt.js +50 -36
  28. package/compiler/parse.js +35 -27
  29. package/compiler/precompile.js +123 -0
  30. package/compiler/prefs.js +26 -0
  31. package/compiler/prototype.js +13 -28
  32. package/compiler/types.js +37 -0
  33. package/compiler/wasmSpec.js +27 -8
  34. package/compiler/wrap.js +49 -44
  35. package/package.json +9 -5
  36. package/porf +4 -0
  37. package/rhemyn/compile.js +5 -3
  38. package/rhemyn/parse.js +323 -320
  39. package/rhemyn/test/parse.js +58 -58
  40. package/runner/compare.js +34 -34
  41. package/runner/debug.js +122 -0
  42. package/runner/index.js +49 -10
  43. package/runner/profiler.js +102 -0
  44. package/runner/repl.js +40 -7
  45. package/runner/sizes.js +37 -37
  46. package/compiler/builtins/base64.js +0 -92
  47. package/runner/info.js +0 -89
  48. package/runner/profile.js +0 -46
  49. package/runner/results.json +0 -1
  50. package/runner/transform.js +0 -15
  51. package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,35 +25,37 @@ const debug = str => {
23
25
  const logChar = n => {
24
26
  code.push(...number(n));
25
27
 
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
28
29
  };
29
30
 
30
31
  for (let i = 0; i < str.length; i++) {
31
32
  logChar(str.charCodeAt(i));
32
33
  }
33
34
 
34
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
35
36
 
36
37
  return code;
37
38
  };
38
39
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
45
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
46
50
 
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
53
53
 
54
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
55
59
  };
56
60
 
57
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
108
  return generateUnary(scope, decl);
105
109
 
106
110
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
112
 
109
113
  case 'IfStatement':
110
114
  return generateIf(scope, decl);
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
119
  case 'WhileStatement':
116
120
  return generateWhile(scope, decl);
117
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
118
125
  case 'ForOfStatement':
119
126
  return generateForOf(scope, decl);
120
127
 
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
131
  case 'ContinueStatement':
125
132
  return generateContinue(scope, decl);
126
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
127
137
  case 'EmptyStatement':
128
138
  return generateEmpty(scope, decl);
129
139
 
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
147
  return generateTry(scope, decl);
138
148
 
139
149
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
141
151
  return [];
142
152
 
143
153
  case 'ArrayExpression':
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
151
161
  const funcsBefore = funcs.length;
152
162
  generate(scope, decl.declaration);
153
163
 
154
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
155
171
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
158
174
 
159
175
  return [];
160
176
 
161
177
  case 'TaggedTemplateExpression': {
162
178
  const funcs = {
163
- asm: str => {
179
+ __Porffor_wasm: str => {
164
180
  let out = [];
165
181
 
166
182
  for (const line of str.split('\n')) {
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
184
  if (asm[0] === '') continue; // blank
169
185
 
170
186
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
189
  continue;
174
190
  }
175
191
 
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
195
  }
180
196
 
181
197
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
183
199
  // todo: add to store/load offset insts
184
200
  continue;
185
201
  }
@@ -188,7 +204,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
188
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
205
 
190
206
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
192
212
 
193
213
  out.push([ ...inst, ...immediates ]);
194
214
  }
@@ -196,35 +216,53 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
196
216
  return out;
197
217
  },
198
218
 
199
- __internal_print_type: str => {
200
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
201
221
 
202
- return [
203
- ...number(type),
204
- [ Opcodes.call, importedFuncs.print ],
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
205
229
 
206
- // newline
207
- ...number(10),
208
- [ Opcodes.call, importedFuncs.printChar ]
209
- ];
210
- }
211
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
212
236
 
213
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
214
238
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
216
243
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
254
+
255
+ return funcs[func](str);
219
256
  }
220
257
 
221
258
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
262
  return [];
225
263
  }
226
264
 
227
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
228
266
  }
229
267
  };
230
268
 
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
252
290
  return [ undefined, undefined ];
253
291
  };
254
292
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
294
  ...generateThrow(scope, {
257
295
  argument: {
258
296
  type: 'NewExpression',
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
274
312
  const name = mapName(rawName);
275
313
  let local = scope.locals[rawName];
276
314
 
277
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
278
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
280
321
  }
281
322
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
283
324
  // todo: return an actual something
284
325
  return number(1);
285
326
  }
286
327
 
287
- if (local === undefined) {
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
333
+ if (local?.idx === undefined) {
288
334
  // no local var with name
289
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
290
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
335
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
336
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
337
 
292
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
293
339
  }
294
340
 
295
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
296
342
  // return undefined if unknown key in already known var
297
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
298
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
301
347
  if (!parentLookup[1]) return number(UNDEFINED);
302
348
  }
303
349
 
304
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
350
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
305
351
 
306
352
  return [ [ Opcodes.local_get, local.idx ] ];
307
353
  };
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
314
360
  // just bare "return"
315
361
  return [
316
362
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
318
366
  [ Opcodes.return ]
319
367
  ];
320
368
  }
321
369
 
322
370
  return [
323
371
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
325
375
  [ Opcodes.return ]
326
376
  ];
327
377
  };
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
385
  return idx;
336
386
  };
337
387
 
338
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
339
390
 
340
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
392
  const checks = {
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
395
  '??': nullish
345
396
  };
346
397
 
347
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
348
399
 
349
400
  // generic structure for {a} OP {b}
350
401
  // -->
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
403
 
353
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
405
  // (like if we are in an if condition - very common)
355
- const leftIsInt = isIntOp(left[left.length - 1]);
356
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
408
 
358
409
  const canInt = leftIsInt && rightIsInt;
359
410
 
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
421
  ...right,
371
422
  // note type
372
423
  ...rightType,
373
- setLastType(scope),
424
+ ...setLastType(scope),
374
425
  [ Opcodes.else ],
375
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
427
  // note type
377
428
  ...leftType,
378
- setLastType(scope),
429
+ ...setLastType(scope),
379
430
  [ Opcodes.end ],
380
431
  Opcodes.i32_from
381
432
  ];
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
440
  ...right,
390
441
  // note type
391
442
  ...rightType,
392
- setLastType(scope),
443
+ ...setLastType(scope),
393
444
  [ Opcodes.else ],
394
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
446
  // note type
396
447
  ...leftType,
397
- setLastType(scope),
448
+ ...setLastType(scope),
398
449
  [ Opcodes.end ]
399
450
  ];
400
451
  };
401
452
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
455
  // todo: convert left and right to strings if not
405
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
462
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
463
  if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
465
 
418
466
  return [
419
467
  // setup right
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
487
 
440
488
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
442
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
491
 
444
492
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
494
  [ Opcodes.i32_mul ],
447
495
  [ Opcodes.i32_add ],
448
496
 
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
499
  ...number(ValtypeSize.i32, Valtype.i32),
452
500
  [ Opcodes.i32_add ],
453
501
 
454
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
455
503
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
505
  [ Opcodes.i32_mul ],
458
506
 
459
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
560
 
513
561
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
515
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
564
 
517
565
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
567
  [ Opcodes.i32_mul ],
520
568
  [ Opcodes.i32_add ],
521
569
 
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
572
  ...number(ValtypeSize.i32, Valtype.i32),
525
573
  [ Opcodes.i32_add ],
526
574
 
527
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
528
576
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
578
  [ Opcodes.i32_mul ],
531
579
 
532
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
584
  ];
537
585
  };
538
586
 
539
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
588
  // todo: this should be rewritten into a func
541
589
  // todo: convert left and right to strings if not
542
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
545
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
596
 
550
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
573
620
 
574
621
  [ Opcodes.local_get, rightPointer ],
575
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
623
 
578
624
  // fast path: check leftLength != rightLength
579
625
  [ Opcodes.i32_ne ],
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
588
634
  ...number(0, Valtype.i32),
589
635
  [ Opcodes.local_set, index ],
590
636
 
591
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
638
  // we do this instead of having to do mul/div each iter for perf™
593
639
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
596
644
  [ Opcodes.local_set, indexEnd ],
597
645
 
598
646
  // iterate over each char and check if eq
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
602
650
  [ Opcodes.local_get, index ],
603
651
  [ Opcodes.local_get, leftPointer ],
604
652
  [ Opcodes.i32_add ],
605
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
606
656
 
607
657
  // fetch right
608
658
  [ Opcodes.local_get, index ],
609
659
  [ Opcodes.local_get, rightPointer ],
610
660
  [ Opcodes.i32_add ],
611
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
612
664
 
613
665
  // not equal, "return" false
614
666
  [ Opcodes.i32_ne ],
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
617
669
  [ Opcodes.br, 2 ],
618
670
  [ Opcodes.end ],
619
671
 
620
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
673
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
675
  [ Opcodes.i32_add ],
624
676
  [ Opcodes.local_tee, index ],
625
677
 
626
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
627
679
  [ Opcodes.local_get, indexEnd ],
628
680
  [ Opcodes.i32_ne ],
629
681
  [ Opcodes.br_if, 0 ],
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
644
696
  };
645
697
 
646
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
700
  ...wasm,
649
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
704
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
707
 
654
708
  const def = [
655
709
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
711
 
658
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
719
 
666
720
  return [
667
721
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
723
 
670
724
  ...typeSwitch(scope, type, {
671
725
  // [TYPES.number]: def,
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
729
  ],
676
730
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
733
 
680
734
  // get length
@@ -686,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
686
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
741
  ],
688
742
  [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
744
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
745
 
692
746
  // get length
@@ -700,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
754
  };
701
755
 
702
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
704
760
  return [
705
761
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
763
 
708
764
  ...typeSwitch(scope, type, {
709
765
  [TYPES._array]: [
@@ -711,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
768
  ],
713
769
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
771
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
772
 
717
773
  // get length
@@ -722,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
722
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
779
  ],
724
780
  [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
783
 
728
784
  // get length
@@ -734,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
790
  ],
735
791
  default: [
736
792
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
794
 
739
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
800
  };
745
801
 
746
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
748
806
  return [
749
807
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
809
 
752
810
  ...typeSwitch(scope, type, {
753
811
  [TYPES.undefined]: [
@@ -756,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
814
  ],
757
815
  [TYPES.object]: [
758
816
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
818
 
761
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
844
  }
787
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
788
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
850
  const strictOp = op === '===' || op === '!==';
790
851
 
791
852
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
793
854
 
794
855
  // if strict (in)equal check types match
795
856
  if (strictOp) {
@@ -834,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
895
  // todo: if equality op and an operand is undefined, return false
835
896
  // todo: niche null hell with 0
836
897
 
837
- // if (leftType === TYPES.string || rightType === TYPES.string) {
838
- // if (op === '+') {
839
- // // string concat (a + b)
840
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
841
- // }
842
-
843
- // // not an equality op, NaN
844
- // if (!eqOp) return finalise(number(NaN));
845
-
846
- // // else leave bool ops
847
- // // todo: convert string to number if string and number/bool
848
- // // todo: string (>|>=|<|<=) string
849
-
850
- // // string comparison
851
- // if (op === '===' || op === '==') {
852
- // return finalise(compareStrings(scope, left, right));
853
- // }
854
-
855
- // if (op === '!==' || op === '!=') {
856
- // return finalise([
857
- // ...compareStrings(scope, left, right),
858
- // [ Opcodes.i32_eqz ]
859
- // ]);
860
- // }
861
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
862
951
 
863
952
  let ops = operatorOpcode[valtype][op];
864
953
 
@@ -868,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
957
  includeBuiltin(scope, builtinName);
869
958
  const idx = funcIndex[builtinName];
870
959
 
871
- return finalise([
960
+ return finalize([
872
961
  ...left,
873
962
  ...right,
874
963
  [ Opcodes.call, idx ]
875
964
  ]);
876
965
  }
877
966
 
878
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
879
968
 
880
969
  if (!Array.isArray(ops)) ops = [ ops ];
881
970
  ops = [ ops ];
882
971
 
883
972
  let tmpLeft, tmpRight;
884
973
  // if equal op, check if strings for compareStrings
885
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
- const knownLeft = knownType(scope, leftType);
887
- const knownRight = knownType(scope, rightType);
888
-
889
- // todo: intelligent partial skip later
890
- // if neither known are string, stop this madness
891
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
892
- return;
893
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
894
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
979
  tmpLeft = localTmp(scope, '__tmpop_left');
896
980
  tmpRight = localTmp(scope, '__tmpop_right');
897
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
898
1023
  ops.unshift(...stringOnly([
899
1024
  // if left is string
900
1025
  ...leftType,
@@ -906,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
1031
  ...number(TYPES.string, Valtype.i32),
907
1032
  [ Opcodes.i32_eq ],
908
1033
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
911
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
912
1041
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
1042
+ // if left is bytestring
915
1043
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
918
1046
 
919
- // if right is not string
1047
+ // if right is bytestring
920
1048
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
923
1051
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
926
1054
  [ Opcodes.if, Blocktype.void ],
927
- ...number(0, Valtype.i32),
928
- [ Opcodes.br, 1 ],
929
- [ Opcodes.end ],
930
-
931
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
932
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1057
  [ Opcodes.br, 1 ],
935
1058
  [ Opcodes.end ],
@@ -941,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1066
  // }
944
- })();
1067
+ }
945
1068
 
946
- return finalise([
1069
+ return finalize([
947
1070
  ...left,
948
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1072
  ...right,
@@ -960,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1083
  return out;
961
1084
  };
962
1085
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
964
1102
  const existing = funcs.find(x => x.name === name);
965
1103
  if (existing) return existing;
966
1104
 
@@ -972,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1111
  }
974
1112
 
975
- if (typeof wasm === 'function') {
976
- const scope = {
977
- name,
978
- params,
979
- locals,
980
- returns,
981
- localInd: allLocals.length,
982
- };
983
-
984
- wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
985
1117
  }
986
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
987
1121
  let baseGlobalIdx, i = 0;
988
1122
  for (const type of globalTypes) {
989
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1006,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1006
1140
  params,
1007
1141
  locals,
1008
1142
  returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1010
1144
  wasm,
1011
1145
  internal: true,
1012
1146
  index: currentFuncIndex++
@@ -1029,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1029
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1164
  };
1031
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1032
1167
  // T = JS type, V = value/pointer
1033
1168
  // 0bTTT
1034
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1052,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1052
1187
  // 4: internal type
1053
1188
  // 5: pointer
1054
1189
 
1055
- const TYPES = {
1056
- number: 0x00,
1057
- boolean: 0x01,
1058
- string: 0x02,
1059
- undefined: 0x03,
1060
- object: 0x04,
1061
- function: 0x05,
1062
- symbol: 0x06,
1063
- bigint: 0x07,
1064
-
1065
- // these are not "typeof" types but tracked internally
1066
- _array: 0x10,
1067
- _regexp: 0x11,
1068
- _bytestring: 0x12
1069
- };
1070
-
1071
- const TYPE_NAMES = {
1072
- [TYPES.number]: 'Number',
1073
- [TYPES.boolean]: 'Boolean',
1074
- [TYPES.string]: 'String',
1075
- [TYPES.undefined]: 'undefined',
1076
- [TYPES.object]: 'Object',
1077
- [TYPES.function]: 'Function',
1078
- [TYPES.symbol]: 'Symbol',
1079
- [TYPES.bigint]: 'BigInt',
1080
-
1081
- [TYPES._array]: 'Array',
1082
- [TYPES._regexp]: 'RegExp',
1083
- [TYPES._bytestring]: 'ByteString'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1084
1195
  };
1085
1196
 
1086
1197
  const getType = (scope, _name) => {
1087
1198
  const name = mapName(_name);
1088
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1207
 
1092
1208
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1211
 
1096
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1097
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1213
 
1099
1214
  return number(type, Valtype.i32);
1100
1215
  };
@@ -1117,15 +1232,16 @@ const setType = (scope, _name, type) => {
1117
1232
  ];
1118
1233
 
1119
1234
  // throw new Error('could not find var');
1235
+ return [];
1120
1236
  };
1121
1237
 
1122
1238
  const getLastType = scope => {
1123
1239
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1241
  };
1126
1242
 
1127
1243
  const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1129
1245
  };
1130
1246
 
1131
1247
  const getNodeType = (scope, node) => {
@@ -1150,13 +1266,25 @@ const getNodeType = (scope, node) => {
1150
1266
  const name = node.callee.name;
1151
1267
  if (!name) {
1152
1268
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1270
 
1155
1271
  // presume
1156
1272
  // todo: warn here?
1157
1273
  return TYPES.number;
1158
1274
  }
1159
1275
 
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1160
1288
  const func = funcs.find(x => x.name === name);
1161
1289
 
1162
1290
  if (func) {
@@ -1164,7 +1292,7 @@ const getNodeType = (scope, node) => {
1164
1292
  if (func.returnType) return func.returnType;
1165
1293
  }
1166
1294
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1297
 
1170
1298
  // check if this is a prototype function
@@ -1175,11 +1303,16 @@ const getNodeType = (scope, node) => {
1175
1303
  const spl = name.slice(2).split('_');
1176
1304
 
1177
1305
  const func = spl[spl.length - 1];
1178
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1306
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1179
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1308
  }
1181
1309
 
1182
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1183
1316
 
1184
1317
  // presume
1185
1318
  // todo: warn here?
@@ -1227,6 +1360,15 @@ const getNodeType = (scope, node) => {
1227
1360
 
1228
1361
  if (node.type === 'BinaryExpression') {
1229
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1230
1372
  return TYPES.number;
1231
1373
 
1232
1374
  // todo: string concat types
@@ -1251,7 +1393,7 @@ const getNodeType = (scope, node) => {
1251
1393
  if (node.operator === '!') return TYPES.boolean;
1252
1394
  if (node.operator === 'void') return TYPES.undefined;
1253
1395
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1255
1397
 
1256
1398
  return TYPES.number;
1257
1399
  }
@@ -1260,13 +1402,23 @@ const getNodeType = (scope, node) => {
1260
1402
  // hack: if something.length, number type
1261
1403
  if (node.property.name === 'length') return TYPES.number;
1262
1404
 
1263
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1405
+ // ts hack
1406
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1408
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1264
1411
 
1265
1412
  // presume
1266
1413
  return TYPES.number;
1267
1414
  }
1268
1415
 
1269
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1270
1422
 
1271
1423
  // presume
1272
1424
  // todo: warn here?
@@ -1279,28 +1431,11 @@ const getNodeType = (scope, node) => {
1279
1431
  return ret;
1280
1432
  };
1281
1433
 
1282
- const toString = (scope, wasm, type) => {
1283
- const tmp = localTmp(scope, '#tostring_tmp');
1284
- return [
1285
- ...wasm,
1286
- [ Opcodes.local_set, tmp ],
1287
-
1288
- ...typeSwitch(scope, type, {
1289
- [TYPES.string]: [
1290
- [ Opcodes.local_get, tmp ]
1291
- ],
1292
- [TYPES.undefined]: [
1293
- // [ Opcodes.]
1294
- ]
1295
- })
1296
- ]
1297
- };
1298
-
1299
1434
  const generateLiteral = (scope, decl, global, name) => {
1300
1435
  if (decl.value === null) return number(NULL);
1301
1436
 
1437
+ // hack: just return 1 for regex literals
1302
1438
  if (decl.regex) {
1303
- scope.regex[name] = decl.regex;
1304
1439
  return number(1);
1305
1440
  }
1306
1441
 
@@ -1316,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1316
1451
  return makeString(scope, decl.value, global, name);
1317
1452
 
1318
1453
  default:
1319
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1320
1455
  }
1321
1456
  };
1322
1457
 
@@ -1325,6 +1460,8 @@ const countLeftover = wasm => {
1325
1460
 
1326
1461
  for (let i = 0; i < wasm.length; i++) {
1327
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1328
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1329
1466
  if (inst[0] === Opcodes.if) count--;
1330
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1333,7 +1470,7 @@ const countLeftover = wasm => {
1333
1470
  if (inst[0] === Opcodes.end) depth--;
1334
1471
 
1335
1472
  if (depth === 0)
1336
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1337
1474
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1338
1475
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1339
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
@@ -1341,10 +1478,17 @@ const countLeftover = wasm => {
1341
1478
  else if (inst[0] === Opcodes.return) count = 0;
1342
1479
  else if (inst[0] === Opcodes.call) {
1343
1480
  let func = funcs.find(x => x.index === inst[1]);
1344
- if (func) {
1345
- count -= func.params.length;
1346
- } else count--;
1347
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1348
1492
  } else count--;
1349
1493
 
1350
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1436,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1436
1580
  name = func.name;
1437
1581
  }
1438
1582
 
1439
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1440
1584
  // literal eval hack
1441
- const code = decl.arguments[0].value;
1442
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1443
1598
 
1444
1599
  const out = generate(scope, {
1445
1600
  type: 'BlockStatement',
@@ -1453,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1453
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1454
1609
  out.push(
1455
1610
  ...getNodeType(scope, finalStatement),
1456
- setLastType(scope)
1611
+ ...setLastType(scope)
1457
1612
  );
1458
1613
  } else if (countLeftover(out) === 0) {
1459
1614
  out.push(...number(UNDEFINED));
1460
1615
  out.push(
1461
1616
  ...number(TYPES.undefined, Valtype.i32),
1462
- setLastType(scope)
1617
+ ...setLastType(scope)
1463
1618
  );
1464
1619
  }
1465
1620
 
@@ -1481,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1481
1636
 
1482
1637
  target = { ...decl.callee };
1483
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1484
1642
  }
1485
1643
 
1486
1644
  // literal.func()
1487
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1488
1646
  // megahack for /regex/.func()
1489
- if (decl.callee.object.regex) {
1490
- const funcName = decl.callee.property.name;
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1491
1649
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1492
1650
 
1493
1651
  funcIndex[func.name] = func.index;
@@ -1503,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1503
1661
  Opcodes.i32_from_u,
1504
1662
 
1505
1663
  ...number(TYPES.boolean, Valtype.i32),
1506
- setLastType(scope)
1664
+ ...setLastType(scope)
1507
1665
  ];
1508
1666
  }
1509
1667
 
@@ -1528,13 +1686,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1528
1686
  // }
1529
1687
 
1530
1688
  if (protoName) {
1689
+ const protoBC = {};
1690
+
1691
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1692
+
1693
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1694
+ for (const x of builtinProtoCands) {
1695
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1696
+ if (type == null) continue;
1697
+
1698
+ protoBC[type] = generateCall(scope, {
1699
+ callee: {
1700
+ name: x
1701
+ },
1702
+ arguments: [ target, ...decl.arguments ],
1703
+ _protoInternalCall: true
1704
+ });
1705
+ }
1706
+ }
1707
+
1531
1708
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1532
- const f = prototypeFuncs[x][protoName];
1533
- if (f) acc[x] = f;
1709
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1534
1710
  return acc;
1535
1711
  }, {});
1536
1712
 
1537
- // no prototype function candidates, ignore
1538
1713
  if (Object.keys(protoCands).length > 0) {
1539
1714
  // use local for cached i32 length as commonly used
1540
1715
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1552,7 +1727,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1552
1727
 
1553
1728
  let allOptUnused = true;
1554
1729
  let lengthI32CacheUsed = false;
1555
- const protoBC = {};
1556
1730
  for (const x in protoCands) {
1557
1731
  const protoFunc = protoCands[x];
1558
1732
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1560,7 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1560
1734
  ...RTArrayUtil.getLength(getPointer),
1561
1735
 
1562
1736
  ...number(TYPES.number, Valtype.i32),
1563
- setLastType(scope)
1737
+ ...setLastType(scope)
1564
1738
  ];
1565
1739
  continue;
1566
1740
  }
@@ -1597,7 +1771,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1597
1771
  ...protoOut,
1598
1772
 
1599
1773
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1600
- setLastType(scope),
1774
+ ...setLastType(scope),
1601
1775
  [ Opcodes.end ]
1602
1776
  ];
1603
1777
  }
@@ -1623,10 +1797,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1623
1797
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1624
1798
  ];
1625
1799
  }
1800
+
1801
+ if (Object.keys(protoBC).length > 0) {
1802
+ return typeSwitch(scope, getNodeType(scope, target), {
1803
+ ...protoBC,
1804
+
1805
+ // TODO: error better
1806
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1807
+ }, valtypeBinary);
1808
+ }
1626
1809
  }
1627
1810
 
1628
1811
  // TODO: only allows callee as literal
1629
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1812
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1630
1813
 
1631
1814
  let idx = funcIndex[name] ?? importedFuncs[name];
1632
1815
  if (idx === undefined && builtinFuncs[name]) {
@@ -1659,16 +1842,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1659
1842
  idx = -1;
1660
1843
  }
1661
1844
 
1845
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1846
+ const wasmOps = {
1847
+ // pointer, align, offset
1848
+ i32_load: { imms: 2, args: 1, returns: 1 },
1849
+ // pointer, value, align, offset
1850
+ i32_store: { imms: 2, args: 2, returns: 0 },
1851
+ // pointer, align, offset
1852
+ i32_load8_u: { imms: 2, args: 1, returns: 1 },
1853
+ // pointer, value, align, offset
1854
+ i32_store8: { imms: 2, args: 2, returns: 0 },
1855
+ // pointer, align, offset
1856
+ i32_load16_u: { imms: 2, args: 1, returns: 1 },
1857
+ // pointer, value, align, offset
1858
+ i32_store16: { imms: 2, args: 2, returns: 0 },
1859
+
1860
+ // pointer, align, offset
1861
+ f64_load: { imms: 2, args: 1, returns: 1 },
1862
+ // pointer, value, align, offset
1863
+ f64_store: { imms: 2, args: 2, returns: 0 },
1864
+
1865
+ // value
1866
+ i32_const: { imms: 1, args: 0, returns: 1 },
1867
+
1868
+ // a, b
1869
+ i32_or: { imms: 0, args: 2, returns: 1 },
1870
+ };
1871
+
1872
+ const opName = name.slice('__Porffor_wasm_'.length);
1873
+
1874
+ if (wasmOps[opName]) {
1875
+ const op = wasmOps[opName];
1876
+
1877
+ const argOut = [];
1878
+ for (let i = 0; i < op.args; i++) argOut.push(
1879
+ ...generate(scope, decl.arguments[i]),
1880
+ Opcodes.i32_to
1881
+ );
1882
+
1883
+ // literals only
1884
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1885
+
1886
+ return [
1887
+ ...argOut,
1888
+ [ Opcodes[opName], ...imms ],
1889
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1890
+ ];
1891
+ }
1892
+ }
1893
+
1662
1894
  if (idx === undefined) {
1663
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1664
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1895
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1896
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1665
1897
  }
1666
1898
 
1667
1899
  const func = funcs.find(x => x.index === idx);
1668
1900
 
1669
1901
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1670
1902
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1671
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1903
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1672
1904
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1673
1905
 
1674
1906
  let args = decl.arguments;
@@ -1685,14 +1917,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1685
1917
  if (func && func.throws) scope.throws = true;
1686
1918
 
1687
1919
  let out = [];
1688
- for (const arg of args) {
1920
+ for (let i = 0; i < args.length; i++) {
1921
+ const arg = args[i];
1689
1922
  out = out.concat(generate(scope, arg));
1923
+
1924
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1925
+ out.push(Opcodes.i32_to);
1926
+ }
1927
+
1928
+ if (importedFuncs[name] && name.startsWith('profile')) {
1929
+ out.push(Opcodes.i32_to);
1930
+ }
1931
+
1690
1932
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1691
1933
  }
1692
1934
 
1693
1935
  out.push([ Opcodes.call, idx ]);
1694
1936
 
1695
- if (!typedReturn) {
1937
+ if (!typedReturns) {
1696
1938
  // let type;
1697
1939
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1698
1940
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1702,7 +1944,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1702
1944
  // ...number(type, Valtype.i32),
1703
1945
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1704
1946
  // );
1705
- } else out.push(setLastType(scope));
1947
+ } else out.push(...setLastType(scope));
1948
+
1949
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1950
+ out.push(Opcodes.i32_from);
1951
+ }
1706
1952
 
1707
1953
  return out;
1708
1954
  };
@@ -1710,8 +1956,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1710
1956
  const generateNew = (scope, decl, _global, _name) => {
1711
1957
  // hack: basically treat this as a normal call for builtins for now
1712
1958
  const name = mapName(decl.callee.name);
1959
+
1713
1960
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1714
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1961
+
1962
+ if (builtinFuncs[name + '$constructor']) {
1963
+ // custom ...$constructor override builtin func
1964
+ return generateCall(scope, {
1965
+ ...decl,
1966
+ callee: {
1967
+ type: 'Identifier',
1968
+ name: name + '$constructor'
1969
+ }
1970
+ }, _global, _name);
1971
+ }
1972
+
1973
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1715
1974
 
1716
1975
  return generateCall(scope, decl, _global, _name);
1717
1976
  };
@@ -1828,14 +2087,14 @@ const brTable = (input, bc, returns) => {
1828
2087
  };
1829
2088
 
1830
2089
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2090
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1832
2091
 
1833
2092
  const known = knownType(scope, type);
1834
2093
  if (known != null) {
1835
2094
  return bc[known] ?? bc.default;
1836
2095
  }
1837
2096
 
1838
- if (process.argv.includes('-typeswitch-use-brtable'))
2097
+ if (Prefs.typeswitchUseBrtable)
1839
2098
  return brTable(type, bc, returns);
1840
2099
 
1841
2100
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1845,8 +2104,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1845
2104
  [ Opcodes.block, returns ]
1846
2105
  ];
1847
2106
 
1848
- // todo: use br_table?
1849
-
1850
2107
  for (const x in bc) {
1851
2108
  if (x === 'default') continue;
1852
2109
 
@@ -1870,7 +2127,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1870
2127
  return out;
1871
2128
  };
1872
2129
 
1873
- const allocVar = (scope, name, global = false) => {
2130
+ const allocVar = (scope, name, global = false, type = true) => {
1874
2131
  const target = global ? globals : scope.locals;
1875
2132
 
1876
2133
  // already declared
@@ -1884,8 +2141,10 @@ const allocVar = (scope, name, global = false) => {
1884
2141
  let idx = global ? globalInd++ : scope.localInd++;
1885
2142
  target[name] = { idx, type: valtypeBinary };
1886
2143
 
1887
- let typeIdx = global ? globalInd++ : scope.localInd++;
1888
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2144
+ if (type) {
2145
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2146
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2147
+ }
1889
2148
 
1890
2149
  return idx;
1891
2150
  };
@@ -1900,11 +2159,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1900
2159
  };
1901
2160
 
1902
2161
  const typeAnnoToPorfType = x => {
1903
- if (TYPES[x]) return TYPES[x];
1904
- if (TYPES['_' + x]) return TYPES['_' + x];
2162
+ if (!x) return null;
2163
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2164
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1905
2165
 
1906
2166
  switch (x) {
1907
2167
  case 'i32':
2168
+ case 'i64':
2169
+ case 'f64':
1908
2170
  return TYPES.number;
1909
2171
  }
1910
2172
 
@@ -1915,7 +2177,7 @@ const extractTypeAnnotation = decl => {
1915
2177
  let a = decl;
1916
2178
  while (a.typeAnnotation) a = a.typeAnnotation;
1917
2179
 
1918
- let type, elementType;
2180
+ let type = null, elementType = null;
1919
2181
  if (a.typeName) {
1920
2182
  type = a.typeName.name;
1921
2183
  } else if (a.type.endsWith('Keyword')) {
@@ -1928,6 +2190,8 @@ const extractTypeAnnotation = decl => {
1928
2190
  const typeName = type;
1929
2191
  type = typeAnnoToPorfType(type);
1930
2192
 
2193
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2194
+
1931
2195
  // if (decl.name) console.log(decl.name, { type, elementType });
1932
2196
 
1933
2197
  return { type, typeName, elementType };
@@ -1944,6 +2208,8 @@ const generateVar = (scope, decl) => {
1944
2208
  for (const x of decl.declarations) {
1945
2209
  const name = mapName(x.id.name);
1946
2210
 
2211
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2212
+
1947
2213
  if (x.init && isFuncType(x.init.type)) {
1948
2214
  // hack for let a = function () { ... }
1949
2215
  x.init.id = { name };
@@ -1959,9 +2225,10 @@ const generateVar = (scope, decl) => {
1959
2225
  continue; // always ignore
1960
2226
  }
1961
2227
 
1962
- let idx = allocVar(scope, name, global);
2228
+ const typed = typedInput && x.id.typeAnnotation;
2229
+ let idx = allocVar(scope, name, global, !typed);
1963
2230
 
1964
- if (typedInput && x.id.typeAnnotation) {
2231
+ if (typed) {
1965
2232
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1966
2233
  }
1967
2234
 
@@ -1979,7 +2246,8 @@ const generateVar = (scope, decl) => {
1979
2246
  return out;
1980
2247
  };
1981
2248
 
1982
- const generateAssign = (scope, decl) => {
2249
+ // todo: optimize this func for valueUnused
2250
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1983
2251
  const { type, name } = decl.left;
1984
2252
 
1985
2253
  if (type === 'ObjectPattern') {
@@ -1997,9 +2265,9 @@ const generateAssign = (scope, decl) => {
1997
2265
  // hack: .length setter
1998
2266
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1999
2267
  const name = decl.left.object.name;
2000
- const pointer = arrays.get(name);
2268
+ const pointer = scope.arrays?.get(name);
2001
2269
 
2002
- const aotPointer = pointer != null;
2270
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2003
2271
 
2004
2272
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2005
2273
 
@@ -2024,9 +2292,9 @@ const generateAssign = (scope, decl) => {
2024
2292
  // arr[i]
2025
2293
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2026
2294
  const name = decl.left.object.name;
2027
- const pointer = arrays.get(name);
2295
+ const pointer = scope.arrays?.get(name);
2028
2296
 
2029
- const aotPointer = pointer != null;
2297
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2030
2298
 
2031
2299
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2032
2300
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2082,6 +2350,8 @@ const generateAssign = (scope, decl) => {
2082
2350
  ];
2083
2351
  }
2084
2352
 
2353
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2354
+
2085
2355
  const [ local, isGlobal ] = lookupName(scope, name);
2086
2356
 
2087
2357
  if (local === undefined) {
@@ -2128,9 +2398,7 @@ const generateAssign = (scope, decl) => {
2128
2398
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2129
2399
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2130
2400
 
2131
- getLastType(scope),
2132
- // hack: type is idx+1
2133
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2401
+ ...setType(scope, name, getLastType(scope))
2134
2402
  ];
2135
2403
  }
2136
2404
 
@@ -2141,9 +2409,7 @@ const generateAssign = (scope, decl) => {
2141
2409
 
2142
2410
  // todo: string concat types
2143
2411
 
2144
- // hack: type is idx+1
2145
- ...number(TYPES.number, Valtype.i32),
2146
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2412
+ ...setType(scope, name, TYPES.number)
2147
2413
  ];
2148
2414
  };
2149
2415
 
@@ -2189,7 +2455,7 @@ const generateUnary = (scope, decl) => {
2189
2455
  return out;
2190
2456
  }
2191
2457
 
2192
- case 'delete':
2458
+ case 'delete': {
2193
2459
  let toReturn = true, toGenerate = true;
2194
2460
 
2195
2461
  if (decl.argument.type === 'Identifier') {
@@ -2211,9 +2477,26 @@ const generateUnary = (scope, decl) => {
2211
2477
 
2212
2478
  out.push(...number(toReturn ? 1 : 0));
2213
2479
  return out;
2480
+ }
2481
+
2482
+ case 'typeof': {
2483
+ let overrideType, toGenerate = true;
2484
+
2485
+ if (decl.argument.type === 'Identifier') {
2486
+ const out = generateIdent(scope, decl.argument);
2214
2487
 
2215
- case 'typeof':
2216
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2488
+ // if ReferenceError (undeclared var), ignore and return undefined
2489
+ if (out[1]) {
2490
+ // does not exist (2 ops from throw)
2491
+ overrideType = number(TYPES.undefined, Valtype.i32);
2492
+ toGenerate = false;
2493
+ }
2494
+ }
2495
+
2496
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2497
+ disposeLeftover(out);
2498
+
2499
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2217
2500
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2218
2501
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2219
2502
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2224,27 +2507,30 @@ const generateUnary = (scope, decl) => {
2224
2507
 
2225
2508
  // object and internal types
2226
2509
  default: makeString(scope, 'object', false, '#typeof_result'),
2227
- });
2510
+ }));
2511
+
2512
+ return out;
2513
+ }
2228
2514
 
2229
2515
  default:
2230
- return todo(`unary operator ${decl.operator} not implemented yet`);
2516
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2231
2517
  }
2232
2518
  };
2233
2519
 
2234
- const generateUpdate = (scope, decl) => {
2520
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2235
2521
  const { name } = decl.argument;
2236
2522
 
2237
2523
  const [ local, isGlobal ] = lookupName(scope, name);
2238
2524
 
2239
2525
  if (local === undefined) {
2240
- return todo(`update expression with undefined variable`);
2526
+ return todo(scope, `update expression with undefined variable`, true);
2241
2527
  }
2242
2528
 
2243
2529
  const idx = local.idx;
2244
2530
  const out = [];
2245
2531
 
2246
2532
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2247
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2533
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2248
2534
 
2249
2535
  switch (decl.operator) {
2250
2536
  case '++':
@@ -2257,7 +2543,7 @@ const generateUpdate = (scope, decl) => {
2257
2543
  }
2258
2544
 
2259
2545
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2260
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2546
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2261
2547
 
2262
2548
  return out;
2263
2549
  };
@@ -2297,7 +2583,7 @@ const generateConditional = (scope, decl) => {
2297
2583
  // note type
2298
2584
  out.push(
2299
2585
  ...getNodeType(scope, decl.consequent),
2300
- setLastType(scope)
2586
+ ...setLastType(scope)
2301
2587
  );
2302
2588
 
2303
2589
  out.push([ Opcodes.else ]);
@@ -2306,7 +2592,7 @@ const generateConditional = (scope, decl) => {
2306
2592
  // note type
2307
2593
  out.push(
2308
2594
  ...getNodeType(scope, decl.alternate),
2309
- setLastType(scope)
2595
+ ...setLastType(scope)
2310
2596
  );
2311
2597
 
2312
2598
  out.push([ Opcodes.end ]);
@@ -2320,15 +2606,17 @@ const generateFor = (scope, decl) => {
2320
2606
  const out = [];
2321
2607
 
2322
2608
  if (decl.init) {
2323
- out.push(...generate(scope, decl.init));
2609
+ out.push(...generate(scope, decl.init, false, undefined, true));
2324
2610
  disposeLeftover(out);
2325
2611
  }
2326
2612
 
2327
2613
  out.push([ Opcodes.loop, Blocktype.void ]);
2328
2614
  depth.push('for');
2329
2615
 
2330
- out.push(...generate(scope, decl.test));
2331
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2616
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2617
+ else out.push(...number(1, Valtype.i32));
2618
+
2619
+ out.push([ Opcodes.if, Blocktype.void ]);
2332
2620
  depth.push('if');
2333
2621
 
2334
2622
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2336,8 +2624,7 @@ const generateFor = (scope, decl) => {
2336
2624
  out.push(...generate(scope, decl.body));
2337
2625
  out.push([ Opcodes.end ]);
2338
2626
 
2339
- out.push(...generate(scope, decl.update));
2340
- depth.pop();
2627
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2341
2628
 
2342
2629
  out.push([ Opcodes.br, 1 ]);
2343
2630
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2365,6 +2652,36 @@ const generateWhile = (scope, decl) => {
2365
2652
  return out;
2366
2653
  };
2367
2654
 
2655
+ const generateDoWhile = (scope, decl) => {
2656
+ const out = [];
2657
+
2658
+ out.push([ Opcodes.loop, Blocktype.void ]);
2659
+ depth.push('dowhile');
2660
+
2661
+ // block for break (includes all)
2662
+ out.push([ Opcodes.block, Blocktype.void ]);
2663
+ depth.push('block');
2664
+
2665
+ // block for continue
2666
+ // includes body but not test+loop so we can exit body at anytime
2667
+ // and still test+loop after
2668
+ out.push([ Opcodes.block, Blocktype.void ]);
2669
+ depth.push('block');
2670
+
2671
+ out.push(...generate(scope, decl.body));
2672
+
2673
+ out.push([ Opcodes.end ]);
2674
+ depth.pop();
2675
+
2676
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2677
+ out.push([ Opcodes.br_if, 1 ]);
2678
+
2679
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2680
+ depth.pop(); depth.pop();
2681
+
2682
+ return out;
2683
+ };
2684
+
2368
2685
  const generateForOf = (scope, decl) => {
2369
2686
  const out = [];
2370
2687
 
@@ -2394,8 +2711,17 @@ const generateForOf = (scope, decl) => {
2394
2711
  // setup local for left
2395
2712
  generate(scope, decl.left);
2396
2713
 
2397
- const leftName = decl.left.declarations[0].id.name;
2714
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2715
+ if (!leftName && decl.left.name) {
2716
+ leftName = decl.left.name;
2717
+
2718
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2719
+ }
2720
+
2721
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2722
+
2398
2723
  const [ local, isGlobal ] = lookupName(scope, leftName);
2724
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2399
2725
 
2400
2726
  depth.push('block');
2401
2727
  depth.push('block');
@@ -2404,6 +2730,7 @@ const generateForOf = (scope, decl) => {
2404
2730
  // hack: this is naughty and will break things!
2405
2731
  let newOut = number(0, Valtype.f64), newPointer = -1;
2406
2732
  if (pages.hasAnyString) {
2733
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2407
2734
  0, [ newOut, newPointer ] = makeArray(scope, {
2408
2735
  rawElements: new Array(1)
2409
2736
  }, isGlobal, leftName, true, 'i16');
@@ -2495,6 +2822,56 @@ const generateForOf = (scope, decl) => {
2495
2822
  [ Opcodes.end ],
2496
2823
  [ Opcodes.end ]
2497
2824
  ],
2825
+ [TYPES._bytestring]: [
2826
+ ...setType(scope, leftName, TYPES._bytestring),
2827
+
2828
+ [ Opcodes.loop, Blocktype.void ],
2829
+
2830
+ // setup new/out array
2831
+ ...newOut,
2832
+ [ Opcodes.drop ],
2833
+
2834
+ ...number(0, Valtype.i32), // base 0 for store after
2835
+
2836
+ // load current string ind {arg}
2837
+ [ Opcodes.local_get, pointer ],
2838
+ [ Opcodes.local_get, counter ],
2839
+ [ Opcodes.i32_add ],
2840
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2841
+
2842
+ // store to new string ind 0
2843
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2844
+
2845
+ // return new string (page)
2846
+ ...number(newPointer),
2847
+
2848
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2849
+
2850
+ [ Opcodes.block, Blocktype.void ],
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ ...generate(scope, decl.body),
2853
+ [ Opcodes.end ],
2854
+
2855
+ // increment iter pointer
2856
+ // [ Opcodes.local_get, pointer ],
2857
+ // ...number(1, Valtype.i32),
2858
+ // [ Opcodes.i32_add ],
2859
+ // [ Opcodes.local_set, pointer ],
2860
+
2861
+ // increment counter by 1
2862
+ [ Opcodes.local_get, counter ],
2863
+ ...number(1, Valtype.i32),
2864
+ [ Opcodes.i32_add ],
2865
+ [ Opcodes.local_tee, counter ],
2866
+
2867
+ // loop if counter != length
2868
+ [ Opcodes.local_get, length ],
2869
+ [ Opcodes.i32_ne ],
2870
+ [ Opcodes.br_if, 1 ],
2871
+
2872
+ [ Opcodes.end ],
2873
+ [ Opcodes.end ]
2874
+ ],
2498
2875
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2499
2876
  }, Blocktype.void));
2500
2877
 
@@ -2505,28 +2882,65 @@ const generateForOf = (scope, decl) => {
2505
2882
  return out;
2506
2883
  };
2507
2884
 
2885
+ // find the nearest loop in depth map by type
2508
2886
  const getNearestLoop = () => {
2509
2887
  for (let i = depth.length - 1; i >= 0; i--) {
2510
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2888
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2511
2889
  }
2512
2890
 
2513
2891
  return -1;
2514
2892
  };
2515
2893
 
2516
2894
  const generateBreak = (scope, decl) => {
2517
- const nearestLoop = depth.length - getNearestLoop();
2895
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2896
+ const type = depth[target];
2897
+
2898
+ // different loop types have different branch offsets
2899
+ // as they have different wasm block/loop/if structures
2900
+ // we need to use the right offset by type to branch to the one we want
2901
+ // for a break: exit the loop without executing anything else inside it
2902
+ const offset = ({
2903
+ for: 2, // loop > if (wanted branch) > block (we are here)
2904
+ while: 2, // loop > if (wanted branch) (we are here)
2905
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2906
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2907
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2908
+ })[type];
2909
+
2518
2910
  return [
2519
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2911
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2520
2912
  ];
2521
2913
  };
2522
2914
 
2523
2915
  const generateContinue = (scope, decl) => {
2524
- const nearestLoop = depth.length - getNearestLoop();
2916
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2917
+ const type = depth[target];
2918
+
2919
+ // different loop types have different branch offsets
2920
+ // as they have different wasm block/loop/if structures
2921
+ // we need to use the right offset by type to branch to the one we want
2922
+ // for a continue: do test for the loop, and then loop depending on that success
2923
+ const offset = ({
2924
+ for: 3, // loop (wanted branch) > if > block (we are here)
2925
+ while: 1, // loop (wanted branch) > if (we are here)
2926
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2927
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2928
+ })[type];
2929
+
2525
2930
  return [
2526
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2931
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2527
2932
  ];
2528
2933
  };
2529
2934
 
2935
+ const generateLabel = (scope, decl) => {
2936
+ scope.labels ??= new Map();
2937
+
2938
+ const name = decl.label.name;
2939
+ scope.labels.set(name, depth.length);
2940
+
2941
+ return generate(scope, decl.body);
2942
+ };
2943
+
2530
2944
  const generateThrow = (scope, decl) => {
2531
2945
  scope.throws = true;
2532
2946
 
@@ -2535,7 +2949,7 @@ const generateThrow = (scope, decl) => {
2535
2949
  // hack: throw new X("...") -> throw "..."
2536
2950
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2537
2951
  constructor = decl.argument.callee.name;
2538
- message = decl.argument.arguments[0].value;
2952
+ message = decl.argument.arguments[0]?.value ?? '';
2539
2953
  }
2540
2954
 
2541
2955
  if (tags.length === 0) tags.push({
@@ -2547,6 +2961,9 @@ const generateThrow = (scope, decl) => {
2547
2961
  let exceptId = exceptions.push({ constructor, message }) - 1;
2548
2962
  let tagIdx = tags[0].idx;
2549
2963
 
2964
+ scope.exceptions ??= [];
2965
+ scope.exceptions.push(exceptId);
2966
+
2550
2967
  // todo: write a description of how this works lol
2551
2968
 
2552
2969
  return [
@@ -2556,7 +2973,7 @@ const generateThrow = (scope, decl) => {
2556
2973
  };
2557
2974
 
2558
2975
  const generateTry = (scope, decl) => {
2559
- if (decl.finalizer) return todo('try finally not implemented yet');
2976
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2560
2977
 
2561
2978
  const out = [];
2562
2979
 
@@ -2587,11 +3004,11 @@ const generateAssignPat = (scope, decl) => {
2587
3004
  // TODO
2588
3005
  // if identifier declared, use that
2589
3006
  // else, use default (right)
2590
- return todo('assignment pattern (optional arg)');
3007
+ return todo(scope, 'assignment pattern (optional arg)');
2591
3008
  };
2592
3009
 
2593
3010
  let pages = new Map();
2594
- const allocPage = (reason, type) => {
3011
+ const allocPage = (scope, reason, type) => {
2595
3012
  if (pages.has(reason)) return pages.get(reason).ind;
2596
3013
 
2597
3014
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2602,16 +3019,20 @@ const allocPage = (reason, type) => {
2602
3019
  const ind = pages.size;
2603
3020
  pages.set(reason, { ind, type });
2604
3021
 
2605
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3022
+ scope.pages ??= new Map();
3023
+ scope.pages.set(reason, { ind, type });
3024
+
3025
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2606
3026
 
2607
3027
  return ind;
2608
3028
  };
2609
3029
 
3030
+ // todo: add scope.pages
2610
3031
  const freePage = reason => {
2611
3032
  const { ind } = pages.get(reason);
2612
3033
  pages.delete(reason);
2613
3034
 
2614
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3035
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2615
3036
 
2616
3037
  return ind;
2617
3038
  };
@@ -2637,15 +3058,14 @@ const StoreOps = {
2637
3058
 
2638
3059
  let data = [];
2639
3060
 
2640
- const compileBytes = (val, itemType, signed = true) => {
3061
+ const compileBytes = (val, itemType) => {
2641
3062
  // todo: this is a mess and needs confirming / ????
2642
3063
  switch (itemType) {
2643
3064
  case 'i8': return [ val % 256 ];
2644
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2645
-
2646
- case 'i32':
2647
- case 'i64':
2648
- return enforceFourBytes(signedLEB128(val));
3065
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3066
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3067
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3068
+ // todo: i64
2649
3069
 
2650
3070
  case 'f64': return ieee754_binary64(val);
2651
3071
  }
@@ -2663,16 +3083,20 @@ const getAllocType = itemType => {
2663
3083
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2664
3084
  const out = [];
2665
3085
 
3086
+ scope.arrays ??= new Map();
3087
+
2666
3088
  let firstAssign = false;
2667
- if (!arrays.has(name) || name === '$undeclared') {
3089
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2668
3090
  firstAssign = true;
2669
3091
 
2670
3092
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2671
3093
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2672
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3094
+
3095
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3096
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2673
3097
  }
2674
3098
 
2675
- const pointer = arrays.get(name);
3099
+ const pointer = scope.arrays.get(name);
2676
3100
 
2677
3101
  const useRawElements = !!decl.rawElements;
2678
3102
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2680,19 +3104,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2680
3104
  const valtype = itemTypeToValtype[itemType];
2681
3105
  const length = elements.length;
2682
3106
 
2683
- if (firstAssign && useRawElements) {
2684
- let bytes = compileBytes(length, 'i32');
3107
+ if (firstAssign && useRawElements && !Prefs.noData) {
3108
+ // if length is 0 memory/data will just be 0000... anyway
3109
+ if (length !== 0) {
3110
+ let bytes = compileBytes(length, 'i32');
2685
3111
 
2686
- if (!initEmpty) for (let i = 0; i < length; i++) {
2687
- if (elements[i] == null) continue;
3112
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3113
+ if (elements[i] == null) continue;
2688
3114
 
2689
- bytes.push(...compileBytes(elements[i], itemType));
2690
- }
3115
+ bytes.push(...compileBytes(elements[i], itemType));
3116
+ }
2691
3117
 
2692
- data.push({
2693
- offset: pointer,
2694
- bytes
2695
- });
3118
+ const ind = data.push({
3119
+ offset: pointer,
3120
+ bytes
3121
+ }) - 1;
3122
+
3123
+ scope.data ??= [];
3124
+ scope.data.push(ind);
3125
+ }
2696
3126
 
2697
3127
  // local value as pointer
2698
3128
  out.push(...number(pointer));
@@ -2726,7 +3156,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2726
3156
  };
2727
3157
 
2728
3158
  const byteStringable = str => {
2729
- if (!process.argv.includes('-bytestring')) return false;
3159
+ if (!Prefs.bytestring) return false;
2730
3160
 
2731
3161
  for (let i = 0; i < str.length; i++) {
2732
3162
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2735,9 +3165,9 @@ const byteStringable = str => {
2735
3165
  return true;
2736
3166
  };
2737
3167
 
2738
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3168
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2739
3169
  const rawElements = new Array(str.length);
2740
- let byteStringable = process.argv.includes('-bytestring');
3170
+ let byteStringable = Prefs.bytestring;
2741
3171
  for (let i = 0; i < str.length; i++) {
2742
3172
  const c = str.charCodeAt(i);
2743
3173
  rawElements[i] = c;
@@ -2745,25 +3175,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2745
3175
  if (byteStringable && c > 0xFF) byteStringable = false;
2746
3176
  }
2747
3177
 
3178
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3179
+
2748
3180
  return makeArray(scope, {
2749
3181
  rawElements
2750
3182
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2751
3183
  };
2752
3184
 
2753
- let arrays = new Map();
2754
3185
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2755
3186
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2756
3187
  };
2757
3188
 
2758
3189
  export const generateMember = (scope, decl, _global, _name) => {
2759
3190
  const name = decl.object.name;
2760
- const pointer = arrays.get(name);
3191
+ const pointer = scope.arrays?.get(name);
2761
3192
 
2762
- const aotPointer = pointer != null;
3193
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2763
3194
 
2764
3195
  // hack: .length
2765
3196
  if (decl.property.name === 'length') {
2766
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3197
+ const func = funcs.find(x => x.name === name);
3198
+ if (func) {
3199
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3200
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3201
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3202
+ }
3203
+
3204
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3205
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3206
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3207
+
2767
3208
  return [
2768
3209
  ...(aotPointer ? number(0, Valtype.i32) : [
2769
3210
  ...generate(scope, decl.object),
@@ -2807,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2807
3248
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2808
3249
 
2809
3250
  ...number(TYPES.number, Valtype.i32),
2810
- setLastType(scope)
3251
+ ...setLastType(scope)
2811
3252
  ],
2812
3253
 
2813
3254
  [TYPES.string]: [
@@ -2839,7 +3280,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2839
3280
  ...number(newPointer),
2840
3281
 
2841
3282
  ...number(TYPES.string, Valtype.i32),
2842
- setLastType(scope)
3283
+ ...setLastType(scope)
2843
3284
  ],
2844
3285
  [TYPES._bytestring]: [
2845
3286
  // setup new/out array
@@ -2858,19 +3299,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2858
3299
  ]),
2859
3300
 
2860
3301
  // load current string ind {arg}
2861
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3302
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
3303
 
2863
3304
  // store to new string ind 0
2864
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3305
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
3306
 
2866
3307
  // return new string (page)
2867
3308
  ...number(newPointer),
2868
3309
 
2869
3310
  ...number(TYPES._bytestring, Valtype.i32),
2870
- setLastType(scope)
3311
+ ...setLastType(scope)
2871
3312
  ],
2872
3313
 
2873
- default: [ [ Opcodes.unreachable ] ]
3314
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2874
3315
  });
2875
3316
  };
2876
3317
 
@@ -2880,25 +3321,36 @@ const objectHack = node => {
2880
3321
  if (!node) return node;
2881
3322
 
2882
3323
  if (node.type === 'MemberExpression') {
2883
- if (node.computed || node.optional) return node;
3324
+ const out = (() => {
3325
+ if (node.computed || node.optional) return;
3326
+
3327
+ let objectName = node.object.name;
2884
3328
 
2885
- let objectName = node.object.name;
3329
+ // if object is not identifier or another member exp, give up
3330
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3331
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2886
3332
 
2887
- // if object is not identifier or another member exp, give up
2888
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3333
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2889
3334
 
2890
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3335
+ // if .length, give up (hack within a hack!)
3336
+ if (node.property.name === 'length') {
3337
+ node.object = objectHack(node.object);
3338
+ return;
3339
+ }
2891
3340
 
2892
- // if .length, give up (hack within a hack!)
2893
- if (node.property.name === 'length') return node;
3341
+ // no object name, give up
3342
+ if (!objectName) return;
2894
3343
 
2895
- const name = '__' + objectName + '_' + node.property.name;
2896
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3344
+ const name = '__' + objectName + '_' + node.property.name;
3345
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2897
3346
 
2898
- return {
2899
- type: 'Identifier',
2900
- name
2901
- };
3347
+ return {
3348
+ type: 'Identifier',
3349
+ name
3350
+ };
3351
+ })();
3352
+
3353
+ if (out) return out;
2902
3354
  }
2903
3355
 
2904
3356
  for (const x in node) {
@@ -2912,8 +3364,8 @@ const objectHack = node => {
2912
3364
  };
2913
3365
 
2914
3366
  const generateFunc = (scope, decl) => {
2915
- if (decl.async) return todo('async functions are not supported');
2916
- if (decl.generator) return todo('generator functions are not supported');
3367
+ if (decl.async) return todo(scope, 'async functions are not supported');
3368
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2917
3369
 
2918
3370
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2919
3371
  const params = decl.params ?? [];
@@ -2929,6 +3381,11 @@ const generateFunc = (scope, decl) => {
2929
3381
  name
2930
3382
  };
2931
3383
 
3384
+ if (typedInput && decl.returnType) {
3385
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3386
+ innerScope.returns = [ valtypeBinary ];
3387
+ }
3388
+
2932
3389
  for (let i = 0; i < params.length; i++) {
2933
3390
  allocVar(innerScope, params[i].name, false);
2934
3391
 
@@ -2950,13 +3407,13 @@ const generateFunc = (scope, decl) => {
2950
3407
  const func = {
2951
3408
  name,
2952
3409
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2953
- returns: innerScope.returns,
2954
- locals: innerScope.locals,
2955
- throws: innerScope.throws,
2956
- index: currentFuncIndex++
3410
+ index: currentFuncIndex++,
3411
+ ...innerScope
2957
3412
  };
2958
3413
  funcIndex[name] = func.index;
2959
3414
 
3415
+ if (name === 'main') func.gotLastType = true;
3416
+
2960
3417
  // quick hack fixes
2961
3418
  for (const inst of wasm) {
2962
3419
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3008,7 +3465,7 @@ const internalConstrs = {
3008
3465
 
3009
3466
  // todo: check in wasm instead of here
3010
3467
  const literalValue = arg.value ?? 0;
3011
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3468
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3012
3469
 
3013
3470
  return [
3014
3471
  ...number(0, Valtype.i32),
@@ -3019,7 +3476,8 @@ const internalConstrs = {
3019
3476
  ...number(pointer)
3020
3477
  ];
3021
3478
  },
3022
- type: TYPES._array
3479
+ type: TYPES._array,
3480
+ length: 1
3023
3481
  },
3024
3482
 
3025
3483
  __Array_of: {
@@ -3031,7 +3489,94 @@ const internalConstrs = {
3031
3489
  }, global, name);
3032
3490
  },
3033
3491
  type: TYPES._array,
3492
+ notConstr: true,
3493
+ length: 0
3494
+ },
3495
+
3496
+ __Porffor_fastOr: {
3497
+ generate: (scope, decl) => {
3498
+ const out = [];
3499
+
3500
+ for (let i = 0; i < decl.arguments.length; i++) {
3501
+ out.push(
3502
+ ...generate(scope, decl.arguments[i]),
3503
+ Opcodes.i32_to_u,
3504
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3505
+ );
3506
+ }
3507
+
3508
+ return out;
3509
+ },
3510
+ type: TYPES.boolean,
3511
+ notConstr: true
3512
+ },
3513
+
3514
+ __Porffor_fastAnd: {
3515
+ generate: (scope, decl) => {
3516
+ const out = [];
3517
+
3518
+ for (let i = 0; i < decl.arguments.length; i++) {
3519
+ out.push(
3520
+ ...generate(scope, decl.arguments[i]),
3521
+ Opcodes.i32_to_u,
3522
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3523
+ );
3524
+ }
3525
+
3526
+ return out;
3527
+ },
3528
+ type: TYPES.boolean,
3034
3529
  notConstr: true
3530
+ },
3531
+
3532
+ Boolean: {
3533
+ generate: (scope, decl) => {
3534
+ // todo: boolean object when used as constructor
3535
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3536
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3537
+ },
3538
+ type: TYPES.boolean,
3539
+ length: 1
3540
+ },
3541
+
3542
+ __Math_max: {
3543
+ generate: (scope, decl) => {
3544
+ const out = [
3545
+ ...number(-Infinity)
3546
+ ];
3547
+
3548
+ for (let i = 0; i < decl.arguments.length; i++) {
3549
+ out.push(
3550
+ ...generate(scope, decl.arguments[i]),
3551
+ [ Opcodes.f64_max ]
3552
+ );
3553
+ }
3554
+
3555
+ return out;
3556
+ },
3557
+ type: TYPES.number,
3558
+ notConstr: true,
3559
+ length: 2
3560
+ },
3561
+
3562
+ __Math_min: {
3563
+ generate: (scope, decl) => {
3564
+ const out = [
3565
+ ...number(Infinity)
3566
+ ];
3567
+
3568
+ for (let i = 0; i < decl.arguments.length; i++) {
3569
+ out.push(
3570
+ ...generate(scope, decl.arguments[i]),
3571
+ [ Opcodes.f64_min ]
3572
+ );
3573
+ }
3574
+
3575
+ return out;
3576
+ },
3577
+ type: TYPES.number,
3578
+ notConstr: true,
3579
+ length: 2
3035
3580
  }
3036
3581
  };
3037
3582
 
@@ -3060,7 +3605,6 @@ export default program => {
3060
3605
  funcs = [];
3061
3606
  funcIndex = {};
3062
3607
  depth = [];
3063
- arrays = new Map();
3064
3608
  pages = new Map();
3065
3609
  data = [];
3066
3610
  currentFuncIndex = importedFuncs.length;
@@ -3074,6 +3618,10 @@ export default program => {
3074
3618
 
3075
3619
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3076
3620
 
3621
+ globalThis.pageSize = PageSize;
3622
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3623
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3624
+
3077
3625
  // set generic opcodes for current valtype
3078
3626
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3079
3627
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3082,10 +3630,10 @@ export default program => {
3082
3630
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3083
3631
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3084
3632
 
3085
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3086
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3087
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3088
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3633
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3634
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3635
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3636
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3089
3637
 
3090
3638
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3091
3639
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3098,10 +3646,6 @@ export default program => {
3098
3646
 
3099
3647
  program.id = { name: 'main' };
3100
3648
 
3101
- globalThis.pageSize = PageSize;
3102
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3103
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3104
-
3105
3649
  const scope = {
3106
3650
  locals: {},
3107
3651
  localInd: 0
@@ -3112,7 +3656,7 @@ export default program => {
3112
3656
  body: program.body
3113
3657
  };
3114
3658
 
3115
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3659
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3116
3660
 
3117
3661
  generateFunc(scope, program);
3118
3662
 
@@ -3129,7 +3673,11 @@ export default program => {
3129
3673
  }
3130
3674
 
3131
3675
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3132
- main.returns = [];
3676
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3677
+ main.wasm.splice(main.wasm.length - 1, 1);
3678
+ } else {
3679
+ main.returns = [];
3680
+ }
3133
3681
  }
3134
3682
 
3135
3683
  if (lastInst[0] === Opcodes.call) {