porffor 0.2.0-fde989a → 0.14.0-4057a18e9
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 +256 -0
- package/LICENSE +20 -20
- package/README.md +131 -86
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +216 -0
- package/compiler/2c.js +2 -53
- package/compiler/{sections.js → assemble.js} +84 -21
- 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/boolean.ts +18 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2067 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/function.ts +5 -0
- package/compiler/builtins/int.ts +145 -0
- package/compiler/builtins/number.ts +529 -0
- package/compiler/builtins/object.ts +4 -0
- package/compiler/builtins/porffor.d.ts +60 -0
- package/compiler/builtins/set.ts +187 -0
- package/compiler/builtins/string.ts +1080 -0
- package/compiler/builtins.js +436 -283
- package/compiler/{codeGen.js → codegen.js} +1027 -482
- package/compiler/decompile.js +2 -3
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +94 -10
- package/compiler/expression.js +1 -1
- package/compiler/generated_builtins.js +1625 -0
- package/compiler/index.js +25 -36
- package/compiler/log.js +6 -3
- package/compiler/opt.js +55 -41
- package/compiler/parse.js +38 -30
- package/compiler/precompile.js +120 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +31 -46
- package/compiler/types.js +38 -0
- package/compiler/wasmSpec.js +33 -8
- package/compiler/wrap.js +88 -70
- package/package.json +9 -5
- package/porf +2 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +33 -34
- package/runner/debug.js +117 -0
- package/runner/index.js +78 -11
- package/runner/profiler.js +75 -0
- package/runner/repl.js +40 -13
- package/runner/sizes.js +37 -37
- package/runner/version.js +10 -8
- package/compiler/builtins/base64.js +0 -92
- package/filesize.cmd +0 -2
- 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/tmp.c +0 -661
- package/util/enum.js +0 -20
@@ -1,12 +1,14 @@
|
|
1
|
-
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from
|
3
|
-
import { operatorOpcode } from
|
4
|
-
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from
|
5
|
-
import { PrototypeFuncs } from
|
6
|
-
import { number
|
7
|
-
import {
|
8
|
-
import
|
9
|
-
import
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
|
3
|
+
import { operatorOpcode } from './expression.js';
|
4
|
+
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
|
5
|
+
import { PrototypeFuncs } from './prototype.js';
|
6
|
+
import { number } from './embedding.js';
|
7
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
8
|
+
import * as Rhemyn from '../rhemyn/compile.js';
|
9
|
+
import parse from './parse.js';
|
10
|
+
import { log } from './log.js';
|
11
|
+
import Prefs from './prefs.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -17,44 +19,32 @@ let funcIndex = {};
|
|
17
19
|
let currentFuncIndex = importedFuncs.length;
|
18
20
|
let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
code.push(...number(n));
|
25
|
-
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
-
};
|
29
|
-
|
30
|
-
for (let i = 0; i < str.length; i++) {
|
31
|
-
logChar(str.charCodeAt(i));
|
22
|
+
class TodoError extends Error {
|
23
|
+
constructor(message) {
|
24
|
+
super(message);
|
25
|
+
this.name = 'TodoError';
|
32
26
|
}
|
27
|
+
}
|
28
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
29
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
30
|
+
case 'compile':
|
31
|
+
throw new TodoError(msg);
|
33
32
|
|
34
|
-
|
33
|
+
case 'runtime':
|
34
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
35
35
|
|
36
|
-
|
37
|
-
}
|
38
|
-
|
39
|
-
|
40
|
-
class TodoError extends Error {
|
41
|
-
constructor(message) {
|
42
|
-
super(message);
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
36
|
+
// return [
|
37
|
+
// ...debug(`todo! ${msg}`),
|
38
|
+
// [ Opcodes.unreachable ]
|
39
|
+
// ];
|
45
40
|
}
|
46
|
-
|
47
|
-
throw new TodoError(`todo: ${msg}`);
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
53
|
-
|
54
|
-
return code;
|
55
41
|
};
|
56
42
|
|
57
43
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
44
|
+
const hasFuncWithName = name => {
|
45
|
+
const func = funcs.find(x => x.name === name);
|
46
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
47
|
+
};
|
58
48
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
49
|
switch (decl.type) {
|
60
50
|
case 'BinaryExpression':
|
@@ -104,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
94
|
return generateUnary(scope, decl);
|
105
95
|
|
106
96
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
97
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
98
|
|
109
99
|
case 'IfStatement':
|
110
100
|
return generateIf(scope, decl);
|
@@ -115,6 +105,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
105
|
case 'WhileStatement':
|
116
106
|
return generateWhile(scope, decl);
|
117
107
|
|
108
|
+
case 'DoWhileStatement':
|
109
|
+
return generateDoWhile(scope, decl);
|
110
|
+
|
118
111
|
case 'ForOfStatement':
|
119
112
|
return generateForOf(scope, decl);
|
120
113
|
|
@@ -124,6 +117,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
117
|
case 'ContinueStatement':
|
125
118
|
return generateContinue(scope, decl);
|
126
119
|
|
120
|
+
case 'LabeledStatement':
|
121
|
+
return generateLabel(scope, decl);
|
122
|
+
|
127
123
|
case 'EmptyStatement':
|
128
124
|
return generateEmpty(scope, decl);
|
129
125
|
|
@@ -137,7 +133,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
133
|
return generateTry(scope, decl);
|
138
134
|
|
139
135
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
136
|
+
// todo: hook into terminal debugger
|
141
137
|
return [];
|
142
138
|
|
143
139
|
case 'ArrayExpression':
|
@@ -151,16 +147,17 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
147
|
const funcsBefore = funcs.length;
|
152
148
|
generate(scope, decl.declaration);
|
153
149
|
|
154
|
-
if (funcsBefore
|
155
|
-
|
156
|
-
|
157
|
-
|
150
|
+
if (funcsBefore !== funcs.length) {
|
151
|
+
// new func added
|
152
|
+
const newFunc = funcs[funcs.length - 1];
|
153
|
+
newFunc.export = true;
|
154
|
+
}
|
158
155
|
|
159
156
|
return [];
|
160
157
|
|
161
158
|
case 'TaggedTemplateExpression': {
|
162
159
|
const funcs = {
|
163
|
-
|
160
|
+
__Porffor_wasm: str => {
|
164
161
|
let out = [];
|
165
162
|
|
166
163
|
for (const line of str.split('\n')) {
|
@@ -168,8 +165,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
165
|
if (asm[0] === '') continue; // blank
|
169
166
|
|
170
167
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
168
|
+
const [ name, type ] = asm.slice(1);
|
169
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
170
|
continue;
|
174
171
|
}
|
175
172
|
|
@@ -179,52 +176,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
176
|
}
|
180
177
|
|
181
178
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
179
|
+
allocPage(scope, 'asm instrinsic');
|
183
180
|
// todo: add to store/load offset insts
|
184
181
|
continue;
|
185
182
|
}
|
186
183
|
|
187
184
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
185
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
186
|
|
190
187
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
const immediates = asm.slice(1).map(x =>
|
188
|
+
const immediates = asm.slice(1).map(x => {
|
189
|
+
const int = parseInt(x);
|
190
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
191
|
+
return int;
|
192
|
+
});
|
192
193
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
194
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
195
|
}
|
195
196
|
|
196
197
|
return out;
|
197
198
|
},
|
198
199
|
|
199
200
|
__Porffor_bs: str => [
|
200
|
-
...makeString(scope, str,
|
201
|
+
...makeString(scope, str, global, name, true),
|
201
202
|
|
202
|
-
...
|
203
|
-
|
203
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
204
|
+
...number(TYPES.bytestring, Valtype.i32),
|
205
|
+
...setLastType(scope)
|
206
|
+
])
|
204
207
|
],
|
205
208
|
__Porffor_s: str => [
|
206
|
-
...makeString(scope, str,
|
209
|
+
...makeString(scope, str, global, name, false),
|
207
210
|
|
208
|
-
...
|
209
|
-
|
211
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
212
|
+
...number(TYPES.string, Valtype.i32),
|
213
|
+
...setLastType(scope)
|
214
|
+
])
|
210
215
|
],
|
211
216
|
};
|
212
217
|
|
213
|
-
const
|
218
|
+
const func = decl.tag.name;
|
214
219
|
// hack for inline asm
|
215
|
-
if (!funcs[
|
220
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
221
|
+
|
222
|
+
const { quasis, expressions } = decl.quasi;
|
223
|
+
let str = quasis[0].value.raw;
|
224
|
+
|
225
|
+
for (let i = 0; i < expressions.length; i++) {
|
226
|
+
const e = expressions[i];
|
227
|
+
if (!e.name) {
|
228
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
229
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
230
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
231
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
216
232
|
|
217
|
-
|
218
|
-
|
233
|
+
str += quasis[i + 1].value.raw;
|
234
|
+
}
|
235
|
+
|
236
|
+
return funcs[func](str);
|
219
237
|
}
|
220
238
|
|
221
239
|
default:
|
222
|
-
|
223
|
-
|
240
|
+
// ignore typescript nodes
|
241
|
+
if (decl.type.startsWith('TS') ||
|
242
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
243
|
return [];
|
225
244
|
}
|
226
245
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
246
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
247
|
}
|
229
248
|
};
|
230
249
|
|
@@ -252,7 +271,7 @@ const lookupName = (scope, _name) => {
|
|
252
271
|
return [ undefined, undefined ];
|
253
272
|
};
|
254
273
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
274
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
275
|
...generateThrow(scope, {
|
257
276
|
argument: {
|
258
277
|
type: 'NewExpression',
|
@@ -276,7 +295,10 @@ const generateIdent = (scope, decl) => {
|
|
276
295
|
|
277
296
|
if (Object.hasOwn(builtinVars, name)) {
|
278
297
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
298
|
+
|
299
|
+
let wasm = builtinVars[name];
|
300
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
301
|
+
return wasm.slice();
|
280
302
|
}
|
281
303
|
|
282
304
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -284,6 +306,11 @@ const generateIdent = (scope, decl) => {
|
|
284
306
|
return number(1);
|
285
307
|
}
|
286
308
|
|
309
|
+
if (isExistingProtoFunc(name)) {
|
310
|
+
// todo: return an actual something
|
311
|
+
return number(1);
|
312
|
+
}
|
313
|
+
|
287
314
|
if (local?.idx === undefined) {
|
288
315
|
// no local var with name
|
289
316
|
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
@@ -314,14 +341,18 @@ const generateReturn = (scope, decl) => {
|
|
314
341
|
// just bare "return"
|
315
342
|
return [
|
316
343
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
344
|
+
...(scope.returnType != null ? [] : [
|
345
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
346
|
+
]),
|
318
347
|
[ Opcodes.return ]
|
319
348
|
];
|
320
349
|
}
|
321
350
|
|
322
351
|
return [
|
323
352
|
...generate(scope, decl.argument),
|
324
|
-
...
|
353
|
+
...(scope.returnType != null ? [] : [
|
354
|
+
...getNodeType(scope, decl.argument)
|
355
|
+
]),
|
325
356
|
[ Opcodes.return ]
|
326
357
|
];
|
327
358
|
};
|
@@ -335,7 +366,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
366
|
return idx;
|
336
367
|
};
|
337
368
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
369
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
370
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
371
|
|
340
372
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
373
|
const checks = {
|
@@ -344,7 +376,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
376
|
'??': nullish
|
345
377
|
};
|
346
378
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
379
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
380
|
|
349
381
|
// generic structure for {a} OP {b}
|
350
382
|
// -->
|
@@ -352,8 +384,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
384
|
|
353
385
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
386
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
387
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
388
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
389
|
|
358
390
|
const canInt = leftIsInt && rightIsInt;
|
359
391
|
|
@@ -370,12 +402,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
402
|
...right,
|
371
403
|
// note type
|
372
404
|
...rightType,
|
373
|
-
setLastType(scope),
|
405
|
+
...setLastType(scope),
|
374
406
|
[ Opcodes.else ],
|
375
407
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
408
|
// note type
|
377
409
|
...leftType,
|
378
|
-
setLastType(scope),
|
410
|
+
...setLastType(scope),
|
379
411
|
[ Opcodes.end ],
|
380
412
|
Opcodes.i32_from
|
381
413
|
];
|
@@ -389,17 +421,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
421
|
...right,
|
390
422
|
// note type
|
391
423
|
...rightType,
|
392
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
393
425
|
[ Opcodes.else ],
|
394
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
427
|
// note type
|
396
428
|
...leftType,
|
397
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
398
430
|
[ Opcodes.end ]
|
399
431
|
];
|
400
432
|
};
|
401
433
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
434
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
435
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
436
|
// todo: convert left and right to strings if not
|
405
437
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +441,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
441
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
442
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
443
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
444
|
+
if (assign && Prefs.aotPointerOpt) {
|
445
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
446
|
|
418
447
|
return [
|
419
448
|
// setup right
|
@@ -438,11 +467,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
467
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
468
|
|
440
469
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
470
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
471
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
472
|
|
444
473
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
474
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
475
|
[ Opcodes.i32_mul ],
|
447
476
|
[ Opcodes.i32_add ],
|
448
477
|
|
@@ -451,9 +480,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
480
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
481
|
[ Opcodes.i32_add ],
|
453
482
|
|
454
|
-
// size = right length *
|
483
|
+
// size = right length * sizeof valtype
|
455
484
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
485
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
486
|
[ Opcodes.i32_mul ],
|
458
487
|
|
459
488
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -511,11 +540,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
540
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
541
|
|
513
542
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
543
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
544
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
545
|
|
517
546
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
547
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
548
|
[ Opcodes.i32_mul ],
|
520
549
|
[ Opcodes.i32_add ],
|
521
550
|
|
@@ -524,9 +553,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
553
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
554
|
[ Opcodes.i32_add ],
|
526
555
|
|
527
|
-
// size = right length *
|
556
|
+
// size = right length * sizeof valtype
|
528
557
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
558
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
559
|
[ Opcodes.i32_mul ],
|
531
560
|
|
532
561
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +565,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
565
|
];
|
537
566
|
};
|
538
567
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
568
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
569
|
// todo: this should be rewritten into a func
|
541
570
|
// todo: convert left and right to strings if not
|
542
571
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +574,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
574
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
575
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
576
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
577
|
|
550
578
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
579
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -573,7 +601,6 @@ const compareStrings = (scope, left, right) => {
|
|
573
601
|
|
574
602
|
[ Opcodes.local_get, rightPointer ],
|
575
603
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
577
604
|
|
578
605
|
// fast path: check leftLength != rightLength
|
579
606
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +615,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
615
|
...number(0, Valtype.i32),
|
589
616
|
[ Opcodes.local_set, index ],
|
590
617
|
|
591
|
-
// setup index end as length * sizeof
|
618
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
619
|
// we do this instead of having to do mul/div each iter for perf™
|
593
620
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
621
|
+
...(bytestrings ? [] : [
|
622
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
623
|
+
[ Opcodes.i32_mul ],
|
624
|
+
]),
|
596
625
|
[ Opcodes.local_set, indexEnd ],
|
597
626
|
|
598
627
|
// iterate over each char and check if eq
|
@@ -602,13 +631,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
631
|
[ Opcodes.local_get, index ],
|
603
632
|
[ Opcodes.local_get, leftPointer ],
|
604
633
|
[ Opcodes.i32_add ],
|
605
|
-
|
634
|
+
bytestrings ?
|
635
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
636
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
637
|
|
607
638
|
// fetch right
|
608
639
|
[ Opcodes.local_get, index ],
|
609
640
|
[ Opcodes.local_get, rightPointer ],
|
610
641
|
[ Opcodes.i32_add ],
|
611
|
-
|
642
|
+
bytestrings ?
|
643
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
644
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
645
|
|
613
646
|
// not equal, "return" false
|
614
647
|
[ Opcodes.i32_ne ],
|
@@ -617,13 +650,13 @@ const compareStrings = (scope, left, right) => {
|
|
617
650
|
[ Opcodes.br, 2 ],
|
618
651
|
[ Opcodes.end ],
|
619
652
|
|
620
|
-
// index += sizeof
|
653
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
654
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
655
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
656
|
[ Opcodes.i32_add ],
|
624
657
|
[ Opcodes.local_tee, index ],
|
625
658
|
|
626
|
-
// if index != index end (length * sizeof
|
659
|
+
// if index != index end (length * sizeof valtype), loop
|
627
660
|
[ Opcodes.local_get, indexEnd ],
|
628
661
|
[ Opcodes.i32_ne ],
|
629
662
|
[ Opcodes.br_if, 0 ],
|
@@ -644,16 +677,18 @@ const compareStrings = (scope, left, right) => {
|
|
644
677
|
};
|
645
678
|
|
646
679
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
680
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
648
681
|
...wasm,
|
649
682
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
683
|
];
|
684
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
685
|
|
652
|
-
const
|
686
|
+
const useTmp = knownType(scope, type) == null;
|
687
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
688
|
|
654
689
|
const def = [
|
655
690
|
// if value != 0
|
656
|
-
[ Opcodes.local_get, tmp ],
|
691
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
657
692
|
|
658
693
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
659
694
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -665,16 +700,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
665
700
|
|
666
701
|
return [
|
667
702
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
703
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
704
|
|
670
705
|
...typeSwitch(scope, type, {
|
671
706
|
// [TYPES.number]: def,
|
672
|
-
[TYPES.
|
707
|
+
[TYPES.array]: [
|
673
708
|
// arrays are always truthy
|
674
709
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
710
|
],
|
676
711
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
712
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
713
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
714
|
|
680
715
|
// get length
|
@@ -685,8 +720,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
720
|
[ Opcodes.i32_eqz ], */
|
686
721
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
722
|
],
|
688
|
-
[TYPES.
|
689
|
-
|
723
|
+
[TYPES.bytestring]: [ // duplicate of string
|
724
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
725
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
726
|
|
692
727
|
// get length
|
@@ -700,18 +735,20 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
735
|
};
|
701
736
|
|
702
737
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
738
|
+
const useTmp = knownType(scope, type) == null;
|
739
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
740
|
+
|
704
741
|
return [
|
705
742
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
744
|
|
708
745
|
...typeSwitch(scope, type, {
|
709
|
-
[TYPES.
|
746
|
+
[TYPES.array]: [
|
710
747
|
// arrays are always truthy
|
711
748
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
749
|
],
|
713
750
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
751
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
752
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
753
|
|
717
754
|
// get length
|
@@ -721,8 +758,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
721
758
|
[ Opcodes.i32_eqz ],
|
722
759
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
760
|
],
|
724
|
-
[TYPES.
|
725
|
-
[ Opcodes.local_get, tmp ],
|
761
|
+
[TYPES.bytestring]: [ // duplicate of string
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
763
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
764
|
|
728
765
|
// get length
|
@@ -734,7 +771,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
771
|
],
|
735
772
|
default: [
|
736
773
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
774
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
775
|
|
739
776
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
777
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +781,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
781
|
};
|
745
782
|
|
746
783
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
784
|
+
const useTmp = knownType(scope, type) == null;
|
785
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
786
|
+
|
748
787
|
return [
|
749
788
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
789
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
790
|
|
752
791
|
...typeSwitch(scope, type, {
|
753
792
|
[TYPES.undefined]: [
|
@@ -756,7 +795,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
795
|
],
|
757
796
|
[TYPES.object]: [
|
758
797
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
798
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
799
|
|
761
800
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
801
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,39 +824,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
824
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
825
|
}
|
787
826
|
|
827
|
+
const knownLeft = knownType(scope, leftType);
|
828
|
+
const knownRight = knownType(scope, rightType);
|
829
|
+
|
788
830
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
831
|
const strictOp = op === '===' || op === '!==';
|
790
832
|
|
791
833
|
const startOut = [], endOut = [];
|
792
|
-
const
|
834
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
835
|
|
794
836
|
// if strict (in)equal check types match
|
795
837
|
if (strictOp) {
|
796
|
-
// startOut.push(
|
797
|
-
// ...leftType,
|
798
|
-
// ...rightType,
|
799
|
-
// [ Opcodes.i32_eq ]
|
800
|
-
// );
|
801
|
-
|
802
|
-
// endOut.push(
|
803
|
-
// [ Opcodes.i32_and ]
|
804
|
-
// );
|
805
|
-
|
806
|
-
// startOut.push(
|
807
|
-
// [ Opcodes.block, Valtype.i32 ],
|
808
|
-
// ...leftType,
|
809
|
-
// ...rightType,
|
810
|
-
// [ Opcodes.i32_ne ],
|
811
|
-
// [ Opcodes.if, Blocktype.void ],
|
812
|
-
// ...number(op === '===' ? 0 : 1, Valtype.i32),
|
813
|
-
// [ Opcodes.br, 1 ],
|
814
|
-
// [ Opcodes.end ]
|
815
|
-
// );
|
816
|
-
|
817
|
-
// endOut.push(
|
818
|
-
// [ Opcodes.end ]
|
819
|
-
// );
|
820
|
-
|
821
838
|
endOut.push(
|
822
839
|
...leftType,
|
823
840
|
...rightType,
|
@@ -834,31 +851,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
851
|
// todo: if equality op and an operand is undefined, return false
|
835
852
|
// todo: niche null hell with 0
|
836
853
|
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
854
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
855
|
+
if (op === '+') {
|
856
|
+
// todo: this should be dynamic too but for now only static
|
857
|
+
// string concat (a + b)
|
858
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
859
|
+
}
|
860
|
+
|
861
|
+
// not an equality op, NaN
|
862
|
+
if (!eqOp) return number(NaN);
|
863
|
+
|
864
|
+
// else leave bool ops
|
865
|
+
// todo: convert string to number if string and number/bool
|
866
|
+
// todo: string (>|>=|<|<=) string
|
867
|
+
|
868
|
+
// string comparison
|
869
|
+
if (op === '===' || op === '==') {
|
870
|
+
return compareStrings(scope, left, right);
|
871
|
+
}
|
872
|
+
|
873
|
+
if (op === '!==' || op === '!=') {
|
874
|
+
return [
|
875
|
+
...compareStrings(scope, left, right),
|
876
|
+
[ Opcodes.i32_eqz ]
|
877
|
+
];
|
878
|
+
}
|
879
|
+
}
|
880
|
+
|
881
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
882
|
+
if (op === '+') {
|
883
|
+
// todo: this should be dynamic too but for now only static
|
884
|
+
// string concat (a + b)
|
885
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
886
|
+
}
|
887
|
+
|
888
|
+
// not an equality op, NaN
|
889
|
+
if (!eqOp) return number(NaN);
|
890
|
+
|
891
|
+
// else leave bool ops
|
892
|
+
// todo: convert string to number if string and number/bool
|
893
|
+
// todo: string (>|>=|<|<=) string
|
894
|
+
|
895
|
+
// string comparison
|
896
|
+
if (op === '===' || op === '==') {
|
897
|
+
return compareStrings(scope, left, right, true);
|
898
|
+
}
|
899
|
+
|
900
|
+
if (op === '!==' || op === '!=') {
|
901
|
+
return [
|
902
|
+
...compareStrings(scope, left, right, true),
|
903
|
+
[ Opcodes.i32_eqz ]
|
904
|
+
];
|
905
|
+
}
|
906
|
+
}
|
862
907
|
|
863
908
|
let ops = operatorOpcode[valtype][op];
|
864
909
|
|
@@ -868,33 +913,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
913
|
includeBuiltin(scope, builtinName);
|
869
914
|
const idx = funcIndex[builtinName];
|
870
915
|
|
871
|
-
return
|
916
|
+
return finalize([
|
872
917
|
...left,
|
873
918
|
...right,
|
874
919
|
[ Opcodes.call, idx ]
|
875
920
|
]);
|
876
921
|
}
|
877
922
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
923
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
924
|
|
880
925
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
926
|
ops = [ ops ];
|
882
927
|
|
883
928
|
let tmpLeft, tmpRight;
|
884
929
|
// if equal op, check if strings for compareStrings
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
// todo: intelligent partial skip later
|
890
|
-
// if neither known are string, stop this madness
|
891
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
892
|
-
return;
|
893
|
-
}
|
930
|
+
// todo: intelligent partial skip later
|
931
|
+
// if neither known are string, stop this madness
|
932
|
+
// we already do known checks earlier, so don't need to recheck
|
894
933
|
|
934
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
935
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
936
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
937
|
|
938
|
+
// returns false for one string, one not - but more ops/slower
|
939
|
+
// ops.unshift(...stringOnly([
|
940
|
+
// // if left is string
|
941
|
+
// ...leftType,
|
942
|
+
// ...number(TYPES.string, Valtype.i32),
|
943
|
+
// [ Opcodes.i32_eq ],
|
944
|
+
|
945
|
+
// // if right is string
|
946
|
+
// ...rightType,
|
947
|
+
// ...number(TYPES.string, Valtype.i32),
|
948
|
+
// [ Opcodes.i32_eq ],
|
949
|
+
|
950
|
+
// // if either are true
|
951
|
+
// [ Opcodes.i32_or ],
|
952
|
+
// [ Opcodes.if, Blocktype.void ],
|
953
|
+
|
954
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
955
|
+
// // if left is not string
|
956
|
+
// ...leftType,
|
957
|
+
// ...number(TYPES.string, Valtype.i32),
|
958
|
+
// [ Opcodes.i32_ne ],
|
959
|
+
|
960
|
+
// // if right is not string
|
961
|
+
// ...rightType,
|
962
|
+
// ...number(TYPES.string, Valtype.i32),
|
963
|
+
// [ Opcodes.i32_ne ],
|
964
|
+
|
965
|
+
// // if either are true
|
966
|
+
// [ Opcodes.i32_or ],
|
967
|
+
// [ Opcodes.if, Blocktype.void ],
|
968
|
+
// ...number(0, Valtype.i32),
|
969
|
+
// [ Opcodes.br, 2 ],
|
970
|
+
// [ Opcodes.end ],
|
971
|
+
|
972
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
973
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
974
|
+
// [ Opcodes.br, 1 ],
|
975
|
+
// [ Opcodes.end ],
|
976
|
+
// ]));
|
977
|
+
|
978
|
+
// does not handle one string, one not (such cases go past)
|
898
979
|
ops.unshift(...stringOnly([
|
899
980
|
// if left is string
|
900
981
|
...leftType,
|
@@ -906,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
987
|
...number(TYPES.string, Valtype.i32),
|
907
988
|
[ Opcodes.i32_eq ],
|
908
989
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
990
|
+
// if both are true
|
991
|
+
[ Opcodes.i32_and ],
|
911
992
|
[ Opcodes.if, Blocktype.void ],
|
993
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
994
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
995
|
+
[ Opcodes.br, 1 ],
|
996
|
+
[ Opcodes.end ],
|
912
997
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
998
|
+
// if left is bytestring
|
915
999
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
1000
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1001
|
+
[ Opcodes.i32_eq ],
|
918
1002
|
|
919
|
-
// if right is
|
1003
|
+
// if right is bytestring
|
920
1004
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1005
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1006
|
+
[ Opcodes.i32_eq ],
|
923
1007
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1008
|
+
// if both are true
|
1009
|
+
[ Opcodes.i32_and ],
|
926
1010
|
[ Opcodes.if, Blocktype.void ],
|
927
|
-
...
|
928
|
-
[ Opcodes.br, 2 ],
|
929
|
-
[ Opcodes.end ],
|
930
|
-
|
931
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
932
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1011
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1012
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1013
|
[ Opcodes.br, 1 ],
|
935
1014
|
[ Opcodes.end ],
|
@@ -941,9 +1020,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1020
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1021
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1022
|
// }
|
944
|
-
}
|
1023
|
+
}
|
945
1024
|
|
946
|
-
return
|
1025
|
+
return finalize([
|
947
1026
|
...left,
|
948
1027
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1028
|
...right,
|
@@ -960,7 +1039,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1039
|
return out;
|
961
1040
|
};
|
962
1041
|
|
963
|
-
const
|
1042
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1043
|
+
return func({ name, params, locals, returns, localInd }, {
|
1044
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1045
|
+
builtin: name => {
|
1046
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1047
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1048
|
+
includeBuiltin(null, name);
|
1049
|
+
idx = funcIndex[name];
|
1050
|
+
}
|
1051
|
+
|
1052
|
+
return idx;
|
1053
|
+
}
|
1054
|
+
});
|
1055
|
+
};
|
1056
|
+
|
1057
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
964
1058
|
const existing = funcs.find(x => x.name === name);
|
965
1059
|
if (existing) return existing;
|
966
1060
|
|
@@ -972,18 +1066,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1066
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1067
|
}
|
974
1068
|
|
975
|
-
|
976
|
-
const
|
977
|
-
|
978
|
-
|
979
|
-
locals,
|
980
|
-
returns,
|
981
|
-
localInd: allLocals.length,
|
982
|
-
};
|
983
|
-
|
984
|
-
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
1069
|
+
for (const x of _data) {
|
1070
|
+
const copy = { ...x };
|
1071
|
+
copy.offset += pages.size * pageSize;
|
1072
|
+
data.push(copy);
|
985
1073
|
}
|
986
1074
|
|
1075
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1076
|
+
|
987
1077
|
let baseGlobalIdx, i = 0;
|
988
1078
|
for (const type of globalTypes) {
|
989
1079
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1006,7 +1096,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1006
1096
|
params,
|
1007
1097
|
locals,
|
1008
1098
|
returns,
|
1009
|
-
returnType:
|
1099
|
+
returnType: returnType ?? TYPES.number,
|
1010
1100
|
wasm,
|
1011
1101
|
internal: true,
|
1012
1102
|
index: currentFuncIndex++
|
@@ -1029,6 +1119,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1119
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1120
|
};
|
1031
1121
|
|
1122
|
+
// potential future ideas for nan boxing (unused):
|
1032
1123
|
// T = JS type, V = value/pointer
|
1033
1124
|
// 0bTTT
|
1034
1125
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1042,7 +1133,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1042
1133
|
// js type: 4 bits
|
1043
1134
|
// internal type: ? bits
|
1044
1135
|
// pointer: 32 bits
|
1045
|
-
|
1046
1136
|
// generic
|
1047
1137
|
// 1 23 4 5
|
1048
1138
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1052,49 +1142,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1142
|
// 4: internal type
|
1053
1143
|
// 5: pointer
|
1054
1144
|
|
1055
|
-
const
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
object: 0x04,
|
1061
|
-
function: 0x05,
|
1062
|
-
symbol: 0x06,
|
1063
|
-
bigint: 0x07,
|
1064
|
-
|
1065
|
-
// these are not "typeof" types but tracked internally
|
1066
|
-
_array: 0x10,
|
1067
|
-
_regexp: 0x11,
|
1068
|
-
_bytestring: 0x12
|
1069
|
-
};
|
1070
|
-
|
1071
|
-
const TYPE_NAMES = {
|
1072
|
-
[TYPES.number]: 'Number',
|
1073
|
-
[TYPES.boolean]: 'Boolean',
|
1074
|
-
[TYPES.string]: 'String',
|
1075
|
-
[TYPES.undefined]: 'undefined',
|
1076
|
-
[TYPES.object]: 'Object',
|
1077
|
-
[TYPES.function]: 'Function',
|
1078
|
-
[TYPES.symbol]: 'Symbol',
|
1079
|
-
[TYPES.bigint]: 'BigInt',
|
1080
|
-
|
1081
|
-
[TYPES._array]: 'Array',
|
1082
|
-
[TYPES._regexp]: 'RegExp',
|
1083
|
-
[TYPES._bytestring]: 'ByteString'
|
1145
|
+
const isExistingProtoFunc = name => {
|
1146
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1147
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1148
|
+
|
1149
|
+
return false;
|
1084
1150
|
};
|
1085
1151
|
|
1086
1152
|
const getType = (scope, _name) => {
|
1087
1153
|
const name = mapName(_name);
|
1088
1154
|
|
1155
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1156
|
+
|
1157
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1089
1158
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1159
|
+
|
1160
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1090
1161
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1162
|
|
1092
1163
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1164
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1165
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1166
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1167
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1168
|
|
1099
1169
|
return number(type, Valtype.i32);
|
1100
1170
|
};
|
@@ -1117,23 +1187,24 @@ const setType = (scope, _name, type) => {
|
|
1117
1187
|
];
|
1118
1188
|
|
1119
1189
|
// throw new Error('could not find var');
|
1190
|
+
return [];
|
1120
1191
|
};
|
1121
1192
|
|
1122
1193
|
const getLastType = scope => {
|
1123
1194
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1195
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1196
|
};
|
1126
1197
|
|
1127
1198
|
const setLastType = scope => {
|
1128
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1199
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1129
1200
|
};
|
1130
1201
|
|
1131
1202
|
const getNodeType = (scope, node) => {
|
1132
|
-
const
|
1203
|
+
const ret = (() => {
|
1133
1204
|
if (node.type === 'Literal') {
|
1134
|
-
if (node.regex) return TYPES.
|
1205
|
+
if (node.regex) return TYPES.regexp;
|
1135
1206
|
|
1136
|
-
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.
|
1207
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1137
1208
|
|
1138
1209
|
return TYPES[typeof node.value];
|
1139
1210
|
}
|
@@ -1150,21 +1221,32 @@ const getNodeType = (scope, node) => {
|
|
1150
1221
|
const name = node.callee.name;
|
1151
1222
|
if (!name) {
|
1152
1223
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1224
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1225
|
|
1155
1226
|
// presume
|
1156
1227
|
// todo: warn here?
|
1157
1228
|
return TYPES.number;
|
1158
1229
|
}
|
1159
1230
|
|
1231
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1232
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1233
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1234
|
+
|
1235
|
+
// presume
|
1236
|
+
// todo: warn here?
|
1237
|
+
return TYPES.number;
|
1238
|
+
}
|
1239
|
+
|
1240
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1241
|
+
}
|
1242
|
+
|
1160
1243
|
const func = funcs.find(x => x.name === name);
|
1161
1244
|
|
1162
1245
|
if (func) {
|
1163
|
-
// console.log(scope, func, func.returnType);
|
1164
1246
|
if (func.returnType) return func.returnType;
|
1165
1247
|
}
|
1166
1248
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1249
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1250
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1251
|
|
1170
1252
|
// check if this is a prototype function
|
@@ -1175,11 +1257,16 @@ const getNodeType = (scope, node) => {
|
|
1175
1257
|
const spl = name.slice(2).split('_');
|
1176
1258
|
|
1177
1259
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.
|
1260
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1179
1261
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
1262
|
}
|
1181
1263
|
|
1182
|
-
if (
|
1264
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1265
|
+
// todo: return undefined for non-returning ops
|
1266
|
+
return TYPES.number;
|
1267
|
+
}
|
1268
|
+
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1270
|
|
1184
1271
|
// presume
|
1185
1272
|
// todo: warn here?
|
@@ -1222,11 +1309,20 @@ const getNodeType = (scope, node) => {
|
|
1222
1309
|
}
|
1223
1310
|
|
1224
1311
|
if (node.type === 'ArrayExpression') {
|
1225
|
-
return TYPES.
|
1312
|
+
return TYPES.array;
|
1226
1313
|
}
|
1227
1314
|
|
1228
1315
|
if (node.type === 'BinaryExpression') {
|
1229
1316
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1317
|
+
if (node.operator !== '+') return TYPES.number;
|
1318
|
+
|
1319
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1320
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1321
|
+
|
1322
|
+
// todo: this should be dynamic but for now only static
|
1323
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1324
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1325
|
+
|
1230
1326
|
return TYPES.number;
|
1231
1327
|
|
1232
1328
|
// todo: string concat types
|
@@ -1251,34 +1347,47 @@ const getNodeType = (scope, node) => {
|
|
1251
1347
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1348
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1349
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1350
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1255
1351
|
|
1256
1352
|
return TYPES.number;
|
1257
1353
|
}
|
1258
1354
|
|
1259
1355
|
if (node.type === 'MemberExpression') {
|
1356
|
+
// hack: if something.name, string type
|
1357
|
+
if (node.property.name === 'name') {
|
1358
|
+
if (hasFuncWithName(node.object.name)) {
|
1359
|
+
return TYPES.bytestring;
|
1360
|
+
} else {
|
1361
|
+
return TYPES.undefined;
|
1362
|
+
}
|
1363
|
+
}
|
1364
|
+
|
1260
1365
|
// hack: if something.length, number type
|
1261
1366
|
if (node.property.name === 'length') return TYPES.number;
|
1262
1367
|
|
1263
1368
|
// ts hack
|
1264
1369
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1265
|
-
if (scope.locals[node.object.name]?.metadata?.type === TYPES.
|
1370
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1371
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1266
1372
|
|
1267
|
-
if (scope.locals['#last_type']) return
|
1373
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1374
|
|
1269
1375
|
// presume
|
1270
1376
|
return TYPES.number;
|
1271
1377
|
}
|
1272
1378
|
|
1273
|
-
if (
|
1379
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1380
|
+
// hack
|
1381
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1274
1385
|
|
1275
1386
|
// presume
|
1276
1387
|
// todo: warn here?
|
1277
1388
|
return TYPES.number;
|
1278
|
-
};
|
1389
|
+
})();
|
1279
1390
|
|
1280
|
-
const ret = inner();
|
1281
|
-
// console.trace(node, ret);
|
1282
1391
|
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1283
1392
|
return ret;
|
1284
1393
|
};
|
@@ -1303,7 +1412,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1303
1412
|
return makeString(scope, decl.value, global, name);
|
1304
1413
|
|
1305
1414
|
default:
|
1306
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1415
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1307
1416
|
}
|
1308
1417
|
};
|
1309
1418
|
|
@@ -1312,6 +1421,8 @@ const countLeftover = wasm => {
|
|
1312
1421
|
|
1313
1422
|
for (let i = 0; i < wasm.length; i++) {
|
1314
1423
|
const inst = wasm[i];
|
1424
|
+
if (inst[0] == null) continue;
|
1425
|
+
|
1315
1426
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1316
1427
|
if (inst[0] === Opcodes.if) count--;
|
1317
1428
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1320,18 +1431,25 @@ const countLeftover = wasm => {
|
|
1320
1431
|
if (inst[0] === Opcodes.end) depth--;
|
1321
1432
|
|
1322
1433
|
if (depth === 0)
|
1323
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1434
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1324
1435
|
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)) {}
|
1325
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1436
|
+
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++;
|
1326
1437
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1327
1438
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1328
1439
|
else if (inst[0] === Opcodes.return) count = 0;
|
1329
1440
|
else if (inst[0] === Opcodes.call) {
|
1330
1441
|
let func = funcs.find(x => x.index === inst[1]);
|
1331
|
-
if (
|
1332
|
-
count
|
1333
|
-
} else
|
1334
|
-
|
1442
|
+
if (inst[1] === -1) {
|
1443
|
+
// todo: count for calling self
|
1444
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1445
|
+
count -= importedFuncs[inst[1]].params;
|
1446
|
+
count += importedFuncs[inst[1]].returns;
|
1447
|
+
} else {
|
1448
|
+
if (func) {
|
1449
|
+
count -= func.params.length;
|
1450
|
+
} else count--;
|
1451
|
+
if (func) count += func.returns.length;
|
1452
|
+
}
|
1335
1453
|
} else count--;
|
1336
1454
|
|
1337
1455
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1408,25 +1526,27 @@ const RTArrayUtil = {
|
|
1408
1526
|
};
|
1409
1527
|
|
1410
1528
|
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1411
|
-
/* const callee = decl.callee;
|
1412
|
-
const args = decl.arguments;
|
1413
|
-
|
1414
|
-
return [
|
1415
|
-
...generate(args),
|
1416
|
-
...generate(callee),
|
1417
|
-
Opcodes.call_indirect,
|
1418
|
-
]; */
|
1419
|
-
|
1420
1529
|
let name = mapName(decl.callee.name);
|
1421
1530
|
if (isFuncType(decl.callee.type)) { // iife
|
1422
1531
|
const func = generateFunc(scope, decl.callee);
|
1423
1532
|
name = func.name;
|
1424
1533
|
}
|
1425
1534
|
|
1426
|
-
if (name === 'eval' && decl.arguments[0]
|
1535
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1427
1536
|
// literal eval hack
|
1428
|
-
const code = decl.arguments[0]
|
1429
|
-
|
1537
|
+
const code = decl.arguments[0]?.value ?? '';
|
1538
|
+
|
1539
|
+
let parsed;
|
1540
|
+
try {
|
1541
|
+
parsed = parse(code, []);
|
1542
|
+
} catch (e) {
|
1543
|
+
if (e.name === 'SyntaxError') {
|
1544
|
+
// throw syntax errors of evals at runtime instead
|
1545
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1546
|
+
}
|
1547
|
+
|
1548
|
+
throw e;
|
1549
|
+
}
|
1430
1550
|
|
1431
1551
|
const out = generate(scope, {
|
1432
1552
|
type: 'BlockStatement',
|
@@ -1440,13 +1560,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1440
1560
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1441
1561
|
out.push(
|
1442
1562
|
...getNodeType(scope, finalStatement),
|
1443
|
-
setLastType(scope)
|
1563
|
+
...setLastType(scope)
|
1444
1564
|
);
|
1445
1565
|
} else if (countLeftover(out) === 0) {
|
1446
1566
|
out.push(...number(UNDEFINED));
|
1447
1567
|
out.push(
|
1448
1568
|
...number(TYPES.undefined, Valtype.i32),
|
1449
|
-
setLastType(scope)
|
1569
|
+
...setLastType(scope)
|
1450
1570
|
);
|
1451
1571
|
}
|
1452
1572
|
|
@@ -1468,6 +1588,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1468
1588
|
|
1469
1589
|
target = { ...decl.callee };
|
1470
1590
|
target.name = spl.slice(0, -1).join('_');
|
1591
|
+
|
1592
|
+
// failed to lookup name, abort
|
1593
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1471
1594
|
}
|
1472
1595
|
|
1473
1596
|
// literal.func()
|
@@ -1475,22 +1598,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1475
1598
|
// megahack for /regex/.func()
|
1476
1599
|
const funcName = decl.callee.property.name;
|
1477
1600
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1478
|
-
const
|
1601
|
+
const regex = decl.callee.object.regex.pattern;
|
1602
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1479
1603
|
|
1480
|
-
funcIndex[
|
1481
|
-
|
1604
|
+
if (!funcIndex[rhemynName]) {
|
1605
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1482
1606
|
|
1607
|
+
funcIndex[func.name] = func.index;
|
1608
|
+
funcs.push(func);
|
1609
|
+
}
|
1610
|
+
|
1611
|
+
const idx = funcIndex[rhemynName];
|
1483
1612
|
return [
|
1484
1613
|
// make string arg
|
1485
1614
|
...generate(scope, decl.arguments[0]),
|
1615
|
+
Opcodes.i32_to_u,
|
1616
|
+
...getNodeType(scope, decl.arguments[0]),
|
1486
1617
|
|
1487
1618
|
// call regex func
|
1488
|
-
Opcodes.
|
1489
|
-
[ Opcodes.call, func.index ],
|
1619
|
+
[ Opcodes.call, idx ],
|
1490
1620
|
Opcodes.i32_from_u,
|
1491
1621
|
|
1492
1622
|
...number(TYPES.boolean, Valtype.i32),
|
1493
|
-
setLastType(scope)
|
1623
|
+
...setLastType(scope)
|
1494
1624
|
];
|
1495
1625
|
}
|
1496
1626
|
|
@@ -1515,12 +1645,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1645
|
// }
|
1516
1646
|
|
1517
1647
|
if (protoName) {
|
1648
|
+
const protoBC = {};
|
1649
|
+
|
1650
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1651
|
+
|
1652
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1653
|
+
for (const x of builtinProtoCands) {
|
1654
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1655
|
+
if (type == null) continue;
|
1656
|
+
|
1657
|
+
protoBC[type] = generateCall(scope, {
|
1658
|
+
callee: {
|
1659
|
+
type: 'Identifier',
|
1660
|
+
name: x
|
1661
|
+
},
|
1662
|
+
arguments: [ target, ...decl.arguments ],
|
1663
|
+
_protoInternalCall: true
|
1664
|
+
});
|
1665
|
+
}
|
1666
|
+
}
|
1667
|
+
|
1518
1668
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1519
1669
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1520
1670
|
return acc;
|
1521
1671
|
}, {});
|
1522
1672
|
|
1523
|
-
// no prototype function candidates, ignore
|
1524
1673
|
if (Object.keys(protoCands).length > 0) {
|
1525
1674
|
// use local for cached i32 length as commonly used
|
1526
1675
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1538,7 +1687,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1538
1687
|
|
1539
1688
|
let allOptUnused = true;
|
1540
1689
|
let lengthI32CacheUsed = false;
|
1541
|
-
const protoBC = {};
|
1542
1690
|
for (const x in protoCands) {
|
1543
1691
|
const protoFunc = protoCands[x];
|
1544
1692
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1546,13 +1694,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1546
1694
|
...RTArrayUtil.getLength(getPointer),
|
1547
1695
|
|
1548
1696
|
...number(TYPES.number, Valtype.i32),
|
1549
|
-
setLastType(scope)
|
1697
|
+
...setLastType(scope)
|
1550
1698
|
];
|
1551
1699
|
continue;
|
1552
1700
|
}
|
1553
1701
|
|
1554
|
-
// const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1555
|
-
// const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1556
1702
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1557
1703
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1558
1704
|
|
@@ -1583,7 +1729,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1583
1729
|
...protoOut,
|
1584
1730
|
|
1585
1731
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1586
|
-
setLastType(scope),
|
1732
|
+
...setLastType(scope),
|
1587
1733
|
[ Opcodes.end ]
|
1588
1734
|
];
|
1589
1735
|
}
|
@@ -1609,10 +1755,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1609
1755
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1610
1756
|
];
|
1611
1757
|
}
|
1758
|
+
|
1759
|
+
if (Object.keys(protoBC).length > 0) {
|
1760
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1761
|
+
...protoBC,
|
1762
|
+
|
1763
|
+
// TODO: error better
|
1764
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1765
|
+
}, valtypeBinary);
|
1766
|
+
}
|
1612
1767
|
}
|
1613
1768
|
|
1614
1769
|
// TODO: only allows callee as literal
|
1615
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1770
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1616
1771
|
|
1617
1772
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1618
1773
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1620,22 +1775,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1620
1775
|
|
1621
1776
|
includeBuiltin(scope, name);
|
1622
1777
|
idx = funcIndex[name];
|
1623
|
-
|
1624
|
-
// infer arguments types from builtins params
|
1625
|
-
const func = funcs.find(x => x.name === name);
|
1626
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1627
|
-
const arg = decl.arguments[i];
|
1628
|
-
if (!arg.name) continue;
|
1629
|
-
|
1630
|
-
const local = scope.locals[arg.name];
|
1631
|
-
if (!local) continue;
|
1632
|
-
|
1633
|
-
local.type = func.params[i];
|
1634
|
-
if (local.type === Valtype.v128) {
|
1635
|
-
// specify vec subtype inferred from last vec type in function name
|
1636
|
-
local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1637
|
-
}
|
1638
|
-
}
|
1639
1778
|
}
|
1640
1779
|
|
1641
1780
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1645,16 +1784,62 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1645
1784
|
idx = -1;
|
1646
1785
|
}
|
1647
1786
|
|
1787
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1788
|
+
const wasmOps = {
|
1789
|
+
// pointer, align, offset
|
1790
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1791
|
+
// pointer, value, align, offset
|
1792
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1793
|
+
// pointer, align, offset
|
1794
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1795
|
+
// pointer, value, align, offset
|
1796
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1797
|
+
// pointer, align, offset
|
1798
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1799
|
+
// pointer, value, align, offset
|
1800
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1801
|
+
|
1802
|
+
// pointer, align, offset
|
1803
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1804
|
+
// pointer, value, align, offset
|
1805
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1806
|
+
|
1807
|
+
// value
|
1808
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1809
|
+
};
|
1810
|
+
|
1811
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1812
|
+
|
1813
|
+
if (wasmOps[opName]) {
|
1814
|
+
const op = wasmOps[opName];
|
1815
|
+
|
1816
|
+
const argOut = [];
|
1817
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1818
|
+
...generate(scope, decl.arguments[i]),
|
1819
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1820
|
+
);
|
1821
|
+
|
1822
|
+
// literals only
|
1823
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1824
|
+
|
1825
|
+
return [
|
1826
|
+
...argOut,
|
1827
|
+
[ Opcodes[opName], ...imms ],
|
1828
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1829
|
+
];
|
1830
|
+
}
|
1831
|
+
}
|
1832
|
+
|
1648
1833
|
if (idx === undefined) {
|
1649
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1650
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1834
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1835
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1651
1836
|
}
|
1652
1837
|
|
1653
1838
|
const func = funcs.find(x => x.index === idx);
|
1654
1839
|
|
1655
1840
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1656
1841
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
-
const
|
1842
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1658
1843
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1659
1844
|
|
1660
1845
|
let args = decl.arguments;
|
@@ -1671,14 +1856,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1671
1856
|
if (func && func.throws) scope.throws = true;
|
1672
1857
|
|
1673
1858
|
let out = [];
|
1674
|
-
for (
|
1859
|
+
for (let i = 0; i < args.length; i++) {
|
1860
|
+
const arg = args[i];
|
1675
1861
|
out = out.concat(generate(scope, arg));
|
1862
|
+
|
1863
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1864
|
+
out.push(Opcodes.i32_to);
|
1865
|
+
}
|
1866
|
+
|
1867
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1868
|
+
out.push(Opcodes.i32_to);
|
1869
|
+
}
|
1870
|
+
|
1676
1871
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1677
1872
|
}
|
1678
1873
|
|
1679
1874
|
out.push([ Opcodes.call, idx ]);
|
1680
1875
|
|
1681
|
-
if (!
|
1876
|
+
if (!typedReturns) {
|
1682
1877
|
// let type;
|
1683
1878
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1684
1879
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1688,7 +1883,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1688
1883
|
// ...number(type, Valtype.i32),
|
1689
1884
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1690
1885
|
// );
|
1691
|
-
} else out.push(setLastType(scope));
|
1886
|
+
} else out.push(...setLastType(scope));
|
1887
|
+
|
1888
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1889
|
+
out.push(Opcodes.i32_from);
|
1890
|
+
}
|
1692
1891
|
|
1693
1892
|
return out;
|
1694
1893
|
};
|
@@ -1696,8 +1895,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1696
1895
|
const generateNew = (scope, decl, _global, _name) => {
|
1697
1896
|
// hack: basically treat this as a normal call for builtins for now
|
1698
1897
|
const name = mapName(decl.callee.name);
|
1898
|
+
|
1699
1899
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1700
|
-
|
1900
|
+
|
1901
|
+
if (builtinFuncs[name + '$constructor']) {
|
1902
|
+
// custom ...$constructor override builtin func
|
1903
|
+
return generateCall(scope, {
|
1904
|
+
...decl,
|
1905
|
+
callee: {
|
1906
|
+
type: 'Identifier',
|
1907
|
+
name: name + '$constructor'
|
1908
|
+
}
|
1909
|
+
}, _global, _name);
|
1910
|
+
}
|
1911
|
+
|
1912
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1701
1913
|
|
1702
1914
|
return generateCall(scope, decl, _global, _name);
|
1703
1915
|
};
|
@@ -1814,14 +2026,14 @@ const brTable = (input, bc, returns) => {
|
|
1814
2026
|
};
|
1815
2027
|
|
1816
2028
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
-
if (!
|
2029
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
1818
2030
|
|
1819
2031
|
const known = knownType(scope, type);
|
1820
2032
|
if (known != null) {
|
1821
2033
|
return bc[known] ?? bc.default;
|
1822
2034
|
}
|
1823
2035
|
|
1824
|
-
if (
|
2036
|
+
if (Prefs.typeswitchUseBrtable)
|
1825
2037
|
return brTable(type, bc, returns);
|
1826
2038
|
|
1827
2039
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1831,8 +2043,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1831
2043
|
[ Opcodes.block, returns ]
|
1832
2044
|
];
|
1833
2045
|
|
1834
|
-
// todo: use br_table?
|
1835
|
-
|
1836
2046
|
for (const x in bc) {
|
1837
2047
|
if (x === 'default') continue;
|
1838
2048
|
|
@@ -1856,7 +2066,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1856
2066
|
return out;
|
1857
2067
|
};
|
1858
2068
|
|
1859
|
-
const allocVar = (scope, name, global = false) => {
|
2069
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1860
2070
|
const target = global ? globals : scope.locals;
|
1861
2071
|
|
1862
2072
|
// already declared
|
@@ -1870,8 +2080,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1870
2080
|
let idx = global ? globalInd++ : scope.localInd++;
|
1871
2081
|
target[name] = { idx, type: valtypeBinary };
|
1872
2082
|
|
1873
|
-
|
1874
|
-
|
2083
|
+
if (type) {
|
2084
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2085
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2086
|
+
}
|
1875
2087
|
|
1876
2088
|
return idx;
|
1877
2089
|
};
|
@@ -1886,11 +2098,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1886
2098
|
};
|
1887
2099
|
|
1888
2100
|
const typeAnnoToPorfType = x => {
|
1889
|
-
if (
|
1890
|
-
if (TYPES[
|
2101
|
+
if (!x) return null;
|
2102
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
1891
2103
|
|
1892
2104
|
switch (x) {
|
1893
2105
|
case 'i32':
|
2106
|
+
case 'i64':
|
2107
|
+
case 'f64':
|
1894
2108
|
return TYPES.number;
|
1895
2109
|
}
|
1896
2110
|
|
@@ -1901,7 +2115,7 @@ const extractTypeAnnotation = decl => {
|
|
1901
2115
|
let a = decl;
|
1902
2116
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1903
2117
|
|
1904
|
-
let type, elementType;
|
2118
|
+
let type = null, elementType = null;
|
1905
2119
|
if (a.typeName) {
|
1906
2120
|
type = a.typeName.name;
|
1907
2121
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1914,7 +2128,7 @@ const extractTypeAnnotation = decl => {
|
|
1914
2128
|
const typeName = type;
|
1915
2129
|
type = typeAnnoToPorfType(type);
|
1916
2130
|
|
1917
|
-
if (type === TYPES.
|
2131
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
1918
2132
|
|
1919
2133
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1920
2134
|
|
@@ -1926,13 +2140,13 @@ const generateVar = (scope, decl) => {
|
|
1926
2140
|
|
1927
2141
|
const topLevel = scope.name === 'main';
|
1928
2142
|
|
1929
|
-
// global variable if in top scope (main)
|
1930
|
-
const global = topLevel || decl._bare;
|
2143
|
+
// global variable if in top scope (main) or if internally wanted
|
2144
|
+
const global = topLevel || decl._bare;
|
1931
2145
|
|
1932
2146
|
for (const x of decl.declarations) {
|
1933
2147
|
const name = mapName(x.id.name);
|
1934
2148
|
|
1935
|
-
if (!name) return todo('destructuring is not supported yet');
|
2149
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1936
2150
|
|
1937
2151
|
if (x.init && isFuncType(x.init.type)) {
|
1938
2152
|
// hack for let a = function () { ... }
|
@@ -1941,7 +2155,6 @@ const generateVar = (scope, decl) => {
|
|
1941
2155
|
continue;
|
1942
2156
|
}
|
1943
2157
|
|
1944
|
-
// console.log(name);
|
1945
2158
|
if (topLevel && builtinVars[name]) {
|
1946
2159
|
// cannot redeclare
|
1947
2160
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
@@ -1949,16 +2162,29 @@ const generateVar = (scope, decl) => {
|
|
1949
2162
|
continue; // always ignore
|
1950
2163
|
}
|
1951
2164
|
|
1952
|
-
|
2165
|
+
// // generate init before allocating var
|
2166
|
+
// let generated;
|
2167
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
1953
2168
|
|
1954
|
-
|
2169
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2170
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2171
|
+
|
2172
|
+
if (typed) {
|
1955
2173
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
2174
|
}
|
1957
2175
|
|
1958
2176
|
if (x.init) {
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
2177
|
+
const generated = generate(scope, x.init, global, name);
|
2178
|
+
if (scope.arrays?.get(name) != null) {
|
2179
|
+
// hack to set local as pointer before
|
2180
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2181
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2182
|
+
generated.pop();
|
2183
|
+
out = out.concat(generated);
|
2184
|
+
} else {
|
2185
|
+
out = out.concat(generated);
|
2186
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2187
|
+
}
|
1962
2188
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1963
2189
|
}
|
1964
2190
|
|
@@ -1969,7 +2195,8 @@ const generateVar = (scope, decl) => {
|
|
1969
2195
|
return out;
|
1970
2196
|
};
|
1971
2197
|
|
1972
|
-
|
2198
|
+
// todo: optimize this func for valueUnused
|
2199
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1973
2200
|
const { type, name } = decl.left;
|
1974
2201
|
|
1975
2202
|
if (type === 'ObjectPattern') {
|
@@ -1984,22 +2211,30 @@ const generateAssign = (scope, decl) => {
|
|
1984
2211
|
return [];
|
1985
2212
|
}
|
1986
2213
|
|
2214
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2215
|
+
|
1987
2216
|
// hack: .length setter
|
1988
2217
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1989
2218
|
const name = decl.left.object.name;
|
1990
|
-
const pointer = arrays
|
2219
|
+
const pointer = scope.arrays?.get(name);
|
1991
2220
|
|
1992
|
-
const aotPointer = pointer != null;
|
2221
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1993
2222
|
|
1994
2223
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2224
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1995
2225
|
|
1996
2226
|
return [
|
1997
2227
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1998
2228
|
...generate(scope, decl.left.object),
|
1999
2229
|
Opcodes.i32_to_u
|
2000
2230
|
]),
|
2231
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2001
2232
|
|
2002
|
-
...generate(scope, decl.right),
|
2233
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2234
|
+
[ Opcodes.local_get, pointerTmp ],
|
2235
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2236
|
+
Opcodes.i32_from_u
|
2237
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2003
2238
|
[ Opcodes.local_tee, newValueTmp ],
|
2004
2239
|
|
2005
2240
|
Opcodes.i32_to_u,
|
@@ -2009,21 +2244,19 @@ const generateAssign = (scope, decl) => {
|
|
2009
2244
|
];
|
2010
2245
|
}
|
2011
2246
|
|
2012
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2013
|
-
|
2014
2247
|
// arr[i]
|
2015
2248
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2016
2249
|
const name = decl.left.object.name;
|
2017
|
-
const pointer = arrays
|
2250
|
+
const pointer = scope.arrays?.get(name);
|
2018
2251
|
|
2019
|
-
const aotPointer = pointer != null;
|
2252
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2020
2253
|
|
2021
2254
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2022
2255
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2023
2256
|
|
2024
2257
|
return [
|
2025
2258
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
2026
|
-
[TYPES.
|
2259
|
+
[TYPES.array]: [
|
2027
2260
|
...(aotPointer ? [] : [
|
2028
2261
|
...generate(scope, decl.left.object),
|
2029
2262
|
Opcodes.i32_to_u
|
@@ -2072,7 +2305,7 @@ const generateAssign = (scope, decl) => {
|
|
2072
2305
|
];
|
2073
2306
|
}
|
2074
2307
|
|
2075
|
-
if (!name) return todo('destructuring is not supported yet');
|
2308
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2076
2309
|
|
2077
2310
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2078
2311
|
|
@@ -2120,9 +2353,7 @@ const generateAssign = (scope, decl) => {
|
|
2120
2353
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2121
2354
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2122
2355
|
|
2123
|
-
getLastType(scope)
|
2124
|
-
// hack: type is idx+1
|
2125
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2356
|
+
...setType(scope, name, getLastType(scope))
|
2126
2357
|
];
|
2127
2358
|
}
|
2128
2359
|
|
@@ -2133,9 +2364,7 @@ const generateAssign = (scope, decl) => {
|
|
2133
2364
|
|
2134
2365
|
// todo: string concat types
|
2135
2366
|
|
2136
|
-
|
2137
|
-
...number(TYPES.number, Valtype.i32),
|
2138
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2367
|
+
...setType(scope, name, TYPES.number)
|
2139
2368
|
];
|
2140
2369
|
};
|
2141
2370
|
|
@@ -2181,7 +2410,7 @@ const generateUnary = (scope, decl) => {
|
|
2181
2410
|
return out;
|
2182
2411
|
}
|
2183
2412
|
|
2184
|
-
case 'delete':
|
2413
|
+
case 'delete': {
|
2185
2414
|
let toReturn = true, toGenerate = true;
|
2186
2415
|
|
2187
2416
|
if (decl.argument.type === 'Identifier') {
|
@@ -2203,40 +2432,60 @@ const generateUnary = (scope, decl) => {
|
|
2203
2432
|
|
2204
2433
|
out.push(...number(toReturn ? 1 : 0));
|
2205
2434
|
return out;
|
2435
|
+
}
|
2436
|
+
|
2437
|
+
case 'typeof': {
|
2438
|
+
let overrideType, toGenerate = true;
|
2439
|
+
|
2440
|
+
if (decl.argument.type === 'Identifier') {
|
2441
|
+
const out = generateIdent(scope, decl.argument);
|
2442
|
+
|
2443
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2444
|
+
if (out[1]) {
|
2445
|
+
// does not exist (2 ops from throw)
|
2446
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2447
|
+
toGenerate = false;
|
2448
|
+
}
|
2449
|
+
}
|
2206
2450
|
|
2207
|
-
|
2208
|
-
|
2451
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2452
|
+
disposeLeftover(out);
|
2453
|
+
|
2454
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2209
2455
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2210
2456
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2211
2457
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2212
2458
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2213
2459
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2214
2460
|
|
2215
|
-
[TYPES.
|
2461
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
2462
|
|
2217
2463
|
// object and internal types
|
2218
2464
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2219
|
-
});
|
2465
|
+
}));
|
2466
|
+
|
2467
|
+
return out;
|
2468
|
+
}
|
2220
2469
|
|
2221
2470
|
default:
|
2222
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2471
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2223
2472
|
}
|
2224
2473
|
};
|
2225
2474
|
|
2226
|
-
const generateUpdate = (scope, decl) => {
|
2475
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2227
2476
|
const { name } = decl.argument;
|
2228
2477
|
|
2229
2478
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2230
2479
|
|
2231
2480
|
if (local === undefined) {
|
2232
|
-
return todo(`update expression with undefined variable
|
2481
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2233
2482
|
}
|
2234
2483
|
|
2235
2484
|
const idx = local.idx;
|
2236
2485
|
const out = [];
|
2237
2486
|
|
2238
2487
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2239
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2488
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2240
2489
|
|
2241
2490
|
switch (decl.operator) {
|
2242
2491
|
case '++':
|
@@ -2249,7 +2498,7 @@ const generateUpdate = (scope, decl) => {
|
|
2249
2498
|
}
|
2250
2499
|
|
2251
2500
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2252
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2501
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2253
2502
|
|
2254
2503
|
return out;
|
2255
2504
|
};
|
@@ -2289,7 +2538,7 @@ const generateConditional = (scope, decl) => {
|
|
2289
2538
|
// note type
|
2290
2539
|
out.push(
|
2291
2540
|
...getNodeType(scope, decl.consequent),
|
2292
|
-
setLastType(scope)
|
2541
|
+
...setLastType(scope)
|
2293
2542
|
);
|
2294
2543
|
|
2295
2544
|
out.push([ Opcodes.else ]);
|
@@ -2298,7 +2547,7 @@ const generateConditional = (scope, decl) => {
|
|
2298
2547
|
// note type
|
2299
2548
|
out.push(
|
2300
2549
|
...getNodeType(scope, decl.alternate),
|
2301
|
-
setLastType(scope)
|
2550
|
+
...setLastType(scope)
|
2302
2551
|
);
|
2303
2552
|
|
2304
2553
|
out.push([ Opcodes.end ]);
|
@@ -2312,7 +2561,7 @@ const generateFor = (scope, decl) => {
|
|
2312
2561
|
const out = [];
|
2313
2562
|
|
2314
2563
|
if (decl.init) {
|
2315
|
-
out.push(...generate(scope, decl.init));
|
2564
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2316
2565
|
disposeLeftover(out);
|
2317
2566
|
}
|
2318
2567
|
|
@@ -2330,7 +2579,7 @@ const generateFor = (scope, decl) => {
|
|
2330
2579
|
out.push(...generate(scope, decl.body));
|
2331
2580
|
out.push([ Opcodes.end ]);
|
2332
2581
|
|
2333
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2582
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2334
2583
|
|
2335
2584
|
out.push([ Opcodes.br, 1 ]);
|
2336
2585
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2358,6 +2607,36 @@ const generateWhile = (scope, decl) => {
|
|
2358
2607
|
return out;
|
2359
2608
|
};
|
2360
2609
|
|
2610
|
+
const generateDoWhile = (scope, decl) => {
|
2611
|
+
const out = [];
|
2612
|
+
|
2613
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2614
|
+
depth.push('dowhile');
|
2615
|
+
|
2616
|
+
// block for break (includes all)
|
2617
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2618
|
+
depth.push('block');
|
2619
|
+
|
2620
|
+
// block for continue
|
2621
|
+
// includes body but not test+loop so we can exit body at anytime
|
2622
|
+
// and still test+loop after
|
2623
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2624
|
+
depth.push('block');
|
2625
|
+
|
2626
|
+
out.push(...generate(scope, decl.body));
|
2627
|
+
|
2628
|
+
out.push([ Opcodes.end ]);
|
2629
|
+
depth.pop();
|
2630
|
+
|
2631
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2632
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2633
|
+
|
2634
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2635
|
+
depth.pop(); depth.pop();
|
2636
|
+
|
2637
|
+
return out;
|
2638
|
+
};
|
2639
|
+
|
2361
2640
|
const generateForOf = (scope, decl) => {
|
2362
2641
|
const out = [];
|
2363
2642
|
|
@@ -2394,7 +2673,10 @@ const generateForOf = (scope, decl) => {
|
|
2394
2673
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2395
2674
|
}
|
2396
2675
|
|
2676
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2677
|
+
|
2397
2678
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2679
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2398
2680
|
|
2399
2681
|
depth.push('block');
|
2400
2682
|
depth.push('block');
|
@@ -2403,6 +2685,7 @@ const generateForOf = (scope, decl) => {
|
|
2403
2685
|
// hack: this is naughty and will break things!
|
2404
2686
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2405
2687
|
if (pages.hasAnyString) {
|
2688
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2406
2689
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2407
2690
|
rawElements: new Array(1)
|
2408
2691
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2411,7 +2694,7 @@ const generateForOf = (scope, decl) => {
|
|
2411
2694
|
// set type for local
|
2412
2695
|
// todo: optimize away counter and use end pointer
|
2413
2696
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2414
|
-
[TYPES.
|
2697
|
+
[TYPES.array]: [
|
2415
2698
|
...setType(scope, leftName, TYPES.number),
|
2416
2699
|
|
2417
2700
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2494,6 +2777,56 @@ const generateForOf = (scope, decl) => {
|
|
2494
2777
|
[ Opcodes.end ],
|
2495
2778
|
[ Opcodes.end ]
|
2496
2779
|
],
|
2780
|
+
[TYPES.bytestring]: [
|
2781
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2782
|
+
|
2783
|
+
[ Opcodes.loop, Blocktype.void ],
|
2784
|
+
|
2785
|
+
// setup new/out array
|
2786
|
+
...newOut,
|
2787
|
+
[ Opcodes.drop ],
|
2788
|
+
|
2789
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2790
|
+
|
2791
|
+
// load current string ind {arg}
|
2792
|
+
[ Opcodes.local_get, pointer ],
|
2793
|
+
[ Opcodes.local_get, counter ],
|
2794
|
+
[ Opcodes.i32_add ],
|
2795
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2796
|
+
|
2797
|
+
// store to new string ind 0
|
2798
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2799
|
+
|
2800
|
+
// return new string (page)
|
2801
|
+
...number(newPointer),
|
2802
|
+
|
2803
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2804
|
+
|
2805
|
+
[ Opcodes.block, Blocktype.void ],
|
2806
|
+
[ Opcodes.block, Blocktype.void ],
|
2807
|
+
...generate(scope, decl.body),
|
2808
|
+
[ Opcodes.end ],
|
2809
|
+
|
2810
|
+
// increment iter pointer
|
2811
|
+
// [ Opcodes.local_get, pointer ],
|
2812
|
+
// ...number(1, Valtype.i32),
|
2813
|
+
// [ Opcodes.i32_add ],
|
2814
|
+
// [ Opcodes.local_set, pointer ],
|
2815
|
+
|
2816
|
+
// increment counter by 1
|
2817
|
+
[ Opcodes.local_get, counter ],
|
2818
|
+
...number(1, Valtype.i32),
|
2819
|
+
[ Opcodes.i32_add ],
|
2820
|
+
[ Opcodes.local_tee, counter ],
|
2821
|
+
|
2822
|
+
// loop if counter != length
|
2823
|
+
[ Opcodes.local_get, length ],
|
2824
|
+
[ Opcodes.i32_ne ],
|
2825
|
+
[ Opcodes.br_if, 1 ],
|
2826
|
+
|
2827
|
+
[ Opcodes.end ],
|
2828
|
+
[ Opcodes.end ]
|
2829
|
+
],
|
2497
2830
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2498
2831
|
}, Blocktype.void));
|
2499
2832
|
|
@@ -2504,28 +2837,65 @@ const generateForOf = (scope, decl) => {
|
|
2504
2837
|
return out;
|
2505
2838
|
};
|
2506
2839
|
|
2840
|
+
// find the nearest loop in depth map by type
|
2507
2841
|
const getNearestLoop = () => {
|
2508
2842
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2509
|
-
if (
|
2843
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2510
2844
|
}
|
2511
2845
|
|
2512
2846
|
return -1;
|
2513
2847
|
};
|
2514
2848
|
|
2515
2849
|
const generateBreak = (scope, decl) => {
|
2516
|
-
const
|
2850
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2851
|
+
const type = depth[target];
|
2852
|
+
|
2853
|
+
// different loop types have different branch offsets
|
2854
|
+
// as they have different wasm block/loop/if structures
|
2855
|
+
// we need to use the right offset by type to branch to the one we want
|
2856
|
+
// for a break: exit the loop without executing anything else inside it
|
2857
|
+
const offset = ({
|
2858
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2859
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2860
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2861
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2862
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2863
|
+
})[type];
|
2864
|
+
|
2517
2865
|
return [
|
2518
|
-
[ Opcodes.br, ...signedLEB128(
|
2866
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2519
2867
|
];
|
2520
2868
|
};
|
2521
2869
|
|
2522
2870
|
const generateContinue = (scope, decl) => {
|
2523
|
-
const
|
2871
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2872
|
+
const type = depth[target];
|
2873
|
+
|
2874
|
+
// different loop types have different branch offsets
|
2875
|
+
// as they have different wasm block/loop/if structures
|
2876
|
+
// we need to use the right offset by type to branch to the one we want
|
2877
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2878
|
+
const offset = ({
|
2879
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2880
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2881
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2882
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2883
|
+
})[type];
|
2884
|
+
|
2524
2885
|
return [
|
2525
|
-
[ Opcodes.br, ...signedLEB128(
|
2886
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2526
2887
|
];
|
2527
2888
|
};
|
2528
2889
|
|
2890
|
+
const generateLabel = (scope, decl) => {
|
2891
|
+
scope.labels ??= new Map();
|
2892
|
+
|
2893
|
+
const name = decl.label.name;
|
2894
|
+
scope.labels.set(name, depth.length);
|
2895
|
+
|
2896
|
+
return generate(scope, decl.body);
|
2897
|
+
};
|
2898
|
+
|
2529
2899
|
const generateThrow = (scope, decl) => {
|
2530
2900
|
scope.throws = true;
|
2531
2901
|
|
@@ -2546,6 +2916,9 @@ const generateThrow = (scope, decl) => {
|
|
2546
2916
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2547
2917
|
let tagIdx = tags[0].idx;
|
2548
2918
|
|
2919
|
+
scope.exceptions ??= [];
|
2920
|
+
scope.exceptions.push(exceptId);
|
2921
|
+
|
2549
2922
|
// todo: write a description of how this works lol
|
2550
2923
|
|
2551
2924
|
return [
|
@@ -2555,7 +2928,7 @@ const generateThrow = (scope, decl) => {
|
|
2555
2928
|
};
|
2556
2929
|
|
2557
2930
|
const generateTry = (scope, decl) => {
|
2558
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2931
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2559
2932
|
|
2560
2933
|
const out = [];
|
2561
2934
|
|
@@ -2582,15 +2955,8 @@ const generateEmpty = (scope, decl) => {
|
|
2582
2955
|
return [];
|
2583
2956
|
};
|
2584
2957
|
|
2585
|
-
const generateAssignPat = (scope, decl) => {
|
2586
|
-
// TODO
|
2587
|
-
// if identifier declared, use that
|
2588
|
-
// else, use default (right)
|
2589
|
-
return todo('assignment pattern (optional arg)');
|
2590
|
-
};
|
2591
|
-
|
2592
2958
|
let pages = new Map();
|
2593
|
-
const allocPage = (reason, type) => {
|
2959
|
+
const allocPage = (scope, reason, type) => {
|
2594
2960
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2595
2961
|
|
2596
2962
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2601,16 +2967,20 @@ const allocPage = (reason, type) => {
|
|
2601
2967
|
const ind = pages.size;
|
2602
2968
|
pages.set(reason, { ind, type });
|
2603
2969
|
|
2604
|
-
|
2970
|
+
scope.pages ??= new Map();
|
2971
|
+
scope.pages.set(reason, { ind, type });
|
2972
|
+
|
2973
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2605
2974
|
|
2606
2975
|
return ind;
|
2607
2976
|
};
|
2608
2977
|
|
2978
|
+
// todo: add scope.pages
|
2609
2979
|
const freePage = reason => {
|
2610
2980
|
const { ind } = pages.get(reason);
|
2611
2981
|
pages.delete(reason);
|
2612
2982
|
|
2613
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2983
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2614
2984
|
|
2615
2985
|
return ind;
|
2616
2986
|
};
|
@@ -2661,16 +3031,22 @@ const getAllocType = itemType => {
|
|
2661
3031
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2662
3032
|
const out = [];
|
2663
3033
|
|
3034
|
+
scope.arrays ??= new Map();
|
3035
|
+
|
2664
3036
|
let firstAssign = false;
|
2665
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3037
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2666
3038
|
firstAssign = true;
|
2667
3039
|
|
2668
3040
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2669
3041
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2670
|
-
|
3042
|
+
|
3043
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3044
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2671
3045
|
}
|
2672
3046
|
|
2673
|
-
const pointer = arrays.get(name);
|
3047
|
+
const pointer = scope.arrays.get(name);
|
3048
|
+
|
3049
|
+
const local = global ? globals[name] : scope.locals[name];
|
2674
3050
|
|
2675
3051
|
const useRawElements = !!decl.rawElements;
|
2676
3052
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2678,19 +3054,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2678
3054
|
const valtype = itemTypeToValtype[itemType];
|
2679
3055
|
const length = elements.length;
|
2680
3056
|
|
2681
|
-
if (firstAssign && useRawElements) {
|
2682
|
-
|
3057
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3058
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3059
|
+
if (length !== 0) {
|
3060
|
+
let bytes = compileBytes(length, 'i32');
|
2683
3061
|
|
2684
|
-
|
2685
|
-
|
3062
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3063
|
+
if (elements[i] == null) continue;
|
2686
3064
|
|
2687
|
-
|
2688
|
-
|
3065
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3066
|
+
}
|
2689
3067
|
|
2690
|
-
|
2691
|
-
|
2692
|
-
|
2693
|
-
|
3068
|
+
const ind = data.push({
|
3069
|
+
offset: pointer,
|
3070
|
+
bytes
|
3071
|
+
}) - 1;
|
3072
|
+
|
3073
|
+
scope.data ??= [];
|
3074
|
+
scope.data.push(ind);
|
3075
|
+
}
|
2694
3076
|
|
2695
3077
|
// local value as pointer
|
2696
3078
|
out.push(...number(pointer));
|
@@ -2698,11 +3080,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2698
3080
|
return [ out, pointer ];
|
2699
3081
|
}
|
2700
3082
|
|
3083
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3084
|
+
if (pointerTmp != null) {
|
3085
|
+
out.push(
|
3086
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3087
|
+
Opcodes.i32_to_u,
|
3088
|
+
[ Opcodes.local_set, pointerTmp ]
|
3089
|
+
);
|
3090
|
+
}
|
3091
|
+
|
3092
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3093
|
+
|
2701
3094
|
// store length as 0th array
|
2702
3095
|
out.push(
|
2703
|
-
...
|
3096
|
+
...pointerWasm,
|
2704
3097
|
...number(length, Valtype.i32),
|
2705
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3098
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2706
3099
|
);
|
2707
3100
|
|
2708
3101
|
const storeOp = StoreOps[itemType];
|
@@ -2711,20 +3104,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2711
3104
|
if (elements[i] == null) continue;
|
2712
3105
|
|
2713
3106
|
out.push(
|
2714
|
-
...
|
3107
|
+
...pointerWasm,
|
2715
3108
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2716
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3109
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2717
3110
|
);
|
2718
3111
|
}
|
2719
3112
|
|
2720
3113
|
// local value as pointer
|
2721
|
-
out.push(...
|
3114
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2722
3115
|
|
2723
3116
|
return [ out, pointer ];
|
2724
3117
|
};
|
2725
3118
|
|
2726
3119
|
const byteStringable = str => {
|
2727
|
-
if (!
|
3120
|
+
if (!Prefs.bytestring) return false;
|
2728
3121
|
|
2729
3122
|
for (let i = 0; i < str.length; i++) {
|
2730
3123
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2735,7 +3128,7 @@ const byteStringable = str => {
|
|
2735
3128
|
|
2736
3129
|
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2737
3130
|
const rawElements = new Array(str.length);
|
2738
|
-
let byteStringable =
|
3131
|
+
let byteStringable = Prefs.bytestring;
|
2739
3132
|
for (let i = 0; i < str.length; i++) {
|
2740
3133
|
const c = str.charCodeAt(i);
|
2741
3134
|
rawElements[i] = c;
|
@@ -2750,20 +3143,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2750
3143
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2751
3144
|
};
|
2752
3145
|
|
2753
|
-
let arrays = new Map();
|
2754
3146
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2755
3147
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2756
3148
|
};
|
2757
3149
|
|
2758
3150
|
export const generateMember = (scope, decl, _global, _name) => {
|
2759
3151
|
const name = decl.object.name;
|
2760
|
-
const pointer = arrays
|
3152
|
+
const pointer = scope.arrays?.get(name);
|
3153
|
+
|
3154
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
3155
|
+
|
3156
|
+
// hack: .name
|
3157
|
+
if (decl.property.name === 'name') {
|
3158
|
+
if (hasFuncWithName(name)) {
|
3159
|
+
let nameProp = name;
|
3160
|
+
|
3161
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3162
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
2761
3163
|
|
2762
|
-
|
3164
|
+
return makeString(scope, nameProp, _global, _name, true);
|
3165
|
+
} else {
|
3166
|
+
return generate(scope, DEFAULT_VALUE);
|
3167
|
+
}
|
3168
|
+
}
|
2763
3169
|
|
2764
3170
|
// hack: .length
|
2765
3171
|
if (decl.property.name === 'length') {
|
2766
|
-
|
3172
|
+
const func = funcs.find(x => x.name === name);
|
3173
|
+
if (func) {
|
3174
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3175
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3176
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3177
|
+
}
|
3178
|
+
|
3179
|
+
if (builtinFuncs[name + '$constructor']) {
|
3180
|
+
const regularFunc = builtinFuncs[name];
|
3181
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3182
|
+
|
3183
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3184
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3185
|
+
|
3186
|
+
return number(Math.max(regularParams, constructorParams));
|
3187
|
+
}
|
3188
|
+
|
3189
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3190
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3191
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3192
|
+
|
2767
3193
|
return [
|
2768
3194
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2769
3195
|
...generate(scope, decl.object),
|
@@ -2788,7 +3214,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2788
3214
|
}
|
2789
3215
|
|
2790
3216
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2791
|
-
[TYPES.
|
3217
|
+
[TYPES.array]: [
|
2792
3218
|
// get index as valtype
|
2793
3219
|
...property,
|
2794
3220
|
|
@@ -2807,7 +3233,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2807
3233
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2808
3234
|
|
2809
3235
|
...number(TYPES.number, Valtype.i32),
|
2810
|
-
setLastType(scope)
|
3236
|
+
...setLastType(scope)
|
2811
3237
|
],
|
2812
3238
|
|
2813
3239
|
[TYPES.string]: [
|
@@ -2839,9 +3265,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2839
3265
|
...number(newPointer),
|
2840
3266
|
|
2841
3267
|
...number(TYPES.string, Valtype.i32),
|
2842
|
-
setLastType(scope)
|
3268
|
+
...setLastType(scope)
|
2843
3269
|
],
|
2844
|
-
[TYPES.
|
3270
|
+
[TYPES.bytestring]: [
|
2845
3271
|
// setup new/out array
|
2846
3272
|
...newOut,
|
2847
3273
|
[ Opcodes.drop ],
|
@@ -2858,19 +3284,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2858
3284
|
]),
|
2859
3285
|
|
2860
3286
|
// load current string ind {arg}
|
2861
|
-
[ Opcodes.i32_load8_u,
|
3287
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
3288
|
|
2863
3289
|
// store to new string ind 0
|
2864
|
-
[ Opcodes.i32_store8,
|
3290
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
3291
|
|
2866
3292
|
// return new string (page)
|
2867
3293
|
...number(newPointer),
|
2868
3294
|
|
2869
|
-
...number(TYPES.
|
2870
|
-
setLastType(scope)
|
3295
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3296
|
+
...setLastType(scope)
|
2871
3297
|
],
|
2872
3298
|
|
2873
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3299
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2874
3300
|
});
|
2875
3301
|
};
|
2876
3302
|
|
@@ -2880,28 +3306,36 @@ const objectHack = node => {
|
|
2880
3306
|
if (!node) return node;
|
2881
3307
|
|
2882
3308
|
if (node.type === 'MemberExpression') {
|
2883
|
-
|
3309
|
+
const out = (() => {
|
3310
|
+
if (node.computed || node.optional) return;
|
2884
3311
|
|
2885
|
-
|
3312
|
+
let objectName = node.object.name;
|
2886
3313
|
|
2887
|
-
|
2888
|
-
|
3314
|
+
// if object is not identifier or another member exp, give up
|
3315
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3316
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2889
3317
|
|
2890
|
-
|
3318
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2891
3319
|
|
2892
|
-
|
2893
|
-
|
3320
|
+
// if .name or .length, give up (hack within a hack!)
|
3321
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3322
|
+
node.object = objectHack(node.object);
|
3323
|
+
return;
|
3324
|
+
}
|
2894
3325
|
|
2895
|
-
|
2896
|
-
|
3326
|
+
// no object name, give up
|
3327
|
+
if (!objectName) return;
|
2897
3328
|
|
2898
|
-
|
2899
|
-
|
3329
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3330
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2900
3331
|
|
2901
|
-
|
2902
|
-
|
2903
|
-
|
2904
|
-
|
3332
|
+
return {
|
3333
|
+
type: 'Identifier',
|
3334
|
+
name
|
3335
|
+
};
|
3336
|
+
})();
|
3337
|
+
|
3338
|
+
if (out) return out;
|
2905
3339
|
}
|
2906
3340
|
|
2907
3341
|
for (const x in node) {
|
@@ -2915,8 +3349,8 @@ const objectHack = node => {
|
|
2915
3349
|
};
|
2916
3350
|
|
2917
3351
|
const generateFunc = (scope, decl) => {
|
2918
|
-
if (decl.async) return todo('async functions are not supported');
|
2919
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3352
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3353
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2920
3354
|
|
2921
3355
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2922
3356
|
const params = decl.params ?? [];
|
@@ -2932,6 +3366,14 @@ const generateFunc = (scope, decl) => {
|
|
2932
3366
|
name
|
2933
3367
|
};
|
2934
3368
|
|
3369
|
+
if (typedInput && decl.returnType) {
|
3370
|
+
const { type } = extractTypeAnnotation(decl.returnType);
|
3371
|
+
if (type != null) {
|
3372
|
+
innerScope.returnType = type;
|
3373
|
+
innerScope.returns = [ valtypeBinary ];
|
3374
|
+
}
|
3375
|
+
}
|
3376
|
+
|
2935
3377
|
for (let i = 0; i < params.length; i++) {
|
2936
3378
|
allocVar(innerScope, params[i].name, false);
|
2937
3379
|
|
@@ -2958,6 +3400,8 @@ const generateFunc = (scope, decl) => {
|
|
2958
3400
|
};
|
2959
3401
|
funcIndex[name] = func.index;
|
2960
3402
|
|
3403
|
+
if (name === 'main') func.gotLastType = true;
|
3404
|
+
|
2961
3405
|
// quick hack fixes
|
2962
3406
|
for (const inst of wasm) {
|
2963
3407
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2969,7 +3413,7 @@ const generateFunc = (scope, decl) => {
|
|
2969
3413
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2970
3414
|
wasm.push(
|
2971
3415
|
...number(0),
|
2972
|
-
...number(TYPES.undefined, Valtype.i32),
|
3416
|
+
...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
2973
3417
|
[ Opcodes.return ]
|
2974
3418
|
);
|
2975
3419
|
}
|
@@ -2992,16 +3436,6 @@ const generateCode = (scope, decl) => {
|
|
2992
3436
|
};
|
2993
3437
|
|
2994
3438
|
const internalConstrs = {
|
2995
|
-
Boolean: {
|
2996
|
-
generate: (scope, decl) => {
|
2997
|
-
if (decl.arguments.length === 0) return number(0);
|
2998
|
-
|
2999
|
-
// should generate/run all args
|
3000
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3001
|
-
},
|
3002
|
-
type: TYPES.boolean
|
3003
|
-
},
|
3004
|
-
|
3005
3439
|
Array: {
|
3006
3440
|
generate: (scope, decl, global, name) => {
|
3007
3441
|
// new Array(i0, i1, ...)
|
@@ -3019,7 +3453,7 @@ const internalConstrs = {
|
|
3019
3453
|
|
3020
3454
|
// todo: check in wasm instead of here
|
3021
3455
|
const literalValue = arg.value ?? 0;
|
3022
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3456
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3023
3457
|
|
3024
3458
|
return [
|
3025
3459
|
...number(0, Valtype.i32),
|
@@ -3030,7 +3464,8 @@ const internalConstrs = {
|
|
3030
3464
|
...number(pointer)
|
3031
3465
|
];
|
3032
3466
|
},
|
3033
|
-
type: TYPES.
|
3467
|
+
type: TYPES.array,
|
3468
|
+
length: 1
|
3034
3469
|
},
|
3035
3470
|
|
3036
3471
|
__Array_of: {
|
@@ -3041,27 +3476,134 @@ const internalConstrs = {
|
|
3041
3476
|
elements: decl.arguments
|
3042
3477
|
}, global, name);
|
3043
3478
|
},
|
3044
|
-
type: TYPES.
|
3479
|
+
type: TYPES.array,
|
3480
|
+
notConstr: true,
|
3481
|
+
length: 0
|
3482
|
+
},
|
3483
|
+
|
3484
|
+
__Porffor_fastOr: {
|
3485
|
+
generate: (scope, decl) => {
|
3486
|
+
const out = [];
|
3487
|
+
|
3488
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3489
|
+
out.push(
|
3490
|
+
...generate(scope, decl.arguments[i]),
|
3491
|
+
Opcodes.i32_to_u,
|
3492
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3493
|
+
);
|
3494
|
+
}
|
3495
|
+
|
3496
|
+
out.push(Opcodes.i32_from_u);
|
3497
|
+
|
3498
|
+
return out;
|
3499
|
+
},
|
3500
|
+
type: TYPES.boolean,
|
3045
3501
|
notConstr: true
|
3046
|
-
}
|
3047
|
-
};
|
3502
|
+
},
|
3048
3503
|
|
3049
|
-
|
3050
|
-
|
3051
|
-
|
3052
|
-
// for (const x of arr) {
|
3053
|
-
// if (x === undefined) {
|
3054
|
-
// console.trace(arr);
|
3055
|
-
// process.exit();
|
3056
|
-
// }
|
3057
|
-
// if (Array.isArray(x)) check(x);
|
3058
|
-
// }
|
3059
|
-
// };
|
3060
|
-
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
3061
|
-
// // if (Array.isArray(a)) check(a);
|
3504
|
+
__Porffor_fastAnd: {
|
3505
|
+
generate: (scope, decl) => {
|
3506
|
+
const out = [];
|
3062
3507
|
|
3063
|
-
|
3064
|
-
|
3508
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3509
|
+
out.push(
|
3510
|
+
...generate(scope, decl.arguments[i]),
|
3511
|
+
Opcodes.i32_to_u,
|
3512
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3513
|
+
);
|
3514
|
+
}
|
3515
|
+
|
3516
|
+
out.push(Opcodes.i32_from_u);
|
3517
|
+
|
3518
|
+
return out;
|
3519
|
+
},
|
3520
|
+
type: TYPES.boolean,
|
3521
|
+
notConstr: true
|
3522
|
+
},
|
3523
|
+
|
3524
|
+
Boolean: {
|
3525
|
+
generate: (scope, decl) => {
|
3526
|
+
// todo: boolean object when used as constructor
|
3527
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3528
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3529
|
+
},
|
3530
|
+
type: TYPES.boolean,
|
3531
|
+
length: 1
|
3532
|
+
},
|
3533
|
+
|
3534
|
+
__Math_max: {
|
3535
|
+
generate: (scope, decl) => {
|
3536
|
+
const out = [
|
3537
|
+
...number(-Infinity)
|
3538
|
+
];
|
3539
|
+
|
3540
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3541
|
+
out.push(
|
3542
|
+
...generate(scope, decl.arguments[i]),
|
3543
|
+
[ Opcodes.f64_max ]
|
3544
|
+
);
|
3545
|
+
}
|
3546
|
+
|
3547
|
+
return out;
|
3548
|
+
},
|
3549
|
+
type: TYPES.number,
|
3550
|
+
notConstr: true,
|
3551
|
+
length: 2
|
3552
|
+
},
|
3553
|
+
|
3554
|
+
__Math_min: {
|
3555
|
+
generate: (scope, decl) => {
|
3556
|
+
const out = [
|
3557
|
+
...number(Infinity)
|
3558
|
+
];
|
3559
|
+
|
3560
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3561
|
+
out.push(
|
3562
|
+
...generate(scope, decl.arguments[i]),
|
3563
|
+
[ Opcodes.f64_min ]
|
3564
|
+
);
|
3565
|
+
}
|
3566
|
+
|
3567
|
+
return out;
|
3568
|
+
},
|
3569
|
+
type: TYPES.number,
|
3570
|
+
notConstr: true,
|
3571
|
+
length: 2
|
3572
|
+
},
|
3573
|
+
|
3574
|
+
__console_log: {
|
3575
|
+
generate: (scope, decl) => {
|
3576
|
+
const out = [];
|
3577
|
+
|
3578
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3579
|
+
out.push(
|
3580
|
+
...generateCall(scope, {
|
3581
|
+
callee: {
|
3582
|
+
type: 'Identifier',
|
3583
|
+
name: '__Porffor_print'
|
3584
|
+
},
|
3585
|
+
arguments: [ decl.arguments[i] ]
|
3586
|
+
}),
|
3587
|
+
|
3588
|
+
// print space
|
3589
|
+
...number(32),
|
3590
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3591
|
+
);
|
3592
|
+
}
|
3593
|
+
|
3594
|
+
// print newline
|
3595
|
+
out.push(
|
3596
|
+
...number(10),
|
3597
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3598
|
+
);
|
3599
|
+
|
3600
|
+
return out;
|
3601
|
+
},
|
3602
|
+
type: TYPES.undefined,
|
3603
|
+
notConstr: true,
|
3604
|
+
length: 0
|
3605
|
+
}
|
3606
|
+
};
|
3065
3607
|
|
3066
3608
|
export default program => {
|
3067
3609
|
globals = {};
|
@@ -3071,20 +3613,23 @@ export default program => {
|
|
3071
3613
|
funcs = [];
|
3072
3614
|
funcIndex = {};
|
3073
3615
|
depth = [];
|
3074
|
-
arrays = new Map();
|
3075
3616
|
pages = new Map();
|
3076
3617
|
data = [];
|
3077
3618
|
currentFuncIndex = importedFuncs.length;
|
3078
3619
|
|
3079
3620
|
globalThis.valtype = 'f64';
|
3080
3621
|
|
3081
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3622
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3082
3623
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3083
3624
|
|
3084
3625
|
globalThis.valtypeBinary = Valtype[valtype];
|
3085
3626
|
|
3086
3627
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3087
3628
|
|
3629
|
+
globalThis.pageSize = PageSize;
|
3630
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3631
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3632
|
+
|
3088
3633
|
// set generic opcodes for current valtype
|
3089
3634
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3090
3635
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3093,10 +3638,10 @@ export default program => {
|
|
3093
3638
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3094
3639
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3095
3640
|
|
3096
|
-
Opcodes.i32_to = [ [
|
3097
|
-
Opcodes.i32_to_u = [ [
|
3098
|
-
Opcodes.i32_from = [ [
|
3099
|
-
Opcodes.i32_from_u = [ [
|
3641
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3642
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3643
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3644
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3100
3645
|
|
3101
3646
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3102
3647
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3109,10 +3654,6 @@ export default program => {
|
|
3109
3654
|
|
3110
3655
|
program.id = { name: 'main' };
|
3111
3656
|
|
3112
|
-
globalThis.pageSize = PageSize;
|
3113
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3114
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3115
|
-
|
3116
3657
|
const scope = {
|
3117
3658
|
locals: {},
|
3118
3659
|
localInd: 0
|
@@ -3123,7 +3664,7 @@ export default program => {
|
|
3123
3664
|
body: program.body
|
3124
3665
|
};
|
3125
3666
|
|
3126
|
-
if (
|
3667
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3127
3668
|
|
3128
3669
|
generateFunc(scope, program);
|
3129
3670
|
|
@@ -3140,7 +3681,11 @@ export default program => {
|
|
3140
3681
|
}
|
3141
3682
|
|
3142
3683
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3143
|
-
|
3684
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3685
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3686
|
+
} else {
|
3687
|
+
main.returns = [];
|
3688
|
+
}
|
3144
3689
|
}
|
3145
3690
|
|
3146
3691
|
if (lastInst[0] === Opcodes.call) {
|