porffor 0.2.0-c7b7423 → 0.2.0-c908b46

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