porffor 0.14.0-a24ac8cf2 → 0.14.0-b1e1c2265

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':
@@ -140,18 +140,23 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
140
140
  case 'ArrayExpression':
141
141
  return generateArray(scope, decl, global, name);
142
142
 
143
+ case 'ObjectExpression':
144
+ return generateObject(scope, decl, global, name);
145
+
143
146
  case 'MemberExpression':
144
147
  return generateMember(scope, decl, global, name);
145
148
 
146
149
  case 'ExportNamedDeclaration':
147
- // hack to flag new func for export
148
- const funcsBefore = funcs.length;
150
+ const funcsBefore = funcs.map(x => x.name);
149
151
  generate(scope, decl.declaration);
150
152
 
151
- if (funcsBefore !== funcs.length) {
152
- // new func added
153
- const newFunc = funcs[funcs.length - 1];
154
- 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
+ }
155
160
  }
156
161
 
157
162
  return [];
@@ -200,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
200
205
 
201
206
  __Porffor_bs: str => [
202
207
  ...makeString(scope, str, global, name, true),
203
-
204
- ...(name ? setType(scope, name, TYPES.bytestring) : [
205
- ...number(TYPES.bytestring, Valtype.i32),
206
- ...setLastType(scope)
207
- ])
208
+ ...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
208
209
  ],
209
210
  __Porffor_s: str => [
210
211
  ...makeString(scope, str, global, name, false),
211
-
212
- ...(name ? setType(scope, name, TYPES.string) : [
213
- ...number(TYPES.string, Valtype.i32),
214
- ...setLastType(scope)
215
- ])
212
+ ...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
216
213
  ],
217
214
  };
218
215
 
@@ -366,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
366
363
  };
367
364
 
368
365
  const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
369
- const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
366
+ const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
370
367
 
371
368
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
372
369
  const checks = {
@@ -383,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
383
380
 
384
381
  // if we can, use int tmp and convert at the end to help prevent unneeded conversions
385
382
  // (like if we are in an if condition - very common)
386
- const leftIsInt = isFloatToIntOp(left[left.length - 1]);
387
- const rightIsInt = isFloatToIntOp(right[right.length - 1]);
383
+ const leftWasInt = isIntToFloatOp(left[left.length - 1]);
384
+ const rightWasInt = isIntToFloatOp(right[right.length - 1]);
388
385
 
389
- const canInt = leftIsInt && rightIsInt;
386
+ const canInt = leftWasInt && rightWasInt;
390
387
 
391
388
  if (canInt) {
392
389
  // remove int -> float conversions from left and right
@@ -400,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
400
397
  [ Opcodes.if, Valtype.i32 ],
401
398
  ...right,
402
399
  // note type
403
- ...rightType,
404
- ...setLastType(scope),
400
+ ...setLastType(scope, rightType),
405
401
  [ Opcodes.else ],
406
402
  [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
407
403
  // note type
408
- ...leftType,
409
- ...setLastType(scope),
404
+ ...setLastType(scope, leftType),
410
405
  [ Opcodes.end ],
411
406
  Opcodes.i32_from
412
407
  ];
@@ -419,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
419
414
  [ Opcodes.if, valtypeBinary ],
420
415
  ...right,
421
416
  // note type
422
- ...rightType,
423
- ...setLastType(scope),
417
+ ...setLastType(scope, rightType),
424
418
  [ Opcodes.else ],
425
419
  [ Opcodes.local_get, localTmp(scope, 'logictmp') ],
426
420
  // note type
427
- ...leftType,
428
- ...setLastType(scope),
421
+ ...setLastType(scope, leftType),
429
422
  [ Opcodes.end ]
430
423
  ];
431
424
  };
@@ -453,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
453
446
  ...number(0, Valtype.i32), // base 0 for store later
454
447
 
455
448
  ...number(pointer, Valtype.i32),
456
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
449
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
457
450
  [ Opcodes.local_tee, leftLength ],
458
451
 
459
452
  [ Opcodes.local_get, rightPointer ],
460
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
453
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
461
454
  [ Opcodes.local_tee, rightLength ],
462
455
 
463
456
  [ Opcodes.i32_add ],
@@ -513,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
513
506
  ...number(0, Valtype.i32), // base 0 for store later
514
507
 
515
508
  [ Opcodes.local_get, leftPointer ],
516
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
509
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
517
510
  [ Opcodes.local_tee, leftLength ],
518
511
 
519
512
  [ Opcodes.local_get, rightPointer ],
520
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
513
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
521
514
  [ Opcodes.local_tee, rightLength ],
522
515
 
523
516
  [ Opcodes.i32_add ],
@@ -595,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
595
588
 
596
589
  // get lengths
597
590
  [ Opcodes.local_get, leftPointer ],
598
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
591
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
599
592
  [ Opcodes.local_tee, leftLength ],
600
593
 
601
594
  [ Opcodes.local_get, rightPointer ],
602
- [ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
595
+ [ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
603
596
 
604
597
  // fast path: check leftLength != rightLength
605
598
  [ Opcodes.i32_ne ],
@@ -655,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
655
648
  [ Opcodes.i32_add ],
656
649
  [ Opcodes.local_tee, index ],
657
650
 
658
- // if index != index end (length * sizeof valtype), loop
651
+ // if index < index end (length * sizeof valtype), loop
659
652
  [ Opcodes.local_get, indexEnd ],
660
- [ Opcodes.i32_ne ],
653
+ [ Opcodes.i32_lt_s ],
661
654
  [ Opcodes.br_if, 0 ],
662
655
  [ Opcodes.end ],
663
656
 
@@ -675,8 +668,8 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
675
668
  ];
676
669
  };
677
670
 
678
- const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
679
- 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 [
680
673
  ...wasm,
681
674
  ...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
682
675
  ];
@@ -685,28 +678,38 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
685
678
  const useTmp = knownType(scope, type) == null;
686
679
  const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
687
680
 
688
- const def = [
689
- // if value != 0
690
- ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
681
+ const def = (truthyMode => {
682
+ if (truthyMode === 'full') return [
683
+ // if value != 0 or NaN
684
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
685
+ ...(intIn ? [ ] : [ Opcodes.i32_to ]),
691
686
 
692
- // ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
693
- ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
687
+ [ Opcodes.i32_eqz ],
688
+ [ Opcodes.i32_eqz ],
694
689
 
695
- /* Opcodes.eqz,
696
- [ Opcodes.i32_eqz ],
697
- Opcodes.i32_from */
698
- ];
690
+ ...(intOut ? [] : [ Opcodes.i32_from ]),
691
+ ];
692
+
693
+ if (truthyMode === 'no_negative') return [
694
+ // if value != 0 or NaN, non-binary output. negative numbers not truthy :/
695
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
696
+ ...(intIn ? [] : [ Opcodes.i32_to ]),
697
+ ...(intOut ? [] : [ Opcodes.i32_from ])
698
+ ];
699
+
700
+ if (truthyMode === 'no_nan_negative') return [
701
+ // simpler and faster but makes NaN truthy and negative numbers not truthy,
702
+ // plus non-binary output
703
+ ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
704
+ ...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
705
+ ];
706
+ })(forceTruthyMode ?? Prefs.truthy ?? 'full');
699
707
 
700
708
  return [
701
709
  ...wasm,
702
710
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
703
711
 
704
712
  ...typeSwitch(scope, type, {
705
- // [TYPES.number]: def,
706
- [TYPES.array]: [
707
- // arrays are always truthy
708
- ...number(1, intOut ? Valtype.i32 : valtypeBinary)
709
- ],
710
713
  [TYPES.string]: [
711
714
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
712
715
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -742,10 +745,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
742
745
  ...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
743
746
 
744
747
  ...typeSwitch(scope, type, {
745
- [TYPES.array]: [
746
- // arrays are always truthy
747
- ...number(0, intOut ? Valtype.i32 : valtypeBinary)
748
- ],
749
748
  [TYPES.string]: [
750
749
  ...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
751
750
  ...(intIn ? [] : [ Opcodes.i32_to_u ]),
@@ -989,7 +988,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
989
988
  // if both are true
990
989
  [ Opcodes.i32_and ],
991
990
  [ Opcodes.if, Blocktype.void ],
992
- ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
991
+ ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
993
992
  ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
994
993
  [ Opcodes.br, 1 ],
995
994
  [ Opcodes.end ],
@@ -1038,14 +1037,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
1038
1037
  return out;
1039
1038
  };
1040
1039
 
1041
- const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
1042
- return func({ name, params, locals, returns, localInd }, {
1040
+ const asmFuncToAsm = (func, scope) => {
1041
+ return func(scope, {
1043
1042
  TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
1044
- builtin: name => {
1045
- let idx = funcIndex[name] ?? importedFuncs[name];
1046
- if (idx === undefined && builtinFuncs[name]) {
1047
- includeBuiltin(null, name);
1048
- idx = funcIndex[name];
1043
+ builtin: n => {
1044
+ let idx = funcIndex[n] ?? importedFuncs[n];
1045
+ if (idx == null && builtinFuncs[n]) {
1046
+ includeBuiltin(null, n);
1047
+ idx = funcIndex[n];
1049
1048
  }
1050
1049
 
1051
1050
  return idx;
@@ -1053,7 +1052,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
1053
1052
  });
1054
1053
  };
1055
1054
 
1056
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
1055
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
1057
1056
  const existing = funcs.find(x => x.name === name);
1058
1057
  if (existing) return existing;
1059
1058
 
@@ -1071,7 +1070,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1071
1070
  data.push(copy);
1072
1071
  }
1073
1072
 
1074
- if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
1073
+ const func = {
1074
+ name,
1075
+ params,
1076
+ locals,
1077
+ localInd: allLocals.length,
1078
+ returns,
1079
+ returnType: returnType ?? TYPES.number,
1080
+ internal: true,
1081
+ index: currentFuncIndex++,
1082
+ table
1083
+ };
1084
+
1085
+ funcs.push(func);
1086
+ funcIndex[name] = func.index;
1087
+
1088
+ if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
1075
1089
 
1076
1090
  let baseGlobalIdx, i = 0;
1077
1091
  for (const type of globalTypes) {
@@ -1090,19 +1104,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
1090
1104
  }
1091
1105
  }
1092
1106
 
1093
- const func = {
1094
- name,
1095
- params,
1096
- locals,
1097
- returns,
1098
- returnType: returnType ?? TYPES.number,
1099
- wasm,
1100
- internal: true,
1101
- index: currentFuncIndex++
1102
- };
1107
+ if (table) for (const inst of wasm) {
1108
+ if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
1109
+ inst.splice(2, 99);
1110
+ inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
1111
+ }
1112
+ }
1103
1113
 
1104
- funcs.push(func);
1105
- funcIndex[name] = func.index;
1114
+ func.wasm = wasm;
1106
1115
 
1107
1116
  return func;
1108
1117
  };
@@ -1194,9 +1203,10 @@ const getLastType = scope => {
1194
1203
  return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
1195
1204
  };
1196
1205
 
1197
- const setLastType = scope => {
1198
- return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
1199
- };
1206
+ const setLastType = (scope, type = []) => [
1207
+ ...(typeof type === 'number' ? number(type, Valtype.i32) : type),
1208
+ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
1209
+ ];
1200
1210
 
1201
1211
  const getNodeType = (scope, node) => {
1202
1212
  const ret = (() => {
@@ -1257,7 +1267,17 @@ const getNodeType = (scope, node) => {
1257
1267
 
1258
1268
  const func = spl[spl.length - 1];
1259
1269
  const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
1260
- if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
1270
+ if (protoFuncs.length === 1) {
1271
+ if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
1272
+ }
1273
+
1274
+ if (protoFuncs.length > 0) {
1275
+ if (scope.locals['#last_type']) return getLastType(scope);
1276
+
1277
+ // presume
1278
+ // todo: warn here?
1279
+ return TYPES.number;
1280
+ }
1261
1281
  }
1262
1282
 
1263
1283
  if (name.startsWith('__Porffor_wasm_')) {
@@ -1352,22 +1372,27 @@ const getNodeType = (scope, node) => {
1352
1372
  }
1353
1373
 
1354
1374
  if (node.type === 'MemberExpression') {
1355
- // hack: if something.name, string type
1356
- if (node.property.name === 'name') {
1357
- if (hasFuncWithName(node.object.name)) {
1358
- return TYPES.bytestring;
1359
- } else {
1360
- return TYPES.undefined;
1361
- }
1375
+ const name = node.property.name;
1376
+
1377
+ if (name === 'length') {
1378
+ if (hasFuncWithName(node.object.name)) return TYPES.number;
1379
+ if (Prefs.fastLength) return TYPES.number;
1362
1380
  }
1363
1381
 
1364
- // hack: if something.length, number type
1365
- if (node.property.name === 'length') return TYPES.number;
1366
1382
 
1367
- // ts hack
1368
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
1369
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
1370
- if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
1383
+ const objectKnownType = knownType(scope, getNodeType(scope, node.object));
1384
+ if (objectKnownType != null) {
1385
+ if (name === 'length') {
1386
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
1387
+ else return TYPES.undefined;
1388
+ }
1389
+
1390
+ if (node.computed) {
1391
+ if (objectKnownType === TYPES.string) return TYPES.string;
1392
+ if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
1393
+ if (objectKnownType === TYPES.array) return TYPES.number;
1394
+ }
1395
+ }
1371
1396
 
1372
1397
  if (scope.locals['#last_type']) return getLastType(scope);
1373
1398
 
@@ -1438,16 +1463,11 @@ const countLeftover = wasm => {
1438
1463
  else if (inst[0] === Opcodes.return) count = 0;
1439
1464
  else if (inst[0] === Opcodes.call) {
1440
1465
  let func = funcs.find(x => x.index === inst[1]);
1441
- if (inst[1] === -1) {
1442
- // todo: count for calling self
1443
- } else if (!func && inst[1] < importedFuncs.length) {
1444
- count -= importedFuncs[inst[1]].params;
1445
- count += importedFuncs[inst[1]].returns;
1466
+ if (inst[1] < importedFuncs.length) {
1467
+ func = importedFuncs[inst[1]];
1468
+ count = count - func.params + func.returns;
1446
1469
  } else {
1447
- if (func) {
1448
- count -= func.params.length;
1449
- } else count--;
1450
- if (func) count += func.returns.length;
1470
+ count = count - func.params.length + func.returns.length;
1451
1471
  }
1452
1472
  } else if (inst[0] === Opcodes.call_indirect) {
1453
1473
  count--; // funcidx
@@ -1470,7 +1490,7 @@ const disposeLeftover = wasm => {
1470
1490
  const generateExp = (scope, decl) => {
1471
1491
  const expression = decl.expression;
1472
1492
 
1473
- const out = generate(scope, expression, undefined, undefined, true);
1493
+ const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
1474
1494
  disposeLeftover(out);
1475
1495
 
1476
1496
  return out;
@@ -1561,16 +1581,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1561
1581
  out.splice(out.length - 1, 1);
1562
1582
 
1563
1583
  const finalStatement = parsed.body[parsed.body.length - 1];
1564
- out.push(
1565
- ...getNodeType(scope, finalStatement),
1566
- ...setLastType(scope)
1567
- );
1584
+ out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
1568
1585
  } else if (countLeftover(out) === 0) {
1569
1586
  out.push(...number(UNDEFINED));
1570
- out.push(
1571
- ...number(TYPES.undefined, Valtype.i32),
1572
- ...setLastType(scope)
1573
- );
1587
+ out.push(...setLastType(scope, TYPES.undefined));
1574
1588
  }
1575
1589
 
1576
1590
  // if (lastInst && lastInst[0] === Opcodes.drop) {
@@ -1606,6 +1620,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1606
1620
 
1607
1621
  if (!funcIndex[rhemynName]) {
1608
1622
  const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
1623
+ func.internal = true;
1609
1624
 
1610
1625
  funcIndex[func.name] = func.index;
1611
1626
  funcs.push(func);
@@ -1622,8 +1637,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1622
1637
  [ Opcodes.call, idx ],
1623
1638
  Opcodes.i32_from_u,
1624
1639
 
1625
- ...number(TYPES.boolean, Valtype.i32),
1626
- ...setLastType(scope)
1640
+ ...setLastType(scope, TYPES.boolean)
1627
1641
  ];
1628
1642
  }
1629
1643
 
@@ -1695,9 +1709,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1695
1709
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
1696
1710
  protoBC[x] = [
1697
1711
  ...RTArrayUtil.getLength(getPointer),
1698
-
1699
- ...number(TYPES.number, Valtype.i32),
1700
- ...setLastType(scope)
1712
+ ...setLastType(scope, TYPES.number)
1701
1713
  ];
1702
1714
  continue;
1703
1715
  }
@@ -1716,7 +1728,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1716
1728
  getI32: () => RTArrayUtil.getLengthI32(getPointer),
1717
1729
  set: value => RTArrayUtil.setLength(getPointer, value),
1718
1730
  setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
1719
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1731
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1720
1732
  return makeArray(scope, {
1721
1733
  rawElements: new Array(length)
1722
1734
  }, _global, _name, true, itemType);
@@ -1730,9 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1730
1742
  protoBC[x] = [
1731
1743
  [ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
1732
1744
  ...protoOut,
1733
-
1734
- ...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
1735
- ...setLastType(scope),
1745
+ ...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
1736
1746
  [ Opcodes.end ]
1737
1747
  ];
1738
1748
  }
@@ -1782,11 +1792,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1782
1792
 
1783
1793
  if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1784
1794
 
1785
- if (idx === undefined && name === scope.name) {
1786
- // hack: calling self, func generator will fix later
1787
- idx = -1;
1788
- }
1789
-
1790
1795
  if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
1791
1796
  const wasmOps = {
1792
1797
  // pointer, align, offset
@@ -1838,36 +1843,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1838
1843
  const [ local, global ] = lookupName(scope, name);
1839
1844
  if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
1840
1845
 
1841
- // todo: only works when:
1842
- // 1. arg count matches arg count of function
1843
- // 2. function uses typedParams and typedReturns
1846
+ // todo: only works when function uses typedParams and typedReturns
1847
+
1848
+ const indirectMode = Prefs.indirectCallMode ?? 'vararg';
1849
+ // options: vararg, strict
1850
+ // - strict: simpler, smaller size usage, no func argc lut needed.
1851
+ // ONLY works when arg count of call == arg count of function being called
1852
+ // - vararg: large size usage, cursed.
1853
+ // works when arg count of call != arg count of function being called*
1854
+ // * most of the time, some edgecases
1844
1855
 
1845
1856
  funcs.table = true;
1857
+ scope.table = true;
1846
1858
 
1847
1859
  let args = decl.arguments;
1848
- let argWasm = [];
1860
+ let out = [];
1861
+
1862
+ let locals = [];
1863
+
1864
+ if (indirectMode === 'vararg') {
1865
+ const minArgc = Prefs.indirectCallMinArgc ?? 3;
1866
+
1867
+ if (args.length < minArgc) {
1868
+ args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
1869
+ }
1870
+ }
1849
1871
 
1850
1872
  for (let i = 0; i < args.length; i++) {
1851
1873
  const arg = args[i];
1852
- argWasm = argWasm.concat(generate(scope, arg));
1874
+ out = out.concat(generate(scope, arg));
1853
1875
 
1854
1876
  if (valtypeBinary !== Valtype.i32 && (
1855
1877
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1856
1878
  (importedFuncs[name] && name.startsWith('profile'))
1857
1879
  )) {
1858
- argWasm.push(Opcodes.i32_to);
1880
+ out.push(Opcodes.i32_to);
1881
+ }
1882
+
1883
+ out = out.concat(getNodeType(scope, arg));
1884
+
1885
+ if (indirectMode === 'vararg') {
1886
+ const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
1887
+ const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
1888
+
1889
+ locals.push([valLocal, typeLocal]);
1890
+
1891
+ out.push(
1892
+ [ Opcodes.local_set, typeLocal ],
1893
+ [ Opcodes.local_set, valLocal ]
1894
+ );
1895
+ }
1896
+ }
1897
+
1898
+ if (indirectMode === 'strict') {
1899
+ return typeSwitch(scope, getNodeType(scope, decl.callee), {
1900
+ [TYPES.function]: [
1901
+ ...argWasm,
1902
+ [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1903
+ Opcodes.i32_to_u,
1904
+ [ Opcodes.call_indirect, args.length, 0 ],
1905
+ ...setLastType(scope)
1906
+ ],
1907
+ default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1908
+ });
1909
+ }
1910
+
1911
+ // hi, I will now explain how vararg mode works:
1912
+ // wasm's indirect_call instruction requires you know the func type at compile-time
1913
+ // since we have varargs (variable argument count), we do not know it.
1914
+ // we could just store args in memory and not use wasm func args,
1915
+ // but that is slow (probably) and breaks js exports.
1916
+ // instead, we generate every* possibility of argc and use different indirect_call
1917
+ // ops for each one, with type depending on argc for that branch.
1918
+ // then we load the argc for the wanted function from a memory lut,
1919
+ // and call the branch with the matching argc we require.
1920
+ // sorry, yes it is very cursed (and size inefficient), but indirect calls
1921
+ // are kind of rare anyway (mostly callbacks) so I am not concerned atm.
1922
+ // *for argc 0-3, in future (todo:) the max number should be
1923
+ // dynamically changed to the max argc of any func in the js file.
1924
+
1925
+ const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
1926
+
1927
+ const gen = argc => {
1928
+ const out = [];
1929
+ for (let i = 0; i < argc; i++) {
1930
+ out.push(
1931
+ [ Opcodes.local_get, locals[i][0] ],
1932
+ [ Opcodes.local_get, locals[i][1] ]
1933
+ );
1859
1934
  }
1860
1935
 
1861
- argWasm = argWasm.concat(getNodeType(scope, arg));
1936
+ out.push(
1937
+ [ Opcodes.local_get, funcLocal ],
1938
+ [ Opcodes.call_indirect, argc, 0 ],
1939
+ ...setLastType(scope)
1940
+ )
1941
+
1942
+ return out;
1943
+ };
1944
+
1945
+ const tableBc = {};
1946
+ for (let i = 0; i <= args.length; i++) {
1947
+ tableBc[i] = gen(i);
1862
1948
  }
1863
1949
 
1950
+ // todo/perf: check if we should use br_table here or just generate our own big if..elses
1951
+
1864
1952
  return typeSwitch(scope, getNodeType(scope, decl.callee), {
1865
1953
  [TYPES.function]: [
1866
- ...argWasm,
1954
+ ...out,
1955
+
1867
1956
  [ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1868
1957
  Opcodes.i32_to_u,
1869
- [ Opcodes.call_indirect, args.length, 0 ],
1870
- ...setLastType(scope)
1958
+ [ Opcodes.local_set, funcLocal ],
1959
+
1960
+ ...brTable([
1961
+ // get argc of func we are calling
1962
+ [ Opcodes.local_get, funcLocal ],
1963
+ ...number(ValtypeSize.i16, Valtype.i32),
1964
+ [ Opcodes.i32_mul ],
1965
+
1966
+ [ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
1967
+ ], tableBc, valtypeBinary)
1871
1968
  ],
1872
1969
  default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
1873
1970
  });
@@ -1876,11 +1973,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1876
1973
  return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
1877
1974
  }
1878
1975
 
1879
- const func = funcs.find(x => x.index === idx);
1880
-
1881
- const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
1976
+ const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
1977
+ const userFunc = func && !func.internal;
1882
1978
  const typedParams = userFunc || builtinFuncs[name]?.typedParams;
1883
- const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
1979
+ const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
1884
1980
  const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
1885
1981
 
1886
1982
  let args = decl.arguments;
@@ -1901,6 +1997,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
1901
1997
  const arg = args[i];
1902
1998
  out = out.concat(generate(scope, arg));
1903
1999
 
2000
+ // todo: this should be used instead of the too many args thing above (by removing that)
2001
+ if (i >= paramCount) {
2002
+ // over param count of func, drop arg
2003
+ out.push([ Opcodes.drop ]);
2004
+ continue;
2005
+ }
2006
+
1904
2007
  if (valtypeBinary !== Valtype.i32 && (
1905
2008
  (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
1906
2009
  (importedFuncs[name] && name.startsWith('profile'))
@@ -1949,6 +2052,11 @@ const generateNew = (scope, decl, _global, _name) => {
1949
2052
  }, _global, _name);
1950
2053
  }
1951
2054
 
2055
+ if (
2056
+ (builtinFuncs[name] && !builtinFuncs[name].constr) ||
2057
+ (internalConstrs[name] && builtinFuncs[name].notConstr)
2058
+ ) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
2059
+
1952
2060
  if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
1953
2061
 
1954
2062
  return generateCall(scope, decl, _global, _name);
@@ -1974,8 +2082,11 @@ const knownType = (scope, type) => {
1974
2082
  const idx = type[0][1];
1975
2083
 
1976
2084
  // type idx = var idx + 1
1977
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1978
- if (v.metadata?.type != null) return v.metadata.type;
2085
+ const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
2086
+ if (name) {
2087
+ const local = scope.locals[name];
2088
+ if (local.metadata?.type != null) return v.metadata.type;
2089
+ }
1979
2090
  }
1980
2091
 
1981
2092
  return null;
@@ -2010,16 +2121,17 @@ const brTable = (input, bc, returns) => {
2010
2121
  }
2011
2122
 
2012
2123
  for (let i = 0; i < count; i++) {
2013
- if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2124
+ // if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
2125
+ if (i === 0) out.push([ Opcodes.block, returns ]);
2014
2126
  else out.push([ Opcodes.block, Blocktype.void ]);
2015
2127
  }
2016
2128
 
2017
- const nums = keys.filter(x => +x);
2129
+ const nums = keys.filter(x => +x >= 0);
2018
2130
  const offset = Math.min(...nums);
2019
2131
  const max = Math.max(...nums);
2020
2132
 
2021
2133
  const table = [];
2022
- let br = 1;
2134
+ let br = 0;
2023
2135
 
2024
2136
  for (let i = offset; i <= max; i++) {
2025
2137
  // if branch for this num, go to that block
@@ -2059,10 +2171,9 @@ const brTable = (input, bc, returns) => {
2059
2171
  br--;
2060
2172
  }
2061
2173
 
2062
- return [
2063
- ...out,
2064
- [ Opcodes.end, 'br table end' ]
2065
- ];
2174
+ out.push([ Opcodes.end ]);
2175
+
2176
+ return out;
2066
2177
  };
2067
2178
 
2068
2179
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2106,6 +2217,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2106
2217
  return out;
2107
2218
  };
2108
2219
 
2220
+ const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
2221
+ const out = [];
2222
+
2223
+ for (let i = 0; i < types.length; i++) {
2224
+ out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
2225
+ if (i !== 0) out.push([ Opcodes.i32_or ]);
2226
+ }
2227
+
2228
+ return out;
2229
+ };
2230
+
2109
2231
  const allocVar = (scope, name, global = false, type = true) => {
2110
2232
  const target = global ? globals : scope.locals;
2111
2233
 
@@ -2122,7 +2244,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2122
2244
 
2123
2245
  if (type) {
2124
2246
  let typeIdx = global ? globalInd++ : scope.localInd++;
2125
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2247
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2126
2248
  }
2127
2249
 
2128
2250
  return idx;
@@ -2335,18 +2457,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2335
2457
  Opcodes.i32_to_u,
2336
2458
 
2337
2459
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2338
- ...number(ValtypeSize[valtype], Valtype.i32),
2460
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2339
2461
  [ Opcodes.i32_mul ],
2340
2462
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2341
2463
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2342
2464
 
2343
2465
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2344
2466
  [ Opcodes.local_get, pointerTmp ],
2345
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2346
- ], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right), false, name, true)),
2467
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2468
+ ], generate(scope, decl.right), [
2469
+ [ Opcodes.local_get, pointerTmp ],
2470
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
2471
+ ], getNodeType(scope, decl.right), false, name, true)),
2347
2472
  [ Opcodes.local_tee, newValueTmp ],
2348
2473
 
2349
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2474
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2350
2475
  ],
2351
2476
 
2352
2477
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2454,6 +2579,7 @@ const generateUnary = (scope, decl) => {
2454
2579
  ];
2455
2580
 
2456
2581
  case '!':
2582
+ // todo/perf: optimize !!
2457
2583
  // !=
2458
2584
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2459
2585
 
@@ -2523,6 +2649,7 @@ const generateUnary = (scope, decl) => {
2523
2649
  [TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
2524
2650
  [TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
2525
2651
  [TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
2652
+ [TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
2526
2653
 
2527
2654
  [TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
2528
2655
 
@@ -2599,21 +2726,16 @@ const generateConditional = (scope, decl) => {
2599
2726
  out.push([ Opcodes.if, valtypeBinary ]);
2600
2727
  depth.push('if');
2601
2728
 
2602
- out.push(...generate(scope, decl.consequent));
2603
-
2604
- // note type
2605
2729
  out.push(
2606
- ...getNodeType(scope, decl.consequent),
2607
- ...setLastType(scope)
2730
+ ...generate(scope, decl.consequent),
2731
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2608
2732
  );
2609
2733
 
2610
2734
  out.push([ Opcodes.else ]);
2611
- out.push(...generate(scope, decl.alternate));
2612
2735
 
2613
- // note type
2614
2736
  out.push(
2615
- ...getNodeType(scope, decl.alternate),
2616
- ...setLastType(scope)
2737
+ ...generate(scope, decl.alternate),
2738
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2617
2739
  );
2618
2740
 
2619
2741
  out.push([ Opcodes.end ]);
@@ -2761,12 +2883,15 @@ const generateForOf = (scope, decl) => {
2761
2883
  // todo: optimize away counter and use end pointer
2762
2884
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2763
2885
  [TYPES.array]: [
2764
- ...setType(scope, leftName, TYPES.number),
2765
-
2766
2886
  [ Opcodes.loop, Blocktype.void ],
2767
2887
 
2768
2888
  [ Opcodes.local_get, pointer ],
2769
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
2889
+ [ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
2890
+
2891
+ ...setType(scope, leftName, [
2892
+ [ Opcodes.local_get, pointer ],
2893
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
2894
+ ]),
2770
2895
 
2771
2896
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2772
2897
 
@@ -2775,9 +2900,9 @@ const generateForOf = (scope, decl) => {
2775
2900
  ...generate(scope, decl.body),
2776
2901
  [ Opcodes.end ],
2777
2902
 
2778
- // increment iter pointer by valtype size
2903
+ // increment iter pointer by valtype size + 1
2779
2904
  [ Opcodes.local_get, pointer ],
2780
- ...number(ValtypeSize[valtype], Valtype.i32),
2905
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2781
2906
  [ Opcodes.i32_add ],
2782
2907
  [ Opcodes.local_set, pointer ],
2783
2908
 
@@ -2994,14 +3119,18 @@ const generateThrow = (scope, decl) => {
2994
3119
  };
2995
3120
 
2996
3121
  const generateTry = (scope, decl) => {
2997
- if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
3122
+ // todo: handle control-flow pre-exit for finally
3123
+ // "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
2998
3124
 
2999
3125
  const out = [];
3000
3126
 
3127
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3128
+
3001
3129
  out.push([ Opcodes.try, Blocktype.void ]);
3002
3130
  depth.push('try');
3003
3131
 
3004
3132
  out.push(...generate(scope, decl.block));
3133
+ out.push(...finalizer);
3005
3134
 
3006
3135
  if (decl.handler) {
3007
3136
  depth.pop();
@@ -3009,6 +3138,7 @@ const generateTry = (scope, decl) => {
3009
3138
 
3010
3139
  out.push([ Opcodes.catch_all ]);
3011
3140
  out.push(...generate(scope, decl.handler.body));
3141
+ out.push(...finalizer);
3012
3142
  }
3013
3143
 
3014
3144
  out.push([ Opcodes.end ]);
@@ -3094,7 +3224,7 @@ const getAllocType = itemType => {
3094
3224
  }
3095
3225
  };
3096
3226
 
3097
- const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
3227
+ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
3098
3228
  const out = [];
3099
3229
 
3100
3230
  scope.arrays ??= new Map();
@@ -3106,8 +3236,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3106
3236
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3107
3237
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3108
3238
 
3109
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3110
- else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
3239
+ let page;
3240
+ if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
3241
+ else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
3242
+
3243
+ // hack: use 1 for page 0 pointer for fast truthiness
3244
+ const ptr = page === 0 ? 1 : (page * pageSize);
3245
+ scope.arrays.set(name, ptr);
3111
3246
  }
3112
3247
 
3113
3248
  const pointer = scope.arrays.get(name);
@@ -3157,7 +3292,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3157
3292
 
3158
3293
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3159
3294
 
3160
- // store length as 0th array
3295
+ // store length
3161
3296
  out.push(
3162
3297
  ...pointerWasm,
3163
3298
  ...number(length, Valtype.i32),
@@ -3165,14 +3300,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3165
3300
  );
3166
3301
 
3167
3302
  const storeOp = StoreOps[itemType];
3168
-
3303
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3169
3304
  if (!initEmpty) for (let i = 0; i < length; i++) {
3170
3305
  if (elements[i] == null) continue;
3171
3306
 
3307
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3172
3308
  out.push(
3173
3309
  ...pointerWasm,
3174
3310
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3175
- [ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
3311
+ [ storeOp, 0, ...unsignedLEB128(offset) ],
3312
+ ...(!typed ? [] : [ // typed presumes !useRawElements
3313
+ ...pointerWasm,
3314
+ ...getNodeType(scope, elements[i]),
3315
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
3316
+ ])
3176
3317
  );
3177
3318
  }
3178
3319
 
@@ -3182,6 +3323,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3182
3323
  return [ out, pointer ];
3183
3324
  };
3184
3325
 
3326
+ const storeArray = (scope, array, index, element, aotPointer = null) => {
3327
+ if (!Array.isArray(element)) element = generate(scope, element);
3328
+ if (typeof index === 'number') index = number(index);
3329
+
3330
+ const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
3331
+
3332
+ return [
3333
+ // calculate offset
3334
+ ...index,
3335
+ Opcodes.i32_to_u,
3336
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3337
+ [ Opcodes.i32_mul ],
3338
+ ...(aotPointer ? [] : [
3339
+ ...array,
3340
+ Opcodes.i32_to_u,
3341
+ [ Opcodes.i32_add ],
3342
+ ]),
3343
+ [ Opcodes.local_set, offset ],
3344
+
3345
+ // store value
3346
+ [ Opcodes.local_get, offset ],
3347
+ ...generate(scope, element),
3348
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3349
+
3350
+ // store type
3351
+ [ Opcodes.local_get, offset ],
3352
+ ...getNodeType(scope, element),
3353
+ [ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3354
+ ];
3355
+ };
3356
+
3357
+ const loadArray = (scope, array, index, aotPointer = null) => {
3358
+ if (typeof index === 'number') index = number(index);
3359
+
3360
+ const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
3361
+
3362
+ return [
3363
+ // calculate offset
3364
+ ...index,
3365
+ Opcodes.i32_to_u,
3366
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
3367
+ [ Opcodes.i32_mul ],
3368
+ ...(aotPointer ? [] : [
3369
+ ...array,
3370
+ Opcodes.i32_to_u,
3371
+ [ Opcodes.i32_add ],
3372
+ ]),
3373
+ [ Opcodes.local_set, offset ],
3374
+
3375
+ // load value
3376
+ [ Opcodes.local_get, offset ],
3377
+ [ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3378
+
3379
+ // load type
3380
+ [ Opcodes.local_get, offset ],
3381
+ [ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
3382
+ ];
3383
+ };
3384
+
3185
3385
  const byteStringable = str => {
3186
3386
  if (!Prefs.bytestring) return false;
3187
3387
 
@@ -3210,14 +3410,28 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3210
3410
  };
3211
3411
 
3212
3412
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3213
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3413
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3214
3414
  };
3215
3415
 
3216
- export const generateMember = (scope, decl, _global, _name) => {
3416
+ const generateObject = (scope, decl, global = false, name = '$undeclared') => {
3417
+ if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
3418
+
3419
+ return [
3420
+ ...number(1),
3421
+ ...setLastType(scope, TYPES.object)
3422
+ ];
3423
+ };
3424
+
3425
+ const withType = (scope, wasm, type) => [
3426
+ ...wasm,
3427
+ ...setLastType(scope, type)
3428
+ ];
3429
+
3430
+ const generateMember = (scope, decl, _global, _name) => {
3217
3431
  const name = decl.object.name;
3218
3432
  const pointer = scope.arrays?.get(name);
3219
3433
 
3220
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3434
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3221
3435
 
3222
3436
  // hack: .name
3223
3437
  if (decl.property.name === 'name') {
@@ -3227,9 +3441,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3227
3441
  // eg: __String_prototype_toLowerCase -> toLowerCase
3228
3442
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3229
3443
 
3230
- return makeString(scope, nameProp, _global, _name, true);
3444
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3231
3445
  } else {
3232
- return generate(scope, DEFAULT_VALUE);
3446
+ return withType(scope, number(0), TYPES.undefined);
3233
3447
  }
3234
3448
  }
3235
3449
 
@@ -3237,9 +3451,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3237
3451
  if (decl.property.name === 'length') {
3238
3452
  const func = funcs.find(x => x.name === name);
3239
3453
  if (func) {
3240
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3241
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3242
- return number(typedParams ? func.params.length / 2 : func.params.length);
3454
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3455
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3243
3456
  }
3244
3457
 
3245
3458
  if (builtinFuncs[name + '$constructor']) {
@@ -3249,24 +3462,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3462
  const constructorFunc = builtinFuncs[name + '$constructor'];
3250
3463
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3251
3464
 
3252
- return number(Math.max(regularParams, constructorParams));
3465
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3253
3466
  }
3254
3467
 
3255
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3256
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3257
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3468
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3469
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3470
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3471
+
3472
+ if (Prefs.fastLength) {
3473
+ // presume valid length object
3474
+ return [
3475
+ ...(aotPointer ? number(0, Valtype.i32) : [
3476
+ ...generate(scope, decl.object),
3477
+ Opcodes.i32_to_u
3478
+ ]),
3479
+
3480
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3481
+ Opcodes.i32_from_u
3482
+ ];
3483
+ }
3484
+
3485
+ const type = getNodeType(scope, decl.object);
3486
+ const known = knownType(scope, type);
3487
+ if (known != null) {
3488
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3489
+ ...(aotPointer ? number(0, Valtype.i32) : [
3490
+ ...generate(scope, decl.object),
3491
+ Opcodes.i32_to_u
3492
+ ]),
3493
+
3494
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3495
+ Opcodes.i32_from_u
3496
+ ];
3497
+
3498
+ return number(0);
3499
+ }
3258
3500
 
3259
3501
  return [
3260
- ...(aotPointer ? number(0, Valtype.i32) : [
3261
- ...generate(scope, decl.object),
3262
- Opcodes.i32_to_u
3263
- ]),
3502
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3503
+ [ Opcodes.if, valtypeBinary ],
3504
+ ...(aotPointer ? number(0, Valtype.i32) : [
3505
+ ...generate(scope, decl.object),
3506
+ Opcodes.i32_to_u
3507
+ ]),
3264
3508
 
3265
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3266
- Opcodes.i32_from_u
3509
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3510
+ Opcodes.i32_from_u,
3511
+
3512
+ ...setLastType(scope, TYPES.number),
3513
+ [ Opcodes.else ],
3514
+ ...number(0),
3515
+ ...setLastType(scope, TYPES.undefined),
3516
+ [ Opcodes.end ]
3267
3517
  ];
3268
3518
  }
3269
3519
 
3520
+ // todo: generate this array procedurally during builtinFuncs creation
3521
+ if (['size', 'description'].includes(decl.property.name)) {
3522
+ const bc = {};
3523
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3524
+
3525
+ if (cands.length > 0) {
3526
+ for (const x of cands) {
3527
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3528
+ if (type == null) continue;
3529
+
3530
+ bc[type] = generateCall(scope, {
3531
+ callee: {
3532
+ type: 'Identifier',
3533
+ name: x
3534
+ },
3535
+ arguments: [ decl.object ],
3536
+ _protoInternalCall: true
3537
+ });
3538
+ }
3539
+ }
3540
+
3541
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3542
+ ...bc,
3543
+ default: withType(scope, number(0), TYPES.undefined)
3544
+ }, valtypeBinary);
3545
+ }
3546
+
3270
3547
  const object = generate(scope, decl.object);
3271
3548
  const property = generate(scope, decl.property);
3272
3549
 
@@ -3281,24 +3558,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3281
3558
 
3282
3559
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3283
3560
  [TYPES.array]: [
3284
- // get index as valtype
3285
- ...property,
3286
-
3287
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3288
- Opcodes.i32_to_u,
3289
- ...number(ValtypeSize[valtype], Valtype.i32),
3290
- [ Opcodes.i32_mul ],
3291
-
3292
- ...(aotPointer ? [] : [
3293
- ...object,
3294
- Opcodes.i32_to_u,
3295
- [ Opcodes.i32_add ]
3296
- ]),
3297
-
3298
- // read from memory
3299
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3300
-
3301
- ...number(TYPES.number, Valtype.i32),
3561
+ ...loadArray(scope, object, property, aotPointer),
3302
3562
  ...setLastType(scope)
3303
3563
  ],
3304
3564
 
@@ -3329,9 +3589,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3329
3589
 
3330
3590
  // return new string (page)
3331
3591
  ...number(newPointer),
3332
-
3333
- ...number(TYPES.string, Valtype.i32),
3334
- ...setLastType(scope)
3592
+ ...setLastType(scope, TYPES.string)
3335
3593
  ],
3336
3594
  [TYPES.bytestring]: [
3337
3595
  // setup new/out array
@@ -3357,9 +3615,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3357
3615
 
3358
3616
  // return new string (page)
3359
3617
  ...number(newPointer),
3360
-
3361
- ...number(TYPES.bytestring, Valtype.i32),
3362
- ...setLastType(scope)
3618
+ ...setLastType(scope, TYPES.bytestring)
3363
3619
  ],
3364
3620
 
3365
3621
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3384,7 +3640,7 @@ const objectHack = node => {
3384
3640
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3385
3641
 
3386
3642
  // if .name or .length, give up (hack within a hack!)
3387
- if (['name', 'length'].includes(node.property.name)) {
3643
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3388
3644
  node.object = objectHack(node.object);
3389
3645
  return;
3390
3646
  }
@@ -3421,33 +3677,39 @@ const generateFunc = (scope, decl) => {
3421
3677
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3422
3678
  const params = decl.params ?? [];
3423
3679
 
3424
- // const innerScope = { ...scope };
3425
3680
  // TODO: share scope/locals between !!!
3426
- const innerScope = {
3681
+ const func = {
3427
3682
  locals: {},
3428
3683
  localInd: 0,
3429
3684
  // value, type
3430
3685
  returns: [ valtypeBinary, Valtype.i32 ],
3431
3686
  throws: false,
3432
- name
3687
+ name,
3688
+ index: currentFuncIndex++
3433
3689
  };
3434
3690
 
3435
3691
  if (typedInput && decl.returnType) {
3436
3692
  const { type } = extractTypeAnnotation(decl.returnType);
3437
- if (type != null && !Prefs.indirectCalls) {
3438
- innerScope.returnType = type;
3439
- innerScope.returns = [ valtypeBinary ];
3693
+ // if (type != null && !Prefs.indirectCalls) {
3694
+ if (type != null) {
3695
+ func.returnType = type;
3696
+ func.returns = [ valtypeBinary ];
3440
3697
  }
3441
3698
  }
3442
3699
 
3443
3700
  for (let i = 0; i < params.length; i++) {
3444
- allocVar(innerScope, params[i].name, false);
3701
+ const name = params[i].name;
3702
+ // if (name == null) return todo('non-identifier args are not supported');
3703
+
3704
+ allocVar(func, name, false);
3445
3705
 
3446
3706
  if (typedInput && params[i].typeAnnotation) {
3447
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3707
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3448
3708
  }
3449
3709
  }
3450
3710
 
3711
+ func.params = Object.values(func.locals).map(x => x.type);
3712
+
3451
3713
  let body = objectHack(decl.body);
3452
3714
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3453
3715
  // hack: () => 0 -> () => return 0
@@ -3457,37 +3719,23 @@ const generateFunc = (scope, decl) => {
3457
3719
  };
3458
3720
  }
3459
3721
 
3460
- const wasm = generate(innerScope, body);
3461
- const func = {
3462
- name,
3463
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3464
- index: currentFuncIndex++,
3465
- ...innerScope
3466
- };
3467
3722
  funcIndex[name] = func.index;
3723
+ funcs.push(func);
3468
3724
 
3469
- if (name === 'main') func.gotLastType = true;
3725
+ const wasm = generate(func, body);
3726
+ func.wasm = wasm;
3470
3727
 
3471
- // quick hack fixes
3472
- for (const inst of wasm) {
3473
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3474
- inst[1] = func.index;
3475
- }
3476
- }
3728
+ if (name === 'main') func.gotLastType = true;
3477
3729
 
3478
3730
  // add end return if not found
3479
3731
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3480
3732
  wasm.push(
3481
3733
  ...number(0),
3482
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3734
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3483
3735
  [ Opcodes.return ]
3484
3736
  );
3485
3737
  }
3486
3738
 
3487
- func.wasm = wasm;
3488
-
3489
- funcs.push(func);
3490
-
3491
3739
  return func;
3492
3740
  };
3493
3741
 
@@ -3591,7 +3839,7 @@ const internalConstrs = {
3591
3839
  generate: (scope, decl) => {
3592
3840
  // todo: boolean object when used as constructor
3593
3841
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3594
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3842
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3595
3843
  },
3596
3844
  type: TYPES.boolean,
3597
3845
  length: 1
@@ -3736,9 +3984,8 @@ export default program => {
3736
3984
 
3737
3985
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3738
3986
 
3739
- generateFunc(scope, program);
3987
+ const main = generateFunc(scope, program);
3740
3988
 
3741
- const main = funcs[funcs.length - 1];
3742
3989
  main.export = true;
3743
3990
  main.returns = [ valtypeBinary, Valtype.i32 ];
3744
3991
 
@@ -3765,7 +4012,7 @@ export default program => {
3765
4012
  }
3766
4013
 
3767
4014
  // if blank main func and other exports, remove it
3768
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4015
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3769
4016
 
3770
4017
  return { funcs, globals, tags, exceptions, pages, data };
3771
4018
  };