porffor 0.14.0-cdebd5442 → 0.14.0-d6c141d91

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);
1859
1881
  }
1860
1882
 
1861
- argWasm = argWasm.concat(getNodeType(scope, arg));
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
+ }
1862
1896
  }
1863
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
+ );
1934
+ }
1935
+
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);
1948
+ }
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,7 @@ 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)
1904
2001
  if (i >= paramCount) {
1905
2002
  // over param count of func, drop arg
1906
2003
  out.push([ Opcodes.drop ]);
@@ -1985,8 +2082,11 @@ const knownType = (scope, type) => {
1985
2082
  const idx = type[0][1];
1986
2083
 
1987
2084
  // type idx = var idx + 1
1988
- const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
1989
- 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
+ }
1990
2090
  }
1991
2091
 
1992
2092
  return null;
@@ -2021,16 +2121,17 @@ const brTable = (input, bc, returns) => {
2021
2121
  }
2022
2122
 
2023
2123
  for (let i = 0; i < count; i++) {
2024
- 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 ]);
2025
2126
  else out.push([ Opcodes.block, Blocktype.void ]);
2026
2127
  }
2027
2128
 
2028
- const nums = keys.filter(x => +x);
2129
+ const nums = keys.filter(x => +x >= 0);
2029
2130
  const offset = Math.min(...nums);
2030
2131
  const max = Math.max(...nums);
2031
2132
 
2032
2133
  const table = [];
2033
- let br = 1;
2134
+ let br = 0;
2034
2135
 
2035
2136
  for (let i = offset; i <= max; i++) {
2036
2137
  // if branch for this num, go to that block
@@ -2070,10 +2171,9 @@ const brTable = (input, bc, returns) => {
2070
2171
  br--;
2071
2172
  }
2072
2173
 
2073
- return [
2074
- ...out,
2075
- [ Opcodes.end, 'br table end' ]
2076
- ];
2174
+ out.push([ Opcodes.end ]);
2175
+
2176
+ return out;
2077
2177
  };
2078
2178
 
2079
2179
  const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
@@ -2117,6 +2217,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2117
2217
  return out;
2118
2218
  };
2119
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
+
2120
2231
  const allocVar = (scope, name, global = false, type = true) => {
2121
2232
  const target = global ? globals : scope.locals;
2122
2233
 
@@ -2133,7 +2244,7 @@ const allocVar = (scope, name, global = false, type = true) => {
2133
2244
 
2134
2245
  if (type) {
2135
2246
  let typeIdx = global ? globalInd++ : scope.localInd++;
2136
- target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
2247
+ target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
2137
2248
  }
2138
2249
 
2139
2250
  return idx;
@@ -2346,18 +2457,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
2346
2457
  Opcodes.i32_to_u,
2347
2458
 
2348
2459
  // turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
2349
- ...number(ValtypeSize[valtype], Valtype.i32),
2460
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2350
2461
  [ Opcodes.i32_mul ],
2351
2462
  ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
2352
2463
  ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
2353
2464
 
2354
2465
  ...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
2355
2466
  [ Opcodes.local_get, pointerTmp ],
2356
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2357
- ], 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)),
2358
2472
  [ Opcodes.local_tee, newValueTmp ],
2359
2473
 
2360
- [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2474
+ [ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
2361
2475
  ],
2362
2476
 
2363
2477
  default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
@@ -2465,6 +2579,7 @@ const generateUnary = (scope, decl) => {
2465
2579
  ];
2466
2580
 
2467
2581
  case '!':
2582
+ // todo/perf: optimize !!
2468
2583
  // !=
2469
2584
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
2470
2585
 
@@ -2611,21 +2726,16 @@ const generateConditional = (scope, decl) => {
2611
2726
  out.push([ Opcodes.if, valtypeBinary ]);
2612
2727
  depth.push('if');
2613
2728
 
2614
- out.push(...generate(scope, decl.consequent));
2615
-
2616
- // note type
2617
2729
  out.push(
2618
- ...getNodeType(scope, decl.consequent),
2619
- ...setLastType(scope)
2730
+ ...generate(scope, decl.consequent),
2731
+ ...setLastType(scope, getNodeType(scope, decl.consequent))
2620
2732
  );
2621
2733
 
2622
2734
  out.push([ Opcodes.else ]);
2623
- out.push(...generate(scope, decl.alternate));
2624
2735
 
2625
- // note type
2626
2736
  out.push(
2627
- ...getNodeType(scope, decl.alternate),
2628
- ...setLastType(scope)
2737
+ ...generate(scope, decl.alternate),
2738
+ ...setLastType(scope, getNodeType(scope, decl.alternate))
2629
2739
  );
2630
2740
 
2631
2741
  out.push([ Opcodes.end ]);
@@ -2773,12 +2883,15 @@ const generateForOf = (scope, decl) => {
2773
2883
  // todo: optimize away counter and use end pointer
2774
2884
  out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
2775
2885
  [TYPES.array]: [
2776
- ...setType(scope, leftName, TYPES.number),
2777
-
2778
2886
  [ Opcodes.loop, Blocktype.void ],
2779
2887
 
2780
2888
  [ Opcodes.local_get, pointer ],
2781
- [ 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
+ ]),
2782
2895
 
2783
2896
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
2784
2897
 
@@ -2787,9 +2900,9 @@ const generateForOf = (scope, decl) => {
2787
2900
  ...generate(scope, decl.body),
2788
2901
  [ Opcodes.end ],
2789
2902
 
2790
- // increment iter pointer by valtype size
2903
+ // increment iter pointer by valtype size + 1
2791
2904
  [ Opcodes.local_get, pointer ],
2792
- ...number(ValtypeSize[valtype], Valtype.i32),
2905
+ ...number(ValtypeSize[valtype] + 1, Valtype.i32),
2793
2906
  [ Opcodes.i32_add ],
2794
2907
  [ Opcodes.local_set, pointer ],
2795
2908
 
@@ -3006,14 +3119,18 @@ const generateThrow = (scope, decl) => {
3006
3119
  };
3007
3120
 
3008
3121
  const generateTry = (scope, decl) => {
3009
- 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."
3010
3124
 
3011
3125
  const out = [];
3012
3126
 
3127
+ const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
3128
+
3013
3129
  out.push([ Opcodes.try, Blocktype.void ]);
3014
3130
  depth.push('try');
3015
3131
 
3016
3132
  out.push(...generate(scope, decl.block));
3133
+ out.push(...finalizer);
3017
3134
 
3018
3135
  if (decl.handler) {
3019
3136
  depth.pop();
@@ -3021,6 +3138,7 @@ const generateTry = (scope, decl) => {
3021
3138
 
3022
3139
  out.push([ Opcodes.catch_all ]);
3023
3140
  out.push(...generate(scope, decl.handler.body));
3141
+ out.push(...finalizer);
3024
3142
  }
3025
3143
 
3026
3144
  out.push([ Opcodes.end ]);
@@ -3106,7 +3224,7 @@ const getAllocType = itemType => {
3106
3224
  }
3107
3225
  };
3108
3226
 
3109
- 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) => {
3110
3228
  const out = [];
3111
3229
 
3112
3230
  scope.arrays ??= new Map();
@@ -3118,8 +3236,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3118
3236
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
3119
3237
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
3120
3238
 
3121
- if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
3122
- 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);
3123
3246
  }
3124
3247
 
3125
3248
  const pointer = scope.arrays.get(name);
@@ -3169,7 +3292,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3169
3292
 
3170
3293
  const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
3171
3294
 
3172
- // store length as 0th array
3295
+ // store length
3173
3296
  out.push(
3174
3297
  ...pointerWasm,
3175
3298
  ...number(length, Valtype.i32),
@@ -3177,14 +3300,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3177
3300
  );
3178
3301
 
3179
3302
  const storeOp = StoreOps[itemType];
3180
-
3303
+ const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
3181
3304
  if (!initEmpty) for (let i = 0; i < length; i++) {
3182
3305
  if (elements[i] == null) continue;
3183
3306
 
3307
+ const offset = ValtypeSize.i32 + i * sizePerEl;
3184
3308
  out.push(
3185
3309
  ...pointerWasm,
3186
3310
  ...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
3187
- [ 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
+ ])
3188
3317
  );
3189
3318
  }
3190
3319
 
@@ -3194,6 +3323,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
3194
3323
  return [ out, pointer ];
3195
3324
  };
3196
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
+
3197
3385
  const byteStringable = str => {
3198
3386
  if (!Prefs.bytestring) return false;
3199
3387
 
@@ -3222,14 +3410,39 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
3222
3410
  };
3223
3411
 
3224
3412
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
3225
- return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
3413
+ return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
3414
+ };
3415
+
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
+ ];
3226
3423
  };
3227
3424
 
3228
- export const generateMember = (scope, decl, _global, _name) => {
3425
+ const withType = (scope, wasm, type) => [
3426
+ ...wasm,
3427
+ ...setLastType(scope, type)
3428
+ ];
3429
+
3430
+ const generateMember = (scope, decl, _global, _name) => {
3229
3431
  const name = decl.object.name;
3230
- const pointer = scope.arrays?.get(name);
3231
3432
 
3232
- const aotPointer = Prefs.aotPointerOpt && pointer != null;
3433
+ // hack: process.argv[n]
3434
+ if (name === '__process_argv') {
3435
+ const setPointer = scope.arrays?.get(_name);
3436
+
3437
+ return [
3438
+ ...number(decl.property.value - 1),
3439
+ ...(setPointer ? number(setPointer) : allocPage(scope, `__process_argv out (${randId()})`)),
3440
+ [ Opcodes.call, importedFuncs.__Porffor_readArgv ]
3441
+ ];
3442
+ }
3443
+
3444
+ const pointer = scope.arrays?.get(name);
3445
+ const aotPointer = Prefs.aotPointerOpt && pointer;
3233
3446
 
3234
3447
  // hack: .name
3235
3448
  if (decl.property.name === 'name') {
@@ -3239,9 +3452,9 @@ export const generateMember = (scope, decl, _global, _name) => {
3239
3452
  // eg: __String_prototype_toLowerCase -> toLowerCase
3240
3453
  if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
3241
3454
 
3242
- return makeString(scope, nameProp, _global, _name, true);
3455
+ return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
3243
3456
  } else {
3244
- return generate(scope, DEFAULT_VALUE);
3457
+ return withType(scope, number(0), TYPES.undefined);
3245
3458
  }
3246
3459
  }
3247
3460
 
@@ -3249,9 +3462,8 @@ export const generateMember = (scope, decl, _global, _name) => {
3249
3462
  if (decl.property.name === 'length') {
3250
3463
  const func = funcs.find(x => x.name === name);
3251
3464
  if (func) {
3252
- const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
3253
- const typedParams = userFunc || builtinFuncs[name]?.typedParams;
3254
- return number(typedParams ? func.params.length / 2 : func.params.length);
3465
+ const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
3466
+ return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
3255
3467
  }
3256
3468
 
3257
3469
  if (builtinFuncs[name + '$constructor']) {
@@ -3261,24 +3473,88 @@ export const generateMember = (scope, decl, _global, _name) => {
3261
3473
  const constructorFunc = builtinFuncs[name + '$constructor'];
3262
3474
  const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
3263
3475
 
3264
- return number(Math.max(regularParams, constructorParams));
3476
+ return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
3265
3477
  }
3266
3478
 
3267
- if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
3268
- if (importedFuncs[name]) return number(importedFuncs[name].params);
3269
- if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
3479
+ if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
3480
+ if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
3481
+ if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
3482
+
3483
+ if (Prefs.fastLength) {
3484
+ // presume valid length object
3485
+ return [
3486
+ ...(aotPointer ? number(0, Valtype.i32) : [
3487
+ ...generate(scope, decl.object),
3488
+ Opcodes.i32_to_u
3489
+ ]),
3490
+
3491
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3492
+ Opcodes.i32_from_u
3493
+ ];
3494
+ }
3495
+
3496
+ const type = getNodeType(scope, decl.object);
3497
+ const known = knownType(scope, type);
3498
+ if (known != null) {
3499
+ if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
3500
+ ...(aotPointer ? number(0, Valtype.i32) : [
3501
+ ...generate(scope, decl.object),
3502
+ Opcodes.i32_to_u
3503
+ ]),
3504
+
3505
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3506
+ Opcodes.i32_from_u
3507
+ ];
3508
+
3509
+ return number(0);
3510
+ }
3270
3511
 
3271
3512
  return [
3272
- ...(aotPointer ? number(0, Valtype.i32) : [
3273
- ...generate(scope, decl.object),
3274
- Opcodes.i32_to_u
3275
- ]),
3513
+ ...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
3514
+ [ Opcodes.if, valtypeBinary ],
3515
+ ...(aotPointer ? number(0, Valtype.i32) : [
3516
+ ...generate(scope, decl.object),
3517
+ Opcodes.i32_to_u
3518
+ ]),
3519
+
3520
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
3521
+ Opcodes.i32_from_u,
3276
3522
 
3277
- [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128((aotPointer ? pointer : 0)) ],
3278
- Opcodes.i32_from_u
3523
+ ...setLastType(scope, TYPES.number),
3524
+ [ Opcodes.else ],
3525
+ ...number(0),
3526
+ ...setLastType(scope, TYPES.undefined),
3527
+ [ Opcodes.end ]
3279
3528
  ];
3280
3529
  }
3281
3530
 
3531
+ // todo: generate this array procedurally during builtinFuncs creation
3532
+ if (['size', 'description'].includes(decl.property.name)) {
3533
+ const bc = {};
3534
+ const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
3535
+
3536
+ if (cands.length > 0) {
3537
+ for (const x of cands) {
3538
+ const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
3539
+ if (type == null) continue;
3540
+
3541
+ bc[type] = generateCall(scope, {
3542
+ callee: {
3543
+ type: 'Identifier',
3544
+ name: x
3545
+ },
3546
+ arguments: [ decl.object ],
3547
+ _protoInternalCall: true
3548
+ });
3549
+ }
3550
+ }
3551
+
3552
+ return typeSwitch(scope, getNodeType(scope, decl.object), {
3553
+ ...bc,
3554
+ default: withType(scope, number(0), TYPES.undefined)
3555
+ }, valtypeBinary);
3556
+ }
3557
+
3282
3558
  const object = generate(scope, decl.object);
3283
3559
  const property = generate(scope, decl.property);
3284
3560
 
@@ -3293,24 +3569,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3293
3569
 
3294
3570
  return typeSwitch(scope, getNodeType(scope, decl.object), {
3295
3571
  [TYPES.array]: [
3296
- // get index as valtype
3297
- ...property,
3298
-
3299
- // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
3300
- Opcodes.i32_to_u,
3301
- ...number(ValtypeSize[valtype], Valtype.i32),
3302
- [ Opcodes.i32_mul ],
3303
-
3304
- ...(aotPointer ? [] : [
3305
- ...object,
3306
- Opcodes.i32_to_u,
3307
- [ Opcodes.i32_add ]
3308
- ]),
3309
-
3310
- // read from memory
3311
- [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
3312
-
3313
- ...number(TYPES.number, Valtype.i32),
3572
+ ...loadArray(scope, object, property, aotPointer),
3314
3573
  ...setLastType(scope)
3315
3574
  ],
3316
3575
 
@@ -3341,9 +3600,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3341
3600
 
3342
3601
  // return new string (page)
3343
3602
  ...number(newPointer),
3344
-
3345
- ...number(TYPES.string, Valtype.i32),
3346
- ...setLastType(scope)
3603
+ ...setLastType(scope, TYPES.string)
3347
3604
  ],
3348
3605
  [TYPES.bytestring]: [
3349
3606
  // setup new/out array
@@ -3369,9 +3626,7 @@ export const generateMember = (scope, decl, _global, _name) => {
3369
3626
 
3370
3627
  // return new string (page)
3371
3628
  ...number(newPointer),
3372
-
3373
- ...number(TYPES.bytestring, Valtype.i32),
3374
- ...setLastType(scope)
3629
+ ...setLastType(scope, TYPES.bytestring)
3375
3630
  ],
3376
3631
 
3377
3632
  default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
@@ -3396,7 +3651,7 @@ const objectHack = node => {
3396
3651
  if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
3397
3652
 
3398
3653
  // if .name or .length, give up (hack within a hack!)
3399
- if (['name', 'length'].includes(node.property.name)) {
3654
+ if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
3400
3655
  node.object = objectHack(node.object);
3401
3656
  return;
3402
3657
  }
@@ -3433,33 +3688,39 @@ const generateFunc = (scope, decl) => {
3433
3688
  const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
3434
3689
  const params = decl.params ?? [];
3435
3690
 
3436
- // const innerScope = { ...scope };
3437
3691
  // TODO: share scope/locals between !!!
3438
- const innerScope = {
3692
+ const func = {
3439
3693
  locals: {},
3440
3694
  localInd: 0,
3441
3695
  // value, type
3442
3696
  returns: [ valtypeBinary, Valtype.i32 ],
3443
3697
  throws: false,
3444
- name
3698
+ name,
3699
+ index: currentFuncIndex++
3445
3700
  };
3446
3701
 
3447
3702
  if (typedInput && decl.returnType) {
3448
3703
  const { type } = extractTypeAnnotation(decl.returnType);
3449
- if (type != null && !Prefs.indirectCalls) {
3450
- innerScope.returnType = type;
3451
- innerScope.returns = [ valtypeBinary ];
3704
+ // if (type != null && !Prefs.indirectCalls) {
3705
+ if (type != null) {
3706
+ func.returnType = type;
3707
+ func.returns = [ valtypeBinary ];
3452
3708
  }
3453
3709
  }
3454
3710
 
3455
3711
  for (let i = 0; i < params.length; i++) {
3456
- allocVar(innerScope, params[i].name, false);
3712
+ const name = params[i].name;
3713
+ // if (name == null) return todo('non-identifier args are not supported');
3714
+
3715
+ allocVar(func, name, false);
3457
3716
 
3458
3717
  if (typedInput && params[i].typeAnnotation) {
3459
- addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
3718
+ addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
3460
3719
  }
3461
3720
  }
3462
3721
 
3722
+ func.params = Object.values(func.locals).map(x => x.type);
3723
+
3463
3724
  let body = objectHack(decl.body);
3464
3725
  if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
3465
3726
  // hack: () => 0 -> () => return 0
@@ -3469,37 +3730,23 @@ const generateFunc = (scope, decl) => {
3469
3730
  };
3470
3731
  }
3471
3732
 
3472
- const wasm = generate(innerScope, body);
3473
- const func = {
3474
- name,
3475
- params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
3476
- index: currentFuncIndex++,
3477
- ...innerScope
3478
- };
3479
3733
  funcIndex[name] = func.index;
3734
+ funcs.push(func);
3480
3735
 
3481
- if (name === 'main') func.gotLastType = true;
3736
+ const wasm = generate(func, body);
3737
+ func.wasm = wasm;
3482
3738
 
3483
- // quick hack fixes
3484
- for (const inst of wasm) {
3485
- if (inst[0] === Opcodes.call && inst[1] === -1) {
3486
- inst[1] = func.index;
3487
- }
3488
- }
3739
+ if (name === 'main') func.gotLastType = true;
3489
3740
 
3490
3741
  // add end return if not found
3491
3742
  if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
3492
3743
  wasm.push(
3493
3744
  ...number(0),
3494
- ...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3745
+ ...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
3495
3746
  [ Opcodes.return ]
3496
3747
  );
3497
3748
  }
3498
3749
 
3499
- func.wasm = wasm;
3500
-
3501
- funcs.push(func);
3502
-
3503
3750
  return func;
3504
3751
  };
3505
3752
 
@@ -3603,7 +3850,7 @@ const internalConstrs = {
3603
3850
  generate: (scope, decl) => {
3604
3851
  // todo: boolean object when used as constructor
3605
3852
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
3606
- return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
3853
+ return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
3607
3854
  },
3608
3855
  type: TYPES.boolean,
3609
3856
  length: 1
@@ -3748,9 +3995,8 @@ export default program => {
3748
3995
 
3749
3996
  if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
3750
3997
 
3751
- generateFunc(scope, program);
3998
+ const main = generateFunc(scope, program);
3752
3999
 
3753
- const main = funcs[funcs.length - 1];
3754
4000
  main.export = true;
3755
4001
  main.returns = [ valtypeBinary, Valtype.i32 ];
3756
4002
 
@@ -3777,7 +4023,7 @@ export default program => {
3777
4023
  }
3778
4024
 
3779
4025
  // if blank main func and other exports, remove it
3780
- if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
4026
+ if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
3781
4027
 
3782
4028
  return { funcs, globals, tags, exceptions, pages, data };
3783
4029
  };