porffor 0.2.0-a759814 → 0.2.0-a910bd0

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 (56) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +156 -87
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +317 -72
  8. package/compiler/{sections.js → assemble.js} +64 -16
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +147 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/boolean.ts +20 -0
  14. package/compiler/builtins/crypto.ts +120 -0
  15. package/compiler/builtins/date.ts +2069 -0
  16. package/compiler/builtins/escape.ts +141 -0
  17. package/compiler/builtins/function.ts +7 -0
  18. package/compiler/builtins/int.ts +147 -0
  19. package/compiler/builtins/number.ts +531 -0
  20. package/compiler/builtins/object.ts +6 -0
  21. package/compiler/builtins/porffor.d.ts +60 -0
  22. package/compiler/builtins/set.ts +190 -0
  23. package/compiler/builtins/string.ts +1080 -0
  24. package/compiler/builtins.js +580 -272
  25. package/compiler/{codeGen.js → codegen.js} +1311 -467
  26. package/compiler/decompile.js +3 -4
  27. package/compiler/embedding.js +22 -22
  28. package/compiler/encoding.js +108 -10
  29. package/compiler/generated_builtins.js +1625 -0
  30. package/compiler/index.js +36 -34
  31. package/compiler/log.js +6 -3
  32. package/compiler/opt.js +57 -31
  33. package/compiler/parse.js +33 -23
  34. package/compiler/precompile.js +120 -0
  35. package/compiler/prefs.js +27 -0
  36. package/compiler/prototype.js +182 -42
  37. package/compiler/types.js +38 -0
  38. package/compiler/wasmSpec.js +31 -7
  39. package/compiler/wrap.js +176 -65
  40. package/package.json +9 -5
  41. package/porf +4 -0
  42. package/rhemyn/compile.js +46 -27
  43. package/rhemyn/parse.js +322 -320
  44. package/rhemyn/test/parse.js +58 -58
  45. package/runner/compare.js +34 -34
  46. package/runner/debug.js +122 -0
  47. package/runner/index.js +91 -11
  48. package/runner/profiler.js +102 -0
  49. package/runner/repl.js +42 -9
  50. package/runner/sizes.js +37 -37
  51. package/compiler/builtins/base64.js +0 -92
  52. package/runner/info.js +0 -89
  53. package/runner/profile.js +0 -46
  54. package/runner/results.json +0 -1
  55. package/runner/transform.js +0 -15
  56. package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
@@ -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,45 @@ 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 hasFuncWithName = name => {
63
+ const func = funcs.find(x => x.name === name);
64
+ return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
65
+ };
66
+ const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
59
67
  switch (decl.type) {
60
68
  case 'BinaryExpression':
61
69
  return generateBinaryExp(scope, decl, global, name);
@@ -68,7 +76,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
68
76
 
69
77
  case 'ArrowFunctionExpression':
70
78
  case 'FunctionDeclaration':
71
- generateFunc(scope, decl);
79
+ const func = generateFunc(scope, decl);
80
+
81
+ if (decl.type.endsWith('Expression')) {
82
+ return number(func.index);
83
+ }
84
+
72
85
  return [];
73
86
 
74
87
  case 'BlockStatement':
@@ -81,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
81
94
  return generateExp(scope, decl);
82
95
 
83
96
  case 'CallExpression':
84
- return generateCall(scope, decl, global, name);
97
+ return generateCall(scope, decl, global, name, valueUnused);
85
98
 
86
99
  case 'NewExpression':
87
100
  return generateNew(scope, decl, global, name);
@@ -99,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
99
112
  return generateUnary(scope, decl);
100
113
 
101
114
  case 'UpdateExpression':
102
- return generateUpdate(scope, decl);
115
+ return generateUpdate(scope, decl, global, name, valueUnused);
103
116
 
104
117
  case 'IfStatement':
105
118
  return generateIf(scope, decl);
@@ -110,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
110
123
  case 'WhileStatement':
111
124
  return generateWhile(scope, decl);
112
125
 
126
+ case 'DoWhileStatement':
127
+ return generateDoWhile(scope, decl);
128
+
113
129
  case 'ForOfStatement':
114
130
  return generateForOf(scope, decl);
115
131
 
@@ -119,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
119
135
  case 'ContinueStatement':
120
136
  return generateContinue(scope, decl);
121
137
 
138
+ case 'LabeledStatement':
139
+ return generateLabel(scope, decl);
140
+
122
141
  case 'EmptyStatement':
123
142
  return generateEmpty(scope, decl);
124
143
 
@@ -132,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
132
151
  return generateTry(scope, decl);
133
152
 
134
153
  case 'DebuggerStatement':
135
- // todo: add fancy terminal debugger?
154
+ // todo: hook into terminal debugger
136
155
  return [];
137
156
 
138
157
  case 'ArrayExpression':
@@ -146,16 +165,17 @@ const generate = (scope, decl, global = false, name = undefined) => {
146
165
  const funcsBefore = funcs.length;
147
166
  generate(scope, decl.declaration);
148
167
 
149
- if (funcsBefore === funcs.length) throw new Error('no new func added in export');
150
-
151
- const newFunc = funcs[funcs.length - 1];
152
- newFunc.export = true;
168
+ if (funcsBefore !== funcs.length) {
169
+ // new func added
170
+ const newFunc = funcs[funcs.length - 1];
171
+ newFunc.export = true;
172
+ }
153
173
 
154
174
  return [];
155
175
 
156
176
  case 'TaggedTemplateExpression': {
157
177
  const funcs = {
158
- asm: str => {
178
+ __Porffor_wasm: str => {
159
179
  let out = [];
160
180
 
161
181
  for (const line of str.split('\n')) {
@@ -163,8 +183,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
163
183
  if (asm[0] === '') continue; // blank
164
184
 
165
185
  if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
186
+ const [ name, type ] = asm.slice(1);
187
+ scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
168
188
  continue;
169
189
  }
170
190
 
@@ -174,52 +194,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
174
194
  }
175
195
 
176
196
  if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
197
+ allocPage(scope, 'asm instrinsic');
178
198
  // todo: add to store/load offset insts
179
199
  continue;
180
200
  }
181
201
 
182
202
  let inst = Opcodes[asm[0].replace('.', '_')];
183
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
203
+ if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
184
204
 
185
205
  if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
206
+ const immediates = asm.slice(1).map(x => {
207
+ const int = parseInt(x);
208
+ if (Number.isNaN(int)) return scope.locals[x]?.idx;
209
+ return int;
210
+ });
187
211
 
188
- out.push([ ...inst, ...immediates ]);
212
+ out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
189
213
  }
190
214
 
191
215
  return out;
192
216
  },
193
217
 
194
- __internal_print_type: str => {
195
- const type = getType(scope, str) - TYPES.number;
218
+ __Porffor_bs: str => [
219
+ ...makeString(scope, str, global, name, true),
196
220
 
197
- return [
198
- ...number(type),
199
- [ Opcodes.call, importedFuncs.print ],
221
+ ...(name ? setType(scope, name, TYPES.bytestring) : [
222
+ ...number(TYPES.bytestring, Valtype.i32),
223
+ ...setLastType(scope)
224
+ ])
225
+ ],
226
+ __Porffor_s: str => [
227
+ ...makeString(scope, str, global, name, false),
200
228
 
201
- // newline
202
- ...number(10),
203
- [ Opcodes.call, importedFuncs.printChar ]
204
- ];
205
- }
206
- }
229
+ ...(name ? setType(scope, name, TYPES.string) : [
230
+ ...number(TYPES.string, Valtype.i32),
231
+ ...setLastType(scope)
232
+ ])
233
+ ],
234
+ };
207
235
 
208
- const name = decl.tag.name;
236
+ const func = decl.tag.name;
209
237
  // hack for inline asm
210
- if (!funcs[name]) return todo('tagged template expressions not implemented');
238
+ if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
239
+
240
+ const { quasis, expressions } = decl.quasi;
241
+ let str = quasis[0].value.raw;
242
+
243
+ for (let i = 0; i < expressions.length; i++) {
244
+ const e = expressions[i];
245
+ if (!e.name) {
246
+ if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
247
+ str += lookupName(scope, e.left.name)[0].idx + e.right.value;
248
+ } else todo(scope, 'unsupported expression in intrinsic');
249
+ } else str += lookupName(scope, e.name)[0].idx;
250
+
251
+ str += quasis[i + 1].value.raw;
252
+ }
211
253
 
212
- const str = decl.quasi.quasis[0].value.raw;
213
- return funcs[name](str);
254
+ return funcs[func](str);
214
255
  }
215
256
 
216
257
  default:
217
- if (decl.type.startsWith('TS')) {
218
- // ignore typescript nodes
258
+ // ignore typescript nodes
259
+ if (decl.type.startsWith('TS') ||
260
+ decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
219
261
  return [];
220
262
  }
221
263
 
222
- return todo(`no generation for ${decl.type}!`);
264
+ return todo(scope, `no generation for ${decl.type}!`);
223
265
  }
224
266
  };
225
267
 
@@ -247,7 +289,7 @@ const lookupName = (scope, _name) => {
247
289
  return [ undefined, undefined ];
248
290
  };
249
291
 
250
- const internalThrow = (scope, constructor, message, expectsValue = false) => [
292
+ const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
251
293
  ...generateThrow(scope, {
252
294
  argument: {
253
295
  type: 'NewExpression',
@@ -269,25 +311,33 @@ const generateIdent = (scope, decl) => {
269
311
  const name = mapName(rawName);
270
312
  let local = scope.locals[rawName];
271
313
 
272
- if (builtinVars[name]) {
314
+ if (Object.hasOwn(builtinVars, name)) {
273
315
  if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
274
- return builtinVars[name];
316
+
317
+ let wasm = builtinVars[name];
318
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
319
+ return wasm.slice();
320
+ }
321
+
322
+ if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
323
+ // todo: return an actual something
324
+ return number(1);
275
325
  }
276
326
 
277
- if (builtinFuncs[name] || internalConstrs[name]) {
327
+ if (isExistingProtoFunc(name)) {
278
328
  // todo: return an actual something
279
329
  return number(1);
280
330
  }
281
331
 
282
- if (local === undefined) {
332
+ if (local?.idx === undefined) {
283
333
  // no local var with name
284
- if (importedFuncs.hasOwnProperty(name)) return number(importedFuncs[name]);
285
- if (funcIndex[name] !== undefined) return number(funcIndex[name]);
334
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
335
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
286
336
 
287
- if (globals[name] !== undefined) return [ [ Opcodes.global_get, globals[name].idx ] ];
337
+ if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
288
338
  }
289
339
 
290
- if (local === undefined && rawName.startsWith('__')) {
340
+ if (local?.idx === undefined && rawName.startsWith('__')) {
291
341
  // return undefined if unknown key in already known var
292
342
  let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
293
343
  if (parent.includes('_')) parent = '__' + parent;
@@ -296,7 +346,7 @@ const generateIdent = (scope, decl) => {
296
346
  if (!parentLookup[1]) return number(UNDEFINED);
297
347
  }
298
348
 
299
- if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
349
+ if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
300
350
 
301
351
  return [ [ Opcodes.local_get, local.idx ] ];
302
352
  };
@@ -309,14 +359,18 @@ const generateReturn = (scope, decl) => {
309
359
  // just bare "return"
310
360
  return [
311
361
  ...number(UNDEFINED), // "undefined" if func returns
312
- ...number(TYPES.undefined, Valtype.i32), // type undefined
362
+ ...(scope.returnType != null ? [] : [
363
+ ...number(TYPES.undefined, Valtype.i32) // type undefined
364
+ ]),
313
365
  [ Opcodes.return ]
314
366
  ];
315
367
  }
316
368
 
317
369
  return [
318
370
  ...generate(scope, decl.argument),
319
- ...getNodeType(scope, decl.argument),
371
+ ...(scope.returnType != null ? [] : [
372
+ ...getNodeType(scope, decl.argument)
373
+ ]),
320
374
  [ Opcodes.return ]
321
375
  ];
322
376
  };
@@ -330,7 +384,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
330
384
  return idx;
331
385
  };
332
386
 
333
- const isIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
387
+ const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
388
+ const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
334
389
 
335
390
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
336
391
  const checks = {
@@ -339,7 +394,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
339
394
  '??': nullish
340
395
  };
341
396
 
342
- if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
397
+ if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
343
398
 
344
399
  // generic structure for {a} OP {b}
345
400
  // -->
@@ -347,8 +402,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
347
402
 
348
403
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
349
404
  // (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]);
405
+ const leftIsInt = isFloatToIntOp(left[left.length - 1]);
406
+ const rightIsInt = isFloatToIntOp(right[right.length - 1]);
352
407
 
353
408
  const canInt = leftIsInt && rightIsInt;
354
409
 
@@ -365,12 +420,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
365
420
  ...right,
366
421
  // note type
367
422
  ...rightType,
368
- setLastType(scope),
423
+ ...setLastType(scope),
369
424
  [ Opcodes.else ],
370
425
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
371
426
  // note type
372
427
  ...leftType,
373
- setLastType(scope),
428
+ ...setLastType(scope),
374
429
  [ Opcodes.end ],
375
430
  Opcodes.i32_from
376
431
  ];
@@ -384,17 +439,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
439
  ...right,
385
440
  // note type
386
441
  ...rightType,
387
- setLastType(scope),
442
+ ...setLastType(scope),
388
443
  [ Opcodes.else ],
389
444
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
390
445
  // note type
391
446
  ...leftType,
392
- setLastType(scope),
447
+ ...setLastType(scope),
393
448
  [ Opcodes.end ]
394
449
  ];
395
450
  };
396
451
 
397
- const concatStrings = (scope, left, right, global, name, assign) => {
452
+ const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
398
453
  // todo: this should be rewritten into a built-in/func: String.prototype.concat
399
454
  // todo: convert left and right to strings if not
400
455
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -404,11 +459,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
404
459
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
405
460
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
406
461
 
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');
462
+ if (assign && Prefs.aotPointerOpt) {
463
+ const pointer = scope.arrays?.get(name ?? '$undeclared');
412
464
 
413
465
  return [
414
466
  // setup right
@@ -433,11 +485,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
433
485
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
434
486
 
435
487
  // copy right
436
- // dst = out pointer + length size + current length * i16 size
488
+ // dst = out pointer + length size + current length * sizeof valtype
437
489
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
438
490
 
439
491
  [ Opcodes.local_get, leftLength ],
440
- ...number(ValtypeSize.i16, Valtype.i32),
492
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
441
493
  [ Opcodes.i32_mul ],
442
494
  [ Opcodes.i32_add ],
443
495
 
@@ -446,9 +498,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
446
498
  ...number(ValtypeSize.i32, Valtype.i32),
447
499
  [ Opcodes.i32_add ],
448
500
 
449
- // size = right length * i16 size
501
+ // size = right length * sizeof valtype
450
502
  [ Opcodes.local_get, rightLength ],
451
- ...number(ValtypeSize.i16, Valtype.i32),
503
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
452
504
  [ Opcodes.i32_mul ],
453
505
 
454
506
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -506,11 +558,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
506
558
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
507
559
 
508
560
  // copy right
509
- // dst = out pointer + length size + left length * i16 size
561
+ // dst = out pointer + length size + left length * sizeof valtype
510
562
  ...number(pointer + ValtypeSize.i32, Valtype.i32),
511
563
 
512
564
  [ Opcodes.local_get, leftLength ],
513
- ...number(ValtypeSize.i16, Valtype.i32),
565
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
514
566
  [ Opcodes.i32_mul ],
515
567
  [ Opcodes.i32_add ],
516
568
 
@@ -519,9 +571,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
519
571
  ...number(ValtypeSize.i32, Valtype.i32),
520
572
  [ Opcodes.i32_add ],
521
573
 
522
- // size = right length * i16 size
574
+ // size = right length * sizeof valtype
523
575
  [ Opcodes.local_get, rightLength ],
524
- ...number(ValtypeSize.i16, Valtype.i32),
576
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
525
577
  [ Opcodes.i32_mul ],
526
578
 
527
579
  [ ...Opcodes.memory_copy, 0x00, 0x00 ],
@@ -531,7 +583,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
531
583
  ];
532
584
  };
533
585
 
534
- const compareStrings = (scope, left, right) => {
586
+ const compareStrings = (scope, left, right, bytestrings = false) => {
535
587
  // todo: this should be rewritten into a func
536
588
  // todo: convert left and right to strings if not
537
589
  // todo: optimize by looking up names in arrays and using that if exists?
@@ -540,7 +592,6 @@ const compareStrings = (scope, left, right) => {
540
592
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
541
593
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
542
594
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
543
- const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
544
595
 
545
596
  const index = localTmp(scope, 'compare_index', Valtype.i32);
546
597
  const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
@@ -568,7 +619,6 @@ const compareStrings = (scope, left, right) => {
568
619
 
569
620
  [ Opcodes.local_get, rightPointer ],
570
621
  [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
571
- [ Opcodes.local_tee, rightLength ],
572
622
 
573
623
  // fast path: check leftLength != rightLength
574
624
  [ Opcodes.i32_ne ],
@@ -583,11 +633,13 @@ const compareStrings = (scope, left, right) => {
583
633
  ...number(0, Valtype.i32),
584
634
  [ Opcodes.local_set, index ],
585
635
 
586
- // setup index end as length * sizeof i16 (2)
636
+ // setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
587
637
  // we do this instead of having to do mul/div each iter for perf™
588
638
  [ Opcodes.local_get, leftLength ],
589
- ...number(ValtypeSize.i16, Valtype.i32),
590
- [ Opcodes.i32_mul ],
639
+ ...(bytestrings ? [] : [
640
+ ...number(ValtypeSize.i16, Valtype.i32),
641
+ [ Opcodes.i32_mul ],
642
+ ]),
591
643
  [ Opcodes.local_set, indexEnd ],
592
644
 
593
645
  // iterate over each char and check if eq
@@ -597,13 +649,17 @@ const compareStrings = (scope, left, right) => {
597
649
  [ Opcodes.local_get, index ],
598
650
  [ Opcodes.local_get, leftPointer ],
599
651
  [ Opcodes.i32_add ],
600
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
652
+ bytestrings ?
653
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
654
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
601
655
 
602
656
  // fetch right
603
657
  [ Opcodes.local_get, index ],
604
658
  [ Opcodes.local_get, rightPointer ],
605
659
  [ Opcodes.i32_add ],
606
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
660
+ bytestrings ?
661
+ [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
662
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
607
663
 
608
664
  // not equal, "return" false
609
665
  [ Opcodes.i32_ne ],
@@ -612,13 +668,13 @@ const compareStrings = (scope, left, right) => {
612
668
  [ Opcodes.br, 2 ],
613
669
  [ Opcodes.end ],
614
670
 
615
- // index += sizeof i16 (2)
671
+ // index += sizeof valtype (1 for bytestring, 2 for string)
616
672
  [ Opcodes.local_get, index ],
617
- ...number(ValtypeSize.i16, Valtype.i32),
673
+ ...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
618
674
  [ Opcodes.i32_add ],
619
675
  [ Opcodes.local_tee, index ],
620
676
 
621
- // if index != index end (length * sizeof 16), loop
677
+ // if index != index end (length * sizeof valtype), loop
622
678
  [ Opcodes.local_get, indexEnd ],
623
679
  [ Opcodes.i32_ne ],
624
680
  [ Opcodes.br_if, 0 ],
@@ -639,16 +695,18 @@ const compareStrings = (scope, left, right) => {
639
695
  };
640
696
 
641
697
  const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
642
- if (isIntOp(wasm[wasm.length - 1])) return [
698
+ if (isFloatToIntOp(wasm[wasm.length - 1])) return [
643
699
  ...wasm,
644
700
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
645
701
  ];
702
+ // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
646
703
 
647
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
704
+ const useTmp = knownType(scope, type) == null;
705
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
648
706
 
649
707
  const def = [
650
708
  // if value != 0
651
- [ Opcodes.local_get, tmp ],
709
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
652
710
 
653
711
  // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
654
712
  ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
@@ -660,16 +718,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
660
718
 
661
719
  return [
662
720
  ...wasm,
663
- [ Opcodes.local_set, tmp ],
721
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
664
722
 
665
723
  ...typeSwitch(scope, type, {
666
724
  // [TYPES.number]: def,
667
- [TYPES._array]: [
725
+ [TYPES.array]: [
668
726
  // arrays are always truthy
669
727
  ...number(1, intOut ? Valtype.i32 : valtypeBinary)
670
728
  ],
671
729
  [TYPES.string]: [
672
- [ Opcodes.local_get, tmp ],
730
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
673
731
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
674
732
 
675
733
  // get length
@@ -680,24 +738,46 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
738
  [ Opcodes.i32_eqz ], */
681
739
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
682
740
  ],
741
+ [TYPES.bytestring]: [ // duplicate of string
742
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
743
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
744
+
745
+ // get length
746
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
747
+
748
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
749
+ ],
683
750
  default: def
684
751
  }, intOut ? Valtype.i32 : valtypeBinary)
685
752
  ];
686
753
  };
687
754
 
688
755
  const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
689
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
756
+ const useTmp = knownType(scope, type) == null;
757
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
758
+
690
759
  return [
691
760
  ...wasm,
692
- [ Opcodes.local_set, tmp ],
761
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
693
762
 
694
763
  ...typeSwitch(scope, type, {
695
- [TYPES._array]: [
764
+ [TYPES.array]: [
696
765
  // arrays are always truthy
697
766
  ...number(0, intOut ? Valtype.i32 : valtypeBinary)
698
767
  ],
699
768
  [TYPES.string]: [
700
- [ Opcodes.local_get, tmp ],
769
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
770
+ ...(intIn ? [] : [ Opcodes.i32_to_u ]),
771
+
772
+ // get length
773
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
774
+
775
+ // if length == 0
776
+ [ Opcodes.i32_eqz ],
777
+ ...(intOut ? [] : [ Opcodes.i32_from_u ])
778
+ ],
779
+ [TYPES.bytestring]: [ // duplicate of string
780
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
701
781
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
702
782
 
703
783
  // get length
@@ -709,7 +789,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
709
789
  ],
710
790
  default: [
711
791
  // if value == 0
712
- [ Opcodes.local_get, tmp ],
792
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
793
 
714
794
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
715
795
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -719,10 +799,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
719
799
  };
720
800
 
721
801
  const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
722
- const tmp = localTmp(scope, `$logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
802
+ const useTmp = knownType(scope, type) == null;
803
+ const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
804
+
723
805
  return [
724
806
  ...wasm,
725
- [ Opcodes.local_set, tmp ],
807
+ ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
726
808
 
727
809
  ...typeSwitch(scope, type, {
728
810
  [TYPES.undefined]: [
@@ -731,7 +813,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
731
813
  ],
732
814
  [TYPES.object]: [
733
815
  // object, null if == 0
734
- [ Opcodes.local_get, tmp ],
816
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
735
817
 
736
818
  ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
737
819
  ...(intOut ? [] : [ Opcodes.i32_from_u ])
@@ -760,11 +842,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
760
842
  return performLogicOp(scope, op, left, right, leftType, rightType);
761
843
  }
762
844
 
845
+ const knownLeft = knownType(scope, leftType);
846
+ const knownRight = knownType(scope, rightType);
847
+
763
848
  const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
764
849
  const strictOp = op === '===' || op === '!==';
765
850
 
766
851
  const startOut = [], endOut = [];
767
- const finalise = out => startOut.concat(out, endOut);
852
+ const finalize = out => startOut.concat(out, endOut);
768
853
 
769
854
  // if strict (in)equal check types match
770
855
  if (strictOp) {
@@ -809,31 +894,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
809
894
  // todo: if equality op and an operand is undefined, return false
810
895
  // todo: niche null hell with 0
811
896
 
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
- // }
897
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) {
898
+ if (op === '+') {
899
+ // todo: this should be dynamic too but for now only static
900
+ // string concat (a + b)
901
+ return concatStrings(scope, left, right, _global, _name, assign, false);
902
+ }
903
+
904
+ // not an equality op, NaN
905
+ if (!eqOp) return number(NaN);
906
+
907
+ // else leave bool ops
908
+ // todo: convert string to number if string and number/bool
909
+ // todo: string (>|>=|<|<=) string
910
+
911
+ // string comparison
912
+ if (op === '===' || op === '==') {
913
+ return compareStrings(scope, left, right);
914
+ }
915
+
916
+ if (op === '!==' || op === '!=') {
917
+ return [
918
+ ...compareStrings(scope, left, right),
919
+ [ Opcodes.i32_eqz ]
920
+ ];
921
+ }
922
+ }
923
+
924
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
925
+ if (op === '+') {
926
+ // todo: this should be dynamic too but for now only static
927
+ // string concat (a + b)
928
+ return concatStrings(scope, left, right, _global, _name, assign, true);
929
+ }
930
+
931
+ // not an equality op, NaN
932
+ if (!eqOp) return number(NaN);
933
+
934
+ // else leave bool ops
935
+ // todo: convert string to number if string and number/bool
936
+ // todo: string (>|>=|<|<=) string
937
+
938
+ // string comparison
939
+ if (op === '===' || op === '==') {
940
+ return compareStrings(scope, left, right, true);
941
+ }
942
+
943
+ if (op === '!==' || op === '!=') {
944
+ return [
945
+ ...compareStrings(scope, left, right, true),
946
+ [ Opcodes.i32_eqz ]
947
+ ];
948
+ }
949
+ }
837
950
 
838
951
  let ops = operatorOpcode[valtype][op];
839
952
 
@@ -843,33 +956,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
843
956
  includeBuiltin(scope, builtinName);
844
957
  const idx = funcIndex[builtinName];
845
958
 
846
- return finalise([
959
+ return finalize([
847
960
  ...left,
848
961
  ...right,
849
962
  [ Opcodes.call, idx ]
850
963
  ]);
851
964
  }
852
965
 
853
- if (!ops) return todo(`operator ${op} not implemented yet`); // throw new Error(`unknown operator ${op}`);
966
+ if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
854
967
 
855
968
  if (!Array.isArray(ops)) ops = [ ops ];
856
969
  ops = [ ops ];
857
970
 
858
971
  let tmpLeft, tmpRight;
859
972
  // 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
- }
973
+ // todo: intelligent partial skip later
974
+ // if neither known are string, stop this madness
975
+ // we already do known checks earlier, so don't need to recheck
869
976
 
977
+ if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
870
978
  tmpLeft = localTmp(scope, '__tmpop_left');
871
979
  tmpRight = localTmp(scope, '__tmpop_right');
872
980
 
981
+ // returns false for one string, one not - but more ops/slower
982
+ // ops.unshift(...stringOnly([
983
+ // // if left is string
984
+ // ...leftType,
985
+ // ...number(TYPES.string, Valtype.i32),
986
+ // [ Opcodes.i32_eq ],
987
+
988
+ // // if right is string
989
+ // ...rightType,
990
+ // ...number(TYPES.string, Valtype.i32),
991
+ // [ Opcodes.i32_eq ],
992
+
993
+ // // if either are true
994
+ // [ Opcodes.i32_or ],
995
+ // [ Opcodes.if, Blocktype.void ],
996
+
997
+ // // todo: convert non-strings to strings, for now fail immediately if one is not
998
+ // // if left is not string
999
+ // ...leftType,
1000
+ // ...number(TYPES.string, Valtype.i32),
1001
+ // [ Opcodes.i32_ne ],
1002
+
1003
+ // // if right is not string
1004
+ // ...rightType,
1005
+ // ...number(TYPES.string, Valtype.i32),
1006
+ // [ Opcodes.i32_ne ],
1007
+
1008
+ // // if either are true
1009
+ // [ Opcodes.i32_or ],
1010
+ // [ Opcodes.if, Blocktype.void ],
1011
+ // ...number(0, Valtype.i32),
1012
+ // [ Opcodes.br, 2 ],
1013
+ // [ Opcodes.end ],
1014
+
1015
+ // ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1016
+ // ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1017
+ // [ Opcodes.br, 1 ],
1018
+ // [ Opcodes.end ],
1019
+ // ]));
1020
+
1021
+ // does not handle one string, one not (such cases go past)
873
1022
  ops.unshift(...stringOnly([
874
1023
  // if left is string
875
1024
  ...leftType,
@@ -881,30 +1030,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
881
1030
  ...number(TYPES.string, Valtype.i32),
882
1031
  [ Opcodes.i32_eq ],
883
1032
 
884
- // if either are true
885
- [ Opcodes.i32_or ],
1033
+ // if both are true
1034
+ [ Opcodes.i32_and ],
886
1035
  [ Opcodes.if, Blocktype.void ],
1036
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
1037
+ ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
1038
+ [ Opcodes.br, 1 ],
1039
+ [ Opcodes.end ],
887
1040
 
888
- // todo: convert non-strings to strings, for now fail immediately if one is not
889
- // if left is not string
1041
+ // if left is bytestring
890
1042
  ...leftType,
891
- ...number(TYPES.string, Valtype.i32),
892
- [ Opcodes.i32_ne ],
1043
+ ...number(TYPES.bytestring, Valtype.i32),
1044
+ [ Opcodes.i32_eq ],
893
1045
 
894
- // if right is not string
1046
+ // if right is bytestring
895
1047
  ...rightType,
896
- ...number(TYPES.string, Valtype.i32),
897
- [ Opcodes.i32_ne ],
1048
+ ...number(TYPES.bytestring, Valtype.i32),
1049
+ [ Opcodes.i32_eq ],
898
1050
 
899
- // if either are true
900
- [ Opcodes.i32_or ],
1051
+ // if both are true
1052
+ [ Opcodes.i32_and ],
901
1053
  [ 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 ] ]),
1054
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
908
1055
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
909
1056
  [ Opcodes.br, 1 ],
910
1057
  [ Opcodes.end ],
@@ -916,9 +1063,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
916
1063
  // endOut.push(stringOnly([ Opcodes.end ]));
917
1064
  endOut.unshift(stringOnly([ Opcodes.end ]));
918
1065
  // }
919
- })();
1066
+ }
920
1067
 
921
- return finalise([
1068
+ return finalize([
922
1069
  ...left,
923
1070
  ...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
924
1071
  ...right,
@@ -935,7 +1082,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
935
1082
  return out;
936
1083
  };
937
1084
 
938
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
1085
+ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1086
+ return func({ name, params, locals, returns, localInd }, {
1087
+ TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1088
+ builtin: name => {
1089
+ let idx = funcIndex[name] ?? importedFuncs[name];
1090
+ if (idx === undefined && builtinFuncs[name]) {
1091
+ includeBuiltin(null, name);
1092
+ idx = funcIndex[name];
1093
+ }
1094
+
1095
+ return idx;
1096
+ }
1097
+ });
1098
+ };
1099
+
1100
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
939
1101
  const existing = funcs.find(x => x.name === name);
940
1102
  if (existing) return existing;
941
1103
 
@@ -947,6 +1109,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
947
1109
  locals[nameParam(i)] = { idx: i, type: allLocals[i] };
948
1110
  }
949
1111
 
1112
+ for (const x of _data) {
1113
+ const copy = { ...x };
1114
+ copy.offset += pages.size * pageSize;
1115
+ data.push(copy);
1116
+ }
1117
+
1118
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1119
+
950
1120
  let baseGlobalIdx, i = 0;
951
1121
  for (const type of globalTypes) {
952
1122
  if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
@@ -969,7 +1139,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
969
1139
  params,
970
1140
  locals,
971
1141
  returns,
972
- returnType: TYPES[returnType ?? 'number'],
1142
+ returnType: returnType ?? TYPES.number,
973
1143
  wasm,
974
1144
  internal: true,
975
1145
  index: currentFuncIndex++
@@ -992,6 +1162,7 @@ const generateLogicExp = (scope, decl) => {
992
1162
  return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
993
1163
  };
994
1164
 
1165
+ // potential future ideas for nan boxing (unused):
995
1166
  // T = JS type, V = value/pointer
996
1167
  // 0bTTT
997
1168
  // qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
@@ -1005,7 +1176,6 @@ const generateLogicExp = (scope, decl) => {
1005
1176
  // js type: 4 bits
1006
1177
  // internal type: ? bits
1007
1178
  // pointer: 32 bits
1008
-
1009
1179
  // generic
1010
1180
  // 1 23 4 5
1011
1181
  // 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
@@ -1015,47 +1185,29 @@ const generateLogicExp = (scope, decl) => {
1015
1185
  // 4: internal type
1016
1186
  // 5: pointer
1017
1187
 
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,
1027
-
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',
1188
+ const isExistingProtoFunc = name => {
1189
+ if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
1190
+ if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
1042
1191
 
1043
- [TYPES._array]: 'Array',
1044
- [TYPES._regexp]: 'RegExp'
1192
+ return false;
1045
1193
  };
1046
1194
 
1047
1195
  const getType = (scope, _name) => {
1048
1196
  const name = mapName(_name);
1049
1197
 
1198
+ // if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
1199
+
1200
+ if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
1050
1201
  if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
1202
+
1203
+ if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
1051
1204
  if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
1052
1205
 
1053
1206
  let type = TYPES.undefined;
1054
- if (builtinVars[name]) type = TYPES[builtinVars[name].type ?? 'number'];
1207
+ if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
1055
1208
  if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
1056
1209
 
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;
1210
+ if (isExistingProtoFunc(name)) type = TYPES.function;
1059
1211
 
1060
1212
  return number(type, Valtype.i32);
1061
1213
  };
@@ -1078,21 +1230,24 @@ const setType = (scope, _name, type) => {
1078
1230
  ];
1079
1231
 
1080
1232
  // throw new Error('could not find var');
1233
+ return [];
1081
1234
  };
1082
1235
 
1083
1236
  const getLastType = scope => {
1084
1237
  scope.gotLastType = true;
1085
- return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
1238
+ return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1086
1239
  };
1087
1240
 
1088
1241
  const setLastType = scope => {
1089
- return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
1242
+ return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1090
1243
  };
1091
1244
 
1092
1245
  const getNodeType = (scope, node) => {
1093
- const inner = () => {
1246
+ const ret = (() => {
1094
1247
  if (node.type === 'Literal') {
1095
- if (node.regex) return TYPES._regexp;
1248
+ if (node.regex) return TYPES.regexp;
1249
+
1250
+ if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
1096
1251
 
1097
1252
  return TYPES[typeof node.value];
1098
1253
  }
@@ -1107,14 +1262,34 @@ const getNodeType = (scope, node) => {
1107
1262
 
1108
1263
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1109
1264
  const name = node.callee.name;
1265
+ if (!name) {
1266
+ // iife
1267
+ if (scope.locals['#last_type']) return getLastType(scope);
1268
+
1269
+ // presume
1270
+ // todo: warn here?
1271
+ return TYPES.number;
1272
+ }
1273
+
1274
+ if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
1275
+ if (builtinFuncs[name + '$constructor'].typedReturns) {
1276
+ if (scope.locals['#last_type']) return getLastType(scope);
1277
+
1278
+ // presume
1279
+ // todo: warn here?
1280
+ return TYPES.number;
1281
+ }
1282
+
1283
+ return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
1284
+ }
1285
+
1110
1286
  const func = funcs.find(x => x.name === name);
1111
1287
 
1112
1288
  if (func) {
1113
- // console.log(scope, func, func.returnType);
1114
1289
  if (func.returnType) return func.returnType;
1115
1290
  }
1116
1291
 
1117
- if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
1292
+ if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
1118
1293
  if (internalConstrs[name]) return internalConstrs[name].type;
1119
1294
 
1120
1295
  // check if this is a prototype function
@@ -1125,11 +1300,16 @@ const getNodeType = (scope, node) => {
1125
1300
  const spl = name.slice(2).split('_');
1126
1301
 
1127
1302
  const func = spl[spl.length - 1];
1128
- const protoFuncs = Object.values(prototypeFuncs).filter(x => x[func] != null);
1303
+ const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1129
1304
  if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1130
1305
  }
1131
1306
 
1132
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1307
+ if (name.startsWith('__Porffor_wasm_')) {
1308
+ // todo: return undefined for non-returning ops
1309
+ return TYPES.number;
1310
+ }
1311
+
1312
+ if (scope.locals['#last_type']) return getLastType(scope);
1133
1313
 
1134
1314
  // presume
1135
1315
  // todo: warn here?
@@ -1172,11 +1352,20 @@ const getNodeType = (scope, node) => {
1172
1352
  }
1173
1353
 
1174
1354
  if (node.type === 'ArrayExpression') {
1175
- return TYPES._array;
1355
+ return TYPES.array;
1176
1356
  }
1177
1357
 
1178
1358
  if (node.type === 'BinaryExpression') {
1179
1359
  if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
1360
+ if (node.operator !== '+') return TYPES.number;
1361
+
1362
+ const knownLeft = knownType(scope, getNodeType(scope, node.left));
1363
+ const knownRight = knownType(scope, getNodeType(scope, node.right));
1364
+
1365
+ // todo: this should be dynamic but for now only static
1366
+ if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
1367
+ if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
1368
+
1180
1369
  return TYPES.number;
1181
1370
 
1182
1371
  // todo: string concat types
@@ -1201,28 +1390,47 @@ const getNodeType = (scope, node) => {
1201
1390
  if (node.operator === '!') return TYPES.boolean;
1202
1391
  if (node.operator === 'void') return TYPES.undefined;
1203
1392
  if (node.operator === 'delete') return TYPES.boolean;
1204
- if (node.operator === 'typeof') return TYPES.string;
1393
+ if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
1205
1394
 
1206
1395
  return TYPES.number;
1207
1396
  }
1208
1397
 
1209
1398
  if (node.type === 'MemberExpression') {
1399
+ // hack: if something.name, string type
1400
+ if (node.property.name === 'name') {
1401
+ if (hasFuncWithName(node.object.name)) {
1402
+ return TYPES.bytestring;
1403
+ } else {
1404
+ return TYPES.undefined;
1405
+ }
1406
+ }
1407
+
1210
1408
  // hack: if something.length, number type
1211
1409
  if (node.property.name === 'length') return TYPES.number;
1212
1410
 
1213
- // we cannot guess
1411
+ // ts hack
1412
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1413
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1414
+ if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1415
+
1416
+ if (scope.locals['#last_type']) return getLastType(scope);
1417
+
1418
+ // presume
1214
1419
  return TYPES.number;
1215
1420
  }
1216
1421
 
1217
- if (scope.locals['#last_type']) return [ getLastType(scope) ];
1422
+ if (node.type === 'TaggedTemplateExpression') {
1423
+ // hack
1424
+ if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
1425
+ }
1426
+
1427
+ if (scope.locals['#last_type']) return getLastType(scope);
1218
1428
 
1219
1429
  // presume
1220
1430
  // todo: warn here?
1221
1431
  return TYPES.number;
1222
- };
1432
+ })();
1223
1433
 
1224
- const ret = inner();
1225
- // console.trace(node, ret);
1226
1434
  if (typeof ret === 'number') return number(ret, Valtype.i32);
1227
1435
  return ret;
1228
1436
  };
@@ -1230,8 +1438,8 @@ const getNodeType = (scope, node) => {
1230
1438
  const generateLiteral = (scope, decl, global, name) => {
1231
1439
  if (decl.value === null) return number(NULL);
1232
1440
 
1441
+ // hack: just return 1 for regex literals
1233
1442
  if (decl.regex) {
1234
- scope.regex[name] = decl.regex;
1235
1443
  return number(1);
1236
1444
  }
1237
1445
 
@@ -1244,19 +1452,10 @@ const generateLiteral = (scope, decl, global, name) => {
1244
1452
  return number(decl.value ? 1 : 0);
1245
1453
 
1246
1454
  case 'string':
1247
- const str = decl.value;
1248
- const rawElements = new Array(str.length);
1249
- let j = 0;
1250
- for (let i = 0; i < str.length; i++) {
1251
- rawElements[i] = str.charCodeAt(i);
1252
- }
1253
-
1254
- return makeArray(scope, {
1255
- rawElements
1256
- }, global, name, false, 'i16')[0];
1455
+ return makeString(scope, decl.value, global, name);
1257
1456
 
1258
1457
  default:
1259
- return todo(`cannot generate literal of type ${typeof decl.value}`);
1458
+ return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
1260
1459
  }
1261
1460
  };
1262
1461
 
@@ -1265,6 +1464,8 @@ const countLeftover = wasm => {
1265
1464
 
1266
1465
  for (let i = 0; i < wasm.length; i++) {
1267
1466
  const inst = wasm[i];
1467
+ if (inst[0] == null) continue;
1468
+
1268
1469
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
1269
1470
  if (inst[0] === Opcodes.if) count--;
1270
1471
  if (inst[1] !== Blocktype.void) count++;
@@ -1273,18 +1474,25 @@ const countLeftover = wasm => {
1273
1474
  if (inst[0] === Opcodes.end) depth--;
1274
1475
 
1275
1476
  if (depth === 0)
1276
- if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1277
- 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)) {}
1278
- else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
1279
- else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
1477
+ if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
1478
+ 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)) {}
1479
+ 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++;
1480
+ else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
1280
1481
  else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
1281
1482
  else if (inst[0] === Opcodes.return) count = 0;
1282
1483
  else if (inst[0] === Opcodes.call) {
1283
1484
  let func = funcs.find(x => x.index === inst[1]);
1284
- if (func) {
1285
- count -= func.params.length;
1286
- } else count--;
1287
- if (func) count += func.returns.length;
1485
+ if (inst[1] === -1) {
1486
+ // todo: count for calling self
1487
+ } else if (!func && inst[1] < importedFuncs.length) {
1488
+ count -= importedFuncs[inst[1]].params;
1489
+ count += importedFuncs[inst[1]].returns;
1490
+ } else {
1491
+ if (func) {
1492
+ count -= func.params.length;
1493
+ } else count--;
1494
+ if (func) count += func.returns.length;
1495
+ }
1288
1496
  } else count--;
1289
1497
 
1290
1498
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1302,7 +1510,7 @@ const disposeLeftover = wasm => {
1302
1510
  const generateExp = (scope, decl) => {
1303
1511
  const expression = decl.expression;
1304
1512
 
1305
- const out = generate(scope, expression);
1513
+ const out = generate(scope, expression, undefined, undefined, true);
1306
1514
  disposeLeftover(out);
1307
1515
 
1308
1516
  return out;
@@ -1360,7 +1568,7 @@ const RTArrayUtil = {
1360
1568
  ]
1361
1569
  };
1362
1570
 
1363
- const generateCall = (scope, decl, _global, _name) => {
1571
+ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1364
1572
  /* const callee = decl.callee;
1365
1573
  const args = decl.arguments;
1366
1574
 
@@ -1376,10 +1584,21 @@ const generateCall = (scope, decl, _global, _name) => {
1376
1584
  name = func.name;
1377
1585
  }
1378
1586
 
1379
- if (name === 'eval' && decl.arguments[0].type === 'Literal') {
1587
+ if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
1380
1588
  // literal eval hack
1381
- const code = decl.arguments[0].value;
1382
- const parsed = parse(code, []);
1589
+ const code = decl.arguments[0]?.value ?? '';
1590
+
1591
+ let parsed;
1592
+ try {
1593
+ parsed = parse(code, []);
1594
+ } catch (e) {
1595
+ if (e.name === 'SyntaxError') {
1596
+ // throw syntax errors of evals at runtime instead
1597
+ return internalThrow(scope, 'SyntaxError', e.message, true);
1598
+ }
1599
+
1600
+ throw e;
1601
+ }
1383
1602
 
1384
1603
  const out = generate(scope, {
1385
1604
  type: 'BlockStatement',
@@ -1393,13 +1612,13 @@ const generateCall = (scope, decl, _global, _name) => {
1393
1612
  const finalStatement = parsed.body[parsed.body.length - 1];
1394
1613
  out.push(
1395
1614
  ...getNodeType(scope, finalStatement),
1396
- setLastType(scope)
1615
+ ...setLastType(scope)
1397
1616
  );
1398
1617
  } else if (countLeftover(out) === 0) {
1399
1618
  out.push(...number(UNDEFINED));
1400
1619
  out.push(
1401
1620
  ...number(TYPES.undefined, Valtype.i32),
1402
- setLastType(scope)
1621
+ ...setLastType(scope)
1403
1622
  );
1404
1623
  }
1405
1624
 
@@ -1421,29 +1640,39 @@ const generateCall = (scope, decl, _global, _name) => {
1421
1640
 
1422
1641
  target = { ...decl.callee };
1423
1642
  target.name = spl.slice(0, -1).join('_');
1643
+
1644
+ // failed to lookup name, abort
1645
+ if (!lookupName(scope, target.name)[0]) protoName = null;
1424
1646
  }
1425
1647
 
1426
1648
  // literal.func()
1427
1649
  if (!name && decl.callee.type === 'MemberExpression') {
1428
1650
  // megahack for /regex/.func()
1429
- if (decl.callee.object.regex) {
1430
- const funcName = decl.callee.property.name;
1431
- const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1651
+ const funcName = decl.callee.property.name;
1652
+ if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
1653
+ const regex = decl.callee.object.regex.pattern;
1654
+ const rhemynName = `regex_${funcName}_${regex}`;
1432
1655
 
1433
- funcIndex[func.name] = func.index;
1434
- funcs.push(func);
1656
+ if (!funcIndex[rhemynName]) {
1657
+ const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1435
1658
 
1659
+ funcIndex[func.name] = func.index;
1660
+ funcs.push(func);
1661
+ }
1662
+
1663
+ const idx = funcIndex[rhemynName];
1436
1664
  return [
1437
1665
  // make string arg
1438
1666
  ...generate(scope, decl.arguments[0]),
1667
+ Opcodes.i32_to_u,
1668
+ ...getNodeType(scope, decl.arguments[0]),
1439
1669
 
1440
1670
  // call regex func
1441
- Opcodes.i32_to_u,
1442
- [ Opcodes.call, func.index ],
1671
+ [ Opcodes.call, idx ],
1443
1672
  Opcodes.i32_from_u,
1444
1673
 
1445
1674
  ...number(TYPES.boolean, Valtype.i32),
1446
- setLastType(scope)
1675
+ ...setLastType(scope)
1447
1676
  ];
1448
1677
  }
1449
1678
 
@@ -1468,23 +1697,48 @@ const generateCall = (scope, decl, _global, _name) => {
1468
1697
  // }
1469
1698
 
1470
1699
  if (protoName) {
1700
+ const protoBC = {};
1701
+
1702
+ const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
1703
+
1704
+ if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
1705
+ for (const x of builtinProtoCands) {
1706
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
1707
+ if (type == null) continue;
1708
+
1709
+ protoBC[type] = generateCall(scope, {
1710
+ callee: {
1711
+ type: 'Identifier',
1712
+ name: x
1713
+ },
1714
+ arguments: [ target, ...decl.arguments ],
1715
+ _protoInternalCall: true
1716
+ });
1717
+ }
1718
+ }
1719
+
1471
1720
  const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
1472
- const f = prototypeFuncs[x][protoName];
1473
- if (f) acc[x] = f;
1721
+ if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
1474
1722
  return acc;
1475
1723
  }, {});
1476
1724
 
1477
- // no prototype function candidates, ignore
1478
1725
  if (Object.keys(protoCands).length > 0) {
1479
1726
  // use local for cached i32 length as commonly used
1480
1727
  const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1481
1728
  const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
1482
- const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
1483
1729
 
1484
1730
  // TODO: long-term, prototypes should be their individual separate funcs
1485
1731
 
1732
+ const rawPointer = [
1733
+ ...generate(scope, target),
1734
+ Opcodes.i32_to_u
1735
+ ];
1736
+
1737
+ const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
1738
+ const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
1739
+
1740
+ let allOptUnused = true;
1486
1741
  let lengthI32CacheUsed = false;
1487
- const protoBC = {};
1488
1742
  for (const x in protoCands) {
1489
1743
  const protoFunc = protoCands[x];
1490
1744
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
@@ -1492,7 +1746,7 @@ const generateCall = (scope, decl, _global, _name) => {
1492
1746
  ...RTArrayUtil.getLength(getPointer),
1493
1747
 
1494
1748
  ...number(TYPES.number, Valtype.i32),
1495
- setLastType(scope)
1749
+ ...setLastType(scope)
1496
1750
  ];
1497
1751
  continue;
1498
1752
  }
@@ -1502,6 +1756,7 @@ const generateCall = (scope, decl, _global, _name) => {
1502
1756
  const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
1503
1757
  const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
1504
1758
 
1759
+ let optUnused = false;
1505
1760
  const protoOut = protoFunc(getPointer, {
1506
1761
  getCachedI32: () => {
1507
1762
  lengthI32CacheUsed = true;
@@ -1516,23 +1771,30 @@ const generateCall = (scope, decl, _global, _name) => {
1516
1771
  return makeArray(scope, {
1517
1772
  rawElements: new Array(length)
1518
1773
  }, _global, _name, true, itemType);
1774
+ }, () => {
1775
+ optUnused = true;
1776
+ return unusedValue;
1519
1777
  });
1520
1778
 
1779
+ if (!optUnused) allOptUnused = false;
1780
+
1521
1781
  protoBC[x] = [
1522
- [ Opcodes.block, valtypeBinary ],
1782
+ [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1523
1783
  ...protoOut,
1524
1784
 
1525
1785
  ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1526
- setLastType(scope),
1786
+ ...setLastType(scope),
1527
1787
  [ Opcodes.end ]
1528
1788
  ];
1529
1789
  }
1530
1790
 
1531
- return [
1532
- ...generate(scope, target),
1791
+ // todo: if some cands use optUnused and some don't, we will probably crash
1533
1792
 
1534
- Opcodes.i32_to_u,
1535
- [ Opcodes.local_set, pointerLocal ],
1793
+ return [
1794
+ ...(usePointerCache ? [
1795
+ ...rawPointer,
1796
+ [ Opcodes.local_set, pointerLocal ],
1797
+ ] : []),
1536
1798
 
1537
1799
  ...(!lengthI32CacheUsed ? [] : [
1538
1800
  ...RTArrayUtil.getLengthI32(getPointer),
@@ -1544,13 +1806,22 @@ const generateCall = (scope, decl, _global, _name) => {
1544
1806
 
1545
1807
  // TODO: error better
1546
1808
  default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1547
- }, valtypeBinary),
1809
+ }, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
1548
1810
  ];
1549
1811
  }
1812
+
1813
+ if (Object.keys(protoBC).length > 0) {
1814
+ return typeSwitch(scope, getNodeType(scope, target), {
1815
+ ...protoBC,
1816
+
1817
+ // TODO: error better
1818
+ default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
1819
+ }, valtypeBinary);
1820
+ }
1550
1821
  }
1551
1822
 
1552
1823
  // TODO: only allows callee as literal
1553
- if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
1824
+ if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
1554
1825
 
1555
1826
  let idx = funcIndex[name] ?? importedFuncs[name];
1556
1827
  if (idx === undefined && builtinFuncs[name]) {
@@ -1558,22 +1829,6 @@ const generateCall = (scope, decl, _global, _name) => {
1558
1829
 
1559
1830
  includeBuiltin(scope, name);
1560
1831
  idx = funcIndex[name];
1561
-
1562
- // infer arguments types from builtins params
1563
- const func = funcs.find(x => x.name === name);
1564
- for (let i = 0; i < decl.arguments.length; i++) {
1565
- const arg = decl.arguments[i];
1566
- if (!arg.name) continue;
1567
-
1568
- const local = scope.locals[arg.name];
1569
- if (!local) continue;
1570
-
1571
- local.type = func.params[i];
1572
- if (local.type === Valtype.v128) {
1573
- // specify vec subtype inferred from last vec type in function name
1574
- local.vecType = name.split('_').reverse().find(x => x.includes('x'));
1575
- }
1576
- }
1577
1832
  }
1578
1833
 
1579
1834
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
@@ -1583,15 +1838,63 @@ const generateCall = (scope, decl, _global, _name) => {
1583
1838
  idx = -1;
1584
1839
  }
1585
1840
 
1841
+ if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1842
+ const wasmOps = {
1843
+ // pointer, align, offset
1844
+ i32_load: { imms: 2, args: [ true ], returns: 1 },
1845
+ // pointer, value, align, offset
1846
+ i32_store: { imms: 2, args: [ true, true ], returns: 0 },
1847
+ // pointer, align, offset
1848
+ i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
1849
+ // pointer, value, align, offset
1850
+ i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
1851
+ // pointer, align, offset
1852
+ i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
1853
+ // pointer, value, align, offset
1854
+ i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
1855
+
1856
+ // pointer, align, offset
1857
+ f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
1858
+ // pointer, value, align, offset
1859
+ f64_store: { imms: 2, args: [ true, false ], returns: 0 },
1860
+
1861
+ // value
1862
+ i32_const: { imms: 1, args: [], returns: 1 },
1863
+ };
1864
+
1865
+ const opName = name.slice('__Porffor_wasm_'.length);
1866
+
1867
+ if (wasmOps[opName]) {
1868
+ const op = wasmOps[opName];
1869
+
1870
+ const argOut = [];
1871
+ for (let i = 0; i < op.args.length; i++) argOut.push(
1872
+ ...generate(scope, decl.arguments[i]),
1873
+ ...(op.args[i] ? [ Opcodes.i32_to ] : [])
1874
+ );
1875
+
1876
+ // literals only
1877
+ const imms = decl.arguments.slice(op.args.length).map(x => x.value);
1878
+
1879
+ return [
1880
+ ...argOut,
1881
+ [ Opcodes[opName], ...imms ],
1882
+ ...(new Array(op.returns).fill(Opcodes.i32_from))
1883
+ ];
1884
+ }
1885
+ }
1886
+
1586
1887
  if (idx === undefined) {
1587
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`);
1588
- return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1888
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1889
+ return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1589
1890
  }
1590
1891
 
1591
1892
  const func = funcs.find(x => x.index === idx);
1592
1893
 
1593
1894
  const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1594
- const paramCount = func && (userFunc ? func.params.length / 2 : func.params.length);
1895
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1896
+ const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1897
+ const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1595
1898
 
1596
1899
  let args = decl.arguments;
1597
1900
  if (func && args.length < paramCount) {
@@ -1607,14 +1910,24 @@ const generateCall = (scope, decl, _global, _name) => {
1607
1910
  if (func && func.throws) scope.throws = true;
1608
1911
 
1609
1912
  let out = [];
1610
- for (const arg of args) {
1913
+ for (let i = 0; i < args.length; i++) {
1914
+ const arg = args[i];
1611
1915
  out = out.concat(generate(scope, arg));
1612
- if (userFunc) out = out.concat(getNodeType(scope, arg));
1916
+
1917
+ if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1918
+ out.push(Opcodes.i32_to);
1919
+ }
1920
+
1921
+ if (importedFuncs[name] && name.startsWith('profile')) {
1922
+ out.push(Opcodes.i32_to);
1923
+ }
1924
+
1925
+ if (typedParams) out = out.concat(getNodeType(scope, arg));
1613
1926
  }
1614
1927
 
1615
1928
  out.push([ Opcodes.call, idx ]);
1616
1929
 
1617
- if (!userFunc) {
1930
+ if (!typedReturns) {
1618
1931
  // let type;
1619
1932
  // if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
1620
1933
  // if (internalConstrs[name]) type = internalConstrs[name].type;
@@ -1624,7 +1937,11 @@ const generateCall = (scope, decl, _global, _name) => {
1624
1937
  // ...number(type, Valtype.i32),
1625
1938
  // [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1626
1939
  // );
1627
- } else out.push(setLastType(scope));
1940
+ } else out.push(...setLastType(scope));
1941
+
1942
+ if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1943
+ out.push(Opcodes.i32_from);
1944
+ }
1628
1945
 
1629
1946
  return out;
1630
1947
  };
@@ -1632,8 +1949,21 @@ const generateCall = (scope, decl, _global, _name) => {
1632
1949
  const generateNew = (scope, decl, _global, _name) => {
1633
1950
  // hack: basically treat this as a normal call for builtins for now
1634
1951
  const name = mapName(decl.callee.name);
1952
+
1635
1953
  if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1636
- if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1954
+
1955
+ if (builtinFuncs[name + '$constructor']) {
1956
+ // custom ...$constructor override builtin func
1957
+ return generateCall(scope, {
1958
+ ...decl,
1959
+ callee: {
1960
+ type: 'Identifier',
1961
+ name: name + '$constructor'
1962
+ }
1963
+ }, _global, _name);
1964
+ }
1965
+
1966
+ if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1637
1967
 
1638
1968
  return generateCall(scope, decl, _global, _name);
1639
1969
  };
@@ -1665,46 +1995,132 @@ const knownType = (scope, type) => {
1665
1995
  return null;
1666
1996
  };
1667
1997
 
1668
- const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
1669
- const known = knownType(scope, type);
1670
- if (known != null) {
1671
- return bc[known] ?? bc.default;
1998
+ const brTable = (input, bc, returns) => {
1999
+ const out = [];
2000
+ const keys = Object.keys(bc);
2001
+ const count = keys.length;
2002
+
2003
+ if (count === 1) {
2004
+ // return [
2005
+ // ...input,
2006
+ // ...bc[keys[0]]
2007
+ // ];
2008
+ return bc[keys[0]];
1672
2009
  }
1673
2010
 
1674
- const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2011
+ if (count === 2) {
2012
+ // just use if else
2013
+ const other = keys.find(x => x !== 'default');
2014
+ return [
2015
+ ...input,
2016
+ ...number(other, Valtype.i32),
2017
+ [ Opcodes.i32_eq ],
2018
+ [ Opcodes.if, returns ],
2019
+ ...bc[other],
2020
+ [ Opcodes.else ],
2021
+ ...bc.default,
2022
+ [ Opcodes.end ]
2023
+ ];
2024
+ }
1675
2025
 
1676
- const out = [
1677
- ...type,
1678
- [ Opcodes.local_set, tmp ],
1679
- [ Opcodes.block, returns ]
1680
- ];
2026
+ for (let i = 0; i < count; i++) {
2027
+ if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2028
+ else out.push([ Opcodes.block, Blocktype.void ]);
2029
+ }
1681
2030
 
1682
- // todo: use br_table?
2031
+ const nums = keys.filter(x => +x);
2032
+ const offset = Math.min(...nums);
2033
+ const max = Math.max(...nums);
1683
2034
 
1684
- for (const x in bc) {
1685
- if (x === 'default') continue;
2035
+ const table = [];
2036
+ let br = 1;
1686
2037
 
1687
- // if type == x
1688
- out.push([ Opcodes.local_get, tmp ]);
1689
- out.push(...number(x, Valtype.i32));
1690
- out.push([ Opcodes.i32_eq ]);
2038
+ for (let i = offset; i <= max; i++) {
2039
+ // if branch for this num, go to that block
2040
+ if (bc[i]) {
2041
+ table.push(br);
2042
+ br++;
2043
+ continue;
2044
+ }
1691
2045
 
1692
- out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
1693
- out.push(...bc[x]);
1694
- out.push([ Opcodes.br, 1 ]);
1695
- out.push([ Opcodes.end ]);
2046
+ // else default
2047
+ table.push(0);
1696
2048
  }
1697
2049
 
1698
- // default
1699
- if (bc.default) out.push(...bc.default);
1700
- else if (returns !== Blocktype.void) out.push(...number(0, returns));
2050
+ out.push(
2051
+ [ Opcodes.block, Blocktype.void ],
2052
+ ...input,
2053
+ ...(offset > 0 ? [
2054
+ ...number(offset, Valtype.i32),
2055
+ [ Opcodes.i32_sub ]
2056
+ ] : []),
2057
+ [ Opcodes.br_table, ...encodeVector(table), 0 ]
2058
+ );
2059
+
2060
+ // if you can guess why we sort the wrong way and then reverse
2061
+ // (instead of just sorting the correct way)
2062
+ // dm me and if you are correct and the first person
2063
+ // I will somehow shout you out or something
2064
+ const orderedBc = keys.sort((a, b) => b - a).reverse();
2065
+
2066
+ br = count - 1;
2067
+ for (const x of orderedBc) {
2068
+ out.push(
2069
+ [ Opcodes.end ],
2070
+ ...bc[x],
2071
+ ...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
2072
+ );
2073
+ br--;
2074
+ }
2075
+
2076
+ return [
2077
+ ...out,
2078
+ [ Opcodes.end, 'br table end' ]
2079
+ ];
2080
+ };
2081
+
2082
+ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2083
+ if (!Prefs.bytestring) delete bc[TYPES.bytestring];
2084
+
2085
+ const known = knownType(scope, type);
2086
+ if (known != null) {
2087
+ return bc[known] ?? bc.default;
2088
+ }
2089
+
2090
+ if (Prefs.typeswitchUseBrtable)
2091
+ return brTable(type, bc, returns);
2092
+
2093
+ const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
2094
+ const out = [
2095
+ ...type,
2096
+ [ Opcodes.local_set, tmp ],
2097
+ [ Opcodes.block, returns ]
2098
+ ];
2099
+
2100
+ for (const x in bc) {
2101
+ if (x === 'default') continue;
2102
+
2103
+ // if type == x
2104
+ out.push([ Opcodes.local_get, tmp ]);
2105
+ out.push(...number(x, Valtype.i32));
2106
+ out.push([ Opcodes.i32_eq ]);
2107
+
2108
+ out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
2109
+ out.push(...bc[x]);
2110
+ out.push([ Opcodes.br, 1 ]);
2111
+ out.push([ Opcodes.end ]);
2112
+ }
2113
+
2114
+ // default
2115
+ if (bc.default) out.push(...bc.default);
2116
+ else if (returns !== Blocktype.void) out.push(...number(0, returns));
1701
2117
 
1702
2118
  out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
1703
2119
 
1704
2120
  return out;
1705
2121
  };
1706
2122
 
1707
- const allocVar = (scope, name, global = false) => {
2123
+ const allocVar = (scope, name, global = false, type = true) => {
1708
2124
  const target = global ? globals : scope.locals;
1709
2125
 
1710
2126
  // already declared
@@ -1718,8 +2134,10 @@ const allocVar = (scope, name, global = false) => {
1718
2134
  let idx = global ? globalInd++ : scope.localInd++;
1719
2135
  target[name] = { idx, type: valtypeBinary };
1720
2136
 
1721
- let typeIdx = global ? globalInd++ : scope.localInd++;
1722
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2137
+ if (type) {
2138
+ let typeIdx = global ? globalInd++ : scope.localInd++;
2139
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2140
+ }
1723
2141
 
1724
2142
  return idx;
1725
2143
  };
@@ -1734,11 +2152,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
1734
2152
  };
1735
2153
 
1736
2154
  const typeAnnoToPorfType = x => {
1737
- if (TYPES[x]) return TYPES[x];
1738
- if (TYPES['_' + x]) return TYPES['_' + x];
2155
+ if (!x) return null;
2156
+ if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
1739
2157
 
1740
2158
  switch (x) {
1741
2159
  case 'i32':
2160
+ case 'i64':
2161
+ case 'f64':
1742
2162
  return TYPES.number;
1743
2163
  }
1744
2164
 
@@ -1749,7 +2169,7 @@ const extractTypeAnnotation = decl => {
1749
2169
  let a = decl;
1750
2170
  while (a.typeAnnotation) a = a.typeAnnotation;
1751
2171
 
1752
- let type, elementType;
2172
+ let type = null, elementType = null;
1753
2173
  if (a.typeName) {
1754
2174
  type = a.typeName.name;
1755
2175
  } else if (a.type.endsWith('Keyword')) {
@@ -1762,6 +2182,8 @@ const extractTypeAnnotation = decl => {
1762
2182
  const typeName = type;
1763
2183
  type = typeAnnoToPorfType(type);
1764
2184
 
2185
+ if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
2186
+
1765
2187
  // if (decl.name) console.log(decl.name, { type, elementType });
1766
2188
 
1767
2189
  return { type, typeName, elementType };
@@ -1772,12 +2194,14 @@ const generateVar = (scope, decl) => {
1772
2194
 
1773
2195
  const topLevel = scope.name === 'main';
1774
2196
 
1775
- // global variable if in top scope (main) and var ..., or if wanted
1776
- const global = topLevel || decl._bare; // decl.kind === 'var';
2197
+ // global variable if in top scope (main) or if internally wanted
2198
+ const global = topLevel || decl._bare;
1777
2199
 
1778
2200
  for (const x of decl.declarations) {
1779
2201
  const name = mapName(x.id.name);
1780
2202
 
2203
+ if (!name) return todo(scope, 'destructuring is not supported yet');
2204
+
1781
2205
  if (x.init && isFuncType(x.init.type)) {
1782
2206
  // hack for let a = function () { ... }
1783
2207
  x.init.id = { name };
@@ -1785,7 +2209,6 @@ const generateVar = (scope, decl) => {
1785
2209
  continue;
1786
2210
  }
1787
2211
 
1788
- // console.log(name);
1789
2212
  if (topLevel && builtinVars[name]) {
1790
2213
  // cannot redeclare
1791
2214
  if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
@@ -1793,26 +2216,41 @@ const generateVar = (scope, decl) => {
1793
2216
  continue; // always ignore
1794
2217
  }
1795
2218
 
1796
- let idx = allocVar(scope, name, global);
1797
- if (x.init) {
1798
- out = out.concat(generate(scope, x.init, global, name));
2219
+ // // generate init before allocating var
2220
+ // let generated;
2221
+ // if (x.init) generated = generate(scope, x.init, global, name);
2222
+
2223
+ const typed = typedInput && x.id.typeAnnotation;
2224
+ let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
2225
+
2226
+ if (typed) {
2227
+ addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
2228
+ }
1799
2229
 
1800
- out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2230
+ if (x.init) {
2231
+ const generated = generate(scope, x.init, global, name);
2232
+ if (scope.arrays?.get(name) != null) {
2233
+ // hack to set local as pointer before
2234
+ out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2235
+ if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
2236
+ generated.pop();
2237
+ out = out.concat(generated);
2238
+ } else {
2239
+ out = out.concat(generated);
2240
+ out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2241
+ }
1801
2242
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
1802
2243
  }
1803
2244
 
1804
2245
  // hack: this follows spec properly but is mostly unneeded 😅
1805
2246
  // out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
1806
-
1807
- if (typedInput && x.id.typeAnnotation) {
1808
- addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
1809
- }
1810
2247
  }
1811
2248
 
1812
2249
  return out;
1813
2250
  };
1814
2251
 
1815
- const generateAssign = (scope, decl) => {
2252
+ // todo: optimize this func for valueUnused
2253
+ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
1816
2254
  const { type, name } = decl.left;
1817
2255
 
1818
2256
  if (type === 'ObjectPattern') {
@@ -1827,22 +2265,30 @@ const generateAssign = (scope, decl) => {
1827
2265
  return [];
1828
2266
  }
1829
2267
 
2268
+ const op = decl.operator.slice(0, -1) || '=';
2269
+
1830
2270
  // hack: .length setter
1831
2271
  if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
1832
2272
  const name = decl.left.object.name;
1833
- const pointer = arrays.get(name);
2273
+ const pointer = scope.arrays?.get(name);
1834
2274
 
1835
- const aotPointer = pointer != null;
2275
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1836
2276
 
1837
2277
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
2278
+ const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1838
2279
 
1839
2280
  return [
1840
2281
  ...(aotPointer ? number(0, Valtype.i32) : [
1841
2282
  ...generate(scope, decl.left.object),
1842
2283
  Opcodes.i32_to_u
1843
2284
  ]),
2285
+ ...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1844
2286
 
1845
- ...generate(scope, decl.right),
2287
+ ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2288
+ [ Opcodes.local_get, pointerTmp ],
2289
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
2290
+ Opcodes.i32_from_u
2291
+ ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
1846
2292
  [ Opcodes.local_tee, newValueTmp ],
1847
2293
 
1848
2294
  Opcodes.i32_to_u,
@@ -1852,21 +2298,19 @@ const generateAssign = (scope, decl) => {
1852
2298
  ];
1853
2299
  }
1854
2300
 
1855
- const op = decl.operator.slice(0, -1) || '=';
1856
-
1857
2301
  // arr[i]
1858
2302
  if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1859
2303
  const name = decl.left.object.name;
1860
- const pointer = arrays.get(name);
2304
+ const pointer = scope.arrays?.get(name);
1861
2305
 
1862
- const aotPointer = pointer != null;
2306
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
1863
2307
 
1864
2308
  const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1865
2309
  const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1866
2310
 
1867
2311
  return [
1868
2312
  ...typeSwitch(scope, getNodeType(scope, decl.left.object), {
1869
- [TYPES._array]: [
2313
+ [TYPES.array]: [
1870
2314
  ...(aotPointer ? [] : [
1871
2315
  ...generate(scope, decl.left.object),
1872
2316
  Opcodes.i32_to_u
@@ -1915,6 +2359,8 @@ const generateAssign = (scope, decl) => {
1915
2359
  ];
1916
2360
  }
1917
2361
 
2362
+ if (!name) return todo(scope, 'destructuring is not supported yet', true);
2363
+
1918
2364
  const [ local, isGlobal ] = lookupName(scope, name);
1919
2365
 
1920
2366
  if (local === undefined) {
@@ -1961,9 +2407,7 @@ const generateAssign = (scope, decl) => {
1961
2407
  ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1962
2408
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1963
2409
 
1964
- getLastType(scope),
1965
- // hack: type is idx+1
1966
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2410
+ ...setType(scope, name, getLastType(scope))
1967
2411
  ];
1968
2412
  }
1969
2413
 
@@ -1974,9 +2418,7 @@ const generateAssign = (scope, decl) => {
1974
2418
 
1975
2419
  // todo: string concat types
1976
2420
 
1977
- // hack: type is idx+1
1978
- ...number(TYPES.number, Valtype.i32),
1979
- [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
2421
+ ...setType(scope, name, TYPES.number)
1980
2422
  ];
1981
2423
  };
1982
2424
 
@@ -2022,7 +2464,7 @@ const generateUnary = (scope, decl) => {
2022
2464
  return out;
2023
2465
  }
2024
2466
 
2025
- case 'delete':
2467
+ case 'delete': {
2026
2468
  let toReturn = true, toGenerate = true;
2027
2469
 
2028
2470
  if (decl.argument.type === 'Identifier') {
@@ -2044,38 +2486,60 @@ const generateUnary = (scope, decl) => {
2044
2486
 
2045
2487
  out.push(...number(toReturn ? 1 : 0));
2046
2488
  return out;
2489
+ }
2490
+
2491
+ case 'typeof': {
2492
+ let overrideType, toGenerate = true;
2493
+
2494
+ if (decl.argument.type === 'Identifier') {
2495
+ const out = generateIdent(scope, decl.argument);
2496
+
2497
+ // if ReferenceError (undeclared var), ignore and return undefined
2498
+ if (out[1]) {
2499
+ // does not exist (2 ops from throw)
2500
+ overrideType = number(TYPES.undefined, Valtype.i32);
2501
+ toGenerate = false;
2502
+ }
2503
+ }
2504
+
2505
+ const out = toGenerate ? generate(scope, decl.argument) : [];
2506
+ disposeLeftover(out);
2047
2507
 
2048
- case 'typeof':
2049
- return typeSwitch(scope, getNodeType(scope, decl.argument), {
2508
+ out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
2050
2509
  [TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
2051
2510
  [TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
2052
2511
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2053
2512
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2054
2513
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2055
2514
 
2515
+ [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2516
+
2056
2517
  // object and internal types
2057
2518
  default: makeString(scope, 'object', false, '#typeof_result'),
2058
- });
2519
+ }));
2520
+
2521
+ return out;
2522
+ }
2059
2523
 
2060
2524
  default:
2061
- return todo(`unary operator ${decl.operator} not implemented yet`);
2525
+ return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
2062
2526
  }
2063
2527
  };
2064
2528
 
2065
- const generateUpdate = (scope, decl) => {
2529
+ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
2066
2530
  const { name } = decl.argument;
2067
2531
 
2068
2532
  const [ local, isGlobal ] = lookupName(scope, name);
2069
2533
 
2070
2534
  if (local === undefined) {
2071
- return todo(`update expression with undefined variable`);
2535
+ return todo(scope, `update expression with undefined variable`, true);
2072
2536
  }
2073
2537
 
2074
2538
  const idx = local.idx;
2075
2539
  const out = [];
2076
2540
 
2077
2541
  out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2078
- if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2542
+ if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2079
2543
 
2080
2544
  switch (decl.operator) {
2081
2545
  case '++':
@@ -2088,7 +2552,7 @@ const generateUpdate = (scope, decl) => {
2088
2552
  }
2089
2553
 
2090
2554
  out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
2091
- if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2555
+ if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
2092
2556
 
2093
2557
  return out;
2094
2558
  };
@@ -2128,7 +2592,7 @@ const generateConditional = (scope, decl) => {
2128
2592
  // note type
2129
2593
  out.push(
2130
2594
  ...getNodeType(scope, decl.consequent),
2131
- setLastType(scope)
2595
+ ...setLastType(scope)
2132
2596
  );
2133
2597
 
2134
2598
  out.push([ Opcodes.else ]);
@@ -2137,7 +2601,7 @@ const generateConditional = (scope, decl) => {
2137
2601
  // note type
2138
2602
  out.push(
2139
2603
  ...getNodeType(scope, decl.alternate),
2140
- setLastType(scope)
2604
+ ...setLastType(scope)
2141
2605
  );
2142
2606
 
2143
2607
  out.push([ Opcodes.end ]);
@@ -2151,15 +2615,17 @@ const generateFor = (scope, decl) => {
2151
2615
  const out = [];
2152
2616
 
2153
2617
  if (decl.init) {
2154
- out.push(...generate(scope, decl.init));
2618
+ out.push(...generate(scope, decl.init, false, undefined, true));
2155
2619
  disposeLeftover(out);
2156
2620
  }
2157
2621
 
2158
2622
  out.push([ Opcodes.loop, Blocktype.void ]);
2159
2623
  depth.push('for');
2160
2624
 
2161
- out.push(...generate(scope, decl.test));
2162
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
2625
+ if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
2626
+ else out.push(...number(1, Valtype.i32));
2627
+
2628
+ out.push([ Opcodes.if, Blocktype.void ]);
2163
2629
  depth.push('if');
2164
2630
 
2165
2631
  out.push([ Opcodes.block, Blocktype.void ]);
@@ -2167,8 +2633,7 @@ const generateFor = (scope, decl) => {
2167
2633
  out.push(...generate(scope, decl.body));
2168
2634
  out.push([ Opcodes.end ]);
2169
2635
 
2170
- out.push(...generate(scope, decl.update));
2171
- depth.pop();
2636
+ if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
2172
2637
 
2173
2638
  out.push([ Opcodes.br, 1 ]);
2174
2639
  out.push([ Opcodes.end ], [ Opcodes.end ]);
@@ -2196,6 +2661,36 @@ const generateWhile = (scope, decl) => {
2196
2661
  return out;
2197
2662
  };
2198
2663
 
2664
+ const generateDoWhile = (scope, decl) => {
2665
+ const out = [];
2666
+
2667
+ out.push([ Opcodes.loop, Blocktype.void ]);
2668
+ depth.push('dowhile');
2669
+
2670
+ // block for break (includes all)
2671
+ out.push([ Opcodes.block, Blocktype.void ]);
2672
+ depth.push('block');
2673
+
2674
+ // block for continue
2675
+ // includes body but not test+loop so we can exit body at anytime
2676
+ // and still test+loop after
2677
+ out.push([ Opcodes.block, Blocktype.void ]);
2678
+ depth.push('block');
2679
+
2680
+ out.push(...generate(scope, decl.body));
2681
+
2682
+ out.push([ Opcodes.end ]);
2683
+ depth.pop();
2684
+
2685
+ out.push(...generate(scope, decl.test), Opcodes.i32_to);
2686
+ out.push([ Opcodes.br_if, 1 ]);
2687
+
2688
+ out.push([ Opcodes.end ], [ Opcodes.end ]);
2689
+ depth.pop(); depth.pop();
2690
+
2691
+ return out;
2692
+ };
2693
+
2199
2694
  const generateForOf = (scope, decl) => {
2200
2695
  const out = [];
2201
2696
 
@@ -2225,8 +2720,17 @@ const generateForOf = (scope, decl) => {
2225
2720
  // setup local for left
2226
2721
  generate(scope, decl.left);
2227
2722
 
2228
- const leftName = decl.left.declarations[0].id.name;
2723
+ let leftName = decl.left.declarations?.[0]?.id?.name;
2724
+ if (!leftName && decl.left.name) {
2725
+ leftName = decl.left.name;
2726
+
2727
+ generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
2728
+ }
2729
+
2730
+ // if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
2731
+
2229
2732
  const [ local, isGlobal ] = lookupName(scope, leftName);
2733
+ if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
2230
2734
 
2231
2735
  depth.push('block');
2232
2736
  depth.push('block');
@@ -2234,15 +2738,17 @@ const generateForOf = (scope, decl) => {
2234
2738
  // // todo: we should only do this for strings but we don't know at compile-time :(
2235
2739
  // hack: this is naughty and will break things!
2236
2740
  let newOut = number(0, Valtype.f64), newPointer = -1;
2237
- if (pages.hasString) {
2741
+ if (pages.hasAnyString) {
2742
+ // todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
2238
2743
  0, [ newOut, newPointer ] = makeArray(scope, {
2239
2744
  rawElements: new Array(1)
2240
2745
  }, isGlobal, leftName, true, 'i16');
2241
2746
  }
2242
2747
 
2243
2748
  // set type for local
2749
+ // todo: optimize away counter and use end pointer
2244
2750
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2245
- [TYPES._array]: [
2751
+ [TYPES.array]: [
2246
2752
  ...setType(scope, leftName, TYPES.number),
2247
2753
 
2248
2754
  [ Opcodes.loop, Blocktype.void ],
@@ -2325,6 +2831,56 @@ const generateForOf = (scope, decl) => {
2325
2831
  [ Opcodes.end ],
2326
2832
  [ Opcodes.end ]
2327
2833
  ],
2834
+ [TYPES.bytestring]: [
2835
+ ...setType(scope, leftName, TYPES.bytestring),
2836
+
2837
+ [ Opcodes.loop, Blocktype.void ],
2838
+
2839
+ // setup new/out array
2840
+ ...newOut,
2841
+ [ Opcodes.drop ],
2842
+
2843
+ ...number(0, Valtype.i32), // base 0 for store after
2844
+
2845
+ // load current string ind {arg}
2846
+ [ Opcodes.local_get, pointer ],
2847
+ [ Opcodes.local_get, counter ],
2848
+ [ Opcodes.i32_add ],
2849
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2850
+
2851
+ // store to new string ind 0
2852
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
2853
+
2854
+ // return new string (page)
2855
+ ...number(newPointer),
2856
+
2857
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2858
+
2859
+ [ Opcodes.block, Blocktype.void ],
2860
+ [ Opcodes.block, Blocktype.void ],
2861
+ ...generate(scope, decl.body),
2862
+ [ Opcodes.end ],
2863
+
2864
+ // increment iter pointer
2865
+ // [ Opcodes.local_get, pointer ],
2866
+ // ...number(1, Valtype.i32),
2867
+ // [ Opcodes.i32_add ],
2868
+ // [ Opcodes.local_set, pointer ],
2869
+
2870
+ // increment counter by 1
2871
+ [ Opcodes.local_get, counter ],
2872
+ ...number(1, Valtype.i32),
2873
+ [ Opcodes.i32_add ],
2874
+ [ Opcodes.local_tee, counter ],
2875
+
2876
+ // loop if counter != length
2877
+ [ Opcodes.local_get, length ],
2878
+ [ Opcodes.i32_ne ],
2879
+ [ Opcodes.br_if, 1 ],
2880
+
2881
+ [ Opcodes.end ],
2882
+ [ Opcodes.end ]
2883
+ ],
2328
2884
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2329
2885
  }, Blocktype.void));
2330
2886
 
@@ -2335,28 +2891,65 @@ const generateForOf = (scope, decl) => {
2335
2891
  return out;
2336
2892
  };
2337
2893
 
2894
+ // find the nearest loop in depth map by type
2338
2895
  const getNearestLoop = () => {
2339
2896
  for (let i = depth.length - 1; i >= 0; i--) {
2340
- if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
2897
+ if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
2341
2898
  }
2342
2899
 
2343
2900
  return -1;
2344
2901
  };
2345
2902
 
2346
2903
  const generateBreak = (scope, decl) => {
2347
- const nearestLoop = depth.length - getNearestLoop();
2904
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2905
+ const type = depth[target];
2906
+
2907
+ // different loop types have different branch offsets
2908
+ // as they have different wasm block/loop/if structures
2909
+ // we need to use the right offset by type to branch to the one we want
2910
+ // for a break: exit the loop without executing anything else inside it
2911
+ const offset = ({
2912
+ for: 2, // loop > if (wanted branch) > block (we are here)
2913
+ while: 2, // loop > if (wanted branch) (we are here)
2914
+ dowhile: 2, // loop > block (wanted branch) > block (we are here)
2915
+ forof: 2, // loop > block (wanted branch) > block (we are here)
2916
+ if: 1 // break inside if, branch 0 to skip the rest of the if
2917
+ })[type];
2918
+
2348
2919
  return [
2349
- [ Opcodes.br, ...signedLEB128(nearestLoop - 2) ]
2920
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2350
2921
  ];
2351
2922
  };
2352
2923
 
2353
2924
  const generateContinue = (scope, decl) => {
2354
- const nearestLoop = depth.length - getNearestLoop();
2925
+ const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
2926
+ const type = depth[target];
2927
+
2928
+ // different loop types have different branch offsets
2929
+ // as they have different wasm block/loop/if structures
2930
+ // we need to use the right offset by type to branch to the one we want
2931
+ // for a continue: do test for the loop, and then loop depending on that success
2932
+ const offset = ({
2933
+ for: 3, // loop (wanted branch) > if > block (we are here)
2934
+ while: 1, // loop (wanted branch) > if (we are here)
2935
+ dowhile: 3, // loop > block > block (wanted branch) (we are here)
2936
+ forof: 3 // loop > block > block (wanted branch) (we are here)
2937
+ })[type];
2938
+
2355
2939
  return [
2356
- [ Opcodes.br, ...signedLEB128(nearestLoop - 3) ]
2940
+ [ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
2357
2941
  ];
2358
2942
  };
2359
2943
 
2944
+ const generateLabel = (scope, decl) => {
2945
+ scope.labels ??= new Map();
2946
+
2947
+ const name = decl.label.name;
2948
+ scope.labels.set(name, depth.length);
2949
+
2950
+ return generate(scope, decl.body);
2951
+ };
2952
+
2360
2953
  const generateThrow = (scope, decl) => {
2361
2954
  scope.throws = true;
2362
2955
 
@@ -2365,7 +2958,7 @@ const generateThrow = (scope, decl) => {
2365
2958
  // hack: throw new X("...") -> throw "..."
2366
2959
  if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
2367
2960
  constructor = decl.argument.callee.name;
2368
- message = decl.argument.arguments[0].value;
2961
+ message = decl.argument.arguments[0]?.value ?? '';
2369
2962
  }
2370
2963
 
2371
2964
  if (tags.length === 0) tags.push({
@@ -2377,6 +2970,9 @@ const generateThrow = (scope, decl) => {
2377
2970
  let exceptId = exceptions.push({ constructor, message }) - 1;
2378
2971
  let tagIdx = tags[0].idx;
2379
2972
 
2973
+ scope.exceptions ??= [];
2974
+ scope.exceptions.push(exceptId);
2975
+
2380
2976
  // todo: write a description of how this works lol
2381
2977
 
2382
2978
  return [
@@ -2386,7 +2982,7 @@ const generateThrow = (scope, decl) => {
2386
2982
  };
2387
2983
 
2388
2984
  const generateTry = (scope, decl) => {
2389
- if (decl.finalizer) return todo('try finally not implemented yet');
2985
+ if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
2390
2986
 
2391
2987
  const out = [];
2392
2988
 
@@ -2417,29 +3013,35 @@ const generateAssignPat = (scope, decl) => {
2417
3013
  // TODO
2418
3014
  // if identifier declared, use that
2419
3015
  // else, use default (right)
2420
- return todo('assignment pattern (optional arg)');
3016
+ return todo(scope, 'assignment pattern (optional arg)');
2421
3017
  };
2422
3018
 
2423
3019
  let pages = new Map();
2424
- const allocPage = (reason, type) => {
3020
+ const allocPage = (scope, reason, type) => {
2425
3021
  if (pages.has(reason)) return pages.get(reason).ind;
2426
3022
 
2427
3023
  if (reason.startsWith('array:')) pages.hasArray = true;
2428
3024
  if (reason.startsWith('string:')) pages.hasString = true;
3025
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
3026
+ if (reason.includes('string:')) pages.hasAnyString = true;
2429
3027
 
2430
3028
  const ind = pages.size;
2431
3029
  pages.set(reason, { ind, type });
2432
3030
 
2433
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
3031
+ scope.pages ??= new Map();
3032
+ scope.pages.set(reason, { ind, type });
3033
+
3034
+ if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
2434
3035
 
2435
3036
  return ind;
2436
3037
  };
2437
3038
 
3039
+ // todo: add scope.pages
2438
3040
  const freePage = reason => {
2439
3041
  const { ind } = pages.get(reason);
2440
3042
  pages.delete(reason);
2441
3043
 
2442
- if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
3044
+ if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
2443
3045
 
2444
3046
  return ind;
2445
3047
  };
@@ -2459,38 +3061,53 @@ const StoreOps = {
2459
3061
  f64: Opcodes.f64_store,
2460
3062
 
2461
3063
  // expects i32 input!
2462
- i16: Opcodes.i32_store16
3064
+ i8: Opcodes.i32_store8,
3065
+ i16: Opcodes.i32_store16,
2463
3066
  };
2464
3067
 
2465
3068
  let data = [];
2466
3069
 
2467
- const compileBytes = (val, itemType, signed = true) => {
3070
+ const compileBytes = (val, itemType) => {
2468
3071
  // todo: this is a mess and needs confirming / ????
2469
3072
  switch (itemType) {
2470
3073
  case 'i8': return [ val % 256 ];
2471
- case 'i16': return [ val % 256, Math.floor(val / 256) ];
2472
-
2473
- case 'i32':
2474
- case 'i64':
2475
- return enforceFourBytes(signedLEB128(val));
3074
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3075
+ case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
3076
+ case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
3077
+ // todo: i64
2476
3078
 
2477
3079
  case 'f64': return ieee754_binary64(val);
2478
3080
  }
2479
3081
  };
2480
3082
 
3083
+ const getAllocType = itemType => {
3084
+ switch (itemType) {
3085
+ case 'i8': return 'bytestring';
3086
+ case 'i16': return 'string';
3087
+
3088
+ default: return 'array';
3089
+ }
3090
+ };
3091
+
2481
3092
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
2482
3093
  const out = [];
2483
3094
 
3095
+ scope.arrays ??= new Map();
3096
+
2484
3097
  let firstAssign = false;
2485
- if (!arrays.has(name) || name === '$undeclared') {
3098
+ if (!scope.arrays.has(name) || name === '$undeclared') {
2486
3099
  firstAssign = true;
2487
3100
 
2488
3101
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
2489
3102
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
2490
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
3103
+
3104
+ if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3105
+ else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
2491
3106
  }
2492
3107
 
2493
- const pointer = arrays.get(name);
3108
+ const pointer = scope.arrays.get(name);
3109
+
3110
+ const local = global ? globals[name] : scope.locals[name];
2494
3111
 
2495
3112
  const useRawElements = !!decl.rawElements;
2496
3113
  const elements = useRawElements ? decl.rawElements : decl.elements;
@@ -2498,19 +3115,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2498
3115
  const valtype = itemTypeToValtype[itemType];
2499
3116
  const length = elements.length;
2500
3117
 
2501
- if (firstAssign && useRawElements) {
2502
- let bytes = compileBytes(length, 'i32');
3118
+ if (firstAssign && useRawElements && !Prefs.noData) {
3119
+ // if length is 0 memory/data will just be 0000... anyway
3120
+ if (length !== 0) {
3121
+ let bytes = compileBytes(length, 'i32');
2503
3122
 
2504
- if (!initEmpty) for (let i = 0; i < length; i++) {
2505
- if (elements[i] == null) continue;
3123
+ if (!initEmpty) for (let i = 0; i < length; i++) {
3124
+ if (elements[i] == null) continue;
2506
3125
 
2507
- bytes.push(...compileBytes(elements[i], itemType));
2508
- }
3126
+ bytes.push(...compileBytes(elements[i], itemType));
3127
+ }
2509
3128
 
2510
- data.push({
2511
- offset: pointer,
2512
- bytes
2513
- });
3129
+ const ind = data.push({
3130
+ offset: pointer,
3131
+ bytes
3132
+ }) - 1;
3133
+
3134
+ scope.data ??= [];
3135
+ scope.data.push(ind);
3136
+ }
2514
3137
 
2515
3138
  // local value as pointer
2516
3139
  out.push(...number(pointer));
@@ -2518,11 +3141,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2518
3141
  return [ out, pointer ];
2519
3142
  }
2520
3143
 
3144
+ const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
3145
+ if (pointerTmp != null) {
3146
+ out.push(
3147
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
3148
+ Opcodes.i32_to_u,
3149
+ [ Opcodes.local_set, pointerTmp ]
3150
+ );
3151
+ }
3152
+
3153
+ const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3154
+
2521
3155
  // store length as 0th array
2522
3156
  out.push(
2523
- ...number(0, Valtype.i32),
3157
+ ...pointerWasm,
2524
3158
  ...number(length, Valtype.i32),
2525
- [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
3159
+ [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
2526
3160
  );
2527
3161
 
2528
3162
  const storeOp = StoreOps[itemType];
@@ -2531,43 +3165,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
2531
3165
  if (elements[i] == null) continue;
2532
3166
 
2533
3167
  out.push(
2534
- ...number(0, Valtype.i32),
3168
+ ...pointerWasm,
2535
3169
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
2536
- [ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3170
+ [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
2537
3171
  );
2538
3172
  }
2539
3173
 
2540
3174
  // local value as pointer
2541
- out.push(...number(pointer));
3175
+ out.push(...pointerWasm, Opcodes.i32_from_u);
2542
3176
 
2543
3177
  return [ out, pointer ];
2544
3178
  };
2545
3179
 
2546
- const makeString = (scope, str, global = false, name = '$undeclared') => {
3180
+ const byteStringable = str => {
3181
+ if (!Prefs.bytestring) return false;
3182
+
3183
+ for (let i = 0; i < str.length; i++) {
3184
+ if (str.charCodeAt(i) > 0xFF) return false;
3185
+ }
3186
+
3187
+ return true;
3188
+ };
3189
+
3190
+ const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
2547
3191
  const rawElements = new Array(str.length);
3192
+ let byteStringable = Prefs.bytestring;
2548
3193
  for (let i = 0; i < str.length; i++) {
2549
- rawElements[i] = str.charCodeAt(i);
3194
+ const c = str.charCodeAt(i);
3195
+ rawElements[i] = c;
3196
+
3197
+ if (byteStringable && c > 0xFF) byteStringable = false;
2550
3198
  }
2551
3199
 
3200
+ if (byteStringable && forceBytestring === false) byteStringable = false;
3201
+
2552
3202
  return makeArray(scope, {
2553
3203
  rawElements
2554
- }, global, name, false, 'i16')[0];
3204
+ }, global, name, false, byteStringable ? 'i8' : 'i16')[0];
2555
3205
  };
2556
3206
 
2557
- let arrays = new Map();
2558
3207
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
2559
3208
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
2560
3209
  };
2561
3210
 
2562
3211
  export const generateMember = (scope, decl, _global, _name) => {
2563
3212
  const name = decl.object.name;
2564
- const pointer = arrays.get(name);
3213
+ const pointer = scope.arrays?.get(name);
3214
+
3215
+ const aotPointer = Prefs.aotPointerOpt && pointer != null;
3216
+
3217
+ // hack: .name
3218
+ if (decl.property.name === 'name') {
3219
+ if (hasFuncWithName(name)) {
3220
+ let nameProp = name;
2565
3221
 
2566
- const aotPointer = pointer != null;
3222
+ // eg: __String_prototype_toLowerCase -> toLowerCase
3223
+ if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3224
+
3225
+ return makeString(scope, nameProp, _global, _name, true);
3226
+ } else {
3227
+ return generate(scope, DEFAULT_VALUE);
3228
+ }
3229
+ }
2567
3230
 
2568
3231
  // hack: .length
2569
3232
  if (decl.property.name === 'length') {
2570
- // if (![TYPES._array, TYPES.string].includes(type)) return number(UNDEFINED);
3233
+ const func = funcs.find(x => x.name === name);
3234
+ if (func) {
3235
+ const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3236
+ const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3237
+ return number(typedParams ? func.params.length / 2 : func.params.length);
3238
+ }
3239
+
3240
+ if (builtinFuncs[name + '$constructor']) {
3241
+ const regularFunc = builtinFuncs[name];
3242
+ const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
3243
+
3244
+ const constructorFunc = builtinFuncs[name + '$constructor'];
3245
+ const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3246
+
3247
+ return number(Math.max(regularParams, constructorParams));
3248
+ }
3249
+
3250
+ if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3251
+ if (importedFuncs[name]) return number(importedFuncs[name].params);
3252
+ if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3253
+
2571
3254
  return [
2572
3255
  ...(aotPointer ? number(0, Valtype.i32) : [
2573
3256
  ...generate(scope, decl.object),
@@ -2579,19 +3262,22 @@ export const generateMember = (scope, decl, _global, _name) => {
2579
3262
  ];
2580
3263
  }
2581
3264
 
3265
+ const object = generate(scope, decl.object);
3266
+ const property = generate(scope, decl.property);
3267
+
2582
3268
  // // todo: we should only do this for strings but we don't know at compile-time :(
2583
3269
  // hack: this is naughty and will break things!
2584
- let newOut = number(0, Valtype.f64), newPointer = -1;
2585
- if (pages.hasString) {
3270
+ let newOut = number(0, valtypeBinary), newPointer = -1;
3271
+ if (pages.hasAnyString) {
2586
3272
  0, [ newOut, newPointer ] = makeArray(scope, {
2587
3273
  rawElements: new Array(1)
2588
3274
  }, _global, _name, true, 'i16');
2589
3275
  }
2590
3276
 
2591
3277
  return typeSwitch(scope, getNodeType(scope, decl.object), {
2592
- [TYPES._array]: [
3278
+ [TYPES.array]: [
2593
3279
  // get index as valtype
2594
- ...generate(scope, decl.property),
3280
+ ...property,
2595
3281
 
2596
3282
  // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2597
3283
  Opcodes.i32_to_u,
@@ -2599,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2599
3285
  [ Opcodes.i32_mul ],
2600
3286
 
2601
3287
  ...(aotPointer ? [] : [
2602
- ...generate(scope, decl.object),
3288
+ ...object,
2603
3289
  Opcodes.i32_to_u,
2604
3290
  [ Opcodes.i32_add ]
2605
3291
  ]),
@@ -2608,7 +3294,7 @@ export const generateMember = (scope, decl, _global, _name) => {
2608
3294
  [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
2609
3295
 
2610
3296
  ...number(TYPES.number, Valtype.i32),
2611
- setLastType(scope)
3297
+ ...setLastType(scope)
2612
3298
  ],
2613
3299
 
2614
3300
  [TYPES.string]: [
@@ -2618,14 +3304,14 @@ export const generateMember = (scope, decl, _global, _name) => {
2618
3304
 
2619
3305
  ...number(0, Valtype.i32), // base 0 for store later
2620
3306
 
2621
- ...generate(scope, decl.property),
2622
-
3307
+ ...property,
2623
3308
  Opcodes.i32_to_u,
3309
+
2624
3310
  ...number(ValtypeSize.i16, Valtype.i32),
2625
3311
  [ Opcodes.i32_mul ],
2626
3312
 
2627
3313
  ...(aotPointer ? [] : [
2628
- ...generate(scope, decl.object),
3314
+ ...object,
2629
3315
  Opcodes.i32_to_u,
2630
3316
  [ Opcodes.i32_add ]
2631
3317
  ]),
@@ -2640,10 +3326,38 @@ export const generateMember = (scope, decl, _global, _name) => {
2640
3326
  ...number(newPointer),
2641
3327
 
2642
3328
  ...number(TYPES.string, Valtype.i32),
2643
- setLastType(scope)
3329
+ ...setLastType(scope)
3330
+ ],
3331
+ [TYPES.bytestring]: [
3332
+ // setup new/out array
3333
+ ...newOut,
3334
+ [ Opcodes.drop ],
3335
+
3336
+ ...number(0, Valtype.i32), // base 0 for store later
3337
+
3338
+ ...property,
3339
+ Opcodes.i32_to_u,
3340
+
3341
+ ...(aotPointer ? [] : [
3342
+ ...object,
3343
+ Opcodes.i32_to_u,
3344
+ [ Opcodes.i32_add ]
3345
+ ]),
3346
+
3347
+ // load current string ind {arg}
3348
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3349
+
3350
+ // store to new string ind 0
3351
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
3352
+
3353
+ // return new string (page)
3354
+ ...number(newPointer),
3355
+
3356
+ ...number(TYPES.bytestring, Valtype.i32),
3357
+ ...setLastType(scope)
2644
3358
  ],
2645
3359
 
2646
- default: [ [ Opcodes.unreachable ] ]
3360
+ default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
2647
3361
  });
2648
3362
  };
2649
3363
 
@@ -2653,25 +3367,36 @@ const objectHack = node => {
2653
3367
  if (!node) return node;
2654
3368
 
2655
3369
  if (node.type === 'MemberExpression') {
2656
- if (node.computed || node.optional) return node;
3370
+ const out = (() => {
3371
+ if (node.computed || node.optional) return;
2657
3372
 
2658
- let objectName = node.object.name;
3373
+ let objectName = node.object.name;
2659
3374
 
2660
- // if object is not identifier or another member exp, give up
2661
- if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
3375
+ // if object is not identifier or another member exp, give up
3376
+ if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
3377
+ if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
2662
3378
 
2663
- if (!objectName) objectName = objectHack(node.object).name.slice(2);
3379
+ if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
2664
3380
 
2665
- // if .length, give up (hack within a hack!)
2666
- if (node.property.name === 'length') return node;
3381
+ // if .name or .length, give up (hack within a hack!)
3382
+ if (['name', 'length'].includes(node.property.name)) {
3383
+ node.object = objectHack(node.object);
3384
+ return;
3385
+ }
2667
3386
 
2668
- const name = '__' + objectName + '_' + node.property.name;
2669
- if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3387
+ // no object name, give up
3388
+ if (!objectName) return;
2670
3389
 
2671
- return {
2672
- type: 'Identifier',
2673
- name
2674
- };
3390
+ const name = '__' + objectName + '_' + node.property.name;
3391
+ if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
3392
+
3393
+ return {
3394
+ type: 'Identifier',
3395
+ name
3396
+ };
3397
+ })();
3398
+
3399
+ if (out) return out;
2675
3400
  }
2676
3401
 
2677
3402
  for (const x in node) {
@@ -2685,8 +3410,8 @@ const objectHack = node => {
2685
3410
  };
2686
3411
 
2687
3412
  const generateFunc = (scope, decl) => {
2688
- if (decl.async) return todo('async functions are not supported');
2689
- if (decl.generator) return todo('generator functions are not supported');
3413
+ if (decl.async) return todo(scope, 'async functions are not supported');
3414
+ if (decl.generator) return todo(scope, 'generator functions are not supported');
2690
3415
 
2691
3416
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
2692
3417
  const params = decl.params ?? [];
@@ -2702,6 +3427,14 @@ const generateFunc = (scope, decl) => {
2702
3427
  name
2703
3428
  };
2704
3429
 
3430
+ if (typedInput && decl.returnType) {
3431
+ const { type } = extractTypeAnnotation(decl.returnType);
3432
+ if (type != null) {
3433
+ innerScope.returnType = type;
3434
+ innerScope.returns = [ valtypeBinary ];
3435
+ }
3436
+ }
3437
+
2705
3438
  for (let i = 0; i < params.length; i++) {
2706
3439
  allocVar(innerScope, params[i].name, false);
2707
3440
 
@@ -2723,13 +3456,13 @@ const generateFunc = (scope, decl) => {
2723
3456
  const func = {
2724
3457
  name,
2725
3458
  params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
2726
- returns: innerScope.returns,
2727
- locals: innerScope.locals,
2728
- throws: innerScope.throws,
2729
- index: currentFuncIndex++
3459
+ index: currentFuncIndex++,
3460
+ ...innerScope
2730
3461
  };
2731
3462
  funcIndex[name] = func.index;
2732
3463
 
3464
+ if (name === 'main') func.gotLastType = true;
3465
+
2733
3466
  // quick hack fixes
2734
3467
  for (const inst of wasm) {
2735
3468
  if (inst[0] === Opcodes.call && inst[1] === -1) {
@@ -2741,7 +3474,7 @@ const generateFunc = (scope, decl) => {
2741
3474
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
2742
3475
  wasm.push(
2743
3476
  ...number(0),
2744
- ...number(TYPES.undefined, Valtype.i32),
3477
+ ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
2745
3478
  [ Opcodes.return ]
2746
3479
  );
2747
3480
  }
@@ -2781,7 +3514,7 @@ const internalConstrs = {
2781
3514
 
2782
3515
  // todo: check in wasm instead of here
2783
3516
  const literalValue = arg.value ?? 0;
2784
- if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
3517
+ if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
2785
3518
 
2786
3519
  return [
2787
3520
  ...number(0, Valtype.i32),
@@ -2792,7 +3525,8 @@ const internalConstrs = {
2792
3525
  ...number(pointer)
2793
3526
  ];
2794
3527
  },
2795
- type: TYPES._array
3528
+ type: TYPES.array,
3529
+ length: 1
2796
3530
  },
2797
3531
 
2798
3532
  __Array_of: {
@@ -2803,27 +3537,134 @@ const internalConstrs = {
2803
3537
  elements: decl.arguments
2804
3538
  }, global, name);
2805
3539
  },
2806
- type: TYPES._array,
3540
+ type: TYPES.array,
3541
+ notConstr: true,
3542
+ length: 0
3543
+ },
3544
+
3545
+ __Porffor_fastOr: {
3546
+ generate: (scope, decl) => {
3547
+ const out = [];
3548
+
3549
+ for (let i = 0; i < decl.arguments.length; i++) {
3550
+ out.push(
3551
+ ...generate(scope, decl.arguments[i]),
3552
+ Opcodes.i32_to_u,
3553
+ ...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
3554
+ );
3555
+ }
3556
+
3557
+ out.push(Opcodes.i32_from_u);
3558
+
3559
+ return out;
3560
+ },
3561
+ type: TYPES.boolean,
2807
3562
  notConstr: true
2808
- }
2809
- };
3563
+ },
3564
+
3565
+ __Porffor_fastAnd: {
3566
+ generate: (scope, decl) => {
3567
+ const out = [];
3568
+
3569
+ for (let i = 0; i < decl.arguments.length; i++) {
3570
+ out.push(
3571
+ ...generate(scope, decl.arguments[i]),
3572
+ Opcodes.i32_to_u,
3573
+ ...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
3574
+ );
3575
+ }
2810
3576
 
2811
- // const _ = Array.prototype.push;
2812
- // Array.prototype.push = function (a) {
2813
- // const check = arr => {
2814
- // for (const x of arr) {
2815
- // if (x === undefined) {
2816
- // console.trace(arr);
2817
- // process.exit();
2818
- // }
2819
- // if (Array.isArray(x)) check(x);
2820
- // }
2821
- // };
2822
- // if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
2823
- // // if (Array.isArray(a)) check(a);
3577
+ out.push(Opcodes.i32_from_u);
2824
3578
 
2825
- // return _.apply(this, arguments);
2826
- // };
3579
+ return out;
3580
+ },
3581
+ type: TYPES.boolean,
3582
+ notConstr: true
3583
+ },
3584
+
3585
+ Boolean: {
3586
+ generate: (scope, decl) => {
3587
+ // todo: boolean object when used as constructor
3588
+ const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3589
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3590
+ },
3591
+ type: TYPES.boolean,
3592
+ length: 1
3593
+ },
3594
+
3595
+ __Math_max: {
3596
+ generate: (scope, decl) => {
3597
+ const out = [
3598
+ ...number(-Infinity)
3599
+ ];
3600
+
3601
+ for (let i = 0; i < decl.arguments.length; i++) {
3602
+ out.push(
3603
+ ...generate(scope, decl.arguments[i]),
3604
+ [ Opcodes.f64_max ]
3605
+ );
3606
+ }
3607
+
3608
+ return out;
3609
+ },
3610
+ type: TYPES.number,
3611
+ notConstr: true,
3612
+ length: 2
3613
+ },
3614
+
3615
+ __Math_min: {
3616
+ generate: (scope, decl) => {
3617
+ const out = [
3618
+ ...number(Infinity)
3619
+ ];
3620
+
3621
+ for (let i = 0; i < decl.arguments.length; i++) {
3622
+ out.push(
3623
+ ...generate(scope, decl.arguments[i]),
3624
+ [ Opcodes.f64_min ]
3625
+ );
3626
+ }
3627
+
3628
+ return out;
3629
+ },
3630
+ type: TYPES.number,
3631
+ notConstr: true,
3632
+ length: 2
3633
+ },
3634
+
3635
+ __console_log: {
3636
+ generate: (scope, decl) => {
3637
+ const out = [];
3638
+
3639
+ for (let i = 0; i < decl.arguments.length; i++) {
3640
+ out.push(
3641
+ ...generateCall(scope, {
3642
+ callee: {
3643
+ type: 'Identifier',
3644
+ name: '__Porffor_print'
3645
+ },
3646
+ arguments: [ decl.arguments[i] ]
3647
+ }),
3648
+
3649
+ // print space
3650
+ ...number(32),
3651
+ [ Opcodes.call, importedFuncs.printChar ]
3652
+ );
3653
+ }
3654
+
3655
+ // print newline
3656
+ out.push(
3657
+ ...number(10),
3658
+ [ Opcodes.call, importedFuncs.printChar ]
3659
+ );
3660
+
3661
+ return out;
3662
+ },
3663
+ type: TYPES.undefined,
3664
+ notConstr: true,
3665
+ length: 0
3666
+ }
3667
+ };
2827
3668
 
2828
3669
  export default program => {
2829
3670
  globals = {};
@@ -2833,20 +3674,23 @@ export default program => {
2833
3674
  funcs = [];
2834
3675
  funcIndex = {};
2835
3676
  depth = [];
2836
- arrays = new Map();
2837
3677
  pages = new Map();
2838
3678
  data = [];
2839
3679
  currentFuncIndex = importedFuncs.length;
2840
3680
 
2841
3681
  globalThis.valtype = 'f64';
2842
3682
 
2843
- const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
3683
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
2844
3684
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
2845
3685
 
2846
3686
  globalThis.valtypeBinary = Valtype[valtype];
2847
3687
 
2848
3688
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
2849
3689
 
3690
+ globalThis.pageSize = PageSize;
3691
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
3692
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
3693
+
2850
3694
  // set generic opcodes for current valtype
2851
3695
  Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
2852
3696
  Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
@@ -2855,10 +3699,10 @@ export default program => {
2855
3699
  Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
2856
3700
  Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
2857
3701
 
2858
- Opcodes.i32_to = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
2859
- Opcodes.i32_to_u = [ [ null ], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
2860
- Opcodes.i32_from = [ [ null ], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
2861
- Opcodes.i32_from_u = [ [ null ], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
3702
+ Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
3703
+ Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
3704
+ Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
3705
+ Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
2862
3706
 
2863
3707
  Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
2864
3708
  Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
@@ -2871,10 +3715,6 @@ export default program => {
2871
3715
 
2872
3716
  program.id = { name: 'main' };
2873
3717
 
2874
- globalThis.pageSize = PageSize;
2875
- const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
2876
- if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
2877
-
2878
3718
  const scope = {
2879
3719
  locals: {},
2880
3720
  localInd: 0
@@ -2885,7 +3725,7 @@ export default program => {
2885
3725
  body: program.body
2886
3726
  };
2887
3727
 
2888
- if (process.argv.includes('-ast-log')) console.log(program.body.body);
3728
+ if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
2889
3729
 
2890
3730
  generateFunc(scope, program);
2891
3731
 
@@ -2902,7 +3742,11 @@ export default program => {
2902
3742
  }
2903
3743
 
2904
3744
  if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
2905
- main.returns = [];
3745
+ if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
3746
+ main.wasm.splice(main.wasm.length - 1, 1);
3747
+ } else {
3748
+ main.returns = [];
3749
+ }
2906
3750
  }
2907
3751
 
2908
3752
  if (lastInst[0] === Opcodes.call) {