porffor 0.2.0-a759814 → 0.2.0-ae8cbb8

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 (54) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +156 -87
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +317 -72
  8. package/compiler/{sections.js → assemble.js} +64 -16
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/boolean.ts +6 -0
  14. package/compiler/builtins/crypto.ts +120 -0
  15. package/compiler/builtins/date.ts +2070 -0
  16. package/compiler/builtins/escape.ts +139 -0
  17. package/compiler/builtins/int.ts +147 -0
  18. package/compiler/builtins/number.ts +534 -0
  19. package/compiler/builtins/porffor.d.ts +59 -0
  20. package/compiler/builtins/string.ts +1070 -0
  21. package/compiler/builtins/tostring.ts +45 -0
  22. package/compiler/builtins.js +580 -272
  23. package/compiler/{codeGen.js → codegen.js} +1297 -434
  24. package/compiler/decompile.js +3 -4
  25. package/compiler/embedding.js +22 -22
  26. package/compiler/encoding.js +108 -10
  27. package/compiler/generated_builtins.js +1517 -0
  28. package/compiler/index.js +36 -34
  29. package/compiler/log.js +6 -3
  30. package/compiler/opt.js +56 -30
  31. package/compiler/parse.js +33 -23
  32. package/compiler/precompile.js +128 -0
  33. package/compiler/prefs.js +27 -0
  34. package/compiler/prototype.js +182 -42
  35. package/compiler/types.js +37 -0
  36. package/compiler/wasmSpec.js +31 -7
  37. package/compiler/wrap.js +141 -43
  38. package/package.json +9 -5
  39. package/porf +4 -0
  40. package/rhemyn/compile.js +46 -27
  41. package/rhemyn/parse.js +322 -320
  42. package/rhemyn/test/parse.js +58 -58
  43. package/runner/compare.js +34 -34
  44. package/runner/debug.js +122 -0
  45. package/runner/index.js +91 -11
  46. package/runner/profiler.js +102 -0
  47. package/runner/repl.js +42 -9
  48. package/runner/sizes.js +37 -37
  49. package/compiler/builtins/base64.js +0 -92
  50. package/runner/info.js +0 -89
  51. package/runner/profile.js +0 -46
  52. package/runner/results.json +0 -1
  53. package/runner/transform.js +0 -15
  54. package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -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,39 +25,45 @@ 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';
58
- const generate = (scope, decl, global = false, name = undefined) => {
62
+ const hasFuncWithName = name => {
63
+ const func = funcs.find(x => x.name === name);
64
+ return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name])
65
+ };
66
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
67
  switch (decl.type) {
60
68
  case 'BinaryExpression':
61
69
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +76,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
76
 
69
77
  case 'ArrowFunctionExpression':
70
78
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
79
+ const func = generateFunc(scope, decl);
80
+
81
+ if (decl.type.endsWith('Expression')) {
82
+ return number(func.index);
83
+ }
84
+
72
85
  return [];
73
86
 
74
87
  case 'BlockStatement':
@@ -81,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
94
  return generateExp(scope, decl);
82
95
 
83
96
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
97
+ return generateCall(scope, decl, global, name, valueUnused);
85
98
 
86
99
  case 'NewExpression':
87
100
  return generateNew(scope, decl, global, name);
@@ -99,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
112
  return generateUnary(scope, decl);
100
113
 
101
114
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
115
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
116
 
104
117
  case 'IfStatement':
105
118
  return generateIf(scope, decl);
@@ -110,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
110
123
  case 'WhileStatement':
111
124
  return generateWhile(scope, decl);
112
125
 
126
+ case 'DoWhileStatement':
127
+ return generateDoWhile(scope, decl);
128
+
113
129
  case 'ForOfStatement':
114
130
  return generateForOf(scope, decl);
115
131
 
@@ -119,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
119
135
  case 'ContinueStatement':
120
136
  return generateContinue(scope, decl);
121
137
 
138
+ case 'LabeledStatement':
139
+ return generateLabel(scope, decl);
140
+
122
141
  case 'EmptyStatement':
123
142
  return generateEmpty(scope, decl);
124
143
 
@@ -132,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
132
151
  return generateTry(scope, decl);
133
152
 
134
153
  case 'DebuggerStatement':
135
- // todo: add fancy terminal debugger?
154
+ // todo: hook into terminal debugger
136
155
  return [];
137
156
 
138
157
  case 'ArrayExpression':
@@ -146,16 +165,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
146
165
  const funcsBefore = funcs.length;
147
166
  generate(scope, decl.declaration);
148
167
 
149
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
168
+ if (funcsBefore !== funcs.length) {
169
+ // new func added
170
+ const newFunc = funcs[funcs.length - 1];
171
+ newFunc.export = true;
172
+ }
173
+
174
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
150
175
 
151
- const newFunc = funcs[funcs.length - 1];
152
- newFunc.export = true;
176
+ // const newFunc = funcs[funcs.length - 1];
177
+ // newFunc.export = true;
153
178
 
154
179
  return [];
155
180
 
156
181
  case 'TaggedTemplateExpression': {
157
182
  const funcs = {
158
- asm: str => {
183
+ __Porffor_wasm: str => {
159
184
  let out = [];
160
185
 
161
186
  for (const line of str.split('\n')) {
@@ -163,8 +188,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
188
  if (asm[0] === '') continue; // blank
164
189
 
165
190
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
191
+ const [ name, type ] = asm.slice(1);
192
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
193
  continue;
169
194
  }
170
195
 
@@ -174,52 +199,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
199
  }
175
200
 
176
201
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
202
+ allocPage(scope, 'asm instrinsic');
178
203
  // todo: add to store/load offset insts
179
204
  continue;
180
205
  }
181
206
 
182
207
  let inst = Opcodes[asm[0].replace('.', '_')];
183
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
208
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
209
 
185
210
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
211
+ const immediates = asm.slice(1).map(x => {
212
+ const int = parseInt(x);
213
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
214
+ return int;
215
+ });
187
216
 
188
- out.push([ ...inst, ...immediates ]);
217
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
189
218
  }
190
219
 
191
220
  return out;
192
221
  },
193
222
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
223
+ __Porffor_bs: str => [
224
+ ...makeString(scope, str, global, name, true),
196
225
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
226
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
227
+ ...number(TYPES.bytestring, Valtype.i32),
228
+ ...setLastType(scope)
229
+ ])
230
+ ],
231
+ __Porffor_s: str => [
232
+ ...makeString(scope, str, global, name, false),
200
233
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
234
+ ...(name ? setType(scope, name, TYPES.string) : [
235
+ ...number(TYPES.string, Valtype.i32),
236
+ ...setLastType(scope)
237
+ ])
238
+ ],
239
+ };
207
240
 
208
- const name = decl.tag.name;
241
+ const func = decl.tag.name;
209
242
  // hack for inline asm
210
- if (!funcs[name]) return todo('tagged template expressions not implemented');
243
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
244
+
245
+ const { quasis, expressions } = decl.quasi;
246
+ let str = quasis[0].value.raw;
247
+
248
+ for (let i = 0; i < expressions.length; i++) {
249
+ const e = expressions[i];
250
+ if (!e.name) {
251
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
252
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
253
+ } else todo(scope, 'unsupported expression in intrinsic');
254
+ } else str += lookupName(scope, e.name)[0].idx;
255
+
256
+ str += quasis[i + 1].value.raw;
257
+ }
211
258
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
259
+ return funcs[func](str);
214
260
  }
215
261
 
216
262
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
263
+ // ignore typescript nodes
264
+ if (decl.type.startsWith('TS') ||
265
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
266
  return [];
220
267
  }
221
268
 
222
- return todo(`no generation for ${decl.type}!`);
269
+ return todo(scope, `no generation for ${decl.type}!`);
223
270
  }
224
271
  };
225
272
 
@@ -247,7 +294,7 @@ const lookupName = (scope, _name) => {
247
294
  return [ undefined, undefined ];
248
295
  };
249
296
 
250
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
297
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
251
298
  ...generateThrow(scope, {
252
299
  argument: {
253
300
  type: 'NewExpression',
@@ -269,25 +316,33 @@ const generateIdent = (scope, decl) => {
269
316
  const name = mapName(rawName);
270
317
  let local = scope.locals[rawName];
271
318
 
272
- if (builtinVars[name]) {
319
+ if (Object.hasOwn(builtinVars, name)) {
273
320
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
321
+
322
+ let wasm = builtinVars[name];
323
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
324
+ return wasm.slice();
325
+ }
326
+
327
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
328
+ // todo: return an actual something
329
+ return number(1);
275
330
  }
276
331
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
332
+ if (isExistingProtoFunc(name)) {
278
333
  // todo: return an actual something
279
334
  return number(1);
280
335
  }
281
336
 
282
- if (local === undefined) {
337
+ if (local?.idx === undefined) {
283
338
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
339
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
340
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
341
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
342
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
343
  }
289
344
 
290
- if (local === undefined && rawName.startsWith('__')) {
345
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
346
  // return undefined if unknown key in already known var
292
347
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
348
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +351,7 @@ const generateIdent = (scope, decl) => {
296
351
  if (!parentLookup[1]) return number(UNDEFINED);
297
352
  }
298
353
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
354
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
355
 
301
356
  return [ [ Opcodes.local_get, local.idx ] ];
302
357
  };
@@ -309,14 +364,18 @@ const generateReturn = (scope, decl) => {
309
364
  // just bare "return"
310
365
  return [
311
366
  ...number(UNDEFINED), // "undefined" if func returns
312
- ...number(TYPES.undefined, Valtype.i32), // type undefined
367
+ ...(scope.returnType != null ? [] : [
368
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
369
+ ]),
313
370
  [ Opcodes.return ]
314
371
  ];
315
372
  }
316
373
 
317
374
  return [
318
375
  ...generate(scope, decl.argument),
319
- ...getNodeType(scope, decl.argument),
376
+ ...(scope.returnType != null ? [] : [
377
+ ...getNodeType(scope, decl.argument)
378
+ ]),
320
379
  [ Opcodes.return ]
321
380
  ];
322
381
  };
@@ -330,7 +389,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
330
389
  return idx;
331
390
  };
332
391
 
333
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
392
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
393
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
334
394
 
335
395
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
336
396
  const checks = {
@@ -339,7 +399,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
339
399
  '??': nullish
340
400
  };
341
401
 
342
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
402
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
343
403
 
344
404
  // generic structure for {a} OP {b}
345
405
  // -->
@@ -347,8 +407,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
347
407
 
348
408
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
349
409
  // (like if we are in an if condition - very common)
350
- const leftIsInt = isIntOp(left[left.length - 1]);
351
- const rightIsInt = isIntOp(right[right.length - 1]);
410
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
411
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
352
412
 
353
413
  const canInt = leftIsInt && rightIsInt;
354
414
 
@@ -365,12 +425,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
365
425
  ...right,
366
426
  // note type
367
427
  ...rightType,
368
- setLastType(scope),
428
+ ...setLastType(scope),
369
429
  [ Opcodes.else ],
370
430
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
371
431
  // note type
372
432
  ...leftType,
373
- setLastType(scope),
433
+ ...setLastType(scope),
374
434
  [ Opcodes.end ],
375
435
  Opcodes.i32_from
376
436
  ];
@@ -384,17 +444,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
444
  ...right,
385
445
  // note type
386
446
  ...rightType,
387
- setLastType(scope),
447
+ ...setLastType(scope),
388
448
  [ Opcodes.else ],
389
449
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
390
450
  // note type
391
451
  ...leftType,
392
- setLastType(scope),
452
+ ...setLastType(scope),
393
453
  [ Opcodes.end ]
394
454
  ];
395
455
  };
396
456
 
397
- const concatStrings = (scope, left, right, global, name, assign) => {
457
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
398
458
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
399
459
  // todo: convert left and right to strings if not
400
460
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -404,11 +464,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
464
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
465
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
466
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
- if (assign) {
411
- const pointer = arrays.get(name ?? '$undeclared');
467
+ if (assign && Prefs.aotPointerOpt) {
468
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
412
469
 
413
470
  return [
414
471
  // setup right
@@ -433,11 +490,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
433
490
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
434
491
 
435
492
  // copy right
436
- // dst = out pointer + length size + current length * i16 size
493
+ // dst = out pointer + length size + current length * sizeof valtype
437
494
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
438
495
 
439
496
  [ Opcodes.local_get, leftLength ],
440
- ...number(ValtypeSize.i16, Valtype.i32),
497
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
441
498
  [ Opcodes.i32_mul ],
442
499
  [ Opcodes.i32_add ],
443
500
 
@@ -446,9 +503,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
446
503
  ...number(ValtypeSize.i32, Valtype.i32),
447
504
  [ Opcodes.i32_add ],
448
505
 
449
- // size = right length * i16 size
506
+ // size = right length * sizeof valtype
450
507
  [ Opcodes.local_get, rightLength ],
451
- ...number(ValtypeSize.i16, Valtype.i32),
508
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
452
509
  [ Opcodes.i32_mul ],
453
510
 
454
511
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -506,11 +563,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
506
563
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
507
564
 
508
565
  // copy right
509
- // dst = out pointer + length size + left length * i16 size
566
+ // dst = out pointer + length size + left length * sizeof valtype
510
567
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
511
568
 
512
569
  [ Opcodes.local_get, leftLength ],
513
- ...number(ValtypeSize.i16, Valtype.i32),
570
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
514
571
  [ Opcodes.i32_mul ],
515
572
  [ Opcodes.i32_add ],
516
573
 
@@ -519,9 +576,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
519
576
  ...number(ValtypeSize.i32, Valtype.i32),
520
577
  [ Opcodes.i32_add ],
521
578
 
522
- // size = right length * i16 size
579
+ // size = right length * sizeof valtype
523
580
  [ Opcodes.local_get, rightLength ],
524
- ...number(ValtypeSize.i16, Valtype.i32),
581
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
525
582
  [ Opcodes.i32_mul ],
526
583
 
527
584
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -531,7 +588,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
531
588
  ];
532
589
  };
533
590
 
534
- const compareStrings = (scope, left, right) => {
591
+ const compareStrings = (scope, left, right, bytestrings = false) => {
535
592
  // todo: this should be rewritten into a func
536
593
  // todo: convert left and right to strings if not
537
594
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -540,7 +597,6 @@ const compareStrings = (scope, left, right) => {
540
597
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
541
598
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
542
599
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
543
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
544
600
 
545
601
  const index = localTmp(scope, 'compare_index', Valtype.i32);
546
602
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -568,7 +624,6 @@ const compareStrings = (scope, left, right) => {
568
624
 
569
625
  [ Opcodes.local_get, rightPointer ],
570
626
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
571
- [ Opcodes.local_tee, rightLength ],
572
627
 
573
628
  // fast path: check leftLength != rightLength
574
629
  [ Opcodes.i32_ne ],
@@ -583,11 +638,13 @@ const compareStrings = (scope, left, right) => {
583
638
  ...number(0, Valtype.i32),
584
639
  [ Opcodes.local_set, index ],
585
640
 
586
- // setup index end as length * sizeof i16 (2)
641
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
587
642
  // we do this instead of having to do mul/div each iter for perf™
588
643
  [ Opcodes.local_get, leftLength ],
589
- ...number(ValtypeSize.i16, Valtype.i32),
590
- [ Opcodes.i32_mul ],
644
+ ...(bytestrings ? [] : [
645
+ ...number(ValtypeSize.i16, Valtype.i32),
646
+ [ Opcodes.i32_mul ],
647
+ ]),
591
648
  [ Opcodes.local_set, indexEnd ],
592
649
 
593
650
  // iterate over each char and check if eq
@@ -597,13 +654,17 @@ const compareStrings = (scope, left, right) => {
597
654
  [ Opcodes.local_get, index ],
598
655
  [ Opcodes.local_get, leftPointer ],
599
656
  [ Opcodes.i32_add ],
600
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
657
+ bytestrings ?
658
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
659
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
601
660
 
602
661
  // fetch right
603
662
  [ Opcodes.local_get, index ],
604
663
  [ Opcodes.local_get, rightPointer ],
605
664
  [ Opcodes.i32_add ],
606
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
665
+ bytestrings ?
666
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
667
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
607
668
 
608
669
  // not equal, "return" false
609
670
  [ Opcodes.i32_ne ],
@@ -612,13 +673,13 @@ const compareStrings = (scope, left, right) => {
612
673
  [ Opcodes.br, 2 ],
613
674
  [ Opcodes.end ],
614
675
 
615
- // index += sizeof i16 (2)
676
+ // index += sizeof valtype (1 for bytestring, 2 for string)
616
677
  [ Opcodes.local_get, index ],
617
- ...number(ValtypeSize.i16, Valtype.i32),
678
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
618
679
  [ Opcodes.i32_add ],
619
680
  [ Opcodes.local_tee, index ],
620
681
 
621
- // if index != index end (length * sizeof 16), loop
682
+ // if index != index end (length * sizeof valtype), loop
622
683
  [ Opcodes.local_get, indexEnd ],
623
684
  [ Opcodes.i32_ne ],
624
685
  [ Opcodes.br_if, 0 ],
@@ -639,16 +700,18 @@ const compareStrings = (scope, left, right) => {
639
700
  };
640
701
 
641
702
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
642
- if (isIntOp(wasm[wasm.length - 1])) return [
703
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
643
704
  ...wasm,
644
705
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
706
  ];
707
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
646
708
 
647
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
709
+ const useTmp = knownType(scope, type) == null;
710
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
648
711
 
649
712
  const def = [
650
713
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
714
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
715
 
653
716
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
717
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,16 +723,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
723
 
661
724
  return [
662
725
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
726
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
727
 
665
728
  ...typeSwitch(scope, type, {
666
729
  // [TYPES.number]: def,
667
- [TYPES._array]: [
730
+ [TYPES.array]: [
668
731
  // arrays are always truthy
669
732
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
733
  ],
671
734
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
735
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
736
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
737
 
675
738
  // get length
@@ -680,24 +743,46 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
743
  [ Opcodes.i32_eqz ], */
681
744
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
745
  ],
746
+ [TYPES.bytestring]: [ // duplicate of string
747
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
748
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
749
+
750
+ // get length
751
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
752
+
753
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
754
+ ],
683
755
  default: def
684
756
  }, intOut ? Valtype.i32 : valtypeBinary)
685
757
  ];
686
758
  };
687
759
 
688
760
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
761
+ const useTmp = knownType(scope, type) == null;
762
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
763
+
690
764
  return [
691
765
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
766
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
767
 
694
768
  ...typeSwitch(scope, type, {
695
- [TYPES._array]: [
769
+ [TYPES.array]: [
696
770
  // arrays are always truthy
697
771
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
772
  ],
699
773
  [TYPES.string]: [
700
- [ Opcodes.local_get, tmp ],
774
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
775
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
776
+
777
+ // get length
778
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
779
+
780
+ // if length == 0
781
+ [ Opcodes.i32_eqz ],
782
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
783
+ ],
784
+ [TYPES.bytestring]: [ // duplicate of string
785
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
701
786
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
787
 
703
788
  // get length
@@ -709,7 +794,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
794
  ],
710
795
  default: [
711
796
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
797
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
798
 
714
799
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
800
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +804,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
804
  };
720
805
 
721
806
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
807
+ const useTmp = knownType(scope, type) == null;
808
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
809
+
723
810
  return [
724
811
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
812
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
813
 
727
814
  ...typeSwitch(scope, type, {
728
815
  [TYPES.undefined]: [
@@ -731,7 +818,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
818
  ],
732
819
  [TYPES.object]: [
733
820
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
821
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
822
 
736
823
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
824
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +847,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
847
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
848
  }
762
849
 
850
+ const knownLeft = knownType(scope, leftType);
851
+ const knownRight = knownType(scope, rightType);
852
+
763
853
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
854
  const strictOp = op === '===' || op === '!==';
765
855
 
766
856
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
857
+ const finalize = out => startOut.concat(out, endOut);
768
858
 
769
859
  // if strict (in)equal check types match
770
860
  if (strictOp) {
@@ -809,31 +899,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
899
  // todo: if equality op and an operand is undefined, return false
810
900
  // todo: niche null hell with 0
811
901
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
902
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
903
+ if (op === '+') {
904
+ // todo: this should be dynamic too but for now only static
905
+ // string concat (a + b)
906
+ return concatStrings(scope, left, right, _global, _name, assign, false);
907
+ }
908
+
909
+ // not an equality op, NaN
910
+ if (!eqOp) return number(NaN);
911
+
912
+ // else leave bool ops
913
+ // todo: convert string to number if string and number/bool
914
+ // todo: string (>|>=|<|<=) string
915
+
916
+ // string comparison
917
+ if (op === '===' || op === '==') {
918
+ return compareStrings(scope, left, right);
919
+ }
920
+
921
+ if (op === '!==' || op === '!=') {
922
+ return [
923
+ ...compareStrings(scope, left, right),
924
+ [ Opcodes.i32_eqz ]
925
+ ];
926
+ }
927
+ }
928
+
929
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
930
+ if (op === '+') {
931
+ // todo: this should be dynamic too but for now only static
932
+ // string concat (a + b)
933
+ return concatStrings(scope, left, right, _global, _name, assign, true);
934
+ }
935
+
936
+ // not an equality op, NaN
937
+ if (!eqOp) return number(NaN);
938
+
939
+ // else leave bool ops
940
+ // todo: convert string to number if string and number/bool
941
+ // todo: string (>|>=|<|<=) string
942
+
943
+ // string comparison
944
+ if (op === '===' || op === '==') {
945
+ return compareStrings(scope, left, right, true);
946
+ }
947
+
948
+ if (op === '!==' || op === '!=') {
949
+ return [
950
+ ...compareStrings(scope, left, right, true),
951
+ [ Opcodes.i32_eqz ]
952
+ ];
953
+ }
954
+ }
837
955
 
838
956
  let ops = operatorOpcode[valtype][op];
839
957
 
@@ -843,33 +961,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
961
  includeBuiltin(scope, builtinName);
844
962
  const idx = funcIndex[builtinName];
845
963
 
846
- return finalise([
964
+ return finalize([
847
965
  ...left,
848
966
  ...right,
849
967
  [ Opcodes.call, idx ]
850
968
  ]);
851
969
  }
852
970
 
853
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
971
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
854
972
 
855
973
  if (!Array.isArray(ops)) ops = [ ops ];
856
974
  ops = [ ops ];
857
975
 
858
976
  let tmpLeft, tmpRight;
859
977
  // if equal op, check if strings for compareStrings
860
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
- // todo: intelligent partial skip later
865
- // if neither known are string, stop this madness
866
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
867
- return;
868
- }
978
+ // todo: intelligent partial skip later
979
+ // if neither known are string, stop this madness
980
+ // we already do known checks earlier, so don't need to recheck
869
981
 
982
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
870
983
  tmpLeft = localTmp(scope, '__tmpop_left');
871
984
  tmpRight = localTmp(scope, '__tmpop_right');
872
985
 
986
+ // returns false for one string, one not - but more ops/slower
987
+ // ops.unshift(...stringOnly([
988
+ // // if left is string
989
+ // ...leftType,
990
+ // ...number(TYPES.string, Valtype.i32),
991
+ // [ Opcodes.i32_eq ],
992
+
993
+ // // if right is string
994
+ // ...rightType,
995
+ // ...number(TYPES.string, Valtype.i32),
996
+ // [ Opcodes.i32_eq ],
997
+
998
+ // // if either are true
999
+ // [ Opcodes.i32_or ],
1000
+ // [ Opcodes.if, Blocktype.void ],
1001
+
1002
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
1003
+ // // if left is not string
1004
+ // ...leftType,
1005
+ // ...number(TYPES.string, Valtype.i32),
1006
+ // [ Opcodes.i32_ne ],
1007
+
1008
+ // // if right is not string
1009
+ // ...rightType,
1010
+ // ...number(TYPES.string, Valtype.i32),
1011
+ // [ Opcodes.i32_ne ],
1012
+
1013
+ // // if either are true
1014
+ // [ Opcodes.i32_or ],
1015
+ // [ Opcodes.if, Blocktype.void ],
1016
+ // ...number(0, Valtype.i32),
1017
+ // [ Opcodes.br, 2 ],
1018
+ // [ Opcodes.end ],
1019
+
1020
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1021
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1022
+ // [ Opcodes.br, 1 ],
1023
+ // [ Opcodes.end ],
1024
+ // ]));
1025
+
1026
+ // does not handle one string, one not (such cases go past)
873
1027
  ops.unshift(...stringOnly([
874
1028
  // if left is string
875
1029
  ...leftType,
@@ -881,30 +1035,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
881
1035
  ...number(TYPES.string, Valtype.i32),
882
1036
  [ Opcodes.i32_eq ],
883
1037
 
884
- // if either are true
885
- [ Opcodes.i32_or ],
1038
+ // if both are true
1039
+ [ Opcodes.i32_and ],
886
1040
  [ Opcodes.if, Blocktype.void ],
1041
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1042
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1043
+ [ Opcodes.br, 1 ],
1044
+ [ Opcodes.end ],
887
1045
 
888
- // todo: convert non-strings to strings, for now fail immediately if one is not
889
- // if left is not string
1046
+ // if left is bytestring
890
1047
  ...leftType,
891
- ...number(TYPES.string, Valtype.i32),
892
- [ Opcodes.i32_ne ],
1048
+ ...number(TYPES.bytestring, Valtype.i32),
1049
+ [ Opcodes.i32_eq ],
893
1050
 
894
- // if right is not string
1051
+ // if right is bytestring
895
1052
  ...rightType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1053
+ ...number(TYPES.bytestring, Valtype.i32),
1054
+ [ Opcodes.i32_eq ],
898
1055
 
899
- // if either are true
900
- [ Opcodes.i32_or ],
1056
+ // if both are true
1057
+ [ Opcodes.i32_and ],
901
1058
  [ Opcodes.if, Blocktype.void ],
902
- ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
904
- [ Opcodes.end ],
905
-
906
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
907
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1059
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
908
1060
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
909
1061
  [ Opcodes.br, 1 ],
910
1062
  [ Opcodes.end ],
@@ -916,9 +1068,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
916
1068
  // endOut.push(stringOnly([ Opcodes.end ]));
917
1069
  endOut.unshift(stringOnly([ Opcodes.end ]));
918
1070
  // }
919
- })();
1071
+ }
920
1072
 
921
- return finalise([
1073
+ return finalize([
922
1074
  ...left,
923
1075
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
1076
  ...right,
@@ -935,7 +1087,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
1087
  return out;
936
1088
  };
937
1089
 
938
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1090
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1091
+ return func({ name, params, locals, returns, localInd }, {
1092
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1093
+ builtin: name => {
1094
+ let idx = funcIndex[name] ?? importedFuncs[name];
1095
+ if (idx === undefined && builtinFuncs[name]) {
1096
+ includeBuiltin(null, name);
1097
+ idx = funcIndex[name];
1098
+ }
1099
+
1100
+ return idx;
1101
+ }
1102
+ });
1103
+ };
1104
+
1105
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
939
1106
  const existing = funcs.find(x => x.name === name);
940
1107
  if (existing) return existing;
941
1108
 
@@ -947,6 +1114,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1114
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1115
  }
949
1116
 
1117
+ for (const x of _data) {
1118
+ const copy = { ...x };
1119
+ copy.offset += pages.size * pageSize;
1120
+ data.push(copy);
1121
+ }
1122
+
1123
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1124
+
950
1125
  let baseGlobalIdx, i = 0;
951
1126
  for (const type of globalTypes) {
952
1127
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -969,7 +1144,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
969
1144
  params,
970
1145
  locals,
971
1146
  returns,
972
- returnType: TYPES[returnType ?? 'number'],
1147
+ returnType: returnType ?? TYPES.number,
973
1148
  wasm,
974
1149
  internal: true,
975
1150
  index: currentFuncIndex++
@@ -992,6 +1167,7 @@ const generateLogicExp = (scope, decl) => {
992
1167
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
993
1168
  };
994
1169
 
1170
+ // potential future ideas for nan boxing (unused):
995
1171
  // T = JS type, V = value/pointer
996
1172
  // 0bTTT
997
1173
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1005,7 +1181,6 @@ const generateLogicExp = (scope, decl) => {
1005
1181
  // js type: 4 bits
1006
1182
  // internal type: ? bits
1007
1183
  // pointer: 32 bits
1008
-
1009
1184
  // generic
1010
1185
  // 1 23 4 5
1011
1186
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1015,47 +1190,29 @@ const generateLogicExp = (scope, decl) => {
1015
1190
  // 4: internal type
1016
1191
  // 5: pointer
1017
1192
 
1018
- const TYPES = {
1019
- number: 0x00,
1020
- boolean: 0x01,
1021
- string: 0x02,
1022
- undefined: 0x03,
1023
- object: 0x04,
1024
- function: 0x05,
1025
- symbol: 0x06,
1026
- bigint: 0x07,
1193
+ const isExistingProtoFunc = name => {
1194
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1195
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1027
1196
 
1028
- // these are not "typeof" types but tracked internally
1029
- _array: 0x10,
1030
- _regexp: 0x11
1031
- };
1032
-
1033
- const TYPE_NAMES = {
1034
- [TYPES.number]: 'Number',
1035
- [TYPES.boolean]: 'Boolean',
1036
- [TYPES.string]: 'String',
1037
- [TYPES.undefined]: 'undefined',
1038
- [TYPES.object]: 'Object',
1039
- [TYPES.function]: 'Function',
1040
- [TYPES.symbol]: 'Symbol',
1041
- [TYPES.bigint]: 'BigInt',
1042
-
1043
- [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1197
+ return false;
1045
1198
  };
1046
1199
 
1047
1200
  const getType = (scope, _name) => {
1048
1201
  const name = mapName(_name);
1049
1202
 
1203
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1204
+
1205
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1206
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1207
+
1208
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1209
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1210
 
1053
1211
  let type = TYPES.undefined;
1054
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1212
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1055
1213
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1056
1214
 
1057
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1058
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1215
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1059
1216
 
1060
1217
  return number(type, Valtype.i32);
1061
1218
  };
@@ -1078,21 +1235,24 @@ const setType = (scope, _name, type) => {
1078
1235
  ];
1079
1236
 
1080
1237
  // throw new Error('could not find var');
1238
+ return [];
1081
1239
  };
1082
1240
 
1083
1241
  const getLastType = scope => {
1084
1242
  scope.gotLastType = true;
1085
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1243
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1086
1244
  };
1087
1245
 
1088
1246
  const setLastType = scope => {
1089
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1247
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1090
1248
  };
1091
1249
 
1092
1250
  const getNodeType = (scope, node) => {
1093
1251
  const inner = () => {
1094
1252
  if (node.type === 'Literal') {
1095
- if (node.regex) return TYPES._regexp;
1253
+ if (node.regex) return TYPES.regexp;
1254
+
1255
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1096
1256
 
1097
1257
  return TYPES[typeof node.value];
1098
1258
  }
@@ -1107,6 +1267,27 @@ const getNodeType = (scope, node) => {
1107
1267
 
1108
1268
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1269
  const name = node.callee.name;
1270
+ if (!name) {
1271
+ // iife
1272
+ if (scope.locals['#last_type']) return getLastType(scope);
1273
+
1274
+ // presume
1275
+ // todo: warn here?
1276
+ return TYPES.number;
1277
+ }
1278
+
1279
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1280
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1281
+ if (scope.locals['#last_type']) return getLastType(scope);
1282
+
1283
+ // presume
1284
+ // todo: warn here?
1285
+ return TYPES.number;
1286
+ }
1287
+
1288
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1289
+ }
1290
+
1110
1291
  const func = funcs.find(x => x.name === name);
1111
1292
 
1112
1293
  if (func) {
@@ -1114,7 +1295,7 @@ const getNodeType = (scope, node) => {
1114
1295
  if (func.returnType) return func.returnType;
1115
1296
  }
1116
1297
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1298
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1118
1299
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1300
 
1120
1301
  // check if this is a prototype function
@@ -1125,11 +1306,16 @@ const getNodeType = (scope, node) => {
1125
1306
  const spl = name.slice(2).split('_');
1126
1307
 
1127
1308
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1309
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1129
1310
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1311
  }
1131
1312
 
1132
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1313
+ if (name.startsWith('__Porffor_wasm_')) {
1314
+ // todo: return undefined for non-returning ops
1315
+ return TYPES.number;
1316
+ }
1317
+
1318
+ if (scope.locals['#last_type']) return getLastType(scope);
1133
1319
 
1134
1320
  // presume
1135
1321
  // todo: warn here?
@@ -1172,11 +1358,20 @@ const getNodeType = (scope, node) => {
1172
1358
  }
1173
1359
 
1174
1360
  if (node.type === 'ArrayExpression') {
1175
- return TYPES._array;
1361
+ return TYPES.array;
1176
1362
  }
1177
1363
 
1178
1364
  if (node.type === 'BinaryExpression') {
1179
1365
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1366
+ if (node.operator !== '+') return TYPES.number;
1367
+
1368
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1369
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1370
+
1371
+ // todo: this should be dynamic but for now only static
1372
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1373
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1374
+
1180
1375
  return TYPES.number;
1181
1376
 
1182
1377
  // todo: string concat types
@@ -1201,20 +1396,41 @@ const getNodeType = (scope, node) => {
1201
1396
  if (node.operator === '!') return TYPES.boolean;
1202
1397
  if (node.operator === 'void') return TYPES.undefined;
1203
1398
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1399
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1205
1400
 
1206
1401
  return TYPES.number;
1207
1402
  }
1208
1403
 
1209
1404
  if (node.type === 'MemberExpression') {
1405
+ // hack: if something.name, string type
1406
+ if (node.property.name === 'name') {
1407
+ if (hasFuncWithName(node.object.name)) {
1408
+ return TYPES.bytestring;
1409
+ } else {
1410
+ return TYPES.undefined;
1411
+ }
1412
+ }
1413
+
1210
1414
  // hack: if something.length, number type
1211
1415
  if (node.property.name === 'length') return TYPES.number;
1212
1416
 
1213
- // we cannot guess
1417
+ // ts hack
1418
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1419
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1420
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1421
+
1422
+ if (scope.locals['#last_type']) return getLastType(scope);
1423
+
1424
+ // presume
1214
1425
  return TYPES.number;
1215
1426
  }
1216
1427
 
1217
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1428
+ if (node.type === 'TaggedTemplateExpression') {
1429
+ // hack
1430
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1431
+ }
1432
+
1433
+ if (scope.locals['#last_type']) return getLastType(scope);
1218
1434
 
1219
1435
  // presume
1220
1436
  // todo: warn here?
@@ -1230,8 +1446,8 @@ const getNodeType = (scope, node) => {
1230
1446
  const generateLiteral = (scope, decl, global, name) => {
1231
1447
  if (decl.value === null) return number(NULL);
1232
1448
 
1449
+ // hack: just return 1 for regex literals
1233
1450
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1451
  return number(1);
1236
1452
  }
1237
1453
 
@@ -1244,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1460
  return number(decl.value ? 1 : 0);
1245
1461
 
1246
1462
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1463
+ return makeString(scope, decl.value, global, name);
1257
1464
 
1258
1465
  default:
1259
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1466
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1260
1467
  }
1261
1468
  };
1262
1469
 
@@ -1265,6 +1472,8 @@ const countLeftover = wasm => {
1265
1472
 
1266
1473
  for (let i = 0; i < wasm.length; i++) {
1267
1474
  const inst = wasm[i];
1475
+ if (inst[0] == null) continue;
1476
+
1268
1477
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1269
1478
  if (inst[0] === Opcodes.if) count--;
1270
1479
  if (inst[1] !== Blocktype.void) count++;
@@ -1273,18 +1482,25 @@ const countLeftover = wasm => {
1273
1482
  if (inst[0] === Opcodes.end) depth--;
1274
1483
 
1275
1484
  if (depth === 0)
1276
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- 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.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1278
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1279
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1485
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1486
+ 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)) {}
1487
+ 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++;
1488
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1489
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1490
  else if (inst[0] === Opcodes.return) count = 0;
1282
1491
  else if (inst[0] === Opcodes.call) {
1283
1492
  let func = funcs.find(x => x.index === inst[1]);
1284
- if (func) {
1285
- count -= func.params.length;
1286
- } else count--;
1287
- if (func) count += func.returns.length;
1493
+ if (inst[1] === -1) {
1494
+ // todo: count for calling self
1495
+ } else if (!func && inst[1] < importedFuncs.length) {
1496
+ count -= importedFuncs[inst[1]].params;
1497
+ count += importedFuncs[inst[1]].returns;
1498
+ } else {
1499
+ if (func) {
1500
+ count -= func.params.length;
1501
+ } else count--;
1502
+ if (func) count += func.returns.length;
1503
+ }
1288
1504
  } else count--;
1289
1505
 
1290
1506
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1302,7 +1518,7 @@ const disposeLeftover = wasm => {
1302
1518
  const generateExp = (scope, decl) => {
1303
1519
  const expression = decl.expression;
1304
1520
 
1305
- const out = generate(scope, expression);
1521
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1522
  disposeLeftover(out);
1307
1523
 
1308
1524
  return out;
@@ -1360,7 +1576,7 @@ const RTArrayUtil = {
1360
1576
  ]
1361
1577
  };
1362
1578
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1579
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1580
  /* const callee = decl.callee;
1365
1581
  const args = decl.arguments;
1366
1582
 
@@ -1376,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
1376
1592
  name = func.name;
1377
1593
  }
1378
1594
 
1379
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1595
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1380
1596
  // literal eval hack
1381
- const code = decl.arguments[0].value;
1382
- const parsed = parse(code, []);
1597
+ const code = decl.arguments[0]?.value ?? '';
1598
+
1599
+ let parsed;
1600
+ try {
1601
+ parsed = parse(code, []);
1602
+ } catch (e) {
1603
+ if (e.name === 'SyntaxError') {
1604
+ // throw syntax errors of evals at runtime instead
1605
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1606
+ }
1607
+
1608
+ throw e;
1609
+ }
1383
1610
 
1384
1611
  const out = generate(scope, {
1385
1612
  type: 'BlockStatement',
@@ -1393,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
1393
1620
  const finalStatement = parsed.body[parsed.body.length - 1];
1394
1621
  out.push(
1395
1622
  ...getNodeType(scope, finalStatement),
1396
- setLastType(scope)
1623
+ ...setLastType(scope)
1397
1624
  );
1398
1625
  } else if (countLeftover(out) === 0) {
1399
1626
  out.push(...number(UNDEFINED));
1400
1627
  out.push(
1401
1628
  ...number(TYPES.undefined, Valtype.i32),
1402
- setLastType(scope)
1629
+ ...setLastType(scope)
1403
1630
  );
1404
1631
  }
1405
1632
 
@@ -1421,29 +1648,39 @@ const generateCall = (scope, decl, _global, _name) => {
1421
1648
 
1422
1649
  target = { ...decl.callee };
1423
1650
  target.name = spl.slice(0, -1).join('_');
1651
+
1652
+ // failed to lookup name, abort
1653
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1424
1654
  }
1425
1655
 
1426
1656
  // literal.func()
1427
1657
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1658
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1431
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1659
+ const funcName = decl.callee.property.name;
1660
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1661
+ const regex = decl.callee.object.regex.pattern;
1662
+ const rhemynName = `regex_${funcName}_${regex}`;
1663
+
1664
+ if (!funcIndex[rhemynName]) {
1665
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1432
1666
 
1433
- funcIndex[func.name] = func.index;
1434
- funcs.push(func);
1667
+ funcIndex[func.name] = func.index;
1668
+ funcs.push(func);
1669
+ }
1435
1670
 
1671
+ const idx = funcIndex[rhemynName];
1436
1672
  return [
1437
1673
  // make string arg
1438
1674
  ...generate(scope, decl.arguments[0]),
1675
+ Opcodes.i32_to_u,
1676
+ ...getNodeType(scope, decl.arguments[0]),
1439
1677
 
1440
1678
  // call regex func
1441
- Opcodes.i32_to_u,
1442
- [ Opcodes.call, func.index ],
1679
+ [ Opcodes.call, idx ],
1443
1680
  Opcodes.i32_from_u,
1444
1681
 
1445
1682
  ...number(TYPES.boolean, Valtype.i32),
1446
- setLastType(scope)
1683
+ ...setLastType(scope)
1447
1684
  ];
1448
1685
  }
1449
1686
 
@@ -1468,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
1468
1705
  // }
1469
1706
 
1470
1707
  if (protoName) {
1708
+ const protoBC = {};
1709
+
1710
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1711
+
1712
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1713
+ for (const x of builtinProtoCands) {
1714
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1715
+ if (type == null) continue;
1716
+
1717
+ protoBC[type] = generateCall(scope, {
1718
+ callee: {
1719
+ type: 'Identifier',
1720
+ name: x
1721
+ },
1722
+ arguments: [ target, ...decl.arguments ],
1723
+ _protoInternalCall: true
1724
+ });
1725
+ }
1726
+ }
1727
+
1471
1728
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1729
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1730
  return acc;
1475
1731
  }, {});
1476
1732
 
1477
- // no prototype function candidates, ignore
1478
1733
  if (Object.keys(protoCands).length > 0) {
1479
1734
  // use local for cached i32 length as commonly used
1480
1735
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1736
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1737
 
1484
1738
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1739
 
1740
+ const rawPointer = [
1741
+ ...generate(scope, target),
1742
+ Opcodes.i32_to_u
1743
+ ];
1744
+
1745
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1746
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1747
+
1748
+ let allOptUnused = true;
1486
1749
  let lengthI32CacheUsed = false;
1487
- const protoBC = {};
1488
1750
  for (const x in protoCands) {
1489
1751
  const protoFunc = protoCands[x];
1490
1752
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1492,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
1492
1754
  ...RTArrayUtil.getLength(getPointer),
1493
1755
 
1494
1756
  ...number(TYPES.number, Valtype.i32),
1495
- setLastType(scope)
1757
+ ...setLastType(scope)
1496
1758
  ];
1497
1759
  continue;
1498
1760
  }
@@ -1502,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1764
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1765
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1766
 
1767
+ let optUnused = false;
1505
1768
  const protoOut = protoFunc(getPointer, {
1506
1769
  getCachedI32: () => {
1507
1770
  lengthI32CacheUsed = true;
@@ -1516,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1779
  return makeArray(scope, {
1517
1780
  rawElements: new Array(length)
1518
1781
  }, _global, _name, true, itemType);
1782
+ }, () => {
1783
+ optUnused = true;
1784
+ return unusedValue;
1519
1785
  });
1520
1786
 
1787
+ if (!optUnused) allOptUnused = false;
1788
+
1521
1789
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1790
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1791
  ...protoOut,
1524
1792
 
1525
1793
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1526
- setLastType(scope),
1794
+ ...setLastType(scope),
1527
1795
  [ Opcodes.end ]
1528
1796
  ];
1529
1797
  }
1530
1798
 
1531
- return [
1532
- ...generate(scope, target),
1799
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1800
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1801
+ return [
1802
+ ...(usePointerCache ? [
1803
+ ...rawPointer,
1804
+ [ Opcodes.local_set, pointerLocal ],
1805
+ ] : []),
1536
1806
 
1537
1807
  ...(!lengthI32CacheUsed ? [] : [
1538
1808
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1814
 
1545
1815
  // TODO: error better
1546
1816
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1817
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1818
  ];
1549
1819
  }
1820
+
1821
+ if (Object.keys(protoBC).length > 0) {
1822
+ return typeSwitch(scope, getNodeType(scope, target), {
1823
+ ...protoBC,
1824
+
1825
+ // TODO: error better
1826
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1827
+ }, valtypeBinary);
1828
+ }
1550
1829
  }
1551
1830
 
1552
1831
  // TODO: only allows callee as literal
1553
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1832
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1554
1833
 
1555
1834
  let idx = funcIndex[name] ?? importedFuncs[name];
1556
1835
  if (idx === undefined && builtinFuncs[name]) {
@@ -1560,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
1560
1839
  idx = funcIndex[name];
1561
1840
 
1562
1841
  // infer arguments types from builtins params
1563
- const func = funcs.find(x => x.name === name);
1564
- for (let i = 0; i < decl.arguments.length; i++) {
1565
- const arg = decl.arguments[i];
1566
- if (!arg.name) continue;
1567
-
1568
- const local = scope.locals[arg.name];
1569
- if (!local) continue;
1570
-
1571
- local.type = func.params[i];
1572
- if (local.type === Valtype.v128) {
1573
- // specify vec subtype inferred from last vec type in function name
1574
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1575
- }
1576
- }
1842
+ // const func = funcs.find(x => x.name === name);
1843
+ // for (let i = 0; i < decl.arguments.length; i++) {
1844
+ // const arg = decl.arguments[i];
1845
+ // if (!arg.name) continue;
1846
+
1847
+ // const local = scope.locals[arg.name];
1848
+ // if (!local) continue;
1849
+
1850
+ // local.type = func.params[i];
1851
+ // if (local.type === Valtype.v128) {
1852
+ // // specify vec subtype inferred from last vec type in function name
1853
+ // local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1854
+ // }
1855
+ // }
1577
1856
  }
1578
1857
 
1579
1858
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1583,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
1583
1862
  idx = -1;
1584
1863
  }
1585
1864
 
1865
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1866
+ const wasmOps = {
1867
+ // pointer, align, offset
1868
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1869
+ // pointer, value, align, offset
1870
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1871
+ // pointer, align, offset
1872
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1873
+ // pointer, value, align, offset
1874
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1875
+ // pointer, align, offset
1876
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1877
+ // pointer, value, align, offset
1878
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1879
+
1880
+ // pointer, align, offset
1881
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1882
+ // pointer, value, align, offset
1883
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1884
+
1885
+ // value
1886
+ i32_const: { imms: 1, args: [], returns: 1 },
1887
+ };
1888
+
1889
+ const opName = name.slice('__Porffor_wasm_'.length);
1890
+
1891
+ if (wasmOps[opName]) {
1892
+ const op = wasmOps[opName];
1893
+
1894
+ const argOut = [];
1895
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1896
+ ...generate(scope, decl.arguments[i]),
1897
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1898
+ );
1899
+
1900
+ // literals only
1901
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1902
+
1903
+ return [
1904
+ ...argOut,
1905
+ [ Opcodes[opName], ...imms ],
1906
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1907
+ ];
1908
+ }
1909
+ }
1910
+
1586
1911
  if (idx === undefined) {
1587
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1588
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1912
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1913
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1589
1914
  }
1590
1915
 
1591
1916
  const func = funcs.find(x => x.index === idx);
1592
1917
 
1593
1918
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1919
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1920
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1921
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1922
 
1596
1923
  let args = decl.arguments;
1597
1924
  if (func && args.length < paramCount) {
@@ -1607,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
1607
1934
  if (func && func.throws) scope.throws = true;
1608
1935
 
1609
1936
  let out = [];
1610
- for (const arg of args) {
1937
+ for (let i = 0; i < args.length; i++) {
1938
+ const arg = args[i];
1611
1939
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1940
+
1941
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1942
+ out.push(Opcodes.i32_to);
1943
+ }
1944
+
1945
+ if (importedFuncs[name] && name.startsWith('profile')) {
1946
+ out.push(Opcodes.i32_to);
1947
+ }
1948
+
1949
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1950
  }
1614
1951
 
1615
1952
  out.push([ Opcodes.call, idx ]);
1616
1953
 
1617
- if (!userFunc) {
1954
+ if (!typedReturns) {
1618
1955
  // let type;
1619
1956
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1957
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1624,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1961
  // ...number(type, Valtype.i32),
1625
1962
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1626
1963
  // );
1627
- } else out.push(setLastType(scope));
1964
+ } else out.push(...setLastType(scope));
1965
+
1966
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1967
+ out.push(Opcodes.i32_from);
1968
+ }
1628
1969
 
1629
1970
  return out;
1630
1971
  };
@@ -1632,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
1632
1973
  const generateNew = (scope, decl, _global, _name) => {
1633
1974
  // hack: basically treat this as a normal call for builtins for now
1634
1975
  const name = mapName(decl.callee.name);
1976
+
1635
1977
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1636
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1978
+
1979
+ if (builtinFuncs[name + '$constructor']) {
1980
+ // custom ...$constructor override builtin func
1981
+ return generateCall(scope, {
1982
+ ...decl,
1983
+ callee: {
1984
+ type: 'Identifier',
1985
+ name: name + '$constructor'
1986
+ }
1987
+ }, _global, _name);
1988
+ }
1989
+
1990
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1637
1991
 
1638
1992
  return generateCall(scope, decl, _global, _name);
1639
1993
  };
@@ -1665,22 +2019,108 @@ const knownType = (scope, type) => {
1665
2019
  return null;
1666
2020
  };
1667
2021
 
2022
+ const brTable = (input, bc, returns) => {
2023
+ const out = [];
2024
+ const keys = Object.keys(bc);
2025
+ const count = keys.length;
2026
+
2027
+ if (count === 1) {
2028
+ // return [
2029
+ // ...input,
2030
+ // ...bc[keys[0]]
2031
+ // ];
2032
+ return bc[keys[0]];
2033
+ }
2034
+
2035
+ if (count === 2) {
2036
+ // just use if else
2037
+ const other = keys.find(x => x !== 'default');
2038
+ return [
2039
+ ...input,
2040
+ ...number(other, Valtype.i32),
2041
+ [ Opcodes.i32_eq ],
2042
+ [ Opcodes.if, returns ],
2043
+ ...bc[other],
2044
+ [ Opcodes.else ],
2045
+ ...bc.default,
2046
+ [ Opcodes.end ]
2047
+ ];
2048
+ }
2049
+
2050
+ for (let i = 0; i < count; i++) {
2051
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2052
+ else out.push([ Opcodes.block, Blocktype.void ]);
2053
+ }
2054
+
2055
+ const nums = keys.filter(x => +x);
2056
+ const offset = Math.min(...nums);
2057
+ const max = Math.max(...nums);
2058
+
2059
+ const table = [];
2060
+ let br = 1;
2061
+
2062
+ for (let i = offset; i <= max; i++) {
2063
+ // if branch for this num, go to that block
2064
+ if (bc[i]) {
2065
+ table.push(br);
2066
+ br++;
2067
+ continue;
2068
+ }
2069
+
2070
+ // else default
2071
+ table.push(0);
2072
+ }
2073
+
2074
+ out.push(
2075
+ [ Opcodes.block, Blocktype.void ],
2076
+ ...input,
2077
+ ...(offset > 0 ? [
2078
+ ...number(offset, Valtype.i32),
2079
+ [ Opcodes.i32_sub ]
2080
+ ] : []),
2081
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
2082
+ );
2083
+
2084
+ // if you can guess why we sort the wrong way and then reverse
2085
+ // (instead of just sorting the correct way)
2086
+ // dm me and if you are correct and the first person
2087
+ // I will somehow shout you out or something
2088
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
2089
+
2090
+ br = count - 1;
2091
+ for (const x of orderedBc) {
2092
+ out.push(
2093
+ [ Opcodes.end ],
2094
+ ...bc[x],
2095
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
2096
+ );
2097
+ br--;
2098
+ }
2099
+
2100
+ return [
2101
+ ...out,
2102
+ [ Opcodes.end, 'br table end' ]
2103
+ ];
2104
+ };
2105
+
1668
2106
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2107
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
2108
+
1669
2109
  const known = knownType(scope, type);
1670
2110
  if (known != null) {
1671
2111
  return bc[known] ?? bc.default;
1672
2112
  }
1673
2113
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2114
+ if (Prefs.typeswitchUseBrtable)
2115
+ return brTable(type, bc, returns);
1675
2116
 
2117
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
2118
  const out = [
1677
2119
  ...type,
1678
2120
  [ Opcodes.local_set, tmp ],
1679
2121
  [ Opcodes.block, returns ]
1680
2122
  ];
1681
2123
 
1682
- // todo: use br_table?
1683
-
1684
2124
  for (const x in bc) {
1685
2125
  if (x === 'default') continue;
1686
2126
 
@@ -1704,7 +2144,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1704
2144
  return out;
1705
2145
  };
1706
2146
 
1707
- const allocVar = (scope, name, global = false) => {
2147
+ const allocVar = (scope, name, global = false, type = true) => {
1708
2148
  const target = global ? globals : scope.locals;
1709
2149
 
1710
2150
  // already declared
@@ -1718,8 +2158,10 @@ const allocVar = (scope, name, global = false) => {
1718
2158
  let idx = global ? globalInd++ : scope.localInd++;
1719
2159
  target[name] = { idx, type: valtypeBinary };
1720
2160
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2161
+ if (type) {
2162
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2163
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2164
+ }
1723
2165
 
1724
2166
  return idx;
1725
2167
  };
@@ -1734,11 +2176,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1734
2176
  };
1735
2177
 
1736
2178
  const typeAnnoToPorfType = x => {
1737
- if (TYPES[x]) return TYPES[x];
1738
- if (TYPES['_' + x]) return TYPES['_' + x];
2179
+ if (!x) return null;
2180
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2181
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1739
2182
 
1740
2183
  switch (x) {
1741
2184
  case 'i32':
2185
+ case 'i64':
2186
+ case 'f64':
1742
2187
  return TYPES.number;
1743
2188
  }
1744
2189
 
@@ -1749,7 +2194,7 @@ const extractTypeAnnotation = decl => {
1749
2194
  let a = decl;
1750
2195
  while (a.typeAnnotation) a = a.typeAnnotation;
1751
2196
 
1752
- let type, elementType;
2197
+ let type = null, elementType = null;
1753
2198
  if (a.typeName) {
1754
2199
  type = a.typeName.name;
1755
2200
  } else if (a.type.endsWith('Keyword')) {
@@ -1762,6 +2207,8 @@ const extractTypeAnnotation = decl => {
1762
2207
  const typeName = type;
1763
2208
  type = typeAnnoToPorfType(type);
1764
2209
 
2210
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
2211
+
1765
2212
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
2213
 
1767
2214
  return { type, typeName, elementType };
@@ -1774,10 +2221,13 @@ const generateVar = (scope, decl) => {
1774
2221
 
1775
2222
  // global variable if in top scope (main) and var ..., or if wanted
1776
2223
  const global = topLevel || decl._bare; // decl.kind === 'var';
2224
+ const target = global ? globals : scope.locals;
1777
2225
 
1778
2226
  for (const x of decl.declarations) {
1779
2227
  const name = mapName(x.id.name);
1780
2228
 
2229
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2230
+
1781
2231
  if (x.init && isFuncType(x.init.type)) {
1782
2232
  // hack for let a = function () { ... }
1783
2233
  x.init.id = { name };
@@ -1793,26 +2243,41 @@ const generateVar = (scope, decl) => {
1793
2243
  continue; // always ignore
1794
2244
  }
1795
2245
 
1796
- let idx = allocVar(scope, name, global);
1797
- if (x.init) {
1798
- out = out.concat(generate(scope, x.init, global, name));
2246
+ // // generate init before allocating var
2247
+ // let generated;
2248
+ // if (x.init) generated = generate(scope, x.init, global, name);
2249
+
2250
+ const typed = typedInput && x.id.typeAnnotation;
2251
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2252
+
2253
+ if (typed) {
2254
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2255
+ }
1799
2256
 
1800
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2257
+ if (x.init) {
2258
+ const generated = generate(scope, x.init, global, name);
2259
+ if (scope.arrays?.get(name) != null) {
2260
+ // hack to set local as pointer before
2261
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2262
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2263
+ generated.pop();
2264
+ out = out.concat(generated);
2265
+ } else {
2266
+ out = out.concat(generated);
2267
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2268
+ }
1801
2269
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1802
2270
  }
1803
2271
 
1804
2272
  // hack: this follows spec properly but is mostly unneeded 😅
1805
2273
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1806
-
1807
- if (typedInput && x.id.typeAnnotation) {
1808
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1809
- }
1810
2274
  }
1811
2275
 
1812
2276
  return out;
1813
2277
  };
1814
2278
 
1815
- const generateAssign = (scope, decl) => {
2279
+ // todo: optimize this func for valueUnused
2280
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1816
2281
  const { type, name } = decl.left;
1817
2282
 
1818
2283
  if (type === 'ObjectPattern') {
@@ -1827,22 +2292,30 @@ const generateAssign = (scope, decl) => {
1827
2292
  return [];
1828
2293
  }
1829
2294
 
2295
+ const op = decl.operator.slice(0, -1) || '=';
2296
+
1830
2297
  // hack: .length setter
1831
2298
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1832
2299
  const name = decl.left.object.name;
1833
- const pointer = arrays.get(name);
2300
+ const pointer = scope.arrays?.get(name);
1834
2301
 
1835
- const aotPointer = pointer != null;
2302
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1836
2303
 
1837
2304
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2305
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1838
2306
 
1839
2307
  return [
1840
2308
  ...(aotPointer ? number(0, Valtype.i32) : [
1841
2309
  ...generate(scope, decl.left.object),
1842
2310
  Opcodes.i32_to_u
1843
2311
  ]),
2312
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1844
2313
 
1845
- ...generate(scope, decl.right),
2314
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2315
+ [ Opcodes.local_get, pointerTmp ],
2316
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2317
+ Opcodes.i32_from_u
2318
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
1846
2319
  [ Opcodes.local_tee, newValueTmp ],
1847
2320
 
1848
2321
  Opcodes.i32_to_u,
@@ -1852,21 +2325,19 @@ const generateAssign = (scope, decl) => {
1852
2325
  ];
1853
2326
  }
1854
2327
 
1855
- const op = decl.operator.slice(0, -1) || '=';
1856
-
1857
2328
  // arr[i]
1858
2329
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1859
2330
  const name = decl.left.object.name;
1860
- const pointer = arrays.get(name);
2331
+ const pointer = scope.arrays?.get(name);
1861
2332
 
1862
- const aotPointer = pointer != null;
2333
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1863
2334
 
1864
2335
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1865
2336
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1866
2337
 
1867
2338
  return [
1868
2339
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1869
- [TYPES._array]: [
2340
+ [TYPES.array]: [
1870
2341
  ...(aotPointer ? [] : [
1871
2342
  ...generate(scope, decl.left.object),
1872
2343
  Opcodes.i32_to_u
@@ -1915,6 +2386,8 @@ const generateAssign = (scope, decl) => {
1915
2386
  ];
1916
2387
  }
1917
2388
 
2389
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2390
+
1918
2391
  const [ local, isGlobal ] = lookupName(scope, name);
1919
2392
 
1920
2393
  if (local === undefined) {
@@ -1961,9 +2434,7 @@ const generateAssign = (scope, decl) => {
1961
2434
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1962
2435
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1963
2436
 
1964
- getLastType(scope),
1965
- // hack: type is idx+1
1966
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2437
+ ...setType(scope, name, getLastType(scope))
1967
2438
  ];
1968
2439
  }
1969
2440
 
@@ -1974,9 +2445,7 @@ const generateAssign = (scope, decl) => {
1974
2445
 
1975
2446
  // todo: string concat types
1976
2447
 
1977
- // hack: type is idx+1
1978
- ...number(TYPES.number, Valtype.i32),
1979
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2448
+ ...setType(scope, name, TYPES.number)
1980
2449
  ];
1981
2450
  };
1982
2451
 
@@ -2022,7 +2491,7 @@ const generateUnary = (scope, decl) => {
2022
2491
  return out;
2023
2492
  }
2024
2493
 
2025
- case 'delete':
2494
+ case 'delete': {
2026
2495
  let toReturn = true, toGenerate = true;
2027
2496
 
2028
2497
  if (decl.argument.type === 'Identifier') {
@@ -2044,38 +2513,60 @@ const generateUnary = (scope, decl) => {
2044
2513
 
2045
2514
  out.push(...number(toReturn ? 1 : 0));
2046
2515
  return out;
2516
+ }
2517
+
2518
+ case 'typeof': {
2519
+ let overrideType, toGenerate = true;
2520
+
2521
+ if (decl.argument.type === 'Identifier') {
2522
+ const out = generateIdent(scope, decl.argument);
2523
+
2524
+ // if ReferenceError (undeclared var), ignore and return undefined
2525
+ if (out[1]) {
2526
+ // does not exist (2 ops from throw)
2527
+ overrideType = number(TYPES.undefined, Valtype.i32);
2528
+ toGenerate = false;
2529
+ }
2530
+ }
2531
+
2532
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2533
+ disposeLeftover(out);
2047
2534
 
2048
- case 'typeof':
2049
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2535
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2050
2536
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2051
2537
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2052
2538
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2053
2539
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2054
2540
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2055
2541
 
2542
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2543
+
2056
2544
  // object and internal types
2057
2545
  default: makeString(scope, 'object', false, '#typeof_result'),
2058
- });
2546
+ }));
2547
+
2548
+ return out;
2549
+ }
2059
2550
 
2060
2551
  default:
2061
- return todo(`unary operator ${decl.operator} not implemented yet`);
2552
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2062
2553
  }
2063
2554
  };
2064
2555
 
2065
- const generateUpdate = (scope, decl) => {
2556
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2066
2557
  const { name } = decl.argument;
2067
2558
 
2068
2559
  const [ local, isGlobal ] = lookupName(scope, name);
2069
2560
 
2070
2561
  if (local === undefined) {
2071
- return todo(`update expression with undefined variable`);
2562
+ return todo(scope, `update expression with undefined variable`, true);
2072
2563
  }
2073
2564
 
2074
2565
  const idx = local.idx;
2075
2566
  const out = [];
2076
2567
 
2077
2568
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2078
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2569
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2079
2570
 
2080
2571
  switch (decl.operator) {
2081
2572
  case '++':
@@ -2088,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
2088
2579
  }
2089
2580
 
2090
2581
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2091
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2582
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2092
2583
 
2093
2584
  return out;
2094
2585
  };
@@ -2128,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2128
2619
  // note type
2129
2620
  out.push(
2130
2621
  ...getNodeType(scope, decl.consequent),
2131
- setLastType(scope)
2622
+ ...setLastType(scope)
2132
2623
  );
2133
2624
 
2134
2625
  out.push([ Opcodes.else ]);
@@ -2137,7 +2628,7 @@ const generateConditional = (scope, decl) => {
2137
2628
  // note type
2138
2629
  out.push(
2139
2630
  ...getNodeType(scope, decl.alternate),
2140
- setLastType(scope)
2631
+ ...setLastType(scope)
2141
2632
  );
2142
2633
 
2143
2634
  out.push([ Opcodes.end ]);
@@ -2151,15 +2642,17 @@ const generateFor = (scope, decl) => {
2151
2642
  const out = [];
2152
2643
 
2153
2644
  if (decl.init) {
2154
- out.push(...generate(scope, decl.init));
2645
+ out.push(...generate(scope, decl.init, false, undefined, true));
2155
2646
  disposeLeftover(out);
2156
2647
  }
2157
2648
 
2158
2649
  out.push([ Opcodes.loop, Blocktype.void ]);
2159
2650
  depth.push('for');
2160
2651
 
2161
- out.push(...generate(scope, decl.test));
2162
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2652
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2653
+ else out.push(...number(1, Valtype.i32));
2654
+
2655
+ out.push([ Opcodes.if, Blocktype.void ]);
2163
2656
  depth.push('if');
2164
2657
 
2165
2658
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2167,8 +2660,7 @@ const generateFor = (scope, decl) => {
2167
2660
  out.push(...generate(scope, decl.body));
2168
2661
  out.push([ Opcodes.end ]);
2169
2662
 
2170
- out.push(...generate(scope, decl.update));
2171
- depth.pop();
2663
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2172
2664
 
2173
2665
  out.push([ Opcodes.br, 1 ]);
2174
2666
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2196,6 +2688,36 @@ const generateWhile = (scope, decl) => {
2196
2688
  return out;
2197
2689
  };
2198
2690
 
2691
+ const generateDoWhile = (scope, decl) => {
2692
+ const out = [];
2693
+
2694
+ out.push([ Opcodes.loop, Blocktype.void ]);
2695
+ depth.push('dowhile');
2696
+
2697
+ // block for break (includes all)
2698
+ out.push([ Opcodes.block, Blocktype.void ]);
2699
+ depth.push('block');
2700
+
2701
+ // block for continue
2702
+ // includes body but not test+loop so we can exit body at anytime
2703
+ // and still test+loop after
2704
+ out.push([ Opcodes.block, Blocktype.void ]);
2705
+ depth.push('block');
2706
+
2707
+ out.push(...generate(scope, decl.body));
2708
+
2709
+ out.push([ Opcodes.end ]);
2710
+ depth.pop();
2711
+
2712
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2713
+ out.push([ Opcodes.br_if, 1 ]);
2714
+
2715
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2716
+ depth.pop(); depth.pop();
2717
+
2718
+ return out;
2719
+ };
2720
+
2199
2721
  const generateForOf = (scope, decl) => {
2200
2722
  const out = [];
2201
2723
 
@@ -2225,8 +2747,17 @@ const generateForOf = (scope, decl) => {
2225
2747
  // setup local for left
2226
2748
  generate(scope, decl.left);
2227
2749
 
2228
- const leftName = decl.left.declarations[0].id.name;
2750
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2751
+ if (!leftName && decl.left.name) {
2752
+ leftName = decl.left.name;
2753
+
2754
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2755
+ }
2756
+
2757
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2758
+
2229
2759
  const [ local, isGlobal ] = lookupName(scope, leftName);
2760
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2230
2761
 
2231
2762
  depth.push('block');
2232
2763
  depth.push('block');
@@ -2234,15 +2765,17 @@ const generateForOf = (scope, decl) => {
2234
2765
  // // todo: we should only do this for strings but we don't know at compile-time :(
2235
2766
  // hack: this is naughty and will break things!
2236
2767
  let newOut = number(0, Valtype.f64), newPointer = -1;
2237
- if (pages.hasString) {
2768
+ if (pages.hasAnyString) {
2769
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2238
2770
  0, [ newOut, newPointer ] = makeArray(scope, {
2239
2771
  rawElements: new Array(1)
2240
2772
  }, isGlobal, leftName, true, 'i16');
2241
2773
  }
2242
2774
 
2243
2775
  // set type for local
2776
+ // todo: optimize away counter and use end pointer
2244
2777
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2245
- [TYPES._array]: [
2778
+ [TYPES.array]: [
2246
2779
  ...setType(scope, leftName, TYPES.number),
2247
2780
 
2248
2781
  [ Opcodes.loop, Blocktype.void ],
@@ -2325,6 +2858,56 @@ const generateForOf = (scope, decl) => {
2325
2858
  [ Opcodes.end ],
2326
2859
  [ Opcodes.end ]
2327
2860
  ],
2861
+ [TYPES.bytestring]: [
2862
+ ...setType(scope, leftName, TYPES.bytestring),
2863
+
2864
+ [ Opcodes.loop, Blocktype.void ],
2865
+
2866
+ // setup new/out array
2867
+ ...newOut,
2868
+ [ Opcodes.drop ],
2869
+
2870
+ ...number(0, Valtype.i32), // base 0 for store after
2871
+
2872
+ // load current string ind {arg}
2873
+ [ Opcodes.local_get, pointer ],
2874
+ [ Opcodes.local_get, counter ],
2875
+ [ Opcodes.i32_add ],
2876
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2877
+
2878
+ // store to new string ind 0
2879
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2880
+
2881
+ // return new string (page)
2882
+ ...number(newPointer),
2883
+
2884
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2885
+
2886
+ [ Opcodes.block, Blocktype.void ],
2887
+ [ Opcodes.block, Blocktype.void ],
2888
+ ...generate(scope, decl.body),
2889
+ [ Opcodes.end ],
2890
+
2891
+ // increment iter pointer
2892
+ // [ Opcodes.local_get, pointer ],
2893
+ // ...number(1, Valtype.i32),
2894
+ // [ Opcodes.i32_add ],
2895
+ // [ Opcodes.local_set, pointer ],
2896
+
2897
+ // increment counter by 1
2898
+ [ Opcodes.local_get, counter ],
2899
+ ...number(1, Valtype.i32),
2900
+ [ Opcodes.i32_add ],
2901
+ [ Opcodes.local_tee, counter ],
2902
+
2903
+ // loop if counter != length
2904
+ [ Opcodes.local_get, length ],
2905
+ [ Opcodes.i32_ne ],
2906
+ [ Opcodes.br_if, 1 ],
2907
+
2908
+ [ Opcodes.end ],
2909
+ [ Opcodes.end ]
2910
+ ],
2328
2911
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2329
2912
  }, Blocktype.void));
2330
2913
 
@@ -2335,28 +2918,65 @@ const generateForOf = (scope, decl) => {
2335
2918
  return out;
2336
2919
  };
2337
2920
 
2921
+ // find the nearest loop in depth map by type
2338
2922
  const getNearestLoop = () => {
2339
2923
  for (let i = depth.length - 1; i >= 0; i--) {
2340
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2924
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2341
2925
  }
2342
2926
 
2343
2927
  return -1;
2344
2928
  };
2345
2929
 
2346
2930
  const generateBreak = (scope, decl) => {
2347
- const nearestLoop = depth.length - getNearestLoop();
2931
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2932
+ const type = depth[target];
2933
+
2934
+ // different loop types have different branch offsets
2935
+ // as they have different wasm block/loop/if structures
2936
+ // we need to use the right offset by type to branch to the one we want
2937
+ // for a break: exit the loop without executing anything else inside it
2938
+ const offset = ({
2939
+ for: 2, // loop > if (wanted branch) > block (we are here)
2940
+ while: 2, // loop > if (wanted branch) (we are here)
2941
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2942
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2943
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2944
+ })[type];
2945
+
2348
2946
  return [
2349
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2947
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2350
2948
  ];
2351
2949
  };
2352
2950
 
2353
2951
  const generateContinue = (scope, decl) => {
2354
- const nearestLoop = depth.length - getNearestLoop();
2952
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2953
+ const type = depth[target];
2954
+
2955
+ // different loop types have different branch offsets
2956
+ // as they have different wasm block/loop/if structures
2957
+ // we need to use the right offset by type to branch to the one we want
2958
+ // for a continue: do test for the loop, and then loop depending on that success
2959
+ const offset = ({
2960
+ for: 3, // loop (wanted branch) > if > block (we are here)
2961
+ while: 1, // loop (wanted branch) > if (we are here)
2962
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2963
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2964
+ })[type];
2965
+
2355
2966
  return [
2356
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2967
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2357
2968
  ];
2358
2969
  };
2359
2970
 
2971
+ const generateLabel = (scope, decl) => {
2972
+ scope.labels ??= new Map();
2973
+
2974
+ const name = decl.label.name;
2975
+ scope.labels.set(name, depth.length);
2976
+
2977
+ return generate(scope, decl.body);
2978
+ };
2979
+
2360
2980
  const generateThrow = (scope, decl) => {
2361
2981
  scope.throws = true;
2362
2982
 
@@ -2365,7 +2985,7 @@ const generateThrow = (scope, decl) => {
2365
2985
  // hack: throw new X("...") -> throw "..."
2366
2986
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2367
2987
  constructor = decl.argument.callee.name;
2368
- message = decl.argument.arguments[0].value;
2988
+ message = decl.argument.arguments[0]?.value ?? '';
2369
2989
  }
2370
2990
 
2371
2991
  if (tags.length === 0) tags.push({
@@ -2377,6 +2997,9 @@ const generateThrow = (scope, decl) => {
2377
2997
  let exceptId = exceptions.push({ constructor, message }) - 1;
2378
2998
  let tagIdx = tags[0].idx;
2379
2999
 
3000
+ scope.exceptions ??= [];
3001
+ scope.exceptions.push(exceptId);
3002
+
2380
3003
  // todo: write a description of how this works lol
2381
3004
 
2382
3005
  return [
@@ -2386,7 +3009,7 @@ const generateThrow = (scope, decl) => {
2386
3009
  };
2387
3010
 
2388
3011
  const generateTry = (scope, decl) => {
2389
- if (decl.finalizer) return todo('try finally not implemented yet');
3012
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2390
3013
 
2391
3014
  const out = [];
2392
3015
 
@@ -2417,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
2417
3040
  // TODO
2418
3041
  // if identifier declared, use that
2419
3042
  // else, use default (right)
2420
- return todo('assignment pattern (optional arg)');
3043
+ return todo(scope, 'assignment pattern (optional arg)');
2421
3044
  };
2422
3045
 
2423
3046
  let pages = new Map();
2424
- const allocPage = (reason, type) => {
3047
+ const allocPage = (scope, reason, type) => {
2425
3048
  if (pages.has(reason)) return pages.get(reason).ind;
2426
3049
 
2427
3050
  if (reason.startsWith('array:')) pages.hasArray = true;
2428
3051
  if (reason.startsWith('string:')) pages.hasString = true;
3052
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3053
+ if (reason.includes('string:')) pages.hasAnyString = true;
2429
3054
 
2430
3055
  const ind = pages.size;
2431
3056
  pages.set(reason, { ind, type });
2432
3057
 
2433
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3058
+ scope.pages ??= new Map();
3059
+ scope.pages.set(reason, { ind, type });
3060
+
3061
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2434
3062
 
2435
3063
  return ind;
2436
3064
  };
2437
3065
 
3066
+ // todo: add scope.pages
2438
3067
  const freePage = reason => {
2439
3068
  const { ind } = pages.get(reason);
2440
3069
  pages.delete(reason);
2441
3070
 
2442
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3071
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2443
3072
 
2444
3073
  return ind;
2445
3074
  };
@@ -2459,38 +3088,53 @@ const StoreOps = {
2459
3088
  f64: Opcodes.f64_store,
2460
3089
 
2461
3090
  // expects i32 input!
2462
- i16: Opcodes.i32_store16
3091
+ i8: Opcodes.i32_store8,
3092
+ i16: Opcodes.i32_store16,
2463
3093
  };
2464
3094
 
2465
3095
  let data = [];
2466
3096
 
2467
- const compileBytes = (val, itemType, signed = true) => {
3097
+ const compileBytes = (val, itemType) => {
2468
3098
  // todo: this is a mess and needs confirming / ????
2469
3099
  switch (itemType) {
2470
3100
  case 'i8': return [ val % 256 ];
2471
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2472
-
2473
- case 'i32':
2474
- case 'i64':
2475
- return enforceFourBytes(signedLEB128(val));
3101
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3102
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3103
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3104
+ // todo: i64
2476
3105
 
2477
3106
  case 'f64': return ieee754_binary64(val);
2478
3107
  }
2479
3108
  };
2480
3109
 
3110
+ const getAllocType = itemType => {
3111
+ switch (itemType) {
3112
+ case 'i8': return 'bytestring';
3113
+ case 'i16': return 'string';
3114
+
3115
+ default: return 'array';
3116
+ }
3117
+ };
3118
+
2481
3119
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2482
3120
  const out = [];
2483
3121
 
3122
+ scope.arrays ??= new Map();
3123
+
2484
3124
  let firstAssign = false;
2485
- if (!arrays.has(name) || name === '$undeclared') {
3125
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2486
3126
  firstAssign = true;
2487
3127
 
2488
3128
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2489
3129
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2490
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3130
+
3131
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3132
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2491
3133
  }
2492
3134
 
2493
- const pointer = arrays.get(name);
3135
+ const pointer = scope.arrays.get(name);
3136
+
3137
+ const local = global ? globals[name] : scope.locals[name];
2494
3138
 
2495
3139
  const useRawElements = !!decl.rawElements;
2496
3140
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2498,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2498
3142
  const valtype = itemTypeToValtype[itemType];
2499
3143
  const length = elements.length;
2500
3144
 
2501
- if (firstAssign && useRawElements) {
2502
- let bytes = compileBytes(length, 'i32');
3145
+ if (firstAssign && useRawElements && !Prefs.noData) {
3146
+ // if length is 0 memory/data will just be 0000... anyway
3147
+ if (length !== 0) {
3148
+ let bytes = compileBytes(length, 'i32');
2503
3149
 
2504
- if (!initEmpty) for (let i = 0; i < length; i++) {
2505
- if (elements[i] == null) continue;
3150
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3151
+ if (elements[i] == null) continue;
2506
3152
 
2507
- bytes.push(...compileBytes(elements[i], itemType));
2508
- }
3153
+ bytes.push(...compileBytes(elements[i], itemType));
3154
+ }
2509
3155
 
2510
- data.push({
2511
- offset: pointer,
2512
- bytes
2513
- });
3156
+ const ind = data.push({
3157
+ offset: pointer,
3158
+ bytes
3159
+ }) - 1;
3160
+
3161
+ scope.data ??= [];
3162
+ scope.data.push(ind);
3163
+ }
2514
3164
 
2515
3165
  // local value as pointer
2516
3166
  out.push(...number(pointer));
@@ -2518,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2518
3168
  return [ out, pointer ];
2519
3169
  }
2520
3170
 
3171
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3172
+ if (pointerTmp != null) {
3173
+ out.push(
3174
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3175
+ Opcodes.i32_to_u,
3176
+ [ Opcodes.local_set, pointerTmp ]
3177
+ );
3178
+ }
3179
+
3180
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3181
+
2521
3182
  // store length as 0th array
2522
3183
  out.push(
2523
- ...number(0, Valtype.i32),
3184
+ ...pointerWasm,
2524
3185
  ...number(length, Valtype.i32),
2525
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3186
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2526
3187
  );
2527
3188
 
2528
3189
  const storeOp = StoreOps[itemType];
@@ -2531,43 +3192,87 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2531
3192
  if (elements[i] == null) continue;
2532
3193
 
2533
3194
  out.push(
2534
- ...number(0, Valtype.i32),
3195
+ ...pointerWasm,
2535
3196
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2536
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3197
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2537
3198
  );
2538
3199
  }
2539
3200
 
2540
3201
  // local value as pointer
2541
- out.push(...number(pointer));
3202
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2542
3203
 
2543
3204
  return [ out, pointer ];
2544
3205
  };
2545
3206
 
2546
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3207
+ const byteStringable = str => {
3208
+ if (!Prefs.bytestring) return false;
3209
+
3210
+ for (let i = 0; i < str.length; i++) {
3211
+ if (str.charCodeAt(i) > 0xFF) return false;
3212
+ }
3213
+
3214
+ return true;
3215
+ };
3216
+
3217
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2547
3218
  const rawElements = new Array(str.length);
3219
+ let byteStringable = Prefs.bytestring;
2548
3220
  for (let i = 0; i < str.length; i++) {
2549
- rawElements[i] = str.charCodeAt(i);
3221
+ const c = str.charCodeAt(i);
3222
+ rawElements[i] = c;
3223
+
3224
+ if (byteStringable && c > 0xFF) byteStringable = false;
2550
3225
  }
2551
3226
 
3227
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3228
+
2552
3229
  return makeArray(scope, {
2553
3230
  rawElements
2554
- }, global, name, false, 'i16')[0];
3231
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2555
3232
  };
2556
3233
 
2557
- let arrays = new Map();
2558
3234
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2559
3235
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2560
3236
  };
2561
3237
 
2562
3238
  export const generateMember = (scope, decl, _global, _name) => {
2563
3239
  const name = decl.object.name;
2564
- const pointer = arrays.get(name);
3240
+ const pointer = scope.arrays?.get(name);
2565
3241
 
2566
- const aotPointer = pointer != null;
3242
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
3243
+
3244
+ // hack: .name
3245
+ if (decl.property.name === 'name') {
3246
+ if (hasFuncWithName(name)) {
3247
+ return makeString(scope, name, _global, _name, true);
3248
+ } else {
3249
+ return generate(scope, DEFAULT_VALUE);
3250
+ }
3251
+ }
2567
3252
 
2568
3253
  // hack: .length
2569
3254
  if (decl.property.name === 'length') {
2570
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3255
+ const func = funcs.find(x => x.name === name);
3256
+ if (func) {
3257
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3258
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3259
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3260
+ }
3261
+
3262
+ if (builtinFuncs[name + '$constructor']) {
3263
+ const regularFunc = builtinFuncs[name];
3264
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3265
+
3266
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3267
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3268
+
3269
+ return number(Math.max(regularParams, constructorParams));
3270
+ }
3271
+
3272
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3273
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3274
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3275
+
2571
3276
  return [
2572
3277
  ...(aotPointer ? number(0, Valtype.i32) : [
2573
3278
  ...generate(scope, decl.object),
@@ -2579,19 +3284,22 @@ export const generateMember = (scope, decl, _global, _name) => {
2579
3284
  ];
2580
3285
  }
2581
3286
 
3287
+ const object = generate(scope, decl.object);
3288
+ const property = generate(scope, decl.property);
3289
+
2582
3290
  // // todo: we should only do this for strings but we don't know at compile-time :(
2583
3291
  // hack: this is naughty and will break things!
2584
- let newOut = number(0, Valtype.f64), newPointer = -1;
2585
- if (pages.hasString) {
3292
+ let newOut = number(0, valtypeBinary), newPointer = -1;
3293
+ if (pages.hasAnyString) {
2586
3294
  0, [ newOut, newPointer ] = makeArray(scope, {
2587
3295
  rawElements: new Array(1)
2588
3296
  }, _global, _name, true, 'i16');
2589
3297
  }
2590
3298
 
2591
3299
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2592
- [TYPES._array]: [
3300
+ [TYPES.array]: [
2593
3301
  // get index as valtype
2594
- ...generate(scope, decl.property),
3302
+ ...property,
2595
3303
 
2596
3304
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2597
3305
  Opcodes.i32_to_u,
@@ -2599,7 +3307,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2599
3307
  [ Opcodes.i32_mul ],
2600
3308
 
2601
3309
  ...(aotPointer ? [] : [
2602
- ...generate(scope, decl.object),
3310
+ ...object,
2603
3311
  Opcodes.i32_to_u,
2604
3312
  [ Opcodes.i32_add ]
2605
3313
  ]),
@@ -2608,7 +3316,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2608
3316
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2609
3317
 
2610
3318
  ...number(TYPES.number, Valtype.i32),
2611
- setLastType(scope)
3319
+ ...setLastType(scope)
2612
3320
  ],
2613
3321
 
2614
3322
  [TYPES.string]: [
@@ -2618,14 +3326,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2618
3326
 
2619
3327
  ...number(0, Valtype.i32), // base 0 for store later
2620
3328
 
2621
- ...generate(scope, decl.property),
2622
-
3329
+ ...property,
2623
3330
  Opcodes.i32_to_u,
3331
+
2624
3332
  ...number(ValtypeSize.i16, Valtype.i32),
2625
3333
  [ Opcodes.i32_mul ],
2626
3334
 
2627
3335
  ...(aotPointer ? [] : [
2628
- ...generate(scope, decl.object),
3336
+ ...object,
2629
3337
  Opcodes.i32_to_u,
2630
3338
  [ Opcodes.i32_add ]
2631
3339
  ]),
@@ -2640,10 +3348,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2640
3348
  ...number(newPointer),
2641
3349
 
2642
3350
  ...number(TYPES.string, Valtype.i32),
2643
- setLastType(scope)
3351
+ ...setLastType(scope)
3352
+ ],
3353
+ [TYPES.bytestring]: [
3354
+ // setup new/out array
3355
+ ...newOut,
3356
+ [ Opcodes.drop ],
3357
+
3358
+ ...number(0, Valtype.i32), // base 0 for store later
3359
+
3360
+ ...property,
3361
+ Opcodes.i32_to_u,
3362
+
3363
+ ...(aotPointer ? [] : [
3364
+ ...object,
3365
+ Opcodes.i32_to_u,
3366
+ [ Opcodes.i32_add ]
3367
+ ]),
3368
+
3369
+ // load current string ind {arg}
3370
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3371
+
3372
+ // store to new string ind 0
3373
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3374
+
3375
+ // return new string (page)
3376
+ ...number(newPointer),
3377
+
3378
+ ...number(TYPES.bytestring, Valtype.i32),
3379
+ ...setLastType(scope)
2644
3380
  ],
2645
3381
 
2646
- default: [ [ Opcodes.unreachable ] ]
3382
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2647
3383
  });
2648
3384
  };
2649
3385
 
@@ -2653,25 +3389,36 @@ const objectHack = node => {
2653
3389
  if (!node) return node;
2654
3390
 
2655
3391
  if (node.type === 'MemberExpression') {
2656
- if (node.computed || node.optional) return node;
3392
+ const out = (() => {
3393
+ if (node.computed || node.optional) return;
2657
3394
 
2658
- let objectName = node.object.name;
3395
+ let objectName = node.object.name;
2659
3396
 
2660
- // if object is not identifier or another member exp, give up
2661
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3397
+ // if object is not identifier or another member exp, give up
3398
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3399
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2662
3400
 
2663
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3401
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2664
3402
 
2665
- // if .length, give up (hack within a hack!)
2666
- if (node.property.name === 'length') return node;
3403
+ // if .name or .length, give up (hack within a hack!)
3404
+ if (['name', 'length'].includes(node.property.name)) {
3405
+ node.object = objectHack(node.object);
3406
+ return;
3407
+ }
2667
3408
 
2668
- const name = '__' + objectName + '_' + node.property.name;
2669
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3409
+ // no object name, give up
3410
+ if (!objectName) return;
2670
3411
 
2671
- return {
2672
- type: 'Identifier',
2673
- name
2674
- };
3412
+ const name = '__' + objectName + '_' + node.property.name;
3413
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3414
+
3415
+ return {
3416
+ type: 'Identifier',
3417
+ name
3418
+ };
3419
+ })();
3420
+
3421
+ if (out) return out;
2675
3422
  }
2676
3423
 
2677
3424
  for (const x in node) {
@@ -2685,8 +3432,8 @@ const objectHack = node => {
2685
3432
  };
2686
3433
 
2687
3434
  const generateFunc = (scope, decl) => {
2688
- if (decl.async) return todo('async functions are not supported');
2689
- if (decl.generator) return todo('generator functions are not supported');
3435
+ if (decl.async) return todo(scope, 'async functions are not supported');
3436
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2690
3437
 
2691
3438
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2692
3439
  const params = decl.params ?? [];
@@ -2702,6 +3449,11 @@ const generateFunc = (scope, decl) => {
2702
3449
  name
2703
3450
  };
2704
3451
 
3452
+ if (typedInput && decl.returnType) {
3453
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3454
+ innerScope.returns = [ valtypeBinary ];
3455
+ }
3456
+
2705
3457
  for (let i = 0; i < params.length; i++) {
2706
3458
  allocVar(innerScope, params[i].name, false);
2707
3459
 
@@ -2723,13 +3475,13 @@ const generateFunc = (scope, decl) => {
2723
3475
  const func = {
2724
3476
  name,
2725
3477
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2726
- returns: innerScope.returns,
2727
- locals: innerScope.locals,
2728
- throws: innerScope.throws,
2729
- index: currentFuncIndex++
3478
+ index: currentFuncIndex++,
3479
+ ...innerScope
2730
3480
  };
2731
3481
  funcIndex[name] = func.index;
2732
3482
 
3483
+ if (name === 'main') func.gotLastType = true;
3484
+
2733
3485
  // quick hack fixes
2734
3486
  for (const inst of wasm) {
2735
3487
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2781,7 +3533,7 @@ const internalConstrs = {
2781
3533
 
2782
3534
  // todo: check in wasm instead of here
2783
3535
  const literalValue = arg.value ?? 0;
2784
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3536
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2785
3537
 
2786
3538
  return [
2787
3539
  ...number(0, Valtype.i32),
@@ -2792,7 +3544,8 @@ const internalConstrs = {
2792
3544
  ...number(pointer)
2793
3545
  ];
2794
3546
  },
2795
- type: TYPES._array
3547
+ type: TYPES.array,
3548
+ length: 1
2796
3549
  },
2797
3550
 
2798
3551
  __Array_of: {
@@ -2803,27 +3556,134 @@ const internalConstrs = {
2803
3556
  elements: decl.arguments
2804
3557
  }, global, name);
2805
3558
  },
2806
- type: TYPES._array,
3559
+ type: TYPES.array,
3560
+ notConstr: true,
3561
+ length: 0
3562
+ },
3563
+
3564
+ __Porffor_fastOr: {
3565
+ generate: (scope, decl) => {
3566
+ const out = [];
3567
+
3568
+ for (let i = 0; i < decl.arguments.length; i++) {
3569
+ out.push(
3570
+ ...generate(scope, decl.arguments[i]),
3571
+ Opcodes.i32_to_u,
3572
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3573
+ );
3574
+ }
3575
+
3576
+ out.push(Opcodes.i32_from_u);
3577
+
3578
+ return out;
3579
+ },
3580
+ type: TYPES.boolean,
2807
3581
  notConstr: true
2808
- }
2809
- };
3582
+ },
3583
+
3584
+ __Porffor_fastAnd: {
3585
+ generate: (scope, decl) => {
3586
+ const out = [];
3587
+
3588
+ for (let i = 0; i < decl.arguments.length; i++) {
3589
+ out.push(
3590
+ ...generate(scope, decl.arguments[i]),
3591
+ Opcodes.i32_to_u,
3592
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3593
+ );
3594
+ }
3595
+
3596
+ out.push(Opcodes.i32_from_u);
3597
+
3598
+ return out;
3599
+ },
3600
+ type: TYPES.boolean,
3601
+ notConstr: true
3602
+ },
2810
3603
 
2811
- // const _ = Array.prototype.push;
2812
- // Array.prototype.push = function (a) {
2813
- // const check = arr => {
2814
- // for (const x of arr) {
2815
- // if (x === undefined) {
2816
- // console.trace(arr);
2817
- // process.exit();
2818
- // }
2819
- // if (Array.isArray(x)) check(x);
2820
- // }
2821
- // };
2822
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2823
- // // if (Array.isArray(a)) check(a);
3604
+ Boolean: {
3605
+ generate: (scope, decl) => {
3606
+ // todo: boolean object when used as constructor
3607
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3608
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3609
+ },
3610
+ type: TYPES.boolean,
3611
+ length: 1
3612
+ },
2824
3613
 
2825
- // return _.apply(this, arguments);
2826
- // };
3614
+ __Math_max: {
3615
+ generate: (scope, decl) => {
3616
+ const out = [
3617
+ ...number(-Infinity)
3618
+ ];
3619
+
3620
+ for (let i = 0; i < decl.arguments.length; i++) {
3621
+ out.push(
3622
+ ...generate(scope, decl.arguments[i]),
3623
+ [ Opcodes.f64_max ]
3624
+ );
3625
+ }
3626
+
3627
+ return out;
3628
+ },
3629
+ type: TYPES.number,
3630
+ notConstr: true,
3631
+ length: 2
3632
+ },
3633
+
3634
+ __Math_min: {
3635
+ generate: (scope, decl) => {
3636
+ const out = [
3637
+ ...number(Infinity)
3638
+ ];
3639
+
3640
+ for (let i = 0; i < decl.arguments.length; i++) {
3641
+ out.push(
3642
+ ...generate(scope, decl.arguments[i]),
3643
+ [ Opcodes.f64_min ]
3644
+ );
3645
+ }
3646
+
3647
+ return out;
3648
+ },
3649
+ type: TYPES.number,
3650
+ notConstr: true,
3651
+ length: 2
3652
+ },
3653
+
3654
+ __console_log: {
3655
+ generate: (scope, decl) => {
3656
+ const out = [];
3657
+
3658
+ for (let i = 0; i < decl.arguments.length; i++) {
3659
+ out.push(
3660
+ ...generateCall(scope, {
3661
+ callee: {
3662
+ type: 'Identifier',
3663
+ name: '__Porffor_print'
3664
+ },
3665
+ arguments: [ decl.arguments[i] ]
3666
+ }),
3667
+
3668
+ // print space
3669
+ ...number(32),
3670
+ [ Opcodes.call, importedFuncs.printChar ]
3671
+ );
3672
+ }
3673
+
3674
+ // print newline
3675
+ out.push(
3676
+ ...number(10),
3677
+ [ Opcodes.call, importedFuncs.printChar ]
3678
+ );
3679
+
3680
+ return out;
3681
+ },
3682
+ type: TYPES.undefined,
3683
+ notConstr: true,
3684
+ length: 0
3685
+ }
3686
+ };
2827
3687
 
2828
3688
  export default program => {
2829
3689
  globals = {};
@@ -2833,20 +3693,23 @@ export default program => {
2833
3693
  funcs = [];
2834
3694
  funcIndex = {};
2835
3695
  depth = [];
2836
- arrays = new Map();
2837
3696
  pages = new Map();
2838
3697
  data = [];
2839
3698
  currentFuncIndex = importedFuncs.length;
2840
3699
 
2841
3700
  globalThis.valtype = 'f64';
2842
3701
 
2843
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3702
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2844
3703
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2845
3704
 
2846
3705
  globalThis.valtypeBinary = Valtype[valtype];
2847
3706
 
2848
3707
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2849
3708
 
3709
+ globalThis.pageSize = PageSize;
3710
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3711
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3712
+
2850
3713
  // set generic opcodes for current valtype
2851
3714
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2852
3715
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2855,10 +3718,10 @@ export default program => {
2855
3718
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2856
3719
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2857
3720
 
2858
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2859
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2860
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2861
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3721
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3722
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3723
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3724
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2862
3725
 
2863
3726
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2864
3727
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2871,10 +3734,6 @@ export default program => {
2871
3734
 
2872
3735
  program.id = { name: 'main' };
2873
3736
 
2874
- globalThis.pageSize = PageSize;
2875
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2876
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2877
-
2878
3737
  const scope = {
2879
3738
  locals: {},
2880
3739
  localInd: 0
@@ -2885,7 +3744,7 @@ export default program => {
2885
3744
  body: program.body
2886
3745
  };
2887
3746
 
2888
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3747
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2889
3748
 
2890
3749
  generateFunc(scope, program);
2891
3750
 
@@ -2902,7 +3761,11 @@ export default program => {
2902
3761
  }
2903
3762
 
2904
3763
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2905
- main.returns = [];
3764
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3765
+ main.wasm.splice(main.wasm.length - 1, 1);
3766
+ } else {
3767
+ main.returns = [];
3768
+ }
2906
3769
  }
2907
3770
 
2908
3771
  if (lastInst[0] === Opcodes.call) {