porffor 0.2.0-5e33105 → 0.2.0-6352ecf

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/LICENSE +20 -20
  2. package/README.md +157 -89
  3. package/asur/README.md +2 -0
  4. package/asur/index.js +1262 -0
  5. package/byg/index.js +237 -0
  6. package/compiler/2c.js +317 -72
  7. package/compiler/{sections.js → assemble.js} +63 -15
  8. package/compiler/builtins/annexb_string.js +72 -0
  9. package/compiler/builtins/annexb_string.ts +19 -0
  10. package/compiler/builtins/array.ts +145 -0
  11. package/compiler/builtins/base64.ts +151 -0
  12. package/compiler/builtins/crypto.ts +120 -0
  13. package/compiler/builtins/date.ts +1848 -0
  14. package/compiler/builtins/escape.ts +141 -0
  15. package/compiler/builtins/int.ts +147 -0
  16. package/compiler/builtins/number.ts +527 -0
  17. package/compiler/builtins/porffor.d.ts +42 -0
  18. package/compiler/builtins/string.ts +1055 -0
  19. package/compiler/builtins/tostring.ts +45 -0
  20. package/compiler/builtins.js +601 -272
  21. package/compiler/{codeGen.js → codegen.js} +1168 -420
  22. package/compiler/decompile.js +3 -4
  23. package/compiler/embedding.js +22 -22
  24. package/compiler/encoding.js +108 -10
  25. package/compiler/generated_builtins.js +1445 -0
  26. package/compiler/index.js +36 -34
  27. package/compiler/log.js +6 -3
  28. package/compiler/opt.js +51 -36
  29. package/compiler/parse.js +33 -23
  30. package/compiler/precompile.js +128 -0
  31. package/compiler/prefs.js +27 -0
  32. package/compiler/prototype.js +177 -37
  33. package/compiler/types.js +37 -0
  34. package/compiler/wasmSpec.js +30 -7
  35. package/compiler/wrap.js +141 -43
  36. package/package.json +9 -5
  37. package/porf +4 -0
  38. package/rhemyn/compile.js +46 -27
  39. package/rhemyn/parse.js +322 -320
  40. package/rhemyn/test/parse.js +58 -58
  41. package/runner/compare.js +34 -34
  42. package/runner/debug.js +122 -0
  43. package/runner/index.js +91 -11
  44. package/runner/profiler.js +102 -0
  45. package/runner/repl.js +42 -9
  46. package/runner/sizes.js +37 -37
  47. package/compiler/builtins/base64.js +0 -92
  48. package/node_trace.1.log +0 -1
  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);
@@ -68,7 +72,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
72
 
69
73
  case 'ArrowFunctionExpression':
70
74
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
75
+ const func = generateFunc(scope, decl);
76
+
77
+ if (decl.type.endsWith('Expression')) {
78
+ return number(func.index);
79
+ }
80
+
72
81
  return [];
73
82
 
74
83
  case 'BlockStatement':
@@ -81,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
90
  return generateExp(scope, decl);
82
91
 
83
92
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
93
+ return generateCall(scope, decl, global, name, valueUnused);
85
94
 
86
95
  case 'NewExpression':
87
96
  return generateNew(scope, decl, global, name);
@@ -99,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
108
  return generateUnary(scope, decl);
100
109
 
101
110
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
111
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
112
 
104
113
  case 'IfStatement':
105
114
  return generateIf(scope, decl);
@@ -110,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
110
119
  case 'WhileStatement':
111
120
  return generateWhile(scope, decl);
112
121
 
122
+ case 'DoWhileStatement':
123
+ return generateDoWhile(scope, decl);
124
+
113
125
  case 'ForOfStatement':
114
126
  return generateForOf(scope, decl);
115
127
 
@@ -119,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
119
131
  case 'ContinueStatement':
120
132
  return generateContinue(scope, decl);
121
133
 
134
+ case 'LabeledStatement':
135
+ return generateLabel(scope, decl);
136
+
122
137
  case 'EmptyStatement':
123
138
  return generateEmpty(scope, decl);
124
139
 
@@ -132,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
132
147
  return generateTry(scope, decl);
133
148
 
134
149
  case 'DebuggerStatement':
135
- // todo: add fancy terminal debugger?
150
+ // todo: hook into terminal debugger
136
151
  return [];
137
152
 
138
153
  case 'ArrayExpression':
@@ -146,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
146
161
  const funcsBefore = funcs.length;
147
162
  generate(scope, decl.declaration);
148
163
 
149
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
164
+ if (funcsBefore !== funcs.length) {
165
+ // new func added
166
+ const newFunc = funcs[funcs.length - 1];
167
+ newFunc.export = true;
168
+ }
169
+
170
+ // if (funcsBefore === funcs.length) throw new Error('no new func added in export');
150
171
 
151
- const newFunc = funcs[funcs.length - 1];
152
- newFunc.export = true;
172
+ // const newFunc = funcs[funcs.length - 1];
173
+ // newFunc.export = true;
153
174
 
154
175
  return [];
155
176
 
156
177
  case 'TaggedTemplateExpression': {
157
178
  const funcs = {
158
- asm: str => {
179
+ __Porffor_wasm: str => {
159
180
  let out = [];
160
181
 
161
182
  for (const line of str.split('\n')) {
@@ -163,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
184
  if (asm[0] === '') continue; // blank
164
185
 
165
186
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
187
+ const [ name, type ] = asm.slice(1);
188
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
189
  continue;
169
190
  }
170
191
 
@@ -174,52 +195,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
195
  }
175
196
 
176
197
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
198
+ allocPage(scope, 'asm instrinsic');
178
199
  // todo: add to store/load offset insts
179
200
  continue;
180
201
  }
181
202
 
182
203
  let inst = Opcodes[asm[0].replace('.', '_')];
183
- 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`);
184
205
 
185
206
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
207
+ const immediates = asm.slice(1).map(x => {
208
+ const int = parseInt(x);
209
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
210
+ return int;
211
+ });
187
212
 
188
- out.push([ ...inst, ...immediates ]);
213
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
189
214
  }
190
215
 
191
216
  return out;
192
217
  },
193
218
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
219
+ __Porffor_bs: str => [
220
+ ...makeString(scope, str, global, name, true),
196
221
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
222
+ ...(name ? setType(scope, name, TYPES._bytestring) : [
223
+ ...number(TYPES._bytestring, Valtype.i32),
224
+ ...setLastType(scope)
225
+ ])
226
+ ],
227
+ __Porffor_s: str => [
228
+ ...makeString(scope, str, global, name, false),
200
229
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
230
+ ...(name ? setType(scope, name, TYPES.string) : [
231
+ ...number(TYPES.string, Valtype.i32),
232
+ ...setLastType(scope)
233
+ ])
234
+ ],
235
+ };
207
236
 
208
- const name = decl.tag.name;
237
+ const func = decl.tag.name;
209
238
  // hack for inline asm
210
- if (!funcs[name]) return todo('tagged template expressions not implemented');
239
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
240
+
241
+ const { quasis, expressions } = decl.quasi;
242
+ let str = quasis[0].value.raw;
243
+
244
+ for (let i = 0; i < expressions.length; i++) {
245
+ const e = expressions[i];
246
+ if (!e.name) {
247
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
248
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
249
+ } else todo(scope, 'unsupported expression in intrinsic');
250
+ } else str += lookupName(scope, e.name)[0].idx;
251
+
252
+ str += quasis[i + 1].value.raw;
253
+ }
211
254
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
255
+ return funcs[func](str);
214
256
  }
215
257
 
216
258
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
259
+ // ignore typescript nodes
260
+ if (decl.type.startsWith('TS') ||
261
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
262
  return [];
220
263
  }
221
264
 
222
- return todo(`no generation for ${decl.type}!`);
265
+ return todo(scope, `no generation for ${decl.type}!`);
223
266
  }
224
267
  };
225
268
 
@@ -247,7 +290,7 @@ const lookupName = (scope, _name) => {
247
290
  return [ undefined, undefined ];
248
291
  };
249
292
 
250
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
293
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
251
294
  ...generateThrow(scope, {
252
295
  argument: {
253
296
  type: 'NewExpression',
@@ -269,25 +312,33 @@ const generateIdent = (scope, decl) => {
269
312
  const name = mapName(rawName);
270
313
  let local = scope.locals[rawName];
271
314
 
272
- if (builtinVars[name]) {
315
+ if (Object.hasOwn(builtinVars, name)) {
273
316
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
317
+
318
+ let wasm = builtinVars[name];
319
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
320
+ return wasm.slice();
321
+ }
322
+
323
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
324
+ // todo: return an actual something
325
+ return number(1);
275
326
  }
276
327
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
328
+ if (isExistingProtoFunc(name)) {
278
329
  // todo: return an actual something
279
330
  return number(1);
280
331
  }
281
332
 
282
- if (local === undefined) {
333
+ if (local?.idx === undefined) {
283
334
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
335
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
336
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
337
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
338
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
339
  }
289
340
 
290
- if (local === undefined && rawName.startsWith('__')) {
341
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
342
  // return undefined if unknown key in already known var
292
343
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
344
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +347,7 @@ const generateIdent = (scope, decl) => {
296
347
  if (!parentLookup[1]) return number(UNDEFINED);
297
348
  }
298
349
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
350
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
351
 
301
352
  return [ [ Opcodes.local_get, local.idx ] ];
302
353
  };
@@ -309,14 +360,18 @@ const generateReturn = (scope, decl) => {
309
360
  // just bare "return"
310
361
  return [
311
362
  ...number(UNDEFINED), // "undefined" if func returns
312
- ...number(TYPES.undefined, Valtype.i32), // type undefined
363
+ ...(scope.returnType != null ? [] : [
364
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
365
+ ]),
313
366
  [ Opcodes.return ]
314
367
  ];
315
368
  }
316
369
 
317
370
  return [
318
371
  ...generate(scope, decl.argument),
319
- ...getNodeType(scope, decl.argument),
372
+ ...(scope.returnType != null ? [] : [
373
+ ...getNodeType(scope, decl.argument)
374
+ ]),
320
375
  [ Opcodes.return ]
321
376
  ];
322
377
  };
@@ -330,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
330
385
  return idx;
331
386
  };
332
387
 
333
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
388
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
389
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
334
390
 
335
391
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
336
392
  const checks = {
@@ -339,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
339
395
  '??': nullish
340
396
  };
341
397
 
342
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
398
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
343
399
 
344
400
  // generic structure for {a} OP {b}
345
401
  // -->
@@ -347,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
347
403
 
348
404
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
349
405
  // (like if we are in an if condition - very common)
350
- const leftIsInt = isIntOp(left[left.length - 1]);
351
- const rightIsInt = isIntOp(right[right.length - 1]);
406
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
407
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
352
408
 
353
409
  const canInt = leftIsInt && rightIsInt;
354
410
 
@@ -365,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
365
421
  ...right,
366
422
  // note type
367
423
  ...rightType,
368
- setLastType(scope),
424
+ ...setLastType(scope),
369
425
  [ Opcodes.else ],
370
426
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
371
427
  // note type
372
428
  ...leftType,
373
- setLastType(scope),
429
+ ...setLastType(scope),
374
430
  [ Opcodes.end ],
375
431
  Opcodes.i32_from
376
432
  ];
@@ -384,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
440
  ...right,
385
441
  // note type
386
442
  ...rightType,
387
- setLastType(scope),
443
+ ...setLastType(scope),
388
444
  [ Opcodes.else ],
389
445
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
390
446
  // note type
391
447
  ...leftType,
392
- setLastType(scope),
448
+ ...setLastType(scope),
393
449
  [ Opcodes.end ]
394
450
  ];
395
451
  };
396
452
 
397
- const concatStrings = (scope, left, right, global, name, assign) => {
453
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
398
454
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
399
455
  // todo: convert left and right to strings if not
400
456
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -404,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
460
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
461
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
462
 
407
- const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
408
- if (aotWFA) addVarMeta(name, { wellFormed: undefined });
409
-
410
- if (assign) {
411
- const pointer = arrays.get(name ?? '$undeclared');
463
+ if (assign && Prefs.aotPointerOpt) {
464
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
412
465
 
413
466
  return [
414
467
  // setup right
@@ -433,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
433
486
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
434
487
 
435
488
  // copy right
436
- // dst = out pointer + length size + current length * i16 size
489
+ // dst = out pointer + length size + current length * sizeof valtype
437
490
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
438
491
 
439
492
  [ Opcodes.local_get, leftLength ],
440
- ...number(ValtypeSize.i16, Valtype.i32),
493
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
441
494
  [ Opcodes.i32_mul ],
442
495
  [ Opcodes.i32_add ],
443
496
 
@@ -446,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
446
499
  ...number(ValtypeSize.i32, Valtype.i32),
447
500
  [ Opcodes.i32_add ],
448
501
 
449
- // size = right length * i16 size
502
+ // size = right length * sizeof valtype
450
503
  [ Opcodes.local_get, rightLength ],
451
- ...number(ValtypeSize.i16, Valtype.i32),
504
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
452
505
  [ Opcodes.i32_mul ],
453
506
 
454
507
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -506,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
506
559
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
507
560
 
508
561
  // copy right
509
- // dst = out pointer + length size + left length * i16 size
562
+ // dst = out pointer + length size + left length * sizeof valtype
510
563
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
511
564
 
512
565
  [ Opcodes.local_get, leftLength ],
513
- ...number(ValtypeSize.i16, Valtype.i32),
566
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
514
567
  [ Opcodes.i32_mul ],
515
568
  [ Opcodes.i32_add ],
516
569
 
@@ -519,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
519
572
  ...number(ValtypeSize.i32, Valtype.i32),
520
573
  [ Opcodes.i32_add ],
521
574
 
522
- // size = right length * i16 size
575
+ // size = right length * sizeof valtype
523
576
  [ Opcodes.local_get, rightLength ],
524
- ...number(ValtypeSize.i16, Valtype.i32),
577
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
525
578
  [ Opcodes.i32_mul ],
526
579
 
527
580
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -531,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
531
584
  ];
532
585
  };
533
586
 
534
- const compareStrings = (scope, left, right) => {
587
+ const compareStrings = (scope, left, right, bytestrings = false) => {
535
588
  // todo: this should be rewritten into a func
536
589
  // todo: convert left and right to strings if not
537
590
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -540,7 +593,6 @@ const compareStrings = (scope, left, right) => {
540
593
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
541
594
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
542
595
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
543
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
544
596
 
545
597
  const index = localTmp(scope, 'compare_index', Valtype.i32);
546
598
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -568,7 +620,6 @@ const compareStrings = (scope, left, right) => {
568
620
 
569
621
  [ Opcodes.local_get, rightPointer ],
570
622
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
571
- [ Opcodes.local_tee, rightLength ],
572
623
 
573
624
  // fast path: check leftLength != rightLength
574
625
  [ Opcodes.i32_ne ],
@@ -583,11 +634,13 @@ const compareStrings = (scope, left, right) => {
583
634
  ...number(0, Valtype.i32),
584
635
  [ Opcodes.local_set, index ],
585
636
 
586
- // setup index end as length * sizeof i16 (2)
637
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
587
638
  // we do this instead of having to do mul/div each iter for perf™
588
639
  [ Opcodes.local_get, leftLength ],
589
- ...number(ValtypeSize.i16, Valtype.i32),
590
- [ Opcodes.i32_mul ],
640
+ ...(bytestrings ? [] : [
641
+ ...number(ValtypeSize.i16, Valtype.i32),
642
+ [ Opcodes.i32_mul ],
643
+ ]),
591
644
  [ Opcodes.local_set, indexEnd ],
592
645
 
593
646
  // iterate over each char and check if eq
@@ -597,13 +650,17 @@ const compareStrings = (scope, left, right) => {
597
650
  [ Opcodes.local_get, index ],
598
651
  [ Opcodes.local_get, leftPointer ],
599
652
  [ Opcodes.i32_add ],
600
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
653
+ bytestrings ?
654
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
655
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
601
656
 
602
657
  // fetch right
603
658
  [ Opcodes.local_get, index ],
604
659
  [ Opcodes.local_get, rightPointer ],
605
660
  [ Opcodes.i32_add ],
606
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
661
+ bytestrings ?
662
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
663
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
607
664
 
608
665
  // not equal, "return" false
609
666
  [ Opcodes.i32_ne ],
@@ -612,13 +669,13 @@ const compareStrings = (scope, left, right) => {
612
669
  [ Opcodes.br, 2 ],
613
670
  [ Opcodes.end ],
614
671
 
615
- // index += sizeof i16 (2)
672
+ // index += sizeof valtype (1 for bytestring, 2 for string)
616
673
  [ Opcodes.local_get, index ],
617
- ...number(ValtypeSize.i16, Valtype.i32),
674
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
618
675
  [ Opcodes.i32_add ],
619
676
  [ Opcodes.local_tee, index ],
620
677
 
621
- // if index != index end (length * sizeof 16), loop
678
+ // if index != index end (length * sizeof valtype), loop
622
679
  [ Opcodes.local_get, indexEnd ],
623
680
  [ Opcodes.i32_ne ],
624
681
  [ Opcodes.br_if, 0 ],
@@ -639,16 +696,18 @@ const compareStrings = (scope, left, right) => {
639
696
  };
640
697
 
641
698
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
642
- if (isIntOp(wasm[wasm.length - 1])) return [
699
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
643
700
  ...wasm,
644
701
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
702
  ];
703
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
646
704
 
647
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
705
+ const useTmp = knownType(scope, type) == null;
706
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
648
707
 
649
708
  const def = [
650
709
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
710
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
711
 
653
712
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
713
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
719
 
661
720
  return [
662
721
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
722
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
723
 
665
724
  ...typeSwitch(scope, type, {
666
725
  // [TYPES.number]: def,
@@ -669,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
669
728
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
729
  ],
671
730
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
731
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
732
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
733
 
675
734
  // get length
@@ -680,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
739
  [ Opcodes.i32_eqz ], */
681
740
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
741
  ],
742
+ [TYPES._bytestring]: [ // duplicate of string
743
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
744
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
745
+
746
+ // get length
747
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
748
+
749
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
750
+ ],
683
751
  default: def
684
752
  }, intOut ? Valtype.i32 : valtypeBinary)
685
753
  ];
686
754
  };
687
755
 
688
756
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
757
+ const useTmp = knownType(scope, type) == null;
758
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
759
+
690
760
  return [
691
761
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
762
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
763
 
694
764
  ...typeSwitch(scope, type, {
695
765
  [TYPES._array]: [
@@ -697,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
697
767
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
768
  ],
699
769
  [TYPES.string]: [
700
- [ Opcodes.local_get, tmp ],
770
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
771
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
772
+
773
+ // get length
774
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
775
+
776
+ // if length == 0
777
+ [ Opcodes.i32_eqz ],
778
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
779
+ ],
780
+ [TYPES._bytestring]: [ // duplicate of string
781
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
701
782
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
783
 
703
784
  // get length
@@ -709,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
790
  ],
710
791
  default: [
711
792
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
793
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
794
 
714
795
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
796
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
800
  };
720
801
 
721
802
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
803
+ const useTmp = knownType(scope, type) == null;
804
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
805
+
723
806
  return [
724
807
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
808
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
809
 
727
810
  ...typeSwitch(scope, type, {
728
811
  [TYPES.undefined]: [
@@ -731,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
814
  ],
732
815
  [TYPES.object]: [
733
816
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
817
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
818
 
736
819
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
820
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
843
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
844
  }
762
845
 
846
+ const knownLeft = knownType(scope, leftType);
847
+ const knownRight = knownType(scope, rightType);
848
+
763
849
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
850
  const strictOp = op === '===' || op === '!==';
765
851
 
766
852
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
853
+ const finalize = out => startOut.concat(out, endOut);
768
854
 
769
855
  // if strict (in)equal check types match
770
856
  if (strictOp) {
@@ -809,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
895
  // todo: if equality op and an operand is undefined, return false
810
896
  // todo: niche null hell with 0
811
897
 
812
- // if (leftType === TYPES.string || rightType === TYPES.string) {
813
- // if (op === '+') {
814
- // // string concat (a + b)
815
- // return finalise(concatStrings(scope, left, right, _global, _name, assign));
816
- // }
817
-
818
- // // not an equality op, NaN
819
- // if (!eqOp) return finalise(number(NaN));
820
-
821
- // // else leave bool ops
822
- // // todo: convert string to number if string and number/bool
823
- // // todo: string (>|>=|<|<=) string
824
-
825
- // // string comparison
826
- // if (op === '===' || op === '==') {
827
- // return finalise(compareStrings(scope, left, right));
828
- // }
829
-
830
- // if (op === '!==' || op === '!=') {
831
- // return finalise([
832
- // ...compareStrings(scope, left, right),
833
- // [ Opcodes.i32_eqz ]
834
- // ]);
835
- // }
836
- // }
898
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
899
+ if (op === '+') {
900
+ // todo: this should be dynamic too but for now only static
901
+ // string concat (a + b)
902
+ return concatStrings(scope, left, right, _global, _name, assign, false);
903
+ }
904
+
905
+ // not an equality op, NaN
906
+ if (!eqOp) return number(NaN);
907
+
908
+ // else leave bool ops
909
+ // todo: convert string to number if string and number/bool
910
+ // todo: string (>|>=|<|<=) string
911
+
912
+ // string comparison
913
+ if (op === '===' || op === '==') {
914
+ return compareStrings(scope, left, right);
915
+ }
916
+
917
+ if (op === '!==' || op === '!=') {
918
+ return [
919
+ ...compareStrings(scope, left, right),
920
+ [ Opcodes.i32_eqz ]
921
+ ];
922
+ }
923
+ }
924
+
925
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
926
+ if (op === '+') {
927
+ // todo: this should be dynamic too but for now only static
928
+ // string concat (a + b)
929
+ return concatStrings(scope, left, right, _global, _name, assign, true);
930
+ }
931
+
932
+ // not an equality op, NaN
933
+ if (!eqOp) return number(NaN);
934
+
935
+ // else leave bool ops
936
+ // todo: convert string to number if string and number/bool
937
+ // todo: string (>|>=|<|<=) string
938
+
939
+ // string comparison
940
+ if (op === '===' || op === '==') {
941
+ return compareStrings(scope, left, right, true);
942
+ }
943
+
944
+ if (op === '!==' || op === '!=') {
945
+ return [
946
+ ...compareStrings(scope, left, right, true),
947
+ [ Opcodes.i32_eqz ]
948
+ ];
949
+ }
950
+ }
837
951
 
838
952
  let ops = operatorOpcode[valtype][op];
839
953
 
@@ -843,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
957
  includeBuiltin(scope, builtinName);
844
958
  const idx = funcIndex[builtinName];
845
959
 
846
- return finalise([
960
+ return finalize([
847
961
  ...left,
848
962
  ...right,
849
963
  [ Opcodes.call, idx ]
850
964
  ]);
851
965
  }
852
966
 
853
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
967
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
854
968
 
855
969
  if (!Array.isArray(ops)) ops = [ ops ];
856
970
  ops = [ ops ];
857
971
 
858
972
  let tmpLeft, tmpRight;
859
973
  // if equal op, check if strings for compareStrings
860
- if (op === '===' || op === '==' || op === '!==' || op === '!=') (() => {
861
- const knownLeft = knownType(scope, leftType);
862
- const knownRight = knownType(scope, rightType);
863
-
864
- // todo: intelligent partial skip later
865
- // if neither known are string, stop this madness
866
- if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
867
- return;
868
- }
974
+ // todo: intelligent partial skip later
975
+ // if neither known are string, stop this madness
976
+ // we already do known checks earlier, so don't need to recheck
869
977
 
978
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
870
979
  tmpLeft = localTmp(scope, '__tmpop_left');
871
980
  tmpRight = localTmp(scope, '__tmpop_right');
872
981
 
982
+ // returns false for one string, one not - but more ops/slower
983
+ // ops.unshift(...stringOnly([
984
+ // // if left is string
985
+ // ...leftType,
986
+ // ...number(TYPES.string, Valtype.i32),
987
+ // [ Opcodes.i32_eq ],
988
+
989
+ // // if right is string
990
+ // ...rightType,
991
+ // ...number(TYPES.string, Valtype.i32),
992
+ // [ Opcodes.i32_eq ],
993
+
994
+ // // if either are true
995
+ // [ Opcodes.i32_or ],
996
+ // [ Opcodes.if, Blocktype.void ],
997
+
998
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
999
+ // // if left is not string
1000
+ // ...leftType,
1001
+ // ...number(TYPES.string, Valtype.i32),
1002
+ // [ Opcodes.i32_ne ],
1003
+
1004
+ // // if right is not string
1005
+ // ...rightType,
1006
+ // ...number(TYPES.string, Valtype.i32),
1007
+ // [ Opcodes.i32_ne ],
1008
+
1009
+ // // if either are true
1010
+ // [ Opcodes.i32_or ],
1011
+ // [ Opcodes.if, Blocktype.void ],
1012
+ // ...number(0, Valtype.i32),
1013
+ // [ Opcodes.br, 2 ],
1014
+ // [ Opcodes.end ],
1015
+
1016
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1017
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1018
+ // [ Opcodes.br, 1 ],
1019
+ // [ Opcodes.end ],
1020
+ // ]));
1021
+
1022
+ // does not handle one string, one not (such cases go past)
873
1023
  ops.unshift(...stringOnly([
874
1024
  // if left is string
875
1025
  ...leftType,
@@ -881,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
881
1031
  ...number(TYPES.string, Valtype.i32),
882
1032
  [ Opcodes.i32_eq ],
883
1033
 
884
- // if either are true
885
- [ Opcodes.i32_or ],
1034
+ // if both are true
1035
+ [ Opcodes.i32_and ],
886
1036
  [ Opcodes.if, Blocktype.void ],
1037
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1038
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1039
+ [ Opcodes.br, 1 ],
1040
+ [ Opcodes.end ],
887
1041
 
888
- // todo: convert non-strings to strings, for now fail immediately if one is not
889
- // if left is not string
1042
+ // if left is bytestring
890
1043
  ...leftType,
891
- ...number(TYPES.string, Valtype.i32),
892
- [ Opcodes.i32_ne ],
1044
+ ...number(TYPES._bytestring, Valtype.i32),
1045
+ [ Opcodes.i32_eq ],
893
1046
 
894
- // if right is not string
1047
+ // if right is bytestring
895
1048
  ...rightType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1049
+ ...number(TYPES._bytestring, Valtype.i32),
1050
+ [ Opcodes.i32_eq ],
898
1051
 
899
- // if either are true
900
- [ Opcodes.i32_or ],
1052
+ // if both are true
1053
+ [ Opcodes.i32_and ],
901
1054
  [ Opcodes.if, Blocktype.void ],
902
- ...number(0, Valtype.i32),
903
- [ Opcodes.br, 1 ],
904
- [ Opcodes.end ],
905
-
906
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
907
- // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1055
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
908
1056
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
909
1057
  [ Opcodes.br, 1 ],
910
1058
  [ Opcodes.end ],
@@ -916,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
916
1064
  // endOut.push(stringOnly([ Opcodes.end ]));
917
1065
  endOut.unshift(stringOnly([ Opcodes.end ]));
918
1066
  // }
919
- })();
1067
+ }
920
1068
 
921
- return finalise([
1069
+ return finalize([
922
1070
  ...left,
923
1071
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
1072
  ...right,
@@ -935,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
1083
  return out;
936
1084
  };
937
1085
 
938
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1086
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1087
+ return func({ name, params, locals, returns, localInd }, {
1088
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
1089
+ builtin: name => {
1090
+ let idx = funcIndex[name] ?? importedFuncs[name];
1091
+ if (idx === undefined && builtinFuncs[name]) {
1092
+ includeBuiltin(null, name);
1093
+ idx = funcIndex[name];
1094
+ }
1095
+
1096
+ return idx;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
939
1102
  const existing = funcs.find(x => x.name === name);
940
1103
  if (existing) return existing;
941
1104
 
@@ -947,6 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1110
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1111
  }
949
1112
 
1113
+ for (const x of _data) {
1114
+ const copy = { ...x };
1115
+ copy.offset += pages.size * pageSize;
1116
+ data.push(copy);
1117
+ }
1118
+
1119
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1120
+
950
1121
  let baseGlobalIdx, i = 0;
951
1122
  for (const type of globalTypes) {
952
1123
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -969,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
969
1140
  params,
970
1141
  locals,
971
1142
  returns,
972
- returnType: TYPES[returnType ?? 'number'],
1143
+ returnType: returnType ?? TYPES.number,
973
1144
  wasm,
974
1145
  internal: true,
975
1146
  index: currentFuncIndex++
@@ -992,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
992
1163
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
993
1164
  };
994
1165
 
1166
+ // potential future ideas for nan boxing (unused):
995
1167
  // T = JS type, V = value/pointer
996
1168
  // 0bTTT
997
1169
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1015,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
1015
1187
  // 4: internal type
1016
1188
  // 5: pointer
1017
1189
 
1018
- const TYPES = {
1019
- number: 0x00,
1020
- boolean: 0x01,
1021
- string: 0x02,
1022
- undefined: 0x03,
1023
- object: 0x04,
1024
- function: 0x05,
1025
- symbol: 0x06,
1026
- bigint: 0x07,
1190
+ const isExistingProtoFunc = name => {
1191
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
1192
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1027
1193
 
1028
- // these are not "typeof" types but tracked internally
1029
- _array: 0x10,
1030
- _regexp: 0x11
1031
- };
1032
-
1033
- const TYPE_NAMES = {
1034
- [TYPES.number]: 'Number',
1035
- [TYPES.boolean]: 'Boolean',
1036
- [TYPES.string]: 'String',
1037
- [TYPES.undefined]: 'undefined',
1038
- [TYPES.object]: 'Object',
1039
- [TYPES.function]: 'Function',
1040
- [TYPES.symbol]: 'Symbol',
1041
- [TYPES.bigint]: 'BigInt',
1042
-
1043
- [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1194
+ return false;
1045
1195
  };
1046
1196
 
1047
1197
  const getType = (scope, _name) => {
1048
1198
  const name = mapName(_name);
1049
1199
 
1200
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1201
+
1202
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1203
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1204
+
1205
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1206
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1207
 
1053
1208
  let type = TYPES.undefined;
1054
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1209
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1055
1210
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1056
1211
 
1057
- if (name.startsWith('__Array_prototype_') && prototypeFuncs[TYPES._array][name.slice(18)] ||
1058
- name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
1212
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1059
1213
 
1060
1214
  return number(type, Valtype.i32);
1061
1215
  };
@@ -1078,15 +1232,16 @@ const setType = (scope, _name, type) => {
1078
1232
  ];
1079
1233
 
1080
1234
  // throw new Error('could not find var');
1235
+ return [];
1081
1236
  };
1082
1237
 
1083
1238
  const getLastType = scope => {
1084
1239
  scope.gotLastType = true;
1085
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1240
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1086
1241
  };
1087
1242
 
1088
1243
  const setLastType = scope => {
1089
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1244
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1090
1245
  };
1091
1246
 
1092
1247
  const getNodeType = (scope, node) => {
@@ -1094,6 +1249,8 @@ const getNodeType = (scope, node) => {
1094
1249
  if (node.type === 'Literal') {
1095
1250
  if (node.regex) return TYPES._regexp;
1096
1251
 
1252
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
1253
+
1097
1254
  return TYPES[typeof node.value];
1098
1255
  }
1099
1256
 
@@ -1107,6 +1264,27 @@ const getNodeType = (scope, node) => {
1107
1264
 
1108
1265
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1266
  const name = node.callee.name;
1267
+ if (!name) {
1268
+ // iife
1269
+ if (scope.locals['#last_type']) return getLastType(scope);
1270
+
1271
+ // presume
1272
+ // todo: warn here?
1273
+ return TYPES.number;
1274
+ }
1275
+
1276
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1277
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1278
+ if (scope.locals['#last_type']) return getLastType(scope);
1279
+
1280
+ // presume
1281
+ // todo: warn here?
1282
+ return TYPES.number;
1283
+ }
1284
+
1285
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1286
+ }
1287
+
1110
1288
  const func = funcs.find(x => x.name === name);
1111
1289
 
1112
1290
  if (func) {
@@ -1114,7 +1292,7 @@ const getNodeType = (scope, node) => {
1114
1292
  if (func.returnType) return func.returnType;
1115
1293
  }
1116
1294
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1295
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1118
1296
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1297
 
1120
1298
  // check if this is a prototype function
@@ -1125,11 +1303,16 @@ const getNodeType = (scope, node) => {
1125
1303
  const spl = name.slice(2).split('_');
1126
1304
 
1127
1305
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1306
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
1129
1307
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1308
  }
1131
1309
 
1132
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1310
+ if (name.startsWith('__Porffor_wasm_')) {
1311
+ // todo: return undefined for non-returning ops
1312
+ return TYPES.number;
1313
+ }
1314
+
1315
+ if (scope.locals['#last_type']) return getLastType(scope);
1133
1316
 
1134
1317
  // presume
1135
1318
  // todo: warn here?
@@ -1177,6 +1360,15 @@ const getNodeType = (scope, node) => {
1177
1360
 
1178
1361
  if (node.type === 'BinaryExpression') {
1179
1362
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1363
+ if (node.operator !== '+') return TYPES.number;
1364
+
1365
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1366
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1367
+
1368
+ // todo: this should be dynamic but for now only static
1369
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1370
+ if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
1371
+
1180
1372
  return TYPES.number;
1181
1373
 
1182
1374
  // todo: string concat types
@@ -1201,7 +1393,7 @@ const getNodeType = (scope, node) => {
1201
1393
  if (node.operator === '!') return TYPES.boolean;
1202
1394
  if (node.operator === 'void') return TYPES.undefined;
1203
1395
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1396
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
1205
1397
 
1206
1398
  return TYPES.number;
1207
1399
  }
@@ -1210,11 +1402,23 @@ const getNodeType = (scope, node) => {
1210
1402
  // hack: if something.length, number type
1211
1403
  if (node.property.name === 'length') return TYPES.number;
1212
1404
 
1213
- // we cannot guess
1405
+ // ts hack
1406
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1407
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
1408
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
1409
+
1410
+ if (scope.locals['#last_type']) return getLastType(scope);
1411
+
1412
+ // presume
1214
1413
  return TYPES.number;
1215
1414
  }
1216
1415
 
1217
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1416
+ if (node.type === 'TaggedTemplateExpression') {
1417
+ // hack
1418
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1419
+ }
1420
+
1421
+ if (scope.locals['#last_type']) return getLastType(scope);
1218
1422
 
1219
1423
  // presume
1220
1424
  // todo: warn here?
@@ -1227,28 +1431,11 @@ const getNodeType = (scope, node) => {
1227
1431
  return ret;
1228
1432
  };
1229
1433
 
1230
- const toString = (scope, wasm, type) => {
1231
- const tmp = localTmp(scope, '#tostring_tmp');
1232
- return [
1233
- ...wasm,
1234
- [ Opcodes.local_set, tmp ],
1235
-
1236
- ...typeSwitch(scope, type, {
1237
- [TYPES.string]: [
1238
- [ Opcodes.local_get, tmp ]
1239
- ],
1240
- [TYPES.undefined]: [
1241
- // [ Opcodes.]
1242
- ]
1243
- })
1244
- ]
1245
- };
1246
-
1247
1434
  const generateLiteral = (scope, decl, global, name) => {
1248
1435
  if (decl.value === null) return number(NULL);
1249
1436
 
1437
+ // hack: just return 1 for regex literals
1250
1438
  if (decl.regex) {
1251
- scope.regex[name] = decl.regex;
1252
1439
  return number(1);
1253
1440
  }
1254
1441
 
@@ -1261,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
1261
1448
  return number(decl.value ? 1 : 0);
1262
1449
 
1263
1450
  case 'string':
1264
- const str = decl.value;
1265
- const rawElements = new Array(str.length);
1266
- let j = 0;
1267
- for (let i = 0; i < str.length; i++) {
1268
- rawElements[i] = str.charCodeAt(i);
1269
- }
1270
-
1271
- return makeArray(scope, {
1272
- rawElements
1273
- }, global, name, false, 'i16')[0];
1451
+ return makeString(scope, decl.value, global, name);
1274
1452
 
1275
1453
  default:
1276
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1454
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1277
1455
  }
1278
1456
  };
1279
1457
 
@@ -1282,6 +1460,8 @@ const countLeftover = wasm => {
1282
1460
 
1283
1461
  for (let i = 0; i < wasm.length; i++) {
1284
1462
  const inst = wasm[i];
1463
+ if (inst[0] == null) continue;
1464
+
1285
1465
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1286
1466
  if (inst[0] === Opcodes.if) count--;
1287
1467
  if (inst[1] !== Blocktype.void) count++;
@@ -1290,18 +1470,25 @@ const countLeftover = wasm => {
1290
1470
  if (inst[0] === Opcodes.end) depth--;
1291
1471
 
1292
1472
  if (depth === 0)
1293
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1294
- 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)) {}
1295
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1296
- 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;
1297
1477
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1298
1478
  else if (inst[0] === Opcodes.return) count = 0;
1299
1479
  else if (inst[0] === Opcodes.call) {
1300
1480
  let func = funcs.find(x => x.index === inst[1]);
1301
- if (func) {
1302
- count -= func.params.length;
1303
- } else count--;
1304
- 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
+ }
1305
1492
  } else count--;
1306
1493
 
1307
1494
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1319,7 +1506,7 @@ const disposeLeftover = wasm => {
1319
1506
  const generateExp = (scope, decl) => {
1320
1507
  const expression = decl.expression;
1321
1508
 
1322
- const out = generate(scope, expression);
1509
+ const out = generate(scope, expression, undefined, undefined, true);
1323
1510
  disposeLeftover(out);
1324
1511
 
1325
1512
  return out;
@@ -1377,7 +1564,7 @@ const RTArrayUtil = {
1377
1564
  ]
1378
1565
  };
1379
1566
 
1380
- const generateCall = (scope, decl, _global, _name) => {
1567
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1381
1568
  /* const callee = decl.callee;
1382
1569
  const args = decl.arguments;
1383
1570
 
@@ -1393,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
1393
1580
  name = func.name;
1394
1581
  }
1395
1582
 
1396
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1583
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1397
1584
  // literal eval hack
1398
- const code = decl.arguments[0].value;
1399
- 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
+ }
1400
1598
 
1401
1599
  const out = generate(scope, {
1402
1600
  type: 'BlockStatement',
@@ -1410,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
1410
1608
  const finalStatement = parsed.body[parsed.body.length - 1];
1411
1609
  out.push(
1412
1610
  ...getNodeType(scope, finalStatement),
1413
- setLastType(scope)
1611
+ ...setLastType(scope)
1414
1612
  );
1415
1613
  } else if (countLeftover(out) === 0) {
1416
1614
  out.push(...number(UNDEFINED));
1417
1615
  out.push(
1418
1616
  ...number(TYPES.undefined, Valtype.i32),
1419
- setLastType(scope)
1617
+ ...setLastType(scope)
1420
1618
  );
1421
1619
  }
1422
1620
 
@@ -1438,29 +1636,39 @@ const generateCall = (scope, decl, _global, _name) => {
1438
1636
 
1439
1637
  target = { ...decl.callee };
1440
1638
  target.name = spl.slice(0, -1).join('_');
1639
+
1640
+ // failed to lookup name, abort
1641
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1441
1642
  }
1442
1643
 
1443
1644
  // literal.func()
1444
1645
  if (!name && decl.callee.type === 'MemberExpression') {
1445
1646
  // megahack for /regex/.func()
1446
- if (decl.callee.object.regex) {
1447
- const funcName = decl.callee.property.name;
1448
- 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}`;
1449
1651
 
1450
- funcIndex[func.name] = func.index;
1451
- funcs.push(func);
1652
+ if (!funcIndex[rhemynName]) {
1653
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1452
1654
 
1655
+ funcIndex[func.name] = func.index;
1656
+ funcs.push(func);
1657
+ }
1658
+
1659
+ const idx = funcIndex[rhemynName];
1453
1660
  return [
1454
1661
  // make string arg
1455
1662
  ...generate(scope, decl.arguments[0]),
1663
+ Opcodes.i32_to_u,
1664
+ ...getNodeType(scope, decl.arguments[0]),
1456
1665
 
1457
1666
  // call regex func
1458
- Opcodes.i32_to_u,
1459
- [ Opcodes.call, func.index ],
1667
+ [ Opcodes.call, idx ],
1460
1668
  Opcodes.i32_from_u,
1461
1669
 
1462
1670
  ...number(TYPES.boolean, Valtype.i32),
1463
- setLastType(scope)
1671
+ ...setLastType(scope)
1464
1672
  ];
1465
1673
  }
1466
1674
 
@@ -1485,23 +1693,48 @@ const generateCall = (scope, decl, _global, _name) => {
1485
1693
  // }
1486
1694
 
1487
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
+
1488
1716
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1489
- const f = prototypeFuncs[x][protoName];
1490
- if (f) acc[x] = f;
1717
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1491
1718
  return acc;
1492
1719
  }, {});
1493
1720
 
1494
- // no prototype function candidates, ignore
1495
1721
  if (Object.keys(protoCands).length > 0) {
1496
1722
  // use local for cached i32 length as commonly used
1497
1723
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1498
1724
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1499
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1500
1725
 
1501
1726
  // TODO: long-term, prototypes should be their individual separate funcs
1502
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;
1503
1737
  let lengthI32CacheUsed = false;
1504
- const protoBC = {};
1505
1738
  for (const x in protoCands) {
1506
1739
  const protoFunc = protoCands[x];
1507
1740
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1509,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name) => {
1509
1742
  ...RTArrayUtil.getLength(getPointer),
1510
1743
 
1511
1744
  ...number(TYPES.number, Valtype.i32),
1512
- setLastType(scope)
1745
+ ...setLastType(scope)
1513
1746
  ];
1514
1747
  continue;
1515
1748
  }
@@ -1519,6 +1752,7 @@ const generateCall = (scope, decl, _global, _name) => {
1519
1752
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1520
1753
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1521
1754
 
1755
+ let optUnused = false;
1522
1756
  const protoOut = protoFunc(getPointer, {
1523
1757
  getCachedI32: () => {
1524
1758
  lengthI32CacheUsed = true;
@@ -1533,23 +1767,30 @@ const generateCall = (scope, decl, _global, _name) => {
1533
1767
  return makeArray(scope, {
1534
1768
  rawElements: new Array(length)
1535
1769
  }, _global, _name, true, itemType);
1770
+ }, () => {
1771
+ optUnused = true;
1772
+ return unusedValue;
1536
1773
  });
1537
1774
 
1775
+ if (!optUnused) allOptUnused = false;
1776
+
1538
1777
  protoBC[x] = [
1539
- [ Opcodes.block, valtypeBinary ],
1778
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1540
1779
  ...protoOut,
1541
1780
 
1542
1781
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1543
- setLastType(scope),
1782
+ ...setLastType(scope),
1544
1783
  [ Opcodes.end ]
1545
1784
  ];
1546
1785
  }
1547
1786
 
1548
- return [
1549
- ...generate(scope, target),
1787
+ // todo: if some cands use optUnused and some don't, we will probably crash
1550
1788
 
1551
- Opcodes.i32_to_u,
1552
- [ Opcodes.local_set, pointerLocal ],
1789
+ return [
1790
+ ...(usePointerCache ? [
1791
+ ...rawPointer,
1792
+ [ Opcodes.local_set, pointerLocal ],
1793
+ ] : []),
1553
1794
 
1554
1795
  ...(!lengthI32CacheUsed ? [] : [
1555
1796
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1561,13 +1802,22 @@ const generateCall = (scope, decl, _global, _name) => {
1561
1802
 
1562
1803
  // TODO: error better
1563
1804
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1564
- }, valtypeBinary),
1805
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1565
1806
  ];
1566
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
+ }
1567
1817
  }
1568
1818
 
1569
1819
  // TODO: only allows callee as literal
1570
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1820
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1571
1821
 
1572
1822
  let idx = funcIndex[name] ?? importedFuncs[name];
1573
1823
  if (idx === undefined && builtinFuncs[name]) {
@@ -1577,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name) => {
1577
1827
  idx = funcIndex[name];
1578
1828
 
1579
1829
  // infer arguments types from builtins params
1580
- const func = funcs.find(x => x.name === name);
1581
- for (let i = 0; i < decl.arguments.length; i++) {
1582
- const arg = decl.arguments[i];
1583
- if (!arg.name) continue;
1584
-
1585
- const local = scope.locals[arg.name];
1586
- if (!local) continue;
1587
-
1588
- local.type = func.params[i];
1589
- if (local.type === Valtype.v128) {
1590
- // specify vec subtype inferred from last vec type in function name
1591
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1592
- }
1593
- }
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
+ // }
1594
1844
  }
1595
1845
 
1596
1846
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1600,15 +1850,66 @@ const generateCall = (scope, decl, _global, _name) => {
1600
1850
  idx = -1;
1601
1851
  }
1602
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
+ // a, b
1877
+ i32_or: { imms: 0, args: [ true, true ], returns: 1 },
1878
+ };
1879
+
1880
+ const opName = name.slice('__Porffor_wasm_'.length);
1881
+
1882
+ if (wasmOps[opName]) {
1883
+ const op = wasmOps[opName];
1884
+
1885
+ const argOut = [];
1886
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1887
+ ...generate(scope, decl.arguments[i]),
1888
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1889
+ );
1890
+
1891
+ // literals only
1892
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1893
+
1894
+ return [
1895
+ ...argOut,
1896
+ [ Opcodes[opName], ...imms ],
1897
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1898
+ ];
1899
+ }
1900
+ }
1901
+
1603
1902
  if (idx === undefined) {
1604
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1605
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1903
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1904
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1606
1905
  }
1607
1906
 
1608
1907
  const func = funcs.find(x => x.index === idx);
1609
1908
 
1610
1909
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1611
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1910
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1911
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1912
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1612
1913
 
1613
1914
  let args = decl.arguments;
1614
1915
  if (func && args.length < paramCount) {
@@ -1624,14 +1925,24 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1925
  if (func && func.throws) scope.throws = true;
1625
1926
 
1626
1927
  let out = [];
1627
- for (const arg of args) {
1928
+ for (let i = 0; i < args.length; i++) {
1929
+ const arg = args[i];
1628
1930
  out = out.concat(generate(scope, arg));
1629
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1931
+
1932
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1933
+ out.push(Opcodes.i32_to);
1934
+ }
1935
+
1936
+ if (importedFuncs[name] && name.startsWith('profile')) {
1937
+ out.push(Opcodes.i32_to);
1938
+ }
1939
+
1940
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1630
1941
  }
1631
1942
 
1632
1943
  out.push([ Opcodes.call, idx ]);
1633
1944
 
1634
- if (!userFunc) {
1945
+ if (!typedReturns) {
1635
1946
  // let type;
1636
1947
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1637
1948
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1641,7 +1952,11 @@ const generateCall = (scope, decl, _global, _name) => {
1641
1952
  // ...number(type, Valtype.i32),
1642
1953
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1643
1954
  // );
1644
- } else out.push(setLastType(scope));
1955
+ } else out.push(...setLastType(scope));
1956
+
1957
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1958
+ out.push(Opcodes.i32_from);
1959
+ }
1645
1960
 
1646
1961
  return out;
1647
1962
  };
@@ -1649,8 +1964,21 @@ const generateCall = (scope, decl, _global, _name) => {
1649
1964
  const generateNew = (scope, decl, _global, _name) => {
1650
1965
  // hack: basically treat this as a normal call for builtins for now
1651
1966
  const name = mapName(decl.callee.name);
1967
+
1652
1968
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1653
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1969
+
1970
+ if (builtinFuncs[name + '$constructor']) {
1971
+ // custom ...$constructor override builtin func
1972
+ return generateCall(scope, {
1973
+ ...decl,
1974
+ callee: {
1975
+ type: 'Identifier',
1976
+ name: name + '$constructor'
1977
+ }
1978
+ }, _global, _name);
1979
+ }
1980
+
1981
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1654
1982
 
1655
1983
  return generateCall(scope, decl, _global, _name);
1656
1984
  };
@@ -1767,12 +2095,14 @@ const brTable = (input, bc, returns) => {
1767
2095
  };
1768
2096
 
1769
2097
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2098
+ if (!Prefs.bytestring) delete bc[TYPES._bytestring];
2099
+
1770
2100
  const known = knownType(scope, type);
1771
2101
  if (known != null) {
1772
2102
  return bc[known] ?? bc.default;
1773
2103
  }
1774
2104
 
1775
- if (process.argv.includes('-typeswitch-use-brtable'))
2105
+ if (Prefs.typeswitchUseBrtable)
1776
2106
  return brTable(type, bc, returns);
1777
2107
 
1778
2108
  const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
@@ -1782,8 +2112,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1782
2112
  [ Opcodes.block, returns ]
1783
2113
  ];
1784
2114
 
1785
- // todo: use br_table?
1786
-
1787
2115
  for (const x in bc) {
1788
2116
  if (x === 'default') continue;
1789
2117
 
@@ -1807,7 +2135,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1807
2135
  return out;
1808
2136
  };
1809
2137
 
1810
- const allocVar = (scope, name, global = false) => {
2138
+ const allocVar = (scope, name, global = false, type = true) => {
1811
2139
  const target = global ? globals : scope.locals;
1812
2140
 
1813
2141
  // already declared
@@ -1821,8 +2149,10 @@ const allocVar = (scope, name, global = false) => {
1821
2149
  let idx = global ? globalInd++ : scope.localInd++;
1822
2150
  target[name] = { idx, type: valtypeBinary };
1823
2151
 
1824
- let typeIdx = global ? globalInd++ : scope.localInd++;
1825
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2152
+ if (type) {
2153
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2154
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2155
+ }
1826
2156
 
1827
2157
  return idx;
1828
2158
  };
@@ -1837,11 +2167,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1837
2167
  };
1838
2168
 
1839
2169
  const typeAnnoToPorfType = x => {
1840
- if (TYPES[x]) return TYPES[x];
1841
- if (TYPES['_' + x]) return TYPES['_' + x];
2170
+ if (!x) return null;
2171
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
2172
+ if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
1842
2173
 
1843
2174
  switch (x) {
1844
2175
  case 'i32':
2176
+ case 'i64':
2177
+ case 'f64':
1845
2178
  return TYPES.number;
1846
2179
  }
1847
2180
 
@@ -1852,7 +2185,7 @@ const extractTypeAnnotation = decl => {
1852
2185
  let a = decl;
1853
2186
  while (a.typeAnnotation) a = a.typeAnnotation;
1854
2187
 
1855
- let type, elementType;
2188
+ let type = null, elementType = null;
1856
2189
  if (a.typeName) {
1857
2190
  type = a.typeName.name;
1858
2191
  } else if (a.type.endsWith('Keyword')) {
@@ -1865,6 +2198,8 @@ const extractTypeAnnotation = decl => {
1865
2198
  const typeName = type;
1866
2199
  type = typeAnnoToPorfType(type);
1867
2200
 
2201
+ if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
2202
+
1868
2203
  // if (decl.name) console.log(decl.name, { type, elementType });
1869
2204
 
1870
2205
  return { type, typeName, elementType };
@@ -1877,10 +2212,13 @@ const generateVar = (scope, decl) => {
1877
2212
 
1878
2213
  // global variable if in top scope (main) and var ..., or if wanted
1879
2214
  const global = topLevel || decl._bare; // decl.kind === 'var';
2215
+ const target = global ? globals : scope.locals;
1880
2216
 
1881
2217
  for (const x of decl.declarations) {
1882
2218
  const name = mapName(x.id.name);
1883
2219
 
2220
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2221
+
1884
2222
  if (x.init && isFuncType(x.init.type)) {
1885
2223
  // hack for let a = function () { ... }
1886
2224
  x.init.id = { name };
@@ -1896,16 +2234,29 @@ const generateVar = (scope, decl) => {
1896
2234
  continue; // always ignore
1897
2235
  }
1898
2236
 
1899
- let idx = allocVar(scope, name, global);
2237
+ // // generate init before allocating var
2238
+ // let generated;
2239
+ // if (x.init) generated = generate(scope, x.init, global, name);
2240
+
2241
+ const typed = typedInput && x.id.typeAnnotation;
2242
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
1900
2243
 
1901
- if (typedInput && x.id.typeAnnotation) {
2244
+ if (typed) {
1902
2245
  addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1903
2246
  }
1904
2247
 
1905
2248
  if (x.init) {
1906
- out = out.concat(generate(scope, x.init, global, name));
1907
-
1908
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2249
+ const generated = generate(scope, x.init, global, name);
2250
+ if (scope.arrays?.get(name) != null) {
2251
+ // hack to set local as pointer before
2252
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2253
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2254
+ generated.pop();
2255
+ out = out.concat(generated);
2256
+ } else {
2257
+ out = out.concat(generated);
2258
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2259
+ }
1909
2260
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1910
2261
  }
1911
2262
 
@@ -1916,7 +2267,8 @@ const generateVar = (scope, decl) => {
1916
2267
  return out;
1917
2268
  };
1918
2269
 
1919
- const generateAssign = (scope, decl) => {
2270
+ // todo: optimize this func for valueUnused
2271
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1920
2272
  const { type, name } = decl.left;
1921
2273
 
1922
2274
  if (type === 'ObjectPattern') {
@@ -1931,22 +2283,30 @@ const generateAssign = (scope, decl) => {
1931
2283
  return [];
1932
2284
  }
1933
2285
 
2286
+ const op = decl.operator.slice(0, -1) || '=';
2287
+
1934
2288
  // hack: .length setter
1935
2289
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1936
2290
  const name = decl.left.object.name;
1937
- const pointer = arrays.get(name);
2291
+ const pointer = scope.arrays?.get(name);
1938
2292
 
1939
- const aotPointer = pointer != null;
2293
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1940
2294
 
1941
2295
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2296
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1942
2297
 
1943
2298
  return [
1944
2299
  ...(aotPointer ? number(0, Valtype.i32) : [
1945
2300
  ...generate(scope, decl.left.object),
1946
2301
  Opcodes.i32_to_u
1947
2302
  ]),
2303
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1948
2304
 
1949
- ...generate(scope, decl.right),
2305
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2306
+ [ Opcodes.local_get, pointerTmp ],
2307
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2308
+ Opcodes.i32_from_u
2309
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
1950
2310
  [ Opcodes.local_tee, newValueTmp ],
1951
2311
 
1952
2312
  Opcodes.i32_to_u,
@@ -1956,14 +2316,12 @@ const generateAssign = (scope, decl) => {
1956
2316
  ];
1957
2317
  }
1958
2318
 
1959
- const op = decl.operator.slice(0, -1) || '=';
1960
-
1961
2319
  // arr[i]
1962
2320
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1963
2321
  const name = decl.left.object.name;
1964
- const pointer = arrays.get(name);
2322
+ const pointer = scope.arrays?.get(name);
1965
2323
 
1966
- const aotPointer = pointer != null;
2324
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1967
2325
 
1968
2326
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1969
2327
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
@@ -2019,6 +2377,8 @@ const generateAssign = (scope, decl) => {
2019
2377
  ];
2020
2378
  }
2021
2379
 
2380
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2381
+
2022
2382
  const [ local, isGlobal ] = lookupName(scope, name);
2023
2383
 
2024
2384
  if (local === undefined) {
@@ -2065,9 +2425,7 @@ const generateAssign = (scope, decl) => {
2065
2425
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
2066
2426
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2067
2427
 
2068
- getLastType(scope),
2069
- // hack: type is idx+1
2070
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2428
+ ...setType(scope, name, getLastType(scope))
2071
2429
  ];
2072
2430
  }
2073
2431
 
@@ -2078,9 +2436,7 @@ const generateAssign = (scope, decl) => {
2078
2436
 
2079
2437
  // todo: string concat types
2080
2438
 
2081
- // hack: type is idx+1
2082
- ...number(TYPES.number, Valtype.i32),
2083
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2439
+ ...setType(scope, name, TYPES.number)
2084
2440
  ];
2085
2441
  };
2086
2442
 
@@ -2126,7 +2482,7 @@ const generateUnary = (scope, decl) => {
2126
2482
  return out;
2127
2483
  }
2128
2484
 
2129
- case 'delete':
2485
+ case 'delete': {
2130
2486
  let toReturn = true, toGenerate = true;
2131
2487
 
2132
2488
  if (decl.argument.type === 'Identifier') {
@@ -2148,38 +2504,60 @@ const generateUnary = (scope, decl) => {
2148
2504
 
2149
2505
  out.push(...number(toReturn ? 1 : 0));
2150
2506
  return out;
2507
+ }
2151
2508
 
2152
- case 'typeof':
2153
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2509
+ case 'typeof': {
2510
+ let overrideType, toGenerate = true;
2511
+
2512
+ if (decl.argument.type === 'Identifier') {
2513
+ const out = generateIdent(scope, decl.argument);
2514
+
2515
+ // if ReferenceError (undeclared var), ignore and return undefined
2516
+ if (out[1]) {
2517
+ // does not exist (2 ops from throw)
2518
+ overrideType = number(TYPES.undefined, Valtype.i32);
2519
+ toGenerate = false;
2520
+ }
2521
+ }
2522
+
2523
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2524
+ disposeLeftover(out);
2525
+
2526
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2154
2527
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2155
2528
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2156
2529
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2157
2530
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2158
2531
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2159
2532
 
2533
+ [TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2534
+
2160
2535
  // object and internal types
2161
2536
  default: makeString(scope, 'object', false, '#typeof_result'),
2162
- });
2537
+ }));
2538
+
2539
+ return out;
2540
+ }
2163
2541
 
2164
2542
  default:
2165
- return todo(`unary operator ${decl.operator} not implemented yet`);
2543
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2166
2544
  }
2167
2545
  };
2168
2546
 
2169
- const generateUpdate = (scope, decl) => {
2547
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2170
2548
  const { name } = decl.argument;
2171
2549
 
2172
2550
  const [ local, isGlobal ] = lookupName(scope, name);
2173
2551
 
2174
2552
  if (local === undefined) {
2175
- return todo(`update expression with undefined variable`);
2553
+ return todo(scope, `update expression with undefined variable`, true);
2176
2554
  }
2177
2555
 
2178
2556
  const idx = local.idx;
2179
2557
  const out = [];
2180
2558
 
2181
2559
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2182
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2560
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2183
2561
 
2184
2562
  switch (decl.operator) {
2185
2563
  case '++':
@@ -2192,7 +2570,7 @@ const generateUpdate = (scope, decl) => {
2192
2570
  }
2193
2571
 
2194
2572
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2195
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2573
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2196
2574
 
2197
2575
  return out;
2198
2576
  };
@@ -2232,7 +2610,7 @@ const generateConditional = (scope, decl) => {
2232
2610
  // note type
2233
2611
  out.push(
2234
2612
  ...getNodeType(scope, decl.consequent),
2235
- setLastType(scope)
2613
+ ...setLastType(scope)
2236
2614
  );
2237
2615
 
2238
2616
  out.push([ Opcodes.else ]);
@@ -2241,7 +2619,7 @@ const generateConditional = (scope, decl) => {
2241
2619
  // note type
2242
2620
  out.push(
2243
2621
  ...getNodeType(scope, decl.alternate),
2244
- setLastType(scope)
2622
+ ...setLastType(scope)
2245
2623
  );
2246
2624
 
2247
2625
  out.push([ Opcodes.end ]);
@@ -2255,15 +2633,17 @@ const generateFor = (scope, decl) => {
2255
2633
  const out = [];
2256
2634
 
2257
2635
  if (decl.init) {
2258
- out.push(...generate(scope, decl.init));
2636
+ out.push(...generate(scope, decl.init, false, undefined, true));
2259
2637
  disposeLeftover(out);
2260
2638
  }
2261
2639
 
2262
2640
  out.push([ Opcodes.loop, Blocktype.void ]);
2263
2641
  depth.push('for');
2264
2642
 
2265
- out.push(...generate(scope, decl.test));
2266
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2643
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2644
+ else out.push(...number(1, Valtype.i32));
2645
+
2646
+ out.push([ Opcodes.if, Blocktype.void ]);
2267
2647
  depth.push('if');
2268
2648
 
2269
2649
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2271,8 +2651,7 @@ const generateFor = (scope, decl) => {
2271
2651
  out.push(...generate(scope, decl.body));
2272
2652
  out.push([ Opcodes.end ]);
2273
2653
 
2274
- out.push(...generate(scope, decl.update));
2275
- depth.pop();
2654
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2276
2655
 
2277
2656
  out.push([ Opcodes.br, 1 ]);
2278
2657
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2300,6 +2679,36 @@ const generateWhile = (scope, decl) => {
2300
2679
  return out;
2301
2680
  };
2302
2681
 
2682
+ const generateDoWhile = (scope, decl) => {
2683
+ const out = [];
2684
+
2685
+ out.push([ Opcodes.loop, Blocktype.void ]);
2686
+ depth.push('dowhile');
2687
+
2688
+ // block for break (includes all)
2689
+ out.push([ Opcodes.block, Blocktype.void ]);
2690
+ depth.push('block');
2691
+
2692
+ // block for continue
2693
+ // includes body but not test+loop so we can exit body at anytime
2694
+ // and still test+loop after
2695
+ out.push([ Opcodes.block, Blocktype.void ]);
2696
+ depth.push('block');
2697
+
2698
+ out.push(...generate(scope, decl.body));
2699
+
2700
+ out.push([ Opcodes.end ]);
2701
+ depth.pop();
2702
+
2703
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2704
+ out.push([ Opcodes.br_if, 1 ]);
2705
+
2706
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2707
+ depth.pop(); depth.pop();
2708
+
2709
+ return out;
2710
+ };
2711
+
2303
2712
  const generateForOf = (scope, decl) => {
2304
2713
  const out = [];
2305
2714
 
@@ -2329,8 +2738,17 @@ const generateForOf = (scope, decl) => {
2329
2738
  // setup local for left
2330
2739
  generate(scope, decl.left);
2331
2740
 
2332
- const leftName = decl.left.declarations[0].id.name;
2741
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2742
+ if (!leftName && decl.left.name) {
2743
+ leftName = decl.left.name;
2744
+
2745
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2746
+ }
2747
+
2748
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2749
+
2333
2750
  const [ local, isGlobal ] = lookupName(scope, leftName);
2751
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2334
2752
 
2335
2753
  depth.push('block');
2336
2754
  depth.push('block');
@@ -2338,13 +2756,15 @@ const generateForOf = (scope, decl) => {
2338
2756
  // // todo: we should only do this for strings but we don't know at compile-time :(
2339
2757
  // hack: this is naughty and will break things!
2340
2758
  let newOut = number(0, Valtype.f64), newPointer = -1;
2341
- if (pages.hasString) {
2759
+ if (pages.hasAnyString) {
2760
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2342
2761
  0, [ newOut, newPointer ] = makeArray(scope, {
2343
2762
  rawElements: new Array(1)
2344
2763
  }, isGlobal, leftName, true, 'i16');
2345
2764
  }
2346
2765
 
2347
2766
  // set type for local
2767
+ // todo: optimize away counter and use end pointer
2348
2768
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2349
2769
  [TYPES._array]: [
2350
2770
  ...setType(scope, leftName, TYPES.number),
@@ -2429,6 +2849,56 @@ const generateForOf = (scope, decl) => {
2429
2849
  [ Opcodes.end ],
2430
2850
  [ Opcodes.end ]
2431
2851
  ],
2852
+ [TYPES._bytestring]: [
2853
+ ...setType(scope, leftName, TYPES._bytestring),
2854
+
2855
+ [ Opcodes.loop, Blocktype.void ],
2856
+
2857
+ // setup new/out array
2858
+ ...newOut,
2859
+ [ Opcodes.drop ],
2860
+
2861
+ ...number(0, Valtype.i32), // base 0 for store after
2862
+
2863
+ // load current string ind {arg}
2864
+ [ Opcodes.local_get, pointer ],
2865
+ [ Opcodes.local_get, counter ],
2866
+ [ Opcodes.i32_add ],
2867
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2868
+
2869
+ // store to new string ind 0
2870
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2871
+
2872
+ // return new string (page)
2873
+ ...number(newPointer),
2874
+
2875
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2876
+
2877
+ [ Opcodes.block, Blocktype.void ],
2878
+ [ Opcodes.block, Blocktype.void ],
2879
+ ...generate(scope, decl.body),
2880
+ [ Opcodes.end ],
2881
+
2882
+ // increment iter pointer
2883
+ // [ Opcodes.local_get, pointer ],
2884
+ // ...number(1, Valtype.i32),
2885
+ // [ Opcodes.i32_add ],
2886
+ // [ Opcodes.local_set, pointer ],
2887
+
2888
+ // increment counter by 1
2889
+ [ Opcodes.local_get, counter ],
2890
+ ...number(1, Valtype.i32),
2891
+ [ Opcodes.i32_add ],
2892
+ [ Opcodes.local_tee, counter ],
2893
+
2894
+ // loop if counter != length
2895
+ [ Opcodes.local_get, length ],
2896
+ [ Opcodes.i32_ne ],
2897
+ [ Opcodes.br_if, 1 ],
2898
+
2899
+ [ Opcodes.end ],
2900
+ [ Opcodes.end ]
2901
+ ],
2432
2902
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2433
2903
  }, Blocktype.void));
2434
2904
 
@@ -2439,28 +2909,65 @@ const generateForOf = (scope, decl) => {
2439
2909
  return out;
2440
2910
  };
2441
2911
 
2912
+ // find the nearest loop in depth map by type
2442
2913
  const getNearestLoop = () => {
2443
2914
  for (let i = depth.length - 1; i >= 0; i--) {
2444
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2915
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2445
2916
  }
2446
2917
 
2447
2918
  return -1;
2448
2919
  };
2449
2920
 
2450
2921
  const generateBreak = (scope, decl) => {
2451
- const nearestLoop = depth.length - getNearestLoop();
2922
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2923
+ const type = depth[target];
2924
+
2925
+ // different loop types have different branch offsets
2926
+ // as they have different wasm block/loop/if structures
2927
+ // we need to use the right offset by type to branch to the one we want
2928
+ // for a break: exit the loop without executing anything else inside it
2929
+ const offset = ({
2930
+ for: 2, // loop > if (wanted branch) > block (we are here)
2931
+ while: 2, // loop > if (wanted branch) (we are here)
2932
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2933
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2934
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2935
+ })[type];
2936
+
2452
2937
  return [
2453
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2938
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2454
2939
  ];
2455
2940
  };
2456
2941
 
2457
2942
  const generateContinue = (scope, decl) => {
2458
- const nearestLoop = depth.length - getNearestLoop();
2943
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2944
+ const type = depth[target];
2945
+
2946
+ // different loop types have different branch offsets
2947
+ // as they have different wasm block/loop/if structures
2948
+ // we need to use the right offset by type to branch to the one we want
2949
+ // for a continue: do test for the loop, and then loop depending on that success
2950
+ const offset = ({
2951
+ for: 3, // loop (wanted branch) > if > block (we are here)
2952
+ while: 1, // loop (wanted branch) > if (we are here)
2953
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2954
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2955
+ })[type];
2956
+
2459
2957
  return [
2460
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2958
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2461
2959
  ];
2462
2960
  };
2463
2961
 
2962
+ const generateLabel = (scope, decl) => {
2963
+ scope.labels ??= new Map();
2964
+
2965
+ const name = decl.label.name;
2966
+ scope.labels.set(name, depth.length);
2967
+
2968
+ return generate(scope, decl.body);
2969
+ };
2970
+
2464
2971
  const generateThrow = (scope, decl) => {
2465
2972
  scope.throws = true;
2466
2973
 
@@ -2469,7 +2976,7 @@ const generateThrow = (scope, decl) => {
2469
2976
  // hack: throw new X("...") -> throw "..."
2470
2977
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2471
2978
  constructor = decl.argument.callee.name;
2472
- message = decl.argument.arguments[0].value;
2979
+ message = decl.argument.arguments[0]?.value ?? '';
2473
2980
  }
2474
2981
 
2475
2982
  if (tags.length === 0) tags.push({
@@ -2481,6 +2988,9 @@ const generateThrow = (scope, decl) => {
2481
2988
  let exceptId = exceptions.push({ constructor, message }) - 1;
2482
2989
  let tagIdx = tags[0].idx;
2483
2990
 
2991
+ scope.exceptions ??= [];
2992
+ scope.exceptions.push(exceptId);
2993
+
2484
2994
  // todo: write a description of how this works lol
2485
2995
 
2486
2996
  return [
@@ -2490,7 +3000,7 @@ const generateThrow = (scope, decl) => {
2490
3000
  };
2491
3001
 
2492
3002
  const generateTry = (scope, decl) => {
2493
- if (decl.finalizer) return todo('try finally not implemented yet');
3003
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2494
3004
 
2495
3005
  const out = [];
2496
3006
 
@@ -2521,29 +3031,35 @@ const generateAssignPat = (scope, decl) => {
2521
3031
  // TODO
2522
3032
  // if identifier declared, use that
2523
3033
  // else, use default (right)
2524
- return todo('assignment pattern (optional arg)');
3034
+ return todo(scope, 'assignment pattern (optional arg)');
2525
3035
  };
2526
3036
 
2527
3037
  let pages = new Map();
2528
- const allocPage = (reason, type) => {
3038
+ const allocPage = (scope, reason, type) => {
2529
3039
  if (pages.has(reason)) return pages.get(reason).ind;
2530
3040
 
2531
3041
  if (reason.startsWith('array:')) pages.hasArray = true;
2532
3042
  if (reason.startsWith('string:')) pages.hasString = true;
3043
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3044
+ if (reason.includes('string:')) pages.hasAnyString = true;
2533
3045
 
2534
3046
  const ind = pages.size;
2535
3047
  pages.set(reason, { ind, type });
2536
3048
 
2537
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3049
+ scope.pages ??= new Map();
3050
+ scope.pages.set(reason, { ind, type });
3051
+
3052
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2538
3053
 
2539
3054
  return ind;
2540
3055
  };
2541
3056
 
3057
+ // todo: add scope.pages
2542
3058
  const freePage = reason => {
2543
3059
  const { ind } = pages.get(reason);
2544
3060
  pages.delete(reason);
2545
3061
 
2546
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3062
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2547
3063
 
2548
3064
  return ind;
2549
3065
  };
@@ -2563,38 +3079,53 @@ const StoreOps = {
2563
3079
  f64: Opcodes.f64_store,
2564
3080
 
2565
3081
  // expects i32 input!
2566
- i16: Opcodes.i32_store16
3082
+ i8: Opcodes.i32_store8,
3083
+ i16: Opcodes.i32_store16,
2567
3084
  };
2568
3085
 
2569
3086
  let data = [];
2570
3087
 
2571
- const compileBytes = (val, itemType, signed = true) => {
3088
+ const compileBytes = (val, itemType) => {
2572
3089
  // todo: this is a mess and needs confirming / ????
2573
3090
  switch (itemType) {
2574
3091
  case 'i8': return [ val % 256 ];
2575
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2576
-
2577
- case 'i32':
2578
- case 'i64':
2579
- return enforceFourBytes(signedLEB128(val));
3092
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3093
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3094
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3095
+ // todo: i64
2580
3096
 
2581
3097
  case 'f64': return ieee754_binary64(val);
2582
3098
  }
2583
3099
  };
2584
3100
 
3101
+ const getAllocType = itemType => {
3102
+ switch (itemType) {
3103
+ case 'i8': return 'bytestring';
3104
+ case 'i16': return 'string';
3105
+
3106
+ default: return 'array';
3107
+ }
3108
+ };
3109
+
2585
3110
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2586
3111
  const out = [];
2587
3112
 
3113
+ scope.arrays ??= new Map();
3114
+
2588
3115
  let firstAssign = false;
2589
- if (!arrays.has(name) || name === '$undeclared') {
3116
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2590
3117
  firstAssign = true;
2591
3118
 
2592
3119
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2593
3120
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2594
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3121
+
3122
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3123
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2595
3124
  }
2596
3125
 
2597
- const pointer = arrays.get(name);
3126
+ const pointer = scope.arrays.get(name);
3127
+
3128
+ const local = global ? globals[name] : scope.locals[name];
2598
3129
 
2599
3130
  const useRawElements = !!decl.rawElements;
2600
3131
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2602,19 +3133,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2602
3133
  const valtype = itemTypeToValtype[itemType];
2603
3134
  const length = elements.length;
2604
3135
 
2605
- if (firstAssign && useRawElements) {
2606
- let bytes = compileBytes(length, 'i32');
3136
+ if (firstAssign && useRawElements && !Prefs.noData) {
3137
+ // if length is 0 memory/data will just be 0000... anyway
3138
+ if (length !== 0) {
3139
+ let bytes = compileBytes(length, 'i32');
2607
3140
 
2608
- if (!initEmpty) for (let i = 0; i < length; i++) {
2609
- if (elements[i] == null) continue;
3141
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3142
+ if (elements[i] == null) continue;
2610
3143
 
2611
- bytes.push(...compileBytes(elements[i], itemType));
2612
- }
3144
+ bytes.push(...compileBytes(elements[i], itemType));
3145
+ }
2613
3146
 
2614
- data.push({
2615
- offset: pointer,
2616
- bytes
2617
- });
3147
+ const ind = data.push({
3148
+ offset: pointer,
3149
+ bytes
3150
+ }) - 1;
3151
+
3152
+ scope.data ??= [];
3153
+ scope.data.push(ind);
3154
+ }
2618
3155
 
2619
3156
  // local value as pointer
2620
3157
  out.push(...number(pointer));
@@ -2622,11 +3159,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2622
3159
  return [ out, pointer ];
2623
3160
  }
2624
3161
 
3162
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3163
+ if (pointerTmp != null) {
3164
+ out.push(
3165
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3166
+ Opcodes.i32_to_u,
3167
+ [ Opcodes.local_set, pointerTmp ]
3168
+ );
3169
+ }
3170
+
3171
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3172
+
2625
3173
  // store length as 0th array
2626
3174
  out.push(
2627
- ...number(0, Valtype.i32),
3175
+ ...pointerWasm,
2628
3176
  ...number(length, Valtype.i32),
2629
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3177
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2630
3178
  );
2631
3179
 
2632
3180
  const storeOp = StoreOps[itemType];
@@ -2635,43 +3183,68 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2635
3183
  if (elements[i] == null) continue;
2636
3184
 
2637
3185
  out.push(
2638
- ...number(0, Valtype.i32),
3186
+ ...pointerWasm,
2639
3187
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2640
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3188
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2641
3189
  );
2642
3190
  }
2643
3191
 
2644
3192
  // local value as pointer
2645
- out.push(...number(pointer));
3193
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2646
3194
 
2647
3195
  return [ out, pointer ];
2648
3196
  };
2649
3197
 
2650
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3198
+ const byteStringable = str => {
3199
+ if (!Prefs.bytestring) return false;
3200
+
3201
+ for (let i = 0; i < str.length; i++) {
3202
+ if (str.charCodeAt(i) > 0xFF) return false;
3203
+ }
3204
+
3205
+ return true;
3206
+ };
3207
+
3208
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2651
3209
  const rawElements = new Array(str.length);
3210
+ let byteStringable = Prefs.bytestring;
2652
3211
  for (let i = 0; i < str.length; i++) {
2653
- rawElements[i] = str.charCodeAt(i);
3212
+ const c = str.charCodeAt(i);
3213
+ rawElements[i] = c;
3214
+
3215
+ if (byteStringable && c > 0xFF) byteStringable = false;
2654
3216
  }
2655
3217
 
3218
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3219
+
2656
3220
  return makeArray(scope, {
2657
3221
  rawElements
2658
- }, global, name, false, 'i16')[0];
3222
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2659
3223
  };
2660
3224
 
2661
- let arrays = new Map();
2662
3225
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2663
3226
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2664
3227
  };
2665
3228
 
2666
3229
  export const generateMember = (scope, decl, _global, _name) => {
2667
3230
  const name = decl.object.name;
2668
- const pointer = arrays.get(name);
3231
+ const pointer = scope.arrays?.get(name);
2669
3232
 
2670
- const aotPointer = pointer != null;
3233
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
2671
3234
 
2672
3235
  // hack: .length
2673
3236
  if (decl.property.name === 'length') {
2674
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3237
+ const func = funcs.find(x => x.name === name);
3238
+ if (func) {
3239
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3240
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3241
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3242
+ }
3243
+
3244
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3245
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3246
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3247
+
2675
3248
  return [
2676
3249
  ...(aotPointer ? number(0, Valtype.i32) : [
2677
3250
  ...generate(scope, decl.object),
@@ -2683,10 +3256,13 @@ export const generateMember = (scope, decl, _global, _name) => {
2683
3256
  ];
2684
3257
  }
2685
3258
 
3259
+ const object = generate(scope, decl.object);
3260
+ const property = generate(scope, decl.property);
3261
+
2686
3262
  // // todo: we should only do this for strings but we don't know at compile-time :(
2687
3263
  // hack: this is naughty and will break things!
2688
3264
  let newOut = number(0, valtypeBinary), newPointer = -1;
2689
- if (pages.hasString) {
3265
+ if (pages.hasAnyString) {
2690
3266
  0, [ newOut, newPointer ] = makeArray(scope, {
2691
3267
  rawElements: new Array(1)
2692
3268
  }, _global, _name, true, 'i16');
@@ -2695,7 +3271,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2695
3271
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2696
3272
  [TYPES._array]: [
2697
3273
  // get index as valtype
2698
- ...generate(scope, decl.property),
3274
+ ...property,
2699
3275
 
2700
3276
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2701
3277
  Opcodes.i32_to_u,
@@ -2703,7 +3279,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2703
3279
  [ Opcodes.i32_mul ],
2704
3280
 
2705
3281
  ...(aotPointer ? [] : [
2706
- ...generate(scope, decl.object),
3282
+ ...object,
2707
3283
  Opcodes.i32_to_u,
2708
3284
  [ Opcodes.i32_add ]
2709
3285
  ]),
@@ -2712,7 +3288,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2712
3288
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2713
3289
 
2714
3290
  ...number(TYPES.number, Valtype.i32),
2715
- setLastType(scope)
3291
+ ...setLastType(scope)
2716
3292
  ],
2717
3293
 
2718
3294
  [TYPES.string]: [
@@ -2722,14 +3298,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2722
3298
 
2723
3299
  ...number(0, Valtype.i32), // base 0 for store later
2724
3300
 
2725
- ...generate(scope, decl.property),
2726
-
3301
+ ...property,
2727
3302
  Opcodes.i32_to_u,
3303
+
2728
3304
  ...number(ValtypeSize.i16, Valtype.i32),
2729
3305
  [ Opcodes.i32_mul ],
2730
3306
 
2731
3307
  ...(aotPointer ? [] : [
2732
- ...generate(scope, decl.object),
3308
+ ...object,
2733
3309
  Opcodes.i32_to_u,
2734
3310
  [ Opcodes.i32_add ]
2735
3311
  ]),
@@ -2744,10 +3320,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2744
3320
  ...number(newPointer),
2745
3321
 
2746
3322
  ...number(TYPES.string, Valtype.i32),
2747
- setLastType(scope)
3323
+ ...setLastType(scope)
2748
3324
  ],
3325
+ [TYPES._bytestring]: [
3326
+ // setup new/out array
3327
+ ...newOut,
3328
+ [ Opcodes.drop ],
3329
+
3330
+ ...number(0, Valtype.i32), // base 0 for store later
3331
+
3332
+ ...property,
3333
+ Opcodes.i32_to_u,
3334
+
3335
+ ...(aotPointer ? [] : [
3336
+ ...object,
3337
+ Opcodes.i32_to_u,
3338
+ [ Opcodes.i32_add ]
3339
+ ]),
3340
+
3341
+ // load current string ind {arg}
3342
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2749
3343
 
2750
- default: [ [ Opcodes.unreachable ] ]
3344
+ // store to new string ind 0
3345
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3346
+
3347
+ // return new string (page)
3348
+ ...number(newPointer),
3349
+
3350
+ ...number(TYPES._bytestring, Valtype.i32),
3351
+ ...setLastType(scope)
3352
+ ],
3353
+
3354
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2751
3355
  });
2752
3356
  };
2753
3357
 
@@ -2757,25 +3361,36 @@ const objectHack = node => {
2757
3361
  if (!node) return node;
2758
3362
 
2759
3363
  if (node.type === 'MemberExpression') {
2760
- if (node.computed || node.optional) return node;
3364
+ const out = (() => {
3365
+ if (node.computed || node.optional) return;
2761
3366
 
2762
- let objectName = node.object.name;
3367
+ let objectName = node.object.name;
2763
3368
 
2764
- // if object is not identifier or another member exp, give up
2765
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3369
+ // if object is not identifier or another member exp, give up
3370
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3371
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2766
3372
 
2767
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3373
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2768
3374
 
2769
- // if .length, give up (hack within a hack!)
2770
- if (node.property.name === 'length') return node;
3375
+ // if .length, give up (hack within a hack!)
3376
+ if (node.property.name === 'length') {
3377
+ node.object = objectHack(node.object);
3378
+ return;
3379
+ }
2771
3380
 
2772
- const name = '__' + objectName + '_' + node.property.name;
2773
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3381
+ // no object name, give up
3382
+ if (!objectName) return;
2774
3383
 
2775
- return {
2776
- type: 'Identifier',
2777
- name
2778
- };
3384
+ const name = '__' + objectName + '_' + node.property.name;
3385
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3386
+
3387
+ return {
3388
+ type: 'Identifier',
3389
+ name
3390
+ };
3391
+ })();
3392
+
3393
+ if (out) return out;
2779
3394
  }
2780
3395
 
2781
3396
  for (const x in node) {
@@ -2789,8 +3404,8 @@ const objectHack = node => {
2789
3404
  };
2790
3405
 
2791
3406
  const generateFunc = (scope, decl) => {
2792
- if (decl.async) return todo('async functions are not supported');
2793
- if (decl.generator) return todo('generator functions are not supported');
3407
+ if (decl.async) return todo(scope, 'async functions are not supported');
3408
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2794
3409
 
2795
3410
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2796
3411
  const params = decl.params ?? [];
@@ -2806,6 +3421,11 @@ const generateFunc = (scope, decl) => {
2806
3421
  name
2807
3422
  };
2808
3423
 
3424
+ if (typedInput && decl.returnType) {
3425
+ innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
3426
+ innerScope.returns = [ valtypeBinary ];
3427
+ }
3428
+
2809
3429
  for (let i = 0; i < params.length; i++) {
2810
3430
  allocVar(innerScope, params[i].name, false);
2811
3431
 
@@ -2827,13 +3447,13 @@ const generateFunc = (scope, decl) => {
2827
3447
  const func = {
2828
3448
  name,
2829
3449
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2830
- returns: innerScope.returns,
2831
- locals: innerScope.locals,
2832
- throws: innerScope.throws,
2833
- index: currentFuncIndex++
3450
+ index: currentFuncIndex++,
3451
+ ...innerScope
2834
3452
  };
2835
3453
  funcIndex[name] = func.index;
2836
3454
 
3455
+ if (name === 'main') func.gotLastType = true;
3456
+
2837
3457
  // quick hack fixes
2838
3458
  for (const inst of wasm) {
2839
3459
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2885,7 +3505,7 @@ const internalConstrs = {
2885
3505
 
2886
3506
  // todo: check in wasm instead of here
2887
3507
  const literalValue = arg.value ?? 0;
2888
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3508
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2889
3509
 
2890
3510
  return [
2891
3511
  ...number(0, Valtype.i32),
@@ -2896,7 +3516,8 @@ const internalConstrs = {
2896
3516
  ...number(pointer)
2897
3517
  ];
2898
3518
  },
2899
- type: TYPES._array
3519
+ type: TYPES._array,
3520
+ length: 1
2900
3521
  },
2901
3522
 
2902
3523
  __Array_of: {
@@ -2908,7 +3529,131 @@ const internalConstrs = {
2908
3529
  }, global, name);
2909
3530
  },
2910
3531
  type: TYPES._array,
3532
+ notConstr: true,
3533
+ length: 0
3534
+ },
3535
+
3536
+ __Porffor_fastOr: {
3537
+ generate: (scope, decl) => {
3538
+ const out = [];
3539
+
3540
+ for (let i = 0; i < decl.arguments.length; i++) {
3541
+ out.push(
3542
+ ...generate(scope, decl.arguments[i]),
3543
+ Opcodes.i32_to_u,
3544
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3545
+ );
3546
+ }
3547
+
3548
+ out.push(Opcodes.i32_from_u);
3549
+
3550
+ return out;
3551
+ },
3552
+ type: TYPES.boolean,
2911
3553
  notConstr: true
3554
+ },
3555
+
3556
+ __Porffor_fastAnd: {
3557
+ generate: (scope, decl) => {
3558
+ const out = [];
3559
+
3560
+ for (let i = 0; i < decl.arguments.length; i++) {
3561
+ out.push(
3562
+ ...generate(scope, decl.arguments[i]),
3563
+ Opcodes.i32_to_u,
3564
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3565
+ );
3566
+ }
3567
+
3568
+ out.push(Opcodes.i32_from_u);
3569
+
3570
+ return out;
3571
+ },
3572
+ type: TYPES.boolean,
3573
+ notConstr: true
3574
+ },
3575
+
3576
+ Boolean: {
3577
+ generate: (scope, decl) => {
3578
+ // todo: boolean object when used as constructor
3579
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3580
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3581
+ },
3582
+ type: TYPES.boolean,
3583
+ length: 1
3584
+ },
3585
+
3586
+ __Math_max: {
3587
+ generate: (scope, decl) => {
3588
+ const out = [
3589
+ ...number(-Infinity)
3590
+ ];
3591
+
3592
+ for (let i = 0; i < decl.arguments.length; i++) {
3593
+ out.push(
3594
+ ...generate(scope, decl.arguments[i]),
3595
+ [ Opcodes.f64_max ]
3596
+ );
3597
+ }
3598
+
3599
+ return out;
3600
+ },
3601
+ type: TYPES.number,
3602
+ notConstr: true,
3603
+ length: 2
3604
+ },
3605
+
3606
+ __Math_min: {
3607
+ generate: (scope, decl) => {
3608
+ const out = [
3609
+ ...number(Infinity)
3610
+ ];
3611
+
3612
+ for (let i = 0; i < decl.arguments.length; i++) {
3613
+ out.push(
3614
+ ...generate(scope, decl.arguments[i]),
3615
+ [ Opcodes.f64_min ]
3616
+ );
3617
+ }
3618
+
3619
+ return out;
3620
+ },
3621
+ type: TYPES.number,
3622
+ notConstr: true,
3623
+ length: 2
3624
+ },
3625
+
3626
+ __console_log: {
3627
+ generate: (scope, decl) => {
3628
+ const out = [];
3629
+
3630
+ for (let i = 0; i < decl.arguments.length; i++) {
3631
+ out.push(
3632
+ ...generateCall(scope, {
3633
+ callee: {
3634
+ type: 'Identifier',
3635
+ name: '__Porffor_print'
3636
+ },
3637
+ arguments: [ decl.arguments[i] ]
3638
+ }),
3639
+
3640
+ // print space
3641
+ ...number(32),
3642
+ [ Opcodes.call, importedFuncs.printChar ]
3643
+ );
3644
+ }
3645
+
3646
+ // print newline
3647
+ out.push(
3648
+ ...number(10),
3649
+ [ Opcodes.call, importedFuncs.printChar ]
3650
+ );
3651
+
3652
+ return out;
3653
+ },
3654
+ type: TYPES.undefined,
3655
+ notConstr: true,
3656
+ length: 0
2912
3657
  }
2913
3658
  };
2914
3659
 
@@ -2937,20 +3682,23 @@ export default program => {
2937
3682
  funcs = [];
2938
3683
  funcIndex = {};
2939
3684
  depth = [];
2940
- arrays = new Map();
2941
3685
  pages = new Map();
2942
3686
  data = [];
2943
3687
  currentFuncIndex = importedFuncs.length;
2944
3688
 
2945
3689
  globalThis.valtype = 'f64';
2946
3690
 
2947
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3691
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2948
3692
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2949
3693
 
2950
3694
  globalThis.valtypeBinary = Valtype[valtype];
2951
3695
 
2952
3696
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2953
3697
 
3698
+ globalThis.pageSize = PageSize;
3699
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3700
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3701
+
2954
3702
  // set generic opcodes for current valtype
2955
3703
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2956
3704
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2959,10 +3707,10 @@ export default program => {
2959
3707
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2960
3708
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2961
3709
 
2962
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2963
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2964
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2965
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3710
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3711
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3712
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3713
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2966
3714
 
2967
3715
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2968
3716
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2975,10 +3723,6 @@ export default program => {
2975
3723
 
2976
3724
  program.id = { name: 'main' };
2977
3725
 
2978
- globalThis.pageSize = PageSize;
2979
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2980
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2981
-
2982
3726
  const scope = {
2983
3727
  locals: {},
2984
3728
  localInd: 0
@@ -2989,7 +3733,7 @@ export default program => {
2989
3733
  body: program.body
2990
3734
  };
2991
3735
 
2992
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3736
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2993
3737
 
2994
3738
  generateFunc(scope, program);
2995
3739
 
@@ -3006,7 +3750,11 @@ export default program => {
3006
3750
  }
3007
3751
 
3008
3752
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
3009
- main.returns = [];
3753
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3754
+ main.wasm.splice(main.wasm.length - 1, 1);
3755
+ } else {
3756
+ main.returns = [];
3757
+ }
3010
3758
  }
3011
3759
 
3012
3760
  if (lastInst[0] === Opcodes.call) {