porffor 0.2.0-e562242 → 0.2.0-e62542f
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/CONTRIBUTING.md +254 -0
- package/LICENSE +20 -20
- package/README.md +150 -89
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2071 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +527 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +580 -272
- package/compiler/{codeGen.js → codegen.js} +1173 -428
- package/compiler/decompile.js +3 -4
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1481 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +182 -42
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +141 -43
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/node_trace.1.log +0 -1
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,41 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
62
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
63
|
switch (decl.type) {
|
60
64
|
case 'BinaryExpression':
|
61
65
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +72,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
72
|
|
69
73
|
case 'ArrowFunctionExpression':
|
70
74
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
75
|
+
const func = generateFunc(scope, decl);
|
76
|
+
|
77
|
+
if (decl.type.endsWith('Expression')) {
|
78
|
+
return number(func.index);
|
79
|
+
}
|
80
|
+
|
72
81
|
return [];
|
73
82
|
|
74
83
|
case 'BlockStatement':
|
@@ -81,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
90
|
return generateExp(scope, decl);
|
82
91
|
|
83
92
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
93
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
94
|
|
86
95
|
case 'NewExpression':
|
87
96
|
return generateNew(scope, decl, global, name);
|
@@ -99,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
99
108
|
return generateUnary(scope, decl);
|
100
109
|
|
101
110
|
case 'UpdateExpression':
|
102
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
103
112
|
|
104
113
|
case 'IfStatement':
|
105
114
|
return generateIf(scope, decl);
|
@@ -110,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
110
119
|
case 'WhileStatement':
|
111
120
|
return generateWhile(scope, decl);
|
112
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
113
125
|
case 'ForOfStatement':
|
114
126
|
return generateForOf(scope, decl);
|
115
127
|
|
@@ -119,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
119
131
|
case 'ContinueStatement':
|
120
132
|
return generateContinue(scope, decl);
|
121
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
122
137
|
case 'EmptyStatement':
|
123
138
|
return generateEmpty(scope, decl);
|
124
139
|
|
@@ -132,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
132
147
|
return generateTry(scope, decl);
|
133
148
|
|
134
149
|
case 'DebuggerStatement':
|
135
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
136
151
|
return [];
|
137
152
|
|
138
153
|
case 'ArrayExpression':
|
@@ -146,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
146
161
|
const funcsBefore = funcs.length;
|
147
162
|
generate(scope, decl.declaration);
|
148
163
|
|
149
|
-
if (funcsBefore
|
164
|
+
if (funcsBefore !== funcs.length) {
|
165
|
+
// new func added
|
166
|
+
const newFunc = funcs[funcs.length - 1];
|
167
|
+
newFunc.export = true;
|
168
|
+
}
|
169
|
+
|
170
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
150
171
|
|
151
|
-
const newFunc = funcs[funcs.length - 1];
|
152
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
153
174
|
|
154
175
|
return [];
|
155
176
|
|
156
177
|
case 'TaggedTemplateExpression': {
|
157
178
|
const funcs = {
|
158
|
-
|
179
|
+
__Porffor_wasm: str => {
|
159
180
|
let out = [];
|
160
181
|
|
161
182
|
for (const line of str.split('\n')) {
|
@@ -163,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
184
|
if (asm[0] === '') continue; // blank
|
164
185
|
|
165
186
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
189
|
continue;
|
169
190
|
}
|
170
191
|
|
@@ -174,52 +195,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
195
|
}
|
175
196
|
|
176
197
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
198
|
+
allocPage(scope, 'asm instrinsic');
|
178
199
|
// todo: add to store/load offset insts
|
179
200
|
continue;
|
180
201
|
}
|
181
202
|
|
182
203
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
183
|
-
if (
|
204
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
205
|
|
185
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
const immediates = asm.slice(1).map(x =>
|
207
|
+
const immediates = asm.slice(1).map(x => {
|
208
|
+
const int = parseInt(x);
|
209
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
210
|
+
return int;
|
211
|
+
});
|
187
212
|
|
188
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
189
214
|
}
|
190
215
|
|
191
216
|
return out;
|
192
217
|
},
|
193
218
|
|
194
|
-
|
195
|
-
|
219
|
+
__Porffor_bs: str => [
|
220
|
+
...makeString(scope, str, global, name, true),
|
196
221
|
|
197
|
-
|
198
|
-
...number(
|
199
|
-
|
222
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
223
|
+
...number(TYPES.bytestring, Valtype.i32),
|
224
|
+
...setLastType(scope)
|
225
|
+
])
|
226
|
+
],
|
227
|
+
__Porffor_s: str => [
|
228
|
+
...makeString(scope, str, global, name, false),
|
200
229
|
|
201
|
-
|
202
|
-
...number(
|
203
|
-
|
204
|
-
]
|
205
|
-
|
206
|
-
}
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
234
|
+
],
|
235
|
+
};
|
207
236
|
|
208
|
-
const
|
237
|
+
const func = decl.tag.name;
|
209
238
|
// hack for inline asm
|
210
|
-
if (!funcs[
|
239
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
240
|
+
|
241
|
+
const { quasis, expressions } = decl.quasi;
|
242
|
+
let str = quasis[0].value.raw;
|
243
|
+
|
244
|
+
for (let i = 0; i < expressions.length; i++) {
|
245
|
+
const e = expressions[i];
|
246
|
+
if (!e.name) {
|
247
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
248
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
249
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
250
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
251
|
+
|
252
|
+
str += quasis[i + 1].value.raw;
|
253
|
+
}
|
211
254
|
|
212
|
-
|
213
|
-
return funcs[name](str);
|
255
|
+
return funcs[func](str);
|
214
256
|
}
|
215
257
|
|
216
258
|
default:
|
217
|
-
|
218
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
219
262
|
return [];
|
220
263
|
}
|
221
264
|
|
222
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
223
266
|
}
|
224
267
|
};
|
225
268
|
|
@@ -247,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
247
290
|
return [ undefined, undefined ];
|
248
291
|
};
|
249
292
|
|
250
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
251
294
|
...generateThrow(scope, {
|
252
295
|
argument: {
|
253
296
|
type: 'NewExpression',
|
@@ -269,25 +312,33 @@ const generateIdent = (scope, decl) => {
|
|
269
312
|
const name = mapName(rawName);
|
270
313
|
let local = scope.locals[rawName];
|
271
314
|
|
272
|
-
if (builtinVars
|
315
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
321
|
+
}
|
322
|
+
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
324
|
+
// todo: return an actual something
|
325
|
+
return number(1);
|
275
326
|
}
|
276
327
|
|
277
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
278
329
|
// todo: return an actual something
|
279
330
|
return number(1);
|
280
331
|
}
|
281
332
|
|
282
|
-
if (local === undefined) {
|
333
|
+
if (local?.idx === undefined) {
|
283
334
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
335
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
336
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
337
|
|
287
|
-
if (globals
|
338
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
339
|
}
|
289
340
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
341
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
342
|
// return undefined if unknown key in already known var
|
292
343
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
344
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +347,7 @@ const generateIdent = (scope, decl) => {
|
|
296
347
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
348
|
}
|
298
349
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
350
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
351
|
|
301
352
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
353
|
};
|
@@ -309,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
309
360
|
// just bare "return"
|
310
361
|
return [
|
311
362
|
...number(UNDEFINED), // "undefined" if func returns
|
312
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
313
366
|
[ Opcodes.return ]
|
314
367
|
];
|
315
368
|
}
|
316
369
|
|
317
370
|
return [
|
318
371
|
...generate(scope, decl.argument),
|
319
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
320
375
|
[ Opcodes.return ]
|
321
376
|
];
|
322
377
|
};
|
@@ -330,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
330
385
|
return idx;
|
331
386
|
};
|
332
387
|
|
333
|
-
const isIntOp = op => op && (op[0] >=
|
388
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
389
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
334
390
|
|
335
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
336
392
|
const checks = {
|
@@ -339,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
339
395
|
'??': nullish
|
340
396
|
};
|
341
397
|
|
342
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
398
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
343
399
|
|
344
400
|
// generic structure for {a} OP {b}
|
345
401
|
// -->
|
@@ -347,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
347
403
|
|
348
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
349
405
|
// (like if we are in an if condition - very common)
|
350
|
-
const leftIsInt =
|
351
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
352
408
|
|
353
409
|
const canInt = leftIsInt && rightIsInt;
|
354
410
|
|
@@ -365,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
365
421
|
...right,
|
366
422
|
// note type
|
367
423
|
...rightType,
|
368
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
369
425
|
[ Opcodes.else ],
|
370
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
371
427
|
// note type
|
372
428
|
...leftType,
|
373
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
374
430
|
[ Opcodes.end ],
|
375
431
|
Opcodes.i32_from
|
376
432
|
];
|
@@ -384,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
384
440
|
...right,
|
385
441
|
// note type
|
386
442
|
...rightType,
|
387
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
388
444
|
[ Opcodes.else ],
|
389
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
390
446
|
// note type
|
391
447
|
...leftType,
|
392
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
393
449
|
[ Opcodes.end ]
|
394
450
|
];
|
395
451
|
};
|
396
452
|
|
397
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
398
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
399
455
|
// todo: convert left and right to strings if not
|
400
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -404,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
404
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
405
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
406
462
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
if (assign) {
|
411
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
463
|
+
if (assign && Prefs.aotPointerOpt) {
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
412
465
|
|
413
466
|
return [
|
414
467
|
// setup right
|
@@ -433,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
433
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
434
487
|
|
435
488
|
// copy right
|
436
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
437
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
438
491
|
|
439
492
|
[ Opcodes.local_get, leftLength ],
|
440
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
441
494
|
[ Opcodes.i32_mul ],
|
442
495
|
[ Opcodes.i32_add ],
|
443
496
|
|
@@ -446,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
446
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
447
500
|
[ Opcodes.i32_add ],
|
448
501
|
|
449
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
450
503
|
[ Opcodes.local_get, rightLength ],
|
451
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
452
505
|
[ Opcodes.i32_mul ],
|
453
506
|
|
454
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -506,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
506
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
507
560
|
|
508
561
|
// copy right
|
509
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
510
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
511
564
|
|
512
565
|
[ Opcodes.local_get, leftLength ],
|
513
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
514
567
|
[ Opcodes.i32_mul ],
|
515
568
|
[ Opcodes.i32_add ],
|
516
569
|
|
@@ -519,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
519
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
520
573
|
[ Opcodes.i32_add ],
|
521
574
|
|
522
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
523
576
|
[ Opcodes.local_get, rightLength ],
|
524
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
525
578
|
[ Opcodes.i32_mul ],
|
526
579
|
|
527
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -531,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
531
584
|
];
|
532
585
|
};
|
533
586
|
|
534
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
535
588
|
// todo: this should be rewritten into a func
|
536
589
|
// todo: convert left and right to strings if not
|
537
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -540,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
540
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
541
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
542
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
543
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
544
596
|
|
545
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
546
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -568,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
568
620
|
|
569
621
|
[ Opcodes.local_get, rightPointer ],
|
570
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
571
|
-
[ Opcodes.local_tee, rightLength ],
|
572
623
|
|
573
624
|
// fast path: check leftLength != rightLength
|
574
625
|
[ Opcodes.i32_ne ],
|
@@ -583,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
583
634
|
...number(0, Valtype.i32),
|
584
635
|
[ Opcodes.local_set, index ],
|
585
636
|
|
586
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
587
638
|
// we do this instead of having to do mul/div each iter for perf™
|
588
639
|
[ Opcodes.local_get, leftLength ],
|
589
|
-
...
|
590
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
591
644
|
[ Opcodes.local_set, indexEnd ],
|
592
645
|
|
593
646
|
// iterate over each char and check if eq
|
@@ -597,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
597
650
|
[ Opcodes.local_get, index ],
|
598
651
|
[ Opcodes.local_get, leftPointer ],
|
599
652
|
[ Opcodes.i32_add ],
|
600
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
601
656
|
|
602
657
|
// fetch right
|
603
658
|
[ Opcodes.local_get, index ],
|
604
659
|
[ Opcodes.local_get, rightPointer ],
|
605
660
|
[ Opcodes.i32_add ],
|
606
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
607
664
|
|
608
665
|
// not equal, "return" false
|
609
666
|
[ Opcodes.i32_ne ],
|
@@ -612,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
612
669
|
[ Opcodes.br, 2 ],
|
613
670
|
[ Opcodes.end ],
|
614
671
|
|
615
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
616
673
|
[ Opcodes.local_get, index ],
|
617
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
618
675
|
[ Opcodes.i32_add ],
|
619
676
|
[ Opcodes.local_tee, index ],
|
620
677
|
|
621
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
622
679
|
[ Opcodes.local_get, indexEnd ],
|
623
680
|
[ Opcodes.i32_ne ],
|
624
681
|
[ Opcodes.br_if, 0 ],
|
@@ -639,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
639
696
|
};
|
640
697
|
|
641
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
642
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
643
700
|
...wasm,
|
644
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
645
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
646
704
|
|
647
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
648
707
|
|
649
708
|
const def = [
|
650
709
|
// if value != 0
|
651
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
652
711
|
|
653
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
654
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -660,16 +719,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
660
719
|
|
661
720
|
return [
|
662
721
|
...wasm,
|
663
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
664
723
|
|
665
724
|
...typeSwitch(scope, type, {
|
666
725
|
// [TYPES.number]: def,
|
667
|
-
[TYPES.
|
726
|
+
[TYPES.array]: [
|
668
727
|
// arrays are always truthy
|
669
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
670
729
|
],
|
671
730
|
[TYPES.string]: [
|
672
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
673
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
674
733
|
|
675
734
|
// get length
|
@@ -680,24 +739,46 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
739
|
[ Opcodes.i32_eqz ], */
|
681
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
741
|
],
|
742
|
+
[TYPES.bytestring]: [ // duplicate of string
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
744
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
745
|
+
|
746
|
+
// get length
|
747
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
748
|
+
|
749
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
750
|
+
],
|
683
751
|
default: def
|
684
752
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
753
|
];
|
686
754
|
};
|
687
755
|
|
688
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
689
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
690
760
|
return [
|
691
761
|
...wasm,
|
692
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
693
763
|
|
694
764
|
...typeSwitch(scope, type, {
|
695
|
-
[TYPES.
|
765
|
+
[TYPES.array]: [
|
696
766
|
// arrays are always truthy
|
697
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
698
768
|
],
|
699
769
|
[TYPES.string]: [
|
700
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
771
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
772
|
+
|
773
|
+
// get length
|
774
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
775
|
+
|
776
|
+
// if length == 0
|
777
|
+
[ Opcodes.i32_eqz ],
|
778
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
779
|
+
],
|
780
|
+
[TYPES.bytestring]: [ // duplicate of string
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
701
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
702
783
|
|
703
784
|
// get length
|
@@ -709,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
709
790
|
],
|
710
791
|
default: [
|
711
792
|
// if value == 0
|
712
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
713
794
|
|
714
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
715
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -719,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
719
800
|
};
|
720
801
|
|
721
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
722
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
723
806
|
return [
|
724
807
|
...wasm,
|
725
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
726
809
|
|
727
810
|
...typeSwitch(scope, type, {
|
728
811
|
[TYPES.undefined]: [
|
@@ -731,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
731
814
|
],
|
732
815
|
[TYPES.object]: [
|
733
816
|
// object, null if == 0
|
734
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
735
818
|
|
736
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
737
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -760,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
760
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
761
844
|
}
|
762
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
763
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
764
850
|
const strictOp = op === '===' || op === '!==';
|
765
851
|
|
766
852
|
const startOut = [], endOut = [];
|
767
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
768
854
|
|
769
855
|
// if strict (in)equal check types match
|
770
856
|
if (strictOp) {
|
@@ -809,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
809
895
|
// todo: if equality op and an operand is undefined, return false
|
810
896
|
// todo: niche null hell with 0
|
811
897
|
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
898
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
899
|
+
if (op === '+') {
|
900
|
+
// todo: this should be dynamic too but for now only static
|
901
|
+
// string concat (a + b)
|
902
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
903
|
+
}
|
904
|
+
|
905
|
+
// not an equality op, NaN
|
906
|
+
if (!eqOp) return number(NaN);
|
907
|
+
|
908
|
+
// else leave bool ops
|
909
|
+
// todo: convert string to number if string and number/bool
|
910
|
+
// todo: string (>|>=|<|<=) string
|
911
|
+
|
912
|
+
// string comparison
|
913
|
+
if (op === '===' || op === '==') {
|
914
|
+
return compareStrings(scope, left, right);
|
915
|
+
}
|
916
|
+
|
917
|
+
if (op === '!==' || op === '!=') {
|
918
|
+
return [
|
919
|
+
...compareStrings(scope, left, right),
|
920
|
+
[ Opcodes.i32_eqz ]
|
921
|
+
];
|
922
|
+
}
|
923
|
+
}
|
924
|
+
|
925
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
926
|
+
if (op === '+') {
|
927
|
+
// todo: this should be dynamic too but for now only static
|
928
|
+
// string concat (a + b)
|
929
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
930
|
+
}
|
931
|
+
|
932
|
+
// not an equality op, NaN
|
933
|
+
if (!eqOp) return number(NaN);
|
934
|
+
|
935
|
+
// else leave bool ops
|
936
|
+
// todo: convert string to number if string and number/bool
|
937
|
+
// todo: string (>|>=|<|<=) string
|
938
|
+
|
939
|
+
// string comparison
|
940
|
+
if (op === '===' || op === '==') {
|
941
|
+
return compareStrings(scope, left, right, true);
|
942
|
+
}
|
943
|
+
|
944
|
+
if (op === '!==' || op === '!=') {
|
945
|
+
return [
|
946
|
+
...compareStrings(scope, left, right, true),
|
947
|
+
[ Opcodes.i32_eqz ]
|
948
|
+
];
|
949
|
+
}
|
950
|
+
}
|
837
951
|
|
838
952
|
let ops = operatorOpcode[valtype][op];
|
839
953
|
|
@@ -843,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
843
957
|
includeBuiltin(scope, builtinName);
|
844
958
|
const idx = funcIndex[builtinName];
|
845
959
|
|
846
|
-
return
|
960
|
+
return finalize([
|
847
961
|
...left,
|
848
962
|
...right,
|
849
963
|
[ Opcodes.call, idx ]
|
850
964
|
]);
|
851
965
|
}
|
852
966
|
|
853
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
854
968
|
|
855
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
856
970
|
ops = [ ops ];
|
857
971
|
|
858
972
|
let tmpLeft, tmpRight;
|
859
973
|
// if equal op, check if strings for compareStrings
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
// todo: intelligent partial skip later
|
865
|
-
// if neither known are string, stop this madness
|
866
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
867
|
-
return;
|
868
|
-
}
|
974
|
+
// todo: intelligent partial skip later
|
975
|
+
// if neither known are string, stop this madness
|
976
|
+
// we already do known checks earlier, so don't need to recheck
|
869
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
870
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
871
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
872
981
|
|
982
|
+
// returns false for one string, one not - but more ops/slower
|
983
|
+
// ops.unshift(...stringOnly([
|
984
|
+
// // if left is string
|
985
|
+
// ...leftType,
|
986
|
+
// ...number(TYPES.string, Valtype.i32),
|
987
|
+
// [ Opcodes.i32_eq ],
|
988
|
+
|
989
|
+
// // if right is string
|
990
|
+
// ...rightType,
|
991
|
+
// ...number(TYPES.string, Valtype.i32),
|
992
|
+
// [ Opcodes.i32_eq ],
|
993
|
+
|
994
|
+
// // if either are true
|
995
|
+
// [ Opcodes.i32_or ],
|
996
|
+
// [ Opcodes.if, Blocktype.void ],
|
997
|
+
|
998
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
999
|
+
// // if left is not string
|
1000
|
+
// ...leftType,
|
1001
|
+
// ...number(TYPES.string, Valtype.i32),
|
1002
|
+
// [ Opcodes.i32_ne ],
|
1003
|
+
|
1004
|
+
// // if right is not string
|
1005
|
+
// ...rightType,
|
1006
|
+
// ...number(TYPES.string, Valtype.i32),
|
1007
|
+
// [ Opcodes.i32_ne ],
|
1008
|
+
|
1009
|
+
// // if either are true
|
1010
|
+
// [ Opcodes.i32_or ],
|
1011
|
+
// [ Opcodes.if, Blocktype.void ],
|
1012
|
+
// ...number(0, Valtype.i32),
|
1013
|
+
// [ Opcodes.br, 2 ],
|
1014
|
+
// [ Opcodes.end ],
|
1015
|
+
|
1016
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1017
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1018
|
+
// [ Opcodes.br, 1 ],
|
1019
|
+
// [ Opcodes.end ],
|
1020
|
+
// ]));
|
1021
|
+
|
1022
|
+
// does not handle one string, one not (such cases go past)
|
873
1023
|
ops.unshift(...stringOnly([
|
874
1024
|
// if left is string
|
875
1025
|
...leftType,
|
@@ -881,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
881
1031
|
...number(TYPES.string, Valtype.i32),
|
882
1032
|
[ Opcodes.i32_eq ],
|
883
1033
|
|
884
|
-
// if
|
885
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
886
1036
|
[ Opcodes.if, Blocktype.void ],
|
1037
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1038
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1039
|
+
[ Opcodes.br, 1 ],
|
1040
|
+
[ Opcodes.end ],
|
887
1041
|
|
888
|
-
//
|
889
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
890
1043
|
...leftType,
|
891
|
-
...number(TYPES.
|
892
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
893
1046
|
|
894
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
895
1048
|
...rightType,
|
896
|
-
...number(TYPES.
|
897
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
898
1051
|
|
899
|
-
// if
|
900
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
901
1054
|
[ Opcodes.if, Blocktype.void ],
|
902
|
-
...
|
903
|
-
[ Opcodes.br, 1 ],
|
904
|
-
[ Opcodes.end ],
|
905
|
-
|
906
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
907
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
908
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
909
1057
|
[ Opcodes.br, 1 ],
|
910
1058
|
[ Opcodes.end ],
|
@@ -916,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
916
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
917
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
918
1066
|
// }
|
919
|
-
}
|
1067
|
+
}
|
920
1068
|
|
921
|
-
return
|
1069
|
+
return finalize([
|
922
1070
|
...left,
|
923
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
924
1072
|
...right,
|
@@ -935,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
935
1083
|
return out;
|
936
1084
|
};
|
937
1085
|
|
938
|
-
const
|
1086
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1087
|
+
return func({ name, params, locals, returns, localInd }, {
|
1088
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1089
|
+
builtin: name => {
|
1090
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1091
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1092
|
+
includeBuiltin(null, name);
|
1093
|
+
idx = funcIndex[name];
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
return idx;
|
1097
|
+
}
|
1098
|
+
});
|
1099
|
+
};
|
1100
|
+
|
1101
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
939
1102
|
const existing = funcs.find(x => x.name === name);
|
940
1103
|
if (existing) return existing;
|
941
1104
|
|
@@ -947,6 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
1111
|
}
|
949
1112
|
|
1113
|
+
for (const x of _data) {
|
1114
|
+
const copy = { ...x };
|
1115
|
+
copy.offset += pages.size * pageSize;
|
1116
|
+
data.push(copy);
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
950
1121
|
let baseGlobalIdx, i = 0;
|
951
1122
|
for (const type of globalTypes) {
|
952
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -969,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
969
1140
|
params,
|
970
1141
|
locals,
|
971
1142
|
returns,
|
972
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
973
1144
|
wasm,
|
974
1145
|
internal: true,
|
975
1146
|
index: currentFuncIndex++
|
@@ -992,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
992
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
993
1164
|
};
|
994
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
995
1167
|
// T = JS type, V = value/pointer
|
996
1168
|
// 0bTTT
|
997
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1015,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1015
1187
|
// 4: internal type
|
1016
1188
|
// 5: pointer
|
1017
1189
|
|
1018
|
-
const
|
1019
|
-
|
1020
|
-
|
1021
|
-
string: 0x02,
|
1022
|
-
undefined: 0x03,
|
1023
|
-
object: 0x04,
|
1024
|
-
function: 0x05,
|
1025
|
-
symbol: 0x06,
|
1026
|
-
bigint: 0x07,
|
1190
|
+
const isExistingProtoFunc = name => {
|
1191
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1192
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1027
1193
|
|
1028
|
-
|
1029
|
-
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1031
|
-
};
|
1032
|
-
|
1033
|
-
const TYPE_NAMES = {
|
1034
|
-
[TYPES.number]: 'Number',
|
1035
|
-
[TYPES.boolean]: 'Boolean',
|
1036
|
-
[TYPES.string]: 'String',
|
1037
|
-
[TYPES.undefined]: 'undefined',
|
1038
|
-
[TYPES.object]: 'Object',
|
1039
|
-
[TYPES.function]: 'Function',
|
1040
|
-
[TYPES.symbol]: 'Symbol',
|
1041
|
-
[TYPES.bigint]: 'BigInt',
|
1042
|
-
|
1043
|
-
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1194
|
+
return false;
|
1045
1195
|
};
|
1046
1196
|
|
1047
1197
|
const getType = (scope, _name) => {
|
1048
1198
|
const name = mapName(_name);
|
1049
1199
|
|
1200
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1201
|
+
|
1202
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1050
1203
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1204
|
+
|
1205
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1051
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1052
1207
|
|
1053
1208
|
let type = TYPES.undefined;
|
1054
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1055
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1056
1211
|
|
1057
|
-
if (name
|
1058
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1059
1213
|
|
1060
1214
|
return number(type, Valtype.i32);
|
1061
1215
|
};
|
@@ -1078,21 +1232,24 @@ const setType = (scope, _name, type) => {
|
|
1078
1232
|
];
|
1079
1233
|
|
1080
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1081
1236
|
};
|
1082
1237
|
|
1083
1238
|
const getLastType = scope => {
|
1084
1239
|
scope.gotLastType = true;
|
1085
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1086
1241
|
};
|
1087
1242
|
|
1088
1243
|
const setLastType = scope => {
|
1089
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1090
1245
|
};
|
1091
1246
|
|
1092
1247
|
const getNodeType = (scope, node) => {
|
1093
1248
|
const inner = () => {
|
1094
1249
|
if (node.type === 'Literal') {
|
1095
|
-
if (node.regex) return TYPES.
|
1250
|
+
if (node.regex) return TYPES.regexp;
|
1251
|
+
|
1252
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1096
1253
|
|
1097
1254
|
return TYPES[typeof node.value];
|
1098
1255
|
}
|
@@ -1107,6 +1264,27 @@ const getNodeType = (scope, node) => {
|
|
1107
1264
|
|
1108
1265
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1266
|
const name = node.callee.name;
|
1267
|
+
if (!name) {
|
1268
|
+
// iife
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1270
|
+
|
1271
|
+
// presume
|
1272
|
+
// todo: warn here?
|
1273
|
+
return TYPES.number;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1110
1288
|
const func = funcs.find(x => x.name === name);
|
1111
1289
|
|
1112
1290
|
if (func) {
|
@@ -1114,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1114
1292
|
if (func.returnType) return func.returnType;
|
1115
1293
|
}
|
1116
1294
|
|
1117
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1118
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1119
1297
|
|
1120
1298
|
// check if this is a prototype function
|
@@ -1125,11 +1303,16 @@ const getNodeType = (scope, node) => {
|
|
1125
1303
|
const spl = name.slice(2).split('_');
|
1126
1304
|
|
1127
1305
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1129
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1308
|
}
|
1131
1309
|
|
1132
|
-
if (
|
1310
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1311
|
+
// todo: return undefined for non-returning ops
|
1312
|
+
return TYPES.number;
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1133
1316
|
|
1134
1317
|
// presume
|
1135
1318
|
// todo: warn here?
|
@@ -1172,11 +1355,20 @@ const getNodeType = (scope, node) => {
|
|
1172
1355
|
}
|
1173
1356
|
|
1174
1357
|
if (node.type === 'ArrayExpression') {
|
1175
|
-
return TYPES.
|
1358
|
+
return TYPES.array;
|
1176
1359
|
}
|
1177
1360
|
|
1178
1361
|
if (node.type === 'BinaryExpression') {
|
1179
1362
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1363
|
+
if (node.operator !== '+') return TYPES.number;
|
1364
|
+
|
1365
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1366
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1367
|
+
|
1368
|
+
// todo: this should be dynamic but for now only static
|
1369
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1371
|
+
|
1180
1372
|
return TYPES.number;
|
1181
1373
|
|
1182
1374
|
// todo: string concat types
|
@@ -1201,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1205
1397
|
|
1206
1398
|
return TYPES.number;
|
1207
1399
|
}
|
@@ -1210,11 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1210
1402
|
// hack: if something.length, number type
|
1211
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1404
|
|
1213
|
-
//
|
1405
|
+
// ts hack
|
1406
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1407
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1408
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1411
|
+
|
1412
|
+
// presume
|
1214
1413
|
return TYPES.number;
|
1215
1414
|
}
|
1216
1415
|
|
1217
|
-
if (
|
1416
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1417
|
+
// hack
|
1418
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1218
1422
|
|
1219
1423
|
// presume
|
1220
1424
|
// todo: warn here?
|
@@ -1227,28 +1431,11 @@ const getNodeType = (scope, node) => {
|
|
1227
1431
|
return ret;
|
1228
1432
|
};
|
1229
1433
|
|
1230
|
-
const toString = (scope, wasm, type) => {
|
1231
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1232
|
-
return [
|
1233
|
-
...wasm,
|
1234
|
-
[ Opcodes.local_set, tmp ],
|
1235
|
-
|
1236
|
-
...typeSwitch(scope, type, {
|
1237
|
-
[TYPES.string]: [
|
1238
|
-
[ Opcodes.local_get, tmp ]
|
1239
|
-
],
|
1240
|
-
[TYPES.undefined]: [
|
1241
|
-
// [ Opcodes.]
|
1242
|
-
]
|
1243
|
-
})
|
1244
|
-
]
|
1245
|
-
};
|
1246
|
-
|
1247
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1248
1435
|
if (decl.value === null) return number(NULL);
|
1249
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1250
1438
|
if (decl.regex) {
|
1251
|
-
scope.regex[name] = decl.regex;
|
1252
1439
|
return number(1);
|
1253
1440
|
}
|
1254
1441
|
|
@@ -1261,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1261
1448
|
return number(decl.value ? 1 : 0);
|
1262
1449
|
|
1263
1450
|
case 'string':
|
1264
|
-
|
1265
|
-
const rawElements = new Array(str.length);
|
1266
|
-
let j = 0;
|
1267
|
-
for (let i = 0; i < str.length; i++) {
|
1268
|
-
rawElements[i] = str.charCodeAt(i);
|
1269
|
-
}
|
1270
|
-
|
1271
|
-
return makeArray(scope, {
|
1272
|
-
rawElements
|
1273
|
-
}, global, name, false, 'i16')[0];
|
1451
|
+
return makeString(scope, decl.value, global, name);
|
1274
1452
|
|
1275
1453
|
default:
|
1276
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1277
1455
|
}
|
1278
1456
|
};
|
1279
1457
|
|
@@ -1282,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1282
1460
|
|
1283
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1284
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1285
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1286
1466
|
if (inst[0] === Opcodes.if) count--;
|
1287
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1290,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1290
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1291
1471
|
|
1292
1472
|
if (depth === 0)
|
1293
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1294
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1295
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1296
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1474
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1475
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1476
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1297
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1298
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1299
1479
|
else if (inst[0] === Opcodes.call) {
|
1300
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1301
|
-
if (
|
1302
|
-
count
|
1303
|
-
} else
|
1304
|
-
|
1481
|
+
if (inst[1] === -1) {
|
1482
|
+
// todo: count for calling self
|
1483
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1484
|
+
count -= importedFuncs[inst[1]].params;
|
1485
|
+
count += importedFuncs[inst[1]].returns;
|
1486
|
+
} else {
|
1487
|
+
if (func) {
|
1488
|
+
count -= func.params.length;
|
1489
|
+
} else count--;
|
1490
|
+
if (func) count += func.returns.length;
|
1491
|
+
}
|
1305
1492
|
} else count--;
|
1306
1493
|
|
1307
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1319,7 +1506,7 @@ const disposeLeftover = wasm => {
|
|
1319
1506
|
const generateExp = (scope, decl) => {
|
1320
1507
|
const expression = decl.expression;
|
1321
1508
|
|
1322
|
-
const out = generate(scope, expression);
|
1509
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1323
1510
|
disposeLeftover(out);
|
1324
1511
|
|
1325
1512
|
return out;
|
@@ -1377,7 +1564,7 @@ const RTArrayUtil = {
|
|
1377
1564
|
]
|
1378
1565
|
};
|
1379
1566
|
|
1380
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1567
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1381
1568
|
/* const callee = decl.callee;
|
1382
1569
|
const args = decl.arguments;
|
1383
1570
|
|
@@ -1393,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1393
1580
|
name = func.name;
|
1394
1581
|
}
|
1395
1582
|
|
1396
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1397
1584
|
// literal eval hack
|
1398
|
-
const code = decl.arguments[0]
|
1399
|
-
|
1585
|
+
const code = decl.arguments[0]?.value ?? '';
|
1586
|
+
|
1587
|
+
let parsed;
|
1588
|
+
try {
|
1589
|
+
parsed = parse(code, []);
|
1590
|
+
} catch (e) {
|
1591
|
+
if (e.name === 'SyntaxError') {
|
1592
|
+
// throw syntax errors of evals at runtime instead
|
1593
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1594
|
+
}
|
1595
|
+
|
1596
|
+
throw e;
|
1597
|
+
}
|
1400
1598
|
|
1401
1599
|
const out = generate(scope, {
|
1402
1600
|
type: 'BlockStatement',
|
@@ -1410,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1410
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1411
1609
|
out.push(
|
1412
1610
|
...getNodeType(scope, finalStatement),
|
1413
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1414
1612
|
);
|
1415
1613
|
} else if (countLeftover(out) === 0) {
|
1416
1614
|
out.push(...number(UNDEFINED));
|
1417
1615
|
out.push(
|
1418
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1419
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1420
1618
|
);
|
1421
1619
|
}
|
1422
1620
|
|
@@ -1438,29 +1636,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1438
1636
|
|
1439
1637
|
target = { ...decl.callee };
|
1440
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1441
1642
|
}
|
1442
1643
|
|
1443
1644
|
// literal.func()
|
1444
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1445
1646
|
// megahack for /regex/.func()
|
1446
|
-
|
1447
|
-
|
1448
|
-
const
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1649
|
+
const regex = decl.callee.object.regex.pattern;
|
1650
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1449
1651
|
|
1450
|
-
funcIndex[
|
1451
|
-
|
1652
|
+
if (!funcIndex[rhemynName]) {
|
1653
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1452
1654
|
|
1655
|
+
funcIndex[func.name] = func.index;
|
1656
|
+
funcs.push(func);
|
1657
|
+
}
|
1658
|
+
|
1659
|
+
const idx = funcIndex[rhemynName];
|
1453
1660
|
return [
|
1454
1661
|
// make string arg
|
1455
1662
|
...generate(scope, decl.arguments[0]),
|
1663
|
+
Opcodes.i32_to_u,
|
1664
|
+
...getNodeType(scope, decl.arguments[0]),
|
1456
1665
|
|
1457
1666
|
// call regex func
|
1458
|
-
Opcodes.
|
1459
|
-
[ Opcodes.call, func.index ],
|
1667
|
+
[ Opcodes.call, idx ],
|
1460
1668
|
Opcodes.i32_from_u,
|
1461
1669
|
|
1462
1670
|
...number(TYPES.boolean, Valtype.i32),
|
1463
|
-
setLastType(scope)
|
1671
|
+
...setLastType(scope)
|
1464
1672
|
];
|
1465
1673
|
}
|
1466
1674
|
|
@@ -1485,23 +1693,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1485
1693
|
// }
|
1486
1694
|
|
1487
1695
|
if (protoName) {
|
1696
|
+
const protoBC = {};
|
1697
|
+
|
1698
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1699
|
+
|
1700
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1701
|
+
for (const x of builtinProtoCands) {
|
1702
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1703
|
+
if (type == null) continue;
|
1704
|
+
|
1705
|
+
protoBC[type] = generateCall(scope, {
|
1706
|
+
callee: {
|
1707
|
+
type: 'Identifier',
|
1708
|
+
name: x
|
1709
|
+
},
|
1710
|
+
arguments: [ target, ...decl.arguments ],
|
1711
|
+
_protoInternalCall: true
|
1712
|
+
});
|
1713
|
+
}
|
1714
|
+
}
|
1715
|
+
|
1488
1716
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1489
|
-
|
1490
|
-
if (f) acc[x] = f;
|
1717
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1491
1718
|
return acc;
|
1492
1719
|
}, {});
|
1493
1720
|
|
1494
|
-
// no prototype function candidates, ignore
|
1495
1721
|
if (Object.keys(protoCands).length > 0) {
|
1496
1722
|
// use local for cached i32 length as commonly used
|
1497
1723
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1498
1724
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1499
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1500
1725
|
|
1501
1726
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1502
1727
|
|
1728
|
+
const rawPointer = [
|
1729
|
+
...generate(scope, target),
|
1730
|
+
Opcodes.i32_to_u
|
1731
|
+
];
|
1732
|
+
|
1733
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1734
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1735
|
+
|
1736
|
+
let allOptUnused = true;
|
1503
1737
|
let lengthI32CacheUsed = false;
|
1504
|
-
const protoBC = {};
|
1505
1738
|
for (const x in protoCands) {
|
1506
1739
|
const protoFunc = protoCands[x];
|
1507
1740
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1509,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1742
|
...RTArrayUtil.getLength(getPointer),
|
1510
1743
|
|
1511
1744
|
...number(TYPES.number, Valtype.i32),
|
1512
|
-
setLastType(scope)
|
1745
|
+
...setLastType(scope)
|
1513
1746
|
];
|
1514
1747
|
continue;
|
1515
1748
|
}
|
@@ -1519,6 +1752,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1519
1752
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1520
1753
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1521
1754
|
|
1755
|
+
let optUnused = false;
|
1522
1756
|
const protoOut = protoFunc(getPointer, {
|
1523
1757
|
getCachedI32: () => {
|
1524
1758
|
lengthI32CacheUsed = true;
|
@@ -1533,23 +1767,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1533
1767
|
return makeArray(scope, {
|
1534
1768
|
rawElements: new Array(length)
|
1535
1769
|
}, _global, _name, true, itemType);
|
1770
|
+
}, () => {
|
1771
|
+
optUnused = true;
|
1772
|
+
return unusedValue;
|
1536
1773
|
});
|
1537
1774
|
|
1775
|
+
if (!optUnused) allOptUnused = false;
|
1776
|
+
|
1538
1777
|
protoBC[x] = [
|
1539
|
-
[ Opcodes.block, valtypeBinary ],
|
1778
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1540
1779
|
...protoOut,
|
1541
1780
|
|
1542
1781
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1543
|
-
setLastType(scope),
|
1782
|
+
...setLastType(scope),
|
1544
1783
|
[ Opcodes.end ]
|
1545
1784
|
];
|
1546
1785
|
}
|
1547
1786
|
|
1548
|
-
|
1549
|
-
...generate(scope, target),
|
1787
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1550
1788
|
|
1551
|
-
|
1552
|
-
|
1789
|
+
return [
|
1790
|
+
...(usePointerCache ? [
|
1791
|
+
...rawPointer,
|
1792
|
+
[ Opcodes.local_set, pointerLocal ],
|
1793
|
+
] : []),
|
1553
1794
|
|
1554
1795
|
...(!lengthI32CacheUsed ? [] : [
|
1555
1796
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1561,13 +1802,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1561
1802
|
|
1562
1803
|
// TODO: error better
|
1563
1804
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1564
|
-
}, valtypeBinary),
|
1805
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1565
1806
|
];
|
1566
1807
|
}
|
1808
|
+
|
1809
|
+
if (Object.keys(protoBC).length > 0) {
|
1810
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1811
|
+
...protoBC,
|
1812
|
+
|
1813
|
+
// TODO: error better
|
1814
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1815
|
+
}, valtypeBinary);
|
1816
|
+
}
|
1567
1817
|
}
|
1568
1818
|
|
1569
1819
|
// TODO: only allows callee as literal
|
1570
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1820
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1571
1821
|
|
1572
1822
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1573
1823
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1577,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1577
1827
|
idx = funcIndex[name];
|
1578
1828
|
|
1579
1829
|
// infer arguments types from builtins params
|
1580
|
-
const func = funcs.find(x => x.name === name);
|
1581
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
}
|
1830
|
+
// const func = funcs.find(x => x.name === name);
|
1831
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1832
|
+
// const arg = decl.arguments[i];
|
1833
|
+
// if (!arg.name) continue;
|
1834
|
+
|
1835
|
+
// const local = scope.locals[arg.name];
|
1836
|
+
// if (!local) continue;
|
1837
|
+
|
1838
|
+
// local.type = func.params[i];
|
1839
|
+
// if (local.type === Valtype.v128) {
|
1840
|
+
// // specify vec subtype inferred from last vec type in function name
|
1841
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1842
|
+
// }
|
1843
|
+
// }
|
1594
1844
|
}
|
1595
1845
|
|
1596
1846
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1600,15 +1850,63 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1850
|
idx = -1;
|
1601
1851
|
}
|
1602
1852
|
|
1853
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1854
|
+
const wasmOps = {
|
1855
|
+
// pointer, align, offset
|
1856
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1859
|
+
// pointer, align, offset
|
1860
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1861
|
+
// pointer, value, align, offset
|
1862
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1863
|
+
// pointer, align, offset
|
1864
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1865
|
+
// pointer, value, align, offset
|
1866
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1867
|
+
|
1868
|
+
// pointer, align, offset
|
1869
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1870
|
+
// pointer, value, align, offset
|
1871
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1872
|
+
|
1873
|
+
// value
|
1874
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1875
|
+
};
|
1876
|
+
|
1877
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1878
|
+
|
1879
|
+
if (wasmOps[opName]) {
|
1880
|
+
const op = wasmOps[opName];
|
1881
|
+
|
1882
|
+
const argOut = [];
|
1883
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1884
|
+
...generate(scope, decl.arguments[i]),
|
1885
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1886
|
+
);
|
1887
|
+
|
1888
|
+
// literals only
|
1889
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1890
|
+
|
1891
|
+
return [
|
1892
|
+
...argOut,
|
1893
|
+
[ Opcodes[opName], ...imms ],
|
1894
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1895
|
+
];
|
1896
|
+
}
|
1897
|
+
}
|
1898
|
+
|
1603
1899
|
if (idx === undefined) {
|
1604
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1605
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1900
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1901
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1606
1902
|
}
|
1607
1903
|
|
1608
1904
|
const func = funcs.find(x => x.index === idx);
|
1609
1905
|
|
1610
1906
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1611
|
-
const
|
1907
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1908
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1909
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1612
1910
|
|
1613
1911
|
let args = decl.arguments;
|
1614
1912
|
if (func && args.length < paramCount) {
|
@@ -1624,14 +1922,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1624
1922
|
if (func && func.throws) scope.throws = true;
|
1625
1923
|
|
1626
1924
|
let out = [];
|
1627
|
-
for (
|
1925
|
+
for (let i = 0; i < args.length; i++) {
|
1926
|
+
const arg = args[i];
|
1628
1927
|
out = out.concat(generate(scope, arg));
|
1629
|
-
|
1928
|
+
|
1929
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1930
|
+
out.push(Opcodes.i32_to);
|
1931
|
+
}
|
1932
|
+
|
1933
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1934
|
+
out.push(Opcodes.i32_to);
|
1935
|
+
}
|
1936
|
+
|
1937
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1630
1938
|
}
|
1631
1939
|
|
1632
1940
|
out.push([ Opcodes.call, idx ]);
|
1633
1941
|
|
1634
|
-
if (!
|
1942
|
+
if (!typedReturns) {
|
1635
1943
|
// let type;
|
1636
1944
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1637
1945
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1641,7 +1949,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1641
1949
|
// ...number(type, Valtype.i32),
|
1642
1950
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1643
1951
|
// );
|
1644
|
-
} else out.push(setLastType(scope));
|
1952
|
+
} else out.push(...setLastType(scope));
|
1953
|
+
|
1954
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1955
|
+
out.push(Opcodes.i32_from);
|
1956
|
+
}
|
1645
1957
|
|
1646
1958
|
return out;
|
1647
1959
|
};
|
@@ -1649,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1649
1961
|
const generateNew = (scope, decl, _global, _name) => {
|
1650
1962
|
// hack: basically treat this as a normal call for builtins for now
|
1651
1963
|
const name = mapName(decl.callee.name);
|
1964
|
+
|
1652
1965
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1653
|
-
|
1966
|
+
|
1967
|
+
if (builtinFuncs[name + '$constructor']) {
|
1968
|
+
// custom ...$constructor override builtin func
|
1969
|
+
return generateCall(scope, {
|
1970
|
+
...decl,
|
1971
|
+
callee: {
|
1972
|
+
type: 'Identifier',
|
1973
|
+
name: name + '$constructor'
|
1974
|
+
}
|
1975
|
+
}, _global, _name);
|
1976
|
+
}
|
1977
|
+
|
1978
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1654
1979
|
|
1655
1980
|
return generateCall(scope, decl, _global, _name);
|
1656
1981
|
};
|
@@ -1767,12 +2092,14 @@ const brTable = (input, bc, returns) => {
|
|
1767
2092
|
};
|
1768
2093
|
|
1769
2094
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2095
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
2096
|
+
|
1770
2097
|
const known = knownType(scope, type);
|
1771
2098
|
if (known != null) {
|
1772
2099
|
return bc[known] ?? bc.default;
|
1773
2100
|
}
|
1774
2101
|
|
1775
|
-
if (
|
2102
|
+
if (Prefs.typeswitchUseBrtable)
|
1776
2103
|
return brTable(type, bc, returns);
|
1777
2104
|
|
1778
2105
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1782,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1782
2109
|
[ Opcodes.block, returns ]
|
1783
2110
|
];
|
1784
2111
|
|
1785
|
-
// todo: use br_table?
|
1786
|
-
|
1787
2112
|
for (const x in bc) {
|
1788
2113
|
if (x === 'default') continue;
|
1789
2114
|
|
@@ -1807,7 +2132,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1807
2132
|
return out;
|
1808
2133
|
};
|
1809
2134
|
|
1810
|
-
const allocVar = (scope, name, global = false) => {
|
2135
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1811
2136
|
const target = global ? globals : scope.locals;
|
1812
2137
|
|
1813
2138
|
// already declared
|
@@ -1821,8 +2146,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1821
2146
|
let idx = global ? globalInd++ : scope.localInd++;
|
1822
2147
|
target[name] = { idx, type: valtypeBinary };
|
1823
2148
|
|
1824
|
-
|
1825
|
-
|
2149
|
+
if (type) {
|
2150
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2151
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2152
|
+
}
|
1826
2153
|
|
1827
2154
|
return idx;
|
1828
2155
|
};
|
@@ -1837,11 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1837
2164
|
};
|
1838
2165
|
|
1839
2166
|
const typeAnnoToPorfType = x => {
|
1840
|
-
if (
|
1841
|
-
if (TYPES[
|
2167
|
+
if (!x) return null;
|
2168
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2169
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1842
2170
|
|
1843
2171
|
switch (x) {
|
1844
2172
|
case 'i32':
|
2173
|
+
case 'i64':
|
2174
|
+
case 'f64':
|
1845
2175
|
return TYPES.number;
|
1846
2176
|
}
|
1847
2177
|
|
@@ -1852,7 +2182,7 @@ const extractTypeAnnotation = decl => {
|
|
1852
2182
|
let a = decl;
|
1853
2183
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1854
2184
|
|
1855
|
-
let type, elementType;
|
2185
|
+
let type = null, elementType = null;
|
1856
2186
|
if (a.typeName) {
|
1857
2187
|
type = a.typeName.name;
|
1858
2188
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1865,6 +2195,8 @@ const extractTypeAnnotation = decl => {
|
|
1865
2195
|
const typeName = type;
|
1866
2196
|
type = typeAnnoToPorfType(type);
|
1867
2197
|
|
2198
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
2199
|
+
|
1868
2200
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1869
2201
|
|
1870
2202
|
return { type, typeName, elementType };
|
@@ -1877,10 +2209,13 @@ const generateVar = (scope, decl) => {
|
|
1877
2209
|
|
1878
2210
|
// global variable if in top scope (main) and var ..., or if wanted
|
1879
2211
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2212
|
+
const target = global ? globals : scope.locals;
|
1880
2213
|
|
1881
2214
|
for (const x of decl.declarations) {
|
1882
2215
|
const name = mapName(x.id.name);
|
1883
2216
|
|
2217
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2218
|
+
|
1884
2219
|
if (x.init && isFuncType(x.init.type)) {
|
1885
2220
|
// hack for let a = function () { ... }
|
1886
2221
|
x.init.id = { name };
|
@@ -1896,16 +2231,29 @@ const generateVar = (scope, decl) => {
|
|
1896
2231
|
continue; // always ignore
|
1897
2232
|
}
|
1898
2233
|
|
1899
|
-
|
2234
|
+
// // generate init before allocating var
|
2235
|
+
// let generated;
|
2236
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2237
|
+
|
2238
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2239
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1900
2240
|
|
1901
|
-
if (
|
2241
|
+
if (typed) {
|
1902
2242
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1903
2243
|
}
|
1904
2244
|
|
1905
2245
|
if (x.init) {
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
2246
|
+
const generated = generate(scope, x.init, global, name);
|
2247
|
+
if (scope.arrays?.get(name) != null) {
|
2248
|
+
// hack to set local as pointer before
|
2249
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2250
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2251
|
+
generated.pop();
|
2252
|
+
out = out.concat(generated);
|
2253
|
+
} else {
|
2254
|
+
out = out.concat(generated);
|
2255
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2256
|
+
}
|
1909
2257
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1910
2258
|
}
|
1911
2259
|
|
@@ -1916,7 +2264,8 @@ const generateVar = (scope, decl) => {
|
|
1916
2264
|
return out;
|
1917
2265
|
};
|
1918
2266
|
|
1919
|
-
|
2267
|
+
// todo: optimize this func for valueUnused
|
2268
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1920
2269
|
const { type, name } = decl.left;
|
1921
2270
|
|
1922
2271
|
if (type === 'ObjectPattern') {
|
@@ -1931,22 +2280,30 @@ const generateAssign = (scope, decl) => {
|
|
1931
2280
|
return [];
|
1932
2281
|
}
|
1933
2282
|
|
2283
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2284
|
+
|
1934
2285
|
// hack: .length setter
|
1935
2286
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1936
2287
|
const name = decl.left.object.name;
|
1937
|
-
const pointer = arrays
|
2288
|
+
const pointer = scope.arrays?.get(name);
|
1938
2289
|
|
1939
|
-
const aotPointer = pointer != null;
|
2290
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1940
2291
|
|
1941
2292
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2293
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1942
2294
|
|
1943
2295
|
return [
|
1944
2296
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1945
2297
|
...generate(scope, decl.left.object),
|
1946
2298
|
Opcodes.i32_to_u
|
1947
2299
|
]),
|
2300
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1948
2301
|
|
1949
|
-
...generate(scope, decl.right),
|
2302
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2303
|
+
[ Opcodes.local_get, pointerTmp ],
|
2304
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2305
|
+
Opcodes.i32_from_u
|
2306
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
1950
2307
|
[ Opcodes.local_tee, newValueTmp ],
|
1951
2308
|
|
1952
2309
|
Opcodes.i32_to_u,
|
@@ -1956,21 +2313,19 @@ const generateAssign = (scope, decl) => {
|
|
1956
2313
|
];
|
1957
2314
|
}
|
1958
2315
|
|
1959
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1960
|
-
|
1961
2316
|
// arr[i]
|
1962
2317
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1963
2318
|
const name = decl.left.object.name;
|
1964
|
-
const pointer = arrays
|
2319
|
+
const pointer = scope.arrays?.get(name);
|
1965
2320
|
|
1966
|
-
const aotPointer = pointer != null;
|
2321
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1967
2322
|
|
1968
2323
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1969
2324
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1970
2325
|
|
1971
2326
|
return [
|
1972
2327
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1973
|
-
[TYPES.
|
2328
|
+
[TYPES.array]: [
|
1974
2329
|
...(aotPointer ? [] : [
|
1975
2330
|
...generate(scope, decl.left.object),
|
1976
2331
|
Opcodes.i32_to_u
|
@@ -2019,6 +2374,8 @@ const generateAssign = (scope, decl) => {
|
|
2019
2374
|
];
|
2020
2375
|
}
|
2021
2376
|
|
2377
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2378
|
+
|
2022
2379
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2023
2380
|
|
2024
2381
|
if (local === undefined) {
|
@@ -2065,9 +2422,7 @@ const generateAssign = (scope, decl) => {
|
|
2065
2422
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2066
2423
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2067
2424
|
|
2068
|
-
getLastType(scope)
|
2069
|
-
// hack: type is idx+1
|
2070
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2425
|
+
...setType(scope, name, getLastType(scope))
|
2071
2426
|
];
|
2072
2427
|
}
|
2073
2428
|
|
@@ -2078,9 +2433,7 @@ const generateAssign = (scope, decl) => {
|
|
2078
2433
|
|
2079
2434
|
// todo: string concat types
|
2080
2435
|
|
2081
|
-
|
2082
|
-
...number(TYPES.number, Valtype.i32),
|
2083
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2436
|
+
...setType(scope, name, TYPES.number)
|
2084
2437
|
];
|
2085
2438
|
};
|
2086
2439
|
|
@@ -2126,7 +2479,7 @@ const generateUnary = (scope, decl) => {
|
|
2126
2479
|
return out;
|
2127
2480
|
}
|
2128
2481
|
|
2129
|
-
case 'delete':
|
2482
|
+
case 'delete': {
|
2130
2483
|
let toReturn = true, toGenerate = true;
|
2131
2484
|
|
2132
2485
|
if (decl.argument.type === 'Identifier') {
|
@@ -2148,38 +2501,60 @@ const generateUnary = (scope, decl) => {
|
|
2148
2501
|
|
2149
2502
|
out.push(...number(toReturn ? 1 : 0));
|
2150
2503
|
return out;
|
2504
|
+
}
|
2505
|
+
|
2506
|
+
case 'typeof': {
|
2507
|
+
let overrideType, toGenerate = true;
|
2151
2508
|
|
2152
|
-
|
2153
|
-
|
2509
|
+
if (decl.argument.type === 'Identifier') {
|
2510
|
+
const out = generateIdent(scope, decl.argument);
|
2511
|
+
|
2512
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2513
|
+
if (out[1]) {
|
2514
|
+
// does not exist (2 ops from throw)
|
2515
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2516
|
+
toGenerate = false;
|
2517
|
+
}
|
2518
|
+
}
|
2519
|
+
|
2520
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2521
|
+
disposeLeftover(out);
|
2522
|
+
|
2523
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2154
2524
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2155
2525
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2156
2526
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2157
2527
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2158
2528
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2159
2529
|
|
2530
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2531
|
+
|
2160
2532
|
// object and internal types
|
2161
2533
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2162
|
-
});
|
2534
|
+
}));
|
2535
|
+
|
2536
|
+
return out;
|
2537
|
+
}
|
2163
2538
|
|
2164
2539
|
default:
|
2165
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2540
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2166
2541
|
}
|
2167
2542
|
};
|
2168
2543
|
|
2169
|
-
const generateUpdate = (scope, decl) => {
|
2544
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2170
2545
|
const { name } = decl.argument;
|
2171
2546
|
|
2172
2547
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2173
2548
|
|
2174
2549
|
if (local === undefined) {
|
2175
|
-
return todo(`update expression with undefined variable
|
2550
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2176
2551
|
}
|
2177
2552
|
|
2178
2553
|
const idx = local.idx;
|
2179
2554
|
const out = [];
|
2180
2555
|
|
2181
2556
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2182
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2557
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2183
2558
|
|
2184
2559
|
switch (decl.operator) {
|
2185
2560
|
case '++':
|
@@ -2192,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
|
|
2192
2567
|
}
|
2193
2568
|
|
2194
2569
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2195
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2570
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2196
2571
|
|
2197
2572
|
return out;
|
2198
2573
|
};
|
@@ -2232,7 +2607,7 @@ const generateConditional = (scope, decl) => {
|
|
2232
2607
|
// note type
|
2233
2608
|
out.push(
|
2234
2609
|
...getNodeType(scope, decl.consequent),
|
2235
|
-
setLastType(scope)
|
2610
|
+
...setLastType(scope)
|
2236
2611
|
);
|
2237
2612
|
|
2238
2613
|
out.push([ Opcodes.else ]);
|
@@ -2241,7 +2616,7 @@ const generateConditional = (scope, decl) => {
|
|
2241
2616
|
// note type
|
2242
2617
|
out.push(
|
2243
2618
|
...getNodeType(scope, decl.alternate),
|
2244
|
-
setLastType(scope)
|
2619
|
+
...setLastType(scope)
|
2245
2620
|
);
|
2246
2621
|
|
2247
2622
|
out.push([ Opcodes.end ]);
|
@@ -2255,15 +2630,17 @@ const generateFor = (scope, decl) => {
|
|
2255
2630
|
const out = [];
|
2256
2631
|
|
2257
2632
|
if (decl.init) {
|
2258
|
-
out.push(...generate(scope, decl.init));
|
2633
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2259
2634
|
disposeLeftover(out);
|
2260
2635
|
}
|
2261
2636
|
|
2262
2637
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2263
2638
|
depth.push('for');
|
2264
2639
|
|
2265
|
-
out.push(...generate(scope, decl.test));
|
2266
|
-
|
2640
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2641
|
+
else out.push(...number(1, Valtype.i32));
|
2642
|
+
|
2643
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2267
2644
|
depth.push('if');
|
2268
2645
|
|
2269
2646
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2271,8 +2648,7 @@ const generateFor = (scope, decl) => {
|
|
2271
2648
|
out.push(...generate(scope, decl.body));
|
2272
2649
|
out.push([ Opcodes.end ]);
|
2273
2650
|
|
2274
|
-
out.push(...generate(scope, decl.update));
|
2275
|
-
depth.pop();
|
2651
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2276
2652
|
|
2277
2653
|
out.push([ Opcodes.br, 1 ]);
|
2278
2654
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2300,6 +2676,36 @@ const generateWhile = (scope, decl) => {
|
|
2300
2676
|
return out;
|
2301
2677
|
};
|
2302
2678
|
|
2679
|
+
const generateDoWhile = (scope, decl) => {
|
2680
|
+
const out = [];
|
2681
|
+
|
2682
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2683
|
+
depth.push('dowhile');
|
2684
|
+
|
2685
|
+
// block for break (includes all)
|
2686
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2687
|
+
depth.push('block');
|
2688
|
+
|
2689
|
+
// block for continue
|
2690
|
+
// includes body but not test+loop so we can exit body at anytime
|
2691
|
+
// and still test+loop after
|
2692
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2693
|
+
depth.push('block');
|
2694
|
+
|
2695
|
+
out.push(...generate(scope, decl.body));
|
2696
|
+
|
2697
|
+
out.push([ Opcodes.end ]);
|
2698
|
+
depth.pop();
|
2699
|
+
|
2700
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2701
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2702
|
+
|
2703
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2704
|
+
depth.pop(); depth.pop();
|
2705
|
+
|
2706
|
+
return out;
|
2707
|
+
};
|
2708
|
+
|
2303
2709
|
const generateForOf = (scope, decl) => {
|
2304
2710
|
const out = [];
|
2305
2711
|
|
@@ -2329,8 +2735,17 @@ const generateForOf = (scope, decl) => {
|
|
2329
2735
|
// setup local for left
|
2330
2736
|
generate(scope, decl.left);
|
2331
2737
|
|
2332
|
-
|
2738
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2739
|
+
if (!leftName && decl.left.name) {
|
2740
|
+
leftName = decl.left.name;
|
2741
|
+
|
2742
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2743
|
+
}
|
2744
|
+
|
2745
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2746
|
+
|
2333
2747
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2748
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2334
2749
|
|
2335
2750
|
depth.push('block');
|
2336
2751
|
depth.push('block');
|
@@ -2338,15 +2753,17 @@ const generateForOf = (scope, decl) => {
|
|
2338
2753
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2339
2754
|
// hack: this is naughty and will break things!
|
2340
2755
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2341
|
-
if (pages.
|
2756
|
+
if (pages.hasAnyString) {
|
2757
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2342
2758
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2343
2759
|
rawElements: new Array(1)
|
2344
2760
|
}, isGlobal, leftName, true, 'i16');
|
2345
2761
|
}
|
2346
2762
|
|
2347
2763
|
// set type for local
|
2764
|
+
// todo: optimize away counter and use end pointer
|
2348
2765
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2349
|
-
[TYPES.
|
2766
|
+
[TYPES.array]: [
|
2350
2767
|
...setType(scope, leftName, TYPES.number),
|
2351
2768
|
|
2352
2769
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2429,6 +2846,56 @@ const generateForOf = (scope, decl) => {
|
|
2429
2846
|
[ Opcodes.end ],
|
2430
2847
|
[ Opcodes.end ]
|
2431
2848
|
],
|
2849
|
+
[TYPES.bytestring]: [
|
2850
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2851
|
+
|
2852
|
+
[ Opcodes.loop, Blocktype.void ],
|
2853
|
+
|
2854
|
+
// setup new/out array
|
2855
|
+
...newOut,
|
2856
|
+
[ Opcodes.drop ],
|
2857
|
+
|
2858
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.local_get, pointer ],
|
2862
|
+
[ Opcodes.local_get, counter ],
|
2863
|
+
[ Opcodes.i32_add ],
|
2864
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// store to new string ind 0
|
2867
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2868
|
+
|
2869
|
+
// return new string (page)
|
2870
|
+
...number(newPointer),
|
2871
|
+
|
2872
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2873
|
+
|
2874
|
+
[ Opcodes.block, Blocktype.void ],
|
2875
|
+
[ Opcodes.block, Blocktype.void ],
|
2876
|
+
...generate(scope, decl.body),
|
2877
|
+
[ Opcodes.end ],
|
2878
|
+
|
2879
|
+
// increment iter pointer
|
2880
|
+
// [ Opcodes.local_get, pointer ],
|
2881
|
+
// ...number(1, Valtype.i32),
|
2882
|
+
// [ Opcodes.i32_add ],
|
2883
|
+
// [ Opcodes.local_set, pointer ],
|
2884
|
+
|
2885
|
+
// increment counter by 1
|
2886
|
+
[ Opcodes.local_get, counter ],
|
2887
|
+
...number(1, Valtype.i32),
|
2888
|
+
[ Opcodes.i32_add ],
|
2889
|
+
[ Opcodes.local_tee, counter ],
|
2890
|
+
|
2891
|
+
// loop if counter != length
|
2892
|
+
[ Opcodes.local_get, length ],
|
2893
|
+
[ Opcodes.i32_ne ],
|
2894
|
+
[ Opcodes.br_if, 1 ],
|
2895
|
+
|
2896
|
+
[ Opcodes.end ],
|
2897
|
+
[ Opcodes.end ]
|
2898
|
+
],
|
2432
2899
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2433
2900
|
}, Blocktype.void));
|
2434
2901
|
|
@@ -2439,28 +2906,65 @@ const generateForOf = (scope, decl) => {
|
|
2439
2906
|
return out;
|
2440
2907
|
};
|
2441
2908
|
|
2909
|
+
// find the nearest loop in depth map by type
|
2442
2910
|
const getNearestLoop = () => {
|
2443
2911
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2444
|
-
if (
|
2912
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2445
2913
|
}
|
2446
2914
|
|
2447
2915
|
return -1;
|
2448
2916
|
};
|
2449
2917
|
|
2450
2918
|
const generateBreak = (scope, decl) => {
|
2451
|
-
const
|
2919
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2920
|
+
const type = depth[target];
|
2921
|
+
|
2922
|
+
// different loop types have different branch offsets
|
2923
|
+
// as they have different wasm block/loop/if structures
|
2924
|
+
// we need to use the right offset by type to branch to the one we want
|
2925
|
+
// for a break: exit the loop without executing anything else inside it
|
2926
|
+
const offset = ({
|
2927
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2928
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2929
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2930
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2931
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2932
|
+
})[type];
|
2933
|
+
|
2452
2934
|
return [
|
2453
|
-
[ Opcodes.br, ...signedLEB128(
|
2935
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2454
2936
|
];
|
2455
2937
|
};
|
2456
2938
|
|
2457
2939
|
const generateContinue = (scope, decl) => {
|
2458
|
-
const
|
2940
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2941
|
+
const type = depth[target];
|
2942
|
+
|
2943
|
+
// different loop types have different branch offsets
|
2944
|
+
// as they have different wasm block/loop/if structures
|
2945
|
+
// we need to use the right offset by type to branch to the one we want
|
2946
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2947
|
+
const offset = ({
|
2948
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2949
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2950
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2951
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2952
|
+
})[type];
|
2953
|
+
|
2459
2954
|
return [
|
2460
|
-
[ Opcodes.br, ...signedLEB128(
|
2955
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2461
2956
|
];
|
2462
2957
|
};
|
2463
2958
|
|
2959
|
+
const generateLabel = (scope, decl) => {
|
2960
|
+
scope.labels ??= new Map();
|
2961
|
+
|
2962
|
+
const name = decl.label.name;
|
2963
|
+
scope.labels.set(name, depth.length);
|
2964
|
+
|
2965
|
+
return generate(scope, decl.body);
|
2966
|
+
};
|
2967
|
+
|
2464
2968
|
const generateThrow = (scope, decl) => {
|
2465
2969
|
scope.throws = true;
|
2466
2970
|
|
@@ -2469,7 +2973,7 @@ const generateThrow = (scope, decl) => {
|
|
2469
2973
|
// hack: throw new X("...") -> throw "..."
|
2470
2974
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2471
2975
|
constructor = decl.argument.callee.name;
|
2472
|
-
message = decl.argument.arguments[0]
|
2976
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2473
2977
|
}
|
2474
2978
|
|
2475
2979
|
if (tags.length === 0) tags.push({
|
@@ -2481,6 +2985,9 @@ const generateThrow = (scope, decl) => {
|
|
2481
2985
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2482
2986
|
let tagIdx = tags[0].idx;
|
2483
2987
|
|
2988
|
+
scope.exceptions ??= [];
|
2989
|
+
scope.exceptions.push(exceptId);
|
2990
|
+
|
2484
2991
|
// todo: write a description of how this works lol
|
2485
2992
|
|
2486
2993
|
return [
|
@@ -2490,7 +2997,7 @@ const generateThrow = (scope, decl) => {
|
|
2490
2997
|
};
|
2491
2998
|
|
2492
2999
|
const generateTry = (scope, decl) => {
|
2493
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3000
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2494
3001
|
|
2495
3002
|
const out = [];
|
2496
3003
|
|
@@ -2521,29 +3028,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2521
3028
|
// TODO
|
2522
3029
|
// if identifier declared, use that
|
2523
3030
|
// else, use default (right)
|
2524
|
-
return todo('assignment pattern (optional arg)');
|
3031
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2525
3032
|
};
|
2526
3033
|
|
2527
3034
|
let pages = new Map();
|
2528
|
-
const allocPage = (reason, type) => {
|
3035
|
+
const allocPage = (scope, reason, type) => {
|
2529
3036
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2530
3037
|
|
2531
3038
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2532
3039
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3040
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3041
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2533
3042
|
|
2534
3043
|
const ind = pages.size;
|
2535
3044
|
pages.set(reason, { ind, type });
|
2536
3045
|
|
2537
|
-
|
3046
|
+
scope.pages ??= new Map();
|
3047
|
+
scope.pages.set(reason, { ind, type });
|
3048
|
+
|
3049
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2538
3050
|
|
2539
3051
|
return ind;
|
2540
3052
|
};
|
2541
3053
|
|
3054
|
+
// todo: add scope.pages
|
2542
3055
|
const freePage = reason => {
|
2543
3056
|
const { ind } = pages.get(reason);
|
2544
3057
|
pages.delete(reason);
|
2545
3058
|
|
2546
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3059
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2547
3060
|
|
2548
3061
|
return ind;
|
2549
3062
|
};
|
@@ -2563,38 +3076,53 @@ const StoreOps = {
|
|
2563
3076
|
f64: Opcodes.f64_store,
|
2564
3077
|
|
2565
3078
|
// expects i32 input!
|
2566
|
-
|
3079
|
+
i8: Opcodes.i32_store8,
|
3080
|
+
i16: Opcodes.i32_store16,
|
2567
3081
|
};
|
2568
3082
|
|
2569
3083
|
let data = [];
|
2570
3084
|
|
2571
|
-
const compileBytes = (val, itemType
|
3085
|
+
const compileBytes = (val, itemType) => {
|
2572
3086
|
// todo: this is a mess and needs confirming / ????
|
2573
3087
|
switch (itemType) {
|
2574
3088
|
case 'i8': return [ val % 256 ];
|
2575
|
-
case 'i16': return [ val % 256,
|
2576
|
-
|
2577
|
-
case 'i32':
|
2578
|
-
|
2579
|
-
return enforceFourBytes(signedLEB128(val));
|
3089
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3090
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3091
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3092
|
+
// todo: i64
|
2580
3093
|
|
2581
3094
|
case 'f64': return ieee754_binary64(val);
|
2582
3095
|
}
|
2583
3096
|
};
|
2584
3097
|
|
3098
|
+
const getAllocType = itemType => {
|
3099
|
+
switch (itemType) {
|
3100
|
+
case 'i8': return 'bytestring';
|
3101
|
+
case 'i16': return 'string';
|
3102
|
+
|
3103
|
+
default: return 'array';
|
3104
|
+
}
|
3105
|
+
};
|
3106
|
+
|
2585
3107
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2586
3108
|
const out = [];
|
2587
3109
|
|
3110
|
+
scope.arrays ??= new Map();
|
3111
|
+
|
2588
3112
|
let firstAssign = false;
|
2589
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3113
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2590
3114
|
firstAssign = true;
|
2591
3115
|
|
2592
3116
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2593
3117
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2594
|
-
|
3118
|
+
|
3119
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3120
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2595
3121
|
}
|
2596
3122
|
|
2597
|
-
const pointer = arrays.get(name);
|
3123
|
+
const pointer = scope.arrays.get(name);
|
3124
|
+
|
3125
|
+
const local = global ? globals[name] : scope.locals[name];
|
2598
3126
|
|
2599
3127
|
const useRawElements = !!decl.rawElements;
|
2600
3128
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2602,19 +3130,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2602
3130
|
const valtype = itemTypeToValtype[itemType];
|
2603
3131
|
const length = elements.length;
|
2604
3132
|
|
2605
|
-
if (firstAssign && useRawElements) {
|
2606
|
-
|
3133
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3134
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3135
|
+
if (length !== 0) {
|
3136
|
+
let bytes = compileBytes(length, 'i32');
|
2607
3137
|
|
2608
|
-
|
2609
|
-
|
3138
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3139
|
+
if (elements[i] == null) continue;
|
2610
3140
|
|
2611
|
-
|
2612
|
-
|
3141
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3142
|
+
}
|
2613
3143
|
|
2614
|
-
|
2615
|
-
|
2616
|
-
|
2617
|
-
|
3144
|
+
const ind = data.push({
|
3145
|
+
offset: pointer,
|
3146
|
+
bytes
|
3147
|
+
}) - 1;
|
3148
|
+
|
3149
|
+
scope.data ??= [];
|
3150
|
+
scope.data.push(ind);
|
3151
|
+
}
|
2618
3152
|
|
2619
3153
|
// local value as pointer
|
2620
3154
|
out.push(...number(pointer));
|
@@ -2622,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2622
3156
|
return [ out, pointer ];
|
2623
3157
|
}
|
2624
3158
|
|
3159
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3160
|
+
if (pointerTmp != null) {
|
3161
|
+
out.push(
|
3162
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3163
|
+
Opcodes.i32_to_u,
|
3164
|
+
[ Opcodes.local_set, pointerTmp ]
|
3165
|
+
);
|
3166
|
+
}
|
3167
|
+
|
3168
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3169
|
+
|
2625
3170
|
// store length as 0th array
|
2626
3171
|
out.push(
|
2627
|
-
...
|
3172
|
+
...pointerWasm,
|
2628
3173
|
...number(length, Valtype.i32),
|
2629
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3174
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2630
3175
|
);
|
2631
3176
|
|
2632
3177
|
const storeOp = StoreOps[itemType];
|
@@ -2635,43 +3180,68 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2635
3180
|
if (elements[i] == null) continue;
|
2636
3181
|
|
2637
3182
|
out.push(
|
2638
|
-
...
|
3183
|
+
...pointerWasm,
|
2639
3184
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2640
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3185
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2641
3186
|
);
|
2642
3187
|
}
|
2643
3188
|
|
2644
3189
|
// local value as pointer
|
2645
|
-
out.push(...
|
3190
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2646
3191
|
|
2647
3192
|
return [ out, pointer ];
|
2648
3193
|
};
|
2649
3194
|
|
2650
|
-
const
|
3195
|
+
const byteStringable = str => {
|
3196
|
+
if (!Prefs.bytestring) return false;
|
3197
|
+
|
3198
|
+
for (let i = 0; i < str.length; i++) {
|
3199
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3200
|
+
}
|
3201
|
+
|
3202
|
+
return true;
|
3203
|
+
};
|
3204
|
+
|
3205
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2651
3206
|
const rawElements = new Array(str.length);
|
3207
|
+
let byteStringable = Prefs.bytestring;
|
2652
3208
|
for (let i = 0; i < str.length; i++) {
|
2653
|
-
|
3209
|
+
const c = str.charCodeAt(i);
|
3210
|
+
rawElements[i] = c;
|
3211
|
+
|
3212
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2654
3213
|
}
|
2655
3214
|
|
3215
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3216
|
+
|
2656
3217
|
return makeArray(scope, {
|
2657
3218
|
rawElements
|
2658
|
-
}, global, name, false, 'i16')[0];
|
3219
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2659
3220
|
};
|
2660
3221
|
|
2661
|
-
let arrays = new Map();
|
2662
3222
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2663
3223
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2664
3224
|
};
|
2665
3225
|
|
2666
3226
|
export const generateMember = (scope, decl, _global, _name) => {
|
2667
3227
|
const name = decl.object.name;
|
2668
|
-
const pointer = arrays
|
3228
|
+
const pointer = scope.arrays?.get(name);
|
2669
3229
|
|
2670
|
-
const aotPointer = pointer != null;
|
3230
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2671
3231
|
|
2672
3232
|
// hack: .length
|
2673
3233
|
if (decl.property.name === 'length') {
|
2674
|
-
|
3234
|
+
const func = funcs.find(x => x.name === name);
|
3235
|
+
if (func) {
|
3236
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3237
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3238
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3239
|
+
}
|
3240
|
+
|
3241
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3242
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3243
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3244
|
+
|
2675
3245
|
return [
|
2676
3246
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2677
3247
|
...generate(scope, decl.object),
|
@@ -2683,19 +3253,22 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2683
3253
|
];
|
2684
3254
|
}
|
2685
3255
|
|
3256
|
+
const object = generate(scope, decl.object);
|
3257
|
+
const property = generate(scope, decl.property);
|
3258
|
+
|
2686
3259
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2687
3260
|
// hack: this is naughty and will break things!
|
2688
3261
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2689
|
-
if (pages.
|
3262
|
+
if (pages.hasAnyString) {
|
2690
3263
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2691
3264
|
rawElements: new Array(1)
|
2692
3265
|
}, _global, _name, true, 'i16');
|
2693
3266
|
}
|
2694
3267
|
|
2695
3268
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2696
|
-
[TYPES.
|
3269
|
+
[TYPES.array]: [
|
2697
3270
|
// get index as valtype
|
2698
|
-
...
|
3271
|
+
...property,
|
2699
3272
|
|
2700
3273
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2701
3274
|
Opcodes.i32_to_u,
|
@@ -2703,7 +3276,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2703
3276
|
[ Opcodes.i32_mul ],
|
2704
3277
|
|
2705
3278
|
...(aotPointer ? [] : [
|
2706
|
-
...
|
3279
|
+
...object,
|
2707
3280
|
Opcodes.i32_to_u,
|
2708
3281
|
[ Opcodes.i32_add ]
|
2709
3282
|
]),
|
@@ -2712,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2712
3285
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2713
3286
|
|
2714
3287
|
...number(TYPES.number, Valtype.i32),
|
2715
|
-
setLastType(scope)
|
3288
|
+
...setLastType(scope)
|
2716
3289
|
],
|
2717
3290
|
|
2718
3291
|
[TYPES.string]: [
|
@@ -2722,14 +3295,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
3295
|
|
2723
3296
|
...number(0, Valtype.i32), // base 0 for store later
|
2724
3297
|
|
2725
|
-
...
|
2726
|
-
|
3298
|
+
...property,
|
2727
3299
|
Opcodes.i32_to_u,
|
3300
|
+
|
2728
3301
|
...number(ValtypeSize.i16, Valtype.i32),
|
2729
3302
|
[ Opcodes.i32_mul ],
|
2730
3303
|
|
2731
3304
|
...(aotPointer ? [] : [
|
2732
|
-
...
|
3305
|
+
...object,
|
2733
3306
|
Opcodes.i32_to_u,
|
2734
3307
|
[ Opcodes.i32_add ]
|
2735
3308
|
]),
|
@@ -2744,10 +3317,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2744
3317
|
...number(newPointer),
|
2745
3318
|
|
2746
3319
|
...number(TYPES.string, Valtype.i32),
|
2747
|
-
setLastType(scope)
|
3320
|
+
...setLastType(scope)
|
2748
3321
|
],
|
3322
|
+
[TYPES.bytestring]: [
|
3323
|
+
// setup new/out array
|
3324
|
+
...newOut,
|
3325
|
+
[ Opcodes.drop ],
|
3326
|
+
|
3327
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2749
3328
|
|
2750
|
-
|
3329
|
+
...property,
|
3330
|
+
Opcodes.i32_to_u,
|
3331
|
+
|
3332
|
+
...(aotPointer ? [] : [
|
3333
|
+
...object,
|
3334
|
+
Opcodes.i32_to_u,
|
3335
|
+
[ Opcodes.i32_add ]
|
3336
|
+
]),
|
3337
|
+
|
3338
|
+
// load current string ind {arg}
|
3339
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3340
|
+
|
3341
|
+
// store to new string ind 0
|
3342
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3343
|
+
|
3344
|
+
// return new string (page)
|
3345
|
+
...number(newPointer),
|
3346
|
+
|
3347
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3348
|
+
...setLastType(scope)
|
3349
|
+
],
|
3350
|
+
|
3351
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2751
3352
|
});
|
2752
3353
|
};
|
2753
3354
|
|
@@ -2757,25 +3358,36 @@ const objectHack = node => {
|
|
2757
3358
|
if (!node) return node;
|
2758
3359
|
|
2759
3360
|
if (node.type === 'MemberExpression') {
|
2760
|
-
|
3361
|
+
const out = (() => {
|
3362
|
+
if (node.computed || node.optional) return;
|
3363
|
+
|
3364
|
+
let objectName = node.object.name;
|
2761
3365
|
|
2762
|
-
|
3366
|
+
// if object is not identifier or another member exp, give up
|
3367
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3368
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2763
3369
|
|
2764
|
-
|
2765
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3370
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2766
3371
|
|
2767
|
-
|
3372
|
+
// if .length, give up (hack within a hack!)
|
3373
|
+
if (node.property.name === 'length') {
|
3374
|
+
node.object = objectHack(node.object);
|
3375
|
+
return;
|
3376
|
+
}
|
2768
3377
|
|
2769
|
-
|
2770
|
-
|
3378
|
+
// no object name, give up
|
3379
|
+
if (!objectName) return;
|
2771
3380
|
|
2772
|
-
|
2773
|
-
|
3381
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3382
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2774
3383
|
|
2775
|
-
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
3384
|
+
return {
|
3385
|
+
type: 'Identifier',
|
3386
|
+
name
|
3387
|
+
};
|
3388
|
+
})();
|
3389
|
+
|
3390
|
+
if (out) return out;
|
2779
3391
|
}
|
2780
3392
|
|
2781
3393
|
for (const x in node) {
|
@@ -2789,8 +3401,8 @@ const objectHack = node => {
|
|
2789
3401
|
};
|
2790
3402
|
|
2791
3403
|
const generateFunc = (scope, decl) => {
|
2792
|
-
if (decl.async) return todo('async functions are not supported');
|
2793
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3404
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3405
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2794
3406
|
|
2795
3407
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2796
3408
|
const params = decl.params ?? [];
|
@@ -2806,6 +3418,11 @@ const generateFunc = (scope, decl) => {
|
|
2806
3418
|
name
|
2807
3419
|
};
|
2808
3420
|
|
3421
|
+
if (typedInput && decl.returnType) {
|
3422
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3423
|
+
innerScope.returns = [ valtypeBinary ];
|
3424
|
+
}
|
3425
|
+
|
2809
3426
|
for (let i = 0; i < params.length; i++) {
|
2810
3427
|
allocVar(innerScope, params[i].name, false);
|
2811
3428
|
|
@@ -2827,13 +3444,13 @@ const generateFunc = (scope, decl) => {
|
|
2827
3444
|
const func = {
|
2828
3445
|
name,
|
2829
3446
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2830
|
-
|
2831
|
-
|
2832
|
-
throws: innerScope.throws,
|
2833
|
-
index: currentFuncIndex++
|
3447
|
+
index: currentFuncIndex++,
|
3448
|
+
...innerScope
|
2834
3449
|
};
|
2835
3450
|
funcIndex[name] = func.index;
|
2836
3451
|
|
3452
|
+
if (name === 'main') func.gotLastType = true;
|
3453
|
+
|
2837
3454
|
// quick hack fixes
|
2838
3455
|
for (const inst of wasm) {
|
2839
3456
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2885,7 +3502,7 @@ const internalConstrs = {
|
|
2885
3502
|
|
2886
3503
|
// todo: check in wasm instead of here
|
2887
3504
|
const literalValue = arg.value ?? 0;
|
2888
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3505
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2889
3506
|
|
2890
3507
|
return [
|
2891
3508
|
...number(0, Valtype.i32),
|
@@ -2896,7 +3513,8 @@ const internalConstrs = {
|
|
2896
3513
|
...number(pointer)
|
2897
3514
|
];
|
2898
3515
|
},
|
2899
|
-
type: TYPES.
|
3516
|
+
type: TYPES.array,
|
3517
|
+
length: 1
|
2900
3518
|
},
|
2901
3519
|
|
2902
3520
|
__Array_of: {
|
@@ -2907,8 +3525,132 @@ const internalConstrs = {
|
|
2907
3525
|
elements: decl.arguments
|
2908
3526
|
}, global, name);
|
2909
3527
|
},
|
2910
|
-
type: TYPES.
|
3528
|
+
type: TYPES.array,
|
3529
|
+
notConstr: true,
|
3530
|
+
length: 0
|
3531
|
+
},
|
3532
|
+
|
3533
|
+
__Porffor_fastOr: {
|
3534
|
+
generate: (scope, decl) => {
|
3535
|
+
const out = [];
|
3536
|
+
|
3537
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3538
|
+
out.push(
|
3539
|
+
...generate(scope, decl.arguments[i]),
|
3540
|
+
Opcodes.i32_to_u,
|
3541
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3542
|
+
);
|
3543
|
+
}
|
3544
|
+
|
3545
|
+
out.push(Opcodes.i32_from_u);
|
3546
|
+
|
3547
|
+
return out;
|
3548
|
+
},
|
3549
|
+
type: TYPES.boolean,
|
2911
3550
|
notConstr: true
|
3551
|
+
},
|
3552
|
+
|
3553
|
+
__Porffor_fastAnd: {
|
3554
|
+
generate: (scope, decl) => {
|
3555
|
+
const out = [];
|
3556
|
+
|
3557
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3558
|
+
out.push(
|
3559
|
+
...generate(scope, decl.arguments[i]),
|
3560
|
+
Opcodes.i32_to_u,
|
3561
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3562
|
+
);
|
3563
|
+
}
|
3564
|
+
|
3565
|
+
out.push(Opcodes.i32_from_u);
|
3566
|
+
|
3567
|
+
return out;
|
3568
|
+
},
|
3569
|
+
type: TYPES.boolean,
|
3570
|
+
notConstr: true
|
3571
|
+
},
|
3572
|
+
|
3573
|
+
Boolean: {
|
3574
|
+
generate: (scope, decl) => {
|
3575
|
+
// todo: boolean object when used as constructor
|
3576
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3577
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3578
|
+
},
|
3579
|
+
type: TYPES.boolean,
|
3580
|
+
length: 1
|
3581
|
+
},
|
3582
|
+
|
3583
|
+
__Math_max: {
|
3584
|
+
generate: (scope, decl) => {
|
3585
|
+
const out = [
|
3586
|
+
...number(-Infinity)
|
3587
|
+
];
|
3588
|
+
|
3589
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3590
|
+
out.push(
|
3591
|
+
...generate(scope, decl.arguments[i]),
|
3592
|
+
[ Opcodes.f64_max ]
|
3593
|
+
);
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
return out;
|
3597
|
+
},
|
3598
|
+
type: TYPES.number,
|
3599
|
+
notConstr: true,
|
3600
|
+
length: 2
|
3601
|
+
},
|
3602
|
+
|
3603
|
+
__Math_min: {
|
3604
|
+
generate: (scope, decl) => {
|
3605
|
+
const out = [
|
3606
|
+
...number(Infinity)
|
3607
|
+
];
|
3608
|
+
|
3609
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3610
|
+
out.push(
|
3611
|
+
...generate(scope, decl.arguments[i]),
|
3612
|
+
[ Opcodes.f64_min ]
|
3613
|
+
);
|
3614
|
+
}
|
3615
|
+
|
3616
|
+
return out;
|
3617
|
+
},
|
3618
|
+
type: TYPES.number,
|
3619
|
+
notConstr: true,
|
3620
|
+
length: 2
|
3621
|
+
},
|
3622
|
+
|
3623
|
+
__console_log: {
|
3624
|
+
generate: (scope, decl) => {
|
3625
|
+
const out = [];
|
3626
|
+
|
3627
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3628
|
+
out.push(
|
3629
|
+
...generateCall(scope, {
|
3630
|
+
callee: {
|
3631
|
+
type: 'Identifier',
|
3632
|
+
name: '__Porffor_print'
|
3633
|
+
},
|
3634
|
+
arguments: [ decl.arguments[i] ]
|
3635
|
+
}),
|
3636
|
+
|
3637
|
+
// print space
|
3638
|
+
...number(32),
|
3639
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3640
|
+
);
|
3641
|
+
}
|
3642
|
+
|
3643
|
+
// print newline
|
3644
|
+
out.push(
|
3645
|
+
...number(10),
|
3646
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3647
|
+
);
|
3648
|
+
|
3649
|
+
return out;
|
3650
|
+
},
|
3651
|
+
type: TYPES.undefined,
|
3652
|
+
notConstr: true,
|
3653
|
+
length: 0
|
2912
3654
|
}
|
2913
3655
|
};
|
2914
3656
|
|
@@ -2937,20 +3679,23 @@ export default program => {
|
|
2937
3679
|
funcs = [];
|
2938
3680
|
funcIndex = {};
|
2939
3681
|
depth = [];
|
2940
|
-
arrays = new Map();
|
2941
3682
|
pages = new Map();
|
2942
3683
|
data = [];
|
2943
3684
|
currentFuncIndex = importedFuncs.length;
|
2944
3685
|
|
2945
3686
|
globalThis.valtype = 'f64';
|
2946
3687
|
|
2947
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3688
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2948
3689
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2949
3690
|
|
2950
3691
|
globalThis.valtypeBinary = Valtype[valtype];
|
2951
3692
|
|
2952
3693
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2953
3694
|
|
3695
|
+
globalThis.pageSize = PageSize;
|
3696
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3697
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3698
|
+
|
2954
3699
|
// set generic opcodes for current valtype
|
2955
3700
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2956
3701
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2959,10 +3704,10 @@ export default program => {
|
|
2959
3704
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2960
3705
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2961
3706
|
|
2962
|
-
Opcodes.i32_to = [ [
|
2963
|
-
Opcodes.i32_to_u = [ [
|
2964
|
-
Opcodes.i32_from = [ [
|
2965
|
-
Opcodes.i32_from_u = [ [
|
3707
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3708
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3709
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3710
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2966
3711
|
|
2967
3712
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2968
3713
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2975,10 +3720,6 @@ export default program => {
|
|
2975
3720
|
|
2976
3721
|
program.id = { name: 'main' };
|
2977
3722
|
|
2978
|
-
globalThis.pageSize = PageSize;
|
2979
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2980
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2981
|
-
|
2982
3723
|
const scope = {
|
2983
3724
|
locals: {},
|
2984
3725
|
localInd: 0
|
@@ -2989,7 +3730,7 @@ export default program => {
|
|
2989
3730
|
body: program.body
|
2990
3731
|
};
|
2991
3732
|
|
2992
|
-
if (
|
3733
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2993
3734
|
|
2994
3735
|
generateFunc(scope, program);
|
2995
3736
|
|
@@ -3006,7 +3747,11 @@ export default program => {
|
|
3006
3747
|
}
|
3007
3748
|
|
3008
3749
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3009
|
-
|
3750
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3751
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3752
|
+
} else {
|
3753
|
+
main.returns = [];
|
3754
|
+
}
|
3010
3755
|
}
|
3011
3756
|
|
3012
3757
|
if (lastInst[0] === Opcodes.call) {
|