porffor 0.14.0-7bef6473d → 0.14.0-83cb6bb87

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.
@@ -40,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
40
40
  }
41
41
  };
42
42
 
43
- const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
44
- const hasFuncWithName = name => {
45
- const func = funcs.find(x => x.name === name);
46
- return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
47
- };
43
+ const isFuncType = type =>
44
+ type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
45
+ const hasFuncWithName = name =>
46
+ funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
47
+
48
48
  const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
49
49
  switch (decl.type) {
50
50
  case 'BinaryExpression':
@@ -58,10 +58,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
58
58
 
59
59
  case 'ArrowFunctionExpression':
60
60
  case 'FunctionDeclaration':
61
+ case 'FunctionExpression':
61
62
  const func = generateFunc(scope, decl);
62
63
 
63
64
  if (decl.type.endsWith('Expression')) {
64
- return number(func.index);
65
+ return number(func.index - importedFuncs.length);
65
66
  }
66
67
 
67
68
  return [];
@@ -139,18 +140,23 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
139
140
  case 'ArrayExpression':
140
141
  return generateArray(scope, decl, global, name);
141
142
 
143
+ case 'ObjectExpression':
144
+ return generateObject(scope, decl, global, name);
145
+
142
146
  case 'MemberExpression':
143
147
  return generateMember(scope, decl, global, name);
144
148
 
145
149
  case 'ExportNamedDeclaration':
146
- // hack to flag new func for export
147
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
148
151
  generate(scope, decl.declaration);
149
152
 
150
- if (funcsBefore !== funcs.length) {
151
- // new func added
152
- const newFunc = funcs[funcs.length - 1];
153
- newFunc.export = true;
153
+ // set new funcs as exported
154
+ if (funcsBefore.length !== funcs.length) {
155
+ const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
156
+
157
+ for (const x of newFuncs) {
158
+ x.export = true;
159
+ }
154
160
  }
155
161
 
156
162
  return [];
@@ -187,7 +193,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
187
193
  if (!Array.isArray(inst)) inst = [ inst ];
188
194
  const immediates = asm.slice(1).map(x => {
189
195
  const int = parseInt(x);
190
- if (Number.isNaN(int)) return scope.locals[x]?.idx;
196
+ if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
191
197
  return int;
192
198
  });
193
199
 
@@ -199,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
199
205
 
200
206
  __Porffor_bs: str => [
201
207
  ...makeString(scope, str, global, name, true),
202
-
203
- ...(name ? setType(scope, name, TYPES.bytestring) : [
204
- ...number(TYPES.bytestring, Valtype.i32),
205
- ...setLastType(scope)
206
- ])
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
207
209
  ],
208
210
  __Porffor_s: str => [
209
211
  ...makeString(scope, str, global, name, false),
210
-
211
- ...(name ? setType(scope, name, TYPES.string) : [
212
- ...number(TYPES.string, Valtype.i32),
213
- ...setLastType(scope)
214
- ])
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
215
213
  ],
216
214
  };
217
215
 
@@ -313,10 +311,10 @@ const generateIdent = (scope, decl) => {
313
311
 
314
312
  if (local?.idx === undefined) {
315
313
  // no local var with name
316
- if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
317
- if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
318
-
319
314
  if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
315
+
316
+ if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
317
+ if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
320
318
  }
321
319
 
322
320
  if (local?.idx === undefined && rawName.startsWith('__')) {
@@ -350,9 +348,7 @@ const generateReturn = (scope, decl) => {
350
348
 
351
349
  return [
352
350
  ...generate(scope, decl.argument),
353
- ...(scope.returnType != null ? [] : [
354
- ...getNodeType(scope, decl.argument)
355
- ]),
351
+ ...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
356
352
  [ Opcodes.return ]
357
353
  ];
358
354
  };
@@ -367,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
367
363
  };
368
364
 
369
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
370
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
371
367
 
372
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
373
369
  const checks = {
@@ -384,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
384
380
 
385
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
386
382
  // (like if we are in an if condition - very common)
387
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
388
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
389
385
 
390
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
391
387
 
392
388
  if (canInt) {
393
389
  // remove int -> float conversions from left and right
@@ -401,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
401
397
  [ Opcodes.if, Valtype.i32 ],
402
398
  ...right,
403
399
  // note type
404
- ...rightType,
405
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
406
401
  [ Opcodes.else ],
407
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
408
403
  // note type
409
- ...leftType,
410
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
411
405
  [ Opcodes.end ],
412
406
  Opcodes.i32_from
413
407
  ];
@@ -420,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
420
414
  [ Opcodes.if, valtypeBinary ],
421
415
  ...right,
422
416
  // note type
423
- ...rightType,
424
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
425
418
  [ Opcodes.else ],
426
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
427
420
  // note type
428
- ...leftType,
429
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
430
422
  [ Opcodes.end ]
431
423
  ];
432
424
  };
@@ -454,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
454
446
  ...number(0, Valtype.i32), // base 0 for store later
455
447
 
456
448
  ...number(pointer, Valtype.i32),
457
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
458
450
  [ Opcodes.local_tee, leftLength ],
459
451
 
460
452
  [ Opcodes.local_get, rightPointer ],
461
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
462
454
  [ Opcodes.local_tee, rightLength ],
463
455
 
464
456
  [ Opcodes.i32_add ],
@@ -514,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
514
506
  ...number(0, Valtype.i32), // base 0 for store later
515
507
 
516
508
  [ Opcodes.local_get, leftPointer ],
517
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
518
510
  [ Opcodes.local_tee, leftLength ],
519
511
 
520
512
  [ Opcodes.local_get, rightPointer ],
521
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
522
514
  [ Opcodes.local_tee, rightLength ],
523
515
 
524
516
  [ Opcodes.i32_add ],
@@ -596,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
596
588
 
597
589
  // get lengths
598
590
  [ Opcodes.local_get, leftPointer ],
599
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
600
592
  [ Opcodes.local_tee, leftLength ],
601
593
 
602
594
  [ Opcodes.local_get, rightPointer ],
603
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
604
596
 
605
597
  // fast path: check leftLength != rightLength
606
598
  [ Opcodes.i32_ne ],
@@ -656,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
656
648
  [ Opcodes.i32_add ],
657
649
  [ Opcodes.local_tee, index ],
658
650
 
659
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
660
652
  [ Opcodes.local_get, indexEnd ],
661
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
662
654
  [ Opcodes.br_if, 0 ],
663
655
  [ Opcodes.end ],
664
656
 
@@ -676,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
676
668
  ];
677
669
  };
678
670
 
679
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
680
- if (isFloatToIntOp(wasm[wasm.length - 1])) return [
671
+ const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
672
+ if (isIntToFloatOp(wasm[wasm.length - 1])) return [
681
673
  ...wasm,
682
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
683
675
  ];
684
676
  // if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
685
677
 
678
+ // todo/perf: use knownType and custom bytecode here instead of typeSwitch
679
+
686
680
  const useTmp = knownType(scope, type) == null;
687
681
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
688
682
 
689
- const def = [
690
- // if value != 0
691
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
683
+ const def = (truthyMode => {
684
+ if (truthyMode === 'full') return [
685
+ // if value != 0 or NaN
686
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
687
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
692
688
 
693
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
694
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
689
+ [ Opcodes.i32_eqz ],
690
+ [ Opcodes.i32_eqz ],
695
691
 
696
- /* Opcodes.eqz,
697
- [ Opcodes.i32_eqz ],
698
- Opcodes.i32_from */
699
- ];
692
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
693
+ ];
694
+
695
+ if (truthyMode === 'no_negative') return [
696
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
697
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
698
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
699
+ ...(intOut ? [] : [ Opcodes.i32_from ])
700
+ ];
701
+
702
+ if (truthyMode === 'no_nan_negative') return [
703
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
704
+ // plus non-binary output
705
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
706
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
707
+ ];
708
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
700
709
 
701
710
  return [
702
711
  ...wasm,
703
712
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
704
713
 
705
714
  ...typeSwitch(scope, type, {
706
- // [TYPES.number]: def,
707
- [TYPES.array]: [
708
- // arrays are always truthy
709
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
710
- ],
711
715
  [TYPES.string]: [
712
716
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
713
717
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -743,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
743
747
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
744
748
 
745
749
  ...typeSwitch(scope, type, {
746
- [TYPES.array]: [
747
- // arrays are always truthy
748
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
749
- ],
750
750
  [TYPES.string]: [
751
751
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
752
752
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -990,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
990
990
  // if both are true
991
991
  [ Opcodes.i32_and ],
992
992
  [ Opcodes.if, Blocktype.void ],
993
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
993
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
994
994
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
995
995
  [ Opcodes.br, 1 ],
996
996
  [ Opcodes.end ],
@@ -1039,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1039
1039
  return out;
1040
1040
  };
1041
1041
 
1042
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1043
- return func({ name, params, locals, returns, localInd }, {
1042
+ const asmFuncToAsm = (func, scope) => {
1043
+ return func(scope, {
1044
1044
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1045
- builtin: name => {
1046
- let idx = funcIndex[name] ?? importedFuncs[name];
1047
- if (idx === undefined && builtinFuncs[name]) {
1048
- includeBuiltin(null, name);
1049
- idx = funcIndex[name];
1045
+ builtin: n => {
1046
+ let idx = funcIndex[n] ?? importedFuncs[n];
1047
+ if (idx == null && builtinFuncs[n]) {
1048
+ includeBuiltin(null, n);
1049
+ idx = funcIndex[n];
1050
1050
  }
1051
1051
 
1052
1052
  return idx;
@@ -1054,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1054
1054
  });
1055
1055
  };
1056
1056
 
1057
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1057
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1058
1058
  const existing = funcs.find(x => x.name === name);
1059
1059
  if (existing) return existing;
1060
1060
 
@@ -1072,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1072
1072
  data.push(copy);
1073
1073
  }
1074
1074
 
1075
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1075
+ const func = {
1076
+ name,
1077
+ params,
1078
+ locals,
1079
+ localInd: allLocals.length,
1080
+ returns,
1081
+ returnType: returnType ?? TYPES.number,
1082
+ internal: true,
1083
+ index: currentFuncIndex++,
1084
+ table
1085
+ };
1086
+
1087
+ funcs.push(func);
1088
+ funcIndex[name] = func.index;
1089
+
1090
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1076
1091
 
1077
1092
  let baseGlobalIdx, i = 0;
1078
1093
  for (const type of globalTypes) {
@@ -1091,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1091
1106
  }
1092
1107
  }
1093
1108
 
1094
- const func = {
1095
- name,
1096
- params,
1097
- locals,
1098
- returns,
1099
- returnType: returnType ?? TYPES.number,
1100
- wasm,
1101
- internal: true,
1102
- index: currentFuncIndex++
1103
- };
1109
+ if (table) for (const inst of wasm) {
1110
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1111
+ inst.splice(2, 99);
1112
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1113
+ }
1114
+ }
1104
1115
 
1105
- funcs.push(func);
1106
- funcIndex[name] = func.index;
1116
+ func.wasm = wasm;
1107
1117
 
1108
1118
  return func;
1109
1119
  };
@@ -1195,9 +1205,10 @@ const getLastType = scope => {
1195
1205
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1196
1206
  };
1197
1207
 
1198
- const setLastType = scope => {
1199
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1200
- };
1208
+ const setLastType = (scope, type = []) => [
1209
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1210
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1211
+ ];
1201
1212
 
1202
1213
  const getNodeType = (scope, node) => {
1203
1214
  const ret = (() => {
@@ -1258,7 +1269,17 @@ const getNodeType = (scope, node) => {
1258
1269
 
1259
1270
  const func = spl[spl.length - 1];
1260
1271
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1261
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1272
+ if (protoFuncs.length === 1) {
1273
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1274
+ }
1275
+
1276
+ if (protoFuncs.length > 0) {
1277
+ if (scope.locals['#last_type']) return getLastType(scope);
1278
+
1279
+ // presume
1280
+ // todo: warn here?
1281
+ return TYPES.number;
1282
+ }
1262
1283
  }
1263
1284
 
1264
1285
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1353,22 +1374,27 @@ const getNodeType = (scope, node) => {
1353
1374
  }
1354
1375
 
1355
1376
  if (node.type === 'MemberExpression') {
1356
- // hack: if something.name, string type
1357
- if (node.property.name === 'name') {
1358
- if (hasFuncWithName(node.object.name)) {
1359
- return TYPES.bytestring;
1360
- } else {
1361
- return TYPES.undefined;
1362
- }
1377
+ const name = node.property.name;
1378
+
1379
+ if (name === 'length') {
1380
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1381
+ if (Prefs.fastLength) return TYPES.number;
1363
1382
  }
1364
1383
 
1365
- // hack: if something.length, number type
1366
- if (node.property.name === 'length') return TYPES.number;
1367
1384
 
1368
- // ts hack
1369
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1371
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1385
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1386
+ if (objectKnownType != null) {
1387
+ if (name === 'length') {
1388
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1389
+ else return TYPES.undefined;
1390
+ }
1391
+
1392
+ if (node.computed) {
1393
+ if (objectKnownType === TYPES.string) return TYPES.string;
1394
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1395
+ if (objectKnownType === TYPES.array) return TYPES.number;
1396
+ }
1397
+ }
1372
1398
 
1373
1399
  if (scope.locals['#last_type']) return getLastType(scope);
1374
1400
 
@@ -1439,17 +1465,16 @@ const countLeftover = wasm => {
1439
1465
  else if (inst[0] === Opcodes.return) count = 0;
1440
1466
  else if (inst[0] === Opcodes.call) {
1441
1467
  let func = funcs.find(x => x.index === inst[1]);
1442
- if (inst[1] === -1) {
1443
- // todo: count for calling self
1444
- } else if (!func && inst[1] < importedFuncs.length) {
1445
- count -= importedFuncs[inst[1]].params;
1446
- count += importedFuncs[inst[1]].returns;
1468
+ if (inst[1] < importedFuncs.length) {
1469
+ func = importedFuncs[inst[1]];
1470
+ count = count - func.params + func.returns;
1447
1471
  } else {
1448
- if (func) {
1449
- count -= func.params.length;
1450
- } else count--;
1451
- if (func) count += func.returns.length;
1472
+ count = count - func.params.length + func.returns.length;
1452
1473
  }
1474
+ } else if (inst[0] === Opcodes.call_indirect) {
1475
+ count--; // funcidx
1476
+ count -= inst[1] * 2; // params * 2 (typed)
1477
+ count += 2; // fixed return (value, type)
1453
1478
  } else count--;
1454
1479
 
1455
1480
  // console.log(count, decompile([ inst ]).slice(0, -1));
@@ -1467,7 +1492,7 @@ const disposeLeftover = wasm => {
1467
1492
  const generateExp = (scope, decl) => {
1468
1493
  const expression = decl.expression;
1469
1494
 
1470
- const out = generate(scope, expression, undefined, undefined, true);
1495
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1471
1496
  disposeLeftover(out);
1472
1497
 
1473
1498
  return out;
@@ -1558,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1558
1583
  out.splice(out.length - 1, 1);
1559
1584
 
1560
1585
  const finalStatement = parsed.body[parsed.body.length - 1];
1561
- out.push(
1562
- ...getNodeType(scope, finalStatement),
1563
- ...setLastType(scope)
1564
- );
1586
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1565
1587
  } else if (countLeftover(out) === 0) {
1566
1588
  out.push(...number(UNDEFINED));
1567
- out.push(
1568
- ...number(TYPES.undefined, Valtype.i32),
1569
- ...setLastType(scope)
1570
- );
1589
+ out.push(...setLastType(scope, TYPES.undefined));
1571
1590
  }
1572
1591
 
1573
1592
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1603,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1603
1622
 
1604
1623
  if (!funcIndex[rhemynName]) {
1605
1624
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1625
+ func.internal = true;
1606
1626
 
1607
1627
  funcIndex[func.name] = func.index;
1608
1628
  funcs.push(func);
@@ -1619,8 +1639,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1619
1639
  [ Opcodes.call, idx ],
1620
1640
  Opcodes.i32_from_u,
1621
1641
 
1622
- ...number(TYPES.boolean, Valtype.i32),
1623
- ...setLastType(scope)
1642
+ ...setLastType(scope, TYPES.boolean)
1624
1643
  ];
1625
1644
  }
1626
1645
 
@@ -1692,9 +1711,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1692
1711
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1693
1712
  protoBC[x] = [
1694
1713
  ...RTArrayUtil.getLength(getPointer),
1695
-
1696
- ...number(TYPES.number, Valtype.i32),
1697
- ...setLastType(scope)
1714
+ ...setLastType(scope, TYPES.number)
1698
1715
  ];
1699
1716
  continue;
1700
1717
  }
@@ -1713,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1713
1730
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1714
1731
  set: value => RTArrayUtil.setLength(getPointer, value),
1715
1732
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1716
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1733
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1717
1734
  return makeArray(scope, {
1718
1735
  rawElements: new Array(length)
1719
1736
  }, _global, _name, true, itemType);
@@ -1727,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1727
1744
  protoBC[x] = [
1728
1745
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1729
1746
  ...protoOut,
1730
-
1731
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1732
- ...setLastType(scope),
1747
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1733
1748
  [ Opcodes.end ]
1734
1749
  ];
1735
1750
  }
@@ -1779,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1779
1794
 
1780
1795
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1781
1796
 
1782
- if (idx === undefined && name === scope.name) {
1783
- // hack: calling self, func generator will fix later
1784
- idx = -1;
1785
- }
1786
-
1787
1797
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1788
1798
  const wasmOps = {
1789
1799
  // pointer, align, offset
@@ -1831,15 +1841,144 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1831
1841
  }
1832
1842
 
1833
1843
  if (idx === undefined) {
1834
- if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1844
+ if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
1845
+ const [ local, global ] = lookupName(scope, name);
1846
+ if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1847
+
1848
+ // todo: only works when function uses typedParams and typedReturns
1849
+
1850
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1851
+ // options: vararg, strict
1852
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1853
+ // ONLY works when arg count of call == arg count of function being called
1854
+ // - vararg: large size usage, cursed.
1855
+ // works when arg count of call != arg count of function being called*
1856
+ // * most of the time, some edgecases
1857
+
1858
+ funcs.table = true;
1859
+ scope.table = true;
1860
+
1861
+ let args = decl.arguments;
1862
+ let out = [];
1863
+
1864
+ let locals = [];
1865
+
1866
+ if (indirectMode === 'vararg') {
1867
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1868
+
1869
+ if (args.length < minArgc) {
1870
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1871
+ }
1872
+ }
1873
+
1874
+ for (let i = 0; i < args.length; i++) {
1875
+ const arg = args[i];
1876
+ out = out.concat(generate(scope, arg));
1877
+
1878
+ if (valtypeBinary !== Valtype.i32 && (
1879
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1880
+ (importedFuncs[name] && name.startsWith('profile'))
1881
+ )) {
1882
+ out.push(Opcodes.i32_to);
1883
+ }
1884
+
1885
+ out = out.concat(getNodeType(scope, arg));
1886
+
1887
+ if (indirectMode === 'vararg') {
1888
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1889
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1890
+
1891
+ locals.push([valLocal, typeLocal]);
1892
+
1893
+ out.push(
1894
+ [ Opcodes.local_set, typeLocal ],
1895
+ [ Opcodes.local_set, valLocal ]
1896
+ );
1897
+ }
1898
+ }
1899
+
1900
+ if (indirectMode === 'strict') {
1901
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1902
+ [TYPES.function]: [
1903
+ ...argWasm,
1904
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1905
+ Opcodes.i32_to_u,
1906
+ [ Opcodes.call_indirect, args.length, 0 ],
1907
+ ...setLastType(scope)
1908
+ ],
1909
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1910
+ });
1911
+ }
1912
+
1913
+ // hi, I will now explain how vararg mode works:
1914
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1915
+ // since we have varargs (variable argument count), we do not know it.
1916
+ // we could just store args in memory and not use wasm func args,
1917
+ // but that is slow (probably) and breaks js exports.
1918
+ // instead, we generate every* possibility of argc and use different indirect_call
1919
+ // ops for each one, with type depending on argc for that branch.
1920
+ // then we load the argc for the wanted function from a memory lut,
1921
+ // and call the branch with the matching argc we require.
1922
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1923
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1924
+ // *for argc 0-3, in future (todo:) the max number should be
1925
+ // dynamically changed to the max argc of any func in the js file.
1926
+
1927
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1928
+
1929
+ const gen = argc => {
1930
+ const out = [];
1931
+ for (let i = 0; i < argc; i++) {
1932
+ out.push(
1933
+ [ Opcodes.local_get, locals[i][0] ],
1934
+ [ Opcodes.local_get, locals[i][1] ]
1935
+ );
1936
+ }
1937
+
1938
+ out.push(
1939
+ [ Opcodes.local_get, funcLocal ],
1940
+ [ Opcodes.call_indirect, argc, 0 ],
1941
+ ...setLastType(scope)
1942
+ )
1943
+
1944
+ return out;
1945
+ };
1946
+
1947
+ const tableBc = {};
1948
+ for (let i = 0; i <= args.length; i++) {
1949
+ tableBc[i] = gen(i);
1950
+ }
1951
+
1952
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1953
+
1954
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1955
+ [TYPES.function]: [
1956
+ ...out,
1957
+
1958
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1959
+ Opcodes.i32_to_u,
1960
+ [ Opcodes.local_set, funcLocal ],
1961
+
1962
+ ...brTable([
1963
+ // get argc of func we are calling
1964
+ [ Opcodes.local_get, funcLocal ],
1965
+ ...number(ValtypeSize.i16, Valtype.i32),
1966
+ [ Opcodes.i32_mul ],
1967
+
1968
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1969
+ ], tableBc, valtypeBinary)
1970
+ ],
1971
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1972
+ });
1973
+ }
1974
+
1835
1975
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1836
1976
  }
1837
1977
 
1838
- const func = funcs.find(x => x.index === idx);
1839
-
1840
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1978
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1979
+ const userFunc = func && !func.internal;
1841
1980
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1842
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1981
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1843
1982
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1844
1983
 
1845
1984
  let args = decl.arguments;
@@ -1860,11 +1999,17 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1860
1999
  const arg = args[i];
1861
2000
  out = out.concat(generate(scope, arg));
1862
2001
 
1863
- if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
1864
- out.push(Opcodes.i32_to);
2002
+ // todo: this should be used instead of the too many args thing above (by removing that)
2003
+ if (i >= paramCount) {
2004
+ // over param count of func, drop arg
2005
+ out.push([ Opcodes.drop ]);
2006
+ continue;
1865
2007
  }
1866
2008
 
1867
- if (importedFuncs[name] && name.startsWith('profile')) {
2009
+ if (valtypeBinary !== Valtype.i32 && (
2010
+ (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
2011
+ (importedFuncs[name] && name.startsWith('profile'))
2012
+ )) {
1868
2013
  out.push(Opcodes.i32_to);
1869
2014
  }
1870
2015
 
@@ -1909,6 +2054,11 @@ const generateNew = (scope, decl, _global, _name) => {
1909
2054
  }, _global, _name);
1910
2055
  }
1911
2056
 
2057
+ if (
2058
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2059
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2060
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2061
+
1912
2062
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1913
2063
 
1914
2064
  return generateCall(scope, decl, _global, _name);
@@ -1934,8 +2084,11 @@ const knownType = (scope, type) => {
1934
2084
  const idx = type[0][1];
1935
2085
 
1936
2086
  // type idx = var idx + 1
1937
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1938
- if (v.metadata?.type != null) return v.metadata.type;
2087
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2088
+ if (name) {
2089
+ const local = scope.locals[name];
2090
+ if (local.metadata?.type != null) return v.metadata.type;
2091
+ }
1939
2092
  }
1940
2093
 
1941
2094
  return null;
@@ -1970,16 +2123,17 @@ const brTable = (input, bc, returns) => {
1970
2123
  }
1971
2124
 
1972
2125
  for (let i = 0; i < count; i++) {
1973
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2126
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2127
+ if (i === 0) out.push([ Opcodes.block, returns ]);
1974
2128
  else out.push([ Opcodes.block, Blocktype.void ]);
1975
2129
  }
1976
2130
 
1977
- const nums = keys.filter(x => +x);
2131
+ const nums = keys.filter(x => +x >= 0);
1978
2132
  const offset = Math.min(...nums);
1979
2133
  const max = Math.max(...nums);
1980
2134
 
1981
2135
  const table = [];
1982
- let br = 1;
2136
+ let br = 0;
1983
2137
 
1984
2138
  for (let i = offset; i <= max; i++) {
1985
2139
  // if branch for this num, go to that block
@@ -2019,10 +2173,9 @@ const brTable = (input, bc, returns) => {
2019
2173
  br--;
2020
2174
  }
2021
2175
 
2022
- return [
2023
- ...out,
2024
- [ Opcodes.end, 'br table end' ]
2025
- ];
2176
+ out.push([ Opcodes.end ]);
2177
+
2178
+ return out;
2026
2179
  };
2027
2180
 
2028
2181
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2066,6 +2219,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2066
2219
  return out;
2067
2220
  };
2068
2221
 
2222
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2223
+ const out = [];
2224
+
2225
+ for (let i = 0; i < types.length; i++) {
2226
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2227
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2228
+ }
2229
+
2230
+ return out;
2231
+ };
2232
+
2069
2233
  const allocVar = (scope, name, global = false, type = true) => {
2070
2234
  const target = global ? globals : scope.locals;
2071
2235
 
@@ -2082,7 +2246,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2082
2246
 
2083
2247
  if (type) {
2084
2248
  let typeIdx = global ? globalInd++ : scope.localInd++;
2085
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2249
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2086
2250
  }
2087
2251
 
2088
2252
  return idx;
@@ -2174,6 +2338,22 @@ const generateVar = (scope, decl) => {
2174
2338
  }
2175
2339
 
2176
2340
  if (x.init) {
2341
+ // if (isFuncType(x.init.type)) {
2342
+ // // let a = function () { ... }
2343
+ // x.init.id = { name };
2344
+
2345
+ // const func = generateFunc(scope, x.init);
2346
+
2347
+ // out.push(
2348
+ // ...number(func.index - importedFuncs.length),
2349
+ // [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
2350
+
2351
+ // ...setType(scope, name, TYPES.function)
2352
+ // );
2353
+
2354
+ // continue;
2355
+ // }
2356
+
2177
2357
  const generated = generate(scope, x.init, global, name);
2178
2358
  if (scope.arrays?.get(name) != null) {
2179
2359
  // hack to set local as pointer before
@@ -2185,6 +2365,7 @@ const generateVar = (scope, decl) => {
2185
2365
  out = out.concat(generated);
2186
2366
  out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
2187
2367
  }
2368
+
2188
2369
  out.push(...setType(scope, name, getNodeType(scope, x.init)));
2189
2370
  }
2190
2371
 
@@ -2198,6 +2379,7 @@ const generateVar = (scope, decl) => {
2198
2379
  // todo: optimize this func for valueUnused
2199
2380
  const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2200
2381
  const { type, name } = decl.left;
2382
+ const [ local, isGlobal ] = lookupName(scope, name);
2201
2383
 
2202
2384
  if (type === 'ObjectPattern') {
2203
2385
  // hack: ignore object parts of `var a = {} = 2`
@@ -2207,8 +2389,18 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2207
2389
  if (isFuncType(decl.right.type)) {
2208
2390
  // hack for a = function () { ... }
2209
2391
  decl.right.id = { name };
2210
- generateFunc(scope, decl.right);
2211
- return [];
2392
+
2393
+ const func = generateFunc(scope, decl.right);
2394
+
2395
+ return [
2396
+ ...number(func.index - importedFuncs.length),
2397
+ ...(local != null ? [
2398
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2399
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
2400
+
2401
+ ...setType(scope, name, TYPES.function)
2402
+ ] : [])
2403
+ ];
2212
2404
  }
2213
2405
 
2214
2406
  const op = decl.operator.slice(0, -1) || '=';
@@ -2267,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2267
2459
  Opcodes.i32_to_u,
2268
2460
 
2269
2461
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2270
- ...number(ValtypeSize[valtype], Valtype.i32),
2462
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2271
2463
  [ Opcodes.i32_mul ],
2272
2464
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2273
2465
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2274
2466
 
2275
2467
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2276
2468
  [ Opcodes.local_get, pointerTmp ],
2277
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2278
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2469
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2470
+ ], generate(scope, decl.right), [
2471
+ [ Opcodes.local_get, pointerTmp ],
2472
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2473
+ ], getNodeType(scope, decl.right), false, name, true)),
2279
2474
  [ Opcodes.local_tee, newValueTmp ],
2280
2475
 
2281
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2476
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2282
2477
  ],
2283
2478
 
2284
2479
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2307,8 +2502,6 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2307
2502
 
2308
2503
  if (!name) return todo(scope, 'destructuring is not supported yet', true);
2309
2504
 
2310
- const [ local, isGlobal ] = lookupName(scope, name);
2311
-
2312
2505
  if (local === undefined) {
2313
2506
  // todo: this should be a sloppy mode only thing
2314
2507
 
@@ -2388,6 +2581,11 @@ const generateUnary = (scope, decl) => {
2388
2581
  ];
2389
2582
 
2390
2583
  case '!':
2584
+ const arg = decl.argument;
2585
+ if (arg.type === "UnaryExpression" && arg.operator === "!") {
2586
+ // !!x -> is x truthy
2587
+ return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
2588
+ }
2391
2589
  // !=
2392
2590
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2393
2591
 
@@ -2457,6 +2655,7 @@ const generateUnary = (scope, decl) => {
2457
2655
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2458
2656
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2459
2657
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2658
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2460
2659
 
2461
2660
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2462
2661
 
@@ -2533,21 +2732,16 @@ const generateConditional = (scope, decl) => {
2533
2732
  out.push([ Opcodes.if, valtypeBinary ]);
2534
2733
  depth.push('if');
2535
2734
 
2536
- out.push(...generate(scope, decl.consequent));
2537
-
2538
- // note type
2539
2735
  out.push(
2540
- ...getNodeType(scope, decl.consequent),
2541
- ...setLastType(scope)
2736
+ ...generate(scope, decl.consequent),
2737
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2542
2738
  );
2543
2739
 
2544
2740
  out.push([ Opcodes.else ]);
2545
- out.push(...generate(scope, decl.alternate));
2546
2741
 
2547
- // note type
2548
2742
  out.push(
2549
- ...getNodeType(scope, decl.alternate),
2550
- ...setLastType(scope)
2743
+ ...generate(scope, decl.alternate),
2744
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2551
2745
  );
2552
2746
 
2553
2747
  out.push([ Opcodes.end ]);
@@ -2695,12 +2889,15 @@ const generateForOf = (scope, decl) => {
2695
2889
  // todo: optimize away counter and use end pointer
2696
2890
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2697
2891
  [TYPES.array]: [
2698
- ...setType(scope, leftName, TYPES.number),
2699
-
2700
2892
  [ Opcodes.loop, Blocktype.void ],
2701
2893
 
2702
2894
  [ Opcodes.local_get, pointer ],
2703
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2895
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2896
+
2897
+ ...setType(scope, leftName, [
2898
+ [ Opcodes.local_get, pointer ],
2899
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2900
+ ]),
2704
2901
 
2705
2902
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2706
2903
 
@@ -2709,9 +2906,9 @@ const generateForOf = (scope, decl) => {
2709
2906
  ...generate(scope, decl.body),
2710
2907
  [ Opcodes.end ],
2711
2908
 
2712
- // increment iter pointer by valtype size
2909
+ // increment iter pointer by valtype size + 1
2713
2910
  [ Opcodes.local_get, pointer ],
2714
- ...number(ValtypeSize[valtype], Valtype.i32),
2911
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2715
2912
  [ Opcodes.i32_add ],
2716
2913
  [ Opcodes.local_set, pointer ],
2717
2914
 
@@ -2827,6 +3024,44 @@ const generateForOf = (scope, decl) => {
2827
3024
  [ Opcodes.end ],
2828
3025
  [ Opcodes.end ]
2829
3026
  ],
3027
+ [TYPES.set]: [
3028
+ [ Opcodes.loop, Blocktype.void ],
3029
+
3030
+ [ Opcodes.local_get, pointer ],
3031
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
3032
+
3033
+ ...setType(scope, leftName, [
3034
+ [ Opcodes.local_get, pointer ],
3035
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
3036
+ ]),
3037
+
3038
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
3039
+
3040
+ [ Opcodes.block, Blocktype.void ],
3041
+ [ Opcodes.block, Blocktype.void ],
3042
+ ...generate(scope, decl.body),
3043
+ [ Opcodes.end ],
3044
+
3045
+ // increment iter pointer by valtype size + 1
3046
+ [ Opcodes.local_get, pointer ],
3047
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3048
+ [ Opcodes.i32_add ],
3049
+ [ Opcodes.local_set, pointer ],
3050
+
3051
+ // increment counter by 1
3052
+ [ Opcodes.local_get, counter ],
3053
+ ...number(1, Valtype.i32),
3054
+ [ Opcodes.i32_add ],
3055
+ [ Opcodes.local_tee, counter ],
3056
+
3057
+ // loop if counter != length
3058
+ [ Opcodes.local_get, length ],
3059
+ [ Opcodes.i32_ne ],
3060
+ [ Opcodes.br_if, 1 ],
3061
+
3062
+ [ Opcodes.end ],
3063
+ [ Opcodes.end ]
3064
+ ],
2830
3065
  default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
2831
3066
  }, Blocktype.void));
2832
3067
 
@@ -2928,14 +3163,18 @@ const generateThrow = (scope, decl) => {
2928
3163
  };
2929
3164
 
2930
3165
  const generateTry = (scope, decl) => {
2931
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3166
+ // todo: handle control-flow pre-exit for finally
3167
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
2932
3168
 
2933
3169
  const out = [];
2934
3170
 
3171
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3172
+
2935
3173
  out.push([ Opcodes.try, Blocktype.void ]);
2936
3174
  depth.push('try');
2937
3175
 
2938
3176
  out.push(...generate(scope, decl.block));
3177
+ out.push(...finalizer);
2939
3178
 
2940
3179
  if (decl.handler) {
2941
3180
  depth.pop();
@@ -2943,6 +3182,7 @@ const generateTry = (scope, decl) => {
2943
3182
 
2944
3183
  out.push([ Opcodes.catch_all ]);
2945
3184
  out.push(...generate(scope, decl.handler.body));
3185
+ out.push(...finalizer);
2946
3186
  }
2947
3187
 
2948
3188
  out.push([ Opcodes.end ]);
@@ -3028,7 +3268,7 @@ const getAllocType = itemType => {
3028
3268
  }
3029
3269
  };
3030
3270
 
3031
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3271
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3032
3272
  const out = [];
3033
3273
 
3034
3274
  scope.arrays ??= new Map();
@@ -3040,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3040
3280
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3041
3281
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3042
3282
 
3043
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3044
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3283
+ let page;
3284
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3285
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3286
+
3287
+ // hack: use 1 for page 0 pointer for fast truthiness
3288
+ const ptr = page === 0 ? 1 : (page * pageSize);
3289
+ scope.arrays.set(name, ptr);
3045
3290
  }
3046
3291
 
3047
3292
  const pointer = scope.arrays.get(name);
@@ -3091,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3091
3336
 
3092
3337
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3093
3338
 
3094
- // store length as 0th array
3339
+ // store length
3095
3340
  out.push(
3096
3341
  ...pointerWasm,
3097
3342
  ...number(length, Valtype.i32),
@@ -3099,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3099
3344
  );
3100
3345
 
3101
3346
  const storeOp = StoreOps[itemType];
3102
-
3347
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3103
3348
  if (!initEmpty) for (let i = 0; i < length; i++) {
3104
3349
  if (elements[i] == null) continue;
3105
3350
 
3351
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3106
3352
  out.push(
3107
3353
  ...pointerWasm,
3108
3354
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3109
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3355
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3356
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3357
+ ...pointerWasm,
3358
+ ...getNodeType(scope, elements[i]),
3359
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3360
+ ])
3110
3361
  );
3111
3362
  }
3112
3363
 
@@ -3116,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3116
3367
  return [ out, pointer ];
3117
3368
  };
3118
3369
 
3370
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3371
+ if (!Array.isArray(element)) element = generate(scope, element);
3372
+ if (typeof index === 'number') index = number(index);
3373
+
3374
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3375
+
3376
+ return [
3377
+ // calculate offset
3378
+ ...index,
3379
+ Opcodes.i32_to_u,
3380
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3381
+ [ Opcodes.i32_mul ],
3382
+ ...(aotPointer ? [] : [
3383
+ ...array,
3384
+ Opcodes.i32_to_u,
3385
+ [ Opcodes.i32_add ],
3386
+ ]),
3387
+ [ Opcodes.local_set, offset ],
3388
+
3389
+ // store value
3390
+ [ Opcodes.local_get, offset ],
3391
+ ...generate(scope, element),
3392
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3393
+
3394
+ // store type
3395
+ [ Opcodes.local_get, offset ],
3396
+ ...getNodeType(scope, element),
3397
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3398
+ ];
3399
+ };
3400
+
3401
+ const loadArray = (scope, array, index, aotPointer = null) => {
3402
+ if (typeof index === 'number') index = number(index);
3403
+
3404
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3405
+
3406
+ return [
3407
+ // calculate offset
3408
+ ...index,
3409
+ Opcodes.i32_to_u,
3410
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3411
+ [ Opcodes.i32_mul ],
3412
+ ...(aotPointer ? [] : [
3413
+ ...array,
3414
+ Opcodes.i32_to_u,
3415
+ [ Opcodes.i32_add ],
3416
+ ]),
3417
+ [ Opcodes.local_set, offset ],
3418
+
3419
+ // load value
3420
+ [ Opcodes.local_get, offset ],
3421
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3422
+
3423
+ // load type
3424
+ [ Opcodes.local_get, offset ],
3425
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3426
+ ];
3427
+ };
3428
+
3119
3429
  const byteStringable = str => {
3120
3430
  if (!Prefs.bytestring) return false;
3121
3431
 
@@ -3144,14 +3454,39 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3144
3454
  };
3145
3455
 
3146
3456
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3147
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3457
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3148
3458
  };
3149
3459
 
3150
- export const generateMember = (scope, decl, _global, _name) => {
3460
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3461
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3462
+
3463
+ return [
3464
+ ...number(1),
3465
+ ...setLastType(scope, TYPES.object)
3466
+ ];
3467
+ };
3468
+
3469
+ const withType = (scope, wasm, type) => [
3470
+ ...wasm,
3471
+ ...setLastType(scope, type)
3472
+ ];
3473
+
3474
+ const generateMember = (scope, decl, _global, _name) => {
3151
3475
  const name = decl.object.name;
3152
- const pointer = scope.arrays?.get(name);
3153
3476
 
3154
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3477
+ // hack: process.argv[n]
3478
+ if (name === '__process_argv') {
3479
+ const setPointer = scope.arrays?.get(_name);
3480
+
3481
+ return [
3482
+ ...number(decl.property.value - 1),
3483
+ ...(setPointer ? number(setPointer) : makeArray(scope, { elements: [] }, undefined, undefined, true, 'i8')[0]),
3484
+ [ Opcodes.call, importedFuncs.__Porffor_readArgv ]
3485
+ ];
3486
+ }
3487
+
3488
+ const pointer = scope.arrays?.get(name);
3489
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3155
3490
 
3156
3491
  // hack: .name
3157
3492
  if (decl.property.name === 'name') {
@@ -3161,9 +3496,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3161
3496
  // eg: __String_prototype_toLowerCase -> toLowerCase
3162
3497
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3163
3498
 
3164
- return makeString(scope, nameProp, _global, _name, true);
3499
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3165
3500
  } else {
3166
- return generate(scope, DEFAULT_VALUE);
3501
+ return withType(scope, number(0), TYPES.undefined);
3167
3502
  }
3168
3503
  }
3169
3504
 
@@ -3171,9 +3506,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3171
3506
  if (decl.property.name === 'length') {
3172
3507
  const func = funcs.find(x => x.name === name);
3173
3508
  if (func) {
3174
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3175
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3176
- return number(typedParams ? func.params.length / 2 : func.params.length);
3509
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3510
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3177
3511
  }
3178
3512
 
3179
3513
  if (builtinFuncs[name + '$constructor']) {
@@ -3183,24 +3517,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3183
3517
  const constructorFunc = builtinFuncs[name + '$constructor'];
3184
3518
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3185
3519
 
3186
- return number(Math.max(regularParams, constructorParams));
3520
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3521
+ }
3522
+
3523
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3524
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3525
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3526
+
3527
+ if (Prefs.fastLength) {
3528
+ // presume valid length object
3529
+ return [
3530
+ ...(aotPointer ? number(0, Valtype.i32) : [
3531
+ ...generate(scope, decl.object),
3532
+ Opcodes.i32_to_u
3533
+ ]),
3534
+
3535
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3536
+ Opcodes.i32_from_u
3537
+ ];
3187
3538
  }
3188
3539
 
3189
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3190
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3191
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3540
+ const type = getNodeType(scope, decl.object);
3541
+ const known = knownType(scope, type);
3542
+ if (known != null) {
3543
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3544
+ ...(aotPointer ? number(0, Valtype.i32) : [
3545
+ ...generate(scope, decl.object),
3546
+ Opcodes.i32_to_u
3547
+ ]),
3548
+
3549
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3550
+ Opcodes.i32_from_u
3551
+ ];
3552
+
3553
+ return number(0);
3554
+ }
3192
3555
 
3193
3556
  return [
3194
- ...(aotPointer ? number(0, Valtype.i32) : [
3195
- ...generate(scope, decl.object),
3196
- Opcodes.i32_to_u
3197
- ]),
3557
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3558
+ [ Opcodes.if, valtypeBinary ],
3559
+ ...(aotPointer ? number(0, Valtype.i32) : [
3560
+ ...generate(scope, decl.object),
3561
+ Opcodes.i32_to_u
3562
+ ]),
3198
3563
 
3199
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3200
- Opcodes.i32_from_u
3564
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3565
+ Opcodes.i32_from_u,
3566
+
3567
+ ...setLastType(scope, TYPES.number),
3568
+ [ Opcodes.else ],
3569
+ ...number(0),
3570
+ ...setLastType(scope, TYPES.undefined),
3571
+ [ Opcodes.end ]
3201
3572
  ];
3202
3573
  }
3203
3574
 
3575
+ // todo: generate this array procedurally during builtinFuncs creation
3576
+ if (['size', 'description'].includes(decl.property.name)) {
3577
+ const bc = {};
3578
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3579
+
3580
+ if (cands.length > 0) {
3581
+ for (const x of cands) {
3582
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3583
+ if (type == null) continue;
3584
+
3585
+ bc[type] = generateCall(scope, {
3586
+ callee: {
3587
+ type: 'Identifier',
3588
+ name: x
3589
+ },
3590
+ arguments: [ decl.object ],
3591
+ _protoInternalCall: true
3592
+ });
3593
+ }
3594
+ }
3595
+
3596
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3597
+ ...bc,
3598
+ default: withType(scope, number(0), TYPES.undefined)
3599
+ }, valtypeBinary);
3600
+ }
3601
+
3204
3602
  const object = generate(scope, decl.object);
3205
3603
  const property = generate(scope, decl.property);
3206
3604
 
@@ -3215,24 +3613,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3215
3613
 
3216
3614
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3217
3615
  [TYPES.array]: [
3218
- // get index as valtype
3219
- ...property,
3220
-
3221
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3222
- Opcodes.i32_to_u,
3223
- ...number(ValtypeSize[valtype], Valtype.i32),
3224
- [ Opcodes.i32_mul ],
3225
-
3226
- ...(aotPointer ? [] : [
3227
- ...object,
3228
- Opcodes.i32_to_u,
3229
- [ Opcodes.i32_add ]
3230
- ]),
3231
-
3232
- // read from memory
3233
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3234
-
3235
- ...number(TYPES.number, Valtype.i32),
3616
+ ...loadArray(scope, object, property, aotPointer),
3236
3617
  ...setLastType(scope)
3237
3618
  ],
3238
3619
 
@@ -3263,9 +3644,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3263
3644
 
3264
3645
  // return new string (page)
3265
3646
  ...number(newPointer),
3266
-
3267
- ...number(TYPES.string, Valtype.i32),
3268
- ...setLastType(scope)
3647
+ ...setLastType(scope, TYPES.string)
3269
3648
  ],
3270
3649
  [TYPES.bytestring]: [
3271
3650
  // setup new/out array
@@ -3291,9 +3670,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3291
3670
 
3292
3671
  // return new string (page)
3293
3672
  ...number(newPointer),
3294
-
3295
- ...number(TYPES.bytestring, Valtype.i32),
3296
- ...setLastType(scope)
3673
+ ...setLastType(scope, TYPES.bytestring)
3297
3674
  ],
3298
3675
 
3299
3676
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3318,7 +3695,7 @@ const objectHack = node => {
3318
3695
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3319
3696
 
3320
3697
  // if .name or .length, give up (hack within a hack!)
3321
- if (['name', 'length'].includes(node.property.name)) {
3698
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3322
3699
  node.object = objectHack(node.object);
3323
3700
  return;
3324
3701
  }
@@ -3355,33 +3732,39 @@ const generateFunc = (scope, decl) => {
3355
3732
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3356
3733
  const params = decl.params ?? [];
3357
3734
 
3358
- // const innerScope = { ...scope };
3359
3735
  // TODO: share scope/locals between !!!
3360
- const innerScope = {
3736
+ const func = {
3361
3737
  locals: {},
3362
3738
  localInd: 0,
3363
3739
  // value, type
3364
3740
  returns: [ valtypeBinary, Valtype.i32 ],
3365
3741
  throws: false,
3366
- name
3742
+ name,
3743
+ index: currentFuncIndex++
3367
3744
  };
3368
3745
 
3369
3746
  if (typedInput && decl.returnType) {
3370
3747
  const { type } = extractTypeAnnotation(decl.returnType);
3748
+ // if (type != null && !Prefs.indirectCalls) {
3371
3749
  if (type != null) {
3372
- innerScope.returnType = type;
3373
- innerScope.returns = [ valtypeBinary ];
3750
+ func.returnType = type;
3751
+ func.returns = [ valtypeBinary ];
3374
3752
  }
3375
3753
  }
3376
3754
 
3377
3755
  for (let i = 0; i < params.length; i++) {
3378
- allocVar(innerScope, params[i].name, false);
3756
+ const name = params[i].name;
3757
+ // if (name == null) return todo('non-identifier args are not supported');
3758
+
3759
+ allocVar(func, name, false);
3379
3760
 
3380
3761
  if (typedInput && params[i].typeAnnotation) {
3381
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3762
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3382
3763
  }
3383
3764
  }
3384
3765
 
3766
+ func.params = Object.values(func.locals).map(x => x.type);
3767
+
3385
3768
  let body = objectHack(decl.body);
3386
3769
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3387
3770
  // hack: () => 0 -> () => return 0
@@ -3391,37 +3774,23 @@ const generateFunc = (scope, decl) => {
3391
3774
  };
3392
3775
  }
3393
3776
 
3394
- const wasm = generate(innerScope, body);
3395
- const func = {
3396
- name,
3397
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3398
- index: currentFuncIndex++,
3399
- ...innerScope
3400
- };
3401
3777
  funcIndex[name] = func.index;
3778
+ funcs.push(func);
3402
3779
 
3403
- if (name === 'main') func.gotLastType = true;
3780
+ const wasm = generate(func, body);
3781
+ func.wasm = wasm;
3404
3782
 
3405
- // quick hack fixes
3406
- for (const inst of wasm) {
3407
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3408
- inst[1] = func.index;
3409
- }
3410
- }
3783
+ if (name === 'main') func.gotLastType = true;
3411
3784
 
3412
3785
  // add end return if not found
3413
3786
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3414
3787
  wasm.push(
3415
3788
  ...number(0),
3416
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3789
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3417
3790
  [ Opcodes.return ]
3418
3791
  );
3419
3792
  }
3420
3793
 
3421
- func.wasm = wasm;
3422
-
3423
- funcs.push(func);
3424
-
3425
3794
  return func;
3426
3795
  };
3427
3796
 
@@ -3525,7 +3894,7 @@ const internalConstrs = {
3525
3894
  generate: (scope, decl) => {
3526
3895
  // todo: boolean object when used as constructor
3527
3896
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3528
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3897
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3529
3898
  },
3530
3899
  type: TYPES.boolean,
3531
3900
  length: 1
@@ -3586,8 +3955,10 @@ const internalConstrs = {
3586
3955
  }),
3587
3956
 
3588
3957
  // print space
3589
- ...number(32),
3590
- [ Opcodes.call, importedFuncs.printChar ]
3958
+ ...(i !== decl.arguments.length - 1 ? [
3959
+ ...number(32),
3960
+ [ Opcodes.call, importedFuncs.printChar ]
3961
+ ] : [])
3591
3962
  );
3592
3963
  }
3593
3964
 
@@ -3597,6 +3968,8 @@ const internalConstrs = {
3597
3968
  [ Opcodes.call, importedFuncs.printChar ]
3598
3969
  );
3599
3970
 
3971
+ out.push(...number(UNDEFINED));
3972
+
3600
3973
  return out;
3601
3974
  },
3602
3975
  type: TYPES.undefined,
@@ -3666,9 +4039,8 @@ export default program => {
3666
4039
 
3667
4040
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3668
4041
 
3669
- generateFunc(scope, program);
4042
+ const main = generateFunc(scope, program);
3670
4043
 
3671
- const main = funcs[funcs.length - 1];
3672
4044
  main.export = true;
3673
4045
  main.returns = [ valtypeBinary, Valtype.i32 ];
3674
4046
 
@@ -3695,7 +4067,7 @@ export default program => {
3695
4067
  }
3696
4068
 
3697
4069
  // if blank main func and other exports, remove it
3698
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4070
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3699
4071
 
3700
4072
  return { funcs, globals, tags, exceptions, pages, data };
3701
4073
  };