porffor 0.2.0-eaee2da → 0.2.0-edb06b8

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