porffor 0.28.2 → 0.28.4

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.
@@ -2680,7 +2680,9 @@ const brTable = (input, bc, returns) => {
2680
2680
  return out;
2681
2681
  };
2682
2682
 
2683
- const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2683
+ let typeswitchDepth = 0;
2684
+
2685
+ const typeSwitch = (scope, type, bc, returns = valtypeBinary, allowFallThrough = false) => {
2684
2686
  if (!Prefs.bytestring) delete bc[TYPES.bytestring];
2685
2687
 
2686
2688
  const known = knownType(scope, type);
@@ -2688,28 +2690,83 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2688
2690
  return bc[known] ?? bc.default;
2689
2691
  }
2690
2692
 
2691
- if (Prefs.typeswitchBrtable)
2693
+ if (Prefs.typeswitchBrtable) {
2694
+ if (allowFallThrough) throw new Error(`Fallthrough is not currently supported with --typeswitch-brtable`)
2692
2695
  return brTable(type, bc, returns);
2696
+ }
2697
+
2698
+ typeswitchDepth++;
2699
+
2700
+ let bcArr = bc;
2701
+ // hack?: we do this so that typeswitchDepth can be properly handled
2702
+ if (typeof bcArr === 'function') {
2703
+ bcArr = bcArr();
2704
+ }
2705
+ // hack: we need to preserve insertion order for fall through so all objects are converted to entries
2706
+ if (!Array.isArray(bcArr)) {
2707
+ bcArr = Object.entries(bc);
2708
+ } else {
2709
+ bc = Object.fromEntries(bcArr);
2710
+ }
2693
2711
 
2694
- const tmp = localTmp(scope, '#typeswitch_tmp' + (Prefs.typeswitchUniqueTmp ? uniqId() : ''), Valtype.i32);
2712
+
2713
+ const tmp = localTmp(scope, `#typeswitch_tmp${typeswitchDepth}${Prefs.typeswitchUniqueTmp ? uniqId() : ''}`, Valtype.i32);
2695
2714
  const out = [
2696
2715
  ...type,
2697
2716
  [ Opcodes.local_set, tmp ],
2698
2717
  [ Opcodes.block, returns ]
2699
2718
  ];
2700
2719
 
2701
- for (const x in bc) {
2720
+ for (let i = 0; i < bcArr.length; i++) {
2721
+ const x = bcArr[i][0];
2702
2722
  if (x === 'default') continue;
2703
2723
 
2704
- // if type == x
2705
- out.push([ Opcodes.local_get, tmp ]);
2706
- out.push(...number(x, Valtype.i32));
2707
- out.push([ Opcodes.i32_eq ]);
2708
-
2709
- out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
2710
- out.push(...bc[x]);
2711
- out.push([ Opcodes.br, 1 ]);
2712
- out.push([ Opcodes.end ]);
2724
+ if (allowFallThrough) {
2725
+ let types = [];
2726
+ let wasm;
2727
+ while (i < bcArr.length) {
2728
+ if (bcArr[i][0] === 'default') continue;
2729
+ types.push(bcArr[i][0]);
2730
+ // look for an empty array, essentially acting as an additional type for our typecheck
2731
+ const bodyWasm = bcArr[i][1];
2732
+ if (bodyWasm.length != 0) {
2733
+ wasm = bodyWasm;
2734
+ break;
2735
+ }
2736
+ i++;
2737
+ }
2738
+ // if we found any types,
2739
+ if (types.length > 0) {
2740
+ for (let j = 0; j < types.length; j++) {
2741
+ // create the type tests
2742
+ out.push(
2743
+ [ Opcodes.local_get, tmp ],
2744
+ ...number(types[j], Valtype.i32),
2745
+ [ Opcodes.i32_eq ]
2746
+ );
2747
+ // for every test but the first, or them together
2748
+ if (j != 0) out.push([ Opcodes.i32_or ]);
2749
+ }
2750
+ out.push(
2751
+ // create the consequent
2752
+ [ Opcodes.if, Blocktype.void, `TYPESWITCH|${types.map(t => TYPE_NAMES[t]).join(',')}` ],
2753
+ ...wasm,
2754
+ // we don't need an `br 1` here because depth[-1] should be 'switch', and that's the only place this is used right now
2755
+ [ Opcodes.end ]
2756
+ );
2757
+ }
2758
+ } else {
2759
+ // if type == x
2760
+ out.push(
2761
+ [ Opcodes.local_get, tmp ],
2762
+ ...number(bcArr[i][0], Valtype.i32),
2763
+ [ Opcodes.i32_eq ],
2764
+ [ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ],
2765
+ ...bcArr[i][1],
2766
+ [ Opcodes.br, 1 ],
2767
+ [ Opcodes.end ]
2768
+ );
2769
+ }
2713
2770
  }
2714
2771
 
2715
2772
  // default
@@ -2718,6 +2775,8 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
2718
2775
 
2719
2776
  out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
2720
2777
 
2778
+ typeswitchDepth--;
2779
+
2721
2780
  return out;
2722
2781
  };
2723
2782
 
@@ -2846,6 +2905,7 @@ const generateVar = (scope, decl) => {
2846
2905
 
2847
2906
  let i = 0;
2848
2907
  const elements = [...x.id.elements];
2908
+ // todo: if elements.length == 0 and Porffor.rawType(tmpName) is not iterable, throw a typeerror
2849
2909
  for (const e of elements) {
2850
2910
  switch (e?.type) {
2851
2911
  case 'RestElement': { // let [ ...foo ] = []
@@ -2878,20 +2938,6 @@ const generateVar = (scope, decl) => {
2878
2938
  continue; // skip i++
2879
2939
  }
2880
2940
 
2881
- case 'Identifier': { // let [ foo ] = []
2882
- decls.push({
2883
- type: 'VariableDeclarator',
2884
- id: e,
2885
- init: {
2886
- type: 'MemberExpression',
2887
- object: { type: 'Identifier', name: tmpName },
2888
- property: { type: 'Literal', value: i }
2889
- }
2890
- });
2891
-
2892
- break;
2893
- }
2894
-
2895
2941
  case 'AssignmentPattern': { // let [ foo = defaultValue ] = []
2896
2942
  decls.push({
2897
2943
  type: 'VariableDeclarator',
@@ -2902,7 +2948,8 @@ const generateVar = (scope, decl) => {
2902
2948
  left: {
2903
2949
  type: 'MemberExpression',
2904
2950
  object: { type: 'Identifier', name: tmpName },
2905
- property: { type: 'Literal', value: i }
2951
+ property: { type: 'Literal', value: i },
2952
+ computed: true
2906
2953
  },
2907
2954
  right: e.right
2908
2955
  }
@@ -2911,22 +2958,22 @@ const generateVar = (scope, decl) => {
2911
2958
  break;
2912
2959
  }
2913
2960
 
2914
- case 'ArrayPattern': { // let [ [ foo, bar ] ] = []
2961
+ case 'ArrayPattern': // let [ [ foo, bar ] ] = []
2962
+ case 'Identifier': // let [ foo ] = []
2963
+ case 'ObjectPattern': { // let [ { foo } ] = []
2915
2964
  decls.push({
2916
2965
  type: 'VariableDeclarator',
2917
2966
  id: e,
2918
2967
  init: {
2919
2968
  type: 'MemberExpression',
2920
2969
  object: { type: 'Identifier', name: tmpName },
2921
- property: { type: 'Literal', value: i }
2970
+ property: { type: 'Literal', value: i },
2971
+ computed: true
2922
2972
  }
2923
2973
  });
2924
2974
 
2925
2975
  break;
2926
2976
  }
2927
-
2928
- case 'ObjectPattern':
2929
- return todo(scope, 'object destructuring is not supported yet')
2930
2977
  }
2931
2978
 
2932
2979
  i++;
@@ -2952,8 +2999,69 @@ const generateVar = (scope, decl) => {
2952
2999
  continue;
2953
3000
  }
2954
3001
 
3002
+ if (x.id.type === 'ObjectPattern') {
3003
+ const decls = [];
3004
+ const tmpName = '#destructure' + uniqId();
3005
+
3006
+ const properties = [...x.id.properties];
3007
+ // todo: if properties.length == 0 and Porffor.rawType(tmpName) != object, throw a typeerror
3008
+ for (const prop of properties) {
3009
+ if (prop.type == 'Property') { // let { foo } = {}
3010
+ if (prop.value.type === 'AssignmentPattern') { // let { foo = defaultValue } = {}
3011
+ decls.push({
3012
+ type: 'VariableDeclarator',
3013
+ id: prop.value.left,
3014
+ init: {
3015
+ type: 'LogicalExpression',
3016
+ operator: '??',
3017
+ left: {
3018
+ type: 'MemberExpression',
3019
+ object: { type: 'Identifier', name: tmpName },
3020
+ property: prop.key,
3021
+ computed: prop.computed
3022
+ },
3023
+ right: prop.value.right
3024
+ }
3025
+ });
3026
+ } else {
3027
+ decls.push({
3028
+ type: 'VariableDeclarator',
3029
+ id: prop.value,
3030
+ init: {
3031
+ type: 'MemberExpression',
3032
+ object: { type: 'Identifier', name: tmpName },
3033
+ property: prop.key,
3034
+ computed: prop.computed
3035
+ }
3036
+ });
3037
+ }
3038
+ } else { // let { ...foo } = {}
3039
+ return todo(scope, 'object rest destructuring is not supported yet')
3040
+ }
3041
+ }
3042
+
3043
+ out = out.concat([
3044
+ ...generateVar(scope, {
3045
+ type: 'VariableDeclaration',
3046
+ declarations: [{
3047
+ type: 'VariableDeclarator',
3048
+ id: { type: 'Identifier', name: tmpName },
3049
+ init: x.init
3050
+ }],
3051
+ kind: decl.kind
3052
+ }),
3053
+ ...generateVar(scope, {
3054
+ type: 'VariableDeclaration',
3055
+ declarations: decls,
3056
+ kind: decl.kind
3057
+ })
3058
+ ]);
3059
+
3060
+ continue;
3061
+ }
3062
+
2955
3063
  const name = mapName(x.id.name);
2956
- if (!name) return todo(scope, 'object destructuring is not supported yet')
3064
+ if (!name) return todo(scope, `variable declarators of type ${x.id.type} are not supported yet`)
2957
3065
 
2958
3066
  if (x.init && isFuncType(x.init.type)) {
2959
3067
  // hack for let a = function () { ... }
@@ -3542,6 +3650,7 @@ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
3542
3650
  const generateIf = (scope, decl) => {
3543
3651
  const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test), false, true);
3544
3652
 
3653
+
3545
3654
  out.push([ Opcodes.if, Blocktype.void ]);
3546
3655
  depth.push('if');
3547
3656
 
@@ -4146,6 +4255,42 @@ const generateSwitch = (scope, decl) => {
4146
4255
 
4147
4256
  depth.push('switch');
4148
4257
 
4258
+ if (
4259
+ decl.discriminant.type === 'CallExpression' && decl.discriminant.callee.type === 'Identifier' && decl.discriminant.callee.name === '__Porffor_rawType'
4260
+ ) {
4261
+ const cases = []
4262
+ let canTypeCheck = true;
4263
+ for (const x of decl.cases) {
4264
+ let type;
4265
+ if (!x.test) {
4266
+ type = 'default';
4267
+ } else if (x.test.type === 'Literal') {
4268
+ type = x.test.value;
4269
+ } else if (x.test.type === 'Identifier' && x.test.name.startsWith('__Porffor_TYPES_')) {
4270
+ type = TYPES[x.test.name.slice('__Porffor_TYPES_'.length)];
4271
+ }
4272
+ if (type !== undefined) {
4273
+ cases.push([type, x.consequent]);
4274
+ } else {
4275
+ canTypeCheck = false;
4276
+ break;
4277
+ }
4278
+ }
4279
+
4280
+ if (canTypeCheck) {
4281
+ const ret = typeSwitch(scope, getNodeType(scope, decl.discriminant.arguments[0]), () => {
4282
+ const ret = [];
4283
+ for (const [type, consequent] of cases) {
4284
+ const o = generateCode(scope, { body: consequent });
4285
+ ret.push([type, o]);
4286
+ }
4287
+ return ret;
4288
+ }, Blocktype.void, true);
4289
+ depth.pop();
4290
+ return ret;
4291
+ }
4292
+ }
4293
+
4149
4294
  const cases = decl.cases.slice();
4150
4295
  const defaultCase = cases.findIndex(x => x.test == null);
4151
4296
  if (defaultCase != -1) {
@@ -4161,6 +4306,7 @@ const generateSwitch = (scope, decl) => {
4161
4306
  for (let i = 0; i < cases.length; i++) {
4162
4307
  const x = cases[i];
4163
4308
  if (x.test) {
4309
+ // todo: this should use same value zero
4164
4310
  out.push(
4165
4311
  [ Opcodes.local_get, tmp ],
4166
4312
  ...generate(scope, x.test),
@@ -5596,6 +5742,7 @@ export default program => {
5596
5742
  pages = new Map();
5597
5743
  data = [];
5598
5744
  currentFuncIndex = importedFuncs.length;
5745
+ typeswitchDepth = 0;
5599
5746
 
5600
5747
  const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
5601
5748
 
package/compiler/opt.js CHANGED
@@ -4,6 +4,37 @@ import { read_signedLEB128, read_ieee754_binary64 } from './encoding.js';
4
4
  import { log } from './log.js';
5
5
  import Prefs from './prefs.js';
6
6
 
7
+ const hasType = (funcs, pages, type) => {
8
+ switch (type) {
9
+ case 'Array':
10
+ return pages.hasArray;
11
+ case 'String':
12
+ return pages.hasString;
13
+ case 'ByteString':
14
+ return pages.hasByteString;
15
+
16
+ case 'Map':
17
+ case 'Set':
18
+ case 'WeakMap':
19
+ case 'WeakSet':
20
+ case 'WeakRef':
21
+ case 'Date':
22
+ case 'Uint8Array':
23
+ case 'Int8Array':
24
+ case 'Uint8ClampedArray':
25
+ case 'Uint16Array':
26
+ case 'Int16Array':
27
+ case 'Uint32Array':
28
+ case 'Int32Array':
29
+ case 'Float32Array':
30
+ case 'Float64Array':
31
+ return funcs.some(x => x.name === type);
32
+
33
+ default:
34
+ return true;
35
+ }
36
+ }
37
+
7
38
  export default (funcs, globals, pages, tags, exceptions) => {
8
39
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
9
40
  if (optLevel === 0) return;
@@ -176,13 +207,13 @@ export default (funcs, globals, pages, tags, exceptions) => {
176
207
  if (inst[0] === Opcodes.if && typeof inst[2] === 'string' && Prefs.rmUnusedTypes) {
177
208
  // remove unneeded typeswitch checks
178
209
 
179
- const type = inst[2].split('|')[1];
180
- let missing = false;
181
- if (type === 'Array') missing = !pages.hasArray;
182
- if (type === 'String') missing = !pages.hasString;
183
- if (type === 'ByteString') missing = !pages.hasByteString;
184
- if (['Set', 'Uint8Array', 'Int8Array', 'Uint8ClampedArray', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array'].includes(type)) {
185
- missing = funcs.find(x => x.name === type) == null;
210
+ const types = inst[2].split('|')[1].split(',');
211
+ let missing = true;
212
+ for (const type of types) {
213
+ if (hasType(funcs, pages, type)) {
214
+ missing = false;
215
+ break;
216
+ }
186
217
  }
187
218
 
188
219
  if (missing) {
@@ -196,11 +227,12 @@ export default (funcs, globals, pages, tags, exceptions) => {
196
227
  }
197
228
  }
198
229
 
199
- wasm.splice(i - 3, 4 + j - i); // remove cond and this if
200
- i -= 4;
230
+ const offset = 3 + 4 * (types.length - 1);
231
+ wasm.splice(i - offset, j - (i - offset) + 1); // remove cond and this if
232
+ i -= 1 + offset;
201
233
  inst = wasm[i];
202
234
 
203
- if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
235
+ if (Prefs.optLog) log('opt', `removed unneeded typeswitch check (${types})`);
204
236
  }
205
237
  }
206
238
 
@@ -121,7 +121,9 @@ const compile = async (file, _funcs) => {
121
121
  y.splice(1, 10, 'local', local.name, local.type);
122
122
  }
123
123
 
124
- if (y[0] === Opcodes.const && (n[0] === Opcodes.local_set || n[0] === Opcodes.local_tee)) {
124
+ if (!n) continue;
125
+
126
+ if (y[0] === Opcodes.const &&(n[0] === Opcodes.local_set || n[0] === Opcodes.local_tee)) {
125
127
  const l = locals[n[1]];
126
128
  if (!l) continue;
127
129
  if (!['#member_prop'].includes(l.name) && ![TYPES.string, TYPES.array, TYPES.bytestring].includes(l.metadata?.type)) continue;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.28.2+b2c217063",
4
+ "version": "0.28.4+0b6831c07",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/runner/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.28.2+b2c217063';
3
+ globalThis.version = '0.28.4+0b6831c07';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {