porffor 0.2.0-9f8ffb2 → 0.2.0-a2c18e7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CONTRIBUTING.md +239 -0
  2. package/LICENSE +20 -20
  3. package/README.md +154 -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 +19 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +151 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +1858 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/int.ts +147 -0
  17. package/compiler/builtins/number.ts +527 -0
  18. package/compiler/builtins/porffor.d.ts +59 -0
  19. package/compiler/builtins/string.ts +1055 -0
  20. package/compiler/builtins/tostring.ts +45 -0
  21. package/compiler/builtins.js +449 -269
  22. package/compiler/{codeGen.js → codegen.js} +1009 -381
  23. package/compiler/decompile.js +0 -1
  24. package/compiler/embedding.js +22 -22
  25. package/compiler/encoding.js +108 -10
  26. package/compiler/generated_builtins.js +1454 -0
  27. package/compiler/index.js +36 -34
  28. package/compiler/log.js +6 -3
  29. package/compiler/opt.js +50 -36
  30. package/compiler/parse.js +33 -23
  31. package/compiler/precompile.js +128 -0
  32. package/compiler/prefs.js +27 -0
  33. package/compiler/prototype.js +13 -28
  34. package/compiler/types.js +37 -0
  35. package/compiler/wasmSpec.js +28 -8
  36. package/compiler/wrap.js +51 -46
  37. package/package.json +9 -5
  38. package/porf +4 -0
  39. package/rhemyn/compile.js +46 -27
  40. package/rhemyn/parse.js +322 -320
  41. package/rhemyn/test/parse.js +58 -58
  42. package/runner/compare.js +34 -34
  43. package/runner/debug.js +122 -0
  44. package/runner/index.js +91 -11
  45. package/runner/profiler.js +102 -0
  46. package/runner/repl.js +42 -9
  47. package/runner/sizes.js +37 -37
  48. package/compiler/builtins/base64.js +0 -92
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
7
7
  import { log } from "./log.js";
8
8
  import parse from "./parse.js";
9
9
  import * as Rhemyn from "../rhemyn/compile.js";
10
+ import Prefs from './prefs.js';
11
+ import { TYPES, TYPE_NAMES } from './types.js';
10
12
 
11
13
  let globals = {};
12
14
  let globalInd = 0;
@@ -23,35 +25,37 @@ 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';
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
104
108
  return generateUnary(scope, decl);
105
109
 
106
110
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
112
 
109
113
  case 'IfStatement':
110
114
  return generateIf(scope, decl);
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
115
119
  case 'WhileStatement':
116
120
  return generateWhile(scope, decl);
117
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
118
125
  case 'ForOfStatement':
119
126
  return generateForOf(scope, decl);
120
127
 
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
124
131
  case 'ContinueStatement':
125
132
  return generateContinue(scope, decl);
126
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
127
137
  case 'EmptyStatement':
128
138
  return generateEmpty(scope, decl);
129
139
 
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
137
147
  return generateTry(scope, decl);
138
148
 
139
149
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
141
151
  return [];
142
152
 
143
153
  case 'ArrayExpression':
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
151
161
  const funcsBefore = funcs.length;
152
162
  generate(scope, decl.declaration);
153
163
 
154
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
155
171
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
158
174
 
159
175
  return [];
160
176
 
161
177
  case 'TaggedTemplateExpression': {
162
178
  const funcs = {
163
- asm: str => {
179
+ __Porffor_wasm: str => {
164
180
  let out = [];
165
181
 
166
182
  for (const line of str.split('\n')) {
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
168
184
  if (asm[0] === '') continue; // blank
169
185
 
170
186
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
189
  continue;
174
190
  }
175
191
 
@@ -179,52 +195,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
179
195
  }
180
196
 
181
197
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
183
199
  // todo: add to store/load offset insts
184
200
  continue;
185
201
  }
186
202
 
187
203
  let inst = Opcodes[asm[0].replace('.', '_')];
188
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
204
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
189
205
 
190
206
  if (!Array.isArray(inst)) inst = [ inst ];
191
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
192
212
 
193
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
194
214
  }
195
215
 
196
216
  return out;
197
217
  },
198
218
 
199
- __internal_print_type: str => {
200
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
201
221
 
202
- return [
203
- ...number(type),
204
- [ Opcodes.call, importedFuncs.print ],
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
205
229
 
206
- // newline
207
- ...number(10),
208
- [ Opcodes.call, importedFuncs.printChar ]
209
- ];
210
- }
211
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
212
236
 
213
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
214
238
  // hack for inline asm
215
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
216
251
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
252
+ str += quasis[i + 1].value.raw;
253
+ }
254
+
255
+ return funcs[func](str);
219
256
  }
220
257
 
221
258
  default:
222
- if (decl.type.startsWith('TS')) {
223
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
224
262
  return [];
225
263
  }
226
264
 
227
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
228
266
  }
229
267
  };
230
268
 
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
252
290
  return [ undefined, undefined ];
253
291
  };
254
292
 
255
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
256
294
  ...generateThrow(scope, {
257
295
  argument: {
258
296
  type: 'NewExpression',
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
274
312
  const name = mapName(rawName);
275
313
  let local = scope.locals[rawName];
276
314
 
277
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
278
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
279
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
321
+ }
322
+
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
324
+ // todo: return an actual something
325
+ return number(1);
280
326
  }
281
327
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
328
+ if (isExistingProtoFunc(name)) {
283
329
  // todo: return an actual something
284
330
  return number(1);
285
331
  }
286
332
 
287
- if (local === undefined) {
333
+ if (local?.idx === undefined) {
288
334
  // no local var with name
289
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
290
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
335
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
336
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
291
337
 
292
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
293
339
  }
294
340
 
295
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
296
342
  // return undefined if unknown key in already known var
297
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
298
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
301
347
  if (!parentLookup[1]) return number(UNDEFINED);
302
348
  }
303
349
 
304
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
350
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
305
351
 
306
352
  return [ [ Opcodes.local_get, local.idx ] ];
307
353
  };
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
314
360
  // just bare "return"
315
361
  return [
316
362
  ...number(UNDEFINED), // "undefined" if func returns
317
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
318
366
  [ Opcodes.return ]
319
367
  ];
320
368
  }
321
369
 
322
370
  return [
323
371
  ...generate(scope, decl.argument),
324
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
325
375
  [ Opcodes.return ]
326
376
  ];
327
377
  };
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
335
385
  return idx;
336
386
  };
337
387
 
338
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
339
390
 
340
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
341
392
  const checks = {
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
344
395
  '??': nullish
345
396
  };
346
397
 
347
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
348
399
 
349
400
  // generic structure for {a} OP {b}
350
401
  // -->
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
352
403
 
353
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
354
405
  // (like if we are in an if condition - very common)
355
- const leftIsInt = isIntOp(left[left.length - 1]);
356
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
357
408
 
358
409
  const canInt = leftIsInt && rightIsInt;
359
410
 
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
370
421
  ...right,
371
422
  // note type
372
423
  ...rightType,
373
- setLastType(scope),
424
+ ...setLastType(scope),
374
425
  [ Opcodes.else ],
375
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
376
427
  // note type
377
428
  ...leftType,
378
- setLastType(scope),
429
+ ...setLastType(scope),
379
430
  [ Opcodes.end ],
380
431
  Opcodes.i32_from
381
432
  ];
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
389
440
  ...right,
390
441
  // note type
391
442
  ...rightType,
392
- setLastType(scope),
443
+ ...setLastType(scope),
393
444
  [ Opcodes.else ],
394
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
395
446
  // note type
396
447
  ...leftType,
397
- setLastType(scope),
448
+ ...setLastType(scope),
398
449
  [ Opcodes.end ]
399
450
  ];
400
451
  };
401
452
 
402
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
403
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
404
455
  // todo: convert left and right to strings if not
405
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
409
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
410
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
411
462
 
412
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
413
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
414
-
415
- if (assign) {
416
- const pointer = arrays.get(name ?? '$undeclared');
463
+ if (assign && Prefs.aotPointerOpt) {
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
417
465
 
418
466
  return [
419
467
  // setup right
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
438
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
439
487
 
440
488
  // copy right
441
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
442
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
443
491
 
444
492
  [ Opcodes.local_get, leftLength ],
445
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
446
494
  [ Opcodes.i32_mul ],
447
495
  [ Opcodes.i32_add ],
448
496
 
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
451
499
  ...number(ValtypeSize.i32, Valtype.i32),
452
500
  [ Opcodes.i32_add ],
453
501
 
454
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
455
503
  [ Opcodes.local_get, rightLength ],
456
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
457
505
  [ Opcodes.i32_mul ],
458
506
 
459
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
511
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
512
560
 
513
561
  // copy right
514
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
515
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
516
564
 
517
565
  [ Opcodes.local_get, leftLength ],
518
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
519
567
  [ Opcodes.i32_mul ],
520
568
  [ Opcodes.i32_add ],
521
569
 
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
524
572
  ...number(ValtypeSize.i32, Valtype.i32),
525
573
  [ Opcodes.i32_add ],
526
574
 
527
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
528
576
  [ Opcodes.local_get, rightLength ],
529
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
530
578
  [ Opcodes.i32_mul ],
531
579
 
532
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
536
584
  ];
537
585
  };
538
586
 
539
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
540
588
  // todo: this should be rewritten into a func
541
589
  // todo: convert left and right to strings if not
542
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
545
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
546
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
547
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
548
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
549
596
 
550
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
551
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
573
620
 
574
621
  [ Opcodes.local_get, rightPointer ],
575
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
576
- [ Opcodes.local_tee, rightLength ],
577
623
 
578
624
  // fast path: check leftLength != rightLength
579
625
  [ Opcodes.i32_ne ],
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
588
634
  ...number(0, Valtype.i32),
589
635
  [ Opcodes.local_set, index ],
590
636
 
591
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
592
638
  // we do this instead of having to do mul/div each iter for perf™
593
639
  [ Opcodes.local_get, leftLength ],
594
- ...number(ValtypeSize.i16, Valtype.i32),
595
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
596
644
  [ Opcodes.local_set, indexEnd ],
597
645
 
598
646
  // iterate over each char and check if eq
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
602
650
  [ Opcodes.local_get, index ],
603
651
  [ Opcodes.local_get, leftPointer ],
604
652
  [ Opcodes.i32_add ],
605
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
606
656
 
607
657
  // fetch right
608
658
  [ Opcodes.local_get, index ],
609
659
  [ Opcodes.local_get, rightPointer ],
610
660
  [ Opcodes.i32_add ],
611
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
612
664
 
613
665
  // not equal, "return" false
614
666
  [ Opcodes.i32_ne ],
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
617
669
  [ Opcodes.br, 2 ],
618
670
  [ Opcodes.end ],
619
671
 
620
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
621
673
  [ Opcodes.local_get, index ],
622
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
623
675
  [ Opcodes.i32_add ],
624
676
  [ Opcodes.local_tee, index ],
625
677
 
626
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
627
679
  [ Opcodes.local_get, indexEnd ],
628
680
  [ Opcodes.i32_ne ],
629
681
  [ Opcodes.br_if, 0 ],
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
644
696
  };
645
697
 
646
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
647
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
648
700
  ...wasm,
649
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
650
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
651
704
 
652
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
653
707
 
654
708
  const def = [
655
709
  // if value != 0
656
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
657
711
 
658
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
659
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
665
719
 
666
720
  return [
667
721
  ...wasm,
668
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
669
723
 
670
724
  ...typeSwitch(scope, type, {
671
725
  // [TYPES.number]: def,
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
674
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
675
729
  ],
676
730
  [TYPES.string]: [
677
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
678
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
679
733
 
680
734
  // get length
@@ -686,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
686
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
741
  ],
688
742
  [TYPES._bytestring]: [ // duplicate of string
689
- [ Opcodes.local_get, tmp ],
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
690
744
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
691
745
 
692
746
  // get length
@@ -700,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
700
754
  };
701
755
 
702
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
703
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
704
760
  return [
705
761
  ...wasm,
706
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
707
763
 
708
764
  ...typeSwitch(scope, type, {
709
765
  [TYPES._array]: [
@@ -711,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
711
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
712
768
  ],
713
769
  [TYPES.string]: [
714
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
715
771
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
716
772
 
717
773
  // get length
@@ -722,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
722
778
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
723
779
  ],
724
780
  [TYPES._bytestring]: [ // duplicate of string
725
- [ Opcodes.local_get, tmp ],
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
726
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
727
783
 
728
784
  // get length
@@ -734,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
734
790
  ],
735
791
  default: [
736
792
  // if value == 0
737
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
738
794
 
739
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
740
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -744,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
744
800
  };
745
801
 
746
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
747
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
748
806
  return [
749
807
  ...wasm,
750
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
751
809
 
752
810
  ...typeSwitch(scope, type, {
753
811
  [TYPES.undefined]: [
@@ -756,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
756
814
  ],
757
815
  [TYPES.object]: [
758
816
  // object, null if == 0
759
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
760
818
 
761
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
762
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -785,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
785
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
786
844
  }
787
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
788
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
789
850
  const strictOp = op === '===' || op === '!==';
790
851
 
791
852
  const startOut = [], endOut = [];
792
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
793
854
 
794
855
  // if strict (in)equal check types match
795
856
  if (strictOp) {
@@ -834,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
834
895
  // todo: if equality op and an operand is undefined, return false
835
896
  // todo: niche null hell with 0
836
897
 
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
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
862
951
 
863
952
  let ops = operatorOpcode[valtype][op];
864
953
 
@@ -868,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
868
957
  includeBuiltin(scope, builtinName);
869
958
  const idx = funcIndex[builtinName];
870
959
 
871
- return finalise([
960
+ return finalize([
872
961
  ...left,
873
962
  ...right,
874
963
  [ Opcodes.call, idx ]
875
964
  ]);
876
965
  }
877
966
 
878
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
879
968
 
880
969
  if (!Array.isArray(ops)) ops = [ ops ];
881
970
  ops = [ ops ];
882
971
 
883
972
  let tmpLeft, tmpRight;
884
973
  // 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
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
894
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
895
979
  tmpLeft = localTmp(scope, '__tmpop_left');
896
980
  tmpRight = localTmp(scope, '__tmpop_right');
897
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
898
1023
  ops.unshift(...stringOnly([
899
1024
  // if left is string
900
1025
  ...leftType,
@@ -906,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
906
1031
  ...number(TYPES.string, Valtype.i32),
907
1032
  [ Opcodes.i32_eq ],
908
1033
 
909
- // if either are true
910
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
911
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
912
1041
 
913
- // todo: convert non-strings to strings, for now fail immediately if one is not
914
- // if left is not string
1042
+ // if left is bytestring
915
1043
  ...leftType,
916
- ...number(TYPES.string, Valtype.i32),
917
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
918
1046
 
919
- // if right is not string
1047
+ // if right is bytestring
920
1048
  ...rightType,
921
- ...number(TYPES.string, Valtype.i32),
922
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
923
1051
 
924
- // if either are true
925
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
926
1054
  [ 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 ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
933
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
934
1057
  [ Opcodes.br, 1 ],
935
1058
  [ Opcodes.end ],
@@ -941,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
941
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
942
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
943
1066
  // }
944
- })();
1067
+ }
945
1068
 
946
- return finalise([
1069
+ return finalize([
947
1070
  ...left,
948
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
949
1072
  ...right,
@@ -960,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
960
1083
  return out;
961
1084
  };
962
1085
 
963
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
964
1102
  const existing = funcs.find(x => x.name === name);
965
1103
  if (existing) return existing;
966
1104
 
@@ -972,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
972
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
973
1111
  }
974
1112
 
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 });
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
985
1117
  }
986
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
987
1121
  let baseGlobalIdx, i = 0;
988
1122
  for (const type of globalTypes) {
989
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -1006,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1006
1140
  params,
1007
1141
  locals,
1008
1142
  returns,
1009
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
1010
1144
  wasm,
1011
1145
  internal: true,
1012
1146
  index: currentFuncIndex++
@@ -1029,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1029
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1030
1164
  };
1031
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1032
1167
  // T = JS type, V = value/pointer
1033
1168
  // 0bTTT
1034
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1052,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1052
1187
  // 4: internal type
1053
1188
  // 5: pointer
1054
1189
 
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'
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1193
+
1194
+ return false;
1084
1195
  };
1085
1196
 
1086
1197
  const getType = (scope, _name) => {
1087
1198
  const name = mapName(_name);
1088
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1089
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1090
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1091
1207
 
1092
1208
  let type = TYPES.undefined;
1093
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1094
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1095
1211
 
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;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1098
1213
 
1099
1214
  return number(type, Valtype.i32);
1100
1215
  };
@@ -1117,15 +1232,16 @@ const setType = (scope, _name, type) => {
1117
1232
  ];
1118
1233
 
1119
1234
  // throw new Error('could not find var');
1235
+ return [];
1120
1236
  };
1121
1237
 
1122
1238
  const getLastType = scope => {
1123
1239
  scope.gotLastType = true;
1124
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1125
1241
  };
1126
1242
 
1127
1243
  const setLastType = scope => {
1128
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1129
1245
  };
1130
1246
 
1131
1247
  const getNodeType = (scope, node) => {
@@ -1150,13 +1266,25 @@ const getNodeType = (scope, node) => {
1150
1266
  const name = node.callee.name;
1151
1267
  if (!name) {
1152
1268
  // iife
1153
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1154
1270
 
1155
1271
  // presume
1156
1272
  // todo: warn here?
1157
1273
  return TYPES.number;
1158
1274
  }
1159
1275
 
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1160
1288
  const func = funcs.find(x => x.name === name);
1161
1289
 
1162
1290
  if (func) {
@@ -1164,7 +1292,7 @@ const getNodeType = (scope, node) => {
1164
1292
  if (func.returnType) return func.returnType;
1165
1293
  }
1166
1294
 
1167
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1168
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1169
1297
 
1170
1298
  // check if this is a prototype function
@@ -1179,7 +1307,12 @@ const getNodeType = (scope, node) => {
1179
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1180
1308
  }
1181
1309
 
1182
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1183
1316
 
1184
1317
  // presume
1185
1318
  // todo: warn here?
@@ -1227,6 +1360,15 @@ const getNodeType = (scope, node) => {
1227
1360
 
1228
1361
  if (node.type === 'BinaryExpression') {
1229
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1230
1372
  return TYPES.number;
1231
1373
 
1232
1374
  // todo: string concat types
@@ -1251,7 +1393,7 @@ const getNodeType = (scope, node) => {
1251
1393
  if (node.operator === '!') return TYPES.boolean;
1252
1394
  if (node.operator === 'void') return TYPES.undefined;
1253
1395
  if (node.operator === 'delete') return TYPES.boolean;
1254
- if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1255
1397
 
1256
1398
  return TYPES.number;
1257
1399
  }
@@ -1262,15 +1404,21 @@ const getNodeType = (scope, node) => {
1262
1404
 
1263
1405
  // ts hack
1264
1406
  if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1265
1408
  if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1266
1409
 
1267
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
1411
 
1269
1412
  // presume
1270
1413
  return TYPES.number;
1271
1414
  }
1272
1415
 
1273
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1274
1422
 
1275
1423
  // presume
1276
1424
  // todo: warn here?
@@ -1303,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1303
1451
  return makeString(scope, decl.value, global, name);
1304
1452
 
1305
1453
  default:
1306
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1307
1455
  }
1308
1456
  };
1309
1457
 
@@ -1312,6 +1460,8 @@ const countLeftover = wasm => {
1312
1460
 
1313
1461
  for (let i = 0; i < wasm.length; i++) {
1314
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1315
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1316
1466
  if (inst[0] === Opcodes.if) count--;
1317
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1320,18 +1470,25 @@ const countLeftover = wasm => {
1320
1470
  if (inst[0] === Opcodes.end) depth--;
1321
1471
 
1322
1472
  if (depth === 0)
1323
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1324
1474
  else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1325
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1326
1476
  else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1327
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1328
1478
  else if (inst[0] === Opcodes.return) count = 0;
1329
1479
  else if (inst[0] === Opcodes.call) {
1330
1480
  let func = funcs.find(x => x.index === inst[1]);
1331
- if (func) {
1332
- count -= func.params.length;
1333
- } else count--;
1334
- if (func) count += func.returns.length;
1481
+ if (inst[1] === -1) {
1482
+ // todo: count for calling self
1483
+ } else if (!func && inst[1] < importedFuncs.length) {
1484
+ count -= importedFuncs[inst[1]].params;
1485
+ count += importedFuncs[inst[1]].returns;
1486
+ } else {
1487
+ if (func) {
1488
+ count -= func.params.length;
1489
+ } else count--;
1490
+ if (func) count += func.returns.length;
1491
+ }
1335
1492
  } else count--;
1336
1493
 
1337
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1423,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1423
1580
  name = func.name;
1424
1581
  }
1425
1582
 
1426
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1427
1584
  // literal eval hack
1428
- const code = decl.arguments[0].value;
1429
- const parsed = parse(code, []);
1585
+ const code = decl.arguments[0]?.value ?? '';
1586
+
1587
+ let parsed;
1588
+ try {
1589
+ parsed = parse(code, []);
1590
+ } catch (e) {
1591
+ if (e.name === 'SyntaxError') {
1592
+ // throw syntax errors of evals at runtime instead
1593
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1594
+ }
1595
+
1596
+ throw e;
1597
+ }
1430
1598
 
1431
1599
  const out = generate(scope, {
1432
1600
  type: 'BlockStatement',
@@ -1440,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1440
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1441
1609
  out.push(
1442
1610
  ...getNodeType(scope, finalStatement),
1443
- setLastType(scope)
1611
+ ...setLastType(scope)
1444
1612
  );
1445
1613
  } else if (countLeftover(out) === 0) {
1446
1614
  out.push(...number(UNDEFINED));
1447
1615
  out.push(
1448
1616
  ...number(TYPES.undefined, Valtype.i32),
1449
- setLastType(scope)
1617
+ ...setLastType(scope)
1450
1618
  );
1451
1619
  }
1452
1620
 
@@ -1468,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1468
1636
 
1469
1637
  target = { ...decl.callee };
1470
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1471
1642
  }
1472
1643
 
1473
1644
  // literal.func()
@@ -1475,22 +1646,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1475
1646
  // megahack for /regex/.func()
1476
1647
  const funcName = decl.callee.property.name;
1477
1648
  if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1478
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1649
+ const regex = decl.callee.object.regex.pattern;
1650
+ const rhemynName = `regex_${funcName}_${regex}`;
1651
+
1652
+ if (!funcIndex[rhemynName]) {
1653
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1479
1654
 
1480
- funcIndex[func.name] = func.index;
1481
- funcs.push(func);
1655
+ funcIndex[func.name] = func.index;
1656
+ funcs.push(func);
1657
+ }
1482
1658
 
1659
+ const idx = funcIndex[rhemynName];
1483
1660
  return [
1484
1661
  // make string arg
1485
1662
  ...generate(scope, decl.arguments[0]),
1663
+ Opcodes.i32_to_u,
1664
+ ...getNodeType(scope, decl.arguments[0]),
1486
1665
 
1487
1666
  // call regex func
1488
- Opcodes.i32_to_u,
1489
- [ Opcodes.call, func.index ],
1667
+ [ Opcodes.call, idx ],
1490
1668
  Opcodes.i32_from_u,
1491
1669
 
1492
1670
  ...number(TYPES.boolean, Valtype.i32),
1493
- setLastType(scope)
1671
+ ...setLastType(scope)
1494
1672
  ];
1495
1673
  }
1496
1674
 
@@ -1515,12 +1693,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1515
1693
  // }
1516
1694
 
1517
1695
  if (protoName) {
1696
+ const protoBC = {};
1697
+
1698
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1699
+
1700
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1701
+ for (const x of builtinProtoCands) {
1702
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1703
+ if (type == null) continue;
1704
+
1705
+ protoBC[type] = generateCall(scope, {
1706
+ callee: {
1707
+ type: 'Identifier',
1708
+ name: x
1709
+ },
1710
+ arguments: [ target, ...decl.arguments ],
1711
+ _protoInternalCall: true
1712
+ });
1713
+ }
1714
+ }
1715
+
1518
1716
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1519
1717
  if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1520
1718
  return acc;
1521
1719
  }, {});
1522
1720
 
1523
- // no prototype function candidates, ignore
1524
1721
  if (Object.keys(protoCands).length > 0) {
1525
1722
  // use local for cached i32 length as commonly used
1526
1723
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
@@ -1538,7 +1735,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1538
1735
 
1539
1736
  let allOptUnused = true;
1540
1737
  let lengthI32CacheUsed = false;
1541
- const protoBC = {};
1542
1738
  for (const x in protoCands) {
1543
1739
  const protoFunc = protoCands[x];
1544
1740
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1546,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1546
1742
  ...RTArrayUtil.getLength(getPointer),
1547
1743
 
1548
1744
  ...number(TYPES.number, Valtype.i32),
1549
- setLastType(scope)
1745
+ ...setLastType(scope)
1550
1746
  ];
1551
1747
  continue;
1552
1748
  }
@@ -1583,7 +1779,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1583
1779
  ...protoOut,
1584
1780
 
1585
1781
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1586
- setLastType(scope),
1782
+ ...setLastType(scope),
1587
1783
  [ Opcodes.end ]
1588
1784
  ];
1589
1785
  }
@@ -1609,10 +1805,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1609
1805
  }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1610
1806
  ];
1611
1807
  }
1808
+
1809
+ if (Object.keys(protoBC).length > 0) {
1810
+ return typeSwitch(scope, getNodeType(scope, target), {
1811
+ ...protoBC,
1812
+
1813
+ // TODO: error better
1814
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1815
+ }, valtypeBinary);
1816
+ }
1612
1817
  }
1613
1818
 
1614
1819
  // TODO: only allows callee as literal
1615
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1820
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1616
1821
 
1617
1822
  let idx = funcIndex[name] ?? importedFuncs[name];
1618
1823
  if (idx === undefined && builtinFuncs[name]) {
@@ -1622,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1827
  idx = funcIndex[name];
1623
1828
 
1624
1829
  // infer arguments types from builtins params
1625
- const func = funcs.find(x => x.name === name);
1626
- for (let i = 0; i < decl.arguments.length; i++) {
1627
- const arg = decl.arguments[i];
1628
- if (!arg.name) continue;
1629
-
1630
- const local = scope.locals[arg.name];
1631
- if (!local) continue;
1632
-
1633
- local.type = func.params[i];
1634
- if (local.type === Valtype.v128) {
1635
- // specify vec subtype inferred from last vec type in function name
1636
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1637
- }
1638
- }
1830
+ // const func = funcs.find(x => x.name === name);
1831
+ // for (let i = 0; i < decl.arguments.length; i++) {
1832
+ // const arg = decl.arguments[i];
1833
+ // if (!arg.name) continue;
1834
+
1835
+ // const local = scope.locals[arg.name];
1836
+ // if (!local) continue;
1837
+
1838
+ // local.type = func.params[i];
1839
+ // if (local.type === Valtype.v128) {
1840
+ // // specify vec subtype inferred from last vec type in function name
1841
+ // local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1842
+ // }
1843
+ // }
1639
1844
  }
1640
1845
 
1641
1846
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1645,16 +1850,62 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1645
1850
  idx = -1;
1646
1851
  }
1647
1852
 
1853
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1854
+ const wasmOps = {
1855
+ // pointer, align, offset
1856
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1857
+ // pointer, value, align, offset
1858
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1859
+ // pointer, align, offset
1860
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1861
+ // pointer, value, align, offset
1862
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1863
+ // pointer, align, offset
1864
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1865
+ // pointer, value, align, offset
1866
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1867
+
1868
+ // pointer, align, offset
1869
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1870
+ // pointer, value, align, offset
1871
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1872
+
1873
+ // value
1874
+ i32_const: { imms: 1, args: [], returns: 1 },
1875
+ };
1876
+
1877
+ const opName = name.slice('__Porffor_wasm_'.length);
1878
+
1879
+ if (wasmOps[opName]) {
1880
+ const op = wasmOps[opName];
1881
+
1882
+ const argOut = [];
1883
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1884
+ ...generate(scope, decl.arguments[i]),
1885
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1886
+ );
1887
+
1888
+ // literals only
1889
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1890
+
1891
+ return [
1892
+ ...argOut,
1893
+ [ Opcodes[opName], ...imms ],
1894
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1895
+ ];
1896
+ }
1897
+ }
1898
+
1648
1899
  if (idx === undefined) {
1649
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1650
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1900
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1901
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1651
1902
  }
1652
1903
 
1653
1904
  const func = funcs.find(x => x.index === idx);
1654
1905
 
1655
1906
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1656
1907
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1657
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1908
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1658
1909
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1659
1910
 
1660
1911
  let args = decl.arguments;
@@ -1671,14 +1922,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1671
1922
  if (func && func.throws) scope.throws = true;
1672
1923
 
1673
1924
  let out = [];
1674
- for (const arg of args) {
1925
+ for (let i = 0; i < args.length; i++) {
1926
+ const arg = args[i];
1675
1927
  out = out.concat(generate(scope, arg));
1928
+
1929
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1930
+ out.push(Opcodes.i32_to);
1931
+ }
1932
+
1933
+ if (importedFuncs[name] && name.startsWith('profile')) {
1934
+ out.push(Opcodes.i32_to);
1935
+ }
1936
+
1676
1937
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1677
1938
  }
1678
1939
 
1679
1940
  out.push([ Opcodes.call, idx ]);
1680
1941
 
1681
- if (!typedReturn) {
1942
+ if (!typedReturns) {
1682
1943
  // let type;
1683
1944
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1684
1945
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1688,7 +1949,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1688
1949
  // ...number(type, Valtype.i32),
1689
1950
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1690
1951
  // );
1691
- } else out.push(setLastType(scope));
1952
+ } else out.push(...setLastType(scope));
1953
+
1954
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1955
+ out.push(Opcodes.i32_from);
1956
+ }
1692
1957
 
1693
1958
  return out;
1694
1959
  };
@@ -1696,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1696
1961
  const generateNew = (scope, decl, _global, _name) => {
1697
1962
  // hack: basically treat this as a normal call for builtins for now
1698
1963
  const name = mapName(decl.callee.name);
1964
+
1699
1965
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1700
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1966
+
1967
+ if (builtinFuncs[name + '$constructor']) {
1968
+ // custom ...$constructor override builtin func
1969
+ return generateCall(scope, {
1970
+ ...decl,
1971
+ callee: {
1972
+ type: 'Identifier',
1973
+ name: name + '$constructor'
1974
+ }
1975
+ }, _global, _name);
1976
+ }
1977
+
1978
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1701
1979
 
1702
1980
  return generateCall(scope, decl, _global, _name);
1703
1981
  };
@@ -1814,14 +2092,14 @@ const brTable = (input, bc, returns) => {
1814
2092
  };
1815
2093
 
1816
2094
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
- if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
2095
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
1818
2096
 
1819
2097
  const known = knownType(scope, type);
1820
2098
  if (known != null) {
1821
2099
  return bc[known] ?? bc.default;
1822
2100
  }
1823
2101
 
1824
- if (process.argv.includes('-typeswitch-use-brtable'))
2102
+ if (Prefs.typeswitchUseBrtable)
1825
2103
  return brTable(type, bc, returns);
1826
2104
 
1827
2105
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1831,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1831
2109
  [ Opcodes.block, returns ]
1832
2110
  ];
1833
2111
 
1834
- // todo: use br_table?
1835
-
1836
2112
  for (const x in bc) {
1837
2113
  if (x === 'default') continue;
1838
2114
 
@@ -1856,7 +2132,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1856
2132
  return out;
1857
2133
  };
1858
2134
 
1859
- const allocVar = (scope, name, global = false) => {
2135
+ const allocVar = (scope, name, global = false, type = true) => {
1860
2136
  const target = global ? globals : scope.locals;
1861
2137
 
1862
2138
  // already declared
@@ -1870,8 +2146,10 @@ const allocVar = (scope, name, global = false) => {
1870
2146
  let idx = global ? globalInd++ : scope.localInd++;
1871
2147
  target[name] = { idx, type: valtypeBinary };
1872
2148
 
1873
- let typeIdx = global ? globalInd++ : scope.localInd++;
1874
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2149
+ if (type) {
2150
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2151
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2152
+ }
1875
2153
 
1876
2154
  return idx;
1877
2155
  };
@@ -1886,11 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1886
2164
  };
1887
2165
 
1888
2166
  const typeAnnoToPorfType = x => {
1889
- if (TYPES[x]) return TYPES[x];
1890
- if (TYPES['_' + x]) return TYPES['_' + x];
2167
+ if (!x) return null;
2168
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2169
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1891
2170
 
1892
2171
  switch (x) {
1893
2172
  case 'i32':
2173
+ case 'i64':
2174
+ case 'f64':
1894
2175
  return TYPES.number;
1895
2176
  }
1896
2177
 
@@ -1901,7 +2182,7 @@ const extractTypeAnnotation = decl => {
1901
2182
  let a = decl;
1902
2183
  while (a.typeAnnotation) a = a.typeAnnotation;
1903
2184
 
1904
- let type, elementType;
2185
+ let type = null, elementType = null;
1905
2186
  if (a.typeName) {
1906
2187
  type = a.typeName.name;
1907
2188
  } else if (a.type.endsWith('Keyword')) {
@@ -1914,7 +2195,7 @@ const extractTypeAnnotation = decl => {
1914
2195
  const typeName = type;
1915
2196
  type = typeAnnoToPorfType(type);
1916
2197
 
1917
- if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
2198
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
1918
2199
 
1919
2200
  // if (decl.name) console.log(decl.name, { type, elementType });
1920
2201
 
@@ -1928,11 +2209,12 @@ const generateVar = (scope, decl) => {
1928
2209
 
1929
2210
  // global variable if in top scope (main) and var ..., or if wanted
1930
2211
  const global = topLevel || decl._bare; // decl.kind === 'var';
2212
+ const target = global ? globals : scope.locals;
1931
2213
 
1932
2214
  for (const x of decl.declarations) {
1933
2215
  const name = mapName(x.id.name);
1934
2216
 
1935
- if (!name) return todo('destructuring is not supported yet');
2217
+ if (!name) return todo(scope, 'destructuring is not supported yet');
1936
2218
 
1937
2219
  if (x.init && isFuncType(x.init.type)) {
1938
2220
  // hack for let a = function () { ... }
@@ -1949,16 +2231,29 @@ const generateVar = (scope, decl) => {
1949
2231
  continue; // always ignore
1950
2232
  }
1951
2233
 
1952
- let idx = allocVar(scope, name, global);
2234
+ // // generate init before allocating var
2235
+ // let generated;
2236
+ // if (x.init) generated = generate(scope, x.init, global, name);
1953
2237
 
1954
- if (typedInput && x.id.typeAnnotation) {
2238
+ const typed = typedInput && x.id.typeAnnotation;
2239
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2240
+
2241
+ if (typed) {
1955
2242
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1956
2243
  }
1957
2244
 
1958
2245
  if (x.init) {
1959
- out = out.concat(generate(scope, x.init, global, name));
1960
-
1961
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2246
+ const generated = generate(scope, x.init, global, name);
2247
+ if (scope.arrays?.get(name) != null) {
2248
+ // hack to set local as pointer before
2249
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2250
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2251
+ generated.pop();
2252
+ out = out.concat(generated);
2253
+ } else {
2254
+ out = out.concat(generated);
2255
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2256
+ }
1962
2257
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1963
2258
  }
1964
2259
 
@@ -1969,7 +2264,8 @@ const generateVar = (scope, decl) => {
1969
2264
  return out;
1970
2265
  };
1971
2266
 
1972
- const generateAssign = (scope, decl) => {
2267
+ // todo: optimize this func for valueUnused
2268
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1973
2269
  const { type, name } = decl.left;
1974
2270
 
1975
2271
  if (type === 'ObjectPattern') {
@@ -1984,22 +2280,30 @@ const generateAssign = (scope, decl) => {
1984
2280
  return [];
1985
2281
  }
1986
2282
 
2283
+ const op = decl.operator.slice(0, -1) || '=';
2284
+
1987
2285
  // hack: .length setter
1988
2286
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1989
2287
  const name = decl.left.object.name;
1990
- const pointer = arrays.get(name);
2288
+ const pointer = scope.arrays?.get(name);
1991
2289
 
1992
- const aotPointer = pointer != null;
2290
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1993
2291
 
1994
2292
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2293
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1995
2294
 
1996
2295
  return [
1997
2296
  ...(aotPointer ? number(0, Valtype.i32) : [
1998
2297
  ...generate(scope, decl.left.object),
1999
2298
  Opcodes.i32_to_u
2000
2299
  ]),
2300
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2001
2301
 
2002
- ...generate(scope, decl.right),
2302
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2303
+ [ Opcodes.local_get, pointerTmp ],
2304
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2305
+ Opcodes.i32_from_u
2306
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
2003
2307
  [ Opcodes.local_tee, newValueTmp ],
2004
2308
 
2005
2309
  Opcodes.i32_to_u,
@@ -2009,14 +2313,12 @@ const generateAssign = (scope, decl) => {
2009
2313
  ];
2010
2314
  }
2011
2315
 
2012
- const op = decl.operator.slice(0, -1) || '=';
2013
-
2014
2316
  // arr[i]
2015
2317
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
2016
2318
  const name = decl.left.object.name;
2017
- const pointer = arrays.get(name);
2319
+ const pointer = scope.arrays?.get(name);
2018
2320
 
2019
- const aotPointer = pointer != null;
2321
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2020
2322
 
2021
2323
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
2022
2324
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2072,7 +2374,7 @@ const generateAssign = (scope, decl) => {
2072
2374
  ];
2073
2375
  }
2074
2376
 
2075
- if (!name) return todo('destructuring is not supported yet');
2377
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2076
2378
 
2077
2379
  const [ local, isGlobal ] = lookupName(scope, name);
2078
2380
 
@@ -2120,9 +2422,7 @@ const generateAssign = (scope, decl) => {
2120
2422
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2121
2423
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2122
2424
 
2123
- getLastType(scope),
2124
- // hack: type is idx+1
2125
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2425
+ ...setType(scope, name, getLastType(scope))
2126
2426
  ];
2127
2427
  }
2128
2428
 
@@ -2133,9 +2433,7 @@ const generateAssign = (scope, decl) => {
2133
2433
 
2134
2434
  // todo: string concat types
2135
2435
 
2136
- // hack: type is idx+1
2137
- ...number(TYPES.number, Valtype.i32),
2138
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2436
+ ...setType(scope, name, TYPES.number)
2139
2437
  ];
2140
2438
  };
2141
2439
 
@@ -2181,7 +2479,7 @@ const generateUnary = (scope, decl) => {
2181
2479
  return out;
2182
2480
  }
2183
2481
 
2184
- case 'delete':
2482
+ case 'delete': {
2185
2483
  let toReturn = true, toGenerate = true;
2186
2484
 
2187
2485
  if (decl.argument.type === 'Identifier') {
@@ -2203,9 +2501,26 @@ const generateUnary = (scope, decl) => {
2203
2501
 
2204
2502
  out.push(...number(toReturn ? 1 : 0));
2205
2503
  return out;
2504
+ }
2505
+
2506
+ case 'typeof': {
2507
+ let overrideType, toGenerate = true;
2508
+
2509
+ if (decl.argument.type === 'Identifier') {
2510
+ const out = generateIdent(scope, decl.argument);
2206
2511
 
2207
- case 'typeof':
2208
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2512
+ // if ReferenceError (undeclared var), ignore and return undefined
2513
+ if (out[1]) {
2514
+ // does not exist (2 ops from throw)
2515
+ overrideType = number(TYPES.undefined, Valtype.i32);
2516
+ toGenerate = false;
2517
+ }
2518
+ }
2519
+
2520
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2521
+ disposeLeftover(out);
2522
+
2523
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2209
2524
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2210
2525
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2211
2526
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
@@ -2216,27 +2531,30 @@ const generateUnary = (scope, decl) => {
2216
2531
 
2217
2532
  // object and internal types
2218
2533
  default: makeString(scope, 'object', false, '#typeof_result'),
2219
- });
2534
+ }));
2535
+
2536
+ return out;
2537
+ }
2220
2538
 
2221
2539
  default:
2222
- return todo(`unary operator ${decl.operator} not implemented yet`);
2540
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2223
2541
  }
2224
2542
  };
2225
2543
 
2226
- const generateUpdate = (scope, decl) => {
2544
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2227
2545
  const { name } = decl.argument;
2228
2546
 
2229
2547
  const [ local, isGlobal ] = lookupName(scope, name);
2230
2548
 
2231
2549
  if (local === undefined) {
2232
- return todo(`update expression with undefined variable`);
2550
+ return todo(scope, `update expression with undefined variable`, true);
2233
2551
  }
2234
2552
 
2235
2553
  const idx = local.idx;
2236
2554
  const out = [];
2237
2555
 
2238
2556
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2239
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2557
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2240
2558
 
2241
2559
  switch (decl.operator) {
2242
2560
  case '++':
@@ -2249,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
2249
2567
  }
2250
2568
 
2251
2569
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2252
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2570
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2253
2571
 
2254
2572
  return out;
2255
2573
  };
@@ -2289,7 +2607,7 @@ const generateConditional = (scope, decl) => {
2289
2607
  // note type
2290
2608
  out.push(
2291
2609
  ...getNodeType(scope, decl.consequent),
2292
- setLastType(scope)
2610
+ ...setLastType(scope)
2293
2611
  );
2294
2612
 
2295
2613
  out.push([ Opcodes.else ]);
@@ -2298,7 +2616,7 @@ const generateConditional = (scope, decl) => {
2298
2616
  // note type
2299
2617
  out.push(
2300
2618
  ...getNodeType(scope, decl.alternate),
2301
- setLastType(scope)
2619
+ ...setLastType(scope)
2302
2620
  );
2303
2621
 
2304
2622
  out.push([ Opcodes.end ]);
@@ -2312,7 +2630,7 @@ const generateFor = (scope, decl) => {
2312
2630
  const out = [];
2313
2631
 
2314
2632
  if (decl.init) {
2315
- out.push(...generate(scope, decl.init));
2633
+ out.push(...generate(scope, decl.init, false, undefined, true));
2316
2634
  disposeLeftover(out);
2317
2635
  }
2318
2636
 
@@ -2330,7 +2648,7 @@ const generateFor = (scope, decl) => {
2330
2648
  out.push(...generate(scope, decl.body));
2331
2649
  out.push([ Opcodes.end ]);
2332
2650
 
2333
- if (decl.update) out.push(...generate(scope, decl.update));
2651
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2334
2652
 
2335
2653
  out.push([ Opcodes.br, 1 ]);
2336
2654
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2358,6 +2676,36 @@ const generateWhile = (scope, decl) => {
2358
2676
  return out;
2359
2677
  };
2360
2678
 
2679
+ const generateDoWhile = (scope, decl) => {
2680
+ const out = [];
2681
+
2682
+ out.push([ Opcodes.loop, Blocktype.void ]);
2683
+ depth.push('dowhile');
2684
+
2685
+ // block for break (includes all)
2686
+ out.push([ Opcodes.block, Blocktype.void ]);
2687
+ depth.push('block');
2688
+
2689
+ // block for continue
2690
+ // includes body but not test+loop so we can exit body at anytime
2691
+ // and still test+loop after
2692
+ out.push([ Opcodes.block, Blocktype.void ]);
2693
+ depth.push('block');
2694
+
2695
+ out.push(...generate(scope, decl.body));
2696
+
2697
+ out.push([ Opcodes.end ]);
2698
+ depth.pop();
2699
+
2700
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2701
+ out.push([ Opcodes.br_if, 1 ]);
2702
+
2703
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2704
+ depth.pop(); depth.pop();
2705
+
2706
+ return out;
2707
+ };
2708
+
2361
2709
  const generateForOf = (scope, decl) => {
2362
2710
  const out = [];
2363
2711
 
@@ -2387,8 +2735,17 @@ const generateForOf = (scope, decl) => {
2387
2735
  // setup local for left
2388
2736
  generate(scope, decl.left);
2389
2737
 
2390
- const leftName = decl.left.declarations[0].id.name;
2738
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2739
+ if (!leftName && decl.left.name) {
2740
+ leftName = decl.left.name;
2741
+
2742
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2743
+ }
2744
+
2745
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2746
+
2391
2747
  const [ local, isGlobal ] = lookupName(scope, leftName);
2748
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2392
2749
 
2393
2750
  depth.push('block');
2394
2751
  depth.push('block');
@@ -2397,6 +2754,7 @@ const generateForOf = (scope, decl) => {
2397
2754
  // hack: this is naughty and will break things!
2398
2755
  let newOut = number(0, Valtype.f64), newPointer = -1;
2399
2756
  if (pages.hasAnyString) {
2757
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2400
2758
  0, [ newOut, newPointer ] = makeArray(scope, {
2401
2759
  rawElements: new Array(1)
2402
2760
  }, isGlobal, leftName, true, 'i16');
@@ -2488,6 +2846,56 @@ const generateForOf = (scope, decl) => {
2488
2846
  [ Opcodes.end ],
2489
2847
  [ Opcodes.end ]
2490
2848
  ],
2849
+ [TYPES._bytestring]: [
2850
+ ...setType(scope, leftName, TYPES._bytestring),
2851
+
2852
+ [ Opcodes.loop, Blocktype.void ],
2853
+
2854
+ // setup new/out array
2855
+ ...newOut,
2856
+ [ Opcodes.drop ],
2857
+
2858
+ ...number(0, Valtype.i32), // base 0 for store after
2859
+
2860
+ // load current string ind {arg}
2861
+ [ Opcodes.local_get, pointer ],
2862
+ [ Opcodes.local_get, counter ],
2863
+ [ Opcodes.i32_add ],
2864
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2865
+
2866
+ // store to new string ind 0
2867
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2868
+
2869
+ // return new string (page)
2870
+ ...number(newPointer),
2871
+
2872
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2873
+
2874
+ [ Opcodes.block, Blocktype.void ],
2875
+ [ Opcodes.block, Blocktype.void ],
2876
+ ...generate(scope, decl.body),
2877
+ [ Opcodes.end ],
2878
+
2879
+ // increment iter pointer
2880
+ // [ Opcodes.local_get, pointer ],
2881
+ // ...number(1, Valtype.i32),
2882
+ // [ Opcodes.i32_add ],
2883
+ // [ Opcodes.local_set, pointer ],
2884
+
2885
+ // increment counter by 1
2886
+ [ Opcodes.local_get, counter ],
2887
+ ...number(1, Valtype.i32),
2888
+ [ Opcodes.i32_add ],
2889
+ [ Opcodes.local_tee, counter ],
2890
+
2891
+ // loop if counter != length
2892
+ [ Opcodes.local_get, length ],
2893
+ [ Opcodes.i32_ne ],
2894
+ [ Opcodes.br_if, 1 ],
2895
+
2896
+ [ Opcodes.end ],
2897
+ [ Opcodes.end ]
2898
+ ],
2491
2899
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2492
2900
  }, Blocktype.void));
2493
2901
 
@@ -2498,28 +2906,65 @@ const generateForOf = (scope, decl) => {
2498
2906
  return out;
2499
2907
  };
2500
2908
 
2909
+ // find the nearest loop in depth map by type
2501
2910
  const getNearestLoop = () => {
2502
2911
  for (let i = depth.length - 1; i >= 0; i--) {
2503
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2912
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2504
2913
  }
2505
2914
 
2506
2915
  return -1;
2507
2916
  };
2508
2917
 
2509
2918
  const generateBreak = (scope, decl) => {
2510
- const nearestLoop = depth.length - getNearestLoop();
2919
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2920
+ const type = depth[target];
2921
+
2922
+ // different loop types have different branch offsets
2923
+ // as they have different wasm block/loop/if structures
2924
+ // we need to use the right offset by type to branch to the one we want
2925
+ // for a break: exit the loop without executing anything else inside it
2926
+ const offset = ({
2927
+ for: 2, // loop > if (wanted branch) > block (we are here)
2928
+ while: 2, // loop > if (wanted branch) (we are here)
2929
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2930
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2931
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2932
+ })[type];
2933
+
2511
2934
  return [
2512
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2935
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2513
2936
  ];
2514
2937
  };
2515
2938
 
2516
2939
  const generateContinue = (scope, decl) => {
2517
- const nearestLoop = depth.length - getNearestLoop();
2940
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2941
+ const type = depth[target];
2942
+
2943
+ // different loop types have different branch offsets
2944
+ // as they have different wasm block/loop/if structures
2945
+ // we need to use the right offset by type to branch to the one we want
2946
+ // for a continue: do test for the loop, and then loop depending on that success
2947
+ const offset = ({
2948
+ for: 3, // loop (wanted branch) > if > block (we are here)
2949
+ while: 1, // loop (wanted branch) > if (we are here)
2950
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2951
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2952
+ })[type];
2953
+
2518
2954
  return [
2519
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2955
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2520
2956
  ];
2521
2957
  };
2522
2958
 
2959
+ const generateLabel = (scope, decl) => {
2960
+ scope.labels ??= new Map();
2961
+
2962
+ const name = decl.label.name;
2963
+ scope.labels.set(name, depth.length);
2964
+
2965
+ return generate(scope, decl.body);
2966
+ };
2967
+
2523
2968
  const generateThrow = (scope, decl) => {
2524
2969
  scope.throws = true;
2525
2970
 
@@ -2540,6 +2985,9 @@ const generateThrow = (scope, decl) => {
2540
2985
  let exceptId = exceptions.push({ constructor, message }) - 1;
2541
2986
  let tagIdx = tags[0].idx;
2542
2987
 
2988
+ scope.exceptions ??= [];
2989
+ scope.exceptions.push(exceptId);
2990
+
2543
2991
  // todo: write a description of how this works lol
2544
2992
 
2545
2993
  return [
@@ -2549,7 +2997,7 @@ const generateThrow = (scope, decl) => {
2549
2997
  };
2550
2998
 
2551
2999
  const generateTry = (scope, decl) => {
2552
- if (decl.finalizer) return todo('try finally not implemented yet');
3000
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2553
3001
 
2554
3002
  const out = [];
2555
3003
 
@@ -2580,11 +3028,11 @@ const generateAssignPat = (scope, decl) => {
2580
3028
  // TODO
2581
3029
  // if identifier declared, use that
2582
3030
  // else, use default (right)
2583
- return todo('assignment pattern (optional arg)');
3031
+ return todo(scope, 'assignment pattern (optional arg)');
2584
3032
  };
2585
3033
 
2586
3034
  let pages = new Map();
2587
- const allocPage = (reason, type) => {
3035
+ const allocPage = (scope, reason, type) => {
2588
3036
  if (pages.has(reason)) return pages.get(reason).ind;
2589
3037
 
2590
3038
  if (reason.startsWith('array:')) pages.hasArray = true;
@@ -2595,16 +3043,20 @@ const allocPage = (reason, type) => {
2595
3043
  const ind = pages.size;
2596
3044
  pages.set(reason, { ind, type });
2597
3045
 
2598
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3046
+ scope.pages ??= new Map();
3047
+ scope.pages.set(reason, { ind, type });
3048
+
3049
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2599
3050
 
2600
3051
  return ind;
2601
3052
  };
2602
3053
 
3054
+ // todo: add scope.pages
2603
3055
  const freePage = reason => {
2604
3056
  const { ind } = pages.get(reason);
2605
3057
  pages.delete(reason);
2606
3058
 
2607
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3059
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2608
3060
 
2609
3061
  return ind;
2610
3062
  };
@@ -2630,15 +3082,14 @@ const StoreOps = {
2630
3082
 
2631
3083
  let data = [];
2632
3084
 
2633
- const compileBytes = (val, itemType, signed = true) => {
3085
+ const compileBytes = (val, itemType) => {
2634
3086
  // todo: this is a mess and needs confirming / ????
2635
3087
  switch (itemType) {
2636
3088
  case 'i8': return [ val % 256 ];
2637
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2638
-
2639
- case 'i32':
2640
- case 'i64':
2641
- return enforceFourBytes(signedLEB128(val));
3089
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3090
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3091
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3092
+ // todo: i64
2642
3093
 
2643
3094
  case 'f64': return ieee754_binary64(val);
2644
3095
  }
@@ -2656,16 +3107,22 @@ const getAllocType = itemType => {
2656
3107
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2657
3108
  const out = [];
2658
3109
 
3110
+ scope.arrays ??= new Map();
3111
+
2659
3112
  let firstAssign = false;
2660
- if (!arrays.has(name) || name === '$undeclared') {
3113
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2661
3114
  firstAssign = true;
2662
3115
 
2663
3116
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2664
3117
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2665
- arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3118
+
3119
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3120
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2666
3121
  }
2667
3122
 
2668
- const pointer = arrays.get(name);
3123
+ const pointer = scope.arrays.get(name);
3124
+
3125
+ const local = global ? globals[name] : scope.locals[name];
2669
3126
 
2670
3127
  const useRawElements = !!decl.rawElements;
2671
3128
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2673,19 +3130,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2673
3130
  const valtype = itemTypeToValtype[itemType];
2674
3131
  const length = elements.length;
2675
3132
 
2676
- if (firstAssign && useRawElements) {
2677
- let bytes = compileBytes(length, 'i32');
3133
+ if (firstAssign && useRawElements && !Prefs.noData) {
3134
+ // if length is 0 memory/data will just be 0000... anyway
3135
+ if (length !== 0) {
3136
+ let bytes = compileBytes(length, 'i32');
2678
3137
 
2679
- if (!initEmpty) for (let i = 0; i < length; i++) {
2680
- if (elements[i] == null) continue;
3138
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3139
+ if (elements[i] == null) continue;
2681
3140
 
2682
- bytes.push(...compileBytes(elements[i], itemType));
2683
- }
3141
+ bytes.push(...compileBytes(elements[i], itemType));
3142
+ }
2684
3143
 
2685
- data.push({
2686
- offset: pointer,
2687
- bytes
2688
- });
3144
+ const ind = data.push({
3145
+ offset: pointer,
3146
+ bytes
3147
+ }) - 1;
3148
+
3149
+ scope.data ??= [];
3150
+ scope.data.push(ind);
3151
+ }
2689
3152
 
2690
3153
  // local value as pointer
2691
3154
  out.push(...number(pointer));
@@ -2693,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2693
3156
  return [ out, pointer ];
2694
3157
  }
2695
3158
 
3159
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3160
+ if (pointerTmp != null) {
3161
+ out.push(
3162
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3163
+ Opcodes.i32_to_u,
3164
+ [ Opcodes.local_set, pointerTmp ]
3165
+ );
3166
+ }
3167
+
3168
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3169
+
2696
3170
  // store length as 0th array
2697
3171
  out.push(
2698
- ...number(0, Valtype.i32),
3172
+ ...pointerWasm,
2699
3173
  ...number(length, Valtype.i32),
2700
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3174
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2701
3175
  );
2702
3176
 
2703
3177
  const storeOp = StoreOps[itemType];
@@ -2706,20 +3180,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2706
3180
  if (elements[i] == null) continue;
2707
3181
 
2708
3182
  out.push(
2709
- ...number(0, Valtype.i32),
3183
+ ...pointerWasm,
2710
3184
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2711
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3185
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2712
3186
  );
2713
3187
  }
2714
3188
 
2715
3189
  // local value as pointer
2716
- out.push(...number(pointer));
3190
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2717
3191
 
2718
3192
  return [ out, pointer ];
2719
3193
  };
2720
3194
 
2721
3195
  const byteStringable = str => {
2722
- if (!process.argv.includes('-bytestring')) return false;
3196
+ if (!Prefs.bytestring) return false;
2723
3197
 
2724
3198
  for (let i = 0; i < str.length; i++) {
2725
3199
  if (str.charCodeAt(i) > 0xFF) return false;
@@ -2728,9 +3202,9 @@ const byteStringable = str => {
2728
3202
  return true;
2729
3203
  };
2730
3204
 
2731
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3205
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2732
3206
  const rawElements = new Array(str.length);
2733
- let byteStringable = process.argv.includes('-bytestring');
3207
+ let byteStringable = Prefs.bytestring;
2734
3208
  for (let i = 0; i < str.length; i++) {
2735
3209
  const c = str.charCodeAt(i);
2736
3210
  rawElements[i] = c;
@@ -2738,25 +3212,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
2738
3212
  if (byteStringable && c > 0xFF) byteStringable = false;
2739
3213
  }
2740
3214
 
3215
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3216
+
2741
3217
  return makeArray(scope, {
2742
3218
  rawElements
2743
3219
  }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2744
3220
  };
2745
3221
 
2746
- let arrays = new Map();
2747
3222
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2748
3223
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2749
3224
  };
2750
3225
 
2751
3226
  export const generateMember = (scope, decl, _global, _name) => {
2752
3227
  const name = decl.object.name;
2753
- const pointer = arrays.get(name);
3228
+ const pointer = scope.arrays?.get(name);
2754
3229
 
2755
- const aotPointer = pointer != null;
3230
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2756
3231
 
2757
3232
  // hack: .length
2758
3233
  if (decl.property.name === 'length') {
2759
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3234
+ const func = funcs.find(x => x.name === name);
3235
+ if (func) {
3236
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3237
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3238
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3239
+ }
3240
+
3241
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3242
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3243
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3244
+
2760
3245
  return [
2761
3246
  ...(aotPointer ? number(0, Valtype.i32) : [
2762
3247
  ...generate(scope, decl.object),
@@ -2800,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2800
3285
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2801
3286
 
2802
3287
  ...number(TYPES.number, Valtype.i32),
2803
- setLastType(scope)
3288
+ ...setLastType(scope)
2804
3289
  ],
2805
3290
 
2806
3291
  [TYPES.string]: [
@@ -2832,7 +3317,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2832
3317
  ...number(newPointer),
2833
3318
 
2834
3319
  ...number(TYPES.string, Valtype.i32),
2835
- setLastType(scope)
3320
+ ...setLastType(scope)
2836
3321
  ],
2837
3322
  [TYPES._bytestring]: [
2838
3323
  // setup new/out array
@@ -2851,19 +3336,19 @@ export const generateMember = (scope, decl, _global, _name) => {
2851
3336
  ]),
2852
3337
 
2853
3338
  // load current string ind {arg}
2854
- [ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3339
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2855
3340
 
2856
3341
  // store to new string ind 0
2857
- [ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3342
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2858
3343
 
2859
3344
  // return new string (page)
2860
3345
  ...number(newPointer),
2861
3346
 
2862
3347
  ...number(TYPES._bytestring, Valtype.i32),
2863
- setLastType(scope)
3348
+ ...setLastType(scope)
2864
3349
  ],
2865
3350
 
2866
- default: [ [ Opcodes.unreachable ] ]
3351
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2867
3352
  });
2868
3353
  };
2869
3354
 
@@ -2873,28 +3358,36 @@ const objectHack = node => {
2873
3358
  if (!node) return node;
2874
3359
 
2875
3360
  if (node.type === 'MemberExpression') {
2876
- if (node.computed || node.optional) return node;
3361
+ const out = (() => {
3362
+ if (node.computed || node.optional) return;
2877
3363
 
2878
- let objectName = node.object.name;
3364
+ let objectName = node.object.name;
2879
3365
 
2880
- // if object is not identifier or another member exp, give up
2881
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3366
+ // if object is not identifier or another member exp, give up
3367
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3368
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2882
3369
 
2883
- if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3370
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2884
3371
 
2885
- // if .length, give up (hack within a hack!)
2886
- if (node.property.name === 'length') return node;
3372
+ // if .length, give up (hack within a hack!)
3373
+ if (node.property.name === 'length') {
3374
+ node.object = objectHack(node.object);
3375
+ return;
3376
+ }
2887
3377
 
2888
- // no object name, give up
2889
- if (!objectName) return node;
3378
+ // no object name, give up
3379
+ if (!objectName) return;
2890
3380
 
2891
- const name = '__' + objectName + '_' + node.property.name;
2892
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3381
+ const name = '__' + objectName + '_' + node.property.name;
3382
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
2893
3383
 
2894
- return {
2895
- type: 'Identifier',
2896
- name
2897
- };
3384
+ return {
3385
+ type: 'Identifier',
3386
+ name
3387
+ };
3388
+ })();
3389
+
3390
+ if (out) return out;
2898
3391
  }
2899
3392
 
2900
3393
  for (const x in node) {
@@ -2908,8 +3401,8 @@ const objectHack = node => {
2908
3401
  };
2909
3402
 
2910
3403
  const generateFunc = (scope, decl) => {
2911
- if (decl.async) return todo('async functions are not supported');
2912
- if (decl.generator) return todo('generator functions are not supported');
3404
+ if (decl.async) return todo(scope, 'async functions are not supported');
3405
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2913
3406
 
2914
3407
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2915
3408
  const params = decl.params ?? [];
@@ -2925,6 +3418,11 @@ const generateFunc = (scope, decl) => {
2925
3418
  name
2926
3419
  };
2927
3420
 
3421
+ if (typedInput && decl.returnType) {
3422
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3423
+ innerScope.returns = [ valtypeBinary ];
3424
+ }
3425
+
2928
3426
  for (let i = 0; i < params.length; i++) {
2929
3427
  allocVar(innerScope, params[i].name, false);
2930
3428
 
@@ -2951,6 +3449,8 @@ const generateFunc = (scope, decl) => {
2951
3449
  };
2952
3450
  funcIndex[name] = func.index;
2953
3451
 
3452
+ if (name === 'main') func.gotLastType = true;
3453
+
2954
3454
  // quick hack fixes
2955
3455
  for (const inst of wasm) {
2956
3456
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -3002,7 +3502,7 @@ const internalConstrs = {
3002
3502
 
3003
3503
  // todo: check in wasm instead of here
3004
3504
  const literalValue = arg.value ?? 0;
3005
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3505
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
3006
3506
 
3007
3507
  return [
3008
3508
  ...number(0, Valtype.i32),
@@ -3013,7 +3513,8 @@ const internalConstrs = {
3013
3513
  ...number(pointer)
3014
3514
  ];
3015
3515
  },
3016
- type: TYPES._array
3516
+ type: TYPES._array,
3517
+ length: 1
3017
3518
  },
3018
3519
 
3019
3520
  __Array_of: {
@@ -3025,7 +3526,131 @@ const internalConstrs = {
3025
3526
  }, global, name);
3026
3527
  },
3027
3528
  type: TYPES._array,
3529
+ notConstr: true,
3530
+ length: 0
3531
+ },
3532
+
3533
+ __Porffor_fastOr: {
3534
+ generate: (scope, decl) => {
3535
+ const out = [];
3536
+
3537
+ for (let i = 0; i < decl.arguments.length; i++) {
3538
+ out.push(
3539
+ ...generate(scope, decl.arguments[i]),
3540
+ Opcodes.i32_to_u,
3541
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3542
+ );
3543
+ }
3544
+
3545
+ out.push(Opcodes.i32_from_u);
3546
+
3547
+ return out;
3548
+ },
3549
+ type: TYPES.boolean,
3028
3550
  notConstr: true
3551
+ },
3552
+
3553
+ __Porffor_fastAnd: {
3554
+ generate: (scope, decl) => {
3555
+ const out = [];
3556
+
3557
+ for (let i = 0; i < decl.arguments.length; i++) {
3558
+ out.push(
3559
+ ...generate(scope, decl.arguments[i]),
3560
+ Opcodes.i32_to_u,
3561
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3562
+ );
3563
+ }
3564
+
3565
+ out.push(Opcodes.i32_from_u);
3566
+
3567
+ return out;
3568
+ },
3569
+ type: TYPES.boolean,
3570
+ notConstr: true
3571
+ },
3572
+
3573
+ Boolean: {
3574
+ generate: (scope, decl) => {
3575
+ // todo: boolean object when used as constructor
3576
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3577
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3578
+ },
3579
+ type: TYPES.boolean,
3580
+ length: 1
3581
+ },
3582
+
3583
+ __Math_max: {
3584
+ generate: (scope, decl) => {
3585
+ const out = [
3586
+ ...number(-Infinity)
3587
+ ];
3588
+
3589
+ for (let i = 0; i < decl.arguments.length; i++) {
3590
+ out.push(
3591
+ ...generate(scope, decl.arguments[i]),
3592
+ [ Opcodes.f64_max ]
3593
+ );
3594
+ }
3595
+
3596
+ return out;
3597
+ },
3598
+ type: TYPES.number,
3599
+ notConstr: true,
3600
+ length: 2
3601
+ },
3602
+
3603
+ __Math_min: {
3604
+ generate: (scope, decl) => {
3605
+ const out = [
3606
+ ...number(Infinity)
3607
+ ];
3608
+
3609
+ for (let i = 0; i < decl.arguments.length; i++) {
3610
+ out.push(
3611
+ ...generate(scope, decl.arguments[i]),
3612
+ [ Opcodes.f64_min ]
3613
+ );
3614
+ }
3615
+
3616
+ return out;
3617
+ },
3618
+ type: TYPES.number,
3619
+ notConstr: true,
3620
+ length: 2
3621
+ },
3622
+
3623
+ __console_log: {
3624
+ generate: (scope, decl) => {
3625
+ const out = [];
3626
+
3627
+ for (let i = 0; i < decl.arguments.length; i++) {
3628
+ out.push(
3629
+ ...generateCall(scope, {
3630
+ callee: {
3631
+ type: 'Identifier',
3632
+ name: '__Porffor_print'
3633
+ },
3634
+ arguments: [ decl.arguments[i] ]
3635
+ }),
3636
+
3637
+ // print space
3638
+ ...number(32),
3639
+ [ Opcodes.call, importedFuncs.printChar ]
3640
+ );
3641
+ }
3642
+
3643
+ // print newline
3644
+ out.push(
3645
+ ...number(10),
3646
+ [ Opcodes.call, importedFuncs.printChar ]
3647
+ );
3648
+
3649
+ return out;
3650
+ },
3651
+ type: TYPES.undefined,
3652
+ notConstr: true,
3653
+ length: 0
3029
3654
  }
3030
3655
  };
3031
3656
 
@@ -3054,20 +3679,23 @@ export default program => {
3054
3679
  funcs = [];
3055
3680
  funcIndex = {};
3056
3681
  depth = [];
3057
- arrays = new Map();
3058
3682
  pages = new Map();
3059
3683
  data = [];
3060
3684
  currentFuncIndex = importedFuncs.length;
3061
3685
 
3062
3686
  globalThis.valtype = 'f64';
3063
3687
 
3064
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3688
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
3065
3689
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
3066
3690
 
3067
3691
  globalThis.valtypeBinary = Valtype[valtype];
3068
3692
 
3069
3693
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
3070
3694
 
3695
+ globalThis.pageSize = PageSize;
3696
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3697
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3698
+
3071
3699
  // set generic opcodes for current valtype
3072
3700
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
3073
3701
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -3076,10 +3704,10 @@ export default program => {
3076
3704
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
3077
3705
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
3078
3706
 
3079
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3080
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3081
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3082
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3707
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3708
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3709
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3710
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3083
3711
 
3084
3712
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
3085
3713
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -3092,10 +3720,6 @@ export default program => {
3092
3720
 
3093
3721
  program.id = { name: 'main' };
3094
3722
 
3095
- globalThis.pageSize = PageSize;
3096
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
3097
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3098
-
3099
3723
  const scope = {
3100
3724
  locals: {},
3101
3725
  localInd: 0
@@ -3106,7 +3730,7 @@ export default program => {
3106
3730
  body: program.body
3107
3731
  };
3108
3732
 
3109
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3733
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3110
3734
 
3111
3735
  generateFunc(scope, program);
3112
3736
 
@@ -3123,7 +3747,11 @@ export default program => {
3123
3747
  }
3124
3748
 
3125
3749
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3126
- main.returns = [];
3750
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3751
+ main.wasm.splice(main.wasm.length - 1, 1);
3752
+ } else {
3753
+ main.returns = [];
3754
+ }
3127
3755
  }
3128
3756
 
3129
3757
  if (lastInst[0] === Opcodes.call) {