porffor 0.2.0-fde989a → 0.14.0-032e4ad08

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 (64) hide show
  1. package/CONTRIBUTING.md +262 -0
  2. package/LICENSE +20 -20
  3. package/README.md +135 -94
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +216 -0
  7. package/compiler/2c.js +66 -54
  8. package/compiler/{sections.js → assemble.js} +109 -21
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +225 -0
  12. package/compiler/builtins/base64.ts +77 -0
  13. package/compiler/builtins/boolean.ts +20 -0
  14. package/compiler/builtins/crypto.ts +121 -0
  15. package/compiler/builtins/date.ts +2069 -0
  16. package/compiler/builtins/error.js +22 -0
  17. package/compiler/builtins/escape.ts +140 -0
  18. package/compiler/builtins/function.ts +7 -0
  19. package/compiler/builtins/int.ts +147 -0
  20. package/compiler/builtins/math.ts +410 -0
  21. package/compiler/builtins/number.ts +531 -0
  22. package/compiler/builtins/object.ts +6 -0
  23. package/compiler/builtins/porffor.d.ts +60 -0
  24. package/compiler/builtins/set.ts +199 -0
  25. package/compiler/builtins/string.ts +1081 -0
  26. package/compiler/builtins/symbol.ts +62 -0
  27. package/compiler/builtins.js +466 -284
  28. package/compiler/{codeGen.js → codegen.js} +1573 -656
  29. package/compiler/decompile.js +3 -4
  30. package/compiler/embedding.js +22 -22
  31. package/compiler/encoding.js +94 -10
  32. package/compiler/expression.js +1 -1
  33. package/compiler/generated_builtins.js +2110 -0
  34. package/compiler/index.js +29 -44
  35. package/compiler/log.js +6 -3
  36. package/compiler/opt.js +55 -41
  37. package/compiler/parse.js +38 -30
  38. package/compiler/precompile.js +121 -0
  39. package/compiler/prefs.js +31 -0
  40. package/compiler/prototype.js +209 -201
  41. package/compiler/types.js +38 -0
  42. package/compiler/wasmSpec.js +33 -8
  43. package/compiler/wrap.js +154 -89
  44. package/package.json +9 -5
  45. package/porf +2 -0
  46. package/porffor_tmp.c +202 -0
  47. package/rhemyn/compile.js +46 -27
  48. package/rhemyn/parse.js +322 -320
  49. package/rhemyn/test/parse.js +58 -58
  50. package/runner/compare.js +33 -34
  51. package/runner/debug.js +117 -0
  52. package/runner/index.js +80 -12
  53. package/runner/profiler.js +75 -0
  54. package/runner/repl.js +58 -15
  55. package/runner/sizes.js +37 -37
  56. package/runner/version.js +10 -8
  57. package/compiler/builtins/base64.js +0 -92
  58. package/filesize.cmd +0 -2
  59. package/runner/info.js +0 -89
  60. package/runner/profile.js +0 -46
  61. package/runner/results.json +0 -1
  62. package/runner/transform.js +0 -15
  63. package/tmp.c +0 -661
  64. package/util/enum.js +0 -20
@@ -1,12 +1,14 @@
1
- import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
- import { operatorOpcode } from "./expression.js";
4
- import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
- import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
- import { log } from "./log.js";
8
- import parse from "./parse.js";
9
- import * as Rhemyn from "../rhemyn/compile.js";
1
+ import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
3
+ import { operatorOpcode } from './expression.js';
4
+ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
5
+ import { PrototypeFuncs } from './prototype.js';
6
+ import { number } from './embedding.js';
7
+ import { TYPES, TYPE_NAMES } from './types.js';
8
+ import * as Rhemyn from '../rhemyn/compile.js';
9
+ import parse from './parse.js';
10
+ import { log } from './log.js';
11
+ import Prefs from './prefs.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -17,44 +19,32 @@ let funcIndex = {};
17
19
  let currentFuncIndex = importedFuncs.length;
18
20
  let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
19
21
 
20
- const debug = str => {
21
- const code = [];
22
-
23
- const logChar = n => {
24
- code.push(...number(n));
25
-
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
- };
29
-
30
- for (let i = 0; i < str.length; i++) {
31
- logChar(str.charCodeAt(i));
22
+ class TodoError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = 'TodoError';
32
26
  }
27
+ }
28
+ const todo = (scope, msg, expectsValue = undefined) => {
29
+ switch (Prefs.todoTime ?? 'runtime') {
30
+ case 'compile':
31
+ throw new TodoError(msg);
33
32
 
34
- logChar('\n'.charCodeAt(0));
35
-
36
- return code;
37
- };
33
+ case 'runtime':
34
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
38
35
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
36
+ // return [
37
+ // ...debug(`todo! ${msg}`),
38
+ // [ Opcodes.unreachable ]
39
+ // ];
45
40
  }
46
-
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
53
-
54
- return code;
55
41
  };
56
42
 
57
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
58
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
49
  switch (decl.type) {
60
50
  case 'BinaryExpression':
@@ -68,10 +58,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
68
58
 
69
59
  case 'ArrowFunctionExpression':
70
60
  case 'FunctionDeclaration':
61
+ case 'FunctionExpression':
71
62
  const func = generateFunc(scope, decl);
72
63
 
73
64
  if (decl.type.endsWith('Expression')) {
74
- return number(func.index);
65
+ return number(func.index - importedFuncs.length);
75
66
  }
76
67
 
77
68
  return [];
@@ -104,7 +95,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
95
  return generateUnary(scope, decl);
105
96
 
106
97
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
98
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
99
 
109
100
  case 'IfStatement':
110
101
  return generateIf(scope, decl);
@@ -115,6 +106,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
106
  case 'WhileStatement':
116
107
  return generateWhile(scope, decl);
117
108
 
109
+ case 'DoWhileStatement':
110
+ return generateDoWhile(scope, decl);
111
+
118
112
  case 'ForOfStatement':
119
113
  return generateForOf(scope, decl);
120
114
 
@@ -124,6 +118,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
118
  case 'ContinueStatement':
125
119
  return generateContinue(scope, decl);
126
120
 
121
+ case 'LabeledStatement':
122
+ return generateLabel(scope, decl);
123
+
127
124
  case 'EmptyStatement':
128
125
  return generateEmpty(scope, decl);
129
126
 
@@ -137,30 +134,36 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
134
  return generateTry(scope, decl);
138
135
 
139
136
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
137
+ // todo: hook into terminal debugger
141
138
  return [];
142
139
 
143
140
  case 'ArrayExpression':
144
141
  return generateArray(scope, decl, global, name);
145
142
 
143
+ case 'ObjectExpression':
144
+ return generateObject(scope, decl, global, name);
145
+
146
146
  case 'MemberExpression':
147
147
  return generateMember(scope, decl, global, name);
148
148
 
149
149
  case 'ExportNamedDeclaration':
150
- // hack to flag new func for export
151
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
152
151
  generate(scope, decl.declaration);
153
152
 
154
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
155
156
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
160
+ }
158
161
 
159
162
  return [];
160
163
 
161
164
  case 'TaggedTemplateExpression': {
162
165
  const funcs = {
163
- __Porffor_asm: str => {
166
+ __Porffor_wasm: str => {
164
167
  let out = [];
165
168
 
166
169
  for (const line of str.split('\n')) {
@@ -168,8 +171,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
171
  if (asm[0] === '') continue; // blank
169
172
 
170
173
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
174
+ const [ name, type ] = asm.slice(1);
175
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
176
  continue;
174
177
  }
175
178
 
@@ -179,52 +182,66 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
182
  }
180
183
 
181
184
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
185
+ allocPage(scope, 'asm instrinsic');
183
186
  // todo: add to store/load offset insts
184
187
  continue;
185
188
  }
186
189
 
187
190
  let inst = Opcodes[asm[0].replace('.', '_')];
188
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
191
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
192
 
190
193
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
194
+ const immediates = asm.slice(1).map(x => {
195
+ const int = parseInt(x);
196
+ if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
197
+ return int;
198
+ });
192
199
 
193
- out.push([ ...inst, ...immediates ]);
200
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
201
  }
195
202
 
196
203
  return out;
197
204
  },
198
205
 
199
206
  __Porffor_bs: str => [
200
- ...makeString(scope, str, undefined, undefined, true),
201
-
202
- ...number(TYPES._bytestring, Valtype.i32),
203
- setLastType(scope)
207
+ ...makeString(scope, str, global, name, true),
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
204
209
  ],
205
210
  __Porffor_s: str => [
206
- ...makeString(scope, str, undefined, undefined, false),
207
-
208
- ...number(TYPES.string, Valtype.i32),
209
- setLastType(scope)
211
+ ...makeString(scope, str, global, name, false),
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
210
213
  ],
211
214
  };
212
215
 
213
- const name = decl.tag.name;
216
+ const func = decl.tag.name;
214
217
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
218
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
219
+
220
+ const { quasis, expressions } = decl.quasi;
221
+ let str = quasis[0].value.raw;
222
+
223
+ for (let i = 0; i < expressions.length; i++) {
224
+ const e = expressions[i];
225
+ if (!e.name) {
226
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
227
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
228
+ } else todo(scope, 'unsupported expression in intrinsic');
229
+ } else str += lookupName(scope, e.name)[0].idx;
230
+
231
+ str += quasis[i + 1].value.raw;
232
+ }
216
233
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
234
+ return funcs[func](str);
219
235
  }
220
236
 
221
237
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
238
+ // ignore typescript nodes
239
+ if (decl.type.startsWith('TS') ||
240
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
241
  return [];
225
242
  }
226
243
 
227
- return todo(`no generation for ${decl.type}!`);
244
+ return todo(scope, `no generation for ${decl.type}!`);
228
245
  }
229
246
  };
230
247
 
@@ -252,7 +269,7 @@ const lookupName = (scope, _name) => {
252
269
  return [ undefined, undefined ];
253
270
  };
254
271
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
272
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
273
  ...generateThrow(scope, {
257
274
  argument: {
258
275
  type: 'NewExpression',
@@ -276,7 +293,10 @@ const generateIdent = (scope, decl) => {
276
293
 
277
294
  if (Object.hasOwn(builtinVars, name)) {
278
295
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
296
+
297
+ let wasm = builtinVars[name];
298
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
299
+ return wasm.slice();
280
300
  }
281
301
 
282
302
  if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
@@ -284,12 +304,17 @@ const generateIdent = (scope, decl) => {
284
304
  return number(1);
285
305
  }
286
306
 
307
+ if (isExistingProtoFunc(name)) {
308
+ // todo: return an actual something
309
+ return number(1);
310
+ }
311
+
287
312
  if (local?.idx === undefined) {
288
313
  // no local var with name
289
- if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
290
- if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
-
292
314
  if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
315
+
316
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
317
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
293
318
  }
294
319
 
295
320
  if (local?.idx === undefined && rawName.startsWith('__')) {
@@ -314,14 +339,16 @@ const generateReturn = (scope, decl) => {
314
339
  // just bare "return"
315
340
  return [
316
341
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
342
+ ...(scope.returnType != null ? [] : [
343
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
344
+ ]),
318
345
  [ Opcodes.return ]
319
346
  ];
320
347
  }
321
348
 
322
349
  return [
323
350
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
351
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
325
352
  [ Opcodes.return ]
326
353
  ];
327
354
  };
@@ -335,7 +362,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
362
  return idx;
336
363
  };
337
364
 
338
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
365
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
339
367
 
340
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
369
  const checks = {
@@ -344,7 +372,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
372
  '??': nullish
345
373
  };
346
374
 
347
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
375
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
348
376
 
349
377
  // generic structure for {a} OP {b}
350
378
  // -->
@@ -352,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
380
 
353
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
382
  // (like if we are in an if condition - very common)
355
- const leftIsInt = isIntOp(left[left.length - 1]);
356
- const rightIsInt = isIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
357
385
 
358
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
359
387
 
360
388
  if (canInt) {
361
389
  // remove int -> float conversions from left and right
@@ -369,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
369
397
  [ Opcodes.if, Valtype.i32 ],
370
398
  ...right,
371
399
  // note type
372
- ...rightType,
373
- setLastType(scope),
400
+ ...setLastType(scope, rightType),
374
401
  [ Opcodes.else ],
375
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
403
  // note type
377
- ...leftType,
378
- setLastType(scope),
404
+ ...setLastType(scope, leftType),
379
405
  [ Opcodes.end ],
380
406
  Opcodes.i32_from
381
407
  ];
@@ -388,18 +414,16 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
388
414
  [ Opcodes.if, valtypeBinary ],
389
415
  ...right,
390
416
  // note type
391
- ...rightType,
392
- setLastType(scope),
417
+ ...setLastType(scope, rightType),
393
418
  [ Opcodes.else ],
394
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
420
  // note type
396
- ...leftType,
397
- setLastType(scope),
421
+ ...setLastType(scope, leftType),
398
422
  [ Opcodes.end ]
399
423
  ];
400
424
  };
401
425
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
426
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
427
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
428
  // todo: convert left and right to strings if not
405
429
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +433,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
433
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
434
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
435
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
- if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
436
+ if (assign && Prefs.aotPointerOpt) {
437
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
438
 
418
439
  return [
419
440
  // setup right
@@ -425,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
425
446
  ...number(0, Valtype.i32), // base 0 for store later
426
447
 
427
448
  ...number(pointer, Valtype.i32),
428
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
429
450
  [ Opcodes.local_tee, leftLength ],
430
451
 
431
452
  [ Opcodes.local_get, rightPointer ],
432
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
433
454
  [ Opcodes.local_tee, rightLength ],
434
455
 
435
456
  [ Opcodes.i32_add ],
@@ -438,11 +459,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
459
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
460
 
440
461
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
462
+ // dst = out pointer + length size + current length * sizeof valtype
442
463
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
464
 
444
465
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
466
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
467
  [ Opcodes.i32_mul ],
447
468
  [ Opcodes.i32_add ],
448
469
 
@@ -451,9 +472,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
472
  ...number(ValtypeSize.i32, Valtype.i32),
452
473
  [ Opcodes.i32_add ],
453
474
 
454
- // size = right length * i16 size
475
+ // size = right length * sizeof valtype
455
476
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
477
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
478
  [ Opcodes.i32_mul ],
458
479
 
459
480
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -485,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
485
506
  ...number(0, Valtype.i32), // base 0 for store later
486
507
 
487
508
  [ Opcodes.local_get, leftPointer ],
488
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
489
510
  [ Opcodes.local_tee, leftLength ],
490
511
 
491
512
  [ Opcodes.local_get, rightPointer ],
492
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
493
514
  [ Opcodes.local_tee, rightLength ],
494
515
 
495
516
  [ Opcodes.i32_add ],
@@ -511,11 +532,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
532
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
533
 
513
534
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
535
+ // dst = out pointer + length size + left length * sizeof valtype
515
536
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
537
 
517
538
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
539
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
540
  [ Opcodes.i32_mul ],
520
541
  [ Opcodes.i32_add ],
521
542
 
@@ -524,9 +545,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
545
  ...number(ValtypeSize.i32, Valtype.i32),
525
546
  [ Opcodes.i32_add ],
526
547
 
527
- // size = right length * i16 size
548
+ // size = right length * sizeof valtype
528
549
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
550
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
551
  [ Opcodes.i32_mul ],
531
552
 
532
553
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +557,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
557
  ];
537
558
  };
538
559
 
539
- const compareStrings = (scope, left, right) => {
560
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
561
  // todo: this should be rewritten into a func
541
562
  // todo: convert left and right to strings if not
542
563
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +566,6 @@ const compareStrings = (scope, left, right) => {
545
566
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
567
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
568
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
569
 
550
570
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
571
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -568,12 +588,11 @@ const compareStrings = (scope, left, right) => {
568
588
 
569
589
  // get lengths
570
590
  [ Opcodes.local_get, leftPointer ],
571
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
572
592
  [ Opcodes.local_tee, leftLength ],
573
593
 
574
594
  [ Opcodes.local_get, rightPointer ],
575
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
577
596
 
578
597
  // fast path: check leftLength != rightLength
579
598
  [ Opcodes.i32_ne ],
@@ -588,11 +607,13 @@ const compareStrings = (scope, left, right) => {
588
607
  ...number(0, Valtype.i32),
589
608
  [ Opcodes.local_set, index ],
590
609
 
591
- // setup index end as length * sizeof i16 (2)
610
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
611
  // we do this instead of having to do mul/div each iter for perf™
593
612
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
613
+ ...(bytestrings ? [] : [
614
+ ...number(ValtypeSize.i16, Valtype.i32),
615
+ [ Opcodes.i32_mul ],
616
+ ]),
596
617
  [ Opcodes.local_set, indexEnd ],
597
618
 
598
619
  // iterate over each char and check if eq
@@ -602,13 +623,17 @@ const compareStrings = (scope, left, right) => {
602
623
  [ Opcodes.local_get, index ],
603
624
  [ Opcodes.local_get, leftPointer ],
604
625
  [ Opcodes.i32_add ],
605
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
626
+ bytestrings ?
627
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
628
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
606
629
 
607
630
  // fetch right
608
631
  [ Opcodes.local_get, index ],
609
632
  [ Opcodes.local_get, rightPointer ],
610
633
  [ Opcodes.i32_add ],
611
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
634
+ bytestrings ?
635
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
636
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
612
637
 
613
638
  // not equal, "return" false
614
639
  [ Opcodes.i32_ne ],
@@ -617,15 +642,15 @@ const compareStrings = (scope, left, right) => {
617
642
  [ Opcodes.br, 2 ],
618
643
  [ Opcodes.end ],
619
644
 
620
- // index += sizeof i16 (2)
645
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
646
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
647
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
648
  [ Opcodes.i32_add ],
624
649
  [ Opcodes.local_tee, index ],
625
650
 
626
- // if index != index end (length * sizeof 16), loop
651
+ // if index < index end (length * sizeof valtype), loop
627
652
  [ Opcodes.local_get, indexEnd ],
628
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
629
654
  [ Opcodes.br_if, 0 ],
630
655
  [ Opcodes.end ],
631
656
 
@@ -643,38 +668,52 @@ const compareStrings = (scope, left, right) => {
643
668
  ];
644
669
  };
645
670
 
646
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
671
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
648
673
  ...wasm,
649
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
675
  ];
676
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
677
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
653
679
 
654
- const def = [
655
- // if value != 0
656
- [ Opcodes.local_get, tmp ],
680
+ const useTmp = knownType(scope, type) == null;
681
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
657
682
 
658
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
660
688
 
661
- /* Opcodes.eqz,
662
- [ Opcodes.i32_eqz ],
663
- Opcodes.i32_from */
664
- ];
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
691
+
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
665
709
 
666
710
  return [
667
711
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
712
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
713
 
670
714
  ...typeSwitch(scope, type, {
671
- // [TYPES.number]: def,
672
- [TYPES._array]: [
673
- // arrays are always truthy
674
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
- ],
676
715
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
716
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
718
 
680
719
  // get length
@@ -685,8 +724,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
724
  [ Opcodes.i32_eqz ], */
686
725
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
726
  ],
688
- [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
727
+ [TYPES.bytestring]: [ // duplicate of string
728
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
729
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
730
 
692
731
  // get length
@@ -700,18 +739,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
739
  };
701
740
 
702
741
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
742
+ const useTmp = knownType(scope, type) == null;
743
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
744
+
704
745
  return [
705
746
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
747
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
748
 
708
749
  ...typeSwitch(scope, type, {
709
- [TYPES._array]: [
710
- // arrays are always truthy
711
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
- ],
713
750
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
751
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
753
 
717
754
  // get length
@@ -721,8 +758,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
721
758
  [ Opcodes.i32_eqz ],
722
759
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
760
  ],
724
- [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
761
+ [TYPES.bytestring]: [ // duplicate of string
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
763
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
764
 
728
765
  // get length
@@ -734,7 +771,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
771
  ],
735
772
  default: [
736
773
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
774
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
775
 
739
776
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
777
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +781,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
781
  };
745
782
 
746
783
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
784
+ const useTmp = knownType(scope, type) == null;
785
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
786
+
748
787
  return [
749
788
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
789
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
790
 
752
791
  ...typeSwitch(scope, type, {
753
792
  [TYPES.undefined]: [
@@ -756,7 +795,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
795
  ],
757
796
  [TYPES.object]: [
758
797
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
798
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
799
 
761
800
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
801
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,39 +824,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
824
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
825
  }
787
826
 
827
+ const knownLeft = knownType(scope, leftType);
828
+ const knownRight = knownType(scope, rightType);
829
+
788
830
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
831
  const strictOp = op === '===' || op === '!==';
790
832
 
791
833
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
834
+ const finalize = out => startOut.concat(out, endOut);
793
835
 
794
836
  // if strict (in)equal check types match
795
837
  if (strictOp) {
796
- // startOut.push(
797
- // ...leftType,
798
- // ...rightType,
799
- // [ Opcodes.i32_eq ]
800
- // );
801
-
802
- // endOut.push(
803
- // [ Opcodes.i32_and ]
804
- // );
805
-
806
- // startOut.push(
807
- // [ Opcodes.block, Valtype.i32 ],
808
- // ...leftType,
809
- // ...rightType,
810
- // [ Opcodes.i32_ne ],
811
- // [ Opcodes.if, Blocktype.void ],
812
- // ...number(op === '===' ? 0 : 1, Valtype.i32),
813
- // [ Opcodes.br, 1 ],
814
- // [ Opcodes.end ]
815
- // );
816
-
817
- // endOut.push(
818
- // [ Opcodes.end ]
819
- // );
820
-
821
838
  endOut.push(
822
839
  ...leftType,
823
840
  ...rightType,
@@ -834,31 +851,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
851
  // todo: if equality op and an operand is undefined, return false
835
852
  // todo: niche null hell with 0
836
853
 
837
- // if (leftType === TYPES.string || rightType === TYPES.string) {
838
- // if (op === '+') {
839
- // // string concat (a + b)
840
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
841
- // }
842
-
843
- // // not an equality op, NaN
844
- // if (!eqOp) return finalise(number(NaN));
845
-
846
- // // else leave bool ops
847
- // // todo: convert string to number if string and number/bool
848
- // // todo: string (>|>=|<|<=) string
849
-
850
- // // string comparison
851
- // if (op === '===' || op === '==') {
852
- // return finalise(compareStrings(scope, left, right));
853
- // }
854
-
855
- // if (op === '!==' || op === '!=') {
856
- // return finalise([
857
- // ...compareStrings(scope, left, right),
858
- // [ Opcodes.i32_eqz ]
859
- // ]);
860
- // }
861
- // }
854
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
855
+ if (op === '+') {
856
+ // todo: this should be dynamic too but for now only static
857
+ // string concat (a + b)
858
+ return concatStrings(scope, left, right, _global, _name, assign, false);
859
+ }
860
+
861
+ // not an equality op, NaN
862
+ if (!eqOp) return number(NaN);
863
+
864
+ // else leave bool ops
865
+ // todo: convert string to number if string and number/bool
866
+ // todo: string (>|>=|<|<=) string
867
+
868
+ // string comparison
869
+ if (op === '===' || op === '==') {
870
+ return compareStrings(scope, left, right);
871
+ }
872
+
873
+ if (op === '!==' || op === '!=') {
874
+ return [
875
+ ...compareStrings(scope, left, right),
876
+ [ Opcodes.i32_eqz ]
877
+ ];
878
+ }
879
+ }
880
+
881
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
882
+ if (op === '+') {
883
+ // todo: this should be dynamic too but for now only static
884
+ // string concat (a + b)
885
+ return concatStrings(scope, left, right, _global, _name, assign, true);
886
+ }
887
+
888
+ // not an equality op, NaN
889
+ if (!eqOp) return number(NaN);
890
+
891
+ // else leave bool ops
892
+ // todo: convert string to number if string and number/bool
893
+ // todo: string (>|>=|<|<=) string
894
+
895
+ // string comparison
896
+ if (op === '===' || op === '==') {
897
+ return compareStrings(scope, left, right, true);
898
+ }
899
+
900
+ if (op === '!==' || op === '!=') {
901
+ return [
902
+ ...compareStrings(scope, left, right, true),
903
+ [ Opcodes.i32_eqz ]
904
+ ];
905
+ }
906
+ }
862
907
 
863
908
  let ops = operatorOpcode[valtype][op];
864
909
 
@@ -868,33 +913,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
913
  includeBuiltin(scope, builtinName);
869
914
  const idx = funcIndex[builtinName];
870
915
 
871
- return finalise([
916
+ return finalize([
872
917
  ...left,
873
918
  ...right,
874
919
  [ Opcodes.call, idx ]
875
920
  ]);
876
921
  }
877
922
 
878
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
923
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
879
924
 
880
925
  if (!Array.isArray(ops)) ops = [ ops ];
881
926
  ops = [ ops ];
882
927
 
883
928
  let tmpLeft, tmpRight;
884
929
  // if equal op, check if strings for compareStrings
885
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
886
- const knownLeft = knownType(scope, leftType);
887
- const knownRight = knownType(scope, rightType);
888
-
889
- // todo: intelligent partial skip later
890
- // if neither known are string, stop this madness
891
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
892
- return;
893
- }
930
+ // todo: intelligent partial skip later
931
+ // if neither known are string, stop this madness
932
+ // we already do known checks earlier, so don't need to recheck
894
933
 
934
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
935
  tmpLeft = localTmp(scope, '__tmpop_left');
896
936
  tmpRight = localTmp(scope, '__tmpop_right');
897
937
 
938
+ // returns false for one string, one not - but more ops/slower
939
+ // ops.unshift(...stringOnly([
940
+ // // if left is string
941
+ // ...leftType,
942
+ // ...number(TYPES.string, Valtype.i32),
943
+ // [ Opcodes.i32_eq ],
944
+
945
+ // // if right is string
946
+ // ...rightType,
947
+ // ...number(TYPES.string, Valtype.i32),
948
+ // [ Opcodes.i32_eq ],
949
+
950
+ // // if either are true
951
+ // [ Opcodes.i32_or ],
952
+ // [ Opcodes.if, Blocktype.void ],
953
+
954
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
955
+ // // if left is not string
956
+ // ...leftType,
957
+ // ...number(TYPES.string, Valtype.i32),
958
+ // [ Opcodes.i32_ne ],
959
+
960
+ // // if right is not string
961
+ // ...rightType,
962
+ // ...number(TYPES.string, Valtype.i32),
963
+ // [ Opcodes.i32_ne ],
964
+
965
+ // // if either are true
966
+ // [ Opcodes.i32_or ],
967
+ // [ Opcodes.if, Blocktype.void ],
968
+ // ...number(0, Valtype.i32),
969
+ // [ Opcodes.br, 2 ],
970
+ // [ Opcodes.end ],
971
+
972
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
973
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
974
+ // [ Opcodes.br, 1 ],
975
+ // [ Opcodes.end ],
976
+ // ]));
977
+
978
+ // does not handle one string, one not (such cases go past)
898
979
  ops.unshift(...stringOnly([
899
980
  // if left is string
900
981
  ...leftType,
@@ -906,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
987
  ...number(TYPES.string, Valtype.i32),
907
988
  [ Opcodes.i32_eq ],
908
989
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
990
+ // if both are true
991
+ [ Opcodes.i32_and ],
911
992
  [ Opcodes.if, Blocktype.void ],
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
994
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
995
+ [ Opcodes.br, 1 ],
996
+ [ Opcodes.end ],
912
997
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
998
+ // if left is bytestring
915
999
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
1000
+ ...number(TYPES.bytestring, Valtype.i32),
1001
+ [ Opcodes.i32_eq ],
918
1002
 
919
- // if right is not string
1003
+ // if right is bytestring
920
1004
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1005
+ ...number(TYPES.bytestring, Valtype.i32),
1006
+ [ Opcodes.i32_eq ],
923
1007
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1008
+ // if both are true
1009
+ [ Opcodes.i32_and ],
926
1010
  [ Opcodes.if, Blocktype.void ],
927
- ...number(0, Valtype.i32),
928
- [ Opcodes.br, 2 ],
929
- [ Opcodes.end ],
930
-
931
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
932
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1011
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1012
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1013
  [ Opcodes.br, 1 ],
935
1014
  [ Opcodes.end ],
@@ -941,9 +1020,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1020
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1021
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1022
  // }
944
- })();
1023
+ }
945
1024
 
946
- return finalise([
1025
+ return finalize([
947
1026
  ...left,
948
1027
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1028
  ...right,
@@ -960,7 +1039,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1039
  return out;
961
1040
  };
962
1041
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1044
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1050
+ }
1051
+
1052
+ return idx;
1053
+ }
1054
+ });
1055
+ };
1056
+
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
964
1058
  const existing = funcs.find(x => x.name === name);
965
1059
  if (existing) return existing;
966
1060
 
@@ -972,18 +1066,29 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1066
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1067
  }
974
1068
 
975
- if (typeof wasm === 'function') {
976
- const scope = {
977
- name,
978
- params,
979
- locals,
980
- returns,
981
- localInd: allLocals.length,
982
- };
983
-
984
- wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
1069
+ for (const x of _data) {
1070
+ const copy = { ...x };
1071
+ copy.offset += pages.size * pageSize;
1072
+ data.push(copy);
985
1073
  }
986
1074
 
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType: returnType ?? TYPES.number,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1091
+
987
1092
  let baseGlobalIdx, i = 0;
988
1093
  for (const type of globalTypes) {
989
1094
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1001,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1001
1106
  }
1002
1107
  }
1003
1108
 
1004
- const func = {
1005
- name,
1006
- params,
1007
- locals,
1008
- returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1010
- wasm,
1011
- internal: true,
1012
- index: currentFuncIndex++
1013
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1014
1115
 
1015
- funcs.push(func);
1016
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1017
1117
 
1018
1118
  return func;
1019
1119
  };
@@ -1029,6 +1129,7 @@ const generateLogicExp = (scope, decl) => {
1029
1129
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1130
  };
1031
1131
 
1132
+ // potential future ideas for nan boxing (unused):
1032
1133
  // T = JS type, V = value/pointer
1033
1134
  // 0bTTT
1034
1135
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1042,7 +1143,6 @@ const generateLogicExp = (scope, decl) => {
1042
1143
  // js type: 4 bits
1043
1144
  // internal type: ? bits
1044
1145
  // pointer: 32 bits
1045
-
1046
1146
  // generic
1047
1147
  // 1 23 4 5
1048
1148
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1052,49 +1152,29 @@ const generateLogicExp = (scope, decl) => {
1052
1152
  // 4: internal type
1053
1153
  // 5: pointer
1054
1154
 
1055
- const TYPES = {
1056
- number: 0x00,
1057
- boolean: 0x01,
1058
- string: 0x02,
1059
- undefined: 0x03,
1060
- object: 0x04,
1061
- function: 0x05,
1062
- symbol: 0x06,
1063
- bigint: 0x07,
1064
-
1065
- // these are not "typeof" types but tracked internally
1066
- _array: 0x10,
1067
- _regexp: 0x11,
1068
- _bytestring: 0x12
1069
- };
1070
-
1071
- const TYPE_NAMES = {
1072
- [TYPES.number]: 'Number',
1073
- [TYPES.boolean]: 'Boolean',
1074
- [TYPES.string]: 'String',
1075
- [TYPES.undefined]: 'undefined',
1076
- [TYPES.object]: 'Object',
1077
- [TYPES.function]: 'Function',
1078
- [TYPES.symbol]: 'Symbol',
1079
- [TYPES.bigint]: 'BigInt',
1080
-
1081
- [TYPES._array]: 'Array',
1082
- [TYPES._regexp]: 'RegExp',
1083
- [TYPES._bytestring]: 'ByteString'
1155
+ const isExistingProtoFunc = name => {
1156
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1157
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1158
+
1159
+ return false;
1084
1160
  };
1085
1161
 
1086
1162
  const getType = (scope, _name) => {
1087
1163
  const name = mapName(_name);
1088
1164
 
1165
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1166
+
1167
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1168
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1169
+
1170
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1171
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1172
 
1092
1173
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1174
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1175
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1176
 
1096
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1097
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1177
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1178
 
1099
1179
  return number(type, Valtype.i32);
1100
1180
  };
@@ -1117,23 +1197,25 @@ const setType = (scope, _name, type) => {
1117
1197
  ];
1118
1198
 
1119
1199
  // throw new Error('could not find var');
1200
+ return [];
1120
1201
  };
1121
1202
 
1122
1203
  const getLastType = scope => {
1123
1204
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1205
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1206
  };
1126
1207
 
1127
- const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1129
- };
1208
+ const setLastType = (scope, type = []) => [
1209
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1210
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1211
+ ];
1130
1212
 
1131
1213
  const getNodeType = (scope, node) => {
1132
- const inner = () => {
1214
+ const ret = (() => {
1133
1215
  if (node.type === 'Literal') {
1134
- if (node.regex) return TYPES._regexp;
1216
+ if (node.regex) return TYPES.regexp;
1135
1217
 
1136
- if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1218
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1137
1219
 
1138
1220
  return TYPES[typeof node.value];
1139
1221
  }
@@ -1150,21 +1232,32 @@ const getNodeType = (scope, node) => {
1150
1232
  const name = node.callee.name;
1151
1233
  if (!name) {
1152
1234
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1235
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1236
 
1155
1237
  // presume
1156
1238
  // todo: warn here?
1157
1239
  return TYPES.number;
1158
1240
  }
1159
1241
 
1242
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1243
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1244
+ if (scope.locals['#last_type']) return getLastType(scope);
1245
+
1246
+ // presume
1247
+ // todo: warn here?
1248
+ return TYPES.number;
1249
+ }
1250
+
1251
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1252
+ }
1253
+
1160
1254
  const func = funcs.find(x => x.name === name);
1161
1255
 
1162
1256
  if (func) {
1163
- // console.log(scope, func, func.returnType);
1164
1257
  if (func.returnType) return func.returnType;
1165
1258
  }
1166
1259
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1260
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1261
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1262
 
1170
1263
  // check if this is a prototype function
@@ -1175,11 +1268,26 @@ const getNodeType = (scope, node) => {
1175
1268
  const spl = name.slice(2).split('_');
1176
1269
 
1177
1270
  const func = spl[spl.length - 1];
1178
- const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1179
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1271
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1283
+ }
1284
+
1285
+ if (name.startsWith('__Porffor_wasm_')) {
1286
+ // todo: return undefined for non-returning ops
1287
+ return TYPES.number;
1180
1288
  }
1181
1289
 
1182
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1290
+ if (scope.locals['#last_type']) return getLastType(scope);
1183
1291
 
1184
1292
  // presume
1185
1293
  // todo: warn here?
@@ -1222,11 +1330,20 @@ const getNodeType = (scope, node) => {
1222
1330
  }
1223
1331
 
1224
1332
  if (node.type === 'ArrayExpression') {
1225
- return TYPES._array;
1333
+ return TYPES.array;
1226
1334
  }
1227
1335
 
1228
1336
  if (node.type === 'BinaryExpression') {
1229
1337
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1338
+ if (node.operator !== '+') return TYPES.number;
1339
+
1340
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1341
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1342
+
1343
+ // todo: this should be dynamic but for now only static
1344
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1345
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1346
+
1230
1347
  return TYPES.number;
1231
1348
 
1232
1349
  // todo: string concat types
@@ -1251,34 +1368,52 @@ const getNodeType = (scope, node) => {
1251
1368
  if (node.operator === '!') return TYPES.boolean;
1252
1369
  if (node.operator === 'void') return TYPES.undefined;
1253
1370
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1371
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1255
1372
 
1256
1373
  return TYPES.number;
1257
1374
  }
1258
1375
 
1259
1376
  if (node.type === 'MemberExpression') {
1260
- // hack: if something.length, number type
1261
- if (node.property.name === 'length') return TYPES.number;
1377
+ const name = node.property.name;
1378
+
1379
+ if (name === 'length') {
1380
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1381
+ if (Prefs.fastLength) return TYPES.number;
1382
+ }
1383
+
1384
+
1385
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1386
+ if (objectKnownType != null) {
1387
+ if (name === 'length') {
1388
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1389
+ else return TYPES.undefined;
1390
+ }
1262
1391
 
1263
- // ts hack
1264
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1265
- if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1392
+ if (node.computed) {
1393
+ if (objectKnownType === TYPES.string) return TYPES.string;
1394
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1395
+ if (objectKnownType === TYPES.array) return TYPES.number;
1396
+ }
1397
+ }
1266
1398
 
1267
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1399
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
1400
 
1269
1401
  // presume
1270
1402
  return TYPES.number;
1271
1403
  }
1272
1404
 
1273
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1405
+ if (node.type === 'TaggedTemplateExpression') {
1406
+ // hack
1407
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1408
+ }
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1274
1411
 
1275
1412
  // presume
1276
1413
  // todo: warn here?
1277
1414
  return TYPES.number;
1278
- };
1415
+ })();
1279
1416
 
1280
- const ret = inner();
1281
- // console.trace(node, ret);
1282
1417
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1283
1418
  return ret;
1284
1419
  };
@@ -1303,7 +1438,7 @@ const generateLiteral = (scope, decl, global, name) => {
1303
1438
  return makeString(scope, decl.value, global, name);
1304
1439
 
1305
1440
  default:
1306
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1441
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1307
1442
  }
1308
1443
  };
1309
1444
 
@@ -1312,6 +1447,8 @@ const countLeftover = wasm => {
1312
1447
 
1313
1448
  for (let i = 0; i < wasm.length; i++) {
1314
1449
  const inst = wasm[i];
1450
+ if (inst[0] == null) continue;
1451
+
1315
1452
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1316
1453
  if (inst[0] === Opcodes.if) count--;
1317
1454
  if (inst[1] !== Blocktype.void) count++;
@@ -1320,18 +1457,24 @@ const countLeftover = wasm => {
1320
1457
  if (inst[0] === Opcodes.end) depth--;
1321
1458
 
1322
1459
  if (depth === 0)
1323
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1460
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1324
1461
  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)) {}
1325
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1462
+ 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++;
1326
1463
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1327
1464
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1328
1465
  else if (inst[0] === Opcodes.return) count = 0;
1329
1466
  else if (inst[0] === Opcodes.call) {
1330
1467
  let func = funcs.find(x => x.index === inst[1]);
1331
- if (func) {
1332
- count -= func.params.length;
1333
- } else count--;
1334
- if (func) count += func.returns.length;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1471
+ } else {
1472
+ count = count - func.params.length + func.returns.length;
1473
+ }
1474
+ } else if (inst[0] === Opcodes.call_indirect) {
1475
+ count--; // funcidx
1476
+ count -= inst[1] * 2; // params * 2 (typed)
1477
+ count += 2; // fixed return (value, type)
1335
1478
  } else count--;
1336
1479
 
1337
1480
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1349,7 +1492,7 @@ const disposeLeftover = wasm => {
1349
1492
  const generateExp = (scope, decl) => {
1350
1493
  const expression = decl.expression;
1351
1494
 
1352
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1353
1496
  disposeLeftover(out);
1354
1497
 
1355
1498
  return out;
@@ -1408,25 +1551,27 @@ const RTArrayUtil = {
1408
1551
  };
1409
1552
 
1410
1553
  const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1411
- /* const callee = decl.callee;
1412
- const args = decl.arguments;
1413
-
1414
- return [
1415
- ...generate(args),
1416
- ...generate(callee),
1417
- Opcodes.call_indirect,
1418
- ]; */
1419
-
1420
1554
  let name = mapName(decl.callee.name);
1421
1555
  if (isFuncType(decl.callee.type)) { // iife
1422
1556
  const func = generateFunc(scope, decl.callee);
1423
1557
  name = func.name;
1424
1558
  }
1425
1559
 
1426
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1560
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1427
1561
  // literal eval hack
1428
- const code = decl.arguments[0].value;
1429
- const parsed = parse(code, []);
1562
+ const code = decl.arguments[0]?.value ?? '';
1563
+
1564
+ let parsed;
1565
+ try {
1566
+ parsed = parse(code, []);
1567
+ } catch (e) {
1568
+ if (e.name === 'SyntaxError') {
1569
+ // throw syntax errors of evals at runtime instead
1570
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1571
+ }
1572
+
1573
+ throw e;
1574
+ }
1430
1575
 
1431
1576
  const out = generate(scope, {
1432
1577
  type: 'BlockStatement',
@@ -1438,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1438
1583
  out.splice(out.length - 1, 1);
1439
1584
 
1440
1585
  const finalStatement = parsed.body[parsed.body.length - 1];
1441
- out.push(
1442
- ...getNodeType(scope, finalStatement),
1443
- setLastType(scope)
1444
- );
1586
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1445
1587
  } else if (countLeftover(out) === 0) {
1446
1588
  out.push(...number(UNDEFINED));
1447
- out.push(
1448
- ...number(TYPES.undefined, Valtype.i32),
1449
- setLastType(scope)
1450
- );
1589
+ out.push(...setLastType(scope, TYPES.undefined));
1451
1590
  }
1452
1591
 
1453
1592
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1468,6 +1607,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1468
1607
 
1469
1608
  target = { ...decl.callee };
1470
1609
  target.name = spl.slice(0, -1).join('_');
1610
+
1611
+ // failed to lookup name, abort
1612
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1471
1613
  }
1472
1614
 
1473
1615
  // literal.func()
@@ -1475,22 +1617,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1475
1617
  // megahack for /regex/.func()
1476
1618
  const funcName = decl.callee.property.name;
1477
1619
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1478
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1620
+ const regex = decl.callee.object.regex.pattern;
1621
+ const rhemynName = `regex_${funcName}_${regex}`;
1622
+
1623
+ if (!funcIndex[rhemynName]) {
1624
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1479
1626
 
1480
- funcIndex[func.name] = func.index;
1481
- funcs.push(func);
1627
+ funcIndex[func.name] = func.index;
1628
+ funcs.push(func);
1629
+ }
1482
1630
 
1631
+ const idx = funcIndex[rhemynName];
1483
1632
  return [
1484
1633
  // make string arg
1485
1634
  ...generate(scope, decl.arguments[0]),
1635
+ Opcodes.i32_to_u,
1636
+ ...getNodeType(scope, decl.arguments[0]),
1486
1637
 
1487
1638
  // call regex func
1488
- Opcodes.i32_to_u,
1489
- [ Opcodes.call, func.index ],
1639
+ [ Opcodes.call, idx ],
1490
1640
  Opcodes.i32_from_u,
1491
1641
 
1492
- ...number(TYPES.boolean, Valtype.i32),
1493
- setLastType(scope)
1642
+ ...setLastType(scope, TYPES.boolean)
1494
1643
  ];
1495
1644
  }
1496
1645
 
@@ -1515,12 +1664,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1664
  // }
1516
1665
 
1517
1666
  if (protoName) {
1667
+ const protoBC = {};
1668
+
1669
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1670
+
1671
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1672
+ for (const x of builtinProtoCands) {
1673
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1674
+ if (type == null) continue;
1675
+
1676
+ protoBC[type] = generateCall(scope, {
1677
+ callee: {
1678
+ type: 'Identifier',
1679
+ name: x
1680
+ },
1681
+ arguments: [ target, ...decl.arguments ],
1682
+ _protoInternalCall: true
1683
+ });
1684
+ }
1685
+ }
1686
+
1518
1687
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1519
1688
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1520
1689
  return acc;
1521
1690
  }, {});
1522
1691
 
1523
- // no prototype function candidates, ignore
1524
1692
  if (Object.keys(protoCands).length > 0) {
1525
1693
  // use local for cached i32 length as commonly used
1526
1694
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1538,21 +1706,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1538
1706
 
1539
1707
  let allOptUnused = true;
1540
1708
  let lengthI32CacheUsed = false;
1541
- const protoBC = {};
1542
1709
  for (const x in protoCands) {
1543
1710
  const protoFunc = protoCands[x];
1544
1711
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1545
1712
  protoBC[x] = [
1546
1713
  ...RTArrayUtil.getLength(getPointer),
1547
-
1548
- ...number(TYPES.number, Valtype.i32),
1549
- setLastType(scope)
1714
+ ...setLastType(scope, TYPES.number)
1550
1715
  ];
1551
1716
  continue;
1552
1717
  }
1553
1718
 
1554
- // const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
1555
- // const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1556
1719
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1557
1720
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1558
1721
 
@@ -1567,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1567
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1568
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1569
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1570
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1571
1734
  return makeArray(scope, {
1572
1735
  rawElements: new Array(length)
1573
1736
  }, _global, _name, true, itemType);
@@ -1581,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1581
1744
  protoBC[x] = [
1582
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1583
1746
  ...protoOut,
1584
-
1585
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1586
- setLastType(scope),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1587
1748
  [ Opcodes.end ]
1588
1749
  ];
1589
1750
  }
@@ -1609,10 +1770,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1770
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1610
1771
  ];
1611
1772
  }
1773
+
1774
+ if (Object.keys(protoBC).length > 0) {
1775
+ return typeSwitch(scope, getNodeType(scope, target), {
1776
+ ...protoBC,
1777
+
1778
+ // TODO: error better
1779
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1780
+ }, valtypeBinary);
1781
+ }
1612
1782
  }
1613
1783
 
1614
1784
  // TODO: only allows callee as literal
1615
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1785
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1616
1786
 
1617
1787
  let idx = funcIndex[name] ?? importedFuncs[name];
1618
1788
  if (idx === undefined && builtinFuncs[name]) {
@@ -1620,65 +1790,235 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1620
1790
 
1621
1791
  includeBuiltin(scope, name);
1622
1792
  idx = funcIndex[name];
1793
+ }
1623
1794
 
1624
- // infer arguments types from builtins params
1625
- const func = funcs.find(x => x.name === name);
1626
- for (let i = 0; i < decl.arguments.length; i++) {
1627
- const arg = decl.arguments[i];
1628
- if (!arg.name) continue;
1795
+ if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1629
1796
 
1630
- const local = scope.locals[arg.name];
1631
- if (!local) continue;
1797
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1798
+ const wasmOps = {
1799
+ // pointer, align, offset
1800
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1801
+ // pointer, value, align, offset
1802
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1803
+ // pointer, align, offset
1804
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1805
+ // pointer, value, align, offset
1806
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1807
+ // pointer, align, offset
1808
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1809
+ // pointer, value, align, offset
1810
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1811
+
1812
+ // pointer, align, offset
1813
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1814
+ // pointer, value, align, offset
1815
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1816
+
1817
+ // value
1818
+ i32_const: { imms: 1, args: [], returns: 1 },
1819
+ };
1632
1820
 
1633
- local.type = func.params[i];
1634
- if (local.type === Valtype.v128) {
1635
- // specify vec subtype inferred from last vec type in function name
1636
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1637
- }
1638
- }
1639
- }
1821
+ const opName = name.slice('__Porffor_wasm_'.length);
1640
1822
 
1641
- if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1823
+ if (wasmOps[opName]) {
1824
+ const op = wasmOps[opName];
1642
1825
 
1643
- if (idx === undefined && name === scope.name) {
1644
- // hack: calling self, func generator will fix later
1645
- idx = -1;
1826
+ const argOut = [];
1827
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1828
+ ...generate(scope, decl.arguments[i]),
1829
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1830
+ );
1831
+
1832
+ // literals only
1833
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1834
+
1835
+ return [
1836
+ ...argOut,
1837
+ [ Opcodes[opName], ...imms ],
1838
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1839
+ ];
1840
+ }
1646
1841
  }
1647
1842
 
1648
1843
  if (idx === undefined) {
1649
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1650
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1651
- }
1844
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1845
+ const [ local, global ] = lookupName(scope, name);
1846
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1652
1847
 
1653
- const func = funcs.find(x => x.index === idx);
1848
+ // todo: only works when function uses typedParams and typedReturns
1654
1849
 
1655
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1658
- const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1659
1857
 
1660
- let args = decl.arguments;
1661
- if (func && args.length < paramCount) {
1662
- // too little args, push undefineds
1663
- args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1664
- }
1858
+ funcs.table = true;
1859
+ scope.table = true;
1665
1860
 
1666
- if (func && args.length > paramCount) {
1667
- // too many args, slice extras off
1668
- args = args.slice(0, paramCount);
1669
- }
1861
+ let args = decl.arguments;
1862
+ let out = [];
1670
1863
 
1671
- if (func && func.throws) scope.throws = true;
1864
+ let locals = [];
1672
1865
 
1673
- let out = [];
1674
- for (const arg of args) {
1675
- out = out.concat(generate(scope, arg));
1676
- if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
- }
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1678
1868
 
1679
- out.push([ Opcodes.call, idx ]);
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1873
+
1874
+ for (let i = 0; i < args.length; i++) {
1875
+ const arg = args[i];
1876
+ out = out.concat(generate(scope, arg));
1877
+
1878
+ if (valtypeBinary !== Valtype.i32 && (
1879
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1880
+ (importedFuncs[name] && name.startsWith('profile'))
1881
+ )) {
1882
+ out.push(Opcodes.i32_to);
1883
+ }
1884
+
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1897
+ }
1898
+ }
1899
+
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1911
+ }
1912
+
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1950
+ }
1951
+
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1954
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1955
+ [TYPES.function]: [
1956
+ ...out,
1957
+
1958
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1959
+ Opcodes.i32_to_u,
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1970
+ ],
1971
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1972
+ });
1973
+ }
1974
+
1975
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1976
+ }
1977
+
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
1980
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1982
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1983
+
1984
+ let args = decl.arguments;
1985
+ if (func && args.length < paramCount) {
1986
+ // too little args, push undefineds
1987
+ args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
1988
+ }
1989
+
1990
+ if (func && args.length > paramCount) {
1991
+ // too many args, slice extras off
1992
+ args = args.slice(0, paramCount);
1993
+ }
1994
+
1995
+ if (func && func.throws) scope.throws = true;
1996
+
1997
+ let out = [];
1998
+ for (let i = 0; i < args.length; i++) {
1999
+ const arg = args[i];
2000
+ out = out.concat(generate(scope, arg));
2001
+
2002
+ // todo: this should be used instead of the too many args thing above (by removing that)
2003
+ if (i >= paramCount) {
2004
+ // over param count of func, drop arg
2005
+ out.push([ Opcodes.drop ]);
2006
+ continue;
2007
+ }
2008
+
2009
+ if (valtypeBinary !== Valtype.i32 && (
2010
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
2011
+ (importedFuncs[name] && name.startsWith('profile'))
2012
+ )) {
2013
+ out.push(Opcodes.i32_to);
2014
+ }
1680
2015
 
1681
- if (!typedReturn) {
2016
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
2017
+ }
2018
+
2019
+ out.push([ Opcodes.call, idx ]);
2020
+
2021
+ if (!typedReturns) {
1682
2022
  // let type;
1683
2023
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
2024
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1688,7 +2028,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1688
2028
  // ...number(type, Valtype.i32),
1689
2029
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1690
2030
  // );
1691
- } else out.push(setLastType(scope));
2031
+ } else out.push(...setLastType(scope));
2032
+
2033
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
2034
+ out.push(Opcodes.i32_from);
2035
+ }
1692
2036
 
1693
2037
  return out;
1694
2038
  };
@@ -1696,8 +2040,26 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1696
2040
  const generateNew = (scope, decl, _global, _name) => {
1697
2041
  // hack: basically treat this as a normal call for builtins for now
1698
2042
  const name = mapName(decl.callee.name);
2043
+
1699
2044
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1700
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
2045
+
2046
+ if (builtinFuncs[name + '$constructor']) {
2047
+ // custom ...$constructor override builtin func
2048
+ return generateCall(scope, {
2049
+ ...decl,
2050
+ callee: {
2051
+ type: 'Identifier',
2052
+ name: name + '$constructor'
2053
+ }
2054
+ }, _global, _name);
2055
+ }
2056
+
2057
+ if (
2058
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2059
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2060
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2061
+
2062
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1701
2063
 
1702
2064
  return generateCall(scope, decl, _global, _name);
1703
2065
  };
@@ -1722,8 +2084,11 @@ const knownType = (scope, type) => {
1722
2084
  const idx = type[0][1];
1723
2085
 
1724
2086
  // type idx = var idx + 1
1725
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1726
- if (v.metadata?.type != null) return v.metadata.type;
2087
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2088
+ if (name) {
2089
+ const local = scope.locals[name];
2090
+ if (local.metadata?.type != null) return v.metadata.type;
2091
+ }
1727
2092
  }
1728
2093
 
1729
2094
  return null;
@@ -1758,16 +2123,17 @@ const brTable = (input, bc, returns) => {
1758
2123
  }
1759
2124
 
1760
2125
  for (let i = 0; i < count; i++) {
1761
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
1762
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
1763
2129
  }
1764
2130
 
1765
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
1766
2132
  const offset = Math.min(...nums);
1767
2133
  const max = Math.max(...nums);
1768
2134
 
1769
2135
  const table = [];
1770
- let br = 1;
2136
+ let br = 0;
1771
2137
 
1772
2138
  for (let i = offset; i <= max; i++) {
1773
2139
  // if branch for this num, go to that block
@@ -1807,21 +2173,20 @@ const brTable = (input, bc, returns) => {
1807
2173
  br--;
1808
2174
  }
1809
2175
 
1810
- return [
1811
- ...out,
1812
- [ Opcodes.end, 'br table end' ]
1813
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
1814
2179
  };
1815
2180
 
1816
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2182
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
1818
2183
 
1819
2184
  const known = knownType(scope, type);
1820
2185
  if (known != null) {
1821
2186
  return bc[known] ?? bc.default;
1822
2187
  }
1823
2188
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
2189
+ if (Prefs.typeswitchUseBrtable)
1825
2190
  return brTable(type, bc, returns);
1826
2191
 
1827
2192
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1831,8 +2196,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
2196
  [ Opcodes.block, returns ]
1832
2197
  ];
1833
2198
 
1834
- // todo: use br_table?
1835
-
1836
2199
  for (const x in bc) {
1837
2200
  if (x === 'default') continue;
1838
2201
 
@@ -1856,7 +2219,18 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
2219
  return out;
1857
2220
  };
1858
2221
 
1859
- const allocVar = (scope, name, global = false) => {
2222
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2223
+ const out = [];
2224
+
2225
+ for (let i = 0; i < types.length; i++) {
2226
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2227
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2228
+ }
2229
+
2230
+ return out;
2231
+ };
2232
+
2233
+ const allocVar = (scope, name, global = false, type = true) => {
1860
2234
  const target = global ? globals : scope.locals;
1861
2235
 
1862
2236
  // already declared
@@ -1870,8 +2244,10 @@ const allocVar = (scope, name, global = false) => {
1870
2244
  let idx = global ? globalInd++ : scope.localInd++;
1871
2245
  target[name] = { idx, type: valtypeBinary };
1872
2246
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2247
+ if (type) {
2248
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2249
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2250
+ }
1875
2251
 
1876
2252
  return idx;
1877
2253
  };
@@ -1886,11 +2262,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1886
2262
  };
1887
2263
 
1888
2264
  const typeAnnoToPorfType = x => {
1889
- if (TYPES[x]) return TYPES[x];
1890
- if (TYPES['_' + x]) return TYPES['_' + x];
2265
+ if (!x) return null;
2266
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
1891
2267
 
1892
2268
  switch (x) {
1893
2269
  case 'i32':
2270
+ case 'i64':
2271
+ case 'f64':
1894
2272
  return TYPES.number;
1895
2273
  }
1896
2274
 
@@ -1901,7 +2279,7 @@ const extractTypeAnnotation = decl => {
1901
2279
  let a = decl;
1902
2280
  while (a.typeAnnotation) a = a.typeAnnotation;
1903
2281
 
1904
- let type, elementType;
2282
+ let type = null, elementType = null;
1905
2283
  if (a.typeName) {
1906
2284
  type = a.typeName.name;
1907
2285
  } else if (a.type.endsWith('Keyword')) {
@@ -1914,7 +2292,7 @@ const extractTypeAnnotation = decl => {
1914
2292
  const typeName = type;
1915
2293
  type = typeAnnoToPorfType(type);
1916
2294
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2295
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2296
 
1919
2297
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2298
 
@@ -1926,13 +2304,13 @@ const generateVar = (scope, decl) => {
1926
2304
 
1927
2305
  const topLevel = scope.name === 'main';
1928
2306
 
1929
- // global variable if in top scope (main) and var ..., or if wanted
1930
- const global = topLevel || decl._bare; // decl.kind === 'var';
2307
+ // global variable if in top scope (main) or if internally wanted
2308
+ const global = topLevel || decl._bare;
1931
2309
 
1932
2310
  for (const x of decl.declarations) {
1933
2311
  const name = mapName(x.id.name);
1934
2312
 
1935
- if (!name) return todo('destructuring is not supported yet');
2313
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1936
2314
 
1937
2315
  if (x.init && isFuncType(x.init.type)) {
1938
2316
  // hack for let a = function () { ... }
@@ -1941,7 +2319,6 @@ const generateVar = (scope, decl) => {
1941
2319
  continue;
1942
2320
  }
1943
2321
 
1944
- // console.log(name);
1945
2322
  if (topLevel && builtinVars[name]) {
1946
2323
  // cannot redeclare
1947
2324
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -1949,16 +2326,46 @@ const generateVar = (scope, decl) => {
1949
2326
  continue; // always ignore
1950
2327
  }
1951
2328
 
1952
- let idx = allocVar(scope, name, global);
2329
+ // // generate init before allocating var
2330
+ // let generated;
2331
+ // if (x.init) generated = generate(scope, x.init, global, name);
1953
2332
 
1954
- if (typedInput && x.id.typeAnnotation) {
2333
+ const typed = typedInput && x.id.typeAnnotation;
2334
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2335
+
2336
+ if (typed) {
1955
2337
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
2338
  }
1957
2339
 
1958
2340
  if (x.init) {
1959
- out = out.concat(generate(scope, x.init, global, name));
2341
+ // if (isFuncType(x.init.type)) {
2342
+ // // let a = function () { ... }
2343
+ // x.init.id = { name };
2344
+
2345
+ // const func = generateFunc(scope, x.init);
2346
+
2347
+ // out.push(
2348
+ // ...number(func.index - importedFuncs.length),
2349
+ // [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
2350
+
2351
+ // ...setType(scope, name, TYPES.function)
2352
+ // );
2353
+
2354
+ // continue;
2355
+ // }
2356
+
2357
+ const generated = generate(scope, x.init, global, name);
2358
+ if (scope.arrays?.get(name) != null) {
2359
+ // hack to set local as pointer before
2360
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2361
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2362
+ generated.pop();
2363
+ out = out.concat(generated);
2364
+ } else {
2365
+ out = out.concat(generated);
2366
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2367
+ }
1960
2368
 
1961
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
1962
2369
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1963
2370
  }
1964
2371
 
@@ -1969,8 +2376,10 @@ const generateVar = (scope, decl) => {
1969
2376
  return out;
1970
2377
  };
1971
2378
 
1972
- const generateAssign = (scope, decl) => {
2379
+ // todo: optimize this func for valueUnused
2380
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1973
2381
  const { type, name } = decl.left;
2382
+ const [ local, isGlobal ] = lookupName(scope, name);
1974
2383
 
1975
2384
  if (type === 'ObjectPattern') {
1976
2385
  // hack: ignore object parts of `var a = {} = 2`
@@ -1980,26 +2389,44 @@ const generateAssign = (scope, decl) => {
1980
2389
  if (isFuncType(decl.right.type)) {
1981
2390
  // hack for a = function () { ... }
1982
2391
  decl.right.id = { name };
1983
- generateFunc(scope, decl.right);
1984
- return [];
2392
+
2393
+ const func = generateFunc(scope, decl.right);
2394
+
2395
+ return [
2396
+ ...number(func.index - importedFuncs.length),
2397
+ ...(local != null ? [
2398
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2399
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2400
+
2401
+ ...setType(scope, name, TYPES.function)
2402
+ ] : [])
2403
+ ];
1985
2404
  }
1986
2405
 
2406
+ const op = decl.operator.slice(0, -1) || '=';
2407
+
1987
2408
  // hack: .length setter
1988
2409
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1989
2410
  const name = decl.left.object.name;
1990
- const pointer = arrays.get(name);
2411
+ const pointer = scope.arrays?.get(name);
1991
2412
 
1992
- const aotPointer = pointer != null;
2413
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1993
2414
 
1994
2415
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2416
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1995
2417
 
1996
2418
  return [
1997
2419
  ...(aotPointer ? number(0, Valtype.i32) : [
1998
2420
  ...generate(scope, decl.left.object),
1999
2421
  Opcodes.i32_to_u
2000
2422
  ]),
2423
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2001
2424
 
2002
- ...generate(scope, decl.right),
2425
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2426
+ [ Opcodes.local_get, pointerTmp ],
2427
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2428
+ Opcodes.i32_from_u
2429
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2003
2430
  [ Opcodes.local_tee, newValueTmp ],
2004
2431
 
2005
2432
  Opcodes.i32_to_u,
@@ -2009,21 +2436,19 @@ const generateAssign = (scope, decl) => {
2009
2436
  ];
2010
2437
  }
2011
2438
 
2012
- const op = decl.operator.slice(0, -1) || '=';
2013
-
2014
2439
  // arr[i]
2015
2440
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2016
2441
  const name = decl.left.object.name;
2017
- const pointer = arrays.get(name);
2442
+ const pointer = scope.arrays?.get(name);
2018
2443
 
2019
- const aotPointer = pointer != null;
2444
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2020
2445
 
2021
2446
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2022
2447
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
2023
2448
 
2024
2449
  return [
2025
2450
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
2026
- [TYPES._array]: [
2451
+ [TYPES.array]: [
2027
2452
  ...(aotPointer ? [] : [
2028
2453
  ...generate(scope, decl.left.object),
2029
2454
  Opcodes.i32_to_u
@@ -2034,18 +2459,21 @@ const generateAssign = (scope, decl) => {
2034
2459
  Opcodes.i32_to_u,
2035
2460
 
2036
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2037
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2038
2463
  [ Opcodes.i32_mul ],
2039
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2040
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2041
2466
 
2042
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2043
2468
  [ Opcodes.local_get, pointerTmp ],
2044
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2045
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2046
2474
  [ Opcodes.local_tee, newValueTmp ],
2047
2475
 
2048
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2049
2477
  ],
2050
2478
 
2051
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2072,9 +2500,7 @@ const generateAssign = (scope, decl) => {
2072
2500
  ];
2073
2501
  }
2074
2502
 
2075
- if (!name) return todo('destructuring is not supported yet');
2076
-
2077
- const [ local, isGlobal ] = lookupName(scope, name);
2503
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2078
2504
 
2079
2505
  if (local === undefined) {
2080
2506
  // todo: this should be a sloppy mode only thing
@@ -2120,9 +2546,7 @@ const generateAssign = (scope, decl) => {
2120
2546
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2547
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2548
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2549
+ ...setType(scope, name, getLastType(scope))
2126
2550
  ];
2127
2551
  }
2128
2552
 
@@ -2133,9 +2557,7 @@ const generateAssign = (scope, decl) => {
2133
2557
 
2134
2558
  // todo: string concat types
2135
2559
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2560
+ ...setType(scope, name, TYPES.number)
2139
2561
  ];
2140
2562
  };
2141
2563
 
@@ -2159,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2159
2581
  ];
2160
2582
 
2161
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === "UnaryExpression" && arg.operator === "!") {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2162
2589
  // !=
2163
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2164
2591
 
@@ -2181,7 +2608,7 @@ const generateUnary = (scope, decl) => {
2181
2608
  return out;
2182
2609
  }
2183
2610
 
2184
- case 'delete':
2611
+ case 'delete': {
2185
2612
  let toReturn = true, toGenerate = true;
2186
2613
 
2187
2614
  if (decl.argument.type === 'Identifier') {
@@ -2203,40 +2630,61 @@ const generateUnary = (scope, decl) => {
2203
2630
 
2204
2631
  out.push(...number(toReturn ? 1 : 0));
2205
2632
  return out;
2633
+ }
2634
+
2635
+ case 'typeof': {
2636
+ let overrideType, toGenerate = true;
2206
2637
 
2207
- case 'typeof':
2208
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2638
+ if (decl.argument.type === 'Identifier') {
2639
+ const out = generateIdent(scope, decl.argument);
2640
+
2641
+ // if ReferenceError (undeclared var), ignore and return undefined
2642
+ if (out[1]) {
2643
+ // does not exist (2 ops from throw)
2644
+ overrideType = number(TYPES.undefined, Valtype.i32);
2645
+ toGenerate = false;
2646
+ }
2647
+ }
2648
+
2649
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2650
+ disposeLeftover(out);
2651
+
2652
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2209
2653
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2210
2654
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2211
2655
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2212
2656
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2213
2657
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2658
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2214
2659
 
2215
- [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2660
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2216
2661
 
2217
2662
  // object and internal types
2218
2663
  default: makeString(scope, 'object', false, '#typeof_result'),
2219
- });
2664
+ }));
2665
+
2666
+ return out;
2667
+ }
2220
2668
 
2221
2669
  default:
2222
- return todo(`unary operator ${decl.operator} not implemented yet`);
2670
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2223
2671
  }
2224
2672
  };
2225
2673
 
2226
- const generateUpdate = (scope, decl) => {
2674
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2227
2675
  const { name } = decl.argument;
2228
2676
 
2229
2677
  const [ local, isGlobal ] = lookupName(scope, name);
2230
2678
 
2231
2679
  if (local === undefined) {
2232
- return todo(`update expression with undefined variable`);
2680
+ return todo(scope, `update expression with undefined variable`, true);
2233
2681
  }
2234
2682
 
2235
2683
  const idx = local.idx;
2236
2684
  const out = [];
2237
2685
 
2238
2686
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2239
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2687
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2240
2688
 
2241
2689
  switch (decl.operator) {
2242
2690
  case '++':
@@ -2249,7 +2697,7 @@ const generateUpdate = (scope, decl) => {
2249
2697
  }
2250
2698
 
2251
2699
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2252
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2700
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2253
2701
 
2254
2702
  return out;
2255
2703
  };
@@ -2284,21 +2732,16 @@ const generateConditional = (scope, decl) => {
2284
2732
  out.push([ Opcodes.if, valtypeBinary ]);
2285
2733
  depth.push('if');
2286
2734
 
2287
- out.push(...generate(scope, decl.consequent));
2288
-
2289
- // note type
2290
2735
  out.push(
2291
- ...getNodeType(scope, decl.consequent),
2292
- setLastType(scope)
2736
+ ...generate(scope, decl.consequent),
2737
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2293
2738
  );
2294
2739
 
2295
2740
  out.push([ Opcodes.else ]);
2296
- out.push(...generate(scope, decl.alternate));
2297
2741
 
2298
- // note type
2299
2742
  out.push(
2300
- ...getNodeType(scope, decl.alternate),
2301
- setLastType(scope)
2743
+ ...generate(scope, decl.alternate),
2744
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2302
2745
  );
2303
2746
 
2304
2747
  out.push([ Opcodes.end ]);
@@ -2312,7 +2755,7 @@ const generateFor = (scope, decl) => {
2312
2755
  const out = [];
2313
2756
 
2314
2757
  if (decl.init) {
2315
- out.push(...generate(scope, decl.init));
2758
+ out.push(...generate(scope, decl.init, false, undefined, true));
2316
2759
  disposeLeftover(out);
2317
2760
  }
2318
2761
 
@@ -2330,7 +2773,7 @@ const generateFor = (scope, decl) => {
2330
2773
  out.push(...generate(scope, decl.body));
2331
2774
  out.push([ Opcodes.end ]);
2332
2775
 
2333
- if (decl.update) out.push(...generate(scope, decl.update));
2776
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2334
2777
 
2335
2778
  out.push([ Opcodes.br, 1 ]);
2336
2779
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2358,6 +2801,36 @@ const generateWhile = (scope, decl) => {
2358
2801
  return out;
2359
2802
  };
2360
2803
 
2804
+ const generateDoWhile = (scope, decl) => {
2805
+ const out = [];
2806
+
2807
+ out.push([ Opcodes.loop, Blocktype.void ]);
2808
+ depth.push('dowhile');
2809
+
2810
+ // block for break (includes all)
2811
+ out.push([ Opcodes.block, Blocktype.void ]);
2812
+ depth.push('block');
2813
+
2814
+ // block for continue
2815
+ // includes body but not test+loop so we can exit body at anytime
2816
+ // and still test+loop after
2817
+ out.push([ Opcodes.block, Blocktype.void ]);
2818
+ depth.push('block');
2819
+
2820
+ out.push(...generate(scope, decl.body));
2821
+
2822
+ out.push([ Opcodes.end ]);
2823
+ depth.pop();
2824
+
2825
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2826
+ out.push([ Opcodes.br_if, 1 ]);
2827
+
2828
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2829
+ depth.pop(); depth.pop();
2830
+
2831
+ return out;
2832
+ };
2833
+
2361
2834
  const generateForOf = (scope, decl) => {
2362
2835
  const out = [];
2363
2836
 
@@ -2394,7 +2867,10 @@ const generateForOf = (scope, decl) => {
2394
2867
  generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2395
2868
  }
2396
2869
 
2870
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2871
+
2397
2872
  const [ local, isGlobal ] = lookupName(scope, leftName);
2873
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2398
2874
 
2399
2875
  depth.push('block');
2400
2876
  depth.push('block');
@@ -2403,6 +2879,7 @@ const generateForOf = (scope, decl) => {
2403
2879
  // hack: this is naughty and will break things!
2404
2880
  let newOut = number(0, Valtype.f64), newPointer = -1;
2405
2881
  if (pages.hasAnyString) {
2882
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2406
2883
  0, [ newOut, newPointer ] = makeArray(scope, {
2407
2884
  rawElements: new Array(1)
2408
2885
  }, isGlobal, leftName, true, 'i16');
@@ -2411,13 +2888,16 @@ const generateForOf = (scope, decl) => {
2411
2888
  // set type for local
2412
2889
  // todo: optimize away counter and use end pointer
2413
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2414
- [TYPES._array]: [
2415
- ...setType(scope, leftName, TYPES.number),
2416
-
2891
+ [TYPES.array]: [
2417
2892
  [ Opcodes.loop, Blocktype.void ],
2418
2893
 
2419
2894
  [ Opcodes.local_get, pointer ],
2420
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2421
2901
 
2422
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2423
2903
 
@@ -2426,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2426
2906
  ...generate(scope, decl.body),
2427
2907
  [ Opcodes.end ],
2428
2908
 
2429
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2430
2910
  [ Opcodes.local_get, pointer ],
2431
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2432
2912
  [ Opcodes.i32_add ],
2433
2913
  [ Opcodes.local_set, pointer ],
2434
2914
 
@@ -2494,6 +2974,94 @@ const generateForOf = (scope, decl) => {
2494
2974
  [ Opcodes.end ],
2495
2975
  [ Opcodes.end ]
2496
2976
  ],
2977
+ [TYPES.bytestring]: [
2978
+ ...setType(scope, leftName, TYPES.bytestring),
2979
+
2980
+ [ Opcodes.loop, Blocktype.void ],
2981
+
2982
+ // setup new/out array
2983
+ ...newOut,
2984
+ [ Opcodes.drop ],
2985
+
2986
+ ...number(0, Valtype.i32), // base 0 for store after
2987
+
2988
+ // load current string ind {arg}
2989
+ [ Opcodes.local_get, pointer ],
2990
+ [ Opcodes.local_get, counter ],
2991
+ [ Opcodes.i32_add ],
2992
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2993
+
2994
+ // store to new string ind 0
2995
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2996
+
2997
+ // return new string (page)
2998
+ ...number(newPointer),
2999
+
3000
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3001
+
3002
+ [ Opcodes.block, Blocktype.void ],
3003
+ [ Opcodes.block, Blocktype.void ],
3004
+ ...generate(scope, decl.body),
3005
+ [ Opcodes.end ],
3006
+
3007
+ // increment iter pointer
3008
+ // [ Opcodes.local_get, pointer ],
3009
+ // ...number(1, Valtype.i32),
3010
+ // [ Opcodes.i32_add ],
3011
+ // [ Opcodes.local_set, pointer ],
3012
+
3013
+ // increment counter by 1
3014
+ [ Opcodes.local_get, counter ],
3015
+ ...number(1, Valtype.i32),
3016
+ [ Opcodes.i32_add ],
3017
+ [ Opcodes.local_tee, counter ],
3018
+
3019
+ // loop if counter != length
3020
+ [ Opcodes.local_get, length ],
3021
+ [ Opcodes.i32_ne ],
3022
+ [ Opcodes.br_if, 1 ],
3023
+
3024
+ [ Opcodes.end ],
3025
+ [ Opcodes.end ]
3026
+ ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
2497
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2498
3066
  }, Blocktype.void));
2499
3067
 
@@ -2504,28 +3072,65 @@ const generateForOf = (scope, decl) => {
2504
3072
  return out;
2505
3073
  };
2506
3074
 
3075
+ // find the nearest loop in depth map by type
2507
3076
  const getNearestLoop = () => {
2508
3077
  for (let i = depth.length - 1; i >= 0; i--) {
2509
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
3078
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2510
3079
  }
2511
3080
 
2512
3081
  return -1;
2513
3082
  };
2514
3083
 
2515
3084
  const generateBreak = (scope, decl) => {
2516
- const nearestLoop = depth.length - getNearestLoop();
3085
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
3086
+ const type = depth[target];
3087
+
3088
+ // different loop types have different branch offsets
3089
+ // as they have different wasm block/loop/if structures
3090
+ // we need to use the right offset by type to branch to the one we want
3091
+ // for a break: exit the loop without executing anything else inside it
3092
+ const offset = ({
3093
+ for: 2, // loop > if (wanted branch) > block (we are here)
3094
+ while: 2, // loop > if (wanted branch) (we are here)
3095
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
3096
+ forof: 2, // loop > block (wanted branch) > block (we are here)
3097
+ if: 1 // break inside if, branch 0 to skip the rest of the if
3098
+ })[type];
3099
+
2517
3100
  return [
2518
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
3101
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2519
3102
  ];
2520
3103
  };
2521
3104
 
2522
3105
  const generateContinue = (scope, decl) => {
2523
- const nearestLoop = depth.length - getNearestLoop();
3106
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
3107
+ const type = depth[target];
3108
+
3109
+ // different loop types have different branch offsets
3110
+ // as they have different wasm block/loop/if structures
3111
+ // we need to use the right offset by type to branch to the one we want
3112
+ // for a continue: do test for the loop, and then loop depending on that success
3113
+ const offset = ({
3114
+ for: 3, // loop (wanted branch) > if > block (we are here)
3115
+ while: 1, // loop (wanted branch) > if (we are here)
3116
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
3117
+ forof: 3 // loop > block > block (wanted branch) (we are here)
3118
+ })[type];
3119
+
2524
3120
  return [
2525
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
3121
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2526
3122
  ];
2527
3123
  };
2528
3124
 
3125
+ const generateLabel = (scope, decl) => {
3126
+ scope.labels ??= new Map();
3127
+
3128
+ const name = decl.label.name;
3129
+ scope.labels.set(name, depth.length);
3130
+
3131
+ return generate(scope, decl.body);
3132
+ };
3133
+
2529
3134
  const generateThrow = (scope, decl) => {
2530
3135
  scope.throws = true;
2531
3136
 
@@ -2546,6 +3151,9 @@ const generateThrow = (scope, decl) => {
2546
3151
  let exceptId = exceptions.push({ constructor, message }) - 1;
2547
3152
  let tagIdx = tags[0].idx;
2548
3153
 
3154
+ scope.exceptions ??= [];
3155
+ scope.exceptions.push(exceptId);
3156
+
2549
3157
  // todo: write a description of how this works lol
2550
3158
 
2551
3159
  return [
@@ -2555,14 +3163,18 @@ const generateThrow = (scope, decl) => {
2555
3163
  };
2556
3164
 
2557
3165
  const generateTry = (scope, decl) => {
2558
- if (decl.finalizer) return todo('try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
2559
3168
 
2560
3169
  const out = [];
2561
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
2562
3173
  out.push([ Opcodes.try, Blocktype.void ]);
2563
3174
  depth.push('try');
2564
3175
 
2565
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
2566
3178
 
2567
3179
  if (decl.handler) {
2568
3180
  depth.pop();
@@ -2570,6 +3182,7 @@ const generateTry = (scope, decl) => {
2570
3182
 
2571
3183
  out.push([ Opcodes.catch_all ]);
2572
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
2573
3186
  }
2574
3187
 
2575
3188
  out.push([ Opcodes.end ]);
@@ -2582,15 +3195,8 @@ const generateEmpty = (scope, decl) => {
2582
3195
  return [];
2583
3196
  };
2584
3197
 
2585
- const generateAssignPat = (scope, decl) => {
2586
- // TODO
2587
- // if identifier declared, use that
2588
- // else, use default (right)
2589
- return todo('assignment pattern (optional arg)');
2590
- };
2591
-
2592
3198
  let pages = new Map();
2593
- const allocPage = (reason, type) => {
3199
+ const allocPage = (scope, reason, type) => {
2594
3200
  if (pages.has(reason)) return pages.get(reason).ind;
2595
3201
 
2596
3202
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2601,16 +3207,20 @@ const allocPage = (reason, type) => {
2601
3207
  const ind = pages.size;
2602
3208
  pages.set(reason, { ind, type });
2603
3209
 
2604
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3210
+ scope.pages ??= new Map();
3211
+ scope.pages.set(reason, { ind, type });
3212
+
3213
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2605
3214
 
2606
3215
  return ind;
2607
3216
  };
2608
3217
 
3218
+ // todo: add scope.pages
2609
3219
  const freePage = reason => {
2610
3220
  const { ind } = pages.get(reason);
2611
3221
  pages.delete(reason);
2612
3222
 
2613
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3223
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2614
3224
 
2615
3225
  return ind;
2616
3226
  };
@@ -2658,19 +3268,30 @@ const getAllocType = itemType => {
2658
3268
  }
2659
3269
  };
2660
3270
 
2661
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
2662
3272
  const out = [];
2663
3273
 
3274
+ scope.arrays ??= new Map();
3275
+
2664
3276
  let firstAssign = false;
2665
- if (!arrays.has(name) || name === '$undeclared') {
3277
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2666
3278
  firstAssign = true;
2667
3279
 
2668
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2669
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2670
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3282
+
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
2671
3290
  }
2672
3291
 
2673
- const pointer = arrays.get(name);
3292
+ const pointer = scope.arrays.get(name);
3293
+
3294
+ const local = global ? globals[name] : scope.locals[name];
2674
3295
 
2675
3296
  const useRawElements = !!decl.rawElements;
2676
3297
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2678,19 +3299,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2678
3299
  const valtype = itemTypeToValtype[itemType];
2679
3300
  const length = elements.length;
2680
3301
 
2681
- if (firstAssign && useRawElements) {
2682
- let bytes = compileBytes(length, 'i32');
3302
+ if (firstAssign && useRawElements && !Prefs.noData) {
3303
+ // if length is 0 memory/data will just be 0000... anyway
3304
+ if (length !== 0) {
3305
+ let bytes = compileBytes(length, 'i32');
2683
3306
 
2684
- if (!initEmpty) for (let i = 0; i < length; i++) {
2685
- if (elements[i] == null) continue;
3307
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3308
+ if (elements[i] == null) continue;
2686
3309
 
2687
- bytes.push(...compileBytes(elements[i], itemType));
2688
- }
3310
+ bytes.push(...compileBytes(elements[i], itemType));
3311
+ }
2689
3312
 
2690
- data.push({
2691
- offset: pointer,
2692
- bytes
2693
- });
3313
+ const ind = data.push({
3314
+ offset: pointer,
3315
+ bytes
3316
+ }) - 1;
3317
+
3318
+ scope.data ??= [];
3319
+ scope.data.push(ind);
3320
+ }
2694
3321
 
2695
3322
  // local value as pointer
2696
3323
  out.push(...number(pointer));
@@ -2698,33 +3325,109 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2698
3325
  return [ out, pointer ];
2699
3326
  }
2700
3327
 
2701
- // store length as 0th array
3328
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3329
+ if (pointerTmp != null) {
3330
+ out.push(
3331
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3332
+ Opcodes.i32_to_u,
3333
+ [ Opcodes.local_set, pointerTmp ]
3334
+ );
3335
+ }
3336
+
3337
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3338
+
3339
+ // store length
2702
3340
  out.push(
2703
- ...number(0, Valtype.i32),
3341
+ ...pointerWasm,
2704
3342
  ...number(length, Valtype.i32),
2705
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3343
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2706
3344
  );
2707
3345
 
2708
3346
  const storeOp = StoreOps[itemType];
2709
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
2710
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
2711
3349
  if (elements[i] == null) continue;
2712
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
2713
3352
  out.push(
2714
- ...number(0, Valtype.i32),
3353
+ ...pointerWasm,
2715
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2716
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
2717
3361
  );
2718
3362
  }
2719
3363
 
2720
3364
  // local value as pointer
2721
- out.push(...number(pointer));
3365
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2722
3366
 
2723
3367
  return [ out, pointer ];
2724
3368
  };
2725
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
2726
3429
  const byteStringable = str => {
2727
- if (!process.argv.includes('-bytestring')) return false;
3430
+ if (!Prefs.bytestring) return false;
2728
3431
 
2729
3432
  for (let i = 0; i < str.length; i++) {
2730
3433
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2735,7 +3438,7 @@ const byteStringable = str => {
2735
3438
 
2736
3439
  const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2737
3440
  const rawElements = new Array(str.length);
2738
- let byteStringable = process.argv.includes('-bytestring');
3441
+ let byteStringable = Prefs.bytestring;
2739
3442
  for (let i = 0; i < str.length; i++) {
2740
3443
  const c = str.charCodeAt(i);
2741
3444
  rawElements[i] = c;
@@ -2750,31 +3453,152 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
2750
3453
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2751
3454
  };
2752
3455
 
2753
- let arrays = new Map();
2754
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2755
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
2756
3458
  };
2757
3459
 
2758
- export const generateMember = (scope, decl, _global, _name) => {
3460
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3461
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3462
+
3463
+ return [
3464
+ ...number(1),
3465
+ ...setLastType(scope, TYPES.object)
3466
+ ];
3467
+ };
3468
+
3469
+ const withType = (scope, wasm, type) => [
3470
+ ...wasm,
3471
+ ...setLastType(scope, type)
3472
+ ];
3473
+
3474
+ const generateMember = (scope, decl, _global, _name) => {
2759
3475
  const name = decl.object.name;
2760
- const pointer = arrays.get(name);
2761
3476
 
2762
- const aotPointer = pointer != null;
3477
+ // hack: process.argv[n]
3478
+ if (name === '__process_argv') {
3479
+ const setPointer = scope.arrays?.get(_name);
3480
+
3481
+ return [
3482
+ ...number(decl.property.value - 1),
3483
+ ...(setPointer ? number(setPointer) : makeArray(scope, { elements: [] }, undefined, undefined, true, 'i8')[0]),
3484
+ [ Opcodes.call, importedFuncs.__Porffor_readArgv ]
3485
+ ];
3486
+ }
3487
+
3488
+ const pointer = scope.arrays?.get(name);
3489
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3490
+
3491
+ // hack: .name
3492
+ if (decl.property.name === 'name') {
3493
+ if (hasFuncWithName(name)) {
3494
+ let nameProp = name;
3495
+
3496
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3497
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3498
+
3499
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3500
+ } else {
3501
+ return withType(scope, number(0), TYPES.undefined);
3502
+ }
3503
+ }
2763
3504
 
2764
3505
  // hack: .length
2765
3506
  if (decl.property.name === 'length') {
2766
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3507
+ const func = funcs.find(x => x.name === name);
3508
+ if (func) {
3509
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3510
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3511
+ }
3512
+
3513
+ if (builtinFuncs[name + '$constructor']) {
3514
+ const regularFunc = builtinFuncs[name];
3515
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3516
+
3517
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3518
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3519
+
3520
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3521
+ }
3522
+
3523
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3524
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3525
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3526
+
3527
+ if (Prefs.fastLength) {
3528
+ // presume valid length object
3529
+ return [
3530
+ ...(aotPointer ? number(0, Valtype.i32) : [
3531
+ ...generate(scope, decl.object),
3532
+ Opcodes.i32_to_u
3533
+ ]),
3534
+
3535
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3536
+ Opcodes.i32_from_u
3537
+ ];
3538
+ }
3539
+
3540
+ const type = getNodeType(scope, decl.object);
3541
+ const known = knownType(scope, type);
3542
+ if (known != null) {
3543
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3544
+ ...(aotPointer ? number(0, Valtype.i32) : [
3545
+ ...generate(scope, decl.object),
3546
+ Opcodes.i32_to_u
3547
+ ]),
3548
+
3549
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3550
+ Opcodes.i32_from_u
3551
+ ];
3552
+
3553
+ return number(0);
3554
+ }
3555
+
2767
3556
  return [
2768
- ...(aotPointer ? number(0, Valtype.i32) : [
2769
- ...generate(scope, decl.object),
2770
- Opcodes.i32_to_u
2771
- ]),
3557
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3558
+ [ Opcodes.if, valtypeBinary ],
3559
+ ...(aotPointer ? number(0, Valtype.i32) : [
3560
+ ...generate(scope, decl.object),
3561
+ Opcodes.i32_to_u
3562
+ ]),
2772
3563
 
2773
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
2774
- Opcodes.i32_from_u
3564
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3565
+ Opcodes.i32_from_u,
3566
+
3567
+ ...setLastType(scope, TYPES.number),
3568
+ [ Opcodes.else ],
3569
+ ...number(0),
3570
+ ...setLastType(scope, TYPES.undefined),
3571
+ [ Opcodes.end ]
2775
3572
  ];
2776
3573
  }
2777
3574
 
3575
+ // todo: generate this array procedurally during builtinFuncs creation
3576
+ if (['size', 'description'].includes(decl.property.name)) {
3577
+ const bc = {};
3578
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3579
+
3580
+ if (cands.length > 0) {
3581
+ for (const x of cands) {
3582
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3583
+ if (type == null) continue;
3584
+
3585
+ bc[type] = generateCall(scope, {
3586
+ callee: {
3587
+ type: 'Identifier',
3588
+ name: x
3589
+ },
3590
+ arguments: [ decl.object ],
3591
+ _protoInternalCall: true
3592
+ });
3593
+ }
3594
+ }
3595
+
3596
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3597
+ ...bc,
3598
+ default: withType(scope, number(0), TYPES.undefined)
3599
+ }, valtypeBinary);
3600
+ }
3601
+
2778
3602
  const object = generate(scope, decl.object);
2779
3603
  const property = generate(scope, decl.property);
2780
3604
 
@@ -2788,26 +3612,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2788
3612
  }
2789
3613
 
2790
3614
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2791
- [TYPES._array]: [
2792
- // get index as valtype
2793
- ...property,
2794
-
2795
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2796
- Opcodes.i32_to_u,
2797
- ...number(ValtypeSize[valtype], Valtype.i32),
2798
- [ Opcodes.i32_mul ],
2799
-
2800
- ...(aotPointer ? [] : [
2801
- ...object,
2802
- Opcodes.i32_to_u,
2803
- [ Opcodes.i32_add ]
2804
- ]),
2805
-
2806
- // read from memory
2807
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2808
-
2809
- ...number(TYPES.number, Valtype.i32),
2810
- setLastType(scope)
3615
+ [TYPES.array]: [
3616
+ ...loadArray(scope, object, property, aotPointer),
3617
+ ...setLastType(scope)
2811
3618
  ],
2812
3619
 
2813
3620
  [TYPES.string]: [
@@ -2837,11 +3644,9 @@ export const generateMember = (scope, decl, _global, _name) => {
2837
3644
 
2838
3645
  // return new string (page)
2839
3646
  ...number(newPointer),
2840
-
2841
- ...number(TYPES.string, Valtype.i32),
2842
- setLastType(scope)
3647
+ ...setLastType(scope, TYPES.string)
2843
3648
  ],
2844
- [TYPES._bytestring]: [
3649
+ [TYPES.bytestring]: [
2845
3650
  // setup new/out array
2846
3651
  ...newOut,
2847
3652
  [ Opcodes.drop ],
@@ -2858,19 +3663,17 @@ export const generateMember = (scope, decl, _global, _name) => {
2858
3663
  ]),
2859
3664
 
2860
3665
  // load current string ind {arg}
2861
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3666
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2862
3667
 
2863
3668
  // store to new string ind 0
2864
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3669
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2865
3670
 
2866
3671
  // return new string (page)
2867
3672
  ...number(newPointer),
2868
-
2869
- ...number(TYPES._bytestring, Valtype.i32),
2870
- setLastType(scope)
3673
+ ...setLastType(scope, TYPES.bytestring)
2871
3674
  ],
2872
3675
 
2873
- default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
3676
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2874
3677
  });
2875
3678
  };
2876
3679
 
@@ -2880,28 +3683,36 @@ const objectHack = node => {
2880
3683
  if (!node) return node;
2881
3684
 
2882
3685
  if (node.type === 'MemberExpression') {
2883
- if (node.computed || node.optional) return node;
3686
+ const out = (() => {
3687
+ if (node.computed || node.optional) return;
2884
3688
 
2885
- let objectName = node.object.name;
3689
+ let objectName = node.object.name;
2886
3690
 
2887
- // if object is not identifier or another member exp, give up
2888
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3691
+ // if object is not identifier or another member exp, give up
3692
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3693
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2889
3694
 
2890
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3695
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2891
3696
 
2892
- // if .length, give up (hack within a hack!)
2893
- if (node.property.name === 'length') return node;
3697
+ // if .name or .length, give up (hack within a hack!)
3698
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3699
+ node.object = objectHack(node.object);
3700
+ return;
3701
+ }
2894
3702
 
2895
- // no object name, give up
2896
- if (!objectName) return node;
3703
+ // no object name, give up
3704
+ if (!objectName) return;
2897
3705
 
2898
- const name = '__' + objectName + '_' + node.property.name;
2899
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3706
+ const name = '__' + objectName + '_' + node.property.name;
3707
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2900
3708
 
2901
- return {
2902
- type: 'Identifier',
2903
- name
2904
- };
3709
+ return {
3710
+ type: 'Identifier',
3711
+ name
3712
+ };
3713
+ })();
3714
+
3715
+ if (out) return out;
2905
3716
  }
2906
3717
 
2907
3718
  for (const x in node) {
@@ -2915,31 +3726,45 @@ const objectHack = node => {
2915
3726
  };
2916
3727
 
2917
3728
  const generateFunc = (scope, decl) => {
2918
- if (decl.async) return todo('async functions are not supported');
2919
- if (decl.generator) return todo('generator functions are not supported');
3729
+ if (decl.async) return todo(scope, 'async functions are not supported');
3730
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2920
3731
 
2921
3732
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2922
3733
  const params = decl.params ?? [];
2923
3734
 
2924
- // const innerScope = { ...scope };
2925
3735
  // TODO: share scope/locals between !!!
2926
- const innerScope = {
3736
+ const func = {
2927
3737
  locals: {},
2928
3738
  localInd: 0,
2929
3739
  // value, type
2930
3740
  returns: [ valtypeBinary, Valtype.i32 ],
2931
3741
  throws: false,
2932
- name
3742
+ name,
3743
+ index: currentFuncIndex++
2933
3744
  };
2934
3745
 
3746
+ if (typedInput && decl.returnType) {
3747
+ const { type } = extractTypeAnnotation(decl.returnType);
3748
+ // if (type != null && !Prefs.indirectCalls) {
3749
+ if (type != null) {
3750
+ func.returnType = type;
3751
+ func.returns = [ valtypeBinary ];
3752
+ }
3753
+ }
3754
+
2935
3755
  for (let i = 0; i < params.length; i++) {
2936
- allocVar(innerScope, params[i].name, false);
3756
+ const name = params[i].name;
3757
+ // if (name == null) return todo('non-identifier args are not supported');
3758
+
3759
+ allocVar(func, name, false);
2937
3760
 
2938
3761
  if (typedInput && params[i].typeAnnotation) {
2939
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3762
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
2940
3763
  }
2941
3764
  }
2942
3765
 
3766
+ func.params = Object.values(func.locals).map(x => x.type);
3767
+
2943
3768
  let body = objectHack(decl.body);
2944
3769
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
2945
3770
  // hack: () => 0 -> () => return 0
@@ -2949,35 +3774,23 @@ const generateFunc = (scope, decl) => {
2949
3774
  };
2950
3775
  }
2951
3776
 
2952
- const wasm = generate(innerScope, body);
2953
- const func = {
2954
- name,
2955
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2956
- index: currentFuncIndex++,
2957
- ...innerScope
2958
- };
2959
3777
  funcIndex[name] = func.index;
3778
+ funcs.push(func);
2960
3779
 
2961
- // quick hack fixes
2962
- for (const inst of wasm) {
2963
- if (inst[0] === Opcodes.call && inst[1] === -1) {
2964
- inst[1] = func.index;
2965
- }
2966
- }
3780
+ const wasm = generate(func, body);
3781
+ func.wasm = wasm;
3782
+
3783
+ if (name === 'main') func.gotLastType = true;
2967
3784
 
2968
3785
  // add end return if not found
2969
3786
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2970
3787
  wasm.push(
2971
3788
  ...number(0),
2972
- ...number(TYPES.undefined, Valtype.i32),
3789
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
2973
3790
  [ Opcodes.return ]
2974
3791
  );
2975
3792
  }
2976
3793
 
2977
- func.wasm = wasm;
2978
-
2979
- funcs.push(func);
2980
-
2981
3794
  return func;
2982
3795
  };
2983
3796
 
@@ -2992,16 +3805,6 @@ const generateCode = (scope, decl) => {
2992
3805
  };
2993
3806
 
2994
3807
  const internalConstrs = {
2995
- Boolean: {
2996
- generate: (scope, decl) => {
2997
- if (decl.arguments.length === 0) return number(0);
2998
-
2999
- // should generate/run all args
3000
- return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
3001
- },
3002
- type: TYPES.boolean
3003
- },
3004
-
3005
3808
  Array: {
3006
3809
  generate: (scope, decl, global, name) => {
3007
3810
  // new Array(i0, i1, ...)
@@ -3019,7 +3822,7 @@ const internalConstrs = {
3019
3822
 
3020
3823
  // todo: check in wasm instead of here
3021
3824
  const literalValue = arg.value ?? 0;
3022
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3825
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3023
3826
 
3024
3827
  return [
3025
3828
  ...number(0, Valtype.i32),
@@ -3030,7 +3833,8 @@ const internalConstrs = {
3030
3833
  ...number(pointer)
3031
3834
  ];
3032
3835
  },
3033
- type: TYPES._array
3836
+ type: TYPES.array,
3837
+ length: 1
3034
3838
  },
3035
3839
 
3036
3840
  __Array_of: {
@@ -3041,27 +3845,138 @@ const internalConstrs = {
3041
3845
  elements: decl.arguments
3042
3846
  }, global, name);
3043
3847
  },
3044
- type: TYPES._array,
3848
+ type: TYPES.array,
3849
+ notConstr: true,
3850
+ length: 0
3851
+ },
3852
+
3853
+ __Porffor_fastOr: {
3854
+ generate: (scope, decl) => {
3855
+ const out = [];
3856
+
3857
+ for (let i = 0; i < decl.arguments.length; i++) {
3858
+ out.push(
3859
+ ...generate(scope, decl.arguments[i]),
3860
+ Opcodes.i32_to_u,
3861
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3862
+ );
3863
+ }
3864
+
3865
+ out.push(Opcodes.i32_from_u);
3866
+
3867
+ return out;
3868
+ },
3869
+ type: TYPES.boolean,
3045
3870
  notConstr: true
3046
- }
3047
- };
3871
+ },
3872
+
3873
+ __Porffor_fastAnd: {
3874
+ generate: (scope, decl) => {
3875
+ const out = [];
3876
+
3877
+ for (let i = 0; i < decl.arguments.length; i++) {
3878
+ out.push(
3879
+ ...generate(scope, decl.arguments[i]),
3880
+ Opcodes.i32_to_u,
3881
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3882
+ );
3883
+ }
3884
+
3885
+ out.push(Opcodes.i32_from_u);
3886
+
3887
+ return out;
3888
+ },
3889
+ type: TYPES.boolean,
3890
+ notConstr: true
3891
+ },
3892
+
3893
+ Boolean: {
3894
+ generate: (scope, decl) => {
3895
+ // todo: boolean object when used as constructor
3896
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3897
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3898
+ },
3899
+ type: TYPES.boolean,
3900
+ length: 1
3901
+ },
3902
+
3903
+ __Math_max: {
3904
+ generate: (scope, decl) => {
3905
+ const out = [
3906
+ ...number(-Infinity)
3907
+ ];
3048
3908
 
3049
- // const _ = Array.prototype.push;
3050
- // Array.prototype.push = function (a) {
3051
- // const check = arr => {
3052
- // for (const x of arr) {
3053
- // if (x === undefined) {
3054
- // console.trace(arr);
3055
- // process.exit();
3056
- // }
3057
- // if (Array.isArray(x)) check(x);
3058
- // }
3059
- // };
3060
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
3061
- // // if (Array.isArray(a)) check(a);
3909
+ for (let i = 0; i < decl.arguments.length; i++) {
3910
+ out.push(
3911
+ ...generate(scope, decl.arguments[i]),
3912
+ [ Opcodes.f64_max ]
3913
+ );
3914
+ }
3062
3915
 
3063
- // return _.apply(this, arguments);
3064
- // };
3916
+ return out;
3917
+ },
3918
+ type: TYPES.number,
3919
+ notConstr: true,
3920
+ length: 2
3921
+ },
3922
+
3923
+ __Math_min: {
3924
+ generate: (scope, decl) => {
3925
+ const out = [
3926
+ ...number(Infinity)
3927
+ ];
3928
+
3929
+ for (let i = 0; i < decl.arguments.length; i++) {
3930
+ out.push(
3931
+ ...generate(scope, decl.arguments[i]),
3932
+ [ Opcodes.f64_min ]
3933
+ );
3934
+ }
3935
+
3936
+ return out;
3937
+ },
3938
+ type: TYPES.number,
3939
+ notConstr: true,
3940
+ length: 2
3941
+ },
3942
+
3943
+ __console_log: {
3944
+ generate: (scope, decl) => {
3945
+ const out = [];
3946
+
3947
+ for (let i = 0; i < decl.arguments.length; i++) {
3948
+ out.push(
3949
+ ...generateCall(scope, {
3950
+ callee: {
3951
+ type: 'Identifier',
3952
+ name: '__Porffor_print'
3953
+ },
3954
+ arguments: [ decl.arguments[i] ]
3955
+ }),
3956
+
3957
+ // print space
3958
+ ...(i !== decl.arguments.length - 1 ? [
3959
+ ...number(32),
3960
+ [ Opcodes.call, importedFuncs.printChar ]
3961
+ ] : [])
3962
+ );
3963
+ }
3964
+
3965
+ // print newline
3966
+ out.push(
3967
+ ...number(10),
3968
+ [ Opcodes.call, importedFuncs.printChar ]
3969
+ );
3970
+
3971
+ out.push(...number(UNDEFINED));
3972
+
3973
+ return out;
3974
+ },
3975
+ type: TYPES.undefined,
3976
+ notConstr: true,
3977
+ length: 0
3978
+ }
3979
+ };
3065
3980
 
3066
3981
  export default program => {
3067
3982
  globals = {};
@@ -3071,20 +3986,23 @@ export default program => {
3071
3986
  funcs = [];
3072
3987
  funcIndex = {};
3073
3988
  depth = [];
3074
- arrays = new Map();
3075
3989
  pages = new Map();
3076
3990
  data = [];
3077
3991
  currentFuncIndex = importedFuncs.length;
3078
3992
 
3079
3993
  globalThis.valtype = 'f64';
3080
3994
 
3081
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3995
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3082
3996
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3083
3997
 
3084
3998
  globalThis.valtypeBinary = Valtype[valtype];
3085
3999
 
3086
4000
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3087
4001
 
4002
+ globalThis.pageSize = PageSize;
4003
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
4004
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
4005
+
3088
4006
  // set generic opcodes for current valtype
3089
4007
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3090
4008
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3093,10 +4011,10 @@ export default program => {
3093
4011
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3094
4012
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3095
4013
 
3096
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3097
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3098
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3099
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
4014
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
4015
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
4016
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
4017
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3100
4018
 
3101
4019
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3102
4020
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3109,10 +4027,6 @@ export default program => {
3109
4027
 
3110
4028
  program.id = { name: 'main' };
3111
4029
 
3112
- globalThis.pageSize = PageSize;
3113
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3114
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3115
-
3116
4030
  const scope = {
3117
4031
  locals: {},
3118
4032
  localInd: 0
@@ -3123,11 +4037,10 @@ export default program => {
3123
4037
  body: program.body
3124
4038
  };
3125
4039
 
3126
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
4040
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3127
4041
 
3128
- generateFunc(scope, program);
4042
+ const main = generateFunc(scope, program);
3129
4043
 
3130
- const main = funcs[funcs.length - 1];
3131
4044
  main.export = true;
3132
4045
  main.returns = [ valtypeBinary, Valtype.i32 ];
3133
4046
 
@@ -3140,7 +4053,11 @@ export default program => {
3140
4053
  }
3141
4054
 
3142
4055
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3143
- main.returns = [];
4056
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
4057
+ main.wasm.splice(main.wasm.length - 1, 1);
4058
+ } else {
4059
+ main.returns = [];
4060
+ }
3144
4061
  }
3145
4062
 
3146
4063
  if (lastInst[0] === Opcodes.call) {
@@ -3150,7 +4067,7 @@ export default program => {
3150
4067
  }
3151
4068
 
3152
4069
  // if blank main func and other exports, remove it
3153
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4070
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3154
4071
 
3155
4072
  return { funcs, globals, tags, exceptions, pages, data };
3156
4073
  };