porffor 0.2.0-31c2539 → 0.2.0-3272f21

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 (52) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +143 -82
  3. package/asur/README.md +2 -0
  4. package/asur/index.js +1262 -0
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +317 -72
  7. package/compiler/{sections.js → assemble.js} +63 -15
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +151 -0
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/date.ts +1370 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +42 -0
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +601 -272
  21. package/compiler/{codeGen.js → codegen.js} +1184 -372
  22. package/compiler/decompile.js +3 -3
  23. package/compiler/embedding.js +22 -22
  24. package/compiler/encoding.js +108 -10
  25. package/compiler/generated_builtins.js +1262 -0
  26. package/compiler/index.js +36 -34
  27. package/compiler/log.js +6 -3
  28. package/compiler/opt.js +55 -29
  29. package/compiler/parse.js +35 -27
  30. package/compiler/precompile.js +123 -0
  31. package/compiler/prefs.js +26 -0
  32. package/compiler/prototype.js +177 -37
  33. package/compiler/types.js +37 -0
  34. package/compiler/wasmSpec.js +31 -7
  35. package/compiler/wrap.js +141 -43
  36. package/package.json +9 -5
  37. package/porf +4 -0
  38. package/rhemyn/compile.js +5 -3
  39. package/rhemyn/parse.js +323 -320
  40. package/rhemyn/test/parse.js +58 -58
  41. package/runner/compare.js +34 -34
  42. package/runner/debug.js +122 -0
  43. package/runner/index.js +55 -10
  44. package/runner/profiler.js +102 -0
  45. package/runner/repl.js +40 -7
  46. package/runner/sizes.js +37 -37
  47. package/compiler/builtins/base64.js +0 -92
  48. package/runner/info.js +0 -89
  49. package/runner/profile.js +0 -46
  50. package/runner/results.json +0 -1
  51. package/runner/transform.js +0 -15
  52. package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,39 +25,41 @@ const debug = str => {
23
25
  const logChar = n => {
24
26
  code.push(...number(n));
25
27
 
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
28
29
  };
29
30
 
30
31
  for (let i = 0; i < str.length; i++) {
31
32
  logChar(str.charCodeAt(i));
32
33
  }
33
34
 
34
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
35
36
 
36
37
  return code;
37
38
  };
38
39
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
45
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
46
50
 
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
53
53
 
54
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
55
59
  };
56
60
 
57
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
62
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
63
  switch (decl.type) {
60
64
  case 'BinaryExpression':
61
65
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +72,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
72
 
69
73
  case 'ArrowFunctionExpression':
70
74
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
75
+ const func = generateFunc(scope, decl);
76
+
77
+ if (decl.type.endsWith('Expression')) {
78
+ return number(func.index);
79
+ }
80
+
72
81
  return [];
73
82
 
74
83
  case 'BlockStatement':
@@ -81,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
90
  return generateExp(scope, decl);
82
91
 
83
92
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
93
+ return generateCall(scope, decl, global, name, valueUnused);
85
94
 
86
95
  case 'NewExpression':
87
96
  return generateNew(scope, decl, global, name);
@@ -99,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
108
  return generateUnary(scope, decl);
100
109
 
101
110
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
112
 
104
113
  case 'IfStatement':
105
114
  return generateIf(scope, decl);
@@ -110,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
110
119
  case 'WhileStatement':
111
120
  return generateWhile(scope, decl);
112
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
113
125
  case 'ForOfStatement':
114
126
  return generateForOf(scope, decl);
115
127
 
@@ -119,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
119
131
  case 'ContinueStatement':
120
132
  return generateContinue(scope, decl);
121
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
122
137
  case 'EmptyStatement':
123
138
  return generateEmpty(scope, decl);
124
139
 
@@ -132,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
132
147
  return generateTry(scope, decl);
133
148
 
134
149
  case 'DebuggerStatement':
135
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
136
151
  return [];
137
152
 
138
153
  case 'ArrayExpression':
@@ -146,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
146
161
  const funcsBefore = funcs.length;
147
162
  generate(scope, decl.declaration);
148
163
 
149
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
150
171
 
151
- const newFunc = funcs[funcs.length - 1];
152
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
153
174
 
154
175
  return [];
155
176
 
156
177
  case 'TaggedTemplateExpression': {
157
178
  const funcs = {
158
- asm: str => {
179
+ __Porffor_wasm: str => {
159
180
  let out = [];
160
181
 
161
182
  for (const line of str.split('\n')) {
@@ -163,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
184
  if (asm[0] === '') continue; // blank
164
185
 
165
186
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
189
  continue;
169
190
  }
170
191
 
@@ -174,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
195
  }
175
196
 
176
197
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
178
199
  // todo: add to store/load offset insts
179
200
  continue;
180
201
  }
@@ -183,43 +204,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
183
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
205
 
185
206
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
187
212
 
188
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
189
214
  }
190
215
 
191
216
  return out;
192
217
  },
193
218
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
196
221
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
200
229
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
207
236
 
208
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
209
238
  // hack for inline asm
210
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
211
254
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
255
+ return funcs[func](str);
214
256
  }
215
257
 
216
258
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
262
  return [];
220
263
  }
221
264
 
222
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
223
266
  }
224
267
  };
225
268
 
@@ -247,7 +290,7 @@ const lookupName = (scope, _name) => {
247
290
  return [ undefined, undefined ];
248
291
  };
249
292
 
250
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
251
294
  ...generateThrow(scope, {
252
295
  argument: {
253
296
  type: 'NewExpression',
@@ -269,25 +312,33 @@ const generateIdent = (scope, decl) => {
269
312
  const name = mapName(rawName);
270
313
  let local = scope.locals[rawName];
271
314
 
272
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
273
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
321
+ }
322
+
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
324
+ // todo: return an actual something
325
+ return number(1);
275
326
  }
276
327
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
328
+ if (isExistingProtoFunc(name)) {
278
329
  // todo: return an actual something
279
330
  return number(1);
280
331
  }
281
332
 
282
- if (local === undefined) {
333
+ if (local?.idx === undefined) {
283
334
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
335
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
336
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
337
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
339
  }
289
340
 
290
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
342
  // return undefined if unknown key in already known var
292
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +347,7 @@ const generateIdent = (scope, decl) => {
296
347
  if (!parentLookup[1]) return number(UNDEFINED);
297
348
  }
298
349
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
350
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
351
 
301
352
  return [ [ Opcodes.local_get, local.idx ] ];
302
353
  };
@@ -309,14 +360,18 @@ const generateReturn = (scope, decl) => {
309
360
  // just bare "return"
310
361
  return [
311
362
  ...number(UNDEFINED), // "undefined" if func returns
312
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
313
366
  [ Opcodes.return ]
314
367
  ];
315
368
  }
316
369
 
317
370
  return [
318
371
  ...generate(scope, decl.argument),
319
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
320
375
  [ Opcodes.return ]
321
376
  ];
322
377
  };
@@ -330,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
330
385
  return idx;
331
386
  };
332
387
 
333
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
334
390
 
335
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
336
392
  const checks = {
@@ -339,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
339
395
  '??': nullish
340
396
  };
341
397
 
342
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
343
399
 
344
400
  // generic structure for {a} OP {b}
345
401
  // -->
@@ -347,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
347
403
 
348
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
349
405
  // (like if we are in an if condition - very common)
350
- const leftIsInt = isIntOp(left[left.length - 1]);
351
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
352
408
 
353
409
  const canInt = leftIsInt && rightIsInt;
354
410
 
@@ -365,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
365
421
  ...right,
366
422
  // note type
367
423
  ...rightType,
368
- setLastType(scope),
424
+ ...setLastType(scope),
369
425
  [ Opcodes.else ],
370
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
371
427
  // note type
372
428
  ...leftType,
373
- setLastType(scope),
429
+ ...setLastType(scope),
374
430
  [ Opcodes.end ],
375
431
  Opcodes.i32_from
376
432
  ];
@@ -384,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
440
  ...right,
385
441
  // note type
386
442
  ...rightType,
387
- setLastType(scope),
443
+ ...setLastType(scope),
388
444
  [ Opcodes.else ],
389
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
390
446
  // note type
391
447
  ...leftType,
392
- setLastType(scope),
448
+ ...setLastType(scope),
393
449
  [ Opcodes.end ]
394
450
  ];
395
451
  };
396
452
 
397
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
398
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
399
455
  // todo: convert left and right to strings if not
400
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -404,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
462
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
463
  if (assign) {
411
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
412
465
 
413
466
  return [
414
467
  // setup right
@@ -433,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
433
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
434
487
 
435
488
  // copy right
436
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
437
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
438
491
 
439
492
  [ Opcodes.local_get, leftLength ],
440
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
441
494
  [ Opcodes.i32_mul ],
442
495
  [ Opcodes.i32_add ],
443
496
 
@@ -446,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
446
499
  ...number(ValtypeSize.i32, Valtype.i32),
447
500
  [ Opcodes.i32_add ],
448
501
 
449
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
450
503
  [ Opcodes.local_get, rightLength ],
451
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
452
505
  [ Opcodes.i32_mul ],
453
506
 
454
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -506,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
506
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
507
560
 
508
561
  // copy right
509
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
510
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
511
564
 
512
565
  [ Opcodes.local_get, leftLength ],
513
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
514
567
  [ Opcodes.i32_mul ],
515
568
  [ Opcodes.i32_add ],
516
569
 
@@ -519,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
519
572
  ...number(ValtypeSize.i32, Valtype.i32),
520
573
  [ Opcodes.i32_add ],
521
574
 
522
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
523
576
  [ Opcodes.local_get, rightLength ],
524
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
525
578
  [ Opcodes.i32_mul ],
526
579
 
527
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -531,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
531
584
  ];
532
585
  };
533
586
 
534
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
535
588
  // todo: this should be rewritten into a func
536
589
  // todo: convert left and right to strings if not
537
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -540,7 +593,6 @@ const compareStrings = (scope, left, right) => {
540
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
541
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
542
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
543
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
544
596
 
545
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
546
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -568,7 +620,6 @@ const compareStrings = (scope, left, right) => {
568
620
 
569
621
  [ Opcodes.local_get, rightPointer ],
570
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
571
- [ Opcodes.local_tee, rightLength ],
572
623
 
573
624
  // fast path: check leftLength != rightLength
574
625
  [ Opcodes.i32_ne ],
@@ -583,11 +634,13 @@ const compareStrings = (scope, left, right) => {
583
634
  ...number(0, Valtype.i32),
584
635
  [ Opcodes.local_set, index ],
585
636
 
586
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
587
638
  // we do this instead of having to do mul/div each iter for perf™
588
639
  [ Opcodes.local_get, leftLength ],
589
- ...number(ValtypeSize.i16, Valtype.i32),
590
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
591
644
  [ Opcodes.local_set, indexEnd ],
592
645
 
593
646
  // iterate over each char and check if eq
@@ -597,13 +650,17 @@ const compareStrings = (scope, left, right) => {
597
650
  [ Opcodes.local_get, index ],
598
651
  [ Opcodes.local_get, leftPointer ],
599
652
  [ Opcodes.i32_add ],
600
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
601
656
 
602
657
  // fetch right
603
658
  [ Opcodes.local_get, index ],
604
659
  [ Opcodes.local_get, rightPointer ],
605
660
  [ Opcodes.i32_add ],
606
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
607
664
 
608
665
  // not equal, "return" false
609
666
  [ Opcodes.i32_ne ],
@@ -612,13 +669,13 @@ const compareStrings = (scope, left, right) => {
612
669
  [ Opcodes.br, 2 ],
613
670
  [ Opcodes.end ],
614
671
 
615
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
616
673
  [ Opcodes.local_get, index ],
617
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
618
675
  [ Opcodes.i32_add ],
619
676
  [ Opcodes.local_tee, index ],
620
677
 
621
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
622
679
  [ Opcodes.local_get, indexEnd ],
623
680
  [ Opcodes.i32_ne ],
624
681
  [ Opcodes.br_if, 0 ],
@@ -639,16 +696,18 @@ const compareStrings = (scope, left, right) => {
639
696
  };
640
697
 
641
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
642
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
643
700
  ...wasm,
644
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
646
704
 
647
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
648
707
 
649
708
  const def = [
650
709
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
711
 
653
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
719
 
661
720
  return [
662
721
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
723
 
665
724
  ...typeSwitch(scope, type, {
666
725
  // [TYPES.number]: def,
@@ -669,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
669
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
729
  ],
671
730
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
733
 
675
734
  // get length
@@ -680,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
739
  [ Opcodes.i32_eqz ], */
681
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
741
  ],
742
+ [TYPES._bytestring]: [ // duplicate of string
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
744
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
745
+
746
+ // get length
747
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
748
+
749
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
750
+ ],
683
751
  default: def
684
752
  }, intOut ? Valtype.i32 : valtypeBinary)
685
753
  ];
686
754
  };
687
755
 
688
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
690
760
  return [
691
761
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
763
 
694
764
  ...typeSwitch(scope, type, {
695
765
  [TYPES._array]: [
@@ -697,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
697
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
768
  ],
699
769
  [TYPES.string]: [
700
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
771
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
772
+
773
+ // get length
774
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
775
+
776
+ // if length == 0
777
+ [ Opcodes.i32_eqz ],
778
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
779
+ ],
780
+ [TYPES._bytestring]: [ // duplicate of string
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
701
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
783
 
703
784
  // get length
@@ -709,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
790
  ],
710
791
  default: [
711
792
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
794
 
714
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
800
  };
720
801
 
721
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
723
806
  return [
724
807
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
809
 
727
810
  ...typeSwitch(scope, type, {
728
811
  [TYPES.undefined]: [
@@ -731,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
814
  ],
732
815
  [TYPES.object]: [
733
816
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
818
 
736
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
844
  }
762
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
763
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
850
  const strictOp = op === '===' || op === '!==';
765
851
 
766
852
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
768
854
 
769
855
  // if strict (in)equal check types match
770
856
  if (strictOp) {
@@ -809,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
895
  // todo: if equality op and an operand is undefined, return false
810
896
  // todo: niche null hell with 0
811
897
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
837
951
 
838
952
  let ops = operatorOpcode[valtype][op];
839
953
 
@@ -843,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
957
  includeBuiltin(scope, builtinName);
844
958
  const idx = funcIndex[builtinName];
845
959
 
846
- return finalise([
960
+ return finalize([
847
961
  ...left,
848
962
  ...right,
849
963
  [ Opcodes.call, idx ]
850
964
  ]);
851
965
  }
852
966
 
853
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
854
968
 
855
969
  if (!Array.isArray(ops)) ops = [ ops ];
856
970
  ops = [ ops ];
857
971
 
858
972
  let tmpLeft, tmpRight;
859
973
  // if equal op, check if strings for compareStrings
860
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
- // todo: intelligent partial skip later
865
- // if neither known are string, stop this madness
866
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
867
- return;
868
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
869
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
870
979
  tmpLeft = localTmp(scope, '__tmpop_left');
871
980
  tmpRight = localTmp(scope, '__tmpop_right');
872
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
873
1023
  ops.unshift(...stringOnly([
874
1024
  // if left is string
875
1025
  ...leftType,
@@ -881,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
881
1031
  ...number(TYPES.string, Valtype.i32),
882
1032
  [ Opcodes.i32_eq ],
883
1033
 
884
- // if either are true
885
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
886
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
887
1041
 
888
- // todo: convert non-strings to strings, for now fail immediately if one is not
889
- // if left is not string
1042
+ // if left is bytestring
890
1043
  ...leftType,
891
- ...number(TYPES.string, Valtype.i32),
892
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
893
1046
 
894
- // if right is not string
1047
+ // if right is bytestring
895
1048
  ...rightType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
898
1051
 
899
- // if either are true
900
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
901
1054
  [ Opcodes.if, Blocktype.void ],
902
- ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
904
- [ Opcodes.end ],
905
-
906
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
907
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
908
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
909
1057
  [ Opcodes.br, 1 ],
910
1058
  [ Opcodes.end ],
@@ -916,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
916
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
917
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
918
1066
  // }
919
- })();
1067
+ }
920
1068
 
921
- return finalise([
1069
+ return finalize([
922
1070
  ...left,
923
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
1072
  ...right,
@@ -935,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
1083
  return out;
936
1084
  };
937
1085
 
938
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
939
1102
  const existing = funcs.find(x => x.name === name);
940
1103
  if (existing) return existing;
941
1104
 
@@ -947,6 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1111
  }
949
1112
 
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
1117
+ }
1118
+
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
950
1121
  let baseGlobalIdx, i = 0;
951
1122
  for (const type of globalTypes) {
952
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -969,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
969
1140
  params,
970
1141
  locals,
971
1142
  returns,
972
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
973
1144
  wasm,
974
1145
  internal: true,
975
1146
  index: currentFuncIndex++
@@ -992,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
992
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
993
1164
  };
994
1165
 
1166
+ // potential future ideas for nan boxing (unused):
995
1167
  // T = JS type, V = value/pointer
996
1168
  // 0bTTT
997
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1015,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1015
1187
  // 4: internal type
1016
1188
  // 5: pointer
1017
1189
 
1018
- const TYPES = {
1019
- number: 0x00,
1020
- boolean: 0x01,
1021
- string: 0x02,
1022
- undefined: 0x03,
1023
- object: 0x04,
1024
- function: 0x05,
1025
- symbol: 0x06,
1026
- bigint: 0x07,
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1027
1193
 
1028
- // these are not "typeof" types but tracked internally
1029
- _array: 0x10,
1030
- _regexp: 0x11
1031
- };
1032
-
1033
- const TYPE_NAMES = {
1034
- [TYPES.number]: 'Number',
1035
- [TYPES.boolean]: 'Boolean',
1036
- [TYPES.string]: 'String',
1037
- [TYPES.undefined]: 'undefined',
1038
- [TYPES.object]: 'Object',
1039
- [TYPES.function]: 'Function',
1040
- [TYPES.symbol]: 'Symbol',
1041
- [TYPES.bigint]: 'BigInt',
1042
-
1043
- [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1194
+ return false;
1045
1195
  };
1046
1196
 
1047
1197
  const getType = (scope, _name) => {
1048
1198
  const name = mapName(_name);
1049
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1207
 
1053
1208
  let type = TYPES.undefined;
1054
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1055
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1056
1211
 
1057
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1058
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1059
1213
 
1060
1214
  return number(type, Valtype.i32);
1061
1215
  };
@@ -1078,15 +1232,16 @@ const setType = (scope, _name, type) => {
1078
1232
  ];
1079
1233
 
1080
1234
  // throw new Error('could not find var');
1235
+ return [];
1081
1236
  };
1082
1237
 
1083
1238
  const getLastType = scope => {
1084
1239
  scope.gotLastType = true;
1085
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1086
1241
  };
1087
1242
 
1088
1243
  const setLastType = scope => {
1089
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1090
1245
  };
1091
1246
 
1092
1247
  const getNodeType = (scope, node) => {
@@ -1094,6 +1249,8 @@ const getNodeType = (scope, node) => {
1094
1249
  if (node.type === 'Literal') {
1095
1250
  if (node.regex) return TYPES._regexp;
1096
1251
 
1252
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1253
+
1097
1254
  return TYPES[typeof node.value];
1098
1255
  }
1099
1256
 
@@ -1107,6 +1264,27 @@ const getNodeType = (scope, node) => {
1107
1264
 
1108
1265
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1266
  const name = node.callee.name;
1267
+ if (!name) {
1268
+ // iife
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1270
+
1271
+ // presume
1272
+ // todo: warn here?
1273
+ return TYPES.number;
1274
+ }
1275
+
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1110
1288
  const func = funcs.find(x => x.name === name);
1111
1289
 
1112
1290
  if (func) {
@@ -1114,7 +1292,7 @@ const getNodeType = (scope, node) => {
1114
1292
  if (func.returnType) return func.returnType;
1115
1293
  }
1116
1294
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1118
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1297
 
1120
1298
  // check if this is a prototype function
@@ -1125,11 +1303,16 @@ const getNodeType = (scope, node) => {
1125
1303
  const spl = name.slice(2).split('_');
1126
1304
 
1127
1305
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1306
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1308
  }
1131
1309
 
1132
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1133
1316
 
1134
1317
  // presume
1135
1318
  // todo: warn here?
@@ -1177,6 +1360,15 @@ const getNodeType = (scope, node) => {
1177
1360
 
1178
1361
  if (node.type === 'BinaryExpression') {
1179
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1180
1372
  return TYPES.number;
1181
1373
 
1182
1374
  // todo: string concat types
@@ -1201,7 +1393,7 @@ const getNodeType = (scope, node) => {
1201
1393
  if (node.operator === '!') return TYPES.boolean;
1202
1394
  if (node.operator === 'void') return TYPES.undefined;
1203
1395
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1205
1397
 
1206
1398
  return TYPES.number;
1207
1399
  }
@@ -1210,11 +1402,23 @@ const getNodeType = (scope, node) => {
1210
1402
  // hack: if something.length, number type
1211
1403
  if (node.property.name === 'length') return TYPES.number;
1212
1404
 
1213
- // we cannot guess
1405
+ // ts hack
1406
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1408
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1411
+
1412
+ // presume
1214
1413
  return TYPES.number;
1215
1414
  }
1216
1415
 
1217
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1218
1422
 
1219
1423
  // presume
1220
1424
  // todo: warn here?
@@ -1230,8 +1434,8 @@ const getNodeType = (scope, node) => {
1230
1434
  const generateLiteral = (scope, decl, global, name) => {
1231
1435
  if (decl.value === null) return number(NULL);
1232
1436
 
1437
+ // hack: just return 1 for regex literals
1233
1438
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1439
  return number(1);
1236
1440
  }
1237
1441
 
@@ -1244,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1448
  return number(decl.value ? 1 : 0);
1245
1449
 
1246
1450
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1451
+ return makeString(scope, decl.value, global, name);
1257
1452
 
1258
1453
  default:
1259
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1260
1455
  }
1261
1456
  };
1262
1457
 
@@ -1265,6 +1460,8 @@ const countLeftover = wasm => {
1265
1460
 
1266
1461
  for (let i = 0; i < wasm.length; i++) {
1267
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1268
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1269
1466
  if (inst[0] === Opcodes.if) count--;
1270
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1273,18 +1470,25 @@ const countLeftover = wasm => {
1273
1470
  if (inst[0] === Opcodes.end) depth--;
1274
1471
 
1275
1472
  if (depth === 0)
1276
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1278
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1279
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1474
+ 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)) {}
1475
+ 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++;
1476
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1478
  else if (inst[0] === Opcodes.return) count = 0;
1282
1479
  else if (inst[0] === Opcodes.call) {
1283
1480
  let func = funcs.find(x => x.index === inst[1]);
1284
- if (func) {
1285
- count -= func.params.length;
1286
- } else count--;
1287
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1288
1492
  } else count--;
1289
1493
 
1290
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1302,7 +1506,7 @@ const disposeLeftover = wasm => {
1302
1506
  const generateExp = (scope, decl) => {
1303
1507
  const expression = decl.expression;
1304
1508
 
1305
- const out = generate(scope, expression);
1509
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1510
  disposeLeftover(out);
1307
1511
 
1308
1512
  return out;
@@ -1360,7 +1564,7 @@ const RTArrayUtil = {
1360
1564
  ]
1361
1565
  };
1362
1566
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1567
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1568
  /* const callee = decl.callee;
1365
1569
  const args = decl.arguments;
1366
1570
 
@@ -1376,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
1376
1580
  name = func.name;
1377
1581
  }
1378
1582
 
1379
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1380
1584
  // literal eval hack
1381
- const code = decl.arguments[0].value;
1382
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1383
1598
 
1384
1599
  const out = generate(scope, {
1385
1600
  type: 'BlockStatement',
@@ -1393,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1393
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1394
1609
  out.push(
1395
1610
  ...getNodeType(scope, finalStatement),
1396
- setLastType(scope)
1611
+ ...setLastType(scope)
1397
1612
  );
1398
1613
  } else if (countLeftover(out) === 0) {
1399
1614
  out.push(...number(UNDEFINED));
1400
1615
  out.push(
1401
1616
  ...number(TYPES.undefined, Valtype.i32),
1402
- setLastType(scope)
1617
+ ...setLastType(scope)
1403
1618
  );
1404
1619
  }
1405
1620
 
@@ -1421,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name) => {
1421
1636
 
1422
1637
  target = { ...decl.callee };
1423
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1424
1642
  }
1425
1643
 
1426
1644
  // literal.func()
1427
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1646
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1431
1649
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1432
1650
 
1433
1651
  funcIndex[func.name] = func.index;
@@ -1443,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name) => {
1443
1661
  Opcodes.i32_from_u,
1444
1662
 
1445
1663
  ...number(TYPES.boolean, Valtype.i32),
1446
- setLastType(scope)
1664
+ ...setLastType(scope)
1447
1665
  ];
1448
1666
  }
1449
1667
 
@@ -1468,23 +1686,48 @@ const generateCall = (scope, decl, _global, _name) => {
1468
1686
  // }
1469
1687
 
1470
1688
  if (protoName) {
1689
+ const protoBC = {};
1690
+
1691
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1692
+
1693
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1694
+ for (const x of builtinProtoCands) {
1695
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1696
+ if (type == null) continue;
1697
+
1698
+ protoBC[type] = generateCall(scope, {
1699
+ callee: {
1700
+ type: 'Identifier',
1701
+ name: x
1702
+ },
1703
+ arguments: [ target, ...decl.arguments ],
1704
+ _protoInternalCall: true
1705
+ });
1706
+ }
1707
+ }
1708
+
1471
1709
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1710
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1711
  return acc;
1475
1712
  }, {});
1476
1713
 
1477
- // no prototype function candidates, ignore
1478
1714
  if (Object.keys(protoCands).length > 0) {
1479
1715
  // use local for cached i32 length as commonly used
1480
1716
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1717
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1718
 
1484
1719
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1720
 
1721
+ const rawPointer = [
1722
+ ...generate(scope, target),
1723
+ Opcodes.i32_to_u
1724
+ ];
1725
+
1726
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1727
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1728
+
1729
+ let allOptUnused = true;
1486
1730
  let lengthI32CacheUsed = false;
1487
- const protoBC = {};
1488
1731
  for (const x in protoCands) {
1489
1732
  const protoFunc = protoCands[x];
1490
1733
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1492,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name) => {
1492
1735
  ...RTArrayUtil.getLength(getPointer),
1493
1736
 
1494
1737
  ...number(TYPES.number, Valtype.i32),
1495
- setLastType(scope)
1738
+ ...setLastType(scope)
1496
1739
  ];
1497
1740
  continue;
1498
1741
  }
@@ -1502,6 +1745,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1745
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1746
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1747
 
1748
+ let optUnused = false;
1505
1749
  const protoOut = protoFunc(getPointer, {
1506
1750
  getCachedI32: () => {
1507
1751
  lengthI32CacheUsed = true;
@@ -1516,23 +1760,30 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1760
  return makeArray(scope, {
1517
1761
  rawElements: new Array(length)
1518
1762
  }, _global, _name, true, itemType);
1763
+ }, () => {
1764
+ optUnused = true;
1765
+ return unusedValue;
1519
1766
  });
1520
1767
 
1768
+ if (!optUnused) allOptUnused = false;
1769
+
1521
1770
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1771
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1772
  ...protoOut,
1524
1773
 
1525
1774
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1526
- setLastType(scope),
1775
+ ...setLastType(scope),
1527
1776
  [ Opcodes.end ]
1528
1777
  ];
1529
1778
  }
1530
1779
 
1531
- return [
1532
- ...generate(scope, target),
1780
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1781
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1782
+ return [
1783
+ ...(usePointerCache ? [
1784
+ ...rawPointer,
1785
+ [ Opcodes.local_set, pointerLocal ],
1786
+ ] : []),
1536
1787
 
1537
1788
  ...(!lengthI32CacheUsed ? [] : [
1538
1789
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,13 +1795,22 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1795
 
1545
1796
  // TODO: error better
1546
1797
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1798
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1799
  ];
1549
1800
  }
1801
+
1802
+ if (Object.keys(protoBC).length > 0) {
1803
+ return typeSwitch(scope, getNodeType(scope, target), {
1804
+ ...protoBC,
1805
+
1806
+ // TODO: error better
1807
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1808
+ }, valtypeBinary);
1809
+ }
1550
1810
  }
1551
1811
 
1552
1812
  // TODO: only allows callee as literal
1553
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1813
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1554
1814
 
1555
1815
  let idx = funcIndex[name] ?? importedFuncs[name];
1556
1816
  if (idx === undefined && builtinFuncs[name]) {
@@ -1583,15 +1843,66 @@ const generateCall = (scope, decl, _global, _name) => {
1583
1843
  idx = -1;
1584
1844
  }
1585
1845
 
1846
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1847
+ const wasmOps = {
1848
+ // pointer, align, offset
1849
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1850
+ // pointer, value, align, offset
1851
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1852
+ // pointer, align, offset
1853
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1854
+ // pointer, value, align, offset
1855
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1856
+ // pointer, align, offset
1857
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1858
+ // pointer, value, align, offset
1859
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1860
+
1861
+ // pointer, align, offset
1862
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1863
+ // pointer, value, align, offset
1864
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1865
+
1866
+ // value
1867
+ i32_const: { imms: 1, args: [], returns: 1 },
1868
+
1869
+ // a, b
1870
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1871
+ };
1872
+
1873
+ const opName = name.slice('__Porffor_wasm_'.length);
1874
+
1875
+ if (wasmOps[opName]) {
1876
+ const op = wasmOps[opName];
1877
+
1878
+ const argOut = [];
1879
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1880
+ ...generate(scope, decl.arguments[i]),
1881
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1882
+ );
1883
+
1884
+ // literals only
1885
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1886
+
1887
+ return [
1888
+ ...argOut,
1889
+ [ Opcodes[opName], ...imms ],
1890
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1891
+ ];
1892
+ }
1893
+ }
1894
+
1586
1895
  if (idx === undefined) {
1587
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1588
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1896
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1897
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1589
1898
  }
1590
1899
 
1591
1900
  const func = funcs.find(x => x.index === idx);
1592
1901
 
1593
1902
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1903
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1904
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1905
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1906
 
1596
1907
  let args = decl.arguments;
1597
1908
  if (func && args.length < paramCount) {
@@ -1607,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name) => {
1607
1918
  if (func && func.throws) scope.throws = true;
1608
1919
 
1609
1920
  let out = [];
1610
- for (const arg of args) {
1921
+ for (let i = 0; i < args.length; i++) {
1922
+ const arg = args[i];
1611
1923
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1924
+
1925
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1926
+ out.push(Opcodes.i32_to);
1927
+ }
1928
+
1929
+ if (importedFuncs[name] && name.startsWith('profile')) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1933
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1934
  }
1614
1935
 
1615
1936
  out.push([ Opcodes.call, idx ]);
1616
1937
 
1617
- if (!userFunc) {
1938
+ if (!typedReturns) {
1618
1939
  // let type;
1619
1940
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1941
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1624,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1945
  // ...number(type, Valtype.i32),
1625
1946
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1626
1947
  // );
1627
- } else out.push(setLastType(scope));
1948
+ } else out.push(...setLastType(scope));
1949
+
1950
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1951
+ out.push(Opcodes.i32_from);
1952
+ }
1628
1953
 
1629
1954
  return out;
1630
1955
  };
@@ -1632,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name) => {
1632
1957
  const generateNew = (scope, decl, _global, _name) => {
1633
1958
  // hack: basically treat this as a normal call for builtins for now
1634
1959
  const name = mapName(decl.callee.name);
1960
+
1635
1961
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1636
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1962
+
1963
+ if (builtinFuncs[name + '$constructor']) {
1964
+ // custom ...$constructor override builtin func
1965
+ return generateCall(scope, {
1966
+ ...decl,
1967
+ callee: {
1968
+ type: 'Identifier',
1969
+ name: name + '$constructor'
1970
+ }
1971
+ }, _global, _name);
1972
+ }
1973
+
1974
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1637
1975
 
1638
1976
  return generateCall(scope, decl, _global, _name);
1639
1977
  };
@@ -1665,22 +2003,108 @@ const knownType = (scope, type) => {
1665
2003
  return null;
1666
2004
  };
1667
2005
 
2006
+ const brTable = (input, bc, returns) => {
2007
+ const out = [];
2008
+ const keys = Object.keys(bc);
2009
+ const count = keys.length;
2010
+
2011
+ if (count === 1) {
2012
+ // return [
2013
+ // ...input,
2014
+ // ...bc[keys[0]]
2015
+ // ];
2016
+ return bc[keys[0]];
2017
+ }
2018
+
2019
+ if (count === 2) {
2020
+ // just use if else
2021
+ const other = keys.find(x => x !== 'default');
2022
+ return [
2023
+ ...input,
2024
+ ...number(other, Valtype.i32),
2025
+ [ Opcodes.i32_eq ],
2026
+ [ Opcodes.if, returns ],
2027
+ ...bc[other],
2028
+ [ Opcodes.else ],
2029
+ ...bc.default,
2030
+ [ Opcodes.end ]
2031
+ ];
2032
+ }
2033
+
2034
+ for (let i = 0; i < count; i++) {
2035
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2036
+ else out.push([ Opcodes.block, Blocktype.void ]);
2037
+ }
2038
+
2039
+ const nums = keys.filter(x => +x);
2040
+ const offset = Math.min(...nums);
2041
+ const max = Math.max(...nums);
2042
+
2043
+ const table = [];
2044
+ let br = 1;
2045
+
2046
+ for (let i = offset; i <= max; i++) {
2047
+ // if branch for this num, go to that block
2048
+ if (bc[i]) {
2049
+ table.push(br);
2050
+ br++;
2051
+ continue;
2052
+ }
2053
+
2054
+ // else default
2055
+ table.push(0);
2056
+ }
2057
+
2058
+ out.push(
2059
+ [ Opcodes.block, Blocktype.void ],
2060
+ ...input,
2061
+ ...(offset > 0 ? [
2062
+ ...number(offset, Valtype.i32),
2063
+ [ Opcodes.i32_sub ]
2064
+ ] : []),
2065
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
2066
+ );
2067
+
2068
+ // if you can guess why we sort the wrong way and then reverse
2069
+ // (instead of just sorting the correct way)
2070
+ // dm me and if you are correct and the first person
2071
+ // I will somehow shout you out or something
2072
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
2073
+
2074
+ br = count - 1;
2075
+ for (const x of orderedBc) {
2076
+ out.push(
2077
+ [ Opcodes.end ],
2078
+ ...bc[x],
2079
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
2080
+ );
2081
+ br--;
2082
+ }
2083
+
2084
+ return [
2085
+ ...out,
2086
+ [ Opcodes.end, 'br table end' ]
2087
+ ];
2088
+ };
2089
+
1668
2090
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2091
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2092
+
1669
2093
  const known = knownType(scope, type);
1670
2094
  if (known != null) {
1671
2095
  return bc[known] ?? bc.default;
1672
2096
  }
1673
2097
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2098
+ if (Prefs.typeswitchUseBrtable)
2099
+ return brTable(type, bc, returns);
1675
2100
 
2101
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
1676
2102
  const out = [
1677
2103
  ...type,
1678
2104
  [ Opcodes.local_set, tmp ],
1679
2105
  [ Opcodes.block, returns ]
1680
2106
  ];
1681
2107
 
1682
- // todo: use br_table?
1683
-
1684
2108
  for (const x in bc) {
1685
2109
  if (x === 'default') continue;
1686
2110
 
@@ -1704,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1704
2128
  return out;
1705
2129
  };
1706
2130
 
1707
- const allocVar = (scope, name, global = false) => {
2131
+ const allocVar = (scope, name, global = false, type = true) => {
1708
2132
  const target = global ? globals : scope.locals;
1709
2133
 
1710
2134
  // already declared
@@ -1718,8 +2142,10 @@ const allocVar = (scope, name, global = false) => {
1718
2142
  let idx = global ? globalInd++ : scope.localInd++;
1719
2143
  target[name] = { idx, type: valtypeBinary };
1720
2144
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2145
+ if (type) {
2146
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2147
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2148
+ }
1723
2149
 
1724
2150
  return idx;
1725
2151
  };
@@ -1734,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1734
2160
  };
1735
2161
 
1736
2162
  const typeAnnoToPorfType = x => {
1737
- if (TYPES[x]) return TYPES[x];
1738
- if (TYPES['_' + x]) return TYPES['_' + x];
2163
+ if (!x) return null;
2164
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2165
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1739
2166
 
1740
2167
  switch (x) {
1741
2168
  case 'i32':
2169
+ case 'i64':
2170
+ case 'f64':
1742
2171
  return TYPES.number;
1743
2172
  }
1744
2173
 
@@ -1749,7 +2178,7 @@ const extractTypeAnnotation = decl => {
1749
2178
  let a = decl;
1750
2179
  while (a.typeAnnotation) a = a.typeAnnotation;
1751
2180
 
1752
- let type, elementType;
2181
+ let type = null, elementType = null;
1753
2182
  if (a.typeName) {
1754
2183
  type = a.typeName.name;
1755
2184
  } else if (a.type.endsWith('Keyword')) {
@@ -1762,6 +2191,8 @@ const extractTypeAnnotation = decl => {
1762
2191
  const typeName = type;
1763
2192
  type = typeAnnoToPorfType(type);
1764
2193
 
2194
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2195
+
1765
2196
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
2197
 
1767
2198
  return { type, typeName, elementType };
@@ -1778,6 +2209,8 @@ const generateVar = (scope, decl) => {
1778
2209
  for (const x of decl.declarations) {
1779
2210
  const name = mapName(x.id.name);
1780
2211
 
2212
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2213
+
1781
2214
  if (x.init && isFuncType(x.init.type)) {
1782
2215
  // hack for let a = function () { ... }
1783
2216
  x.init.id = { name };
@@ -1793,9 +2226,10 @@ const generateVar = (scope, decl) => {
1793
2226
  continue; // always ignore
1794
2227
  }
1795
2228
 
1796
- let idx = allocVar(scope, name, global);
2229
+ const typed = typedInput && x.id.typeAnnotation;
2230
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1797
2231
 
1798
- if (typedInput && x.id.typeAnnotation) {
2232
+ if (typed) {
1799
2233
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1800
2234
  }
1801
2235
 
@@ -1813,7 +2247,8 @@ const generateVar = (scope, decl) => {
1813
2247
  return out;
1814
2248
  };
1815
2249
 
1816
- const generateAssign = (scope, decl) => {
2250
+ // todo: optimize this func for valueUnused
2251
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1817
2252
  const { type, name } = decl.left;
1818
2253
 
1819
2254
  if (type === 'ObjectPattern') {
@@ -1831,9 +2266,9 @@ const generateAssign = (scope, decl) => {
1831
2266
  // hack: .length setter
1832
2267
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1833
2268
  const name = decl.left.object.name;
1834
- const pointer = arrays.get(name);
2269
+ const pointer = scope.arrays?.get(name);
1835
2270
 
1836
- const aotPointer = pointer != null;
2271
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1837
2272
 
1838
2273
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1839
2274
 
@@ -1858,9 +2293,9 @@ const generateAssign = (scope, decl) => {
1858
2293
  // arr[i]
1859
2294
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1860
2295
  const name = decl.left.object.name;
1861
- const pointer = arrays.get(name);
2296
+ const pointer = scope.arrays?.get(name);
1862
2297
 
1863
- const aotPointer = pointer != null;
2298
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1864
2299
 
1865
2300
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1866
2301
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -1916,6 +2351,8 @@ const generateAssign = (scope, decl) => {
1916
2351
  ];
1917
2352
  }
1918
2353
 
2354
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2355
+
1919
2356
  const [ local, isGlobal ] = lookupName(scope, name);
1920
2357
 
1921
2358
  if (local === undefined) {
@@ -1962,9 +2399,7 @@ const generateAssign = (scope, decl) => {
1962
2399
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1963
2400
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1964
2401
 
1965
- getLastType(scope),
1966
- // hack: type is idx+1
1967
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2402
+ ...setType(scope, name, getLastType(scope))
1968
2403
  ];
1969
2404
  }
1970
2405
 
@@ -1975,9 +2410,7 @@ const generateAssign = (scope, decl) => {
1975
2410
 
1976
2411
  // todo: string concat types
1977
2412
 
1978
- // hack: type is idx+1
1979
- ...number(TYPES.number, Valtype.i32),
1980
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2413
+ ...setType(scope, name, TYPES.number)
1981
2414
  ];
1982
2415
  };
1983
2416
 
@@ -2023,7 +2456,7 @@ const generateUnary = (scope, decl) => {
2023
2456
  return out;
2024
2457
  }
2025
2458
 
2026
- case 'delete':
2459
+ case 'delete': {
2027
2460
  let toReturn = true, toGenerate = true;
2028
2461
 
2029
2462
  if (decl.argument.type === 'Identifier') {
@@ -2045,38 +2478,60 @@ const generateUnary = (scope, decl) => {
2045
2478
 
2046
2479
  out.push(...number(toReturn ? 1 : 0));
2047
2480
  return out;
2481
+ }
2048
2482
 
2049
- case 'typeof':
2050
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2483
+ case 'typeof': {
2484
+ let overrideType, toGenerate = true;
2485
+
2486
+ if (decl.argument.type === 'Identifier') {
2487
+ const out = generateIdent(scope, decl.argument);
2488
+
2489
+ // if ReferenceError (undeclared var), ignore and return undefined
2490
+ if (out[1]) {
2491
+ // does not exist (2 ops from throw)
2492
+ overrideType = number(TYPES.undefined, Valtype.i32);
2493
+ toGenerate = false;
2494
+ }
2495
+ }
2496
+
2497
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2498
+ disposeLeftover(out);
2499
+
2500
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2051
2501
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2052
2502
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2053
2503
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2054
2504
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2055
2505
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2056
2506
 
2507
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2508
+
2057
2509
  // object and internal types
2058
2510
  default: makeString(scope, 'object', false, '#typeof_result'),
2059
- });
2511
+ }));
2512
+
2513
+ return out;
2514
+ }
2060
2515
 
2061
2516
  default:
2062
- return todo(`unary operator ${decl.operator} not implemented yet`);
2517
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2063
2518
  }
2064
2519
  };
2065
2520
 
2066
- const generateUpdate = (scope, decl) => {
2521
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2067
2522
  const { name } = decl.argument;
2068
2523
 
2069
2524
  const [ local, isGlobal ] = lookupName(scope, name);
2070
2525
 
2071
2526
  if (local === undefined) {
2072
- return todo(`update expression with undefined variable`);
2527
+ return todo(scope, `update expression with undefined variable`, true);
2073
2528
  }
2074
2529
 
2075
2530
  const idx = local.idx;
2076
2531
  const out = [];
2077
2532
 
2078
2533
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2079
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2534
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2080
2535
 
2081
2536
  switch (decl.operator) {
2082
2537
  case '++':
@@ -2089,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
2089
2544
  }
2090
2545
 
2091
2546
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2092
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2547
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2093
2548
 
2094
2549
  return out;
2095
2550
  };
@@ -2129,7 +2584,7 @@ const generateConditional = (scope, decl) => {
2129
2584
  // note type
2130
2585
  out.push(
2131
2586
  ...getNodeType(scope, decl.consequent),
2132
- setLastType(scope)
2587
+ ...setLastType(scope)
2133
2588
  );
2134
2589
 
2135
2590
  out.push([ Opcodes.else ]);
@@ -2138,7 +2593,7 @@ const generateConditional = (scope, decl) => {
2138
2593
  // note type
2139
2594
  out.push(
2140
2595
  ...getNodeType(scope, decl.alternate),
2141
- setLastType(scope)
2596
+ ...setLastType(scope)
2142
2597
  );
2143
2598
 
2144
2599
  out.push([ Opcodes.end ]);
@@ -2152,15 +2607,17 @@ const generateFor = (scope, decl) => {
2152
2607
  const out = [];
2153
2608
 
2154
2609
  if (decl.init) {
2155
- out.push(...generate(scope, decl.init));
2610
+ out.push(...generate(scope, decl.init, false, undefined, true));
2156
2611
  disposeLeftover(out);
2157
2612
  }
2158
2613
 
2159
2614
  out.push([ Opcodes.loop, Blocktype.void ]);
2160
2615
  depth.push('for');
2161
2616
 
2162
- out.push(...generate(scope, decl.test));
2163
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2617
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2618
+ else out.push(...number(1, Valtype.i32));
2619
+
2620
+ out.push([ Opcodes.if, Blocktype.void ]);
2164
2621
  depth.push('if');
2165
2622
 
2166
2623
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2168,8 +2625,7 @@ const generateFor = (scope, decl) => {
2168
2625
  out.push(...generate(scope, decl.body));
2169
2626
  out.push([ Opcodes.end ]);
2170
2627
 
2171
- out.push(...generate(scope, decl.update));
2172
- depth.pop();
2628
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2173
2629
 
2174
2630
  out.push([ Opcodes.br, 1 ]);
2175
2631
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2197,6 +2653,36 @@ const generateWhile = (scope, decl) => {
2197
2653
  return out;
2198
2654
  };
2199
2655
 
2656
+ const generateDoWhile = (scope, decl) => {
2657
+ const out = [];
2658
+
2659
+ out.push([ Opcodes.loop, Blocktype.void ]);
2660
+ depth.push('dowhile');
2661
+
2662
+ // block for break (includes all)
2663
+ out.push([ Opcodes.block, Blocktype.void ]);
2664
+ depth.push('block');
2665
+
2666
+ // block for continue
2667
+ // includes body but not test+loop so we can exit body at anytime
2668
+ // and still test+loop after
2669
+ out.push([ Opcodes.block, Blocktype.void ]);
2670
+ depth.push('block');
2671
+
2672
+ out.push(...generate(scope, decl.body));
2673
+
2674
+ out.push([ Opcodes.end ]);
2675
+ depth.pop();
2676
+
2677
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2678
+ out.push([ Opcodes.br_if, 1 ]);
2679
+
2680
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2681
+ depth.pop(); depth.pop();
2682
+
2683
+ return out;
2684
+ };
2685
+
2200
2686
  const generateForOf = (scope, decl) => {
2201
2687
  const out = [];
2202
2688
 
@@ -2226,8 +2712,17 @@ const generateForOf = (scope, decl) => {
2226
2712
  // setup local for left
2227
2713
  generate(scope, decl.left);
2228
2714
 
2229
- const leftName = decl.left.declarations[0].id.name;
2715
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2716
+ if (!leftName && decl.left.name) {
2717
+ leftName = decl.left.name;
2718
+
2719
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2720
+ }
2721
+
2722
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2723
+
2230
2724
  const [ local, isGlobal ] = lookupName(scope, leftName);
2725
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2231
2726
 
2232
2727
  depth.push('block');
2233
2728
  depth.push('block');
@@ -2235,13 +2730,15 @@ const generateForOf = (scope, decl) => {
2235
2730
  // // todo: we should only do this for strings but we don't know at compile-time :(
2236
2731
  // hack: this is naughty and will break things!
2237
2732
  let newOut = number(0, Valtype.f64), newPointer = -1;
2238
- if (pages.hasString) {
2733
+ if (pages.hasAnyString) {
2734
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2239
2735
  0, [ newOut, newPointer ] = makeArray(scope, {
2240
2736
  rawElements: new Array(1)
2241
2737
  }, isGlobal, leftName, true, 'i16');
2242
2738
  }
2243
2739
 
2244
2740
  // set type for local
2741
+ // todo: optimize away counter and use end pointer
2245
2742
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2246
2743
  [TYPES._array]: [
2247
2744
  ...setType(scope, leftName, TYPES.number),
@@ -2326,6 +2823,56 @@ const generateForOf = (scope, decl) => {
2326
2823
  [ Opcodes.end ],
2327
2824
  [ Opcodes.end ]
2328
2825
  ],
2826
+ [TYPES._bytestring]: [
2827
+ ...setType(scope, leftName, TYPES._bytestring),
2828
+
2829
+ [ Opcodes.loop, Blocktype.void ],
2830
+
2831
+ // setup new/out array
2832
+ ...newOut,
2833
+ [ Opcodes.drop ],
2834
+
2835
+ ...number(0, Valtype.i32), // base 0 for store after
2836
+
2837
+ // load current string ind {arg}
2838
+ [ Opcodes.local_get, pointer ],
2839
+ [ Opcodes.local_get, counter ],
2840
+ [ Opcodes.i32_add ],
2841
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2842
+
2843
+ // store to new string ind 0
2844
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2845
+
2846
+ // return new string (page)
2847
+ ...number(newPointer),
2848
+
2849
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2850
+
2851
+ [ Opcodes.block, Blocktype.void ],
2852
+ [ Opcodes.block, Blocktype.void ],
2853
+ ...generate(scope, decl.body),
2854
+ [ Opcodes.end ],
2855
+
2856
+ // increment iter pointer
2857
+ // [ Opcodes.local_get, pointer ],
2858
+ // ...number(1, Valtype.i32),
2859
+ // [ Opcodes.i32_add ],
2860
+ // [ Opcodes.local_set, pointer ],
2861
+
2862
+ // increment counter by 1
2863
+ [ Opcodes.local_get, counter ],
2864
+ ...number(1, Valtype.i32),
2865
+ [ Opcodes.i32_add ],
2866
+ [ Opcodes.local_tee, counter ],
2867
+
2868
+ // loop if counter != length
2869
+ [ Opcodes.local_get, length ],
2870
+ [ Opcodes.i32_ne ],
2871
+ [ Opcodes.br_if, 1 ],
2872
+
2873
+ [ Opcodes.end ],
2874
+ [ Opcodes.end ]
2875
+ ],
2329
2876
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2330
2877
  }, Blocktype.void));
2331
2878
 
@@ -2336,28 +2883,65 @@ const generateForOf = (scope, decl) => {
2336
2883
  return out;
2337
2884
  };
2338
2885
 
2886
+ // find the nearest loop in depth map by type
2339
2887
  const getNearestLoop = () => {
2340
2888
  for (let i = depth.length - 1; i >= 0; i--) {
2341
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2889
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2342
2890
  }
2343
2891
 
2344
2892
  return -1;
2345
2893
  };
2346
2894
 
2347
2895
  const generateBreak = (scope, decl) => {
2348
- const nearestLoop = depth.length - getNearestLoop();
2896
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2897
+ const type = depth[target];
2898
+
2899
+ // different loop types have different branch offsets
2900
+ // as they have different wasm block/loop/if structures
2901
+ // we need to use the right offset by type to branch to the one we want
2902
+ // for a break: exit the loop without executing anything else inside it
2903
+ const offset = ({
2904
+ for: 2, // loop > if (wanted branch) > block (we are here)
2905
+ while: 2, // loop > if (wanted branch) (we are here)
2906
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2907
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2908
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2909
+ })[type];
2910
+
2349
2911
  return [
2350
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2912
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2351
2913
  ];
2352
2914
  };
2353
2915
 
2354
2916
  const generateContinue = (scope, decl) => {
2355
- const nearestLoop = depth.length - getNearestLoop();
2917
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2918
+ const type = depth[target];
2919
+
2920
+ // different loop types have different branch offsets
2921
+ // as they have different wasm block/loop/if structures
2922
+ // we need to use the right offset by type to branch to the one we want
2923
+ // for a continue: do test for the loop, and then loop depending on that success
2924
+ const offset = ({
2925
+ for: 3, // loop (wanted branch) > if > block (we are here)
2926
+ while: 1, // loop (wanted branch) > if (we are here)
2927
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2928
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2929
+ })[type];
2930
+
2356
2931
  return [
2357
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2932
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2358
2933
  ];
2359
2934
  };
2360
2935
 
2936
+ const generateLabel = (scope, decl) => {
2937
+ scope.labels ??= new Map();
2938
+
2939
+ const name = decl.label.name;
2940
+ scope.labels.set(name, depth.length);
2941
+
2942
+ return generate(scope, decl.body);
2943
+ };
2944
+
2361
2945
  const generateThrow = (scope, decl) => {
2362
2946
  scope.throws = true;
2363
2947
 
@@ -2366,7 +2950,7 @@ const generateThrow = (scope, decl) => {
2366
2950
  // hack: throw new X("...") -> throw "..."
2367
2951
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2368
2952
  constructor = decl.argument.callee.name;
2369
- message = decl.argument.arguments[0].value;
2953
+ message = decl.argument.arguments[0]?.value ?? '';
2370
2954
  }
2371
2955
 
2372
2956
  if (tags.length === 0) tags.push({
@@ -2378,6 +2962,9 @@ const generateThrow = (scope, decl) => {
2378
2962
  let exceptId = exceptions.push({ constructor, message }) - 1;
2379
2963
  let tagIdx = tags[0].idx;
2380
2964
 
2965
+ scope.exceptions ??= [];
2966
+ scope.exceptions.push(exceptId);
2967
+
2381
2968
  // todo: write a description of how this works lol
2382
2969
 
2383
2970
  return [
@@ -2387,7 +2974,7 @@ const generateThrow = (scope, decl) => {
2387
2974
  };
2388
2975
 
2389
2976
  const generateTry = (scope, decl) => {
2390
- if (decl.finalizer) return todo('try finally not implemented yet');
2977
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2391
2978
 
2392
2979
  const out = [];
2393
2980
 
@@ -2418,29 +3005,35 @@ const generateAssignPat = (scope, decl) => {
2418
3005
  // TODO
2419
3006
  // if identifier declared, use that
2420
3007
  // else, use default (right)
2421
- return todo('assignment pattern (optional arg)');
3008
+ return todo(scope, 'assignment pattern (optional arg)');
2422
3009
  };
2423
3010
 
2424
3011
  let pages = new Map();
2425
- const allocPage = (reason, type) => {
3012
+ const allocPage = (scope, reason, type) => {
2426
3013
  if (pages.has(reason)) return pages.get(reason).ind;
2427
3014
 
2428
3015
  if (reason.startsWith('array:')) pages.hasArray = true;
2429
3016
  if (reason.startsWith('string:')) pages.hasString = true;
3017
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3018
+ if (reason.includes('string:')) pages.hasAnyString = true;
2430
3019
 
2431
3020
  const ind = pages.size;
2432
3021
  pages.set(reason, { ind, type });
2433
3022
 
2434
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3023
+ scope.pages ??= new Map();
3024
+ scope.pages.set(reason, { ind, type });
3025
+
3026
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2435
3027
 
2436
3028
  return ind;
2437
3029
  };
2438
3030
 
3031
+ // todo: add scope.pages
2439
3032
  const freePage = reason => {
2440
3033
  const { ind } = pages.get(reason);
2441
3034
  pages.delete(reason);
2442
3035
 
2443
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3036
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2444
3037
 
2445
3038
  return ind;
2446
3039
  };
@@ -2460,38 +3053,51 @@ const StoreOps = {
2460
3053
  f64: Opcodes.f64_store,
2461
3054
 
2462
3055
  // expects i32 input!
2463
- i16: Opcodes.i32_store16
3056
+ i8: Opcodes.i32_store8,
3057
+ i16: Opcodes.i32_store16,
2464
3058
  };
2465
3059
 
2466
3060
  let data = [];
2467
3061
 
2468
- const compileBytes = (val, itemType, signed = true) => {
3062
+ const compileBytes = (val, itemType) => {
2469
3063
  // todo: this is a mess and needs confirming / ????
2470
3064
  switch (itemType) {
2471
3065
  case 'i8': return [ val % 256 ];
2472
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2473
-
2474
- case 'i32':
2475
- case 'i64':
2476
- return enforceFourBytes(signedLEB128(val));
3066
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3067
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3068
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3069
+ // todo: i64
2477
3070
 
2478
3071
  case 'f64': return ieee754_binary64(val);
2479
3072
  }
2480
3073
  };
2481
3074
 
3075
+ const getAllocType = itemType => {
3076
+ switch (itemType) {
3077
+ case 'i8': return 'bytestring';
3078
+ case 'i16': return 'string';
3079
+
3080
+ default: return 'array';
3081
+ }
3082
+ };
3083
+
2482
3084
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2483
3085
  const out = [];
2484
3086
 
3087
+ scope.arrays ??= new Map();
3088
+
2485
3089
  let firstAssign = false;
2486
- if (!arrays.has(name) || name === '$undeclared') {
3090
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2487
3091
  firstAssign = true;
2488
3092
 
2489
3093
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2490
3094
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2491
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3095
+
3096
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3097
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2492
3098
  }
2493
3099
 
2494
- const pointer = arrays.get(name);
3100
+ const pointer = scope.arrays.get(name);
2495
3101
 
2496
3102
  const useRawElements = !!decl.rawElements;
2497
3103
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2499,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2499
3105
  const valtype = itemTypeToValtype[itemType];
2500
3106
  const length = elements.length;
2501
3107
 
2502
- if (firstAssign && useRawElements) {
2503
- let bytes = compileBytes(length, 'i32');
3108
+ if (firstAssign && useRawElements && !Prefs.noData) {
3109
+ // if length is 0 memory/data will just be 0000... anyway
3110
+ if (length !== 0) {
3111
+ let bytes = compileBytes(length, 'i32');
2504
3112
 
2505
- if (!initEmpty) for (let i = 0; i < length; i++) {
2506
- if (elements[i] == null) continue;
3113
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3114
+ if (elements[i] == null) continue;
2507
3115
 
2508
- bytes.push(...compileBytes(elements[i], itemType));
2509
- }
3116
+ bytes.push(...compileBytes(elements[i], itemType));
3117
+ }
2510
3118
 
2511
- data.push({
2512
- offset: pointer,
2513
- bytes
2514
- });
3119
+ const ind = data.push({
3120
+ offset: pointer,
3121
+ bytes
3122
+ }) - 1;
3123
+
3124
+ scope.data ??= [];
3125
+ scope.data.push(ind);
3126
+ }
2515
3127
 
2516
3128
  // local value as pointer
2517
3129
  out.push(...number(pointer));
@@ -2534,7 +3146,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2534
3146
  out.push(
2535
3147
  ...number(0, Valtype.i32),
2536
3148
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2537
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3149
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2538
3150
  );
2539
3151
  }
2540
3152
 
@@ -2544,31 +3156,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2544
3156
  return [ out, pointer ];
2545
3157
  };
2546
3158
 
2547
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3159
+ const byteStringable = str => {
3160
+ if (!Prefs.bytestring) return false;
3161
+
3162
+ for (let i = 0; i < str.length; i++) {
3163
+ if (str.charCodeAt(i) > 0xFF) return false;
3164
+ }
3165
+
3166
+ return true;
3167
+ };
3168
+
3169
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2548
3170
  const rawElements = new Array(str.length);
3171
+ let byteStringable = Prefs.bytestring;
2549
3172
  for (let i = 0; i < str.length; i++) {
2550
- rawElements[i] = str.charCodeAt(i);
3173
+ const c = str.charCodeAt(i);
3174
+ rawElements[i] = c;
3175
+
3176
+ if (byteStringable && c > 0xFF) byteStringable = false;
2551
3177
  }
2552
3178
 
3179
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3180
+
2553
3181
  return makeArray(scope, {
2554
3182
  rawElements
2555
- }, global, name, false, 'i16')[0];
3183
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2556
3184
  };
2557
3185
 
2558
- let arrays = new Map();
2559
3186
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2560
3187
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2561
3188
  };
2562
3189
 
2563
3190
  export const generateMember = (scope, decl, _global, _name) => {
2564
3191
  const name = decl.object.name;
2565
- const pointer = arrays.get(name);
3192
+ const pointer = scope.arrays?.get(name);
2566
3193
 
2567
- const aotPointer = pointer != null;
3194
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2568
3195
 
2569
3196
  // hack: .length
2570
3197
  if (decl.property.name === 'length') {
2571
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3198
+ const func = funcs.find(x => x.name === name);
3199
+ if (func) {
3200
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3201
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3202
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3203
+ }
3204
+
3205
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3206
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3207
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3208
+
2572
3209
  return [
2573
3210
  ...(aotPointer ? number(0, Valtype.i32) : [
2574
3211
  ...generate(scope, decl.object),
@@ -2580,10 +3217,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2580
3217
  ];
2581
3218
  }
2582
3219
 
3220
+ const object = generate(scope, decl.object);
3221
+ const property = generate(scope, decl.property);
3222
+
2583
3223
  // // todo: we should only do this for strings but we don't know at compile-time :(
2584
3224
  // hack: this is naughty and will break things!
2585
3225
  let newOut = number(0, valtypeBinary), newPointer = -1;
2586
- if (pages.hasString) {
3226
+ if (pages.hasAnyString) {
2587
3227
  0, [ newOut, newPointer ] = makeArray(scope, {
2588
3228
  rawElements: new Array(1)
2589
3229
  }, _global, _name, true, 'i16');
@@ -2592,7 +3232,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2592
3232
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2593
3233
  [TYPES._array]: [
2594
3234
  // get index as valtype
2595
- ...generate(scope, decl.property),
3235
+ ...property,
2596
3236
 
2597
3237
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2598
3238
  Opcodes.i32_to_u,
@@ -2600,7 +3240,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2600
3240
  [ Opcodes.i32_mul ],
2601
3241
 
2602
3242
  ...(aotPointer ? [] : [
2603
- ...generate(scope, decl.object),
3243
+ ...object,
2604
3244
  Opcodes.i32_to_u,
2605
3245
  [ Opcodes.i32_add ]
2606
3246
  ]),
@@ -2609,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2609
3249
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2610
3250
 
2611
3251
  ...number(TYPES.number, Valtype.i32),
2612
- setLastType(scope)
3252
+ ...setLastType(scope)
2613
3253
  ],
2614
3254
 
2615
3255
  [TYPES.string]: [
@@ -2619,14 +3259,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2619
3259
 
2620
3260
  ...number(0, Valtype.i32), // base 0 for store later
2621
3261
 
2622
- ...generate(scope, decl.property),
2623
-
3262
+ ...property,
2624
3263
  Opcodes.i32_to_u,
3264
+
2625
3265
  ...number(ValtypeSize.i16, Valtype.i32),
2626
3266
  [ Opcodes.i32_mul ],
2627
3267
 
2628
3268
  ...(aotPointer ? [] : [
2629
- ...generate(scope, decl.object),
3269
+ ...object,
2630
3270
  Opcodes.i32_to_u,
2631
3271
  [ Opcodes.i32_add ]
2632
3272
  ]),
@@ -2641,10 +3281,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2641
3281
  ...number(newPointer),
2642
3282
 
2643
3283
  ...number(TYPES.string, Valtype.i32),
2644
- setLastType(scope)
3284
+ ...setLastType(scope)
3285
+ ],
3286
+ [TYPES._bytestring]: [
3287
+ // setup new/out array
3288
+ ...newOut,
3289
+ [ Opcodes.drop ],
3290
+
3291
+ ...number(0, Valtype.i32), // base 0 for store later
3292
+
3293
+ ...property,
3294
+ Opcodes.i32_to_u,
3295
+
3296
+ ...(aotPointer ? [] : [
3297
+ ...object,
3298
+ Opcodes.i32_to_u,
3299
+ [ Opcodes.i32_add ]
3300
+ ]),
3301
+
3302
+ // load current string ind {arg}
3303
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3304
+
3305
+ // store to new string ind 0
3306
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3307
+
3308
+ // return new string (page)
3309
+ ...number(newPointer),
3310
+
3311
+ ...number(TYPES._bytestring, Valtype.i32),
3312
+ ...setLastType(scope)
2645
3313
  ],
2646
3314
 
2647
- default: [ [ Opcodes.unreachable ] ]
3315
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2648
3316
  });
2649
3317
  };
2650
3318
 
@@ -2654,25 +3322,36 @@ const objectHack = node => {
2654
3322
  if (!node) return node;
2655
3323
 
2656
3324
  if (node.type === 'MemberExpression') {
2657
- if (node.computed || node.optional) return node;
3325
+ const out = (() => {
3326
+ if (node.computed || node.optional) return;
2658
3327
 
2659
- let objectName = node.object.name;
3328
+ let objectName = node.object.name;
2660
3329
 
2661
- // if object is not identifier or another member exp, give up
2662
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3330
+ // if object is not identifier or another member exp, give up
3331
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3332
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2663
3333
 
2664
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3334
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2665
3335
 
2666
- // if .length, give up (hack within a hack!)
2667
- if (node.property.name === 'length') return node;
3336
+ // if .length, give up (hack within a hack!)
3337
+ if (node.property.name === 'length') {
3338
+ node.object = objectHack(node.object);
3339
+ return;
3340
+ }
2668
3341
 
2669
- const name = '__' + objectName + '_' + node.property.name;
2670
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3342
+ // no object name, give up
3343
+ if (!objectName) return;
2671
3344
 
2672
- return {
2673
- type: 'Identifier',
2674
- name
2675
- };
3345
+ const name = '__' + objectName + '_' + node.property.name;
3346
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3347
+
3348
+ return {
3349
+ type: 'Identifier',
3350
+ name
3351
+ };
3352
+ })();
3353
+
3354
+ if (out) return out;
2676
3355
  }
2677
3356
 
2678
3357
  for (const x in node) {
@@ -2686,8 +3365,8 @@ const objectHack = node => {
2686
3365
  };
2687
3366
 
2688
3367
  const generateFunc = (scope, decl) => {
2689
- if (decl.async) return todo('async functions are not supported');
2690
- if (decl.generator) return todo('generator functions are not supported');
3368
+ if (decl.async) return todo(scope, 'async functions are not supported');
3369
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2691
3370
 
2692
3371
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2693
3372
  const params = decl.params ?? [];
@@ -2703,6 +3382,11 @@ const generateFunc = (scope, decl) => {
2703
3382
  name
2704
3383
  };
2705
3384
 
3385
+ if (typedInput && decl.returnType) {
3386
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3387
+ innerScope.returns = [ valtypeBinary ];
3388
+ }
3389
+
2706
3390
  for (let i = 0; i < params.length; i++) {
2707
3391
  allocVar(innerScope, params[i].name, false);
2708
3392
 
@@ -2724,13 +3408,13 @@ const generateFunc = (scope, decl) => {
2724
3408
  const func = {
2725
3409
  name,
2726
3410
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2727
- returns: innerScope.returns,
2728
- locals: innerScope.locals,
2729
- throws: innerScope.throws,
2730
- index: currentFuncIndex++
3411
+ index: currentFuncIndex++,
3412
+ ...innerScope
2731
3413
  };
2732
3414
  funcIndex[name] = func.index;
2733
3415
 
3416
+ if (name === 'main') func.gotLastType = true;
3417
+
2734
3418
  // quick hack fixes
2735
3419
  for (const inst of wasm) {
2736
3420
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2782,7 +3466,7 @@ const internalConstrs = {
2782
3466
 
2783
3467
  // todo: check in wasm instead of here
2784
3468
  const literalValue = arg.value ?? 0;
2785
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3469
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2786
3470
 
2787
3471
  return [
2788
3472
  ...number(0, Valtype.i32),
@@ -2793,7 +3477,8 @@ const internalConstrs = {
2793
3477
  ...number(pointer)
2794
3478
  ];
2795
3479
  },
2796
- type: TYPES._array
3480
+ type: TYPES._array,
3481
+ length: 1
2797
3482
  },
2798
3483
 
2799
3484
  __Array_of: {
@@ -2805,7 +3490,131 @@ const internalConstrs = {
2805
3490
  }, global, name);
2806
3491
  },
2807
3492
  type: TYPES._array,
3493
+ notConstr: true,
3494
+ length: 0
3495
+ },
3496
+
3497
+ __Porffor_fastOr: {
3498
+ generate: (scope, decl) => {
3499
+ const out = [];
3500
+
3501
+ for (let i = 0; i < decl.arguments.length; i++) {
3502
+ out.push(
3503
+ ...generate(scope, decl.arguments[i]),
3504
+ Opcodes.i32_to_u,
3505
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3506
+ );
3507
+ }
3508
+
3509
+ out.push(Opcodes.i32_from_u);
3510
+
3511
+ return out;
3512
+ },
3513
+ type: TYPES.boolean,
2808
3514
  notConstr: true
3515
+ },
3516
+
3517
+ __Porffor_fastAnd: {
3518
+ generate: (scope, decl) => {
3519
+ const out = [];
3520
+
3521
+ for (let i = 0; i < decl.arguments.length; i++) {
3522
+ out.push(
3523
+ ...generate(scope, decl.arguments[i]),
3524
+ Opcodes.i32_to_u,
3525
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3526
+ );
3527
+ }
3528
+
3529
+ out.push(Opcodes.i32_from_u);
3530
+
3531
+ return out;
3532
+ },
3533
+ type: TYPES.boolean,
3534
+ notConstr: true
3535
+ },
3536
+
3537
+ Boolean: {
3538
+ generate: (scope, decl) => {
3539
+ // todo: boolean object when used as constructor
3540
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3541
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3542
+ },
3543
+ type: TYPES.boolean,
3544
+ length: 1
3545
+ },
3546
+
3547
+ __Math_max: {
3548
+ generate: (scope, decl) => {
3549
+ const out = [
3550
+ ...number(-Infinity)
3551
+ ];
3552
+
3553
+ for (let i = 0; i < decl.arguments.length; i++) {
3554
+ out.push(
3555
+ ...generate(scope, decl.arguments[i]),
3556
+ [ Opcodes.f64_max ]
3557
+ );
3558
+ }
3559
+
3560
+ return out;
3561
+ },
3562
+ type: TYPES.number,
3563
+ notConstr: true,
3564
+ length: 2
3565
+ },
3566
+
3567
+ __Math_min: {
3568
+ generate: (scope, decl) => {
3569
+ const out = [
3570
+ ...number(Infinity)
3571
+ ];
3572
+
3573
+ for (let i = 0; i < decl.arguments.length; i++) {
3574
+ out.push(
3575
+ ...generate(scope, decl.arguments[i]),
3576
+ [ Opcodes.f64_min ]
3577
+ );
3578
+ }
3579
+
3580
+ return out;
3581
+ },
3582
+ type: TYPES.number,
3583
+ notConstr: true,
3584
+ length: 2
3585
+ },
3586
+
3587
+ __console_log: {
3588
+ generate: (scope, decl) => {
3589
+ const out = [];
3590
+
3591
+ for (let i = 0; i < decl.arguments.length; i++) {
3592
+ out.push(
3593
+ ...generateCall(scope, {
3594
+ callee: {
3595
+ type: 'Identifier',
3596
+ name: '__Porffor_print'
3597
+ },
3598
+ arguments: [ decl.arguments[i] ]
3599
+ }),
3600
+
3601
+ // print space
3602
+ ...number(32),
3603
+ [ Opcodes.call, importedFuncs.printChar ]
3604
+ );
3605
+ }
3606
+
3607
+ // print newline
3608
+ out.push(
3609
+ ...number(10),
3610
+ [ Opcodes.call, importedFuncs.printChar ]
3611
+ );
3612
+
3613
+ return out;
3614
+ },
3615
+ type: TYPES.undefined,
3616
+ notConstr: true,
3617
+ length: 0
2809
3618
  }
2810
3619
  };
2811
3620
 
@@ -2834,7 +3643,6 @@ export default program => {
2834
3643
  funcs = [];
2835
3644
  funcIndex = {};
2836
3645
  depth = [];
2837
- arrays = new Map();
2838
3646
  pages = new Map();
2839
3647
  data = [];
2840
3648
  currentFuncIndex = importedFuncs.length;
@@ -2848,6 +3656,10 @@ export default program => {
2848
3656
 
2849
3657
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2850
3658
 
3659
+ globalThis.pageSize = PageSize;
3660
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3661
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3662
+
2851
3663
  // set generic opcodes for current valtype
2852
3664
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2853
3665
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2856,10 +3668,10 @@ export default program => {
2856
3668
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2857
3669
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2858
3670
 
2859
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2860
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2861
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2862
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3671
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3672
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3673
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3674
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2863
3675
 
2864
3676
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2865
3677
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2872,10 +3684,6 @@ export default program => {
2872
3684
 
2873
3685
  program.id = { name: 'main' };
2874
3686
 
2875
- globalThis.pageSize = PageSize;
2876
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2877
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2878
-
2879
3687
  const scope = {
2880
3688
  locals: {},
2881
3689
  localInd: 0
@@ -2886,7 +3694,7 @@ export default program => {
2886
3694
  body: program.body
2887
3695
  };
2888
3696
 
2889
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3697
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2890
3698
 
2891
3699
  generateFunc(scope, program);
2892
3700
 
@@ -2903,7 +3711,11 @@ export default program => {
2903
3711
  }
2904
3712
 
2905
3713
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2906
- main.returns = [];
3714
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3715
+ main.wasm.splice(main.wasm.length - 1, 1);
3716
+ } else {
3717
+ main.returns = [];
3718
+ }
2907
3719
  }
2908
3720
 
2909
3721
  if (lastInst[0] === Opcodes.call) {