porffor 0.2.0-e562242 → 0.2.0-e69a2a2

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 (55) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +150 -89
  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} +63 -15
  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} +1210 -446
  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 +51 -36
  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 +30 -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/node_trace.1.log +0 -1
  51. package/runner/info.js +0 -89
  52. package/runner/profile.js +0 -46
  53. package/runner/results.json +0 -1
  54. package/runner/transform.js +0 -15
  55. package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,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;
211
247
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
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
+ }
258
+
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?
@@ -1227,28 +1443,11 @@ const getNodeType = (scope, node) => {
1227
1443
  return ret;
1228
1444
  };
1229
1445
 
1230
- const toString = (scope, wasm, type) => {
1231
- const tmp = localTmp(scope, '#tostring_tmp');
1232
- return [
1233
- ...wasm,
1234
- [ Opcodes.local_set, tmp ],
1235
-
1236
- ...typeSwitch(scope, type, {
1237
- [TYPES.string]: [
1238
- [ Opcodes.local_get, tmp ]
1239
- ],
1240
- [TYPES.undefined]: [
1241
- // [ Opcodes.]
1242
- ]
1243
- })
1244
- ]
1245
- };
1246
-
1247
1446
  const generateLiteral = (scope, decl, global, name) => {
1248
1447
  if (decl.value === null) return number(NULL);
1249
1448
 
1449
+ // hack: just return 1 for regex literals
1250
1450
  if (decl.regex) {
1251
- scope.regex[name] = decl.regex;
1252
1451
  return number(1);
1253
1452
  }
1254
1453
 
@@ -1261,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
1261
1460
  return number(decl.value ? 1 : 0);
1262
1461
 
1263
1462
  case 'string':
1264
- const str = decl.value;
1265
- const rawElements = new Array(str.length);
1266
- let j = 0;
1267
- for (let i = 0; i < str.length; i++) {
1268
- rawElements[i] = str.charCodeAt(i);
1269
- }
1270
-
1271
- return makeArray(scope, {
1272
- rawElements
1273
- }, global, name, false, 'i16')[0];
1463
+ return makeString(scope, decl.value, global, name);
1274
1464
 
1275
1465
  default:
1276
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1466
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1277
1467
  }
1278
1468
  };
1279
1469
 
@@ -1282,6 +1472,8 @@ const countLeftover = wasm => {
1282
1472
 
1283
1473
  for (let i = 0; i < wasm.length; i++) {
1284
1474
  const inst = wasm[i];
1475
+ if (inst[0] == null) continue;
1476
+
1285
1477
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1286
1478
  if (inst[0] === Opcodes.if) count--;
1287
1479
  if (inst[1] !== Blocktype.void) count++;
@@ -1290,18 +1482,25 @@ const countLeftover = wasm => {
1290
1482
  if (inst[0] === Opcodes.end) depth--;
1291
1483
 
1292
1484
  if (depth === 0)
1293
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1294
- 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)) {}
1295
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1296
- 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;
1297
1489
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1298
1490
  else if (inst[0] === Opcodes.return) count = 0;
1299
1491
  else if (inst[0] === Opcodes.call) {
1300
1492
  let func = funcs.find(x => x.index === inst[1]);
1301
- if (func) {
1302
- count -= func.params.length;
1303
- } else count--;
1304
- 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
+ }
1305
1504
  } else count--;
1306
1505
 
1307
1506
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1319,7 +1518,7 @@ const disposeLeftover = wasm => {
1319
1518
  const generateExp = (scope, decl) => {
1320
1519
  const expression = decl.expression;
1321
1520
 
1322
- const out = generate(scope, expression);
1521
+ const out = generate(scope, expression, undefined, undefined, true);
1323
1522
  disposeLeftover(out);
1324
1523
 
1325
1524
  return out;
@@ -1377,7 +1576,7 @@ const RTArrayUtil = {
1377
1576
  ]
1378
1577
  };
1379
1578
 
1380
- const generateCall = (scope, decl, _global, _name) => {
1579
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1381
1580
  /* const callee = decl.callee;
1382
1581
  const args = decl.arguments;
1383
1582
 
@@ -1393,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
1393
1592
  name = func.name;
1394
1593
  }
1395
1594
 
1396
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1595
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1397
1596
  // literal eval hack
1398
- const code = decl.arguments[0].value;
1399
- 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
+ }
1400
1610
 
1401
1611
  const out = generate(scope, {
1402
1612
  type: 'BlockStatement',
@@ -1410,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
1410
1620
  const finalStatement = parsed.body[parsed.body.length - 1];
1411
1621
  out.push(
1412
1622
  ...getNodeType(scope, finalStatement),
1413
- setLastType(scope)
1623
+ ...setLastType(scope)
1414
1624
  );
1415
1625
  } else if (countLeftover(out) === 0) {
1416
1626
  out.push(...number(UNDEFINED));
1417
1627
  out.push(
1418
1628
  ...number(TYPES.undefined, Valtype.i32),
1419
- setLastType(scope)
1629
+ ...setLastType(scope)
1420
1630
  );
1421
1631
  }
1422
1632
 
@@ -1438,29 +1648,39 @@ const generateCall = (scope, decl, _global, _name) => {
1438
1648
 
1439
1649
  target = { ...decl.callee };
1440
1650
  target.name = spl.slice(0, -1).join('_');
1651
+
1652
+ // failed to lookup name, abort
1653
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1441
1654
  }
1442
1655
 
1443
1656
  // literal.func()
1444
1657
  if (!name && decl.callee.type === 'MemberExpression') {
1445
1658
  // megahack for /regex/.func()
1446
- if (decl.callee.object.regex) {
1447
- const funcName = decl.callee.property.name;
1448
- 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}`;
1449
1663
 
1450
- funcIndex[func.name] = func.index;
1451
- funcs.push(func);
1664
+ if (!funcIndex[rhemynName]) {
1665
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1452
1666
 
1667
+ funcIndex[func.name] = func.index;
1668
+ funcs.push(func);
1669
+ }
1670
+
1671
+ const idx = funcIndex[rhemynName];
1453
1672
  return [
1454
1673
  // make string arg
1455
1674
  ...generate(scope, decl.arguments[0]),
1675
+ Opcodes.i32_to_u,
1676
+ ...getNodeType(scope, decl.arguments[0]),
1456
1677
 
1457
1678
  // call regex func
1458
- Opcodes.i32_to_u,
1459
- [ Opcodes.call, func.index ],
1679
+ [ Opcodes.call, idx ],
1460
1680
  Opcodes.i32_from_u,
1461
1681
 
1462
1682
  ...number(TYPES.boolean, Valtype.i32),
1463
- setLastType(scope)
1683
+ ...setLastType(scope)
1464
1684
  ];
1465
1685
  }
1466
1686
 
@@ -1485,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
1485
1705
  // }
1486
1706
 
1487
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
+
1488
1728
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1489
- const f = prototypeFuncs[x][protoName];
1490
- if (f) acc[x] = f;
1729
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1491
1730
  return acc;
1492
1731
  }, {});
1493
1732
 
1494
- // no prototype function candidates, ignore
1495
1733
  if (Object.keys(protoCands).length > 0) {
1496
1734
  // use local for cached i32 length as commonly used
1497
1735
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1498
1736
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1499
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1500
1737
 
1501
1738
  // TODO: long-term, prototypes should be their individual separate funcs
1502
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;
1503
1749
  let lengthI32CacheUsed = false;
1504
- const protoBC = {};
1505
1750
  for (const x in protoCands) {
1506
1751
  const protoFunc = protoCands[x];
1507
1752
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1509,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1754
  ...RTArrayUtil.getLength(getPointer),
1510
1755
 
1511
1756
  ...number(TYPES.number, Valtype.i32),
1512
- setLastType(scope)
1757
+ ...setLastType(scope)
1513
1758
  ];
1514
1759
  continue;
1515
1760
  }
@@ -1519,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
1519
1764
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1520
1765
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1521
1766
 
1767
+ let optUnused = false;
1522
1768
  const protoOut = protoFunc(getPointer, {
1523
1769
  getCachedI32: () => {
1524
1770
  lengthI32CacheUsed = true;
@@ -1533,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
1533
1779
  return makeArray(scope, {
1534
1780
  rawElements: new Array(length)
1535
1781
  }, _global, _name, true, itemType);
1782
+ }, () => {
1783
+ optUnused = true;
1784
+ return unusedValue;
1536
1785
  });
1537
1786
 
1787
+ if (!optUnused) allOptUnused = false;
1788
+
1538
1789
  protoBC[x] = [
1539
- [ Opcodes.block, valtypeBinary ],
1790
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1540
1791
  ...protoOut,
1541
1792
 
1542
1793
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1543
- setLastType(scope),
1794
+ ...setLastType(scope),
1544
1795
  [ Opcodes.end ]
1545
1796
  ];
1546
1797
  }
1547
1798
 
1548
- return [
1549
- ...generate(scope, target),
1799
+ // todo: if some cands use optUnused and some don't, we will probably crash
1550
1800
 
1551
- Opcodes.i32_to_u,
1552
- [ Opcodes.local_set, pointerLocal ],
1801
+ return [
1802
+ ...(usePointerCache ? [
1803
+ ...rawPointer,
1804
+ [ Opcodes.local_set, pointerLocal ],
1805
+ ] : []),
1553
1806
 
1554
1807
  ...(!lengthI32CacheUsed ? [] : [
1555
1808
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1561,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
1561
1814
 
1562
1815
  // TODO: error better
1563
1816
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1564
- }, valtypeBinary),
1817
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1565
1818
  ];
1566
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
+ }
1567
1829
  }
1568
1830
 
1569
1831
  // TODO: only allows callee as literal
1570
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1832
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1571
1833
 
1572
1834
  let idx = funcIndex[name] ?? importedFuncs[name];
1573
1835
  if (idx === undefined && builtinFuncs[name]) {
@@ -1577,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
1577
1839
  idx = funcIndex[name];
1578
1840
 
1579
1841
  // infer arguments types from builtins params
1580
- const func = funcs.find(x => x.name === name);
1581
- for (let i = 0; i < decl.arguments.length; i++) {
1582
- const arg = decl.arguments[i];
1583
- if (!arg.name) continue;
1584
-
1585
- const local = scope.locals[arg.name];
1586
- if (!local) continue;
1587
-
1588
- local.type = func.params[i];
1589
- if (local.type === Valtype.v128) {
1590
- // specify vec subtype inferred from last vec type in function name
1591
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1592
- }
1593
- }
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
+ // }
1594
1856
  }
1595
1857
 
1596
1858
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1600,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
1600
1862
  idx = -1;
1601
1863
  }
1602
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
+
1603
1911
  if (idx === undefined) {
1604
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1605
- 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);
1606
1914
  }
1607
1915
 
1608
1916
  const func = funcs.find(x => x.index === idx);
1609
1917
 
1610
1918
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1611
- 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);
1612
1922
 
1613
1923
  let args = decl.arguments;
1614
1924
  if (func && args.length < paramCount) {
@@ -1624,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1934
  if (func && func.throws) scope.throws = true;
1625
1935
 
1626
1936
  let out = [];
1627
- for (const arg of args) {
1937
+ for (let i = 0; i < args.length; i++) {
1938
+ const arg = args[i];
1628
1939
  out = out.concat(generate(scope, arg));
1629
- 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));
1630
1950
  }
1631
1951
 
1632
1952
  out.push([ Opcodes.call, idx ]);
1633
1953
 
1634
- if (!userFunc) {
1954
+ if (!typedReturns) {
1635
1955
  // let type;
1636
1956
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1637
1957
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1641,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
1641
1961
  // ...number(type, Valtype.i32),
1642
1962
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1643
1963
  // );
1644
- } 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
+ }
1645
1969
 
1646
1970
  return out;
1647
1971
  };
@@ -1649,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
1649
1973
  const generateNew = (scope, decl, _global, _name) => {
1650
1974
  // hack: basically treat this as a normal call for builtins for now
1651
1975
  const name = mapName(decl.callee.name);
1976
+
1652
1977
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1653
- 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)})`);
1654
1991
 
1655
1992
  return generateCall(scope, decl, _global, _name);
1656
1993
  };
@@ -1767,12 +2104,14 @@ const brTable = (input, bc, returns) => {
1767
2104
  };
1768
2105
 
1769
2106
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2107
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
2108
+
1770
2109
  const known = knownType(scope, type);
1771
2110
  if (known != null) {
1772
2111
  return bc[known] ?? bc.default;
1773
2112
  }
1774
2113
 
1775
- if (process.argv.includes('-typeswitch-use-brtable'))
2114
+ if (Prefs.typeswitchUseBrtable)
1776
2115
  return brTable(type, bc, returns);
1777
2116
 
1778
2117
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1782,8 +2121,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1782
2121
  [ Opcodes.block, returns ]
1783
2122
  ];
1784
2123
 
1785
- // todo: use br_table?
1786
-
1787
2124
  for (const x in bc) {
1788
2125
  if (x === 'default') continue;
1789
2126
 
@@ -1807,7 +2144,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1807
2144
  return out;
1808
2145
  };
1809
2146
 
1810
- const allocVar = (scope, name, global = false) => {
2147
+ const allocVar = (scope, name, global = false, type = true) => {
1811
2148
  const target = global ? globals : scope.locals;
1812
2149
 
1813
2150
  // already declared
@@ -1821,8 +2158,10 @@ const allocVar = (scope, name, global = false) => {
1821
2158
  let idx = global ? globalInd++ : scope.localInd++;
1822
2159
  target[name] = { idx, type: valtypeBinary };
1823
2160
 
1824
- let typeIdx = global ? globalInd++ : scope.localInd++;
1825
- 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
+ }
1826
2165
 
1827
2166
  return idx;
1828
2167
  };
@@ -1837,11 +2176,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1837
2176
  };
1838
2177
 
1839
2178
  const typeAnnoToPorfType = x => {
1840
- if (TYPES[x]) return TYPES[x];
1841
- 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()];
1842
2182
 
1843
2183
  switch (x) {
1844
2184
  case 'i32':
2185
+ case 'i64':
2186
+ case 'f64':
1845
2187
  return TYPES.number;
1846
2188
  }
1847
2189
 
@@ -1852,7 +2194,7 @@ const extractTypeAnnotation = decl => {
1852
2194
  let a = decl;
1853
2195
  while (a.typeAnnotation) a = a.typeAnnotation;
1854
2196
 
1855
- let type, elementType;
2197
+ let type = null, elementType = null;
1856
2198
  if (a.typeName) {
1857
2199
  type = a.typeName.name;
1858
2200
  } else if (a.type.endsWith('Keyword')) {
@@ -1865,6 +2207,8 @@ const extractTypeAnnotation = decl => {
1865
2207
  const typeName = type;
1866
2208
  type = typeAnnoToPorfType(type);
1867
2209
 
2210
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
2211
+
1868
2212
  // if (decl.name) console.log(decl.name, { type, elementType });
1869
2213
 
1870
2214
  return { type, typeName, elementType };
@@ -1877,10 +2221,13 @@ const generateVar = (scope, decl) => {
1877
2221
 
1878
2222
  // global variable if in top scope (main) and var ..., or if wanted
1879
2223
  const global = topLevel || decl._bare; // decl.kind === 'var';
2224
+ const target = global ? globals : scope.locals;
1880
2225
 
1881
2226
  for (const x of decl.declarations) {
1882
2227
  const name = mapName(x.id.name);
1883
2228
 
2229
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2230
+
1884
2231
  if (x.init && isFuncType(x.init.type)) {
1885
2232
  // hack for let a = function () { ... }
1886
2233
  x.init.id = { name };
@@ -1896,16 +2243,29 @@ const generateVar = (scope, decl) => {
1896
2243
  continue; // always ignore
1897
2244
  }
1898
2245
 
1899
- let idx = allocVar(scope, name, global);
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));
1900
2252
 
1901
- if (typedInput && x.id.typeAnnotation) {
2253
+ if (typed) {
1902
2254
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1903
2255
  }
1904
2256
 
1905
2257
  if (x.init) {
1906
- out = out.concat(generate(scope, x.init, global, name));
1907
-
1908
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
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
+ }
1909
2269
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1910
2270
  }
1911
2271
 
@@ -1916,7 +2276,8 @@ const generateVar = (scope, decl) => {
1916
2276
  return out;
1917
2277
  };
1918
2278
 
1919
- const generateAssign = (scope, decl) => {
2279
+ // todo: optimize this func for valueUnused
2280
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1920
2281
  const { type, name } = decl.left;
1921
2282
 
1922
2283
  if (type === 'ObjectPattern') {
@@ -1931,22 +2292,30 @@ const generateAssign = (scope, decl) => {
1931
2292
  return [];
1932
2293
  }
1933
2294
 
2295
+ const op = decl.operator.slice(0, -1) || '=';
2296
+
1934
2297
  // hack: .length setter
1935
2298
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1936
2299
  const name = decl.left.object.name;
1937
- const pointer = arrays.get(name);
2300
+ const pointer = scope.arrays?.get(name);
1938
2301
 
1939
- const aotPointer = pointer != null;
2302
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1940
2303
 
1941
2304
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2305
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1942
2306
 
1943
2307
  return [
1944
2308
  ...(aotPointer ? number(0, Valtype.i32) : [
1945
2309
  ...generate(scope, decl.left.object),
1946
2310
  Opcodes.i32_to_u
1947
2311
  ]),
2312
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1948
2313
 
1949
- ...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))),
1950
2319
  [ Opcodes.local_tee, newValueTmp ],
1951
2320
 
1952
2321
  Opcodes.i32_to_u,
@@ -1956,21 +2325,19 @@ const generateAssign = (scope, decl) => {
1956
2325
  ];
1957
2326
  }
1958
2327
 
1959
- const op = decl.operator.slice(0, -1) || '=';
1960
-
1961
2328
  // arr[i]
1962
2329
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1963
2330
  const name = decl.left.object.name;
1964
- const pointer = arrays.get(name);
2331
+ const pointer = scope.arrays?.get(name);
1965
2332
 
1966
- const aotPointer = pointer != null;
2333
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1967
2334
 
1968
2335
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1969
2336
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1970
2337
 
1971
2338
  return [
1972
2339
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1973
- [TYPES._array]: [
2340
+ [TYPES.array]: [
1974
2341
  ...(aotPointer ? [] : [
1975
2342
  ...generate(scope, decl.left.object),
1976
2343
  Opcodes.i32_to_u
@@ -2019,6 +2386,8 @@ const generateAssign = (scope, decl) => {
2019
2386
  ];
2020
2387
  }
2021
2388
 
2389
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2390
+
2022
2391
  const [ local, isGlobal ] = lookupName(scope, name);
2023
2392
 
2024
2393
  if (local === undefined) {
@@ -2065,9 +2434,7 @@ const generateAssign = (scope, decl) => {
2065
2434
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2066
2435
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2067
2436
 
2068
- getLastType(scope),
2069
- // hack: type is idx+1
2070
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2437
+ ...setType(scope, name, getLastType(scope))
2071
2438
  ];
2072
2439
  }
2073
2440
 
@@ -2078,9 +2445,7 @@ const generateAssign = (scope, decl) => {
2078
2445
 
2079
2446
  // todo: string concat types
2080
2447
 
2081
- // hack: type is idx+1
2082
- ...number(TYPES.number, Valtype.i32),
2083
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2448
+ ...setType(scope, name, TYPES.number)
2084
2449
  ];
2085
2450
  };
2086
2451
 
@@ -2126,7 +2491,7 @@ const generateUnary = (scope, decl) => {
2126
2491
  return out;
2127
2492
  }
2128
2493
 
2129
- case 'delete':
2494
+ case 'delete': {
2130
2495
  let toReturn = true, toGenerate = true;
2131
2496
 
2132
2497
  if (decl.argument.type === 'Identifier') {
@@ -2148,38 +2513,60 @@ const generateUnary = (scope, decl) => {
2148
2513
 
2149
2514
  out.push(...number(toReturn ? 1 : 0));
2150
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
+ }
2151
2531
 
2152
- case 'typeof':
2153
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2532
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2533
+ disposeLeftover(out);
2534
+
2535
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2154
2536
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2155
2537
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2156
2538
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2157
2539
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2158
2540
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2159
2541
 
2542
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2543
+
2160
2544
  // object and internal types
2161
2545
  default: makeString(scope, 'object', false, '#typeof_result'),
2162
- });
2546
+ }));
2547
+
2548
+ return out;
2549
+ }
2163
2550
 
2164
2551
  default:
2165
- return todo(`unary operator ${decl.operator} not implemented yet`);
2552
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2166
2553
  }
2167
2554
  };
2168
2555
 
2169
- const generateUpdate = (scope, decl) => {
2556
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2170
2557
  const { name } = decl.argument;
2171
2558
 
2172
2559
  const [ local, isGlobal ] = lookupName(scope, name);
2173
2560
 
2174
2561
  if (local === undefined) {
2175
- return todo(`update expression with undefined variable`);
2562
+ return todo(scope, `update expression with undefined variable`, true);
2176
2563
  }
2177
2564
 
2178
2565
  const idx = local.idx;
2179
2566
  const out = [];
2180
2567
 
2181
2568
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2182
- 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 ]);
2183
2570
 
2184
2571
  switch (decl.operator) {
2185
2572
  case '++':
@@ -2192,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
2192
2579
  }
2193
2580
 
2194
2581
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2195
- 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 ]);
2196
2583
 
2197
2584
  return out;
2198
2585
  };
@@ -2232,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2232
2619
  // note type
2233
2620
  out.push(
2234
2621
  ...getNodeType(scope, decl.consequent),
2235
- setLastType(scope)
2622
+ ...setLastType(scope)
2236
2623
  );
2237
2624
 
2238
2625
  out.push([ Opcodes.else ]);
@@ -2241,7 +2628,7 @@ const generateConditional = (scope, decl) => {
2241
2628
  // note type
2242
2629
  out.push(
2243
2630
  ...getNodeType(scope, decl.alternate),
2244
- setLastType(scope)
2631
+ ...setLastType(scope)
2245
2632
  );
2246
2633
 
2247
2634
  out.push([ Opcodes.end ]);
@@ -2255,15 +2642,17 @@ const generateFor = (scope, decl) => {
2255
2642
  const out = [];
2256
2643
 
2257
2644
  if (decl.init) {
2258
- out.push(...generate(scope, decl.init));
2645
+ out.push(...generate(scope, decl.init, false, undefined, true));
2259
2646
  disposeLeftover(out);
2260
2647
  }
2261
2648
 
2262
2649
  out.push([ Opcodes.loop, Blocktype.void ]);
2263
2650
  depth.push('for');
2264
2651
 
2265
- out.push(...generate(scope, decl.test));
2266
- 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 ]);
2267
2656
  depth.push('if');
2268
2657
 
2269
2658
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2271,8 +2660,7 @@ const generateFor = (scope, decl) => {
2271
2660
  out.push(...generate(scope, decl.body));
2272
2661
  out.push([ Opcodes.end ]);
2273
2662
 
2274
- out.push(...generate(scope, decl.update));
2275
- depth.pop();
2663
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2276
2664
 
2277
2665
  out.push([ Opcodes.br, 1 ]);
2278
2666
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2300,6 +2688,36 @@ const generateWhile = (scope, decl) => {
2300
2688
  return out;
2301
2689
  };
2302
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
+
2303
2721
  const generateForOf = (scope, decl) => {
2304
2722
  const out = [];
2305
2723
 
@@ -2329,8 +2747,17 @@ const generateForOf = (scope, decl) => {
2329
2747
  // setup local for left
2330
2748
  generate(scope, decl.left);
2331
2749
 
2332
- 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
+
2333
2759
  const [ local, isGlobal ] = lookupName(scope, leftName);
2760
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2334
2761
 
2335
2762
  depth.push('block');
2336
2763
  depth.push('block');
@@ -2338,15 +2765,17 @@ const generateForOf = (scope, decl) => {
2338
2765
  // // todo: we should only do this for strings but we don't know at compile-time :(
2339
2766
  // hack: this is naughty and will break things!
2340
2767
  let newOut = number(0, Valtype.f64), newPointer = -1;
2341
- 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?
2342
2770
  0, [ newOut, newPointer ] = makeArray(scope, {
2343
2771
  rawElements: new Array(1)
2344
2772
  }, isGlobal, leftName, true, 'i16');
2345
2773
  }
2346
2774
 
2347
2775
  // set type for local
2776
+ // todo: optimize away counter and use end pointer
2348
2777
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2349
- [TYPES._array]: [
2778
+ [TYPES.array]: [
2350
2779
  ...setType(scope, leftName, TYPES.number),
2351
2780
 
2352
2781
  [ Opcodes.loop, Blocktype.void ],
@@ -2429,6 +2858,56 @@ const generateForOf = (scope, decl) => {
2429
2858
  [ Opcodes.end ],
2430
2859
  [ Opcodes.end ]
2431
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
+ ],
2432
2911
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2433
2912
  }, Blocktype.void));
2434
2913
 
@@ -2439,28 +2918,65 @@ const generateForOf = (scope, decl) => {
2439
2918
  return out;
2440
2919
  };
2441
2920
 
2921
+ // find the nearest loop in depth map by type
2442
2922
  const getNearestLoop = () => {
2443
2923
  for (let i = depth.length - 1; i >= 0; i--) {
2444
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2924
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2445
2925
  }
2446
2926
 
2447
2927
  return -1;
2448
2928
  };
2449
2929
 
2450
2930
  const generateBreak = (scope, decl) => {
2451
- 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
+
2452
2946
  return [
2453
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2947
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2454
2948
  ];
2455
2949
  };
2456
2950
 
2457
2951
  const generateContinue = (scope, decl) => {
2458
- 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
+
2459
2966
  return [
2460
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2967
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2461
2968
  ];
2462
2969
  };
2463
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
+
2464
2980
  const generateThrow = (scope, decl) => {
2465
2981
  scope.throws = true;
2466
2982
 
@@ -2469,7 +2985,7 @@ const generateThrow = (scope, decl) => {
2469
2985
  // hack: throw new X("...") -> throw "..."
2470
2986
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2471
2987
  constructor = decl.argument.callee.name;
2472
- message = decl.argument.arguments[0].value;
2988
+ message = decl.argument.arguments[0]?.value ?? '';
2473
2989
  }
2474
2990
 
2475
2991
  if (tags.length === 0) tags.push({
@@ -2481,6 +2997,9 @@ const generateThrow = (scope, decl) => {
2481
2997
  let exceptId = exceptions.push({ constructor, message }) - 1;
2482
2998
  let tagIdx = tags[0].idx;
2483
2999
 
3000
+ scope.exceptions ??= [];
3001
+ scope.exceptions.push(exceptId);
3002
+
2484
3003
  // todo: write a description of how this works lol
2485
3004
 
2486
3005
  return [
@@ -2490,7 +3009,7 @@ const generateThrow = (scope, decl) => {
2490
3009
  };
2491
3010
 
2492
3011
  const generateTry = (scope, decl) => {
2493
- if (decl.finalizer) return todo('try finally not implemented yet');
3012
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2494
3013
 
2495
3014
  const out = [];
2496
3015
 
@@ -2521,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
2521
3040
  // TODO
2522
3041
  // if identifier declared, use that
2523
3042
  // else, use default (right)
2524
- return todo('assignment pattern (optional arg)');
3043
+ return todo(scope, 'assignment pattern (optional arg)');
2525
3044
  };
2526
3045
 
2527
3046
  let pages = new Map();
2528
- const allocPage = (reason, type) => {
3047
+ const allocPage = (scope, reason, type) => {
2529
3048
  if (pages.has(reason)) return pages.get(reason).ind;
2530
3049
 
2531
3050
  if (reason.startsWith('array:')) pages.hasArray = true;
2532
3051
  if (reason.startsWith('string:')) pages.hasString = true;
3052
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3053
+ if (reason.includes('string:')) pages.hasAnyString = true;
2533
3054
 
2534
3055
  const ind = pages.size;
2535
3056
  pages.set(reason, { ind, type });
2536
3057
 
2537
- 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})`);
2538
3062
 
2539
3063
  return ind;
2540
3064
  };
2541
3065
 
3066
+ // todo: add scope.pages
2542
3067
  const freePage = reason => {
2543
3068
  const { ind } = pages.get(reason);
2544
3069
  pages.delete(reason);
2545
3070
 
2546
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3071
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2547
3072
 
2548
3073
  return ind;
2549
3074
  };
@@ -2563,38 +3088,53 @@ const StoreOps = {
2563
3088
  f64: Opcodes.f64_store,
2564
3089
 
2565
3090
  // expects i32 input!
2566
- i16: Opcodes.i32_store16
3091
+ i8: Opcodes.i32_store8,
3092
+ i16: Opcodes.i32_store16,
2567
3093
  };
2568
3094
 
2569
3095
  let data = [];
2570
3096
 
2571
- const compileBytes = (val, itemType, signed = true) => {
3097
+ const compileBytes = (val, itemType) => {
2572
3098
  // todo: this is a mess and needs confirming / ????
2573
3099
  switch (itemType) {
2574
3100
  case 'i8': return [ val % 256 ];
2575
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2576
-
2577
- case 'i32':
2578
- case 'i64':
2579
- 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
2580
3105
 
2581
3106
  case 'f64': return ieee754_binary64(val);
2582
3107
  }
2583
3108
  };
2584
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
+
2585
3119
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2586
3120
  const out = [];
2587
3121
 
3122
+ scope.arrays ??= new Map();
3123
+
2588
3124
  let firstAssign = false;
2589
- if (!arrays.has(name) || name === '$undeclared') {
3125
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2590
3126
  firstAssign = true;
2591
3127
 
2592
3128
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2593
3129
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2594
- 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);
2595
3133
  }
2596
3134
 
2597
- const pointer = arrays.get(name);
3135
+ const pointer = scope.arrays.get(name);
3136
+
3137
+ const local = global ? globals[name] : scope.locals[name];
2598
3138
 
2599
3139
  const useRawElements = !!decl.rawElements;
2600
3140
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2602,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2602
3142
  const valtype = itemTypeToValtype[itemType];
2603
3143
  const length = elements.length;
2604
3144
 
2605
- if (firstAssign && useRawElements) {
2606
- 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');
2607
3149
 
2608
- if (!initEmpty) for (let i = 0; i < length; i++) {
2609
- if (elements[i] == null) continue;
3150
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3151
+ if (elements[i] == null) continue;
2610
3152
 
2611
- bytes.push(...compileBytes(elements[i], itemType));
2612
- }
3153
+ bytes.push(...compileBytes(elements[i], itemType));
3154
+ }
2613
3155
 
2614
- data.push({
2615
- offset: pointer,
2616
- bytes
2617
- });
3156
+ const ind = data.push({
3157
+ offset: pointer,
3158
+ bytes
3159
+ }) - 1;
3160
+
3161
+ scope.data ??= [];
3162
+ scope.data.push(ind);
3163
+ }
2618
3164
 
2619
3165
  // local value as pointer
2620
3166
  out.push(...number(pointer));
@@ -2622,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2622
3168
  return [ out, pointer ];
2623
3169
  }
2624
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
+
2625
3182
  // store length as 0th array
2626
3183
  out.push(
2627
- ...number(0, Valtype.i32),
3184
+ ...pointerWasm,
2628
3185
  ...number(length, Valtype.i32),
2629
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3186
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2630
3187
  );
2631
3188
 
2632
3189
  const storeOp = StoreOps[itemType];
@@ -2635,43 +3192,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2635
3192
  if (elements[i] == null) continue;
2636
3193
 
2637
3194
  out.push(
2638
- ...number(0, Valtype.i32),
3195
+ ...pointerWasm,
2639
3196
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2640
- [ 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]) ]
2641
3198
  );
2642
3199
  }
2643
3200
 
2644
3201
  // local value as pointer
2645
- out.push(...number(pointer));
3202
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2646
3203
 
2647
3204
  return [ out, pointer ];
2648
3205
  };
2649
3206
 
2650
- 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) => {
2651
3218
  const rawElements = new Array(str.length);
3219
+ let byteStringable = Prefs.bytestring;
2652
3220
  for (let i = 0; i < str.length; i++) {
2653
- rawElements[i] = str.charCodeAt(i);
3221
+ const c = str.charCodeAt(i);
3222
+ rawElements[i] = c;
3223
+
3224
+ if (byteStringable && c > 0xFF) byteStringable = false;
2654
3225
  }
2655
3226
 
3227
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3228
+
2656
3229
  return makeArray(scope, {
2657
3230
  rawElements
2658
- }, global, name, false, 'i16')[0];
3231
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2659
3232
  };
2660
3233
 
2661
- let arrays = new Map();
2662
3234
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2663
3235
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2664
3236
  };
2665
3237
 
2666
3238
  export const generateMember = (scope, decl, _global, _name) => {
2667
3239
  const name = decl.object.name;
2668
- const pointer = arrays.get(name);
3240
+ const pointer = scope.arrays?.get(name);
2669
3241
 
2670
- 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
+ let nameProp = name;
3248
+
3249
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3250
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3251
+
3252
+ return makeString(scope, name, _global, _name, true);
3253
+ } else {
3254
+ return generate(scope, DEFAULT_VALUE);
3255
+ }
3256
+ }
2671
3257
 
2672
3258
  // hack: .length
2673
3259
  if (decl.property.name === 'length') {
2674
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3260
+ const func = funcs.find(x => x.name === name);
3261
+ if (func) {
3262
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3263
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3264
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3265
+ }
3266
+
3267
+ if (builtinFuncs[name + '$constructor']) {
3268
+ const regularFunc = builtinFuncs[name];
3269
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3270
+
3271
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3272
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3273
+
3274
+ return number(Math.max(regularParams, constructorParams));
3275
+ }
3276
+
3277
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3278
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3279
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3280
+
2675
3281
  return [
2676
3282
  ...(aotPointer ? number(0, Valtype.i32) : [
2677
3283
  ...generate(scope, decl.object),
@@ -2683,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
2683
3289
  ];
2684
3290
  }
2685
3291
 
3292
+ const object = generate(scope, decl.object);
3293
+ const property = generate(scope, decl.property);
3294
+
2686
3295
  // // todo: we should only do this for strings but we don't know at compile-time :(
2687
3296
  // hack: this is naughty and will break things!
2688
3297
  let newOut = number(0, valtypeBinary), newPointer = -1;
2689
- if (pages.hasString) {
3298
+ if (pages.hasAnyString) {
2690
3299
  0, [ newOut, newPointer ] = makeArray(scope, {
2691
3300
  rawElements: new Array(1)
2692
3301
  }, _global, _name, true, 'i16');
2693
3302
  }
2694
3303
 
2695
3304
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2696
- [TYPES._array]: [
3305
+ [TYPES.array]: [
2697
3306
  // get index as valtype
2698
- ...generate(scope, decl.property),
3307
+ ...property,
2699
3308
 
2700
3309
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2701
3310
  Opcodes.i32_to_u,
@@ -2703,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2703
3312
  [ Opcodes.i32_mul ],
2704
3313
 
2705
3314
  ...(aotPointer ? [] : [
2706
- ...generate(scope, decl.object),
3315
+ ...object,
2707
3316
  Opcodes.i32_to_u,
2708
3317
  [ Opcodes.i32_add ]
2709
3318
  ]),
@@ -2712,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2712
3321
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2713
3322
 
2714
3323
  ...number(TYPES.number, Valtype.i32),
2715
- setLastType(scope)
3324
+ ...setLastType(scope)
2716
3325
  ],
2717
3326
 
2718
3327
  [TYPES.string]: [
@@ -2722,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
3331
 
2723
3332
  ...number(0, Valtype.i32), // base 0 for store later
2724
3333
 
2725
- ...generate(scope, decl.property),
2726
-
3334
+ ...property,
2727
3335
  Opcodes.i32_to_u,
3336
+
2728
3337
  ...number(ValtypeSize.i16, Valtype.i32),
2729
3338
  [ Opcodes.i32_mul ],
2730
3339
 
2731
3340
  ...(aotPointer ? [] : [
2732
- ...generate(scope, decl.object),
3341
+ ...object,
2733
3342
  Opcodes.i32_to_u,
2734
3343
  [ Opcodes.i32_add ]
2735
3344
  ]),
@@ -2744,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2744
3353
  ...number(newPointer),
2745
3354
 
2746
3355
  ...number(TYPES.string, Valtype.i32),
2747
- setLastType(scope)
3356
+ ...setLastType(scope)
3357
+ ],
3358
+ [TYPES.bytestring]: [
3359
+ // setup new/out array
3360
+ ...newOut,
3361
+ [ Opcodes.drop ],
3362
+
3363
+ ...number(0, Valtype.i32), // base 0 for store later
3364
+
3365
+ ...property,
3366
+ Opcodes.i32_to_u,
3367
+
3368
+ ...(aotPointer ? [] : [
3369
+ ...object,
3370
+ Opcodes.i32_to_u,
3371
+ [ Opcodes.i32_add ]
3372
+ ]),
3373
+
3374
+ // load current string ind {arg}
3375
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3376
+
3377
+ // store to new string ind 0
3378
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3379
+
3380
+ // return new string (page)
3381
+ ...number(newPointer),
3382
+
3383
+ ...number(TYPES.bytestring, Valtype.i32),
3384
+ ...setLastType(scope)
2748
3385
  ],
2749
3386
 
2750
- default: [ [ Opcodes.unreachable ] ]
3387
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2751
3388
  });
2752
3389
  };
2753
3390
 
@@ -2757,25 +3394,36 @@ const objectHack = node => {
2757
3394
  if (!node) return node;
2758
3395
 
2759
3396
  if (node.type === 'MemberExpression') {
2760
- if (node.computed || node.optional) return node;
3397
+ const out = (() => {
3398
+ if (node.computed || node.optional) return;
3399
+
3400
+ let objectName = node.object.name;
2761
3401
 
2762
- let objectName = node.object.name;
3402
+ // if object is not identifier or another member exp, give up
3403
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3404
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2763
3405
 
2764
- // if object is not identifier or another member exp, give up
2765
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3406
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2766
3407
 
2767
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3408
+ // if .name or .length, give up (hack within a hack!)
3409
+ if (['name', 'length'].includes(node.property.name)) {
3410
+ node.object = objectHack(node.object);
3411
+ return;
3412
+ }
2768
3413
 
2769
- // if .length, give up (hack within a hack!)
2770
- if (node.property.name === 'length') return node;
3414
+ // no object name, give up
3415
+ if (!objectName) return;
2771
3416
 
2772
- const name = '__' + objectName + '_' + node.property.name;
2773
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3417
+ const name = '__' + objectName + '_' + node.property.name;
3418
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2774
3419
 
2775
- return {
2776
- type: 'Identifier',
2777
- name
2778
- };
3420
+ return {
3421
+ type: 'Identifier',
3422
+ name
3423
+ };
3424
+ })();
3425
+
3426
+ if (out) return out;
2779
3427
  }
2780
3428
 
2781
3429
  for (const x in node) {
@@ -2789,8 +3437,8 @@ const objectHack = node => {
2789
3437
  };
2790
3438
 
2791
3439
  const generateFunc = (scope, decl) => {
2792
- if (decl.async) return todo('async functions are not supported');
2793
- if (decl.generator) return todo('generator functions are not supported');
3440
+ if (decl.async) return todo(scope, 'async functions are not supported');
3441
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2794
3442
 
2795
3443
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2796
3444
  const params = decl.params ?? [];
@@ -2806,6 +3454,11 @@ const generateFunc = (scope, decl) => {
2806
3454
  name
2807
3455
  };
2808
3456
 
3457
+ if (typedInput && decl.returnType) {
3458
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3459
+ innerScope.returns = [ valtypeBinary ];
3460
+ }
3461
+
2809
3462
  for (let i = 0; i < params.length; i++) {
2810
3463
  allocVar(innerScope, params[i].name, false);
2811
3464
 
@@ -2827,13 +3480,13 @@ const generateFunc = (scope, decl) => {
2827
3480
  const func = {
2828
3481
  name,
2829
3482
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2830
- returns: innerScope.returns,
2831
- locals: innerScope.locals,
2832
- throws: innerScope.throws,
2833
- index: currentFuncIndex++
3483
+ index: currentFuncIndex++,
3484
+ ...innerScope
2834
3485
  };
2835
3486
  funcIndex[name] = func.index;
2836
3487
 
3488
+ if (name === 'main') func.gotLastType = true;
3489
+
2837
3490
  // quick hack fixes
2838
3491
  for (const inst of wasm) {
2839
3492
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2885,7 +3538,7 @@ const internalConstrs = {
2885
3538
 
2886
3539
  // todo: check in wasm instead of here
2887
3540
  const literalValue = arg.value ?? 0;
2888
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3541
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2889
3542
 
2890
3543
  return [
2891
3544
  ...number(0, Valtype.i32),
@@ -2896,7 +3549,8 @@ const internalConstrs = {
2896
3549
  ...number(pointer)
2897
3550
  ];
2898
3551
  },
2899
- type: TYPES._array
3552
+ type: TYPES.array,
3553
+ length: 1
2900
3554
  },
2901
3555
 
2902
3556
  __Array_of: {
@@ -2907,27 +3561,134 @@ const internalConstrs = {
2907
3561
  elements: decl.arguments
2908
3562
  }, global, name);
2909
3563
  },
2910
- type: TYPES._array,
3564
+ type: TYPES.array,
3565
+ notConstr: true,
3566
+ length: 0
3567
+ },
3568
+
3569
+ __Porffor_fastOr: {
3570
+ generate: (scope, decl) => {
3571
+ const out = [];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ Opcodes.i32_to_u,
3577
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3578
+ );
3579
+ }
3580
+
3581
+ out.push(Opcodes.i32_from_u);
3582
+
3583
+ return out;
3584
+ },
3585
+ type: TYPES.boolean,
2911
3586
  notConstr: true
2912
- }
2913
- };
3587
+ },
3588
+
3589
+ __Porffor_fastAnd: {
3590
+ generate: (scope, decl) => {
3591
+ const out = [];
2914
3592
 
2915
- // const _ = Array.prototype.push;
2916
- // Array.prototype.push = function (a) {
2917
- // const check = arr => {
2918
- // for (const x of arr) {
2919
- // if (x === undefined) {
2920
- // console.trace(arr);
2921
- // process.exit();
2922
- // }
2923
- // if (Array.isArray(x)) check(x);
2924
- // }
2925
- // };
2926
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2927
- // // if (Array.isArray(a)) check(a);
3593
+ for (let i = 0; i < decl.arguments.length; i++) {
3594
+ out.push(
3595
+ ...generate(scope, decl.arguments[i]),
3596
+ Opcodes.i32_to_u,
3597
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3598
+ );
3599
+ }
3600
+
3601
+ out.push(Opcodes.i32_from_u);
3602
+
3603
+ return out;
3604
+ },
3605
+ type: TYPES.boolean,
3606
+ notConstr: true
3607
+ },
2928
3608
 
2929
- // return _.apply(this, arguments);
2930
- // };
3609
+ Boolean: {
3610
+ generate: (scope, decl) => {
3611
+ // todo: boolean object when used as constructor
3612
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3613
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3614
+ },
3615
+ type: TYPES.boolean,
3616
+ length: 1
3617
+ },
3618
+
3619
+ __Math_max: {
3620
+ generate: (scope, decl) => {
3621
+ const out = [
3622
+ ...number(-Infinity)
3623
+ ];
3624
+
3625
+ for (let i = 0; i < decl.arguments.length; i++) {
3626
+ out.push(
3627
+ ...generate(scope, decl.arguments[i]),
3628
+ [ Opcodes.f64_max ]
3629
+ );
3630
+ }
3631
+
3632
+ return out;
3633
+ },
3634
+ type: TYPES.number,
3635
+ notConstr: true,
3636
+ length: 2
3637
+ },
3638
+
3639
+ __Math_min: {
3640
+ generate: (scope, decl) => {
3641
+ const out = [
3642
+ ...number(Infinity)
3643
+ ];
3644
+
3645
+ for (let i = 0; i < decl.arguments.length; i++) {
3646
+ out.push(
3647
+ ...generate(scope, decl.arguments[i]),
3648
+ [ Opcodes.f64_min ]
3649
+ );
3650
+ }
3651
+
3652
+ return out;
3653
+ },
3654
+ type: TYPES.number,
3655
+ notConstr: true,
3656
+ length: 2
3657
+ },
3658
+
3659
+ __console_log: {
3660
+ generate: (scope, decl) => {
3661
+ const out = [];
3662
+
3663
+ for (let i = 0; i < decl.arguments.length; i++) {
3664
+ out.push(
3665
+ ...generateCall(scope, {
3666
+ callee: {
3667
+ type: 'Identifier',
3668
+ name: '__Porffor_print'
3669
+ },
3670
+ arguments: [ decl.arguments[i] ]
3671
+ }),
3672
+
3673
+ // print space
3674
+ ...number(32),
3675
+ [ Opcodes.call, importedFuncs.printChar ]
3676
+ );
3677
+ }
3678
+
3679
+ // print newline
3680
+ out.push(
3681
+ ...number(10),
3682
+ [ Opcodes.call, importedFuncs.printChar ]
3683
+ );
3684
+
3685
+ return out;
3686
+ },
3687
+ type: TYPES.undefined,
3688
+ notConstr: true,
3689
+ length: 0
3690
+ }
3691
+ };
2931
3692
 
2932
3693
  export default program => {
2933
3694
  globals = {};
@@ -2937,20 +3698,23 @@ export default program => {
2937
3698
  funcs = [];
2938
3699
  funcIndex = {};
2939
3700
  depth = [];
2940
- arrays = new Map();
2941
3701
  pages = new Map();
2942
3702
  data = [];
2943
3703
  currentFuncIndex = importedFuncs.length;
2944
3704
 
2945
3705
  globalThis.valtype = 'f64';
2946
3706
 
2947
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3707
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2948
3708
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2949
3709
 
2950
3710
  globalThis.valtypeBinary = Valtype[valtype];
2951
3711
 
2952
3712
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2953
3713
 
3714
+ globalThis.pageSize = PageSize;
3715
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3716
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3717
+
2954
3718
  // set generic opcodes for current valtype
2955
3719
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2956
3720
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2959,10 +3723,10 @@ export default program => {
2959
3723
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2960
3724
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2961
3725
 
2962
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2963
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2964
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2965
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3726
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3727
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3728
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3729
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2966
3730
 
2967
3731
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2968
3732
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2975,10 +3739,6 @@ export default program => {
2975
3739
 
2976
3740
  program.id = { name: 'main' };
2977
3741
 
2978
- globalThis.pageSize = PageSize;
2979
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2980
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2981
-
2982
3742
  const scope = {
2983
3743
  locals: {},
2984
3744
  localInd: 0
@@ -2989,7 +3749,7 @@ export default program => {
2989
3749
  body: program.body
2990
3750
  };
2991
3751
 
2992
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3752
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2993
3753
 
2994
3754
  generateFunc(scope, program);
2995
3755
 
@@ -3006,7 +3766,11 @@ export default program => {
3006
3766
  }
3007
3767
 
3008
3768
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3009
- main.returns = [];
3769
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3770
+ main.wasm.splice(main.wasm.length - 1, 1);
3771
+ } else {
3772
+ main.returns = [];
3773
+ }
3010
3774
  }
3011
3775
 
3012
3776
  if (lastInst[0] === Opcodes.call) {