porffor 0.2.0-6fd3f05 → 0.2.0-75bc012

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +157 -77
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +322 -72
  8. package/compiler/{sections.js → assemble.js} +64 -16
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/boolean.ts +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 +1080 -0
  21. package/compiler/builtins/tostring.ts +25 -0
  22. package/compiler/builtins.js +580 -272
  23. package/compiler/{codeGen.js → codegen.js} +1413 -549
  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 -69
  55. package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,39 +25,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,47 +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
-
1019
- const TYPE_NAMES = {
1020
- [TYPES.number]: 'Number',
1021
- [TYPES.boolean]: 'Boolean',
1022
- [TYPES.string]: 'String',
1023
- [TYPES.undefined]: 'undefined',
1024
- [TYPES.object]: 'Object',
1025
- [TYPES.function]: 'Function',
1026
- [TYPES.symbol]: 'Symbol',
1027
- [TYPES.bigint]: 'BigInt',
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)];
1028
1196
 
1029
- [TYPES._array]: 'Array',
1030
- [TYPES._regexp]: 'RegExp'
1197
+ return false;
1031
1198
  };
1032
1199
 
1033
1200
  const getType = (scope, _name) => {
1034
1201
  const name = mapName(_name);
1035
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);
1036
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);
1037
1209
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1038
1210
 
1039
1211
  let type = TYPES.undefined;
1040
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1212
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1041
1213
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1042
1214
 
1043
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1044
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1215
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1045
1216
 
1046
1217
  return number(type, Valtype.i32);
1047
1218
  };
@@ -1051,23 +1222,37 @@ const setType = (scope, _name, type) => {
1051
1222
 
1052
1223
  const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
1053
1224
 
1225
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
1054
1226
  if (scope.locals[name]) return [
1055
1227
  ...out,
1056
1228
  [ Opcodes.local_set, scope.locals[name + '#type'].idx ]
1057
1229
  ];
1058
1230
 
1231
+ if (typedInput && globals[name]?.metadata?.type != null) return [];
1059
1232
  if (globals[name]) return [
1060
1233
  ...out,
1061
1234
  [ Opcodes.global_set, globals[name + '#type'].idx ]
1062
1235
  ];
1063
1236
 
1064
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) ] ];
1065
1248
  };
1066
1249
 
1067
1250
  const getNodeType = (scope, node) => {
1068
1251
  const inner = () => {
1069
1252
  if (node.type === 'Literal') {
1070
- 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;
1071
1256
 
1072
1257
  return TYPES[typeof node.value];
1073
1258
  }
@@ -1082,6 +1267,27 @@ const getNodeType = (scope, node) => {
1082
1267
 
1083
1268
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1084
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
+
1085
1291
  const func = funcs.find(x => x.name === name);
1086
1292
 
1087
1293
  if (func) {
@@ -1089,10 +1295,27 @@ const getNodeType = (scope, node) => {
1089
1295
  if (func.returnType) return func.returnType;
1090
1296
  }
1091
1297
 
1092
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1298
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1093
1299
  if (internalConstrs[name]) return internalConstrs[name].type;
1094
1300
 
1095
- 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);
1096
1319
 
1097
1320
  // presume
1098
1321
  // todo: warn here?
@@ -1135,11 +1358,20 @@ const getNodeType = (scope, node) => {
1135
1358
  }
1136
1359
 
1137
1360
  if (node.type === 'ArrayExpression') {
1138
- return TYPES._array;
1361
+ return TYPES.array;
1139
1362
  }
1140
1363
 
1141
1364
  if (node.type === 'BinaryExpression') {
1142
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
+
1143
1375
  return TYPES.number;
1144
1376
 
1145
1377
  // todo: string concat types
@@ -1164,20 +1396,41 @@ const getNodeType = (scope, node) => {
1164
1396
  if (node.operator === '!') return TYPES.boolean;
1165
1397
  if (node.operator === 'void') return TYPES.undefined;
1166
1398
  if (node.operator === 'delete') return TYPES.boolean;
1167
- if (node.operator === 'typeof') return TYPES.string;
1399
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1168
1400
 
1169
1401
  return TYPES.number;
1170
1402
  }
1171
1403
 
1172
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
+
1173
1414
  // hack: if something.length, number type
1174
1415
  if (node.property.name === 'length') return TYPES.number;
1175
1416
 
1176
- // 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
1177
1425
  return TYPES.number;
1178
1426
  }
1179
1427
 
1180
- 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);
1181
1434
 
1182
1435
  // presume
1183
1436
  // todo: warn here?
@@ -1193,8 +1446,8 @@ const getNodeType = (scope, node) => {
1193
1446
  const generateLiteral = (scope, decl, global, name) => {
1194
1447
  if (decl.value === null) return number(NULL);
1195
1448
 
1449
+ // hack: just return 1 for regex literals
1196
1450
  if (decl.regex) {
1197
- scope.regex[name] = decl.regex;
1198
1451
  return number(1);
1199
1452
  }
1200
1453
 
@@ -1207,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
1207
1460
  return number(decl.value ? 1 : 0);
1208
1461
 
1209
1462
  case 'string':
1210
- const str = decl.value;
1211
- const rawElements = new Array(str.length);
1212
- let j = 0;
1213
- for (let i = 0; i < str.length; i++) {
1214
- rawElements[i] = str.charCodeAt(i);
1215
- }
1216
-
1217
- return makeArray(scope, {
1218
- rawElements
1219
- }, global, name, false, 'i16')[0];
1463
+ return makeString(scope, decl.value, global, name);
1220
1464
 
1221
1465
  default:
1222
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1466
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1223
1467
  }
1224
1468
  };
1225
1469
 
@@ -1228,6 +1472,8 @@ const countLeftover = wasm => {
1228
1472
 
1229
1473
  for (let i = 0; i < wasm.length; i++) {
1230
1474
  const inst = wasm[i];
1475
+ if (inst[0] == null) continue;
1476
+
1231
1477
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1232
1478
  if (inst[0] === Opcodes.if) count--;
1233
1479
  if (inst[1] !== Blocktype.void) count++;
@@ -1236,18 +1482,25 @@ const countLeftover = wasm => {
1236
1482
  if (inst[0] === Opcodes.end) depth--;
1237
1483
 
1238
1484
  if (depth === 0)
1239
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1240
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1241
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1242
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
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;
1243
1489
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1244
1490
  else if (inst[0] === Opcodes.return) count = 0;
1245
1491
  else if (inst[0] === Opcodes.call) {
1246
1492
  let func = funcs.find(x => x.index === inst[1]);
1247
- if (func) {
1248
- count -= func.params.length;
1249
- } else count--;
1250
- if (func) count += func.returns.length;
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
+ }
1251
1504
  } else count--;
1252
1505
 
1253
1506
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1265,7 +1518,7 @@ const disposeLeftover = wasm => {
1265
1518
  const generateExp = (scope, decl) => {
1266
1519
  const expression = decl.expression;
1267
1520
 
1268
- const out = generate(scope, expression);
1521
+ const out = generate(scope, expression, undefined, undefined, true);
1269
1522
  disposeLeftover(out);
1270
1523
 
1271
1524
  return out;
@@ -1323,7 +1576,7 @@ const RTArrayUtil = {
1323
1576
  ]
1324
1577
  };
1325
1578
 
1326
- const generateCall = (scope, decl, _global, _name) => {
1579
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1327
1580
  /* const callee = decl.callee;
1328
1581
  const args = decl.arguments;
1329
1582
 
@@ -1339,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
1339
1592
  name = func.name;
1340
1593
  }
1341
1594
 
1342
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1595
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1343
1596
  // literal eval hack
1344
- const code = decl.arguments[0].value;
1345
- 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
+ }
1346
1610
 
1347
1611
  const out = generate(scope, {
1348
1612
  type: 'BlockStatement',
@@ -1356,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
1356
1620
  const finalStatement = parsed.body[parsed.body.length - 1];
1357
1621
  out.push(
1358
1622
  ...getNodeType(scope, finalStatement),
1359
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1623
+ ...setLastType(scope)
1360
1624
  );
1361
1625
  } else if (countLeftover(out) === 0) {
1362
1626
  out.push(...number(UNDEFINED));
1363
1627
  out.push(
1364
1628
  ...number(TYPES.undefined, Valtype.i32),
1365
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1629
+ ...setLastType(scope)
1366
1630
  );
1367
1631
  }
1368
1632
 
@@ -1380,39 +1644,47 @@ const generateCall = (scope, decl, _global, _name) => {
1380
1644
  if (name && name.startsWith('__')) {
1381
1645
  const spl = name.slice(2).split('_');
1382
1646
 
1383
- const func = spl[spl.length - 1];
1384
- protoName = func;
1647
+ protoName = spl[spl.length - 1];
1385
1648
 
1386
1649
  target = { ...decl.callee };
1387
1650
  target.name = spl.slice(0, -1).join('_');
1651
+
1652
+ // failed to lookup name, abort
1653
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1388
1654
  }
1389
1655
 
1390
1656
  // literal.func()
1391
1657
  if (!name && decl.callee.type === 'MemberExpression') {
1392
1658
  // megahack for /regex/.func()
1393
- if (decl.callee.object.regex) {
1394
- const funcName = decl.callee.property.name;
1395
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
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);
1396
1666
 
1397
- funcIndex[func.name] = func.index;
1398
- funcs.push(func);
1667
+ funcIndex[func.name] = func.index;
1668
+ funcs.push(func);
1669
+ }
1399
1670
 
1671
+ const idx = funcIndex[rhemynName];
1400
1672
  return [
1401
1673
  // make string arg
1402
1674
  ...generate(scope, decl.arguments[0]),
1675
+ Opcodes.i32_to_u,
1676
+ ...getNodeType(scope, decl.arguments[0]),
1403
1677
 
1404
1678
  // call regex func
1405
- Opcodes.i32_to_u,
1406
- [ Opcodes.call, func.index ],
1679
+ [ Opcodes.call, idx ],
1407
1680
  Opcodes.i32_from_u,
1408
1681
 
1409
1682
  ...number(TYPES.boolean, Valtype.i32),
1410
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1683
+ ...setLastType(scope)
1411
1684
  ];
1412
1685
  }
1413
1686
 
1414
- const func = decl.callee.property.name;
1415
- protoName = func;
1687
+ protoName = decl.callee.property.name;
1416
1688
 
1417
1689
  target = decl.callee.object;
1418
1690
  }
@@ -1433,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
1433
1705
  // }
1434
1706
 
1435
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
+
1436
1728
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1437
- const f = prototypeFuncs[x][protoName];
1438
- if (f) acc[x] = f;
1729
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1439
1730
  return acc;
1440
1731
  }, {});
1441
1732
 
1442
- // no prototype function candidates, ignore
1443
1733
  if (Object.keys(protoCands).length > 0) {
1444
1734
  // use local for cached i32 length as commonly used
1445
1735
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1446
1736
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1447
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1448
1737
 
1449
1738
  // TODO: long-term, prototypes should be their individual separate funcs
1450
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;
1451
1749
  let lengthI32CacheUsed = false;
1452
- const protoBC = {};
1453
1750
  for (const x in protoCands) {
1454
1751
  const protoFunc = protoCands[x];
1455
1752
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1457,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
1457
1754
  ...RTArrayUtil.getLength(getPointer),
1458
1755
 
1459
1756
  ...number(TYPES.number, Valtype.i32),
1460
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1757
+ ...setLastType(scope)
1461
1758
  ];
1462
1759
  continue;
1463
1760
  }
@@ -1467,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
1467
1764
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1468
1765
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1469
1766
 
1767
+ let optUnused = false;
1470
1768
  const protoOut = protoFunc(getPointer, {
1471
1769
  getCachedI32: () => {
1472
1770
  lengthI32CacheUsed = true;
@@ -1481,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
1481
1779
  return makeArray(scope, {
1482
1780
  rawElements: new Array(length)
1483
1781
  }, _global, _name, true, itemType);
1782
+ }, () => {
1783
+ optUnused = true;
1784
+ return unusedValue;
1484
1785
  });
1485
1786
 
1787
+ if (!optUnused) allOptUnused = false;
1788
+
1486
1789
  protoBC[x] = [
1487
- [ Opcodes.block, valtypeBinary ],
1790
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1488
1791
  ...protoOut,
1489
1792
 
1490
1793
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1491
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ],
1794
+ ...setLastType(scope),
1492
1795
  [ Opcodes.end ]
1493
1796
  ];
1494
1797
  }
1495
1798
 
1496
- return [
1497
- ...generate(scope, target),
1799
+ // todo: if some cands use optUnused and some don't, we will probably crash
1498
1800
 
1499
- Opcodes.i32_to_u,
1500
- [ Opcodes.local_set, pointerLocal ],
1801
+ return [
1802
+ ...(usePointerCache ? [
1803
+ ...rawPointer,
1804
+ [ Opcodes.local_set, pointerLocal ],
1805
+ ] : []),
1501
1806
 
1502
1807
  ...(!lengthI32CacheUsed ? [] : [
1503
1808
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1509,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1814
 
1510
1815
  // TODO: error better
1511
1816
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1512
- }, valtypeBinary),
1817
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1513
1818
  ];
1514
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
+ }
1515
1829
  }
1516
1830
 
1517
1831
  // TODO: only allows callee as literal
1518
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1832
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1519
1833
 
1520
1834
  let idx = funcIndex[name] ?? importedFuncs[name];
1521
1835
  if (idx === undefined && builtinFuncs[name]) {
@@ -1525,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
1525
1839
  idx = funcIndex[name];
1526
1840
 
1527
1841
  // infer arguments types from builtins params
1528
- const func = funcs.find(x => x.name === name);
1529
- for (let i = 0; i < decl.arguments.length; i++) {
1530
- const arg = decl.arguments[i];
1531
- if (!arg.name) continue;
1532
-
1533
- const local = scope.locals[arg.name];
1534
- if (!local) continue;
1535
-
1536
- local.type = func.params[i];
1537
- if (local.type === Valtype.v128) {
1538
- // specify vec subtype inferred from last vec type in function name
1539
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1540
- }
1541
- }
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
+ // }
1542
1856
  }
1543
1857
 
1544
1858
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1548,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
1548
1862
  idx = -1;
1549
1863
  }
1550
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
+
1551
1911
  if (idx === undefined) {
1552
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1553
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
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);
1554
1914
  }
1555
1915
 
1556
1916
  const func = funcs.find(x => x.index === idx);
1557
1917
 
1558
1918
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1559
- 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);
1560
1922
 
1561
1923
  let args = decl.arguments;
1562
1924
  if (func && args.length < paramCount) {
@@ -1572,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
1572
1934
  if (func && func.throws) scope.throws = true;
1573
1935
 
1574
1936
  let out = [];
1575
- for (const arg of args) {
1937
+ for (let i = 0; i < args.length; i++) {
1938
+ const arg = args[i];
1576
1939
  out = out.concat(generate(scope, arg));
1577
- 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));
1578
1950
  }
1579
1951
 
1580
1952
  out.push([ Opcodes.call, idx ]);
1581
1953
 
1582
- if (!userFunc) {
1954
+ if (!typedReturns) {
1583
1955
  // let type;
1584
1956
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1585
1957
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1589,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
1589
1961
  // ...number(type, Valtype.i32),
1590
1962
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1591
1963
  // );
1592
- } 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
+ }
1593
1969
 
1594
1970
  return out;
1595
1971
  };
@@ -1597,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
1597
1973
  const generateNew = (scope, decl, _global, _name) => {
1598
1974
  // hack: basically treat this as a normal call for builtins for now
1599
1975
  const name = mapName(decl.callee.name);
1976
+
1600
1977
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1601
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
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)})`);
1602
1991
 
1603
1992
  return generateCall(scope, decl, _global, _name);
1604
1993
  };
@@ -1614,41 +2003,148 @@ const unhackName = name => {
1614
2003
  return name;
1615
2004
  };
1616
2005
 
1617
- const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1618
- 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
+ }
1619
2010
 
1620
- const out = [
1621
- ...type,
1622
- [ Opcodes.local_set, tmp ],
1623
- [ Opcodes.block, returns ]
1624
- ];
2011
+ if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
2012
+ const idx = type[0][1];
1625
2013
 
1626
- // 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
+ }
1627
2018
 
1628
- for (const x in bc) {
1629
- if (x === 'default') continue;
2019
+ return null;
2020
+ };
1630
2021
 
1631
- // if type == x
1632
- out.push([ Opcodes.local_get, tmp ]);
1633
- out.push(...number(x, Valtype.i32));
1634
- 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;
1635
2026
 
1636
- out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
1637
- out.push(...bc[x]);
1638
- out.push([ Opcodes.br, 1 ]);
1639
- out.push([ Opcodes.end ]);
2027
+ if (count === 1) {
2028
+ // return [
2029
+ // ...input,
2030
+ // ...bc[keys[0]]
2031
+ // ];
2032
+ return bc[keys[0]];
1640
2033
  }
1641
2034
 
1642
- // default
1643
- if (bc.default) out.push(...bc.default);
1644
- 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
+ }
1645
2049
 
1646
- 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
+ }
2054
+
2055
+ const nums = keys.filter(x => +x);
2056
+ const offset = Math.min(...nums);
2057
+ const max = Math.max(...nums);
2058
+
2059
+ const table = [];
2060
+ let br = 1;
2061
+
2062
+ for (let i = offset; i <= max; i++) {
2063
+ // if branch for this num, go to that block
2064
+ if (bc[i]) {
2065
+ table.push(br);
2066
+ br++;
2067
+ continue;
2068
+ }
2069
+
2070
+ // else default
2071
+ table.push(0);
2072
+ }
2073
+
2074
+ out.push(
2075
+ [ Opcodes.block, Blocktype.void ],
2076
+ ...input,
2077
+ ...(offset > 0 ? [
2078
+ ...number(offset, Valtype.i32),
2079
+ [ Opcodes.i32_sub ]
2080
+ ] : []),
2081
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
2082
+ );
2083
+
2084
+ // if you can guess why we sort the wrong way and then reverse
2085
+ // (instead of just sorting the correct way)
2086
+ // dm me and if you are correct and the first person
2087
+ // I will somehow shout you out or something
2088
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
2089
+
2090
+ br = count - 1;
2091
+ for (const x of orderedBc) {
2092
+ out.push(
2093
+ [ Opcodes.end ],
2094
+ ...bc[x],
2095
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
2096
+ );
2097
+ br--;
2098
+ }
2099
+
2100
+ return [
2101
+ ...out,
2102
+ [ Opcodes.end, 'br table end' ]
2103
+ ];
2104
+ };
2105
+
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' ]);
1647
2143
 
1648
2144
  return out;
1649
2145
  };
1650
2146
 
1651
- const allocVar = (scope, name, global = false) => {
2147
+ const allocVar = (scope, name, global = false, type = true) => {
1652
2148
  const target = global ? globals : scope.locals;
1653
2149
 
1654
2150
  // already declared
@@ -1662,12 +2158,62 @@ const allocVar = (scope, name, global = false) => {
1662
2158
  let idx = global ? globalInd++ : scope.localInd++;
1663
2159
  target[name] = { idx, type: valtypeBinary };
1664
2160
 
1665
- let typeIdx = global ? globalInd++ : scope.localInd++;
1666
- 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
+ }
1667
2165
 
1668
2166
  return idx;
1669
2167
  };
1670
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
+
1671
2217
  const generateVar = (scope, decl) => {
1672
2218
  let out = [];
1673
2219
 
@@ -1675,10 +2221,13 @@ const generateVar = (scope, decl) => {
1675
2221
 
1676
2222
  // global variable if in top scope (main) and var ..., or if wanted
1677
2223
  const global = topLevel || decl._bare; // decl.kind === 'var';
2224
+ const target = global ? globals : scope.locals;
1678
2225
 
1679
2226
  for (const x of decl.declarations) {
1680
2227
  const name = mapName(x.id.name);
1681
2228
 
2229
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2230
+
1682
2231
  if (x.init && isFuncType(x.init.type)) {
1683
2232
  // hack for let a = function () { ... }
1684
2233
  x.init.id = { name };
@@ -1694,11 +2243,29 @@ const generateVar = (scope, decl) => {
1694
2243
  continue; // always ignore
1695
2244
  }
1696
2245
 
1697
- let idx = allocVar(scope, name, global);
1698
- if (x.init) {
1699
- 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));
1700
2252
 
1701
- 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
+ }
1702
2269
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1703
2270
  }
1704
2271
 
@@ -1709,7 +2276,8 @@ const generateVar = (scope, decl) => {
1709
2276
  return out;
1710
2277
  };
1711
2278
 
1712
- const generateAssign = (scope, decl) => {
2279
+ // todo: optimize this func for valueUnused
2280
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1713
2281
  const { type, name } = decl.left;
1714
2282
 
1715
2283
  if (type === 'ObjectPattern') {
@@ -1724,22 +2292,30 @@ const generateAssign = (scope, decl) => {
1724
2292
  return [];
1725
2293
  }
1726
2294
 
2295
+ const op = decl.operator.slice(0, -1) || '=';
2296
+
1727
2297
  // hack: .length setter
1728
2298
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1729
2299
  const name = decl.left.object.name;
1730
- const pointer = arrays.get(name);
2300
+ const pointer = scope.arrays?.get(name);
1731
2301
 
1732
- const aotPointer = pointer != null;
2302
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1733
2303
 
1734
2304
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2305
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1735
2306
 
1736
2307
  return [
1737
2308
  ...(aotPointer ? number(0, Valtype.i32) : [
1738
2309
  ...generate(scope, decl.left.object),
1739
2310
  Opcodes.i32_to_u
1740
2311
  ]),
2312
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1741
2313
 
1742
- ...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))),
1743
2319
  [ Opcodes.local_tee, newValueTmp ],
1744
2320
 
1745
2321
  Opcodes.i32_to_u,
@@ -1749,21 +2325,19 @@ const generateAssign = (scope, decl) => {
1749
2325
  ];
1750
2326
  }
1751
2327
 
1752
- const op = decl.operator.slice(0, -1) || '=';
1753
-
1754
2328
  // arr[i]
1755
2329
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1756
2330
  const name = decl.left.object.name;
1757
- const pointer = arrays.get(name);
2331
+ const pointer = scope.arrays?.get(name);
1758
2332
 
1759
- const aotPointer = pointer != null;
2333
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1760
2334
 
1761
2335
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1762
2336
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1763
2337
 
1764
2338
  return [
1765
2339
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1766
- [TYPES._array]: [
2340
+ [TYPES.array]: [
1767
2341
  ...(aotPointer ? [] : [
1768
2342
  ...generate(scope, decl.left.object),
1769
2343
  Opcodes.i32_to_u
@@ -1812,6 +2386,8 @@ const generateAssign = (scope, decl) => {
1812
2386
  ];
1813
2387
  }
1814
2388
 
2389
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2390
+
1815
2391
  const [ local, isGlobal ] = lookupName(scope, name);
1816
2392
 
1817
2393
  if (local === undefined) {
@@ -1858,9 +2434,7 @@ const generateAssign = (scope, decl) => {
1858
2434
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1859
2435
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1860
2436
 
1861
- [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ],
1862
- // hack: type is idx+1
1863
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2437
+ ...setType(scope, name, getLastType(scope))
1864
2438
  ];
1865
2439
  }
1866
2440
 
@@ -1871,9 +2445,7 @@ const generateAssign = (scope, decl) => {
1871
2445
 
1872
2446
  // todo: string concat types
1873
2447
 
1874
- // hack: type is idx+1
1875
- ...number(TYPES.number, Valtype.i32),
1876
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2448
+ ...setType(scope, name, TYPES.number)
1877
2449
  ];
1878
2450
  };
1879
2451
 
@@ -1919,7 +2491,7 @@ const generateUnary = (scope, decl) => {
1919
2491
  return out;
1920
2492
  }
1921
2493
 
1922
- case 'delete':
2494
+ case 'delete': {
1923
2495
  let toReturn = true, toGenerate = true;
1924
2496
 
1925
2497
  if (decl.argument.type === 'Identifier') {
@@ -1941,38 +2513,60 @@ const generateUnary = (scope, decl) => {
1941
2513
 
1942
2514
  out.push(...number(toReturn ? 1 : 0));
1943
2515
  return out;
2516
+ }
1944
2517
 
1945
- case 'typeof':
1946
- 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), {
1947
2536
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
1948
2537
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
1949
2538
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
1950
2539
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
1951
2540
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
1952
2541
 
2542
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2543
+
1953
2544
  // object and internal types
1954
2545
  default: makeString(scope, 'object', false, '#typeof_result'),
1955
- });
2546
+ }));
2547
+
2548
+ return out;
2549
+ }
1956
2550
 
1957
2551
  default:
1958
- return todo(`unary operator ${decl.operator} not implemented yet`);
2552
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
1959
2553
  }
1960
2554
  };
1961
2555
 
1962
- const generateUpdate = (scope, decl) => {
2556
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
1963
2557
  const { name } = decl.argument;
1964
2558
 
1965
2559
  const [ local, isGlobal ] = lookupName(scope, name);
1966
2560
 
1967
2561
  if (local === undefined) {
1968
- return todo(`update expression with undefined variable`);
2562
+ return todo(scope, `update expression with undefined variable`, true);
1969
2563
  }
1970
2564
 
1971
2565
  const idx = local.idx;
1972
2566
  const out = [];
1973
2567
 
1974
2568
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
1975
- 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 ]);
1976
2570
 
1977
2571
  switch (decl.operator) {
1978
2572
  case '++':
@@ -1985,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
1985
2579
  }
1986
2580
 
1987
2581
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
1988
- 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 ]);
1989
2583
 
1990
2584
  return out;
1991
2585
  };
@@ -2025,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2025
2619
  // note type
2026
2620
  out.push(
2027
2621
  ...getNodeType(scope, decl.consequent),
2028
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2622
+ ...setLastType(scope)
2029
2623
  );
2030
2624
 
2031
2625
  out.push([ Opcodes.else ]);
@@ -2034,7 +2628,7 @@ const generateConditional = (scope, decl) => {
2034
2628
  // note type
2035
2629
  out.push(
2036
2630
  ...getNodeType(scope, decl.alternate),
2037
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
2631
+ ...setLastType(scope)
2038
2632
  );
2039
2633
 
2040
2634
  out.push([ Opcodes.end ]);
@@ -2048,15 +2642,17 @@ const generateFor = (scope, decl) => {
2048
2642
  const out = [];
2049
2643
 
2050
2644
  if (decl.init) {
2051
- out.push(...generate(scope, decl.init));
2645
+ out.push(...generate(scope, decl.init, false, undefined, true));
2052
2646
  disposeLeftover(out);
2053
2647
  }
2054
2648
 
2055
2649
  out.push([ Opcodes.loop, Blocktype.void ]);
2056
2650
  depth.push('for');
2057
2651
 
2058
- out.push(...generate(scope, decl.test));
2059
- 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 ]);
2060
2656
  depth.push('if');
2061
2657
 
2062
2658
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2064,8 +2660,7 @@ const generateFor = (scope, decl) => {
2064
2660
  out.push(...generate(scope, decl.body));
2065
2661
  out.push([ Opcodes.end ]);
2066
2662
 
2067
- out.push(...generate(scope, decl.update));
2068
- depth.pop();
2663
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2069
2664
 
2070
2665
  out.push([ Opcodes.br, 1 ]);
2071
2666
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2093,6 +2688,36 @@ const generateWhile = (scope, decl) => {
2093
2688
  return out;
2094
2689
  };
2095
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
+
2096
2721
  const generateForOf = (scope, decl) => {
2097
2722
  const out = [];
2098
2723
 
@@ -2122,8 +2747,17 @@ const generateForOf = (scope, decl) => {
2122
2747
  // setup local for left
2123
2748
  generate(scope, decl.left);
2124
2749
 
2125
- 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
+
2126
2759
  const [ local, isGlobal ] = lookupName(scope, leftName);
2760
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2127
2761
 
2128
2762
  depth.push('block');
2129
2763
  depth.push('block');
@@ -2131,15 +2765,17 @@ const generateForOf = (scope, decl) => {
2131
2765
  // // todo: we should only do this for strings but we don't know at compile-time :(
2132
2766
  // hack: this is naughty and will break things!
2133
2767
  let newOut = number(0, Valtype.f64), newPointer = -1;
2134
- 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?
2135
2770
  0, [ newOut, newPointer ] = makeArray(scope, {
2136
2771
  rawElements: new Array(1)
2137
2772
  }, isGlobal, leftName, true, 'i16');
2138
2773
  }
2139
2774
 
2140
2775
  // set type for local
2776
+ // todo: optimize away counter and use end pointer
2141
2777
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2142
- [TYPES._array]: [
2778
+ [TYPES.array]: [
2143
2779
  ...setType(scope, leftName, TYPES.number),
2144
2780
 
2145
2781
  [ Opcodes.loop, Blocktype.void ],
@@ -2222,6 +2858,56 @@ const generateForOf = (scope, decl) => {
2222
2858
  [ Opcodes.end ],
2223
2859
  [ Opcodes.end ]
2224
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
+ ],
2225
2911
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2226
2912
  }, Blocktype.void));
2227
2913
 
@@ -2232,28 +2918,65 @@ const generateForOf = (scope, decl) => {
2232
2918
  return out;
2233
2919
  };
2234
2920
 
2921
+ // find the nearest loop in depth map by type
2235
2922
  const getNearestLoop = () => {
2236
2923
  for (let i = depth.length - 1; i >= 0; i--) {
2237
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2924
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2238
2925
  }
2239
2926
 
2240
2927
  return -1;
2241
2928
  };
2242
2929
 
2243
2930
  const generateBreak = (scope, decl) => {
2244
- 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
+
2245
2946
  return [
2246
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2947
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2247
2948
  ];
2248
2949
  };
2249
2950
 
2250
2951
  const generateContinue = (scope, decl) => {
2251
- 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
+
2252
2966
  return [
2253
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2967
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2254
2968
  ];
2255
2969
  };
2256
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
+
2257
2980
  const generateThrow = (scope, decl) => {
2258
2981
  scope.throws = true;
2259
2982
 
@@ -2262,7 +2985,7 @@ const generateThrow = (scope, decl) => {
2262
2985
  // hack: throw new X("...") -> throw "..."
2263
2986
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2264
2987
  constructor = decl.argument.callee.name;
2265
- message = decl.argument.arguments[0].value;
2988
+ message = decl.argument.arguments[0]?.value ?? '';
2266
2989
  }
2267
2990
 
2268
2991
  if (tags.length === 0) tags.push({
@@ -2274,6 +2997,9 @@ const generateThrow = (scope, decl) => {
2274
2997
  let exceptId = exceptions.push({ constructor, message }) - 1;
2275
2998
  let tagIdx = tags[0].idx;
2276
2999
 
3000
+ scope.exceptions ??= [];
3001
+ scope.exceptions.push(exceptId);
3002
+
2277
3003
  // todo: write a description of how this works lol
2278
3004
 
2279
3005
  return [
@@ -2283,7 +3009,7 @@ const generateThrow = (scope, decl) => {
2283
3009
  };
2284
3010
 
2285
3011
  const generateTry = (scope, decl) => {
2286
- if (decl.finalizer) return todo('try finally not implemented yet');
3012
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2287
3013
 
2288
3014
  const out = [];
2289
3015
 
@@ -2314,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
2314
3040
  // TODO
2315
3041
  // if identifier declared, use that
2316
3042
  // else, use default (right)
2317
- return todo('assignment pattern (optional arg)');
3043
+ return todo(scope, 'assignment pattern (optional arg)');
2318
3044
  };
2319
3045
 
2320
3046
  let pages = new Map();
2321
- const allocPage = (reason, type) => {
3047
+ const allocPage = (scope, reason, type) => {
2322
3048
  if (pages.has(reason)) return pages.get(reason).ind;
2323
3049
 
2324
3050
  if (reason.startsWith('array:')) pages.hasArray = true;
2325
3051
  if (reason.startsWith('string:')) pages.hasString = true;
3052
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3053
+ if (reason.includes('string:')) pages.hasAnyString = true;
2326
3054
 
2327
3055
  const ind = pages.size;
2328
3056
  pages.set(reason, { ind, type });
2329
3057
 
2330
- 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})`);
2331
3062
 
2332
3063
  return ind;
2333
3064
  };
2334
3065
 
3066
+ // todo: add scope.pages
2335
3067
  const freePage = reason => {
2336
3068
  const { ind } = pages.get(reason);
2337
3069
  pages.delete(reason);
2338
3070
 
2339
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3071
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2340
3072
 
2341
3073
  return ind;
2342
3074
  };
@@ -2356,38 +3088,53 @@ const StoreOps = {
2356
3088
  f64: Opcodes.f64_store,
2357
3089
 
2358
3090
  // expects i32 input!
2359
- i16: Opcodes.i32_store16
3091
+ i8: Opcodes.i32_store8,
3092
+ i16: Opcodes.i32_store16,
2360
3093
  };
2361
3094
 
2362
3095
  let data = [];
2363
3096
 
2364
- const compileBytes = (val, itemType, signed = true) => {
3097
+ const compileBytes = (val, itemType) => {
2365
3098
  // todo: this is a mess and needs confirming / ????
2366
3099
  switch (itemType) {
2367
3100
  case 'i8': return [ val % 256 ];
2368
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2369
-
2370
- case 'i32':
2371
- case 'i64':
2372
- return enforceFourBytes(signedLEB128(val));
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
2373
3105
 
2374
3106
  case 'f64': return ieee754_binary64(val);
2375
3107
  }
2376
3108
  };
2377
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
+
2378
3119
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2379
3120
  const out = [];
2380
3121
 
3122
+ scope.arrays ??= new Map();
3123
+
2381
3124
  let firstAssign = false;
2382
- if (!arrays.has(name) || name === '$undeclared') {
3125
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2383
3126
  firstAssign = true;
2384
3127
 
2385
3128
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2386
3129
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2387
- 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);
2388
3133
  }
2389
3134
 
2390
- const pointer = arrays.get(name);
3135
+ const pointer = scope.arrays.get(name);
3136
+
3137
+ const local = global ? globals[name] : scope.locals[name];
2391
3138
 
2392
3139
  const useRawElements = !!decl.rawElements;
2393
3140
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2395,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2395
3142
  const valtype = itemTypeToValtype[itemType];
2396
3143
  const length = elements.length;
2397
3144
 
2398
- if (firstAssign && useRawElements) {
2399
- 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');
2400
3149
 
2401
- if (!initEmpty) for (let i = 0; i < length; i++) {
2402
- if (elements[i] == null) continue;
3150
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3151
+ if (elements[i] == null) continue;
2403
3152
 
2404
- bytes.push(...compileBytes(elements[i], itemType));
2405
- }
3153
+ bytes.push(...compileBytes(elements[i], itemType));
3154
+ }
2406
3155
 
2407
- data.push({
2408
- offset: pointer,
2409
- bytes
2410
- });
3156
+ const ind = data.push({
3157
+ offset: pointer,
3158
+ bytes
3159
+ }) - 1;
3160
+
3161
+ scope.data ??= [];
3162
+ scope.data.push(ind);
3163
+ }
2411
3164
 
2412
3165
  // local value as pointer
2413
3166
  out.push(...number(pointer));
@@ -2415,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2415
3168
  return [ out, pointer ];
2416
3169
  }
2417
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
+
2418
3182
  // store length as 0th array
2419
3183
  out.push(
2420
- ...number(0, Valtype.i32),
3184
+ ...pointerWasm,
2421
3185
  ...number(length, Valtype.i32),
2422
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3186
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2423
3187
  );
2424
3188
 
2425
3189
  const storeOp = StoreOps[itemType];
@@ -2428,43 +3192,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2428
3192
  if (elements[i] == null) continue;
2429
3193
 
2430
3194
  out.push(
2431
- ...number(0, Valtype.i32),
3195
+ ...pointerWasm,
2432
3196
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2433
- [ 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]) ]
2434
3198
  );
2435
3199
  }
2436
3200
 
2437
3201
  // local value as pointer
2438
- out.push(...number(pointer));
3202
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2439
3203
 
2440
3204
  return [ out, pointer ];
2441
3205
  };
2442
3206
 
2443
- 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) => {
2444
3218
  const rawElements = new Array(str.length);
3219
+ let byteStringable = Prefs.bytestring;
2445
3220
  for (let i = 0; i < str.length; i++) {
2446
- rawElements[i] = str.charCodeAt(i);
3221
+ const c = str.charCodeAt(i);
3222
+ rawElements[i] = c;
3223
+
3224
+ if (byteStringable && c > 0xFF) byteStringable = false;
2447
3225
  }
2448
3226
 
3227
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3228
+
2449
3229
  return makeArray(scope, {
2450
3230
  rawElements
2451
- }, global, name, false, 'i16')[0];
3231
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2452
3232
  };
2453
3233
 
2454
- let arrays = new Map();
2455
3234
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2456
3235
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2457
3236
  };
2458
3237
 
2459
3238
  export const generateMember = (scope, decl, _global, _name) => {
2460
3239
  const name = decl.object.name;
2461
- 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;
2462
3248
 
2463
- 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
+ }
2464
3257
 
2465
3258
  // hack: .length
2466
3259
  if (decl.property.name === 'length') {
2467
- // 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
+
2468
3281
  return [
2469
3282
  ...(aotPointer ? number(0, Valtype.i32) : [
2470
3283
  ...generate(scope, decl.object),
@@ -2476,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
2476
3289
  ];
2477
3290
  }
2478
3291
 
3292
+ const object = generate(scope, decl.object);
3293
+ const property = generate(scope, decl.property);
3294
+
2479
3295
  // // todo: we should only do this for strings but we don't know at compile-time :(
2480
3296
  // hack: this is naughty and will break things!
2481
- let newOut = number(0, Valtype.f64), newPointer = -1;
2482
- if (pages.hasString) {
3297
+ let newOut = number(0, valtypeBinary), newPointer = -1;
3298
+ if (pages.hasAnyString) {
2483
3299
  0, [ newOut, newPointer ] = makeArray(scope, {
2484
3300
  rawElements: new Array(1)
2485
3301
  }, _global, _name, true, 'i16');
2486
3302
  }
2487
3303
 
2488
3304
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2489
- [TYPES._array]: [
3305
+ [TYPES.array]: [
2490
3306
  // get index as valtype
2491
- ...generate(scope, decl.property),
3307
+ ...property,
2492
3308
 
2493
3309
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2494
3310
  Opcodes.i32_to_u,
@@ -2496,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2496
3312
  [ Opcodes.i32_mul ],
2497
3313
 
2498
3314
  ...(aotPointer ? [] : [
2499
- ...generate(scope, decl.object),
3315
+ ...object,
2500
3316
  Opcodes.i32_to_u,
2501
3317
  [ Opcodes.i32_add ]
2502
3318
  ]),
@@ -2505,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2505
3321
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2506
3322
 
2507
3323
  ...number(TYPES.number, Valtype.i32),
2508
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
3324
+ ...setLastType(scope)
2509
3325
  ],
2510
3326
 
2511
3327
  [TYPES.string]: [
@@ -2515,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2515
3331
 
2516
3332
  ...number(0, Valtype.i32), // base 0 for store later
2517
3333
 
2518
- ...generate(scope, decl.property),
2519
-
3334
+ ...property,
2520
3335
  Opcodes.i32_to_u,
3336
+
2521
3337
  ...number(ValtypeSize.i16, Valtype.i32),
2522
3338
  [ Opcodes.i32_mul ],
2523
3339
 
2524
3340
  ...(aotPointer ? [] : [
2525
- ...generate(scope, decl.object),
3341
+ ...object,
2526
3342
  Opcodes.i32_to_u,
2527
3343
  [ Opcodes.i32_add ]
2528
3344
  ]),
@@ -2537,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2537
3353
  ...number(newPointer),
2538
3354
 
2539
3355
  ...number(TYPES.string, Valtype.i32),
2540
- [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
3356
+ ...setLastType(scope)
2541
3357
  ],
3358
+ [TYPES.bytestring]: [
3359
+ // setup new/out array
3360
+ ...newOut,
3361
+ [ Opcodes.drop ],
2542
3362
 
2543
- 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)
2544
3388
  });
2545
3389
  };
2546
3390
 
@@ -2550,25 +3394,36 @@ const objectHack = node => {
2550
3394
  if (!node) return node;
2551
3395
 
2552
3396
  if (node.type === 'MemberExpression') {
2553
- if (node.computed || node.optional) return node;
3397
+ const out = (() => {
3398
+ if (node.computed || node.optional) return;
3399
+
3400
+ let objectName = node.object.name;
2554
3401
 
2555
- 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;
2556
3405
 
2557
- // if object is not identifier or another member exp, give up
2558
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3406
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2559
3407
 
2560
- 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
+ }
2561
3413
 
2562
- // if .length, give up (hack within a hack!)
2563
- if (node.property.name === 'length') return node;
3414
+ // no object name, give up
3415
+ if (!objectName) return;
2564
3416
 
2565
- const name = '__' + objectName + '_' + node.property.name;
2566
- 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}`);
2567
3419
 
2568
- return {
2569
- type: 'Identifier',
2570
- name
2571
- };
3420
+ return {
3421
+ type: 'Identifier',
3422
+ name
3423
+ };
3424
+ })();
3425
+
3426
+ if (out) return out;
2572
3427
  }
2573
3428
 
2574
3429
  for (const x in node) {
@@ -2582,11 +3437,11 @@ const objectHack = node => {
2582
3437
  };
2583
3438
 
2584
3439
  const generateFunc = (scope, decl) => {
2585
- if (decl.async) return todo('async functions are not supported');
2586
- if (decl.generator) return todo('generator functions are not supported');
3440
+ if (decl.async) return todo(scope, 'async functions are not supported');
3441
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2587
3442
 
2588
3443
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2589
- const params = decl.params?.map(x => x.name) ?? [];
3444
+ const params = decl.params ?? [];
2590
3445
 
2591
3446
  // const innerScope = { ...scope };
2592
3447
  // TODO: share scope/locals between !!!
@@ -2599,8 +3454,17 @@ const generateFunc = (scope, decl) => {
2599
3454
  name
2600
3455
  };
2601
3456
 
3457
+ if (typedInput && decl.returnType) {
3458
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3459
+ innerScope.returns = [ valtypeBinary ];
3460
+ }
3461
+
2602
3462
  for (let i = 0; i < params.length; i++) {
2603
- 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
+ }
2604
3468
  }
2605
3469
 
2606
3470
  let body = objectHack(decl.body);
@@ -2616,13 +3480,13 @@ const generateFunc = (scope, decl) => {
2616
3480
  const func = {
2617
3481
  name,
2618
3482
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2619
- returns: innerScope.returns,
2620
- locals: innerScope.locals,
2621
- throws: innerScope.throws,
2622
- index: currentFuncIndex++
3483
+ index: currentFuncIndex++,
3484
+ ...innerScope
2623
3485
  };
2624
3486
  funcIndex[name] = func.index;
2625
3487
 
3488
+ if (name === 'main') func.gotLastType = true;
3489
+
2626
3490
  // quick hack fixes
2627
3491
  for (const inst of wasm) {
2628
3492
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2639,117 +3503,6 @@ const generateFunc = (scope, decl) => {
2639
3503
  );
2640
3504
  }
2641
3505
 
2642
- // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
2643
- let offset = 0, vecParams = 0;
2644
- for (let i = 0; i < params.length; i++) {
2645
- const name = params[i];
2646
- const local = func.locals[name];
2647
- if (local.type === Valtype.v128) {
2648
- vecParams++;
2649
-
2650
- /* wasm.unshift( // add v128 load for param
2651
- [ Opcodes.i32_const, 0 ],
2652
- [ ...Opcodes.v128_load, 0, i * 16 ],
2653
- [ Opcodes.local_set, local.idx ]
2654
- ); */
2655
-
2656
- // using params and replace_lane is noticably faster than just loading from memory (above) somehow
2657
-
2658
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2659
- const { vecType } = local;
2660
- let [ type, lanes ] = vecType.split('x');
2661
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2662
-
2663
- lanes = parseInt(lanes);
2664
- type = Valtype[type];
2665
-
2666
- const name = params[i]; // get original param name
2667
-
2668
- func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
2669
-
2670
- // update index of original local
2671
- // delete func.locals[name];
2672
-
2673
- // add new locals for params
2674
- for (let j = 0; j < lanes; j++) {
2675
- func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
2676
- }
2677
-
2678
- // prepend wasm to generate expected v128 locals
2679
- wasm.splice(i * 2 + offset * 2, 0,
2680
- ...i32x4(0, 0, 0, 0),
2681
- ...new Array(lanes).fill(0).flatMap((_, j) => [
2682
- [ Opcodes.local_get, offset + j ],
2683
- [ ...Opcodes[vecType + '_replace_lane'], j ]
2684
- ]),
2685
- [ Opcodes.local_set, i ]
2686
- );
2687
-
2688
- offset += lanes;
2689
-
2690
- // note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
2691
- /* if (!func.name.startsWith('#')) func.name = '##' + func.name;
2692
-
2693
- // add vec type index to hash name prefix for wrapper to know how to wrap
2694
- const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
2695
- const secondHash = func.name.slice(1).indexOf('#');
2696
- func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
2697
- }
2698
- }
2699
-
2700
- if (offset !== 0) {
2701
- // bump local indexes for all other locals after
2702
- for (const x in func.locals) {
2703
- const local = func.locals[x];
2704
- if (!local.vecParamAutogen) local.idx += offset;
2705
- }
2706
-
2707
- // bump local indexes in wasm local.get/set
2708
- for (let j = 0; j < wasm.length; j++) {
2709
- const inst = wasm[j];
2710
- if (j < offset * 2 + vecParams * 2) {
2711
- if (inst[0] === Opcodes.local_set) inst[1] += offset;
2712
- continue;
2713
- }
2714
-
2715
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
2716
- }
2717
- }
2718
-
2719
- // change v128 return into many <type> instead as unsupported return valtype
2720
- const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
2721
- if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
2722
- const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
2723
- // extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
2724
- const { vecType } = lastReturnLocal;
2725
- let [ type, lanes ] = vecType.split('x');
2726
- if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
2727
-
2728
- lanes = parseInt(lanes);
2729
- type = Valtype[type];
2730
-
2731
- const vecIdx = lastReturnLocal.idx;
2732
-
2733
- const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
2734
- const tmpIdx = [];
2735
- for (let i = 0; i < lanes; i++) {
2736
- const idx = lastIdx + i + 1;
2737
- tmpIdx.push(idx);
2738
- func.locals[name + i] = { idx, type, vecReturnAutogen: true };
2739
- }
2740
-
2741
- wasm.splice(wasm.length - 1, 1,
2742
- ...new Array(lanes).fill(0).flatMap((_, i) => [
2743
- i === 0 ? null : [ Opcodes.local_get, vecIdx ],
2744
- [ ...Opcodes[vecType + '_extract_lane'], i ],
2745
- [ Opcodes.local_set, tmpIdx[i] ],
2746
- ].filter(x => x !== null)),
2747
- ...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
2748
- );
2749
-
2750
- func.returns = new Array(lanes).fill(type);
2751
- }
2752
-
2753
3506
  func.wasm = wasm;
2754
3507
 
2755
3508
  funcs.push(func);
@@ -2785,7 +3538,7 @@ const internalConstrs = {
2785
3538
 
2786
3539
  // todo: check in wasm instead of here
2787
3540
  const literalValue = arg.value ?? 0;
2788
- 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);
2789
3542
 
2790
3543
  return [
2791
3544
  ...number(0, Valtype.i32),
@@ -2796,7 +3549,8 @@ const internalConstrs = {
2796
3549
  ...number(pointer)
2797
3550
  ];
2798
3551
  },
2799
- type: TYPES._array
3552
+ type: TYPES.array,
3553
+ length: 1
2800
3554
  },
2801
3555
 
2802
3556
  __Array_of: {
@@ -2807,27 +3561,134 @@ const internalConstrs = {
2807
3561
  elements: decl.arguments
2808
3562
  }, global, name);
2809
3563
  },
2810
- 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,
2811
3586
  notConstr: true
2812
- }
2813
- };
3587
+ },
2814
3588
 
2815
- // const _ = Array.prototype.push;
2816
- // Array.prototype.push = function (a) {
2817
- // const check = arr => {
2818
- // for (const x of arr) {
2819
- // if (x === undefined) {
2820
- // console.trace(arr);
2821
- // process.exit();
2822
- // }
2823
- // if (Array.isArray(x)) check(x);
2824
- // }
2825
- // };
2826
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2827
- // // if (Array.isArray(a)) check(a);
3589
+ __Porffor_fastAnd: {
3590
+ generate: (scope, decl) => {
3591
+ const out = [];
2828
3592
 
2829
- // return _.apply(this, arguments);
2830
- // };
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
+ };
2831
3692
 
2832
3693
  export default program => {
2833
3694
  globals = {};
@@ -2837,20 +3698,23 @@ export default program => {
2837
3698
  funcs = [];
2838
3699
  funcIndex = {};
2839
3700
  depth = [];
2840
- arrays = new Map();
2841
3701
  pages = new Map();
2842
3702
  data = [];
2843
3703
  currentFuncIndex = importedFuncs.length;
2844
3704
 
2845
3705
  globalThis.valtype = 'f64';
2846
3706
 
2847
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3707
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2848
3708
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2849
3709
 
2850
3710
  globalThis.valtypeBinary = Valtype[valtype];
2851
3711
 
2852
3712
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2853
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
+
2854
3718
  // set generic opcodes for current valtype
2855
3719
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2856
3720
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2859,10 +3723,10 @@ export default program => {
2859
3723
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2860
3724
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2861
3725
 
2862
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2863
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2864
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2865
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
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];
2866
3730
 
2867
3731
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2868
3732
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2875,10 +3739,6 @@ export default program => {
2875
3739
 
2876
3740
  program.id = { name: 'main' };
2877
3741
 
2878
- globalThis.pageSize = PageSize;
2879
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2880
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2881
-
2882
3742
  const scope = {
2883
3743
  locals: {},
2884
3744
  localInd: 0
@@ -2889,7 +3749,7 @@ export default program => {
2889
3749
  body: program.body
2890
3750
  };
2891
3751
 
2892
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3752
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2893
3753
 
2894
3754
  generateFunc(scope, program);
2895
3755
 
@@ -2906,7 +3766,11 @@ export default program => {
2906
3766
  }
2907
3767
 
2908
3768
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2909
- 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
+ }
2910
3774
  }
2911
3775
 
2912
3776
  if (lastInst[0] === Opcodes.call) {