porffor 0.2.0-aea77ff → 0.2.0-b9abe0d

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 (53) hide show
  1. package/.vscode/launch.json +18 -0
  2. package/LICENSE +20 -20
  3. package/README.md +124 -83
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +317 -72
  8. package/compiler/{sections.js → assemble.js} +63 -15
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +151 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +9 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/int.ts +147 -0
  17. package/compiler/builtins/number.ts +527 -0
  18. package/compiler/builtins/porffor.d.ts +42 -0
  19. package/compiler/builtins/string.ts +1055 -0
  20. package/compiler/builtins/tostring.ts +45 -0
  21. package/compiler/builtins.js +479 -262
  22. package/compiler/{codeGen.js → codegen.js} +1049 -394
  23. package/compiler/embedding.js +22 -22
  24. package/compiler/encoding.js +108 -10
  25. package/compiler/generated_builtins.js +722 -0
  26. package/compiler/index.js +36 -34
  27. package/compiler/log.js +6 -3
  28. package/compiler/opt.js +51 -36
  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 +29 -7
  35. package/compiler/wrap.js +52 -39
  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 +49 -10
  44. package/runner/profiler.js +102 -0
  45. package/runner/repl.js +40 -7
  46. package/runner/sizes.js +37 -37
  47. package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
  48. package/compiler/builtins/base64.js +0 -92
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/util/enum.js +0 -20
@@ -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);
@@ -86,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
86
90
  return generateExp(scope, decl);
87
91
 
88
92
  case 'CallExpression':
89
- return generateCall(scope, decl, global, name);
93
+ return generateCall(scope, decl, global, name, valueUnused);
90
94
 
91
95
  case 'NewExpression':
92
96
  return generateNew(scope, decl, global, name);
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
104
108
  return generateUnary(scope, decl);
105
109
 
106
110
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
112
 
109
113
  case 'IfStatement':
110
114
  return generateIf(scope, decl);
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
115
119
  case 'WhileStatement':
116
120
  return generateWhile(scope, decl);
117
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
118
125
  case 'ForOfStatement':
119
126
  return generateForOf(scope, decl);
120
127
 
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
124
131
  case 'ContinueStatement':
125
132
  return generateContinue(scope, decl);
126
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
127
137
  case 'EmptyStatement':
128
138
  return generateEmpty(scope, decl);
129
139
 
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
137
147
  return generateTry(scope, decl);
138
148
 
139
149
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
141
151
  return [];
142
152
 
143
153
  case 'ArrayExpression':
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
151
161
  const funcsBefore = funcs.length;
152
162
  generate(scope, decl.declaration);
153
163
 
154
- 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');
155
171
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
158
174
 
159
175
  return [];
160
176
 
161
177
  case 'TaggedTemplateExpression': {
162
178
  const funcs = {
163
- asm: str => {
179
+ __Porffor_wasm: str => {
164
180
  let out = [];
165
181
 
166
182
  for (const line of str.split('\n')) {
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
168
184
  if (asm[0] === '') continue; // blank
169
185
 
170
186
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- 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] };
173
189
  continue;
174
190
  }
175
191
 
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
179
195
  }
180
196
 
181
197
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
183
199
  // todo: add to store/load offset insts
184
200
  continue;
185
201
  }
@@ -188,7 +204,11 @@ const generate = (scope, decl, global = false, name = undefined) => {
188
204
  if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
205
 
190
206
  if (!Array.isArray(inst)) inst = [ inst ];
191
- 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
+ });
192
212
 
193
213
  out.push([ ...inst, ...immediates ]);
194
214
  }
@@ -196,35 +216,53 @@ const generate = (scope, decl, global = false, name = undefined) => {
196
216
  return out;
197
217
  },
198
218
 
199
- __internal_print_type: str => {
200
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
201
221
 
202
- return [
203
- ...number(type),
204
- [ 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),
205
229
 
206
- // newline
207
- ...number(10),
208
- [ Opcodes.call, importedFuncs.printChar ]
209
- ];
210
- }
211
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
212
236
 
213
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
214
238
  // hack for inline asm
215
- 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;
216
251
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
252
+ str += quasis[i + 1].value.raw;
253
+ }
254
+
255
+ return funcs[func](str);
219
256
  }
220
257
 
221
258
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
262
  return [];
225
263
  }
226
264
 
227
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
228
266
  }
229
267
  };
230
268
 
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
252
290
  return [ undefined, undefined ];
253
291
  };
254
292
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
294
  ...generateThrow(scope, {
257
295
  argument: {
258
296
  type: 'NewExpression',
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
274
312
  const name = mapName(rawName);
275
313
  let local = scope.locals[rawName];
276
314
 
277
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
278
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- 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);
280
326
  }
281
327
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
328
+ if (isExistingProtoFunc(name)) {
283
329
  // todo: return an actual something
284
330
  return number(1);
285
331
  }
286
332
 
287
- if (local === undefined) {
333
+ if (local?.idx === undefined) {
288
334
  // no local var with name
289
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
290
- 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]);
291
337
 
292
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
293
339
  }
294
340
 
295
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
296
342
  // return undefined if unknown key in already known var
297
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
298
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
301
347
  if (!parentLookup[1]) return number(UNDEFINED);
302
348
  }
303
349
 
304
- 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);
305
351
 
306
352
  return [ [ Opcodes.local_get, local.idx ] ];
307
353
  };
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
314
360
  // just bare "return"
315
361
  return [
316
362
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
318
366
  [ Opcodes.return ]
319
367
  ];
320
368
  }
321
369
 
322
370
  return [
323
371
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
325
375
  [ Opcodes.return ]
326
376
  ];
327
377
  };
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
385
  return idx;
336
386
  };
337
387
 
338
- 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);
339
390
 
340
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
392
  const checks = {
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
395
  '??': nullish
345
396
  };
346
397
 
347
- 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);
348
399
 
349
400
  // generic structure for {a} OP {b}
350
401
  // -->
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
403
 
353
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
405
  // (like if we are in an if condition - very common)
355
- const leftIsInt = isIntOp(left[left.length - 1]);
356
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
408
 
358
409
  const canInt = leftIsInt && rightIsInt;
359
410
 
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
421
  ...right,
371
422
  // note type
372
423
  ...rightType,
373
- setLastType(scope),
424
+ ...setLastType(scope),
374
425
  [ Opcodes.else ],
375
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
427
  // note type
377
428
  ...leftType,
378
- setLastType(scope),
429
+ ...setLastType(scope),
379
430
  [ Opcodes.end ],
380
431
  Opcodes.i32_from
381
432
  ];
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
440
  ...right,
390
441
  // note type
391
442
  ...rightType,
392
- setLastType(scope),
443
+ ...setLastType(scope),
393
444
  [ Opcodes.else ],
394
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
446
  // note type
396
447
  ...leftType,
397
- setLastType(scope),
448
+ ...setLastType(scope),
398
449
  [ Opcodes.end ]
399
450
  ];
400
451
  };
401
452
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
455
  // todo: convert left and right to strings if not
405
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
462
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
463
  if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
465
 
418
466
  return [
419
467
  // setup right
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
487
 
440
488
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
442
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
491
 
444
492
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
494
  [ Opcodes.i32_mul ],
447
495
  [ Opcodes.i32_add ],
448
496
 
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
499
  ...number(ValtypeSize.i32, Valtype.i32),
452
500
  [ Opcodes.i32_add ],
453
501
 
454
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
455
503
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
505
  [ Opcodes.i32_mul ],
458
506
 
459
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
560
 
513
561
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
515
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
564
 
517
565
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
567
  [ Opcodes.i32_mul ],
520
568
  [ Opcodes.i32_add ],
521
569
 
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
572
  ...number(ValtypeSize.i32, Valtype.i32),
525
573
  [ Opcodes.i32_add ],
526
574
 
527
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
528
576
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
578
  [ Opcodes.i32_mul ],
531
579
 
532
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
584
  ];
537
585
  };
538
586
 
539
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
588
  // todo: this should be rewritten into a func
541
589
  // todo: convert left and right to strings if not
542
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
545
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
596
 
550
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
573
620
 
574
621
  [ Opcodes.local_get, rightPointer ],
575
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
623
 
578
624
  // fast path: check leftLength != rightLength
579
625
  [ Opcodes.i32_ne ],
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
588
634
  ...number(0, Valtype.i32),
589
635
  [ Opcodes.local_set, index ],
590
636
 
591
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
638
  // we do this instead of having to do mul/div each iter for perf™
593
639
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
596
644
  [ Opcodes.local_set, indexEnd ],
597
645
 
598
646
  // iterate over each char and check if eq
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
602
650
  [ Opcodes.local_get, index ],
603
651
  [ Opcodes.local_get, leftPointer ],
604
652
  [ Opcodes.i32_add ],
605
- [ 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 ],
606
656
 
607
657
  // fetch right
608
658
  [ Opcodes.local_get, index ],
609
659
  [ Opcodes.local_get, rightPointer ],
610
660
  [ Opcodes.i32_add ],
611
- [ 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 ],
612
664
 
613
665
  // not equal, "return" false
614
666
  [ Opcodes.i32_ne ],
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
617
669
  [ Opcodes.br, 2 ],
618
670
  [ Opcodes.end ],
619
671
 
620
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
673
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
675
  [ Opcodes.i32_add ],
624
676
  [ Opcodes.local_tee, index ],
625
677
 
626
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
627
679
  [ Opcodes.local_get, indexEnd ],
628
680
  [ Opcodes.i32_ne ],
629
681
  [ Opcodes.br_if, 0 ],
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
644
696
  };
645
697
 
646
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
700
  ...wasm,
649
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
704
 
652
- 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);
653
707
 
654
708
  const def = [
655
709
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
711
 
658
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
719
 
666
720
  return [
667
721
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
723
 
670
724
  ...typeSwitch(scope, type, {
671
725
  // [TYPES.number]: def,
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
729
  ],
676
730
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
733
 
680
734
  // get length
@@ -685,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
739
  [ Opcodes.i32_eqz ], */
686
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
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
+ ],
688
751
  default: def
689
752
  }, intOut ? Valtype.i32 : valtypeBinary)
690
753
  ];
691
754
  };
692
755
 
693
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
694
- 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
+
695
760
  return [
696
761
  ...wasm,
697
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
698
763
 
699
764
  ...typeSwitch(scope, type, {
700
765
  [TYPES._array]: [
@@ -702,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
702
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
703
768
  ],
704
769
  [TYPES.string]: [
705
- [ 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 ] ]),
706
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
707
783
 
708
784
  // get length
@@ -714,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
714
790
  ],
715
791
  default: [
716
792
  // if value == 0
717
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
718
794
 
719
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
720
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -724,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
724
800
  };
725
801
 
726
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
727
- 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
+
728
806
  return [
729
807
  ...wasm,
730
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
731
809
 
732
810
  ...typeSwitch(scope, type, {
733
811
  [TYPES.undefined]: [
@@ -736,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
736
814
  ],
737
815
  [TYPES.object]: [
738
816
  // object, null if == 0
739
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
740
818
 
741
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
742
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -765,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
765
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
766
844
  }
767
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
768
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
769
850
  const strictOp = op === '===' || op === '!==';
770
851
 
771
852
  const startOut = [], endOut = [];
772
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
773
854
 
774
855
  // if strict (in)equal check types match
775
856
  if (strictOp) {
@@ -814,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
814
895
  // todo: if equality op and an operand is undefined, return false
815
896
  // todo: niche null hell with 0
816
897
 
817
- // if (leftType === TYPES.string || rightType === TYPES.string) {
818
- // if (op === '+') {
819
- // // string concat (a + b)
820
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
821
- // }
822
-
823
- // // not an equality op, NaN
824
- // if (!eqOp) return finalise(number(NaN));
825
-
826
- // // else leave bool ops
827
- // // todo: convert string to number if string and number/bool
828
- // // todo: string (>|>=|<|<=) string
829
-
830
- // // string comparison
831
- // if (op === '===' || op === '==') {
832
- // return finalise(compareStrings(scope, left, right));
833
- // }
834
-
835
- // if (op === '!==' || op === '!=') {
836
- // return finalise([
837
- // ...compareStrings(scope, left, right),
838
- // [ Opcodes.i32_eqz ]
839
- // ]);
840
- // }
841
- // }
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
+ }
842
951
 
843
952
  let ops = operatorOpcode[valtype][op];
844
953
 
@@ -848,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
848
957
  includeBuiltin(scope, builtinName);
849
958
  const idx = funcIndex[builtinName];
850
959
 
851
- return finalise([
960
+ return finalize([
852
961
  ...left,
853
962
  ...right,
854
963
  [ Opcodes.call, idx ]
855
964
  ]);
856
965
  }
857
966
 
858
- 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);
859
968
 
860
969
  if (!Array.isArray(ops)) ops = [ ops ];
861
970
  ops = [ ops ];
862
971
 
863
972
  let tmpLeft, tmpRight;
864
973
  // if equal op, check if strings for compareStrings
865
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
866
- const knownLeft = knownType(scope, leftType);
867
- const knownRight = knownType(scope, rightType);
868
-
869
- // todo: intelligent partial skip later
870
- // if neither known are string, stop this madness
871
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
872
- return;
873
- }
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
874
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
875
979
  tmpLeft = localTmp(scope, '__tmpop_left');
876
980
  tmpRight = localTmp(scope, '__tmpop_right');
877
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)
878
1023
  ops.unshift(...stringOnly([
879
1024
  // if left is string
880
1025
  ...leftType,
@@ -886,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
886
1031
  ...number(TYPES.string, Valtype.i32),
887
1032
  [ Opcodes.i32_eq ],
888
1033
 
889
- // if either are true
890
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
891
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 ],
892
1041
 
893
- // todo: convert non-strings to strings, for now fail immediately if one is not
894
- // if left is not string
1042
+ // if left is bytestring
895
1043
  ...leftType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
898
1046
 
899
- // if right is not string
1047
+ // if right is bytestring
900
1048
  ...rightType,
901
- ...number(TYPES.string, Valtype.i32),
902
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
903
1051
 
904
- // if either are true
905
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
906
1054
  [ Opcodes.if, Blocktype.void ],
907
- ...number(0, Valtype.i32),
908
- [ Opcodes.br, 1 ],
909
- [ Opcodes.end ],
910
-
911
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
912
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
913
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
914
1057
  [ Opcodes.br, 1 ],
915
1058
  [ Opcodes.end ],
@@ -921,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
921
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
922
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
923
1066
  // }
924
- })();
1067
+ }
925
1068
 
926
- return finalise([
1069
+ return finalize([
927
1070
  ...left,
928
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
929
1072
  ...right,
@@ -940,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
940
1083
  return out;
941
1084
  };
942
1085
 
943
- 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 = [] }) => {
944
1102
  const existing = funcs.find(x => x.name === name);
945
1103
  if (existing) return existing;
946
1104
 
@@ -952,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
952
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
953
1111
  }
954
1112
 
955
- if (typeof wasm === 'function') {
956
- const scope = {
957
- name,
958
- params,
959
- locals,
960
- returns,
961
- localInd: allLocals.length,
962
- };
963
-
964
- wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
965
1117
  }
966
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
967
1121
  let baseGlobalIdx, i = 0;
968
1122
  for (const type of globalTypes) {
969
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -986,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
986
1140
  params,
987
1141
  locals,
988
1142
  returns,
989
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
990
1144
  wasm,
991
1145
  internal: true,
992
1146
  index: currentFuncIndex++
@@ -1009,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1009
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1010
1164
  };
1011
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1012
1167
  // T = JS type, V = value/pointer
1013
1168
  // 0bTTT
1014
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1032,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1032
1187
  // 4: internal type
1033
1188
  // 5: pointer
1034
1189
 
1035
- const TYPES = {
1036
- number: 0x00,
1037
- boolean: 0x01,
1038
- string: 0x02,
1039
- undefined: 0x03,
1040
- object: 0x04,
1041
- function: 0x05,
1042
- symbol: 0x06,
1043
- 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)];
1044
1193
 
1045
- // these are not "typeof" types but tracked internally
1046
- _array: 0x10,
1047
- _regexp: 0x11
1048
- };
1049
-
1050
- const TYPE_NAMES = {
1051
- [TYPES.number]: 'Number',
1052
- [TYPES.boolean]: 'Boolean',
1053
- [TYPES.string]: 'String',
1054
- [TYPES.undefined]: 'undefined',
1055
- [TYPES.object]: 'Object',
1056
- [TYPES.function]: 'Function',
1057
- [TYPES.symbol]: 'Symbol',
1058
- [TYPES.bigint]: 'BigInt',
1059
-
1060
- [TYPES._array]: 'Array',
1061
- [TYPES._regexp]: 'RegExp'
1194
+ return false;
1062
1195
  };
1063
1196
 
1064
1197
  const getType = (scope, _name) => {
1065
1198
  const name = mapName(_name);
1066
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);
1067
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);
1068
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1069
1207
 
1070
1208
  let type = TYPES.undefined;
1071
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1072
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1073
1211
 
1074
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1075
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1076
1213
 
1077
1214
  return number(type, Valtype.i32);
1078
1215
  };
@@ -1095,15 +1232,16 @@ const setType = (scope, _name, type) => {
1095
1232
  ];
1096
1233
 
1097
1234
  // throw new Error('could not find var');
1235
+ return [];
1098
1236
  };
1099
1237
 
1100
1238
  const getLastType = scope => {
1101
1239
  scope.gotLastType = true;
1102
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1103
1241
  };
1104
1242
 
1105
1243
  const setLastType = scope => {
1106
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1107
1245
  };
1108
1246
 
1109
1247
  const getNodeType = (scope, node) => {
@@ -1111,6 +1249,8 @@ const getNodeType = (scope, node) => {
1111
1249
  if (node.type === 'Literal') {
1112
1250
  if (node.regex) return TYPES._regexp;
1113
1251
 
1252
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1253
+
1114
1254
  return TYPES[typeof node.value];
1115
1255
  }
1116
1256
 
@@ -1124,6 +1264,27 @@ const getNodeType = (scope, node) => {
1124
1264
 
1125
1265
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1126
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
+
1127
1288
  const func = funcs.find(x => x.name === name);
1128
1289
 
1129
1290
  if (func) {
@@ -1131,7 +1292,7 @@ const getNodeType = (scope, node) => {
1131
1292
  if (func.returnType) return func.returnType;
1132
1293
  }
1133
1294
 
1134
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1135
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1136
1297
 
1137
1298
  // check if this is a prototype function
@@ -1142,11 +1303,16 @@ const getNodeType = (scope, node) => {
1142
1303
  const spl = name.slice(2).split('_');
1143
1304
 
1144
1305
  const func = spl[spl.length - 1];
1145
- 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);
1146
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1147
1308
  }
1148
1309
 
1149
- 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);
1150
1316
 
1151
1317
  // presume
1152
1318
  // todo: warn here?
@@ -1194,6 +1360,15 @@ const getNodeType = (scope, node) => {
1194
1360
 
1195
1361
  if (node.type === 'BinaryExpression') {
1196
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
+
1197
1372
  return TYPES.number;
1198
1373
 
1199
1374
  // todo: string concat types
@@ -1218,7 +1393,7 @@ const getNodeType = (scope, node) => {
1218
1393
  if (node.operator === '!') return TYPES.boolean;
1219
1394
  if (node.operator === 'void') return TYPES.undefined;
1220
1395
  if (node.operator === 'delete') return TYPES.boolean;
1221
- if (node.operator === 'typeof') return TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1222
1397
 
1223
1398
  return TYPES.number;
1224
1399
  }
@@ -1227,11 +1402,23 @@ const getNodeType = (scope, node) => {
1227
1402
  // hack: if something.length, number type
1228
1403
  if (node.property.name === 'length') return TYPES.number;
1229
1404
 
1230
- // 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
1231
1413
  return TYPES.number;
1232
1414
  }
1233
1415
 
1234
- 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);
1235
1422
 
1236
1423
  // presume
1237
1424
  // todo: warn here?
@@ -1244,28 +1431,11 @@ const getNodeType = (scope, node) => {
1244
1431
  return ret;
1245
1432
  };
1246
1433
 
1247
- const toString = (scope, wasm, type) => {
1248
- const tmp = localTmp(scope, '#tostring_tmp');
1249
- return [
1250
- ...wasm,
1251
- [ Opcodes.local_set, tmp ],
1252
-
1253
- ...typeSwitch(scope, type, {
1254
- [TYPES.string]: [
1255
- [ Opcodes.local_get, tmp ]
1256
- ],
1257
- [TYPES.undefined]: [
1258
- // [ Opcodes.]
1259
- ]
1260
- })
1261
- ]
1262
- };
1263
-
1264
1434
  const generateLiteral = (scope, decl, global, name) => {
1265
1435
  if (decl.value === null) return number(NULL);
1266
1436
 
1437
+ // hack: just return 1 for regex literals
1267
1438
  if (decl.regex) {
1268
- scope.regex[name] = decl.regex;
1269
1439
  return number(1);
1270
1440
  }
1271
1441
 
@@ -1278,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
1278
1448
  return number(decl.value ? 1 : 0);
1279
1449
 
1280
1450
  case 'string':
1281
- const str = decl.value;
1282
- const rawElements = new Array(str.length);
1283
- let j = 0;
1284
- for (let i = 0; i < str.length; i++) {
1285
- rawElements[i] = str.charCodeAt(i);
1286
- }
1287
-
1288
- return makeArray(scope, {
1289
- rawElements
1290
- }, global, name, false, 'i16')[0];
1451
+ return makeString(scope, decl.value, global, name);
1291
1452
 
1292
1453
  default:
1293
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1294
1455
  }
1295
1456
  };
1296
1457
 
@@ -1299,6 +1460,8 @@ const countLeftover = wasm => {
1299
1460
 
1300
1461
  for (let i = 0; i < wasm.length; i++) {
1301
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1302
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1303
1466
  if (inst[0] === Opcodes.if) count--;
1304
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1307,18 +1470,25 @@ const countLeftover = wasm => {
1307
1470
  if (inst[0] === Opcodes.end) depth--;
1308
1471
 
1309
1472
  if (depth === 0)
1310
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1311
- 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)) {}
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)) {}
1312
1475
  else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1313
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1476
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1314
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1315
1478
  else if (inst[0] === Opcodes.return) count = 0;
1316
1479
  else if (inst[0] === Opcodes.call) {
1317
1480
  let func = funcs.find(x => x.index === inst[1]);
1318
- if (func) {
1319
- count -= func.params.length;
1320
- } else count--;
1321
- 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
+ }
1322
1492
  } else count--;
1323
1493
 
1324
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1336,7 +1506,7 @@ const disposeLeftover = wasm => {
1336
1506
  const generateExp = (scope, decl) => {
1337
1507
  const expression = decl.expression;
1338
1508
 
1339
- const out = generate(scope, expression);
1509
+ const out = generate(scope, expression, undefined, undefined, true);
1340
1510
  disposeLeftover(out);
1341
1511
 
1342
1512
  return out;
@@ -1394,7 +1564,7 @@ const RTArrayUtil = {
1394
1564
  ]
1395
1565
  };
1396
1566
 
1397
- const generateCall = (scope, decl, _global, _name) => {
1567
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1398
1568
  /* const callee = decl.callee;
1399
1569
  const args = decl.arguments;
1400
1570
 
@@ -1410,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
1410
1580
  name = func.name;
1411
1581
  }
1412
1582
 
1413
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1414
1584
  // literal eval hack
1415
- const code = decl.arguments[0].value;
1416
- 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
+ }
1417
1598
 
1418
1599
  const out = generate(scope, {
1419
1600
  type: 'BlockStatement',
@@ -1427,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1427
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1428
1609
  out.push(
1429
1610
  ...getNodeType(scope, finalStatement),
1430
- setLastType(scope)
1611
+ ...setLastType(scope)
1431
1612
  );
1432
1613
  } else if (countLeftover(out) === 0) {
1433
1614
  out.push(...number(UNDEFINED));
1434
1615
  out.push(
1435
1616
  ...number(TYPES.undefined, Valtype.i32),
1436
- setLastType(scope)
1617
+ ...setLastType(scope)
1437
1618
  );
1438
1619
  }
1439
1620
 
@@ -1455,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name) => {
1455
1636
 
1456
1637
  target = { ...decl.callee };
1457
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1458
1642
  }
1459
1643
 
1460
1644
  // literal.func()
1461
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1462
1646
  // megahack for /regex/.func()
1463
- if (decl.callee.object.regex) {
1464
- const funcName = decl.callee.property.name;
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1465
1649
  const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1466
1650
 
1467
1651
  funcIndex[func.name] = func.index;
@@ -1477,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name) => {
1477
1661
  Opcodes.i32_from_u,
1478
1662
 
1479
1663
  ...number(TYPES.boolean, Valtype.i32),
1480
- setLastType(scope)
1664
+ ...setLastType(scope)
1481
1665
  ];
1482
1666
  }
1483
1667
 
@@ -1502,23 +1686,47 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1686
  // }
1503
1687
 
1504
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
+ name: x
1701
+ },
1702
+ arguments: [ target, ...decl.arguments ],
1703
+ _protoInternalCall: true
1704
+ });
1705
+ }
1706
+ }
1707
+
1505
1708
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1506
- const f = prototypeFuncs[x][protoName];
1507
- if (f) acc[x] = f;
1709
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1508
1710
  return acc;
1509
1711
  }, {});
1510
1712
 
1511
- // no prototype function candidates, ignore
1512
1713
  if (Object.keys(protoCands).length > 0) {
1513
1714
  // use local for cached i32 length as commonly used
1514
1715
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1515
1716
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1516
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1517
1717
 
1518
1718
  // TODO: long-term, prototypes should be their individual separate funcs
1519
1719
 
1720
+ const rawPointer = [
1721
+ ...generate(scope, target),
1722
+ Opcodes.i32_to_u
1723
+ ];
1724
+
1725
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1726
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1727
+
1728
+ let allOptUnused = true;
1520
1729
  let lengthI32CacheUsed = false;
1521
- const protoBC = {};
1522
1730
  for (const x in protoCands) {
1523
1731
  const protoFunc = protoCands[x];
1524
1732
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1526,7 +1734,7 @@ const generateCall = (scope, decl, _global, _name) => {
1526
1734
  ...RTArrayUtil.getLength(getPointer),
1527
1735
 
1528
1736
  ...number(TYPES.number, Valtype.i32),
1529
- setLastType(scope)
1737
+ ...setLastType(scope)
1530
1738
  ];
1531
1739
  continue;
1532
1740
  }
@@ -1536,6 +1744,7 @@ const generateCall = (scope, decl, _global, _name) => {
1536
1744
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1537
1745
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1538
1746
 
1747
+ let optUnused = false;
1539
1748
  const protoOut = protoFunc(getPointer, {
1540
1749
  getCachedI32: () => {
1541
1750
  lengthI32CacheUsed = true;
@@ -1550,23 +1759,30 @@ const generateCall = (scope, decl, _global, _name) => {
1550
1759
  return makeArray(scope, {
1551
1760
  rawElements: new Array(length)
1552
1761
  }, _global, _name, true, itemType);
1762
+ }, () => {
1763
+ optUnused = true;
1764
+ return unusedValue;
1553
1765
  });
1554
1766
 
1767
+ if (!optUnused) allOptUnused = false;
1768
+
1555
1769
  protoBC[x] = [
1556
- [ Opcodes.block, valtypeBinary ],
1770
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1557
1771
  ...protoOut,
1558
1772
 
1559
1773
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1560
- setLastType(scope),
1774
+ ...setLastType(scope),
1561
1775
  [ Opcodes.end ]
1562
1776
  ];
1563
1777
  }
1564
1778
 
1565
- return [
1566
- ...generate(scope, target),
1779
+ // todo: if some cands use optUnused and some don't, we will probably crash
1567
1780
 
1568
- Opcodes.i32_to_u,
1569
- [ Opcodes.local_set, pointerLocal ],
1781
+ return [
1782
+ ...(usePointerCache ? [
1783
+ ...rawPointer,
1784
+ [ Opcodes.local_set, pointerLocal ],
1785
+ ] : []),
1570
1786
 
1571
1787
  ...(!lengthI32CacheUsed ? [] : [
1572
1788
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1578,13 +1794,22 @@ const generateCall = (scope, decl, _global, _name) => {
1578
1794
 
1579
1795
  // TODO: error better
1580
1796
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1581
- }, valtypeBinary),
1797
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1582
1798
  ];
1583
1799
  }
1800
+
1801
+ if (Object.keys(protoBC).length > 0) {
1802
+ return typeSwitch(scope, getNodeType(scope, target), {
1803
+ ...protoBC,
1804
+
1805
+ // TODO: error better
1806
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1807
+ }, valtypeBinary);
1808
+ }
1584
1809
  }
1585
1810
 
1586
1811
  // TODO: only allows callee as literal
1587
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1812
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1588
1813
 
1589
1814
  let idx = funcIndex[name] ?? importedFuncs[name];
1590
1815
  if (idx === undefined && builtinFuncs[name]) {
@@ -1617,16 +1842,68 @@ const generateCall = (scope, decl, _global, _name) => {
1617
1842
  idx = -1;
1618
1843
  }
1619
1844
 
1845
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1846
+ const wasmOps = {
1847
+ // pointer, align, offset
1848
+ i32_load: { imms: 2, args: 1, returns: 1 },
1849
+ // pointer, value, align, offset
1850
+ i32_store: { imms: 2, args: 2, returns: 0 },
1851
+ // pointer, align, offset
1852
+ i32_load8_u: { imms: 2, args: 1, returns: 1 },
1853
+ // pointer, value, align, offset
1854
+ i32_store8: { imms: 2, args: 2, returns: 0 },
1855
+ // pointer, align, offset
1856
+ i32_load16_u: { imms: 2, args: 1, returns: 1 },
1857
+ // pointer, value, align, offset
1858
+ i32_store16: { imms: 2, args: 2, returns: 0 },
1859
+
1860
+ // pointer, align, offset
1861
+ f64_load: { imms: 2, args: 1, returns: 1 },
1862
+ // pointer, value, align, offset
1863
+ f64_store: { imms: 2, args: 2, returns: 0 },
1864
+
1865
+ // value
1866
+ i32_const: { imms: 1, args: 0, returns: 1 },
1867
+
1868
+ // a, b
1869
+ i32_or: { imms: 0, args: 2, returns: 1 },
1870
+
1871
+ add: { imms: 0, args: 2, returns: 1 },
1872
+ i32_to_u: { imms: 0, args: 1, returns: 1 }
1873
+ };
1874
+
1875
+ const opName = name.slice('__Porffor_wasm_'.length);
1876
+
1877
+ if (wasmOps[opName]) {
1878
+ const op = wasmOps[opName];
1879
+
1880
+ const argOut = [];
1881
+ for (let i = 0; i < op.args; i++) argOut.push(
1882
+ ...generate(scope, decl.arguments[i]),
1883
+ Opcodes.i32_to
1884
+ );
1885
+
1886
+ // literals only
1887
+ const imms = decl.arguments.slice(op.args).map(x => x.value);
1888
+
1889
+ return [
1890
+ ...argOut,
1891
+ [ Opcodes[opName], ...imms ],
1892
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1893
+ ];
1894
+ }
1895
+ }
1896
+
1620
1897
  if (idx === undefined) {
1621
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1622
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1898
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1899
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1623
1900
  }
1624
1901
 
1625
1902
  const func = funcs.find(x => x.index === idx);
1626
1903
 
1627
1904
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1628
1905
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1629
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1906
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1630
1907
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1631
1908
 
1632
1909
  let args = decl.arguments;
@@ -1643,14 +1920,24 @@ const generateCall = (scope, decl, _global, _name) => {
1643
1920
  if (func && func.throws) scope.throws = true;
1644
1921
 
1645
1922
  let out = [];
1646
- for (const arg of args) {
1923
+ for (let i = 0; i < args.length; i++) {
1924
+ const arg = args[i];
1647
1925
  out = out.concat(generate(scope, arg));
1926
+
1927
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1928
+ out.push(Opcodes.i32_to);
1929
+ }
1930
+
1931
+ if (importedFuncs[name] && name.startsWith('profile')) {
1932
+ out.push(Opcodes.i32_to);
1933
+ }
1934
+
1648
1935
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1649
1936
  }
1650
1937
 
1651
1938
  out.push([ Opcodes.call, idx ]);
1652
1939
 
1653
- if (!typedReturn) {
1940
+ if (!typedReturns) {
1654
1941
  // let type;
1655
1942
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1656
1943
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1660,7 +1947,11 @@ const generateCall = (scope, decl, _global, _name) => {
1660
1947
  // ...number(type, Valtype.i32),
1661
1948
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1662
1949
  // );
1663
- } else out.push(setLastType(scope));
1950
+ } else out.push(...setLastType(scope));
1951
+
1952
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1953
+ out.push(Opcodes.i32_from);
1954
+ }
1664
1955
 
1665
1956
  return out;
1666
1957
  };
@@ -1668,8 +1959,21 @@ const generateCall = (scope, decl, _global, _name) => {
1668
1959
  const generateNew = (scope, decl, _global, _name) => {
1669
1960
  // hack: basically treat this as a normal call for builtins for now
1670
1961
  const name = mapName(decl.callee.name);
1962
+
1671
1963
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1672
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1964
+
1965
+ if (builtinFuncs[name + '$constructor']) {
1966
+ // custom ...$constructor override builtin func
1967
+ return generateCall(scope, {
1968
+ ...decl,
1969
+ callee: {
1970
+ type: 'Identifier',
1971
+ name: name + '$constructor'
1972
+ }
1973
+ }, _global, _name);
1974
+ }
1975
+
1976
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1673
1977
 
1674
1978
  return generateCall(scope, decl, _global, _name);
1675
1979
  };
@@ -1786,12 +2090,14 @@ const brTable = (input, bc, returns) => {
1786
2090
  };
1787
2091
 
1788
2092
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2093
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2094
+
1789
2095
  const known = knownType(scope, type);
1790
2096
  if (known != null) {
1791
2097
  return bc[known] ?? bc.default;
1792
2098
  }
1793
2099
 
1794
- if (process.argv.includes('-typeswitch-use-brtable'))
2100
+ if (Prefs.typeswitchUseBrtable)
1795
2101
  return brTable(type, bc, returns);
1796
2102
 
1797
2103
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1801,8 +2107,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1801
2107
  [ Opcodes.block, returns ]
1802
2108
  ];
1803
2109
 
1804
- // todo: use br_table?
1805
-
1806
2110
  for (const x in bc) {
1807
2111
  if (x === 'default') continue;
1808
2112
 
@@ -1826,7 +2130,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1826
2130
  return out;
1827
2131
  };
1828
2132
 
1829
- const allocVar = (scope, name, global = false) => {
2133
+ const allocVar = (scope, name, global = false, type = true) => {
1830
2134
  const target = global ? globals : scope.locals;
1831
2135
 
1832
2136
  // already declared
@@ -1840,8 +2144,10 @@ const allocVar = (scope, name, global = false) => {
1840
2144
  let idx = global ? globalInd++ : scope.localInd++;
1841
2145
  target[name] = { idx, type: valtypeBinary };
1842
2146
 
1843
- let typeIdx = global ? globalInd++ : scope.localInd++;
1844
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2147
+ if (type) {
2148
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2149
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2150
+ }
1845
2151
 
1846
2152
  return idx;
1847
2153
  };
@@ -1856,11 +2162,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1856
2162
  };
1857
2163
 
1858
2164
  const typeAnnoToPorfType = x => {
1859
- if (TYPES[x]) return TYPES[x];
1860
- if (TYPES['_' + x]) return TYPES['_' + x];
2165
+ if (!x) return null;
2166
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2167
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1861
2168
 
1862
2169
  switch (x) {
1863
2170
  case 'i32':
2171
+ case 'i64':
2172
+ case 'f64':
1864
2173
  return TYPES.number;
1865
2174
  }
1866
2175
 
@@ -1871,7 +2180,7 @@ const extractTypeAnnotation = decl => {
1871
2180
  let a = decl;
1872
2181
  while (a.typeAnnotation) a = a.typeAnnotation;
1873
2182
 
1874
- let type, elementType;
2183
+ let type = null, elementType = null;
1875
2184
  if (a.typeName) {
1876
2185
  type = a.typeName.name;
1877
2186
  } else if (a.type.endsWith('Keyword')) {
@@ -1884,6 +2193,8 @@ const extractTypeAnnotation = decl => {
1884
2193
  const typeName = type;
1885
2194
  type = typeAnnoToPorfType(type);
1886
2195
 
2196
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2197
+
1887
2198
  // if (decl.name) console.log(decl.name, { type, elementType });
1888
2199
 
1889
2200
  return { type, typeName, elementType };
@@ -1900,6 +2211,8 @@ const generateVar = (scope, decl) => {
1900
2211
  for (const x of decl.declarations) {
1901
2212
  const name = mapName(x.id.name);
1902
2213
 
2214
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2215
+
1903
2216
  if (x.init && isFuncType(x.init.type)) {
1904
2217
  // hack for let a = function () { ... }
1905
2218
  x.init.id = { name };
@@ -1915,9 +2228,10 @@ const generateVar = (scope, decl) => {
1915
2228
  continue; // always ignore
1916
2229
  }
1917
2230
 
1918
- let idx = allocVar(scope, name, global);
2231
+ const typed = typedInput && x.id.typeAnnotation;
2232
+ let idx = allocVar(scope, name, global, !typed);
1919
2233
 
1920
- if (typedInput && x.id.typeAnnotation) {
2234
+ if (typed) {
1921
2235
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1922
2236
  }
1923
2237
 
@@ -1935,7 +2249,8 @@ const generateVar = (scope, decl) => {
1935
2249
  return out;
1936
2250
  };
1937
2251
 
1938
- const generateAssign = (scope, decl) => {
2252
+ // todo: optimize this func for valueUnused
2253
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1939
2254
  const { type, name } = decl.left;
1940
2255
 
1941
2256
  if (type === 'ObjectPattern') {
@@ -1953,9 +2268,9 @@ const generateAssign = (scope, decl) => {
1953
2268
  // hack: .length setter
1954
2269
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1955
2270
  const name = decl.left.object.name;
1956
- const pointer = arrays.get(name);
2271
+ const pointer = scope.arrays?.get(name);
1957
2272
 
1958
- const aotPointer = pointer != null;
2273
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1959
2274
 
1960
2275
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
1961
2276
 
@@ -1980,9 +2295,9 @@ const generateAssign = (scope, decl) => {
1980
2295
  // arr[i]
1981
2296
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1982
2297
  const name = decl.left.object.name;
1983
- const pointer = arrays.get(name);
2298
+ const pointer = scope.arrays?.get(name);
1984
2299
 
1985
- const aotPointer = pointer != null;
2300
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1986
2301
 
1987
2302
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1988
2303
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2038,6 +2353,8 @@ const generateAssign = (scope, decl) => {
2038
2353
  ];
2039
2354
  }
2040
2355
 
2356
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2357
+
2041
2358
  const [ local, isGlobal ] = lookupName(scope, name);
2042
2359
 
2043
2360
  if (local === undefined) {
@@ -2084,9 +2401,7 @@ const generateAssign = (scope, decl) => {
2084
2401
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2085
2402
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2086
2403
 
2087
- getLastType(scope),
2088
- // hack: type is idx+1
2089
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2404
+ ...setType(scope, name, getLastType(scope))
2090
2405
  ];
2091
2406
  }
2092
2407
 
@@ -2097,9 +2412,7 @@ const generateAssign = (scope, decl) => {
2097
2412
 
2098
2413
  // todo: string concat types
2099
2414
 
2100
- // hack: type is idx+1
2101
- ...number(TYPES.number, Valtype.i32),
2102
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2415
+ ...setType(scope, name, TYPES.number)
2103
2416
  ];
2104
2417
  };
2105
2418
 
@@ -2145,7 +2458,7 @@ const generateUnary = (scope, decl) => {
2145
2458
  return out;
2146
2459
  }
2147
2460
 
2148
- case 'delete':
2461
+ case 'delete': {
2149
2462
  let toReturn = true, toGenerate = true;
2150
2463
 
2151
2464
  if (decl.argument.type === 'Identifier') {
@@ -2167,38 +2480,60 @@ const generateUnary = (scope, decl) => {
2167
2480
 
2168
2481
  out.push(...number(toReturn ? 1 : 0));
2169
2482
  return out;
2483
+ }
2170
2484
 
2171
- case 'typeof':
2172
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2485
+ case 'typeof': {
2486
+ let overrideType, toGenerate = true;
2487
+
2488
+ if (decl.argument.type === 'Identifier') {
2489
+ const out = generateIdent(scope, decl.argument);
2490
+
2491
+ // if ReferenceError (undeclared var), ignore and return undefined
2492
+ if (out[1]) {
2493
+ // does not exist (2 ops from throw)
2494
+ overrideType = number(TYPES.undefined, Valtype.i32);
2495
+ toGenerate = false;
2496
+ }
2497
+ }
2498
+
2499
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2500
+ disposeLeftover(out);
2501
+
2502
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2173
2503
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2174
2504
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2175
2505
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2176
2506
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2177
2507
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2178
2508
 
2509
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2510
+
2179
2511
  // object and internal types
2180
2512
  default: makeString(scope, 'object', false, '#typeof_result'),
2181
- });
2513
+ }));
2514
+
2515
+ return out;
2516
+ }
2182
2517
 
2183
2518
  default:
2184
- return todo(`unary operator ${decl.operator} not implemented yet`);
2519
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2185
2520
  }
2186
2521
  };
2187
2522
 
2188
- const generateUpdate = (scope, decl) => {
2523
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2189
2524
  const { name } = decl.argument;
2190
2525
 
2191
2526
  const [ local, isGlobal ] = lookupName(scope, name);
2192
2527
 
2193
2528
  if (local === undefined) {
2194
- return todo(`update expression with undefined variable`);
2529
+ return todo(scope, `update expression with undefined variable`, true);
2195
2530
  }
2196
2531
 
2197
2532
  const idx = local.idx;
2198
2533
  const out = [];
2199
2534
 
2200
2535
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2201
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2536
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2202
2537
 
2203
2538
  switch (decl.operator) {
2204
2539
  case '++':
@@ -2211,7 +2546,7 @@ const generateUpdate = (scope, decl) => {
2211
2546
  }
2212
2547
 
2213
2548
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2214
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2549
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2215
2550
 
2216
2551
  return out;
2217
2552
  };
@@ -2251,7 +2586,7 @@ const generateConditional = (scope, decl) => {
2251
2586
  // note type
2252
2587
  out.push(
2253
2588
  ...getNodeType(scope, decl.consequent),
2254
- setLastType(scope)
2589
+ ...setLastType(scope)
2255
2590
  );
2256
2591
 
2257
2592
  out.push([ Opcodes.else ]);
@@ -2260,7 +2595,7 @@ const generateConditional = (scope, decl) => {
2260
2595
  // note type
2261
2596
  out.push(
2262
2597
  ...getNodeType(scope, decl.alternate),
2263
- setLastType(scope)
2598
+ ...setLastType(scope)
2264
2599
  );
2265
2600
 
2266
2601
  out.push([ Opcodes.end ]);
@@ -2274,15 +2609,17 @@ const generateFor = (scope, decl) => {
2274
2609
  const out = [];
2275
2610
 
2276
2611
  if (decl.init) {
2277
- out.push(...generate(scope, decl.init));
2612
+ out.push(...generate(scope, decl.init, false, undefined, true));
2278
2613
  disposeLeftover(out);
2279
2614
  }
2280
2615
 
2281
2616
  out.push([ Opcodes.loop, Blocktype.void ]);
2282
2617
  depth.push('for');
2283
2618
 
2284
- out.push(...generate(scope, decl.test));
2285
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2619
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2620
+ else out.push(...number(1, Valtype.i32));
2621
+
2622
+ out.push([ Opcodes.if, Blocktype.void ]);
2286
2623
  depth.push('if');
2287
2624
 
2288
2625
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2290,8 +2627,7 @@ const generateFor = (scope, decl) => {
2290
2627
  out.push(...generate(scope, decl.body));
2291
2628
  out.push([ Opcodes.end ]);
2292
2629
 
2293
- out.push(...generate(scope, decl.update));
2294
- depth.pop();
2630
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2295
2631
 
2296
2632
  out.push([ Opcodes.br, 1 ]);
2297
2633
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2319,6 +2655,36 @@ const generateWhile = (scope, decl) => {
2319
2655
  return out;
2320
2656
  };
2321
2657
 
2658
+ const generateDoWhile = (scope, decl) => {
2659
+ const out = [];
2660
+
2661
+ out.push([ Opcodes.loop, Blocktype.void ]);
2662
+ depth.push('dowhile');
2663
+
2664
+ // block for break (includes all)
2665
+ out.push([ Opcodes.block, Blocktype.void ]);
2666
+ depth.push('block');
2667
+
2668
+ // block for continue
2669
+ // includes body but not test+loop so we can exit body at anytime
2670
+ // and still test+loop after
2671
+ out.push([ Opcodes.block, Blocktype.void ]);
2672
+ depth.push('block');
2673
+
2674
+ out.push(...generate(scope, decl.body));
2675
+
2676
+ out.push([ Opcodes.end ]);
2677
+ depth.pop();
2678
+
2679
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2680
+ out.push([ Opcodes.br_if, 1 ]);
2681
+
2682
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2683
+ depth.pop(); depth.pop();
2684
+
2685
+ return out;
2686
+ };
2687
+
2322
2688
  const generateForOf = (scope, decl) => {
2323
2689
  const out = [];
2324
2690
 
@@ -2348,8 +2714,17 @@ const generateForOf = (scope, decl) => {
2348
2714
  // setup local for left
2349
2715
  generate(scope, decl.left);
2350
2716
 
2351
- const leftName = decl.left.declarations[0].id.name;
2717
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2718
+ if (!leftName && decl.left.name) {
2719
+ leftName = decl.left.name;
2720
+
2721
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2722
+ }
2723
+
2724
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2725
+
2352
2726
  const [ local, isGlobal ] = lookupName(scope, leftName);
2727
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2353
2728
 
2354
2729
  depth.push('block');
2355
2730
  depth.push('block');
@@ -2357,13 +2732,15 @@ const generateForOf = (scope, decl) => {
2357
2732
  // // todo: we should only do this for strings but we don't know at compile-time :(
2358
2733
  // hack: this is naughty and will break things!
2359
2734
  let newOut = number(0, Valtype.f64), newPointer = -1;
2360
- if (pages.hasString) {
2735
+ if (pages.hasAnyString) {
2736
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2361
2737
  0, [ newOut, newPointer ] = makeArray(scope, {
2362
2738
  rawElements: new Array(1)
2363
2739
  }, isGlobal, leftName, true, 'i16');
2364
2740
  }
2365
2741
 
2366
2742
  // set type for local
2743
+ // todo: optimize away counter and use end pointer
2367
2744
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2368
2745
  [TYPES._array]: [
2369
2746
  ...setType(scope, leftName, TYPES.number),
@@ -2448,6 +2825,56 @@ const generateForOf = (scope, decl) => {
2448
2825
  [ Opcodes.end ],
2449
2826
  [ Opcodes.end ]
2450
2827
  ],
2828
+ [TYPES._bytestring]: [
2829
+ ...setType(scope, leftName, TYPES._bytestring),
2830
+
2831
+ [ Opcodes.loop, Blocktype.void ],
2832
+
2833
+ // setup new/out array
2834
+ ...newOut,
2835
+ [ Opcodes.drop ],
2836
+
2837
+ ...number(0, Valtype.i32), // base 0 for store after
2838
+
2839
+ // load current string ind {arg}
2840
+ [ Opcodes.local_get, pointer ],
2841
+ [ Opcodes.local_get, counter ],
2842
+ [ Opcodes.i32_add ],
2843
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2844
+
2845
+ // store to new string ind 0
2846
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2847
+
2848
+ // return new string (page)
2849
+ ...number(newPointer),
2850
+
2851
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2852
+
2853
+ [ Opcodes.block, Blocktype.void ],
2854
+ [ Opcodes.block, Blocktype.void ],
2855
+ ...generate(scope, decl.body),
2856
+ [ Opcodes.end ],
2857
+
2858
+ // increment iter pointer
2859
+ // [ Opcodes.local_get, pointer ],
2860
+ // ...number(1, Valtype.i32),
2861
+ // [ Opcodes.i32_add ],
2862
+ // [ Opcodes.local_set, pointer ],
2863
+
2864
+ // increment counter by 1
2865
+ [ Opcodes.local_get, counter ],
2866
+ ...number(1, Valtype.i32),
2867
+ [ Opcodes.i32_add ],
2868
+ [ Opcodes.local_tee, counter ],
2869
+
2870
+ // loop if counter != length
2871
+ [ Opcodes.local_get, length ],
2872
+ [ Opcodes.i32_ne ],
2873
+ [ Opcodes.br_if, 1 ],
2874
+
2875
+ [ Opcodes.end ],
2876
+ [ Opcodes.end ]
2877
+ ],
2451
2878
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2452
2879
  }, Blocktype.void));
2453
2880
 
@@ -2458,28 +2885,65 @@ const generateForOf = (scope, decl) => {
2458
2885
  return out;
2459
2886
  };
2460
2887
 
2888
+ // find the nearest loop in depth map by type
2461
2889
  const getNearestLoop = () => {
2462
2890
  for (let i = depth.length - 1; i >= 0; i--) {
2463
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2891
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2464
2892
  }
2465
2893
 
2466
2894
  return -1;
2467
2895
  };
2468
2896
 
2469
2897
  const generateBreak = (scope, decl) => {
2470
- const nearestLoop = depth.length - getNearestLoop();
2898
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2899
+ const type = depth[target];
2900
+
2901
+ // different loop types have different branch offsets
2902
+ // as they have different wasm block/loop/if structures
2903
+ // we need to use the right offset by type to branch to the one we want
2904
+ // for a break: exit the loop without executing anything else inside it
2905
+ const offset = ({
2906
+ for: 2, // loop > if (wanted branch) > block (we are here)
2907
+ while: 2, // loop > if (wanted branch) (we are here)
2908
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2909
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2910
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2911
+ })[type];
2912
+
2471
2913
  return [
2472
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2914
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2473
2915
  ];
2474
2916
  };
2475
2917
 
2476
2918
  const generateContinue = (scope, decl) => {
2477
- const nearestLoop = depth.length - getNearestLoop();
2919
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2920
+ const type = depth[target];
2921
+
2922
+ // different loop types have different branch offsets
2923
+ // as they have different wasm block/loop/if structures
2924
+ // we need to use the right offset by type to branch to the one we want
2925
+ // for a continue: do test for the loop, and then loop depending on that success
2926
+ const offset = ({
2927
+ for: 3, // loop (wanted branch) > if > block (we are here)
2928
+ while: 1, // loop (wanted branch) > if (we are here)
2929
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2930
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2931
+ })[type];
2932
+
2478
2933
  return [
2479
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2934
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2480
2935
  ];
2481
2936
  };
2482
2937
 
2938
+ const generateLabel = (scope, decl) => {
2939
+ scope.labels ??= new Map();
2940
+
2941
+ const name = decl.label.name;
2942
+ scope.labels.set(name, depth.length);
2943
+
2944
+ return generate(scope, decl.body);
2945
+ };
2946
+
2483
2947
  const generateThrow = (scope, decl) => {
2484
2948
  scope.throws = true;
2485
2949
 
@@ -2488,7 +2952,7 @@ const generateThrow = (scope, decl) => {
2488
2952
  // hack: throw new X("...") -> throw "..."
2489
2953
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2490
2954
  constructor = decl.argument.callee.name;
2491
- message = decl.argument.arguments[0].value;
2955
+ message = decl.argument.arguments[0]?.value ?? '';
2492
2956
  }
2493
2957
 
2494
2958
  if (tags.length === 0) tags.push({
@@ -2500,6 +2964,9 @@ const generateThrow = (scope, decl) => {
2500
2964
  let exceptId = exceptions.push({ constructor, message }) - 1;
2501
2965
  let tagIdx = tags[0].idx;
2502
2966
 
2967
+ scope.exceptions ??= [];
2968
+ scope.exceptions.push(exceptId);
2969
+
2503
2970
  // todo: write a description of how this works lol
2504
2971
 
2505
2972
  return [
@@ -2509,7 +2976,7 @@ const generateThrow = (scope, decl) => {
2509
2976
  };
2510
2977
 
2511
2978
  const generateTry = (scope, decl) => {
2512
- if (decl.finalizer) return todo('try finally not implemented yet');
2979
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2513
2980
 
2514
2981
  const out = [];
2515
2982
 
@@ -2540,29 +3007,35 @@ const generateAssignPat = (scope, decl) => {
2540
3007
  // TODO
2541
3008
  // if identifier declared, use that
2542
3009
  // else, use default (right)
2543
- return todo('assignment pattern (optional arg)');
3010
+ return todo(scope, 'assignment pattern (optional arg)');
2544
3011
  };
2545
3012
 
2546
3013
  let pages = new Map();
2547
- const allocPage = (reason, type) => {
3014
+ const allocPage = (scope, reason, type) => {
2548
3015
  if (pages.has(reason)) return pages.get(reason).ind;
2549
3016
 
2550
3017
  if (reason.startsWith('array:')) pages.hasArray = true;
2551
3018
  if (reason.startsWith('string:')) pages.hasString = true;
3019
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3020
+ if (reason.includes('string:')) pages.hasAnyString = true;
2552
3021
 
2553
3022
  const ind = pages.size;
2554
3023
  pages.set(reason, { ind, type });
2555
3024
 
2556
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3025
+ scope.pages ??= new Map();
3026
+ scope.pages.set(reason, { ind, type });
3027
+
3028
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2557
3029
 
2558
3030
  return ind;
2559
3031
  };
2560
3032
 
3033
+ // todo: add scope.pages
2561
3034
  const freePage = reason => {
2562
3035
  const { ind } = pages.get(reason);
2563
3036
  pages.delete(reason);
2564
3037
 
2565
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3038
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2566
3039
 
2567
3040
  return ind;
2568
3041
  };
@@ -2582,38 +3055,51 @@ const StoreOps = {
2582
3055
  f64: Opcodes.f64_store,
2583
3056
 
2584
3057
  // expects i32 input!
2585
- i16: Opcodes.i32_store16
3058
+ i8: Opcodes.i32_store8,
3059
+ i16: Opcodes.i32_store16,
2586
3060
  };
2587
3061
 
2588
3062
  let data = [];
2589
3063
 
2590
- const compileBytes = (val, itemType, signed = true) => {
3064
+ const compileBytes = (val, itemType) => {
2591
3065
  // todo: this is a mess and needs confirming / ????
2592
3066
  switch (itemType) {
2593
3067
  case 'i8': return [ val % 256 ];
2594
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2595
-
2596
- case 'i32':
2597
- case 'i64':
2598
- return enforceFourBytes(signedLEB128(val));
3068
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3069
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3070
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3071
+ // todo: i64
2599
3072
 
2600
3073
  case 'f64': return ieee754_binary64(val);
2601
3074
  }
2602
3075
  };
2603
3076
 
3077
+ const getAllocType = itemType => {
3078
+ switch (itemType) {
3079
+ case 'i8': return 'bytestring';
3080
+ case 'i16': return 'string';
3081
+
3082
+ default: return 'array';
3083
+ }
3084
+ };
3085
+
2604
3086
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2605
3087
  const out = [];
2606
3088
 
3089
+ scope.arrays ??= new Map();
3090
+
2607
3091
  let firstAssign = false;
2608
- if (!arrays.has(name) || name === '$undeclared') {
3092
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2609
3093
  firstAssign = true;
2610
3094
 
2611
3095
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2612
3096
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2613
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3097
+
3098
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3099
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2614
3100
  }
2615
3101
 
2616
- const pointer = arrays.get(name);
3102
+ const pointer = scope.arrays.get(name);
2617
3103
 
2618
3104
  const useRawElements = !!decl.rawElements;
2619
3105
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2621,19 +3107,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2621
3107
  const valtype = itemTypeToValtype[itemType];
2622
3108
  const length = elements.length;
2623
3109
 
2624
- if (firstAssign && useRawElements) {
2625
- let bytes = compileBytes(length, 'i32');
3110
+ if (firstAssign && useRawElements && !Prefs.noData) {
3111
+ // if length is 0 memory/data will just be 0000... anyway
3112
+ if (length !== 0) {
3113
+ let bytes = compileBytes(length, 'i32');
2626
3114
 
2627
- if (!initEmpty) for (let i = 0; i < length; i++) {
2628
- if (elements[i] == null) continue;
3115
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3116
+ if (elements[i] == null) continue;
2629
3117
 
2630
- bytes.push(...compileBytes(elements[i], itemType));
2631
- }
3118
+ bytes.push(...compileBytes(elements[i], itemType));
3119
+ }
2632
3120
 
2633
- data.push({
2634
- offset: pointer,
2635
- bytes
2636
- });
3121
+ const ind = data.push({
3122
+ offset: pointer,
3123
+ bytes
3124
+ }) - 1;
3125
+
3126
+ scope.data ??= [];
3127
+ scope.data.push(ind);
3128
+ }
2637
3129
 
2638
3130
  // local value as pointer
2639
3131
  out.push(...number(pointer));
@@ -2656,7 +3148,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2656
3148
  out.push(
2657
3149
  ...number(0, Valtype.i32),
2658
3150
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2659
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3151
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2660
3152
  );
2661
3153
  }
2662
3154
 
@@ -2666,31 +3158,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2666
3158
  return [ out, pointer ];
2667
3159
  };
2668
3160
 
2669
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3161
+ const byteStringable = str => {
3162
+ if (!Prefs.bytestring) return false;
3163
+
3164
+ for (let i = 0; i < str.length; i++) {
3165
+ if (str.charCodeAt(i) > 0xFF) return false;
3166
+ }
3167
+
3168
+ return true;
3169
+ };
3170
+
3171
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2670
3172
  const rawElements = new Array(str.length);
3173
+ let byteStringable = Prefs.bytestring;
2671
3174
  for (let i = 0; i < str.length; i++) {
2672
- rawElements[i] = str.charCodeAt(i);
3175
+ const c = str.charCodeAt(i);
3176
+ rawElements[i] = c;
3177
+
3178
+ if (byteStringable && c > 0xFF) byteStringable = false;
2673
3179
  }
2674
3180
 
3181
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3182
+
2675
3183
  return makeArray(scope, {
2676
3184
  rawElements
2677
- }, global, name, false, 'i16')[0];
3185
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2678
3186
  };
2679
3187
 
2680
- let arrays = new Map();
2681
3188
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2682
3189
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2683
3190
  };
2684
3191
 
2685
3192
  export const generateMember = (scope, decl, _global, _name) => {
2686
3193
  const name = decl.object.name;
2687
- const pointer = arrays.get(name);
3194
+ const pointer = scope.arrays?.get(name);
2688
3195
 
2689
- const aotPointer = pointer != null;
3196
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2690
3197
 
2691
3198
  // hack: .length
2692
3199
  if (decl.property.name === 'length') {
2693
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3200
+ const func = funcs.find(x => x.name === name);
3201
+ if (func) {
3202
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3203
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3204
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3205
+ }
3206
+
3207
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3208
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3209
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3210
+
2694
3211
  return [
2695
3212
  ...(aotPointer ? number(0, Valtype.i32) : [
2696
3213
  ...generate(scope, decl.object),
@@ -2702,10 +3219,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2702
3219
  ];
2703
3220
  }
2704
3221
 
3222
+ const object = generate(scope, decl.object);
3223
+ const property = generate(scope, decl.property);
3224
+
2705
3225
  // // todo: we should only do this for strings but we don't know at compile-time :(
2706
3226
  // hack: this is naughty and will break things!
2707
3227
  let newOut = number(0, valtypeBinary), newPointer = -1;
2708
- if (pages.hasString) {
3228
+ if (pages.hasAnyString) {
2709
3229
  0, [ newOut, newPointer ] = makeArray(scope, {
2710
3230
  rawElements: new Array(1)
2711
3231
  }, _global, _name, true, 'i16');
@@ -2714,7 +3234,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2714
3234
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2715
3235
  [TYPES._array]: [
2716
3236
  // get index as valtype
2717
- ...generate(scope, decl.property),
3237
+ ...property,
2718
3238
 
2719
3239
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2720
3240
  Opcodes.i32_to_u,
@@ -2722,7 +3242,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
3242
  [ Opcodes.i32_mul ],
2723
3243
 
2724
3244
  ...(aotPointer ? [] : [
2725
- ...generate(scope, decl.object),
3245
+ ...object,
2726
3246
  Opcodes.i32_to_u,
2727
3247
  [ Opcodes.i32_add ]
2728
3248
  ]),
@@ -2731,7 +3251,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2731
3251
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2732
3252
 
2733
3253
  ...number(TYPES.number, Valtype.i32),
2734
- setLastType(scope)
3254
+ ...setLastType(scope)
2735
3255
  ],
2736
3256
 
2737
3257
  [TYPES.string]: [
@@ -2741,14 +3261,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2741
3261
 
2742
3262
  ...number(0, Valtype.i32), // base 0 for store later
2743
3263
 
2744
- ...generate(scope, decl.property),
2745
-
3264
+ ...property,
2746
3265
  Opcodes.i32_to_u,
3266
+
2747
3267
  ...number(ValtypeSize.i16, Valtype.i32),
2748
3268
  [ Opcodes.i32_mul ],
2749
3269
 
2750
3270
  ...(aotPointer ? [] : [
2751
- ...generate(scope, decl.object),
3271
+ ...object,
2752
3272
  Opcodes.i32_to_u,
2753
3273
  [ Opcodes.i32_add ]
2754
3274
  ]),
@@ -2763,10 +3283,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2763
3283
  ...number(newPointer),
2764
3284
 
2765
3285
  ...number(TYPES.string, Valtype.i32),
2766
- setLastType(scope)
3286
+ ...setLastType(scope)
3287
+ ],
3288
+ [TYPES._bytestring]: [
3289
+ // setup new/out array
3290
+ ...newOut,
3291
+ [ Opcodes.drop ],
3292
+
3293
+ ...number(0, Valtype.i32), // base 0 for store later
3294
+
3295
+ ...property,
3296
+ Opcodes.i32_to_u,
3297
+
3298
+ ...(aotPointer ? [] : [
3299
+ ...object,
3300
+ Opcodes.i32_to_u,
3301
+ [ Opcodes.i32_add ]
3302
+ ]),
3303
+
3304
+ // load current string ind {arg}
3305
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3306
+
3307
+ // store to new string ind 0
3308
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3309
+
3310
+ // return new string (page)
3311
+ ...number(newPointer),
3312
+
3313
+ ...number(TYPES._bytestring, Valtype.i32),
3314
+ ...setLastType(scope)
2767
3315
  ],
2768
3316
 
2769
- default: [ [ Opcodes.unreachable ] ]
3317
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2770
3318
  });
2771
3319
  };
2772
3320
 
@@ -2776,25 +3324,36 @@ const objectHack = node => {
2776
3324
  if (!node) return node;
2777
3325
 
2778
3326
  if (node.type === 'MemberExpression') {
2779
- if (node.computed || node.optional) return node;
3327
+ const out = (() => {
3328
+ if (node.computed || node.optional) return;
2780
3329
 
2781
- let objectName = node.object.name;
3330
+ let objectName = node.object.name;
2782
3331
 
2783
- // if object is not identifier or another member exp, give up
2784
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3332
+ // if object is not identifier or another member exp, give up
3333
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3334
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2785
3335
 
2786
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3336
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2787
3337
 
2788
- // if .length, give up (hack within a hack!)
2789
- if (node.property.name === 'length') return node;
3338
+ // if .length, give up (hack within a hack!)
3339
+ if (node.property.name === 'length') {
3340
+ node.object = objectHack(node.object);
3341
+ return;
3342
+ }
2790
3343
 
2791
- const name = '__' + objectName + '_' + node.property.name;
2792
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3344
+ // no object name, give up
3345
+ if (!objectName) return;
2793
3346
 
2794
- return {
2795
- type: 'Identifier',
2796
- name
2797
- };
3347
+ const name = '__' + objectName + '_' + node.property.name;
3348
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3349
+
3350
+ return {
3351
+ type: 'Identifier',
3352
+ name
3353
+ };
3354
+ })();
3355
+
3356
+ if (out) return out;
2798
3357
  }
2799
3358
 
2800
3359
  for (const x in node) {
@@ -2808,8 +3367,8 @@ const objectHack = node => {
2808
3367
  };
2809
3368
 
2810
3369
  const generateFunc = (scope, decl) => {
2811
- if (decl.async) return todo('async functions are not supported');
2812
- if (decl.generator) return todo('generator functions are not supported');
3370
+ if (decl.async) return todo(scope, 'async functions are not supported');
3371
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2813
3372
 
2814
3373
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2815
3374
  const params = decl.params ?? [];
@@ -2825,6 +3384,11 @@ const generateFunc = (scope, decl) => {
2825
3384
  name
2826
3385
  };
2827
3386
 
3387
+ if (typedInput && decl.returnType) {
3388
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3389
+ innerScope.returns = [ valtypeBinary ];
3390
+ }
3391
+
2828
3392
  for (let i = 0; i < params.length; i++) {
2829
3393
  allocVar(innerScope, params[i].name, false);
2830
3394
 
@@ -2846,13 +3410,13 @@ const generateFunc = (scope, decl) => {
2846
3410
  const func = {
2847
3411
  name,
2848
3412
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2849
- returns: innerScope.returns,
2850
- locals: innerScope.locals,
2851
- throws: innerScope.throws,
2852
- index: currentFuncIndex++
3413
+ index: currentFuncIndex++,
3414
+ ...innerScope
2853
3415
  };
2854
3416
  funcIndex[name] = func.index;
2855
3417
 
3418
+ if (name === 'main') func.gotLastType = true;
3419
+
2856
3420
  // quick hack fixes
2857
3421
  for (const inst of wasm) {
2858
3422
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2904,7 +3468,7 @@ const internalConstrs = {
2904
3468
 
2905
3469
  // todo: check in wasm instead of here
2906
3470
  const literalValue = arg.value ?? 0;
2907
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3471
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2908
3472
 
2909
3473
  return [
2910
3474
  ...number(0, Valtype.i32),
@@ -2915,7 +3479,8 @@ const internalConstrs = {
2915
3479
  ...number(pointer)
2916
3480
  ];
2917
3481
  },
2918
- type: TYPES._array
3482
+ type: TYPES._array,
3483
+ length: 1
2919
3484
  },
2920
3485
 
2921
3486
  __Array_of: {
@@ -2927,7 +3492,94 @@ const internalConstrs = {
2927
3492
  }, global, name);
2928
3493
  },
2929
3494
  type: TYPES._array,
3495
+ notConstr: true,
3496
+ length: 0
3497
+ },
3498
+
3499
+ __Porffor_fastOr: {
3500
+ generate: (scope, decl) => {
3501
+ const out = [];
3502
+
3503
+ for (let i = 0; i < decl.arguments.length; i++) {
3504
+ out.push(
3505
+ ...generate(scope, decl.arguments[i]),
3506
+ Opcodes.i32_to_u,
3507
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3508
+ );
3509
+ }
3510
+
3511
+ return out;
3512
+ },
3513
+ type: TYPES.boolean,
2930
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
+ return out;
3530
+ },
3531
+ type: TYPES.boolean,
3532
+ notConstr: true
3533
+ },
3534
+
3535
+ Boolean: {
3536
+ generate: (scope, decl) => {
3537
+ // todo: boolean object when used as constructor
3538
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3539
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3540
+ },
3541
+ type: TYPES.boolean,
3542
+ length: 1
3543
+ },
3544
+
3545
+ __Math_max: {
3546
+ generate: (scope, decl) => {
3547
+ const out = [
3548
+ ...number(-Infinity)
3549
+ ];
3550
+
3551
+ for (let i = 0; i < decl.arguments.length; i++) {
3552
+ out.push(
3553
+ ...generate(scope, decl.arguments[i]),
3554
+ [ Opcodes.f64_max ]
3555
+ );
3556
+ }
3557
+
3558
+ return out;
3559
+ },
3560
+ type: TYPES.number,
3561
+ notConstr: true,
3562
+ length: 2
3563
+ },
3564
+
3565
+ __Math_min: {
3566
+ generate: (scope, decl) => {
3567
+ const out = [
3568
+ ...number(Infinity)
3569
+ ];
3570
+
3571
+ for (let i = 0; i < decl.arguments.length; i++) {
3572
+ out.push(
3573
+ ...generate(scope, decl.arguments[i]),
3574
+ [ Opcodes.f64_min ]
3575
+ );
3576
+ }
3577
+
3578
+ return out;
3579
+ },
3580
+ type: TYPES.number,
3581
+ notConstr: true,
3582
+ length: 2
2931
3583
  }
2932
3584
  };
2933
3585
 
@@ -2956,7 +3608,6 @@ export default program => {
2956
3608
  funcs = [];
2957
3609
  funcIndex = {};
2958
3610
  depth = [];
2959
- arrays = new Map();
2960
3611
  pages = new Map();
2961
3612
  data = [];
2962
3613
  currentFuncIndex = importedFuncs.length;
@@ -2970,6 +3621,10 @@ export default program => {
2970
3621
 
2971
3622
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2972
3623
 
3624
+ globalThis.pageSize = PageSize;
3625
+ const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3626
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3627
+
2973
3628
  // set generic opcodes for current valtype
2974
3629
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2975
3630
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2978,10 +3633,10 @@ export default program => {
2978
3633
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2979
3634
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2980
3635
 
2981
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2982
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2983
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2984
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3636
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3637
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3638
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3639
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2985
3640
 
2986
3641
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2987
3642
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2994,10 +3649,6 @@ export default program => {
2994
3649
 
2995
3650
  program.id = { name: 'main' };
2996
3651
 
2997
- globalThis.pageSize = PageSize;
2998
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2999
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3000
-
3001
3652
  const scope = {
3002
3653
  locals: {},
3003
3654
  localInd: 0
@@ -3008,7 +3659,7 @@ export default program => {
3008
3659
  body: program.body
3009
3660
  };
3010
3661
 
3011
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3662
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3012
3663
 
3013
3664
  generateFunc(scope, program);
3014
3665
 
@@ -3025,7 +3676,11 @@ export default program => {
3025
3676
  }
3026
3677
 
3027
3678
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3028
- main.returns = [];
3679
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3680
+ main.wasm.splice(main.wasm.length - 1, 1);
3681
+ } else {
3682
+ main.returns = [];
3683
+ }
3029
3684
  }
3030
3685
 
3031
3686
  if (lastInst[0] === Opcodes.call) {