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.
- package/compiler/builtins/console.ts +73 -134
- package/compiler/builtins_precompiled.js +263 -257
- package/compiler/codegen.js +181 -34
- package/compiler/opt.js +42 -10
- package/compiler/precompile.js +3 -1
- package/package.json +1 -1
- package/runner/index.js +1 -1
package/compiler/codegen.js
CHANGED
@@ -2680,7 +2680,9 @@ const brTable = (input, bc, returns) => {
|
|
2680
2680
|
return out;
|
2681
2681
|
};
|
2682
2682
|
|
2683
|
-
|
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
|
-
|
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 (
|
2720
|
+
for (let i = 0; i < bcArr.length; i++) {
|
2721
|
+
const x = bcArr[i][0];
|
2702
2722
|
if (x === 'default') continue;
|
2703
2723
|
|
2704
|
-
|
2705
|
-
|
2706
|
-
|
2707
|
-
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
2711
|
-
|
2712
|
-
|
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':
|
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,
|
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
|
180
|
-
let missing =
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
200
|
-
i
|
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
|
|
package/compiler/precompile.js
CHANGED
@@ -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 (
|
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