porffor 0.2.0-f2bbe1f → 0.2.0-f549952

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 +248 -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 +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +2071 -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} +1153 -418
  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 +1481 -0
  27. package/compiler/index.js +36 -34
  28. package/compiler/log.js +6 -3
  29. package/compiler/opt.js +51 -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 +177 -37
  34. package/compiler/types.js +37 -0
  35. package/compiler/wasmSpec.js +30 -7
  36. package/compiler/wrap.js +56 -40
  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,39 +25,41 @@ const debug = str => {
23
25
  const logChar = n => {
24
26
  code.push(...number(n));
25
27
 
26
- code.push(Opcodes.call);
27
- code.push(...unsignedLEB128(0));
28
+ code.push([ Opcodes.call, 0 ]);
28
29
  };
29
30
 
30
31
  for (let i = 0; i < str.length; i++) {
31
32
  logChar(str.charCodeAt(i));
32
33
  }
33
34
 
34
- logChar('\n'.charCodeAt(0));
35
+ logChar(10); // new line
35
36
 
36
37
  return code;
37
38
  };
38
39
 
39
- const todo = msg => {
40
- class TodoError extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = 'TodoError';
44
- }
40
+ class TodoError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TodoError';
45
44
  }
45
+ }
46
+ const todo = (scope, msg, expectsValue = undefined) => {
47
+ switch (Prefs.todoTime ?? 'runtime') {
48
+ case 'compile':
49
+ throw new TodoError(msg);
46
50
 
47
- throw new TodoError(`todo: ${msg}`);
48
-
49
- const code = [];
50
-
51
- code.push(...debug(`todo! ` + msg));
52
- code.push(Opcodes.unreachable);
51
+ case 'runtime':
52
+ return internalThrow(scope, 'TodoError', msg, expectsValue);
53
53
 
54
- return code;
54
+ // return [
55
+ // ...debug(`todo! ${msg}`),
56
+ // [ Opcodes.unreachable ]
57
+ // ];
58
+ }
55
59
  };
56
60
 
57
61
  const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
58
- const generate = (scope, decl, global = false, name = undefined) => {
62
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
63
  switch (decl.type) {
60
64
  case 'BinaryExpression':
61
65
  return generateBinaryExp(scope, decl, global, name);
@@ -86,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
86
90
  return generateExp(scope, decl);
87
91
 
88
92
  case 'CallExpression':
89
- return generateCall(scope, decl, global, name);
93
+ return generateCall(scope, decl, global, name, valueUnused);
90
94
 
91
95
  case 'NewExpression':
92
96
  return generateNew(scope, decl, global, name);
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
104
108
  return generateUnary(scope, decl);
105
109
 
106
110
  case 'UpdateExpression':
107
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
108
112
 
109
113
  case 'IfStatement':
110
114
  return generateIf(scope, decl);
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
115
119
  case 'WhileStatement':
116
120
  return generateWhile(scope, decl);
117
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
118
125
  case 'ForOfStatement':
119
126
  return generateForOf(scope, decl);
120
127
 
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
124
131
  case 'ContinueStatement':
125
132
  return generateContinue(scope, decl);
126
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
127
137
  case 'EmptyStatement':
128
138
  return generateEmpty(scope, decl);
129
139
 
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
137
147
  return generateTry(scope, decl);
138
148
 
139
149
  case 'DebuggerStatement':
140
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
141
151
  return [];
142
152
 
143
153
  case 'ArrayExpression':
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
151
161
  const funcsBefore = funcs.length;
152
162
  generate(scope, decl.declaration);
153
163
 
154
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
155
171
 
156
- const newFunc = funcs[funcs.length - 1];
157
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
158
174
 
159
175
  return [];
160
176
 
161
177
  case 'TaggedTemplateExpression': {
162
178
  const funcs = {
163
- asm: str => {
179
+ __Porffor_wasm: str => {
164
180
  let out = [];
165
181
 
166
182
  for (const line of str.split('\n')) {
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
168
184
  if (asm[0] === '') continue; // blank
169
185
 
170
186
  if (asm[0] === 'local') {
171
- const [ name, idx, type ] = asm.slice(1);
172
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
173
189
  continue;
174
190
  }
175
191
 
@@ -179,52 +195,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
179
195
  }
180
196
 
181
197
  if (asm[0] === 'memory') {
182
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
183
199
  // todo: add to store/load offset insts
184
200
  continue;
185
201
  }
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;
216
243
 
217
- const str = decl.quasi.quasis[0].value.raw;
218
- return funcs[name](str);
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
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();
280
321
  }
281
322
 
282
- if (builtinFuncs[name] || internalConstrs[name]) {
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
283
324
  // todo: return an actual something
284
325
  return number(1);
285
326
  }
286
327
 
287
- if (local === undefined) {
328
+ if (isExistingProtoFunc(name)) {
329
+ // todo: return an actual something
330
+ return number(1);
331
+ }
332
+
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
@@ -685,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
739
  [ Opcodes.i32_eqz ], */
686
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
687
741
  ],
742
+ [TYPES._bytestring]: [ // duplicate of string
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
744
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
745
+
746
+ // get length
747
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
748
+
749
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
750
+ ],
688
751
  default: def
689
752
  }, intOut ? Valtype.i32 : valtypeBinary)
690
753
  ];
691
754
  };
692
755
 
693
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
694
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
695
760
  return [
696
761
  ...wasm,
697
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
698
763
 
699
764
  ...typeSwitch(scope, type, {
700
765
  [TYPES._array]: [
@@ -702,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
702
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
703
768
  ],
704
769
  [TYPES.string]: [
705
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
771
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
772
+
773
+ // get length
774
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
775
+
776
+ // if length == 0
777
+ [ Opcodes.i32_eqz ],
778
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
779
+ ],
780
+ [TYPES._bytestring]: [ // duplicate of string
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
707
783
 
708
784
  // get length
@@ -714,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
714
790
  ],
715
791
  default: [
716
792
  // if value == 0
717
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
718
794
 
719
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
720
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -724,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
724
800
  };
725
801
 
726
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
727
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
728
806
  return [
729
807
  ...wasm,
730
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
731
809
 
732
810
  ...typeSwitch(scope, type, {
733
811
  [TYPES.undefined]: [
@@ -736,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
736
814
  ],
737
815
  [TYPES.object]: [
738
816
  // object, null if == 0
739
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
740
818
 
741
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
742
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -765,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
765
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
766
844
  }
767
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
768
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
769
850
  const strictOp = op === '===' || op === '!==';
770
851
 
771
852
  const startOut = [], endOut = [];
772
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
773
854
 
774
855
  // if strict (in)equal check types match
775
856
  if (strictOp) {
@@ -814,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
814
895
  // todo: if equality op and an operand is undefined, return false
815
896
  // todo: niche null hell with 0
816
897
 
817
- // if (leftType === TYPES.string || rightType === TYPES.string) {
818
- // if (op === '+') {
819
- // // string concat (a + b)
820
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
821
- // }
822
-
823
- // // not an equality op, NaN
824
- // if (!eqOp) return finalise(number(NaN));
825
-
826
- // // else leave bool ops
827
- // // todo: convert string to number if string and number/bool
828
- // // todo: string (>|>=|<|<=) string
829
-
830
- // // string comparison
831
- // if (op === '===' || op === '==') {
832
- // return finalise(compareStrings(scope, left, right));
833
- // }
834
-
835
- // if (op === '!==' || op === '!=') {
836
- // return finalise([
837
- // ...compareStrings(scope, left, right),
838
- // [ Opcodes.i32_eqz ]
839
- // ]);
840
- // }
841
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
842
951
 
843
952
  let ops = operatorOpcode[valtype][op];
844
953
 
@@ -848,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
848
957
  includeBuiltin(scope, builtinName);
849
958
  const idx = funcIndex[builtinName];
850
959
 
851
- return finalise([
960
+ return finalize([
852
961
  ...left,
853
962
  ...right,
854
963
  [ Opcodes.call, idx ]
855
964
  ]);
856
965
  }
857
966
 
858
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
859
968
 
860
969
  if (!Array.isArray(ops)) ops = [ ops ];
861
970
  ops = [ ops ];
862
971
 
863
972
  let tmpLeft, tmpRight;
864
973
  // if equal op, check if strings for compareStrings
865
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
866
- const knownLeft = knownType(scope, leftType);
867
- const knownRight = knownType(scope, rightType);
868
-
869
- // todo: intelligent partial skip later
870
- // if neither known are string, stop this madness
871
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
872
- return;
873
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
874
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
875
979
  tmpLeft = localTmp(scope, '__tmpop_left');
876
980
  tmpRight = localTmp(scope, '__tmpop_right');
877
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
878
1023
  ops.unshift(...stringOnly([
879
1024
  // if left is string
880
1025
  ...leftType,
@@ -886,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
886
1031
  ...number(TYPES.string, Valtype.i32),
887
1032
  [ Opcodes.i32_eq ],
888
1033
 
889
- // if either are true
890
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
891
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
892
1041
 
893
- // todo: convert non-strings to strings, for now fail immediately if one is not
894
- // if left is not string
1042
+ // if left is bytestring
895
1043
  ...leftType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
898
1046
 
899
- // if right is not string
1047
+ // if right is bytestring
900
1048
  ...rightType,
901
- ...number(TYPES.string, Valtype.i32),
902
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
903
1051
 
904
- // if either are true
905
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
906
1054
  [ Opcodes.if, Blocktype.void ],
907
- ...number(0, Valtype.i32),
908
- [ Opcodes.br, 1 ],
909
- [ Opcodes.end ],
910
-
911
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
912
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
913
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
914
1057
  [ Opcodes.br, 1 ],
915
1058
  [ Opcodes.end ],
@@ -921,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
921
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
922
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
923
1066
  // }
924
- })();
1067
+ }
925
1068
 
926
- return finalise([
1069
+ return finalize([
927
1070
  ...left,
928
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
929
1072
  ...right,
@@ -940,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
940
1083
  return out;
941
1084
  };
942
1085
 
943
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
944
1102
  const existing = funcs.find(x => x.name === name);
945
1103
  if (existing) return existing;
946
1104
 
@@ -952,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
952
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
953
1111
  }
954
1112
 
955
- if (typeof wasm === 'function') {
956
- const scope = {
957
- name,
958
- params,
959
- locals,
960
- returns,
961
- localInd: allLocals.length,
962
- };
963
-
964
- wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
965
1117
  }
966
1118
 
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
967
1121
  let baseGlobalIdx, i = 0;
968
1122
  for (const type of globalTypes) {
969
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -986,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
986
1140
  params,
987
1141
  locals,
988
1142
  returns,
989
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
990
1144
  wasm,
991
1145
  internal: true,
992
1146
  index: currentFuncIndex++
@@ -1009,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
1009
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
1010
1164
  };
1011
1165
 
1166
+ // potential future ideas for nan boxing (unused):
1012
1167
  // T = JS type, V = value/pointer
1013
1168
  // 0bTTT
1014
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1032,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1032
1187
  // 4: internal type
1033
1188
  // 5: pointer
1034
1189
 
1035
- const TYPES = {
1036
- number: 0x00,
1037
- boolean: 0x01,
1038
- string: 0x02,
1039
- undefined: 0x03,
1040
- object: 0x04,
1041
- function: 0x05,
1042
- symbol: 0x06,
1043
- bigint: 0x07,
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1044
1193
 
1045
- // these are not "typeof" types but tracked internally
1046
- _array: 0x10,
1047
- _regexp: 0x11
1048
- };
1049
-
1050
- const TYPE_NAMES = {
1051
- [TYPES.number]: 'Number',
1052
- [TYPES.boolean]: 'Boolean',
1053
- [TYPES.string]: 'String',
1054
- [TYPES.undefined]: 'undefined',
1055
- [TYPES.object]: 'Object',
1056
- [TYPES.function]: 'Function',
1057
- [TYPES.symbol]: 'Symbol',
1058
- [TYPES.bigint]: 'BigInt',
1059
-
1060
- [TYPES._array]: 'Array',
1061
- [TYPES._regexp]: 'RegExp'
1194
+ return false;
1062
1195
  };
1063
1196
 
1064
1197
  const getType = (scope, _name) => {
1065
1198
  const name = mapName(_name);
1066
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1067
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1068
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1069
1207
 
1070
1208
  let type = TYPES.undefined;
1071
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1072
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1073
1211
 
1074
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1075
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1076
1213
 
1077
1214
  return number(type, Valtype.i32);
1078
1215
  };
@@ -1095,15 +1232,16 @@ const setType = (scope, _name, type) => {
1095
1232
  ];
1096
1233
 
1097
1234
  // throw new Error('could not find var');
1235
+ return [];
1098
1236
  };
1099
1237
 
1100
1238
  const getLastType = scope => {
1101
1239
  scope.gotLastType = true;
1102
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1103
1241
  };
1104
1242
 
1105
1243
  const setLastType = scope => {
1106
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1107
1245
  };
1108
1246
 
1109
1247
  const getNodeType = (scope, node) => {
@@ -1111,6 +1249,8 @@ const getNodeType = (scope, node) => {
1111
1249
  if (node.type === 'Literal') {
1112
1250
  if (node.regex) return TYPES._regexp;
1113
1251
 
1252
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1253
+
1114
1254
  return TYPES[typeof node.value];
1115
1255
  }
1116
1256
 
@@ -1124,6 +1264,27 @@ const getNodeType = (scope, node) => {
1124
1264
 
1125
1265
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1126
1266
  const name = node.callee.name;
1267
+ if (!name) {
1268
+ // iife
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1270
+
1271
+ // presume
1272
+ // todo: warn here?
1273
+ return TYPES.number;
1274
+ }
1275
+
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1127
1288
  const func = funcs.find(x => x.name === name);
1128
1289
 
1129
1290
  if (func) {
@@ -1131,7 +1292,7 @@ const getNodeType = (scope, node) => {
1131
1292
  if (func.returnType) return func.returnType;
1132
1293
  }
1133
1294
 
1134
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1135
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1136
1297
 
1137
1298
  // check if this is a prototype function
@@ -1142,11 +1303,16 @@ const getNodeType = (scope, node) => {
1142
1303
  const spl = name.slice(2).split('_');
1143
1304
 
1144
1305
  const func = spl[spl.length - 1];
1145
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1306
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1146
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1147
1308
  }
1148
1309
 
1149
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1150
1316
 
1151
1317
  // presume
1152
1318
  // todo: warn here?
@@ -1194,6 +1360,15 @@ const getNodeType = (scope, node) => {
1194
1360
 
1195
1361
  if (node.type === 'BinaryExpression') {
1196
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1197
1372
  return TYPES.number;
1198
1373
 
1199
1374
  // todo: string concat types
@@ -1218,7 +1393,7 @@ const getNodeType = (scope, node) => {
1218
1393
  if (node.operator === '!') return TYPES.boolean;
1219
1394
  if (node.operator === 'void') return TYPES.undefined;
1220
1395
  if (node.operator === 'delete') return TYPES.boolean;
1221
- if (node.operator === 'typeof') return TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1222
1397
 
1223
1398
  return TYPES.number;
1224
1399
  }
@@ -1227,11 +1402,23 @@ const getNodeType = (scope, node) => {
1227
1402
  // hack: if something.length, number type
1228
1403
  if (node.property.name === 'length') return TYPES.number;
1229
1404
 
1230
- // we cannot guess
1405
+ // ts hack
1406
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1408
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1411
+
1412
+ // presume
1231
1413
  return TYPES.number;
1232
1414
  }
1233
1415
 
1234
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1235
1422
 
1236
1423
  // presume
1237
1424
  // todo: warn here?
@@ -1244,28 +1431,11 @@ const getNodeType = (scope, node) => {
1244
1431
  return ret;
1245
1432
  };
1246
1433
 
1247
- const toString = (scope, wasm, type) => {
1248
- const tmp = localTmp(scope, '#tostring_tmp');
1249
- return [
1250
- ...wasm,
1251
- [ Opcodes.local_set, tmp ],
1252
-
1253
- ...typeSwitch(scope, type, {
1254
- [TYPES.string]: [
1255
- [ Opcodes.local_get, tmp ]
1256
- ],
1257
- [TYPES.undefined]: [
1258
- // [ Opcodes.]
1259
- ]
1260
- })
1261
- ]
1262
- };
1263
-
1264
1434
  const generateLiteral = (scope, decl, global, name) => {
1265
1435
  if (decl.value === null) return number(NULL);
1266
1436
 
1437
+ // hack: just return 1 for regex literals
1267
1438
  if (decl.regex) {
1268
- scope.regex[name] = decl.regex;
1269
1439
  return number(1);
1270
1440
  }
1271
1441
 
@@ -1281,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
1281
1451
  return makeString(scope, decl.value, global, name);
1282
1452
 
1283
1453
  default:
1284
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1285
1455
  }
1286
1456
  };
1287
1457
 
@@ -1290,6 +1460,8 @@ const countLeftover = wasm => {
1290
1460
 
1291
1461
  for (let i = 0; i < wasm.length; i++) {
1292
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1293
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1294
1466
  if (inst[0] === Opcodes.if) count--;
1295
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1298,18 +1470,25 @@ const countLeftover = wasm => {
1298
1470
  if (inst[0] === Opcodes.end) depth--;
1299
1471
 
1300
1472
  if (depth === 0)
1301
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1302
- else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1303
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1304
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1473
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1474
+ else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
1475
+ else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
1476
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1305
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1306
1478
  else if (inst[0] === Opcodes.return) count = 0;
1307
1479
  else if (inst[0] === Opcodes.call) {
1308
1480
  let func = funcs.find(x => x.index === inst[1]);
1309
- if (func) {
1310
- count -= func.params.length;
1311
- } else count--;
1312
- 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
+ }
1313
1492
  } else count--;
1314
1493
 
1315
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1327,7 +1506,7 @@ const disposeLeftover = wasm => {
1327
1506
  const generateExp = (scope, decl) => {
1328
1507
  const expression = decl.expression;
1329
1508
 
1330
- const out = generate(scope, expression);
1509
+ const out = generate(scope, expression, undefined, undefined, true);
1331
1510
  disposeLeftover(out);
1332
1511
 
1333
1512
  return out;
@@ -1385,7 +1564,7 @@ const RTArrayUtil = {
1385
1564
  ]
1386
1565
  };
1387
1566
 
1388
- const generateCall = (scope, decl, _global, _name) => {
1567
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1389
1568
  /* const callee = decl.callee;
1390
1569
  const args = decl.arguments;
1391
1570
 
@@ -1401,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
1401
1580
  name = func.name;
1402
1581
  }
1403
1582
 
1404
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1405
1584
  // literal eval hack
1406
- const code = decl.arguments[0].value;
1407
- 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
+ }
1408
1598
 
1409
1599
  const out = generate(scope, {
1410
1600
  type: 'BlockStatement',
@@ -1418,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1418
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1419
1609
  out.push(
1420
1610
  ...getNodeType(scope, finalStatement),
1421
- setLastType(scope)
1611
+ ...setLastType(scope)
1422
1612
  );
1423
1613
  } else if (countLeftover(out) === 0) {
1424
1614
  out.push(...number(UNDEFINED));
1425
1615
  out.push(
1426
1616
  ...number(TYPES.undefined, Valtype.i32),
1427
- setLastType(scope)
1617
+ ...setLastType(scope)
1428
1618
  );
1429
1619
  }
1430
1620
 
@@ -1446,29 +1636,39 @@ const generateCall = (scope, decl, _global, _name) => {
1446
1636
 
1447
1637
  target = { ...decl.callee };
1448
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1449
1642
  }
1450
1643
 
1451
1644
  // literal.func()
1452
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1453
1646
  // megahack for /regex/.func()
1454
- if (decl.callee.object.regex) {
1455
- const funcName = decl.callee.property.name;
1456
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1647
+ const funcName = decl.callee.property.name;
1648
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1649
+ const regex = decl.callee.object.regex.pattern;
1650
+ const rhemynName = `regex_${funcName}_${regex}`;
1457
1651
 
1458
- funcIndex[func.name] = func.index;
1459
- funcs.push(func);
1652
+ if (!funcIndex[rhemynName]) {
1653
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1460
1654
 
1655
+ funcIndex[func.name] = func.index;
1656
+ funcs.push(func);
1657
+ }
1658
+
1659
+ const idx = funcIndex[rhemynName];
1461
1660
  return [
1462
1661
  // make string arg
1463
1662
  ...generate(scope, decl.arguments[0]),
1663
+ Opcodes.i32_to_u,
1664
+ ...getNodeType(scope, decl.arguments[0]),
1464
1665
 
1465
1666
  // call regex func
1466
- Opcodes.i32_to_u,
1467
- [ Opcodes.call, func.index ],
1667
+ [ Opcodes.call, idx ],
1468
1668
  Opcodes.i32_from_u,
1469
1669
 
1470
1670
  ...number(TYPES.boolean, Valtype.i32),
1471
- setLastType(scope)
1671
+ ...setLastType(scope)
1472
1672
  ];
1473
1673
  }
1474
1674
 
@@ -1493,23 +1693,48 @@ const generateCall = (scope, decl, _global, _name) => {
1493
1693
  // }
1494
1694
 
1495
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
+
1496
1716
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1497
- const f = prototypeFuncs[x][protoName];
1498
- if (f) acc[x] = f;
1717
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1499
1718
  return acc;
1500
1719
  }, {});
1501
1720
 
1502
- // no prototype function candidates, ignore
1503
1721
  if (Object.keys(protoCands).length > 0) {
1504
1722
  // use local for cached i32 length as commonly used
1505
1723
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1506
1724
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1507
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1508
1725
 
1509
1726
  // TODO: long-term, prototypes should be their individual separate funcs
1510
1727
 
1728
+ const rawPointer = [
1729
+ ...generate(scope, target),
1730
+ Opcodes.i32_to_u
1731
+ ];
1732
+
1733
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1734
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1735
+
1736
+ let allOptUnused = true;
1511
1737
  let lengthI32CacheUsed = false;
1512
- const protoBC = {};
1513
1738
  for (const x in protoCands) {
1514
1739
  const protoFunc = protoCands[x];
1515
1740
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1517,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name) => {
1517
1742
  ...RTArrayUtil.getLength(getPointer),
1518
1743
 
1519
1744
  ...number(TYPES.number, Valtype.i32),
1520
- setLastType(scope)
1745
+ ...setLastType(scope)
1521
1746
  ];
1522
1747
  continue;
1523
1748
  }
@@ -1527,6 +1752,7 @@ const generateCall = (scope, decl, _global, _name) => {
1527
1752
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1528
1753
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1529
1754
 
1755
+ let optUnused = false;
1530
1756
  const protoOut = protoFunc(getPointer, {
1531
1757
  getCachedI32: () => {
1532
1758
  lengthI32CacheUsed = true;
@@ -1541,23 +1767,30 @@ const generateCall = (scope, decl, _global, _name) => {
1541
1767
  return makeArray(scope, {
1542
1768
  rawElements: new Array(length)
1543
1769
  }, _global, _name, true, itemType);
1770
+ }, () => {
1771
+ optUnused = true;
1772
+ return unusedValue;
1544
1773
  });
1545
1774
 
1775
+ if (!optUnused) allOptUnused = false;
1776
+
1546
1777
  protoBC[x] = [
1547
- [ Opcodes.block, valtypeBinary ],
1778
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1548
1779
  ...protoOut,
1549
1780
 
1550
1781
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1551
- setLastType(scope),
1782
+ ...setLastType(scope),
1552
1783
  [ Opcodes.end ]
1553
1784
  ];
1554
1785
  }
1555
1786
 
1556
- return [
1557
- ...generate(scope, target),
1787
+ // todo: if some cands use optUnused and some don't, we will probably crash
1558
1788
 
1559
- Opcodes.i32_to_u,
1560
- [ Opcodes.local_set, pointerLocal ],
1789
+ return [
1790
+ ...(usePointerCache ? [
1791
+ ...rawPointer,
1792
+ [ Opcodes.local_set, pointerLocal ],
1793
+ ] : []),
1561
1794
 
1562
1795
  ...(!lengthI32CacheUsed ? [] : [
1563
1796
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1569,13 +1802,22 @@ const generateCall = (scope, decl, _global, _name) => {
1569
1802
 
1570
1803
  // TODO: error better
1571
1804
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1572
- }, valtypeBinary),
1805
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1573
1806
  ];
1574
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
+ }
1575
1817
  }
1576
1818
 
1577
1819
  // TODO: only allows callee as literal
1578
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1820
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1579
1821
 
1580
1822
  let idx = funcIndex[name] ?? importedFuncs[name];
1581
1823
  if (idx === undefined && builtinFuncs[name]) {
@@ -1585,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name) => {
1585
1827
  idx = funcIndex[name];
1586
1828
 
1587
1829
  // infer arguments types from builtins params
1588
- const func = funcs.find(x => x.name === name);
1589
- for (let i = 0; i < decl.arguments.length; i++) {
1590
- const arg = decl.arguments[i];
1591
- if (!arg.name) continue;
1592
-
1593
- const local = scope.locals[arg.name];
1594
- if (!local) continue;
1595
-
1596
- local.type = func.params[i];
1597
- if (local.type === Valtype.v128) {
1598
- // specify vec subtype inferred from last vec type in function name
1599
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1600
- }
1601
- }
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
+ // }
1602
1844
  }
1603
1845
 
1604
1846
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1608,16 +1850,62 @@ const generateCall = (scope, decl, _global, _name) => {
1608
1850
  idx = -1;
1609
1851
  }
1610
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
+
1611
1899
  if (idx === undefined) {
1612
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1613
- 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);
1614
1902
  }
1615
1903
 
1616
1904
  const func = funcs.find(x => x.index === idx);
1617
1905
 
1618
1906
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1619
1907
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1620
- const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
1908
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1621
1909
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1622
1910
 
1623
1911
  let args = decl.arguments;
@@ -1634,14 +1922,24 @@ const generateCall = (scope, decl, _global, _name) => {
1634
1922
  if (func && func.throws) scope.throws = true;
1635
1923
 
1636
1924
  let out = [];
1637
- for (const arg of args) {
1925
+ for (let i = 0; i < args.length; i++) {
1926
+ const arg = args[i];
1638
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
+
1639
1937
  if (typedParams) out = out.concat(getNodeType(scope, arg));
1640
1938
  }
1641
1939
 
1642
1940
  out.push([ Opcodes.call, idx ]);
1643
1941
 
1644
- if (!typedReturn) {
1942
+ if (!typedReturns) {
1645
1943
  // let type;
1646
1944
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1647
1945
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1651,7 +1949,11 @@ const generateCall = (scope, decl, _global, _name) => {
1651
1949
  // ...number(type, Valtype.i32),
1652
1950
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1653
1951
  // );
1654
- } 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
+ }
1655
1957
 
1656
1958
  return out;
1657
1959
  };
@@ -1659,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name) => {
1659
1961
  const generateNew = (scope, decl, _global, _name) => {
1660
1962
  // hack: basically treat this as a normal call for builtins for now
1661
1963
  const name = mapName(decl.callee.name);
1964
+
1662
1965
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1663
- 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)})`);
1664
1979
 
1665
1980
  return generateCall(scope, decl, _global, _name);
1666
1981
  };
@@ -1777,12 +2092,14 @@ const brTable = (input, bc, returns) => {
1777
2092
  };
1778
2093
 
1779
2094
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2095
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2096
+
1780
2097
  const known = knownType(scope, type);
1781
2098
  if (known != null) {
1782
2099
  return bc[known] ?? bc.default;
1783
2100
  }
1784
2101
 
1785
- if (process.argv.includes('-typeswitch-use-brtable'))
2102
+ if (Prefs.typeswitchUseBrtable)
1786
2103
  return brTable(type, bc, returns);
1787
2104
 
1788
2105
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1792,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1792
2109
  [ Opcodes.block, returns ]
1793
2110
  ];
1794
2111
 
1795
- // todo: use br_table?
1796
-
1797
2112
  for (const x in bc) {
1798
2113
  if (x === 'default') continue;
1799
2114
 
@@ -1817,7 +2132,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1817
2132
  return out;
1818
2133
  };
1819
2134
 
1820
- const allocVar = (scope, name, global = false) => {
2135
+ const allocVar = (scope, name, global = false, type = true) => {
1821
2136
  const target = global ? globals : scope.locals;
1822
2137
 
1823
2138
  // already declared
@@ -1831,8 +2146,10 @@ const allocVar = (scope, name, global = false) => {
1831
2146
  let idx = global ? globalInd++ : scope.localInd++;
1832
2147
  target[name] = { idx, type: valtypeBinary };
1833
2148
 
1834
- let typeIdx = global ? globalInd++ : scope.localInd++;
1835
- 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
+ }
1836
2153
 
1837
2154
  return idx;
1838
2155
  };
@@ -1847,11 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1847
2164
  };
1848
2165
 
1849
2166
  const typeAnnoToPorfType = x => {
1850
- if (TYPES[x]) return TYPES[x];
1851
- 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()];
1852
2170
 
1853
2171
  switch (x) {
1854
2172
  case 'i32':
2173
+ case 'i64':
2174
+ case 'f64':
1855
2175
  return TYPES.number;
1856
2176
  }
1857
2177
 
@@ -1862,7 +2182,7 @@ const extractTypeAnnotation = decl => {
1862
2182
  let a = decl;
1863
2183
  while (a.typeAnnotation) a = a.typeAnnotation;
1864
2184
 
1865
- let type, elementType;
2185
+ let type = null, elementType = null;
1866
2186
  if (a.typeName) {
1867
2187
  type = a.typeName.name;
1868
2188
  } else if (a.type.endsWith('Keyword')) {
@@ -1875,6 +2195,8 @@ const extractTypeAnnotation = decl => {
1875
2195
  const typeName = type;
1876
2196
  type = typeAnnoToPorfType(type);
1877
2197
 
2198
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2199
+
1878
2200
  // if (decl.name) console.log(decl.name, { type, elementType });
1879
2201
 
1880
2202
  return { type, typeName, elementType };
@@ -1887,10 +2209,13 @@ const generateVar = (scope, decl) => {
1887
2209
 
1888
2210
  // global variable if in top scope (main) and var ..., or if wanted
1889
2211
  const global = topLevel || decl._bare; // decl.kind === 'var';
2212
+ const target = global ? globals : scope.locals;
1890
2213
 
1891
2214
  for (const x of decl.declarations) {
1892
2215
  const name = mapName(x.id.name);
1893
2216
 
2217
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2218
+
1894
2219
  if (x.init && isFuncType(x.init.type)) {
1895
2220
  // hack for let a = function () { ... }
1896
2221
  x.init.id = { name };
@@ -1906,16 +2231,29 @@ const generateVar = (scope, decl) => {
1906
2231
  continue; // always ignore
1907
2232
  }
1908
2233
 
1909
- 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);
2237
+
2238
+ const typed = typedInput && x.id.typeAnnotation;
2239
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1910
2240
 
1911
- if (typedInput && x.id.typeAnnotation) {
2241
+ if (typed) {
1912
2242
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1913
2243
  }
1914
2244
 
1915
2245
  if (x.init) {
1916
- out = out.concat(generate(scope, x.init, global, name));
1917
-
1918
- 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
+ }
1919
2257
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1920
2258
  }
1921
2259
 
@@ -1926,7 +2264,8 @@ const generateVar = (scope, decl) => {
1926
2264
  return out;
1927
2265
  };
1928
2266
 
1929
- const generateAssign = (scope, decl) => {
2267
+ // todo: optimize this func for valueUnused
2268
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1930
2269
  const { type, name } = decl.left;
1931
2270
 
1932
2271
  if (type === 'ObjectPattern') {
@@ -1941,22 +2280,30 @@ const generateAssign = (scope, decl) => {
1941
2280
  return [];
1942
2281
  }
1943
2282
 
2283
+ const op = decl.operator.slice(0, -1) || '=';
2284
+
1944
2285
  // hack: .length setter
1945
2286
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1946
2287
  const name = decl.left.object.name;
1947
- const pointer = arrays.get(name);
2288
+ const pointer = scope.arrays?.get(name);
1948
2289
 
1949
- const aotPointer = pointer != null;
2290
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1950
2291
 
1951
2292
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2293
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1952
2294
 
1953
2295
  return [
1954
2296
  ...(aotPointer ? number(0, Valtype.i32) : [
1955
2297
  ...generate(scope, decl.left.object),
1956
2298
  Opcodes.i32_to_u
1957
2299
  ]),
2300
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1958
2301
 
1959
- ...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))),
1960
2307
  [ Opcodes.local_tee, newValueTmp ],
1961
2308
 
1962
2309
  Opcodes.i32_to_u,
@@ -1966,14 +2313,12 @@ const generateAssign = (scope, decl) => {
1966
2313
  ];
1967
2314
  }
1968
2315
 
1969
- const op = decl.operator.slice(0, -1) || '=';
1970
-
1971
2316
  // arr[i]
1972
2317
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1973
2318
  const name = decl.left.object.name;
1974
- const pointer = arrays.get(name);
2319
+ const pointer = scope.arrays?.get(name);
1975
2320
 
1976
- const aotPointer = pointer != null;
2321
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1977
2322
 
1978
2323
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1979
2324
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2029,6 +2374,8 @@ const generateAssign = (scope, decl) => {
2029
2374
  ];
2030
2375
  }
2031
2376
 
2377
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2378
+
2032
2379
  const [ local, isGlobal ] = lookupName(scope, name);
2033
2380
 
2034
2381
  if (local === undefined) {
@@ -2075,9 +2422,7 @@ const generateAssign = (scope, decl) => {
2075
2422
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2076
2423
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2077
2424
 
2078
- getLastType(scope),
2079
- // hack: type is idx+1
2080
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2425
+ ...setType(scope, name, getLastType(scope))
2081
2426
  ];
2082
2427
  }
2083
2428
 
@@ -2088,9 +2433,7 @@ const generateAssign = (scope, decl) => {
2088
2433
 
2089
2434
  // todo: string concat types
2090
2435
 
2091
- // hack: type is idx+1
2092
- ...number(TYPES.number, Valtype.i32),
2093
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2436
+ ...setType(scope, name, TYPES.number)
2094
2437
  ];
2095
2438
  };
2096
2439
 
@@ -2136,7 +2479,7 @@ const generateUnary = (scope, decl) => {
2136
2479
  return out;
2137
2480
  }
2138
2481
 
2139
- case 'delete':
2482
+ case 'delete': {
2140
2483
  let toReturn = true, toGenerate = true;
2141
2484
 
2142
2485
  if (decl.argument.type === 'Identifier') {
@@ -2158,38 +2501,60 @@ const generateUnary = (scope, decl) => {
2158
2501
 
2159
2502
  out.push(...number(toReturn ? 1 : 0));
2160
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);
2511
+
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
+ }
2161
2519
 
2162
- case 'typeof':
2163
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2520
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2521
+ disposeLeftover(out);
2522
+
2523
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2164
2524
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2165
2525
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2166
2526
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2167
2527
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2168
2528
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2169
2529
 
2530
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2531
+
2170
2532
  // object and internal types
2171
2533
  default: makeString(scope, 'object', false, '#typeof_result'),
2172
- });
2534
+ }));
2535
+
2536
+ return out;
2537
+ }
2173
2538
 
2174
2539
  default:
2175
- return todo(`unary operator ${decl.operator} not implemented yet`);
2540
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2176
2541
  }
2177
2542
  };
2178
2543
 
2179
- const generateUpdate = (scope, decl) => {
2544
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2180
2545
  const { name } = decl.argument;
2181
2546
 
2182
2547
  const [ local, isGlobal ] = lookupName(scope, name);
2183
2548
 
2184
2549
  if (local === undefined) {
2185
- return todo(`update expression with undefined variable`);
2550
+ return todo(scope, `update expression with undefined variable`, true);
2186
2551
  }
2187
2552
 
2188
2553
  const idx = local.idx;
2189
2554
  const out = [];
2190
2555
 
2191
2556
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2192
- 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 ]);
2193
2558
 
2194
2559
  switch (decl.operator) {
2195
2560
  case '++':
@@ -2202,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
2202
2567
  }
2203
2568
 
2204
2569
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2205
- 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 ]);
2206
2571
 
2207
2572
  return out;
2208
2573
  };
@@ -2242,7 +2607,7 @@ const generateConditional = (scope, decl) => {
2242
2607
  // note type
2243
2608
  out.push(
2244
2609
  ...getNodeType(scope, decl.consequent),
2245
- setLastType(scope)
2610
+ ...setLastType(scope)
2246
2611
  );
2247
2612
 
2248
2613
  out.push([ Opcodes.else ]);
@@ -2251,7 +2616,7 @@ const generateConditional = (scope, decl) => {
2251
2616
  // note type
2252
2617
  out.push(
2253
2618
  ...getNodeType(scope, decl.alternate),
2254
- setLastType(scope)
2619
+ ...setLastType(scope)
2255
2620
  );
2256
2621
 
2257
2622
  out.push([ Opcodes.end ]);
@@ -2265,15 +2630,17 @@ const generateFor = (scope, decl) => {
2265
2630
  const out = [];
2266
2631
 
2267
2632
  if (decl.init) {
2268
- out.push(...generate(scope, decl.init));
2633
+ out.push(...generate(scope, decl.init, false, undefined, true));
2269
2634
  disposeLeftover(out);
2270
2635
  }
2271
2636
 
2272
2637
  out.push([ Opcodes.loop, Blocktype.void ]);
2273
2638
  depth.push('for');
2274
2639
 
2275
- out.push(...generate(scope, decl.test));
2276
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2640
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2641
+ else out.push(...number(1, Valtype.i32));
2642
+
2643
+ out.push([ Opcodes.if, Blocktype.void ]);
2277
2644
  depth.push('if');
2278
2645
 
2279
2646
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2281,8 +2648,7 @@ const generateFor = (scope, decl) => {
2281
2648
  out.push(...generate(scope, decl.body));
2282
2649
  out.push([ Opcodes.end ]);
2283
2650
 
2284
- out.push(...generate(scope, decl.update));
2285
- depth.pop();
2651
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2286
2652
 
2287
2653
  out.push([ Opcodes.br, 1 ]);
2288
2654
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2310,6 +2676,36 @@ const generateWhile = (scope, decl) => {
2310
2676
  return out;
2311
2677
  };
2312
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
+
2313
2709
  const generateForOf = (scope, decl) => {
2314
2710
  const out = [];
2315
2711
 
@@ -2339,8 +2735,17 @@ const generateForOf = (scope, decl) => {
2339
2735
  // setup local for left
2340
2736
  generate(scope, decl.left);
2341
2737
 
2342
- 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
+
2343
2747
  const [ local, isGlobal ] = lookupName(scope, leftName);
2748
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2344
2749
 
2345
2750
  depth.push('block');
2346
2751
  depth.push('block');
@@ -2348,13 +2753,15 @@ const generateForOf = (scope, decl) => {
2348
2753
  // // todo: we should only do this for strings but we don't know at compile-time :(
2349
2754
  // hack: this is naughty and will break things!
2350
2755
  let newOut = number(0, Valtype.f64), newPointer = -1;
2351
- if (pages.hasString) {
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?
2352
2758
  0, [ newOut, newPointer ] = makeArray(scope, {
2353
2759
  rawElements: new Array(1)
2354
2760
  }, isGlobal, leftName, true, 'i16');
2355
2761
  }
2356
2762
 
2357
2763
  // set type for local
2764
+ // todo: optimize away counter and use end pointer
2358
2765
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2359
2766
  [TYPES._array]: [
2360
2767
  ...setType(scope, leftName, TYPES.number),
@@ -2439,6 +2846,56 @@ const generateForOf = (scope, decl) => {
2439
2846
  [ Opcodes.end ],
2440
2847
  [ Opcodes.end ]
2441
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
+ ],
2442
2899
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2443
2900
  }, Blocktype.void));
2444
2901
 
@@ -2449,28 +2906,65 @@ const generateForOf = (scope, decl) => {
2449
2906
  return out;
2450
2907
  };
2451
2908
 
2909
+ // find the nearest loop in depth map by type
2452
2910
  const getNearestLoop = () => {
2453
2911
  for (let i = depth.length - 1; i >= 0; i--) {
2454
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2912
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2455
2913
  }
2456
2914
 
2457
2915
  return -1;
2458
2916
  };
2459
2917
 
2460
2918
  const generateBreak = (scope, decl) => {
2461
- 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
+
2462
2934
  return [
2463
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2935
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2464
2936
  ];
2465
2937
  };
2466
2938
 
2467
2939
  const generateContinue = (scope, decl) => {
2468
- 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
+
2469
2954
  return [
2470
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2955
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2471
2956
  ];
2472
2957
  };
2473
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
+
2474
2968
  const generateThrow = (scope, decl) => {
2475
2969
  scope.throws = true;
2476
2970
 
@@ -2479,7 +2973,7 @@ const generateThrow = (scope, decl) => {
2479
2973
  // hack: throw new X("...") -> throw "..."
2480
2974
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2481
2975
  constructor = decl.argument.callee.name;
2482
- message = decl.argument.arguments[0].value;
2976
+ message = decl.argument.arguments[0]?.value ?? '';
2483
2977
  }
2484
2978
 
2485
2979
  if (tags.length === 0) tags.push({
@@ -2491,6 +2985,9 @@ const generateThrow = (scope, decl) => {
2491
2985
  let exceptId = exceptions.push({ constructor, message }) - 1;
2492
2986
  let tagIdx = tags[0].idx;
2493
2987
 
2988
+ scope.exceptions ??= [];
2989
+ scope.exceptions.push(exceptId);
2990
+
2494
2991
  // todo: write a description of how this works lol
2495
2992
 
2496
2993
  return [
@@ -2500,7 +2997,7 @@ const generateThrow = (scope, decl) => {
2500
2997
  };
2501
2998
 
2502
2999
  const generateTry = (scope, decl) => {
2503
- if (decl.finalizer) return todo('try finally not implemented yet');
3000
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2504
3001
 
2505
3002
  const out = [];
2506
3003
 
@@ -2531,29 +3028,35 @@ const generateAssignPat = (scope, decl) => {
2531
3028
  // TODO
2532
3029
  // if identifier declared, use that
2533
3030
  // else, use default (right)
2534
- return todo('assignment pattern (optional arg)');
3031
+ return todo(scope, 'assignment pattern (optional arg)');
2535
3032
  };
2536
3033
 
2537
3034
  let pages = new Map();
2538
- const allocPage = (reason, type) => {
3035
+ const allocPage = (scope, reason, type) => {
2539
3036
  if (pages.has(reason)) return pages.get(reason).ind;
2540
3037
 
2541
3038
  if (reason.startsWith('array:')) pages.hasArray = true;
2542
3039
  if (reason.startsWith('string:')) pages.hasString = true;
3040
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3041
+ if (reason.includes('string:')) pages.hasAnyString = true;
2543
3042
 
2544
3043
  const ind = pages.size;
2545
3044
  pages.set(reason, { ind, type });
2546
3045
 
2547
- 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})`);
2548
3050
 
2549
3051
  return ind;
2550
3052
  };
2551
3053
 
3054
+ // todo: add scope.pages
2552
3055
  const freePage = reason => {
2553
3056
  const { ind } = pages.get(reason);
2554
3057
  pages.delete(reason);
2555
3058
 
2556
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3059
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2557
3060
 
2558
3061
  return ind;
2559
3062
  };
@@ -2573,38 +3076,53 @@ const StoreOps = {
2573
3076
  f64: Opcodes.f64_store,
2574
3077
 
2575
3078
  // expects i32 input!
2576
- i16: Opcodes.i32_store16
3079
+ i8: Opcodes.i32_store8,
3080
+ i16: Opcodes.i32_store16,
2577
3081
  };
2578
3082
 
2579
3083
  let data = [];
2580
3084
 
2581
- const compileBytes = (val, itemType, signed = true) => {
3085
+ const compileBytes = (val, itemType) => {
2582
3086
  // todo: this is a mess and needs confirming / ????
2583
3087
  switch (itemType) {
2584
3088
  case 'i8': return [ val % 256 ];
2585
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2586
-
2587
- case 'i32':
2588
- case 'i64':
2589
- 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
2590
3093
 
2591
3094
  case 'f64': return ieee754_binary64(val);
2592
3095
  }
2593
3096
  };
2594
3097
 
3098
+ const getAllocType = itemType => {
3099
+ switch (itemType) {
3100
+ case 'i8': return 'bytestring';
3101
+ case 'i16': return 'string';
3102
+
3103
+ default: return 'array';
3104
+ }
3105
+ };
3106
+
2595
3107
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2596
3108
  const out = [];
2597
3109
 
3110
+ scope.arrays ??= new Map();
3111
+
2598
3112
  let firstAssign = false;
2599
- if (!arrays.has(name) || name === '$undeclared') {
3113
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2600
3114
  firstAssign = true;
2601
3115
 
2602
3116
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2603
3117
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2604
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${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);
2605
3121
  }
2606
3122
 
2607
- const pointer = arrays.get(name);
3123
+ const pointer = scope.arrays.get(name);
3124
+
3125
+ const local = global ? globals[name] : scope.locals[name];
2608
3126
 
2609
3127
  const useRawElements = !!decl.rawElements;
2610
3128
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2612,19 +3130,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2612
3130
  const valtype = itemTypeToValtype[itemType];
2613
3131
  const length = elements.length;
2614
3132
 
2615
- if (firstAssign && useRawElements) {
2616
- 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');
2617
3137
 
2618
- if (!initEmpty) for (let i = 0; i < length; i++) {
2619
- if (elements[i] == null) continue;
3138
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3139
+ if (elements[i] == null) continue;
2620
3140
 
2621
- bytes.push(...compileBytes(elements[i], itemType));
2622
- }
3141
+ bytes.push(...compileBytes(elements[i], itemType));
3142
+ }
2623
3143
 
2624
- data.push({
2625
- offset: pointer,
2626
- bytes
2627
- });
3144
+ const ind = data.push({
3145
+ offset: pointer,
3146
+ bytes
3147
+ }) - 1;
3148
+
3149
+ scope.data ??= [];
3150
+ scope.data.push(ind);
3151
+ }
2628
3152
 
2629
3153
  // local value as pointer
2630
3154
  out.push(...number(pointer));
@@ -2632,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2632
3156
  return [ out, pointer ];
2633
3157
  }
2634
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
+
2635
3170
  // store length as 0th array
2636
3171
  out.push(
2637
- ...number(0, Valtype.i32),
3172
+ ...pointerWasm,
2638
3173
  ...number(length, Valtype.i32),
2639
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3174
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2640
3175
  );
2641
3176
 
2642
3177
  const storeOp = StoreOps[itemType];
@@ -2645,43 +3180,68 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2645
3180
  if (elements[i] == null) continue;
2646
3181
 
2647
3182
  out.push(
2648
- ...number(0, Valtype.i32),
3183
+ ...pointerWasm,
2649
3184
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2650
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3185
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2651
3186
  );
2652
3187
  }
2653
3188
 
2654
3189
  // local value as pointer
2655
- out.push(...number(pointer));
3190
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2656
3191
 
2657
3192
  return [ out, pointer ];
2658
3193
  };
2659
3194
 
2660
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3195
+ const byteStringable = str => {
3196
+ if (!Prefs.bytestring) return false;
3197
+
3198
+ for (let i = 0; i < str.length; i++) {
3199
+ if (str.charCodeAt(i) > 0xFF) return false;
3200
+ }
3201
+
3202
+ return true;
3203
+ };
3204
+
3205
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2661
3206
  const rawElements = new Array(str.length);
3207
+ let byteStringable = Prefs.bytestring;
2662
3208
  for (let i = 0; i < str.length; i++) {
2663
- rawElements[i] = str.charCodeAt(i);
3209
+ const c = str.charCodeAt(i);
3210
+ rawElements[i] = c;
3211
+
3212
+ if (byteStringable && c > 0xFF) byteStringable = false;
2664
3213
  }
2665
3214
 
3215
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3216
+
2666
3217
  return makeArray(scope, {
2667
3218
  rawElements
2668
- }, global, name, false, 'i16')[0];
3219
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2669
3220
  };
2670
3221
 
2671
- let arrays = new Map();
2672
3222
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2673
3223
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2674
3224
  };
2675
3225
 
2676
3226
  export const generateMember = (scope, decl, _global, _name) => {
2677
3227
  const name = decl.object.name;
2678
- const pointer = arrays.get(name);
3228
+ const pointer = scope.arrays?.get(name);
2679
3229
 
2680
- const aotPointer = pointer != null;
3230
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2681
3231
 
2682
3232
  // hack: .length
2683
3233
  if (decl.property.name === 'length') {
2684
- // 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
+
2685
3245
  return [
2686
3246
  ...(aotPointer ? number(0, Valtype.i32) : [
2687
3247
  ...generate(scope, decl.object),
@@ -2693,10 +3253,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2693
3253
  ];
2694
3254
  }
2695
3255
 
3256
+ const object = generate(scope, decl.object);
3257
+ const property = generate(scope, decl.property);
3258
+
2696
3259
  // // todo: we should only do this for strings but we don't know at compile-time :(
2697
3260
  // hack: this is naughty and will break things!
2698
3261
  let newOut = number(0, valtypeBinary), newPointer = -1;
2699
- if (pages.hasString) {
3262
+ if (pages.hasAnyString) {
2700
3263
  0, [ newOut, newPointer ] = makeArray(scope, {
2701
3264
  rawElements: new Array(1)
2702
3265
  }, _global, _name, true, 'i16');
@@ -2705,7 +3268,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2705
3268
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2706
3269
  [TYPES._array]: [
2707
3270
  // get index as valtype
2708
- ...generate(scope, decl.property),
3271
+ ...property,
2709
3272
 
2710
3273
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2711
3274
  Opcodes.i32_to_u,
@@ -2713,7 +3276,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2713
3276
  [ Opcodes.i32_mul ],
2714
3277
 
2715
3278
  ...(aotPointer ? [] : [
2716
- ...generate(scope, decl.object),
3279
+ ...object,
2717
3280
  Opcodes.i32_to_u,
2718
3281
  [ Opcodes.i32_add ]
2719
3282
  ]),
@@ -2722,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
3285
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2723
3286
 
2724
3287
  ...number(TYPES.number, Valtype.i32),
2725
- setLastType(scope)
3288
+ ...setLastType(scope)
2726
3289
  ],
2727
3290
 
2728
3291
  [TYPES.string]: [
@@ -2732,14 +3295,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2732
3295
 
2733
3296
  ...number(0, Valtype.i32), // base 0 for store later
2734
3297
 
2735
- ...generate(scope, decl.property),
2736
-
3298
+ ...property,
2737
3299
  Opcodes.i32_to_u,
3300
+
2738
3301
  ...number(ValtypeSize.i16, Valtype.i32),
2739
3302
  [ Opcodes.i32_mul ],
2740
3303
 
2741
3304
  ...(aotPointer ? [] : [
2742
- ...generate(scope, decl.object),
3305
+ ...object,
2743
3306
  Opcodes.i32_to_u,
2744
3307
  [ Opcodes.i32_add ]
2745
3308
  ]),
@@ -2754,10 +3317,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2754
3317
  ...number(newPointer),
2755
3318
 
2756
3319
  ...number(TYPES.string, Valtype.i32),
2757
- setLastType(scope)
3320
+ ...setLastType(scope)
2758
3321
  ],
3322
+ [TYPES._bytestring]: [
3323
+ // setup new/out array
3324
+ ...newOut,
3325
+ [ Opcodes.drop ],
3326
+
3327
+ ...number(0, Valtype.i32), // base 0 for store later
3328
+
3329
+ ...property,
3330
+ Opcodes.i32_to_u,
3331
+
3332
+ ...(aotPointer ? [] : [
3333
+ ...object,
3334
+ Opcodes.i32_to_u,
3335
+ [ Opcodes.i32_add ]
3336
+ ]),
3337
+
3338
+ // load current string ind {arg}
3339
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2759
3340
 
2760
- default: [ [ Opcodes.unreachable ] ]
3341
+ // store to new string ind 0
3342
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3343
+
3344
+ // return new string (page)
3345
+ ...number(newPointer),
3346
+
3347
+ ...number(TYPES._bytestring, Valtype.i32),
3348
+ ...setLastType(scope)
3349
+ ],
3350
+
3351
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2761
3352
  });
2762
3353
  };
2763
3354
 
@@ -2767,25 +3358,36 @@ const objectHack = node => {
2767
3358
  if (!node) return node;
2768
3359
 
2769
3360
  if (node.type === 'MemberExpression') {
2770
- if (node.computed || node.optional) return node;
3361
+ const out = (() => {
3362
+ if (node.computed || node.optional) return;
2771
3363
 
2772
- let objectName = node.object.name;
3364
+ let objectName = node.object.name;
2773
3365
 
2774
- // if object is not identifier or another member exp, give up
2775
- 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;
2776
3369
 
2777
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3370
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2778
3371
 
2779
- // if .length, give up (hack within a hack!)
2780
- 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
+ }
2781
3377
 
2782
- const name = '__' + objectName + '_' + node.property.name;
2783
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3378
+ // no object name, give up
3379
+ if (!objectName) return;
2784
3380
 
2785
- return {
2786
- type: 'Identifier',
2787
- name
2788
- };
3381
+ const name = '__' + objectName + '_' + node.property.name;
3382
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3383
+
3384
+ return {
3385
+ type: 'Identifier',
3386
+ name
3387
+ };
3388
+ })();
3389
+
3390
+ if (out) return out;
2789
3391
  }
2790
3392
 
2791
3393
  for (const x in node) {
@@ -2799,8 +3401,8 @@ const objectHack = node => {
2799
3401
  };
2800
3402
 
2801
3403
  const generateFunc = (scope, decl) => {
2802
- if (decl.async) return todo('async functions are not supported');
2803
- 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');
2804
3406
 
2805
3407
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2806
3408
  const params = decl.params ?? [];
@@ -2816,6 +3418,11 @@ const generateFunc = (scope, decl) => {
2816
3418
  name
2817
3419
  };
2818
3420
 
3421
+ if (typedInput && decl.returnType) {
3422
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3423
+ innerScope.returns = [ valtypeBinary ];
3424
+ }
3425
+
2819
3426
  for (let i = 0; i < params.length; i++) {
2820
3427
  allocVar(innerScope, params[i].name, false);
2821
3428
 
@@ -2837,13 +3444,13 @@ const generateFunc = (scope, decl) => {
2837
3444
  const func = {
2838
3445
  name,
2839
3446
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2840
- returns: innerScope.returns,
2841
- locals: innerScope.locals,
2842
- throws: innerScope.throws,
2843
- index: currentFuncIndex++
3447
+ index: currentFuncIndex++,
3448
+ ...innerScope
2844
3449
  };
2845
3450
  funcIndex[name] = func.index;
2846
3451
 
3452
+ if (name === 'main') func.gotLastType = true;
3453
+
2847
3454
  // quick hack fixes
2848
3455
  for (const inst of wasm) {
2849
3456
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2895,7 +3502,7 @@ const internalConstrs = {
2895
3502
 
2896
3503
  // todo: check in wasm instead of here
2897
3504
  const literalValue = arg.value ?? 0;
2898
- 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);
2899
3506
 
2900
3507
  return [
2901
3508
  ...number(0, Valtype.i32),
@@ -2906,7 +3513,8 @@ const internalConstrs = {
2906
3513
  ...number(pointer)
2907
3514
  ];
2908
3515
  },
2909
- type: TYPES._array
3516
+ type: TYPES._array,
3517
+ length: 1
2910
3518
  },
2911
3519
 
2912
3520
  __Array_of: {
@@ -2918,7 +3526,131 @@ const internalConstrs = {
2918
3526
  }, global, name);
2919
3527
  },
2920
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,
2921
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
2922
3654
  }
2923
3655
  };
2924
3656
 
@@ -2947,20 +3679,23 @@ export default program => {
2947
3679
  funcs = [];
2948
3680
  funcIndex = {};
2949
3681
  depth = [];
2950
- arrays = new Map();
2951
3682
  pages = new Map();
2952
3683
  data = [];
2953
3684
  currentFuncIndex = importedFuncs.length;
2954
3685
 
2955
3686
  globalThis.valtype = 'f64';
2956
3687
 
2957
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3688
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2958
3689
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2959
3690
 
2960
3691
  globalThis.valtypeBinary = Valtype[valtype];
2961
3692
 
2962
3693
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2963
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
+
2964
3699
  // set generic opcodes for current valtype
2965
3700
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2966
3701
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2969,10 +3704,10 @@ export default program => {
2969
3704
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2970
3705
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2971
3706
 
2972
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2973
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2974
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2975
- 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];
2976
3711
 
2977
3712
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2978
3713
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2985,10 +3720,6 @@ export default program => {
2985
3720
 
2986
3721
  program.id = { name: 'main' };
2987
3722
 
2988
- globalThis.pageSize = PageSize;
2989
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2990
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2991
-
2992
3723
  const scope = {
2993
3724
  locals: {},
2994
3725
  localInd: 0
@@ -2999,7 +3730,7 @@ export default program => {
2999
3730
  body: program.body
3000
3731
  };
3001
3732
 
3002
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3733
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3003
3734
 
3004
3735
  generateFunc(scope, program);
3005
3736
 
@@ -3016,7 +3747,11 @@ export default program => {
3016
3747
  }
3017
3748
 
3018
3749
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3019
- 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
+ }
3020
3755
  }
3021
3756
 
3022
3757
  if (lastInst[0] === Opcodes.call) {