porffor 0.2.0-eaee2da → 0.2.0-ef043de

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 (52) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +159 -88
  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 +1370 -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 +470 -269
  21. package/compiler/{codeGen.js → codegen.js} +999 -378
  22. package/compiler/embedding.js +22 -22
  23. package/compiler/encoding.js +108 -10
  24. package/compiler/generated_builtins.js +1262 -0
  25. package/compiler/index.js +36 -34
  26. package/compiler/log.js +6 -3
  27. package/compiler/opt.js +51 -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 +28 -8
  34. package/compiler/wrap.js +54 -46
  35. package/fib.js +7 -0
  36. package/package.json +9 -5
  37. package/porf +4 -0
  38. package/rhemyn/compile.js +5 -3
  39. package/rhemyn/parse.js +323 -320
  40. package/rhemyn/test/parse.js +58 -58
  41. package/runner/compare.js +34 -34
  42. package/runner/debug.js +122 -0
  43. package/runner/index.js +62 -10
  44. package/runner/profiler.js +102 -0
  45. package/runner/repl.js +40 -7
  46. package/runner/sizes.js +37 -37
  47. package/compiler/builtins/base64.js +0 -92
  48. package/runner/info.js +0 -89
  49. package/runner/profile.js +0 -46
  50. package/runner/results.json +0 -1
  51. package/runner/transform.js +0 -15
  52. 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,43 +204,65 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
188
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
205
 
190
206
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
192
212
 
193
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
214
  }
195
215
 
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;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
216
254
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
255
+ return funcs[func](str);
219
256
  }
220
257
 
221
258
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
262
  return [];
225
263
  }
226
264
 
227
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
228
266
  }
229
267
  };
230
268
 
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
252
290
  return [ undefined, undefined ];
253
291
  };
254
292
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
294
  ...generateThrow(scope, {
257
295
  argument: {
258
296
  type: 'NewExpression',
@@ -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,11 +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
- // we cannot guess
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);
1411
+
1412
+ // presume
1264
1413
  return TYPES.number;
1265
1414
  }
1266
1415
 
1267
- 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);
1268
1422
 
1269
1423
  // presume
1270
1424
  // todo: warn here?
@@ -1277,28 +1431,11 @@ const getNodeType = (scope, node) => {
1277
1431
  return ret;
1278
1432
  };
1279
1433
 
1280
- const toString = (scope, wasm, type) => {
1281
- const tmp = localTmp(scope, '#tostring_tmp');
1282
- return [
1283
- ...wasm,
1284
- [ Opcodes.local_set, tmp ],
1285
-
1286
- ...typeSwitch(scope, type, {
1287
- [TYPES.string]: [
1288
- [ Opcodes.local_get, tmp ]
1289
- ],
1290
- [TYPES.undefined]: [
1291
- // [ Opcodes.]
1292
- ]
1293
- })
1294
- ]
1295
- };
1296
-
1297
1434
  const generateLiteral = (scope, decl, global, name) => {
1298
1435
  if (decl.value === null) return number(NULL);
1299
1436
 
1437
+ // hack: just return 1 for regex literals
1300
1438
  if (decl.regex) {
1301
- scope.regex[name] = decl.regex;
1302
1439
  return number(1);
1303
1440
  }
1304
1441
 
@@ -1314,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1314
1451
  return makeString(scope, decl.value, global, name);
1315
1452
 
1316
1453
  default:
1317
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1318
1455
  }
1319
1456
  };
1320
1457
 
@@ -1323,6 +1460,8 @@ const countLeftover = wasm => {
1323
1460
 
1324
1461
  for (let i = 0; i < wasm.length; i++) {
1325
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1326
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1327
1466
  if (inst[0] === Opcodes.if) count--;
1328
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1331,18 +1470,25 @@ const countLeftover = wasm => {
1331
1470
  if (inst[0] === Opcodes.end) depth--;
1332
1471
 
1333
1472
  if (depth === 0)
1334
- 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--;
1335
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)) {}
1336
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1337
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1338
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1339
1478
  else if (inst[0] === Opcodes.return) count = 0;
1340
1479
  else if (inst[0] === Opcodes.call) {
1341
1480
  let func = funcs.find(x => x.index === inst[1]);
1342
- if (func) {
1343
- count -= func.params.length;
1344
- } else count--;
1345
- 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
+ }
1346
1492
  } else count--;
1347
1493
 
1348
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1434,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1434
1580
  name = func.name;
1435
1581
  }
1436
1582
 
1437
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1438
1584
  // literal eval hack
1439
- const code = decl.arguments[0].value;
1440
- 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
+ }
1441
1598
 
1442
1599
  const out = generate(scope, {
1443
1600
  type: 'BlockStatement',
@@ -1451,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1451
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1452
1609
  out.push(
1453
1610
  ...getNodeType(scope, finalStatement),
1454
- setLastType(scope)
1611
+ ...setLastType(scope)
1455
1612
  );
1456
1613
  } else if (countLeftover(out) === 0) {
1457
1614
  out.push(...number(UNDEFINED));
1458
1615
  out.push(
1459
1616
  ...number(TYPES.undefined, Valtype.i32),
1460
- setLastType(scope)
1617
+ ...setLastType(scope)
1461
1618
  );
1462
1619
  }
1463
1620
 
@@ -1479,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1479
1636
 
1480
1637
  target = { ...decl.callee };
1481
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1482
1642
  }
1483
1643
 
1484
1644
  // literal.func()
1485
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1486
1646
  // megahack for /regex/.func()
1487
- if (decl.callee.object.regex) {
1488
- const funcName = decl.callee.property.name;
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1489
1649
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1490
1650
 
1491
1651
  funcIndex[func.name] = func.index;
@@ -1501,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1501
1661
  Opcodes.i32_from_u,
1502
1662
 
1503
1663
  ...number(TYPES.boolean, Valtype.i32),
1504
- setLastType(scope)
1664
+ ...setLastType(scope)
1505
1665
  ];
1506
1666
  }
1507
1667
 
@@ -1526,13 +1686,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1526
1686
  // }
1527
1687
 
1528
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
+ type: 'Identifier',
1701
+ name: x
1702
+ },
1703
+ arguments: [ target, ...decl.arguments ],
1704
+ _protoInternalCall: true
1705
+ });
1706
+ }
1707
+ }
1708
+
1529
1709
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1530
- const f = prototypeFuncs[x][protoName];
1531
- if (f) acc[x] = f;
1710
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1532
1711
  return acc;
1533
1712
  }, {});
1534
1713
 
1535
- // no prototype function candidates, ignore
1536
1714
  if (Object.keys(protoCands).length > 0) {
1537
1715
  // use local for cached i32 length as commonly used
1538
1716
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1550,7 +1728,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1550
1728
 
1551
1729
  let allOptUnused = true;
1552
1730
  let lengthI32CacheUsed = false;
1553
- const protoBC = {};
1554
1731
  for (const x in protoCands) {
1555
1732
  const protoFunc = protoCands[x];
1556
1733
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1558,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1558
1735
  ...RTArrayUtil.getLength(getPointer),
1559
1736
 
1560
1737
  ...number(TYPES.number, Valtype.i32),
1561
- setLastType(scope)
1738
+ ...setLastType(scope)
1562
1739
  ];
1563
1740
  continue;
1564
1741
  }
@@ -1595,7 +1772,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1595
1772
  ...protoOut,
1596
1773
 
1597
1774
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1598
- setLastType(scope),
1775
+ ...setLastType(scope),
1599
1776
  [ Opcodes.end ]
1600
1777
  ];
1601
1778
  }
@@ -1621,10 +1798,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1621
1798
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1622
1799
  ];
1623
1800
  }
1801
+
1802
+ if (Object.keys(protoBC).length > 0) {
1803
+ return typeSwitch(scope, getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary);
1809
+ }
1624
1810
  }
1625
1811
 
1626
1812
  // TODO: only allows callee as literal
1627
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1813
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1628
1814
 
1629
1815
  let idx = funcIndex[name] ?? importedFuncs[name];
1630
1816
  if (idx === undefined && builtinFuncs[name]) {
@@ -1657,16 +1843,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1657
1843
  idx = -1;
1658
1844
  }
1659
1845
 
1846
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1847
+ const wasmOps = {
1848
+ // pointer, align, offset
1849
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1850
+ // pointer, value, align, offset
1851
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1852
+ // pointer, align, offset
1853
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1854
+ // pointer, value, align, offset
1855
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1856
+ // pointer, align, offset
1857
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1858
+ // pointer, value, align, offset
1859
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1860
+
1861
+ // pointer, align, offset
1862
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1863
+ // pointer, value, align, offset
1864
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1865
+
1866
+ // value
1867
+ i32_const: { imms: 1, args: [], returns: 1 },
1868
+
1869
+ // a, b
1870
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1871
+ };
1872
+
1873
+ const opName = name.slice('__Porffor_wasm_'.length);
1874
+
1875
+ if (wasmOps[opName]) {
1876
+ const op = wasmOps[opName];
1877
+
1878
+ const argOut = [];
1879
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1880
+ ...generate(scope, decl.arguments[i]),
1881
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1882
+ );
1883
+
1884
+ // literals only
1885
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1886
+
1887
+ return [
1888
+ ...argOut,
1889
+ [ Opcodes[opName], ...imms ],
1890
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1891
+ ];
1892
+ }
1893
+ }
1894
+
1660
1895
  if (idx === undefined) {
1661
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1662
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1896
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1897
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1663
1898
  }
1664
1899
 
1665
1900
  const func = funcs.find(x => x.index === idx);
1666
1901
 
1667
1902
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1668
1903
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1669
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1904
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1670
1905
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1671
1906
 
1672
1907
  let args = decl.arguments;
@@ -1683,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1683
1918
  if (func && func.throws) scope.throws = true;
1684
1919
 
1685
1920
  let out = [];
1686
- for (const arg of args) {
1921
+ for (let i = 0; i < args.length; i++) {
1922
+ const arg = args[i];
1687
1923
  out = out.concat(generate(scope, arg));
1924
+
1925
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1926
+ out.push(Opcodes.i32_to);
1927
+ }
1928
+
1929
+ if (importedFuncs[name] && name.startsWith('profile')) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1688
1933
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1689
1934
  }
1690
1935
 
1691
1936
  out.push([ Opcodes.call, idx ]);
1692
1937
 
1693
- if (!typedReturn) {
1938
+ if (!typedReturns) {
1694
1939
  // let type;
1695
1940
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1696
1941
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1700,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1700
1945
  // ...number(type, Valtype.i32),
1701
1946
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1702
1947
  // );
1703
- } else out.push(setLastType(scope));
1948
+ } else out.push(...setLastType(scope));
1949
+
1950
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1951
+ out.push(Opcodes.i32_from);
1952
+ }
1704
1953
 
1705
1954
  return out;
1706
1955
  };
@@ -1708,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1708
1957
  const generateNew = (scope, decl, _global, _name) => {
1709
1958
  // hack: basically treat this as a normal call for builtins for now
1710
1959
  const name = mapName(decl.callee.name);
1960
+
1711
1961
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1712
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1962
+
1963
+ if (builtinFuncs[name + '$constructor']) {
1964
+ // custom ...$constructor override builtin func
1965
+ return generateCall(scope, {
1966
+ ...decl,
1967
+ callee: {
1968
+ type: 'Identifier',
1969
+ name: name + '$constructor'
1970
+ }
1971
+ }, _global, _name);
1972
+ }
1973
+
1974
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1713
1975
 
1714
1976
  return generateCall(scope, decl, _global, _name);
1715
1977
  };
@@ -1826,14 +2088,14 @@ const brTable = (input, bc, returns) => {
1826
2088
  };
1827
2089
 
1828
2090
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1829
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2091
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1830
2092
 
1831
2093
  const known = knownType(scope, type);
1832
2094
  if (known != null) {
1833
2095
  return bc[known] ?? bc.default;
1834
2096
  }
1835
2097
 
1836
- if (process.argv.includes('-typeswitch-use-brtable'))
2098
+ if (Prefs.typeswitchUseBrtable)
1837
2099
  return brTable(type, bc, returns);
1838
2100
 
1839
2101
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1843,8 +2105,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1843
2105
  [ Opcodes.block, returns ]
1844
2106
  ];
1845
2107
 
1846
- // todo: use br_table?
1847
-
1848
2108
  for (const x in bc) {
1849
2109
  if (x === 'default') continue;
1850
2110
 
@@ -1868,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1868
2128
  return out;
1869
2129
  };
1870
2130
 
1871
- const allocVar = (scope, name, global = false) => {
2131
+ const allocVar = (scope, name, global = false, type = true) => {
1872
2132
  const target = global ? globals : scope.locals;
1873
2133
 
1874
2134
  // already declared
@@ -1882,8 +2142,10 @@ const allocVar = (scope, name, global = false) => {
1882
2142
  let idx = global ? globalInd++ : scope.localInd++;
1883
2143
  target[name] = { idx, type: valtypeBinary };
1884
2144
 
1885
- let typeIdx = global ? globalInd++ : scope.localInd++;
1886
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2145
+ if (type) {
2146
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2147
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2148
+ }
1887
2149
 
1888
2150
  return idx;
1889
2151
  };
@@ -1898,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1898
2160
  };
1899
2161
 
1900
2162
  const typeAnnoToPorfType = x => {
1901
- if (TYPES[x]) return TYPES[x];
1902
- if (TYPES['_' + x]) return TYPES['_' + x];
2163
+ if (!x) return null;
2164
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2165
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1903
2166
 
1904
2167
  switch (x) {
1905
2168
  case 'i32':
2169
+ case 'i64':
2170
+ case 'f64':
1906
2171
  return TYPES.number;
1907
2172
  }
1908
2173
 
@@ -1913,7 +2178,7 @@ const extractTypeAnnotation = decl => {
1913
2178
  let a = decl;
1914
2179
  while (a.typeAnnotation) a = a.typeAnnotation;
1915
2180
 
1916
- let type, elementType;
2181
+ let type = null, elementType = null;
1917
2182
  if (a.typeName) {
1918
2183
  type = a.typeName.name;
1919
2184
  } else if (a.type.endsWith('Keyword')) {
@@ -1926,6 +2191,8 @@ const extractTypeAnnotation = decl => {
1926
2191
  const typeName = type;
1927
2192
  type = typeAnnoToPorfType(type);
1928
2193
 
2194
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2195
+
1929
2196
  // if (decl.name) console.log(decl.name, { type, elementType });
1930
2197
 
1931
2198
  return { type, typeName, elementType };
@@ -1942,6 +2209,8 @@ const generateVar = (scope, decl) => {
1942
2209
  for (const x of decl.declarations) {
1943
2210
  const name = mapName(x.id.name);
1944
2211
 
2212
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2213
+
1945
2214
  if (x.init && isFuncType(x.init.type)) {
1946
2215
  // hack for let a = function () { ... }
1947
2216
  x.init.id = { name };
@@ -1957,9 +2226,10 @@ const generateVar = (scope, decl) => {
1957
2226
  continue; // always ignore
1958
2227
  }
1959
2228
 
1960
- let idx = allocVar(scope, name, global);
2229
+ const typed = typedInput && x.id.typeAnnotation;
2230
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1961
2231
 
1962
- if (typedInput && x.id.typeAnnotation) {
2232
+ if (typed) {
1963
2233
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1964
2234
  }
1965
2235
 
@@ -1977,7 +2247,8 @@ const generateVar = (scope, decl) => {
1977
2247
  return out;
1978
2248
  };
1979
2249
 
1980
- const generateAssign = (scope, decl) => {
2250
+ // todo: optimize this func for valueUnused
2251
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1981
2252
  const { type, name } = decl.left;
1982
2253
 
1983
2254
  if (type === 'ObjectPattern') {
@@ -1995,9 +2266,9 @@ const generateAssign = (scope, decl) => {
1995
2266
  // hack: .length setter
1996
2267
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1997
2268
  const name = decl.left.object.name;
1998
- const pointer = arrays.get(name);
2269
+ const pointer = scope.arrays?.get(name);
1999
2270
 
2000
- const aotPointer = pointer != null;
2271
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2001
2272
 
2002
2273
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2003
2274
 
@@ -2022,9 +2293,9 @@ const generateAssign = (scope, decl) => {
2022
2293
  // arr[i]
2023
2294
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2024
2295
  const name = decl.left.object.name;
2025
- const pointer = arrays.get(name);
2296
+ const pointer = scope.arrays?.get(name);
2026
2297
 
2027
- const aotPointer = pointer != null;
2298
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2028
2299
 
2029
2300
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2030
2301
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2080,6 +2351,8 @@ const generateAssign = (scope, decl) => {
2080
2351
  ];
2081
2352
  }
2082
2353
 
2354
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2355
+
2083
2356
  const [ local, isGlobal ] = lookupName(scope, name);
2084
2357
 
2085
2358
  if (local === undefined) {
@@ -2126,9 +2399,7 @@ const generateAssign = (scope, decl) => {
2126
2399
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2127
2400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2128
2401
 
2129
- getLastType(scope),
2130
- // hack: type is idx+1
2131
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2402
+ ...setType(scope, name, getLastType(scope))
2132
2403
  ];
2133
2404
  }
2134
2405
 
@@ -2139,9 +2410,7 @@ const generateAssign = (scope, decl) => {
2139
2410
 
2140
2411
  // todo: string concat types
2141
2412
 
2142
- // hack: type is idx+1
2143
- ...number(TYPES.number, Valtype.i32),
2144
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2413
+ ...setType(scope, name, TYPES.number)
2145
2414
  ];
2146
2415
  };
2147
2416
 
@@ -2187,7 +2456,7 @@ const generateUnary = (scope, decl) => {
2187
2456
  return out;
2188
2457
  }
2189
2458
 
2190
- case 'delete':
2459
+ case 'delete': {
2191
2460
  let toReturn = true, toGenerate = true;
2192
2461
 
2193
2462
  if (decl.argument.type === 'Identifier') {
@@ -2209,9 +2478,26 @@ const generateUnary = (scope, decl) => {
2209
2478
 
2210
2479
  out.push(...number(toReturn ? 1 : 0));
2211
2480
  return out;
2481
+ }
2482
+
2483
+ case 'typeof': {
2484
+ let overrideType, toGenerate = true;
2485
+
2486
+ if (decl.argument.type === 'Identifier') {
2487
+ const out = generateIdent(scope, decl.argument);
2488
+
2489
+ // if ReferenceError (undeclared var), ignore and return undefined
2490
+ if (out[1]) {
2491
+ // does not exist (2 ops from throw)
2492
+ overrideType = number(TYPES.undefined, Valtype.i32);
2493
+ toGenerate = false;
2494
+ }
2495
+ }
2212
2496
 
2213
- case 'typeof':
2214
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2497
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2498
+ disposeLeftover(out);
2499
+
2500
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2215
2501
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2216
2502
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2217
2503
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2222,27 +2508,30 @@ const generateUnary = (scope, decl) => {
2222
2508
 
2223
2509
  // object and internal types
2224
2510
  default: makeString(scope, 'object', false, '#typeof_result'),
2225
- });
2511
+ }));
2512
+
2513
+ return out;
2514
+ }
2226
2515
 
2227
2516
  default:
2228
- return todo(`unary operator ${decl.operator} not implemented yet`);
2517
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2229
2518
  }
2230
2519
  };
2231
2520
 
2232
- const generateUpdate = (scope, decl) => {
2521
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2233
2522
  const { name } = decl.argument;
2234
2523
 
2235
2524
  const [ local, isGlobal ] = lookupName(scope, name);
2236
2525
 
2237
2526
  if (local === undefined) {
2238
- return todo(`update expression with undefined variable`);
2527
+ return todo(scope, `update expression with undefined variable`, true);
2239
2528
  }
2240
2529
 
2241
2530
  const idx = local.idx;
2242
2531
  const out = [];
2243
2532
 
2244
2533
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2245
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2534
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2246
2535
 
2247
2536
  switch (decl.operator) {
2248
2537
  case '++':
@@ -2255,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
2255
2544
  }
2256
2545
 
2257
2546
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2258
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2547
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2259
2548
 
2260
2549
  return out;
2261
2550
  };
@@ -2295,7 +2584,7 @@ const generateConditional = (scope, decl) => {
2295
2584
  // note type
2296
2585
  out.push(
2297
2586
  ...getNodeType(scope, decl.consequent),
2298
- setLastType(scope)
2587
+ ...setLastType(scope)
2299
2588
  );
2300
2589
 
2301
2590
  out.push([ Opcodes.else ]);
@@ -2304,7 +2593,7 @@ const generateConditional = (scope, decl) => {
2304
2593
  // note type
2305
2594
  out.push(
2306
2595
  ...getNodeType(scope, decl.alternate),
2307
- setLastType(scope)
2596
+ ...setLastType(scope)
2308
2597
  );
2309
2598
 
2310
2599
  out.push([ Opcodes.end ]);
@@ -2318,15 +2607,17 @@ const generateFor = (scope, decl) => {
2318
2607
  const out = [];
2319
2608
 
2320
2609
  if (decl.init) {
2321
- out.push(...generate(scope, decl.init));
2610
+ out.push(...generate(scope, decl.init, false, undefined, true));
2322
2611
  disposeLeftover(out);
2323
2612
  }
2324
2613
 
2325
2614
  out.push([ Opcodes.loop, Blocktype.void ]);
2326
2615
  depth.push('for');
2327
2616
 
2328
- out.push(...generate(scope, decl.test));
2329
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2617
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2618
+ else out.push(...number(1, Valtype.i32));
2619
+
2620
+ out.push([ Opcodes.if, Blocktype.void ]);
2330
2621
  depth.push('if');
2331
2622
 
2332
2623
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2334,8 +2625,7 @@ const generateFor = (scope, decl) => {
2334
2625
  out.push(...generate(scope, decl.body));
2335
2626
  out.push([ Opcodes.end ]);
2336
2627
 
2337
- out.push(...generate(scope, decl.update));
2338
- depth.pop();
2628
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2339
2629
 
2340
2630
  out.push([ Opcodes.br, 1 ]);
2341
2631
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2363,6 +2653,36 @@ const generateWhile = (scope, decl) => {
2363
2653
  return out;
2364
2654
  };
2365
2655
 
2656
+ const generateDoWhile = (scope, decl) => {
2657
+ const out = [];
2658
+
2659
+ out.push([ Opcodes.loop, Blocktype.void ]);
2660
+ depth.push('dowhile');
2661
+
2662
+ // block for break (includes all)
2663
+ out.push([ Opcodes.block, Blocktype.void ]);
2664
+ depth.push('block');
2665
+
2666
+ // block for continue
2667
+ // includes body but not test+loop so we can exit body at anytime
2668
+ // and still test+loop after
2669
+ out.push([ Opcodes.block, Blocktype.void ]);
2670
+ depth.push('block');
2671
+
2672
+ out.push(...generate(scope, decl.body));
2673
+
2674
+ out.push([ Opcodes.end ]);
2675
+ depth.pop();
2676
+
2677
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2678
+ out.push([ Opcodes.br_if, 1 ]);
2679
+
2680
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2681
+ depth.pop(); depth.pop();
2682
+
2683
+ return out;
2684
+ };
2685
+
2366
2686
  const generateForOf = (scope, decl) => {
2367
2687
  const out = [];
2368
2688
 
@@ -2392,8 +2712,17 @@ const generateForOf = (scope, decl) => {
2392
2712
  // setup local for left
2393
2713
  generate(scope, decl.left);
2394
2714
 
2395
- const leftName = decl.left.declarations[0].id.name;
2715
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2716
+ if (!leftName && decl.left.name) {
2717
+ leftName = decl.left.name;
2718
+
2719
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2720
+ }
2721
+
2722
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2723
+
2396
2724
  const [ local, isGlobal ] = lookupName(scope, leftName);
2725
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2397
2726
 
2398
2727
  depth.push('block');
2399
2728
  depth.push('block');
@@ -2401,7 +2730,8 @@ const generateForOf = (scope, decl) => {
2401
2730
  // // todo: we should only do this for strings but we don't know at compile-time :(
2402
2731
  // hack: this is naughty and will break things!
2403
2732
  let newOut = number(0, Valtype.f64), newPointer = -1;
2404
- if (pages.hasString) {
2733
+ if (pages.hasAnyString) {
2734
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2405
2735
  0, [ newOut, newPointer ] = makeArray(scope, {
2406
2736
  rawElements: new Array(1)
2407
2737
  }, isGlobal, leftName, true, 'i16');
@@ -2493,6 +2823,56 @@ const generateForOf = (scope, decl) => {
2493
2823
  [ Opcodes.end ],
2494
2824
  [ Opcodes.end ]
2495
2825
  ],
2826
+ [TYPES._bytestring]: [
2827
+ ...setType(scope, leftName, TYPES._bytestring),
2828
+
2829
+ [ Opcodes.loop, Blocktype.void ],
2830
+
2831
+ // setup new/out array
2832
+ ...newOut,
2833
+ [ Opcodes.drop ],
2834
+
2835
+ ...number(0, Valtype.i32), // base 0 for store after
2836
+
2837
+ // load current string ind {arg}
2838
+ [ Opcodes.local_get, pointer ],
2839
+ [ Opcodes.local_get, counter ],
2840
+ [ Opcodes.i32_add ],
2841
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2842
+
2843
+ // store to new string ind 0
2844
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2845
+
2846
+ // return new string (page)
2847
+ ...number(newPointer),
2848
+
2849
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2850
+
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ [ Opcodes.block, Blocktype.void ],
2853
+ ...generate(scope, decl.body),
2854
+ [ Opcodes.end ],
2855
+
2856
+ // increment iter pointer
2857
+ // [ Opcodes.local_get, pointer ],
2858
+ // ...number(1, Valtype.i32),
2859
+ // [ Opcodes.i32_add ],
2860
+ // [ Opcodes.local_set, pointer ],
2861
+
2862
+ // increment counter by 1
2863
+ [ Opcodes.local_get, counter ],
2864
+ ...number(1, Valtype.i32),
2865
+ [ Opcodes.i32_add ],
2866
+ [ Opcodes.local_tee, counter ],
2867
+
2868
+ // loop if counter != length
2869
+ [ Opcodes.local_get, length ],
2870
+ [ Opcodes.i32_ne ],
2871
+ [ Opcodes.br_if, 1 ],
2872
+
2873
+ [ Opcodes.end ],
2874
+ [ Opcodes.end ]
2875
+ ],
2496
2876
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2497
2877
  }, Blocktype.void));
2498
2878
 
@@ -2503,28 +2883,65 @@ const generateForOf = (scope, decl) => {
2503
2883
  return out;
2504
2884
  };
2505
2885
 
2886
+ // find the nearest loop in depth map by type
2506
2887
  const getNearestLoop = () => {
2507
2888
  for (let i = depth.length - 1; i >= 0; i--) {
2508
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2889
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2509
2890
  }
2510
2891
 
2511
2892
  return -1;
2512
2893
  };
2513
2894
 
2514
2895
  const generateBreak = (scope, decl) => {
2515
- const nearestLoop = depth.length - getNearestLoop();
2896
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2897
+ const type = depth[target];
2898
+
2899
+ // different loop types have different branch offsets
2900
+ // as they have different wasm block/loop/if structures
2901
+ // we need to use the right offset by type to branch to the one we want
2902
+ // for a break: exit the loop without executing anything else inside it
2903
+ const offset = ({
2904
+ for: 2, // loop > if (wanted branch) > block (we are here)
2905
+ while: 2, // loop > if (wanted branch) (we are here)
2906
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2907
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2908
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2909
+ })[type];
2910
+
2516
2911
  return [
2517
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2912
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2518
2913
  ];
2519
2914
  };
2520
2915
 
2521
2916
  const generateContinue = (scope, decl) => {
2522
- const nearestLoop = depth.length - getNearestLoop();
2917
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2918
+ const type = depth[target];
2919
+
2920
+ // different loop types have different branch offsets
2921
+ // as they have different wasm block/loop/if structures
2922
+ // we need to use the right offset by type to branch to the one we want
2923
+ // for a continue: do test for the loop, and then loop depending on that success
2924
+ const offset = ({
2925
+ for: 3, // loop (wanted branch) > if > block (we are here)
2926
+ while: 1, // loop (wanted branch) > if (we are here)
2927
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2928
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2929
+ })[type];
2930
+
2523
2931
  return [
2524
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2932
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2525
2933
  ];
2526
2934
  };
2527
2935
 
2936
+ const generateLabel = (scope, decl) => {
2937
+ scope.labels ??= new Map();
2938
+
2939
+ const name = decl.label.name;
2940
+ scope.labels.set(name, depth.length);
2941
+
2942
+ return generate(scope, decl.body);
2943
+ };
2944
+
2528
2945
  const generateThrow = (scope, decl) => {
2529
2946
  scope.throws = true;
2530
2947
 
@@ -2533,7 +2950,7 @@ const generateThrow = (scope, decl) => {
2533
2950
  // hack: throw new X("...") -> throw "..."
2534
2951
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2535
2952
  constructor = decl.argument.callee.name;
2536
- message = decl.argument.arguments[0].value;
2953
+ message = decl.argument.arguments[0]?.value ?? '';
2537
2954
  }
2538
2955
 
2539
2956
  if (tags.length === 0) tags.push({
@@ -2545,6 +2962,9 @@ const generateThrow = (scope, decl) => {
2545
2962
  let exceptId = exceptions.push({ constructor, message }) - 1;
2546
2963
  let tagIdx = tags[0].idx;
2547
2964
 
2965
+ scope.exceptions ??= [];
2966
+ scope.exceptions.push(exceptId);
2967
+
2548
2968
  // todo: write a description of how this works lol
2549
2969
 
2550
2970
  return [
@@ -2554,7 +2974,7 @@ const generateThrow = (scope, decl) => {
2554
2974
  };
2555
2975
 
2556
2976
  const generateTry = (scope, decl) => {
2557
- if (decl.finalizer) return todo('try finally not implemented yet');
2977
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2558
2978
 
2559
2979
  const out = [];
2560
2980
 
@@ -2585,29 +3005,35 @@ const generateAssignPat = (scope, decl) => {
2585
3005
  // TODO
2586
3006
  // if identifier declared, use that
2587
3007
  // else, use default (right)
2588
- return todo('assignment pattern (optional arg)');
3008
+ return todo(scope, 'assignment pattern (optional arg)');
2589
3009
  };
2590
3010
 
2591
3011
  let pages = new Map();
2592
- const allocPage = (reason, type) => {
3012
+ const allocPage = (scope, reason, type) => {
2593
3013
  if (pages.has(reason)) return pages.get(reason).ind;
2594
3014
 
2595
3015
  if (reason.startsWith('array:')) pages.hasArray = true;
2596
3016
  if (reason.startsWith('string:')) pages.hasString = true;
3017
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3018
+ if (reason.includes('string:')) pages.hasAnyString = true;
2597
3019
 
2598
3020
  const ind = pages.size;
2599
3021
  pages.set(reason, { ind, type });
2600
3022
 
2601
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3023
+ scope.pages ??= new Map();
3024
+ scope.pages.set(reason, { ind, type });
3025
+
3026
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2602
3027
 
2603
3028
  return ind;
2604
3029
  };
2605
3030
 
3031
+ // todo: add scope.pages
2606
3032
  const freePage = reason => {
2607
3033
  const { ind } = pages.get(reason);
2608
3034
  pages.delete(reason);
2609
3035
 
2610
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3036
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2611
3037
 
2612
3038
  return ind;
2613
3039
  };
@@ -2633,15 +3059,14 @@ const StoreOps = {
2633
3059
 
2634
3060
  let data = [];
2635
3061
 
2636
- const compileBytes = (val, itemType, signed = true) => {
3062
+ const compileBytes = (val, itemType) => {
2637
3063
  // todo: this is a mess and needs confirming / ????
2638
3064
  switch (itemType) {
2639
3065
  case 'i8': return [ val % 256 ];
2640
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2641
-
2642
- case 'i32':
2643
- case 'i64':
2644
- return enforceFourBytes(signedLEB128(val));
3066
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3067
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3068
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3069
+ // todo: i64
2645
3070
 
2646
3071
  case 'f64': return ieee754_binary64(val);
2647
3072
  }
@@ -2659,16 +3084,20 @@ const getAllocType = itemType => {
2659
3084
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2660
3085
  const out = [];
2661
3086
 
3087
+ scope.arrays ??= new Map();
3088
+
2662
3089
  let firstAssign = false;
2663
- if (!arrays.has(name) || name === '$undeclared') {
3090
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2664
3091
  firstAssign = true;
2665
3092
 
2666
3093
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2667
3094
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2668
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3095
+
3096
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3097
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2669
3098
  }
2670
3099
 
2671
- const pointer = arrays.get(name);
3100
+ const pointer = scope.arrays.get(name);
2672
3101
 
2673
3102
  const useRawElements = !!decl.rawElements;
2674
3103
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2676,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2676
3105
  const valtype = itemTypeToValtype[itemType];
2677
3106
  const length = elements.length;
2678
3107
 
2679
- if (firstAssign && useRawElements) {
2680
- let bytes = compileBytes(length, 'i32');
3108
+ if (firstAssign && useRawElements && !Prefs.noData) {
3109
+ // if length is 0 memory/data will just be 0000... anyway
3110
+ if (length !== 0) {
3111
+ let bytes = compileBytes(length, 'i32');
2681
3112
 
2682
- if (!initEmpty) for (let i = 0; i < length; i++) {
2683
- if (elements[i] == null) continue;
3113
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3114
+ if (elements[i] == null) continue;
2684
3115
 
2685
- bytes.push(...compileBytes(elements[i], itemType));
2686
- }
3116
+ bytes.push(...compileBytes(elements[i], itemType));
3117
+ }
2687
3118
 
2688
- data.push({
2689
- offset: pointer,
2690
- bytes
2691
- });
3119
+ const ind = data.push({
3120
+ offset: pointer,
3121
+ bytes
3122
+ }) - 1;
3123
+
3124
+ scope.data ??= [];
3125
+ scope.data.push(ind);
3126
+ }
2692
3127
 
2693
3128
  // local value as pointer
2694
3129
  out.push(...number(pointer));
@@ -2722,7 +3157,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2722
3157
  };
2723
3158
 
2724
3159
  const byteStringable = str => {
2725
- if (!process.argv.includes('-bytestring')) return false;
3160
+ if (!Prefs.bytestring) return false;
2726
3161
 
2727
3162
  for (let i = 0; i < str.length; i++) {
2728
3163
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2731,9 +3166,9 @@ const byteStringable = str => {
2731
3166
  return true;
2732
3167
  };
2733
3168
 
2734
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3169
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2735
3170
  const rawElements = new Array(str.length);
2736
- let byteStringable = process.argv.includes('-bytestring');
3171
+ let byteStringable = Prefs.bytestring;
2737
3172
  for (let i = 0; i < str.length; i++) {
2738
3173
  const c = str.charCodeAt(i);
2739
3174
  rawElements[i] = c;
@@ -2741,25 +3176,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2741
3176
  if (byteStringable && c > 0xFF) byteStringable = false;
2742
3177
  }
2743
3178
 
3179
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3180
+
2744
3181
  return makeArray(scope, {
2745
3182
  rawElements
2746
3183
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2747
3184
  };
2748
3185
 
2749
- let arrays = new Map();
2750
3186
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2751
3187
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2752
3188
  };
2753
3189
 
2754
3190
  export const generateMember = (scope, decl, _global, _name) => {
2755
3191
  const name = decl.object.name;
2756
- const pointer = arrays.get(name);
3192
+ const pointer = scope.arrays?.get(name);
2757
3193
 
2758
- const aotPointer = pointer != null;
3194
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2759
3195
 
2760
3196
  // hack: .length
2761
3197
  if (decl.property.name === 'length') {
2762
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3198
+ const func = funcs.find(x => x.name === name);
3199
+ if (func) {
3200
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3201
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3202
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3203
+ }
3204
+
3205
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3206
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3207
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3208
+
2763
3209
  return [
2764
3210
  ...(aotPointer ? number(0, Valtype.i32) : [
2765
3211
  ...generate(scope, decl.object),
@@ -2771,10 +3217,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2771
3217
  ];
2772
3218
  }
2773
3219
 
3220
+ const object = generate(scope, decl.object);
3221
+ const property = generate(scope, decl.property);
3222
+
2774
3223
  // // todo: we should only do this for strings but we don't know at compile-time :(
2775
3224
  // hack: this is naughty and will break things!
2776
3225
  let newOut = number(0, valtypeBinary), newPointer = -1;
2777
- if (pages.hasString) {
3226
+ if (pages.hasAnyString) {
2778
3227
  0, [ newOut, newPointer ] = makeArray(scope, {
2779
3228
  rawElements: new Array(1)
2780
3229
  }, _global, _name, true, 'i16');
@@ -2783,7 +3232,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2783
3232
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2784
3233
  [TYPES._array]: [
2785
3234
  // get index as valtype
2786
- ...generate(scope, decl.property),
3235
+ ...property,
2787
3236
 
2788
3237
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2789
3238
  Opcodes.i32_to_u,
@@ -2791,7 +3240,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2791
3240
  [ Opcodes.i32_mul ],
2792
3241
 
2793
3242
  ...(aotPointer ? [] : [
2794
- ...generate(scope, decl.object),
3243
+ ...object,
2795
3244
  Opcodes.i32_to_u,
2796
3245
  [ Opcodes.i32_add ]
2797
3246
  ]),
@@ -2800,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2800
3249
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2801
3250
 
2802
3251
  ...number(TYPES.number, Valtype.i32),
2803
- setLastType(scope)
3252
+ ...setLastType(scope)
2804
3253
  ],
2805
3254
 
2806
3255
  [TYPES.string]: [
@@ -2810,14 +3259,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2810
3259
 
2811
3260
  ...number(0, Valtype.i32), // base 0 for store later
2812
3261
 
2813
- ...generate(scope, decl.property),
2814
-
3262
+ ...property,
2815
3263
  Opcodes.i32_to_u,
3264
+
2816
3265
  ...number(ValtypeSize.i16, Valtype.i32),
2817
3266
  [ Opcodes.i32_mul ],
2818
3267
 
2819
3268
  ...(aotPointer ? [] : [
2820
- ...generate(scope, decl.object),
3269
+ ...object,
2821
3270
  Opcodes.i32_to_u,
2822
3271
  [ Opcodes.i32_add ]
2823
3272
  ]),
@@ -2832,10 +3281,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2832
3281
  ...number(newPointer),
2833
3282
 
2834
3283
  ...number(TYPES.string, Valtype.i32),
2835
- setLastType(scope)
3284
+ ...setLastType(scope)
3285
+ ],
3286
+ [TYPES._bytestring]: [
3287
+ // setup new/out array
3288
+ ...newOut,
3289
+ [ Opcodes.drop ],
3290
+
3291
+ ...number(0, Valtype.i32), // base 0 for store later
3292
+
3293
+ ...property,
3294
+ Opcodes.i32_to_u,
3295
+
3296
+ ...(aotPointer ? [] : [
3297
+ ...object,
3298
+ Opcodes.i32_to_u,
3299
+ [ Opcodes.i32_add ]
3300
+ ]),
3301
+
3302
+ // load current string ind {arg}
3303
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3304
+
3305
+ // store to new string ind 0
3306
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3307
+
3308
+ // return new string (page)
3309
+ ...number(newPointer),
3310
+
3311
+ ...number(TYPES._bytestring, Valtype.i32),
3312
+ ...setLastType(scope)
2836
3313
  ],
2837
3314
 
2838
- default: [ [ Opcodes.unreachable ] ]
3315
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2839
3316
  });
2840
3317
  };
2841
3318
 
@@ -2845,25 +3322,36 @@ const objectHack = node => {
2845
3322
  if (!node) return node;
2846
3323
 
2847
3324
  if (node.type === 'MemberExpression') {
2848
- if (node.computed || node.optional) return node;
3325
+ const out = (() => {
3326
+ if (node.computed || node.optional) return;
2849
3327
 
2850
- let objectName = node.object.name;
3328
+ let objectName = node.object.name;
2851
3329
 
2852
- // if object is not identifier or another member exp, give up
2853
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3330
+ // if object is not identifier or another member exp, give up
3331
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3332
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2854
3333
 
2855
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3334
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2856
3335
 
2857
- // if .length, give up (hack within a hack!)
2858
- if (node.property.name === 'length') return node;
3336
+ // if .length, give up (hack within a hack!)
3337
+ if (node.property.name === 'length') {
3338
+ node.object = objectHack(node.object);
3339
+ return;
3340
+ }
2859
3341
 
2860
- const name = '__' + objectName + '_' + node.property.name;
2861
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3342
+ // no object name, give up
3343
+ if (!objectName) return;
2862
3344
 
2863
- return {
2864
- type: 'Identifier',
2865
- name
2866
- };
3345
+ const name = '__' + objectName + '_' + node.property.name;
3346
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3347
+
3348
+ return {
3349
+ type: 'Identifier',
3350
+ name
3351
+ };
3352
+ })();
3353
+
3354
+ if (out) return out;
2867
3355
  }
2868
3356
 
2869
3357
  for (const x in node) {
@@ -2877,8 +3365,8 @@ const objectHack = node => {
2877
3365
  };
2878
3366
 
2879
3367
  const generateFunc = (scope, decl) => {
2880
- if (decl.async) return todo('async functions are not supported');
2881
- if (decl.generator) return todo('generator functions are not supported');
3368
+ if (decl.async) return todo(scope, 'async functions are not supported');
3369
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2882
3370
 
2883
3371
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2884
3372
  const params = decl.params ?? [];
@@ -2894,6 +3382,11 @@ const generateFunc = (scope, decl) => {
2894
3382
  name
2895
3383
  };
2896
3384
 
3385
+ if (typedInput && decl.returnType) {
3386
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3387
+ innerScope.returns = [ valtypeBinary ];
3388
+ }
3389
+
2897
3390
  for (let i = 0; i < params.length; i++) {
2898
3391
  allocVar(innerScope, params[i].name, false);
2899
3392
 
@@ -2915,13 +3408,13 @@ const generateFunc = (scope, decl) => {
2915
3408
  const func = {
2916
3409
  name,
2917
3410
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2918
- returns: innerScope.returns,
2919
- locals: innerScope.locals,
2920
- throws: innerScope.throws,
2921
- index: currentFuncIndex++
3411
+ index: currentFuncIndex++,
3412
+ ...innerScope
2922
3413
  };
2923
3414
  funcIndex[name] = func.index;
2924
3415
 
3416
+ if (name === 'main') func.gotLastType = true;
3417
+
2925
3418
  // quick hack fixes
2926
3419
  for (const inst of wasm) {
2927
3420
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2973,7 +3466,7 @@ const internalConstrs = {
2973
3466
 
2974
3467
  // todo: check in wasm instead of here
2975
3468
  const literalValue = arg.value ?? 0;
2976
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3469
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2977
3470
 
2978
3471
  return [
2979
3472
  ...number(0, Valtype.i32),
@@ -2984,7 +3477,8 @@ const internalConstrs = {
2984
3477
  ...number(pointer)
2985
3478
  ];
2986
3479
  },
2987
- type: TYPES._array
3480
+ type: TYPES._array,
3481
+ length: 1
2988
3482
  },
2989
3483
 
2990
3484
  __Array_of: {
@@ -2996,7 +3490,131 @@ const internalConstrs = {
2996
3490
  }, global, name);
2997
3491
  },
2998
3492
  type: TYPES._array,
3493
+ notConstr: true,
3494
+ length: 0
3495
+ },
3496
+
3497
+ __Porffor_fastOr: {
3498
+ generate: (scope, decl) => {
3499
+ const out = [];
3500
+
3501
+ for (let i = 0; i < decl.arguments.length; i++) {
3502
+ out.push(
3503
+ ...generate(scope, decl.arguments[i]),
3504
+ Opcodes.i32_to_u,
3505
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3506
+ );
3507
+ }
3508
+
3509
+ out.push(Opcodes.i32_from_u);
3510
+
3511
+ return out;
3512
+ },
3513
+ type: TYPES.boolean,
2999
3514
  notConstr: true
3515
+ },
3516
+
3517
+ __Porffor_fastAnd: {
3518
+ generate: (scope, decl) => {
3519
+ const out = [];
3520
+
3521
+ for (let i = 0; i < decl.arguments.length; i++) {
3522
+ out.push(
3523
+ ...generate(scope, decl.arguments[i]),
3524
+ Opcodes.i32_to_u,
3525
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3526
+ );
3527
+ }
3528
+
3529
+ out.push(Opcodes.i32_from_u);
3530
+
3531
+ return out;
3532
+ },
3533
+ type: TYPES.boolean,
3534
+ notConstr: true
3535
+ },
3536
+
3537
+ Boolean: {
3538
+ generate: (scope, decl) => {
3539
+ // todo: boolean object when used as constructor
3540
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3541
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3542
+ },
3543
+ type: TYPES.boolean,
3544
+ length: 1
3545
+ },
3546
+
3547
+ __Math_max: {
3548
+ generate: (scope, decl) => {
3549
+ const out = [
3550
+ ...number(-Infinity)
3551
+ ];
3552
+
3553
+ for (let i = 0; i < decl.arguments.length; i++) {
3554
+ out.push(
3555
+ ...generate(scope, decl.arguments[i]),
3556
+ [ Opcodes.f64_max ]
3557
+ );
3558
+ }
3559
+
3560
+ return out;
3561
+ },
3562
+ type: TYPES.number,
3563
+ notConstr: true,
3564
+ length: 2
3565
+ },
3566
+
3567
+ __Math_min: {
3568
+ generate: (scope, decl) => {
3569
+ const out = [
3570
+ ...number(Infinity)
3571
+ ];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ [ Opcodes.f64_min ]
3577
+ );
3578
+ }
3579
+
3580
+ return out;
3581
+ },
3582
+ type: TYPES.number,
3583
+ notConstr: true,
3584
+ length: 2
3585
+ },
3586
+
3587
+ __console_log: {
3588
+ generate: (scope, decl) => {
3589
+ const out = [];
3590
+
3591
+ for (let i = 0; i < decl.arguments.length; i++) {
3592
+ out.push(
3593
+ ...generateCall(scope, {
3594
+ callee: {
3595
+ type: 'Identifier',
3596
+ name: '__Porffor_print'
3597
+ },
3598
+ arguments: [ decl.arguments[i] ]
3599
+ }),
3600
+
3601
+ // print space
3602
+ ...number(32),
3603
+ [ Opcodes.call, importedFuncs.printChar ]
3604
+ );
3605
+ }
3606
+
3607
+ // print newline
3608
+ out.push(
3609
+ ...number(10),
3610
+ [ Opcodes.call, importedFuncs.printChar ]
3611
+ );
3612
+
3613
+ return out;
3614
+ },
3615
+ type: TYPES.undefined,
3616
+ notConstr: true,
3617
+ length: 0
3000
3618
  }
3001
3619
  };
3002
3620
 
@@ -3025,7 +3643,6 @@ export default program => {
3025
3643
  funcs = [];
3026
3644
  funcIndex = {};
3027
3645
  depth = [];
3028
- arrays = new Map();
3029
3646
  pages = new Map();
3030
3647
  data = [];
3031
3648
  currentFuncIndex = importedFuncs.length;
@@ -3039,6 +3656,10 @@ export default program => {
3039
3656
 
3040
3657
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3041
3658
 
3659
+ globalThis.pageSize = PageSize;
3660
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3661
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3662
+
3042
3663
  // set generic opcodes for current valtype
3043
3664
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3044
3665
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3047,10 +3668,10 @@ export default program => {
3047
3668
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3048
3669
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3049
3670
 
3050
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3051
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3052
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3053
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3671
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3672
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3673
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3674
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3054
3675
 
3055
3676
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3056
3677
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3063,10 +3684,6 @@ export default program => {
3063
3684
 
3064
3685
  program.id = { name: 'main' };
3065
3686
 
3066
- globalThis.pageSize = PageSize;
3067
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3068
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3069
-
3070
3687
  const scope = {
3071
3688
  locals: {},
3072
3689
  localInd: 0
@@ -3077,7 +3694,7 @@ export default program => {
3077
3694
  body: program.body
3078
3695
  };
3079
3696
 
3080
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3697
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3081
3698
 
3082
3699
  generateFunc(scope, program);
3083
3700
 
@@ -3094,7 +3711,11 @@ export default program => {
3094
3711
  }
3095
3712
 
3096
3713
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3097
- main.returns = [];
3714
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3715
+ main.wasm.splice(main.wasm.length - 1, 1);
3716
+ } else {
3717
+ main.returns = [];
3718
+ }
3098
3719
  }
3099
3720
 
3100
3721
  if (lastInst[0] === Opcodes.call) {