porffor 0.2.0-9ca9aed → 0.2.0-9f58210

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