porffor 0.2.0-eaee2da → 0.2.0-edb06b8
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 +147 -89
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +149 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/boolean.ts +20 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2070 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/function.ts +7 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +534 -0
- package/compiler/builtins/object.ts +6 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1080 -0
- package/compiler/builtins.js +450 -270
- package/compiler/{codeGen.js → codegen.js} +1117 -441
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1526 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +27 -42
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +54 -46
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,38 +25,44 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
62
|
+
const hasFuncWithName = name => {
|
63
|
+
const func = funcs.find(x => x.name === name);
|
64
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
65
|
+
};
|
58
66
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
67
|
switch (decl.type) {
|
60
68
|
case 'BinaryExpression':
|
@@ -104,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
112
|
return generateUnary(scope, decl);
|
105
113
|
|
106
114
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
115
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
116
|
|
109
117
|
case 'IfStatement':
|
110
118
|
return generateIf(scope, decl);
|
@@ -115,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
123
|
case 'WhileStatement':
|
116
124
|
return generateWhile(scope, decl);
|
117
125
|
|
126
|
+
case 'DoWhileStatement':
|
127
|
+
return generateDoWhile(scope, decl);
|
128
|
+
|
118
129
|
case 'ForOfStatement':
|
119
130
|
return generateForOf(scope, decl);
|
120
131
|
|
@@ -124,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
135
|
case 'ContinueStatement':
|
125
136
|
return generateContinue(scope, decl);
|
126
137
|
|
138
|
+
case 'LabeledStatement':
|
139
|
+
return generateLabel(scope, decl);
|
140
|
+
|
127
141
|
case 'EmptyStatement':
|
128
142
|
return generateEmpty(scope, decl);
|
129
143
|
|
@@ -137,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
151
|
return generateTry(scope, decl);
|
138
152
|
|
139
153
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
154
|
+
// todo: hook into terminal debugger
|
141
155
|
return [];
|
142
156
|
|
143
157
|
case 'ArrayExpression':
|
@@ -151,16 +165,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
165
|
const funcsBefore = funcs.length;
|
152
166
|
generate(scope, decl.declaration);
|
153
167
|
|
154
|
-
if (funcsBefore
|
168
|
+
if (funcsBefore !== funcs.length) {
|
169
|
+
// new func added
|
170
|
+
const newFunc = funcs[funcs.length - 1];
|
171
|
+
newFunc.export = true;
|
172
|
+
}
|
155
173
|
|
156
|
-
|
157
|
-
|
174
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
175
|
+
|
176
|
+
// const newFunc = funcs[funcs.length - 1];
|
177
|
+
// newFunc.export = true;
|
158
178
|
|
159
179
|
return [];
|
160
180
|
|
161
181
|
case 'TaggedTemplateExpression': {
|
162
182
|
const funcs = {
|
163
|
-
|
183
|
+
__Porffor_wasm: str => {
|
164
184
|
let out = [];
|
165
185
|
|
166
186
|
for (const line of str.split('\n')) {
|
@@ -168,8 +188,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
188
|
if (asm[0] === '') continue; // blank
|
169
189
|
|
170
190
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
191
|
+
const [ name, type ] = asm.slice(1);
|
192
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
193
|
continue;
|
174
194
|
}
|
175
195
|
|
@@ -179,52 +199,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
199
|
}
|
180
200
|
|
181
201
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
202
|
+
allocPage(scope, 'asm instrinsic');
|
183
203
|
// todo: add to store/load offset insts
|
184
204
|
continue;
|
185
205
|
}
|
186
206
|
|
187
207
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
208
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
209
|
|
190
210
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
const immediates = asm.slice(1).map(x =>
|
211
|
+
const immediates = asm.slice(1).map(x => {
|
212
|
+
const int = parseInt(x);
|
213
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
214
|
+
return int;
|
215
|
+
});
|
192
216
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
217
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
218
|
}
|
195
219
|
|
196
220
|
return out;
|
197
221
|
},
|
198
222
|
|
199
|
-
|
200
|
-
|
223
|
+
__Porffor_bs: str => [
|
224
|
+
...makeString(scope, str, global, name, true),
|
201
225
|
|
202
|
-
|
203
|
-
...number(
|
204
|
-
|
226
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
227
|
+
...number(TYPES.bytestring, Valtype.i32),
|
228
|
+
...setLastType(scope)
|
229
|
+
])
|
230
|
+
],
|
231
|
+
__Porffor_s: str => [
|
232
|
+
...makeString(scope, str, global, name, false),
|
205
233
|
|
206
|
-
|
207
|
-
...number(
|
208
|
-
|
209
|
-
]
|
210
|
-
|
211
|
-
}
|
234
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
235
|
+
...number(TYPES.string, Valtype.i32),
|
236
|
+
...setLastType(scope)
|
237
|
+
])
|
238
|
+
],
|
239
|
+
};
|
212
240
|
|
213
|
-
const
|
241
|
+
const func = decl.tag.name;
|
214
242
|
// hack for inline asm
|
215
|
-
if (!funcs[
|
243
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
244
|
+
|
245
|
+
const { quasis, expressions } = decl.quasi;
|
246
|
+
let str = quasis[0].value.raw;
|
247
|
+
|
248
|
+
for (let i = 0; i < expressions.length; i++) {
|
249
|
+
const e = expressions[i];
|
250
|
+
if (!e.name) {
|
251
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
252
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
253
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
254
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
255
|
+
|
256
|
+
str += quasis[i + 1].value.raw;
|
257
|
+
}
|
216
258
|
|
217
|
-
|
218
|
-
return funcs[name](str);
|
259
|
+
return funcs[func](str);
|
219
260
|
}
|
220
261
|
|
221
262
|
default:
|
222
|
-
|
223
|
-
|
263
|
+
// ignore typescript nodes
|
264
|
+
if (decl.type.startsWith('TS') ||
|
265
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
266
|
return [];
|
225
267
|
}
|
226
268
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
269
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
270
|
}
|
229
271
|
};
|
230
272
|
|
@@ -252,7 +294,7 @@ const lookupName = (scope, _name) => {
|
|
252
294
|
return [ undefined, undefined ];
|
253
295
|
};
|
254
296
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
297
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
298
|
...generateThrow(scope, {
|
257
299
|
argument: {
|
258
300
|
type: 'NewExpression',
|
@@ -274,25 +316,33 @@ const generateIdent = (scope, decl) => {
|
|
274
316
|
const name = mapName(rawName);
|
275
317
|
let local = scope.locals[rawName];
|
276
318
|
|
277
|
-
if (builtinVars
|
319
|
+
if (Object.hasOwn(builtinVars, name)) {
|
278
320
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
321
|
+
|
322
|
+
let wasm = builtinVars[name];
|
323
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
324
|
+
return wasm.slice();
|
325
|
+
}
|
326
|
+
|
327
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
328
|
+
// todo: return an actual something
|
329
|
+
return number(1);
|
280
330
|
}
|
281
331
|
|
282
|
-
if (
|
332
|
+
if (isExistingProtoFunc(name)) {
|
283
333
|
// todo: return an actual something
|
284
334
|
return number(1);
|
285
335
|
}
|
286
336
|
|
287
|
-
if (local === undefined) {
|
337
|
+
if (local?.idx === undefined) {
|
288
338
|
// no local var with name
|
289
|
-
if (
|
290
|
-
if (funcIndex
|
339
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
340
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
341
|
|
292
|
-
if (globals
|
342
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
293
343
|
}
|
294
344
|
|
295
|
-
if (local === undefined && rawName.startsWith('__')) {
|
345
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
296
346
|
// return undefined if unknown key in already known var
|
297
347
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
298
348
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -301,7 +351,7 @@ const generateIdent = (scope, decl) => {
|
|
301
351
|
if (!parentLookup[1]) return number(UNDEFINED);
|
302
352
|
}
|
303
353
|
|
304
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
354
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
305
355
|
|
306
356
|
return [ [ Opcodes.local_get, local.idx ] ];
|
307
357
|
};
|
@@ -314,14 +364,18 @@ const generateReturn = (scope, decl) => {
|
|
314
364
|
// just bare "return"
|
315
365
|
return [
|
316
366
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
367
|
+
...(scope.returnType != null ? [] : [
|
368
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
369
|
+
]),
|
318
370
|
[ Opcodes.return ]
|
319
371
|
];
|
320
372
|
}
|
321
373
|
|
322
374
|
return [
|
323
375
|
...generate(scope, decl.argument),
|
324
|
-
...
|
376
|
+
...(scope.returnType != null ? [] : [
|
377
|
+
...getNodeType(scope, decl.argument)
|
378
|
+
]),
|
325
379
|
[ Opcodes.return ]
|
326
380
|
];
|
327
381
|
};
|
@@ -335,7 +389,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
389
|
return idx;
|
336
390
|
};
|
337
391
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
392
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
393
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
394
|
|
340
395
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
396
|
const checks = {
|
@@ -344,7 +399,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
399
|
'??': nullish
|
345
400
|
};
|
346
401
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
402
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
403
|
|
349
404
|
// generic structure for {a} OP {b}
|
350
405
|
// -->
|
@@ -352,8 +407,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
407
|
|
353
408
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
409
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
410
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
411
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
412
|
|
358
413
|
const canInt = leftIsInt && rightIsInt;
|
359
414
|
|
@@ -370,12 +425,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
425
|
...right,
|
371
426
|
// note type
|
372
427
|
...rightType,
|
373
|
-
setLastType(scope),
|
428
|
+
...setLastType(scope),
|
374
429
|
[ Opcodes.else ],
|
375
430
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
431
|
// note type
|
377
432
|
...leftType,
|
378
|
-
setLastType(scope),
|
433
|
+
...setLastType(scope),
|
379
434
|
[ Opcodes.end ],
|
380
435
|
Opcodes.i32_from
|
381
436
|
];
|
@@ -389,17 +444,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
444
|
...right,
|
390
445
|
// note type
|
391
446
|
...rightType,
|
392
|
-
setLastType(scope),
|
447
|
+
...setLastType(scope),
|
393
448
|
[ Opcodes.else ],
|
394
449
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
450
|
// note type
|
396
451
|
...leftType,
|
397
|
-
setLastType(scope),
|
452
|
+
...setLastType(scope),
|
398
453
|
[ Opcodes.end ]
|
399
454
|
];
|
400
455
|
};
|
401
456
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
457
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
458
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
459
|
// todo: convert left and right to strings if not
|
405
460
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +464,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
464
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
465
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
466
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
467
|
+
if (assign && Prefs.aotPointerOpt) {
|
468
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
469
|
|
418
470
|
return [
|
419
471
|
// setup right
|
@@ -438,11 +490,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
490
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
491
|
|
440
492
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
493
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
494
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
495
|
|
444
496
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
497
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
498
|
[ Opcodes.i32_mul ],
|
447
499
|
[ Opcodes.i32_add ],
|
448
500
|
|
@@ -451,9 +503,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
503
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
504
|
[ Opcodes.i32_add ],
|
453
505
|
|
454
|
-
// size = right length *
|
506
|
+
// size = right length * sizeof valtype
|
455
507
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
508
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
509
|
[ Opcodes.i32_mul ],
|
458
510
|
|
459
511
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -511,11 +563,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
563
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
564
|
|
513
565
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
566
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
567
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
568
|
|
517
569
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
570
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
571
|
[ Opcodes.i32_mul ],
|
520
572
|
[ Opcodes.i32_add ],
|
521
573
|
|
@@ -524,9 +576,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
576
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
577
|
[ Opcodes.i32_add ],
|
526
578
|
|
527
|
-
// size = right length *
|
579
|
+
// size = right length * sizeof valtype
|
528
580
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
581
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
582
|
[ Opcodes.i32_mul ],
|
531
583
|
|
532
584
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +588,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
588
|
];
|
537
589
|
};
|
538
590
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
591
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
592
|
// todo: this should be rewritten into a func
|
541
593
|
// todo: convert left and right to strings if not
|
542
594
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +597,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
597
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
598
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
599
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
600
|
|
550
601
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
602
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -573,7 +624,6 @@ const compareStrings = (scope, left, right) => {
|
|
573
624
|
|
574
625
|
[ Opcodes.local_get, rightPointer ],
|
575
626
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
577
627
|
|
578
628
|
// fast path: check leftLength != rightLength
|
579
629
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +638,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
638
|
...number(0, Valtype.i32),
|
589
639
|
[ Opcodes.local_set, index ],
|
590
640
|
|
591
|
-
// setup index end as length * sizeof
|
641
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
642
|
// we do this instead of having to do mul/div each iter for perf™
|
593
643
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
644
|
+
...(bytestrings ? [] : [
|
645
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
646
|
+
[ Opcodes.i32_mul ],
|
647
|
+
]),
|
596
648
|
[ Opcodes.local_set, indexEnd ],
|
597
649
|
|
598
650
|
// iterate over each char and check if eq
|
@@ -602,13 +654,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
654
|
[ Opcodes.local_get, index ],
|
603
655
|
[ Opcodes.local_get, leftPointer ],
|
604
656
|
[ Opcodes.i32_add ],
|
605
|
-
|
657
|
+
bytestrings ?
|
658
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
659
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
660
|
|
607
661
|
// fetch right
|
608
662
|
[ Opcodes.local_get, index ],
|
609
663
|
[ Opcodes.local_get, rightPointer ],
|
610
664
|
[ Opcodes.i32_add ],
|
611
|
-
|
665
|
+
bytestrings ?
|
666
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
667
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
668
|
|
613
669
|
// not equal, "return" false
|
614
670
|
[ Opcodes.i32_ne ],
|
@@ -617,13 +673,13 @@ const compareStrings = (scope, left, right) => {
|
|
617
673
|
[ Opcodes.br, 2 ],
|
618
674
|
[ Opcodes.end ],
|
619
675
|
|
620
|
-
// index += sizeof
|
676
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
677
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
678
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
679
|
[ Opcodes.i32_add ],
|
624
680
|
[ Opcodes.local_tee, index ],
|
625
681
|
|
626
|
-
// if index != index end (length * sizeof
|
682
|
+
// if index != index end (length * sizeof valtype), loop
|
627
683
|
[ Opcodes.local_get, indexEnd ],
|
628
684
|
[ Opcodes.i32_ne ],
|
629
685
|
[ Opcodes.br_if, 0 ],
|
@@ -644,16 +700,18 @@ const compareStrings = (scope, left, right) => {
|
|
644
700
|
};
|
645
701
|
|
646
702
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
703
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
648
704
|
...wasm,
|
649
705
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
706
|
];
|
707
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
708
|
|
652
|
-
const
|
709
|
+
const useTmp = knownType(scope, type) == null;
|
710
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
711
|
|
654
712
|
const def = [
|
655
713
|
// if value != 0
|
656
|
-
[ Opcodes.local_get, tmp ],
|
714
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
657
715
|
|
658
716
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
659
717
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -665,16 +723,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
665
723
|
|
666
724
|
return [
|
667
725
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
726
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
727
|
|
670
728
|
...typeSwitch(scope, type, {
|
671
729
|
// [TYPES.number]: def,
|
672
|
-
[TYPES.
|
730
|
+
[TYPES.array]: [
|
673
731
|
// arrays are always truthy
|
674
732
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
733
|
],
|
676
734
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
735
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
736
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
737
|
|
680
738
|
// get length
|
@@ -685,8 +743,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
743
|
[ Opcodes.i32_eqz ], */
|
686
744
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
745
|
],
|
688
|
-
[TYPES.
|
689
|
-
|
746
|
+
[TYPES.bytestring]: [ // duplicate of string
|
747
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
748
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
749
|
|
692
750
|
// get length
|
@@ -700,18 +758,20 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
758
|
};
|
701
759
|
|
702
760
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
761
|
+
const useTmp = knownType(scope, type) == null;
|
762
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
763
|
+
|
704
764
|
return [
|
705
765
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
766
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
767
|
|
708
768
|
...typeSwitch(scope, type, {
|
709
|
-
[TYPES.
|
769
|
+
[TYPES.array]: [
|
710
770
|
// arrays are always truthy
|
711
771
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
772
|
],
|
713
773
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
774
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
775
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
776
|
|
717
777
|
// get length
|
@@ -721,8 +781,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
721
781
|
[ Opcodes.i32_eqz ],
|
722
782
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
783
|
],
|
724
|
-
[TYPES.
|
725
|
-
[ Opcodes.local_get, tmp ],
|
784
|
+
[TYPES.bytestring]: [ // duplicate of string
|
785
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
786
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
787
|
|
728
788
|
// get length
|
@@ -734,7 +794,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
794
|
],
|
735
795
|
default: [
|
736
796
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
797
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
798
|
|
739
799
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
800
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +804,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
804
|
};
|
745
805
|
|
746
806
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
807
|
+
const useTmp = knownType(scope, type) == null;
|
808
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
809
|
+
|
748
810
|
return [
|
749
811
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
812
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
813
|
|
752
814
|
...typeSwitch(scope, type, {
|
753
815
|
[TYPES.undefined]: [
|
@@ -756,7 +818,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
818
|
],
|
757
819
|
[TYPES.object]: [
|
758
820
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
821
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
822
|
|
761
823
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
824
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,11 +847,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
847
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
848
|
}
|
787
849
|
|
850
|
+
const knownLeft = knownType(scope, leftType);
|
851
|
+
const knownRight = knownType(scope, rightType);
|
852
|
+
|
788
853
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
854
|
const strictOp = op === '===' || op === '!==';
|
790
855
|
|
791
856
|
const startOut = [], endOut = [];
|
792
|
-
const
|
857
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
858
|
|
794
859
|
// if strict (in)equal check types match
|
795
860
|
if (strictOp) {
|
@@ -834,31 +899,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
899
|
// todo: if equality op and an operand is undefined, return false
|
835
900
|
// todo: niche null hell with 0
|
836
901
|
|
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
|
-
|
902
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
903
|
+
if (op === '+') {
|
904
|
+
// todo: this should be dynamic too but for now only static
|
905
|
+
// string concat (a + b)
|
906
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
907
|
+
}
|
908
|
+
|
909
|
+
// not an equality op, NaN
|
910
|
+
if (!eqOp) return number(NaN);
|
911
|
+
|
912
|
+
// else leave bool ops
|
913
|
+
// todo: convert string to number if string and number/bool
|
914
|
+
// todo: string (>|>=|<|<=) string
|
915
|
+
|
916
|
+
// string comparison
|
917
|
+
if (op === '===' || op === '==') {
|
918
|
+
return compareStrings(scope, left, right);
|
919
|
+
}
|
920
|
+
|
921
|
+
if (op === '!==' || op === '!=') {
|
922
|
+
return [
|
923
|
+
...compareStrings(scope, left, right),
|
924
|
+
[ Opcodes.i32_eqz ]
|
925
|
+
];
|
926
|
+
}
|
927
|
+
}
|
928
|
+
|
929
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
930
|
+
if (op === '+') {
|
931
|
+
// todo: this should be dynamic too but for now only static
|
932
|
+
// string concat (a + b)
|
933
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
934
|
+
}
|
935
|
+
|
936
|
+
// not an equality op, NaN
|
937
|
+
if (!eqOp) return number(NaN);
|
938
|
+
|
939
|
+
// else leave bool ops
|
940
|
+
// todo: convert string to number if string and number/bool
|
941
|
+
// todo: string (>|>=|<|<=) string
|
942
|
+
|
943
|
+
// string comparison
|
944
|
+
if (op === '===' || op === '==') {
|
945
|
+
return compareStrings(scope, left, right, true);
|
946
|
+
}
|
947
|
+
|
948
|
+
if (op === '!==' || op === '!=') {
|
949
|
+
return [
|
950
|
+
...compareStrings(scope, left, right, true),
|
951
|
+
[ Opcodes.i32_eqz ]
|
952
|
+
];
|
953
|
+
}
|
954
|
+
}
|
862
955
|
|
863
956
|
let ops = operatorOpcode[valtype][op];
|
864
957
|
|
@@ -868,33 +961,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
961
|
includeBuiltin(scope, builtinName);
|
869
962
|
const idx = funcIndex[builtinName];
|
870
963
|
|
871
|
-
return
|
964
|
+
return finalize([
|
872
965
|
...left,
|
873
966
|
...right,
|
874
967
|
[ Opcodes.call, idx ]
|
875
968
|
]);
|
876
969
|
}
|
877
970
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
971
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
972
|
|
880
973
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
974
|
ops = [ ops ];
|
882
975
|
|
883
976
|
let tmpLeft, tmpRight;
|
884
977
|
// 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
|
-
}
|
978
|
+
// todo: intelligent partial skip later
|
979
|
+
// if neither known are string, stop this madness
|
980
|
+
// we already do known checks earlier, so don't need to recheck
|
894
981
|
|
982
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
983
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
984
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
985
|
|
986
|
+
// returns false for one string, one not - but more ops/slower
|
987
|
+
// ops.unshift(...stringOnly([
|
988
|
+
// // if left is string
|
989
|
+
// ...leftType,
|
990
|
+
// ...number(TYPES.string, Valtype.i32),
|
991
|
+
// [ Opcodes.i32_eq ],
|
992
|
+
|
993
|
+
// // if right is string
|
994
|
+
// ...rightType,
|
995
|
+
// ...number(TYPES.string, Valtype.i32),
|
996
|
+
// [ Opcodes.i32_eq ],
|
997
|
+
|
998
|
+
// // if either are true
|
999
|
+
// [ Opcodes.i32_or ],
|
1000
|
+
// [ Opcodes.if, Blocktype.void ],
|
1001
|
+
|
1002
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
1003
|
+
// // if left is not string
|
1004
|
+
// ...leftType,
|
1005
|
+
// ...number(TYPES.string, Valtype.i32),
|
1006
|
+
// [ Opcodes.i32_ne ],
|
1007
|
+
|
1008
|
+
// // if right is not string
|
1009
|
+
// ...rightType,
|
1010
|
+
// ...number(TYPES.string, Valtype.i32),
|
1011
|
+
// [ Opcodes.i32_ne ],
|
1012
|
+
|
1013
|
+
// // if either are true
|
1014
|
+
// [ Opcodes.i32_or ],
|
1015
|
+
// [ Opcodes.if, Blocktype.void ],
|
1016
|
+
// ...number(0, Valtype.i32),
|
1017
|
+
// [ Opcodes.br, 2 ],
|
1018
|
+
// [ Opcodes.end ],
|
1019
|
+
|
1020
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1021
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1022
|
+
// [ Opcodes.br, 1 ],
|
1023
|
+
// [ Opcodes.end ],
|
1024
|
+
// ]));
|
1025
|
+
|
1026
|
+
// does not handle one string, one not (such cases go past)
|
898
1027
|
ops.unshift(...stringOnly([
|
899
1028
|
// if left is string
|
900
1029
|
...leftType,
|
@@ -906,30 +1035,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
1035
|
...number(TYPES.string, Valtype.i32),
|
907
1036
|
[ Opcodes.i32_eq ],
|
908
1037
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
1038
|
+
// if both are true
|
1039
|
+
[ Opcodes.i32_and ],
|
911
1040
|
[ Opcodes.if, Blocktype.void ],
|
1041
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1042
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1043
|
+
[ Opcodes.br, 1 ],
|
1044
|
+
[ Opcodes.end ],
|
912
1045
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
1046
|
+
// if left is bytestring
|
915
1047
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
1048
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1049
|
+
[ Opcodes.i32_eq ],
|
918
1050
|
|
919
|
-
// if right is
|
1051
|
+
// if right is bytestring
|
920
1052
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1053
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1054
|
+
[ Opcodes.i32_eq ],
|
923
1055
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1056
|
+
// if both are true
|
1057
|
+
[ Opcodes.i32_and ],
|
926
1058
|
[ Opcodes.if, Blocktype.void ],
|
927
|
-
...
|
928
|
-
[ Opcodes.br, 1 ],
|
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 ] ]),
|
1059
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1060
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1061
|
[ Opcodes.br, 1 ],
|
935
1062
|
[ Opcodes.end ],
|
@@ -941,9 +1068,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1068
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1069
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1070
|
// }
|
944
|
-
}
|
1071
|
+
}
|
945
1072
|
|
946
|
-
return
|
1073
|
+
return finalize([
|
947
1074
|
...left,
|
948
1075
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1076
|
...right,
|
@@ -960,7 +1087,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1087
|
return out;
|
961
1088
|
};
|
962
1089
|
|
963
|
-
const
|
1090
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1091
|
+
return func({ name, params, locals, returns, localInd }, {
|
1092
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1093
|
+
builtin: name => {
|
1094
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1095
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1096
|
+
includeBuiltin(null, name);
|
1097
|
+
idx = funcIndex[name];
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
return idx;
|
1101
|
+
}
|
1102
|
+
});
|
1103
|
+
};
|
1104
|
+
|
1105
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
964
1106
|
const existing = funcs.find(x => x.name === name);
|
965
1107
|
if (existing) return existing;
|
966
1108
|
|
@@ -972,18 +1114,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1114
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1115
|
}
|
974
1116
|
|
975
|
-
|
976
|
-
const
|
977
|
-
|
978
|
-
|
979
|
-
locals,
|
980
|
-
returns,
|
981
|
-
localInd: allLocals.length,
|
982
|
-
};
|
983
|
-
|
984
|
-
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
1117
|
+
for (const x of _data) {
|
1118
|
+
const copy = { ...x };
|
1119
|
+
copy.offset += pages.size * pageSize;
|
1120
|
+
data.push(copy);
|
985
1121
|
}
|
986
1122
|
|
1123
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1124
|
+
|
987
1125
|
let baseGlobalIdx, i = 0;
|
988
1126
|
for (const type of globalTypes) {
|
989
1127
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1006,7 +1144,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1006
1144
|
params,
|
1007
1145
|
locals,
|
1008
1146
|
returns,
|
1009
|
-
returnType:
|
1147
|
+
returnType: returnType ?? TYPES.number,
|
1010
1148
|
wasm,
|
1011
1149
|
internal: true,
|
1012
1150
|
index: currentFuncIndex++
|
@@ -1029,6 +1167,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1167
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1168
|
};
|
1031
1169
|
|
1170
|
+
// potential future ideas for nan boxing (unused):
|
1032
1171
|
// T = JS type, V = value/pointer
|
1033
1172
|
// 0bTTT
|
1034
1173
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1042,7 +1181,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1042
1181
|
// js type: 4 bits
|
1043
1182
|
// internal type: ? bits
|
1044
1183
|
// pointer: 32 bits
|
1045
|
-
|
1046
1184
|
// generic
|
1047
1185
|
// 1 23 4 5
|
1048
1186
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1052,49 +1190,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1190
|
// 4: internal type
|
1053
1191
|
// 5: pointer
|
1054
1192
|
|
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'
|
1193
|
+
const isExistingProtoFunc = name => {
|
1194
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1195
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1196
|
+
|
1197
|
+
return false;
|
1084
1198
|
};
|
1085
1199
|
|
1086
1200
|
const getType = (scope, _name) => {
|
1087
1201
|
const name = mapName(_name);
|
1088
1202
|
|
1203
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1204
|
+
|
1205
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1089
1206
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1207
|
+
|
1208
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1090
1209
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1210
|
|
1092
1211
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1212
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1213
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1214
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1215
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1216
|
|
1099
1217
|
return number(type, Valtype.i32);
|
1100
1218
|
};
|
@@ -1117,23 +1235,24 @@ const setType = (scope, _name, type) => {
|
|
1117
1235
|
];
|
1118
1236
|
|
1119
1237
|
// throw new Error('could not find var');
|
1238
|
+
return [];
|
1120
1239
|
};
|
1121
1240
|
|
1122
1241
|
const getLastType = scope => {
|
1123
1242
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1243
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1244
|
};
|
1126
1245
|
|
1127
1246
|
const setLastType = scope => {
|
1128
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1247
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1129
1248
|
};
|
1130
1249
|
|
1131
1250
|
const getNodeType = (scope, node) => {
|
1132
1251
|
const inner = () => {
|
1133
1252
|
if (node.type === 'Literal') {
|
1134
|
-
if (node.regex) return TYPES.
|
1253
|
+
if (node.regex) return TYPES.regexp;
|
1135
1254
|
|
1136
|
-
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.
|
1255
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1137
1256
|
|
1138
1257
|
return TYPES[typeof node.value];
|
1139
1258
|
}
|
@@ -1150,13 +1269,25 @@ const getNodeType = (scope, node) => {
|
|
1150
1269
|
const name = node.callee.name;
|
1151
1270
|
if (!name) {
|
1152
1271
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1272
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1273
|
|
1155
1274
|
// presume
|
1156
1275
|
// todo: warn here?
|
1157
1276
|
return TYPES.number;
|
1158
1277
|
}
|
1159
1278
|
|
1279
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1280
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1281
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1282
|
+
|
1283
|
+
// presume
|
1284
|
+
// todo: warn here?
|
1285
|
+
return TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1289
|
+
}
|
1290
|
+
|
1160
1291
|
const func = funcs.find(x => x.name === name);
|
1161
1292
|
|
1162
1293
|
if (func) {
|
@@ -1164,7 +1295,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1295
|
if (func.returnType) return func.returnType;
|
1165
1296
|
}
|
1166
1297
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1298
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1299
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1300
|
|
1170
1301
|
// check if this is a prototype function
|
@@ -1175,11 +1306,16 @@ const getNodeType = (scope, node) => {
|
|
1175
1306
|
const spl = name.slice(2).split('_');
|
1176
1307
|
|
1177
1308
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.
|
1309
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1179
1310
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
1311
|
}
|
1181
1312
|
|
1182
|
-
if (
|
1313
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1314
|
+
// todo: return undefined for non-returning ops
|
1315
|
+
return TYPES.number;
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1319
|
|
1184
1320
|
// presume
|
1185
1321
|
// todo: warn here?
|
@@ -1222,11 +1358,20 @@ const getNodeType = (scope, node) => {
|
|
1222
1358
|
}
|
1223
1359
|
|
1224
1360
|
if (node.type === 'ArrayExpression') {
|
1225
|
-
return TYPES.
|
1361
|
+
return TYPES.array;
|
1226
1362
|
}
|
1227
1363
|
|
1228
1364
|
if (node.type === 'BinaryExpression') {
|
1229
1365
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1366
|
+
if (node.operator !== '+') return TYPES.number;
|
1367
|
+
|
1368
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1369
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1370
|
+
|
1371
|
+
// todo: this should be dynamic but for now only static
|
1372
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1373
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1374
|
+
|
1230
1375
|
return TYPES.number;
|
1231
1376
|
|
1232
1377
|
// todo: string concat types
|
@@ -1251,20 +1396,41 @@ const getNodeType = (scope, node) => {
|
|
1251
1396
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1397
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1398
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1399
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1255
1400
|
|
1256
1401
|
return TYPES.number;
|
1257
1402
|
}
|
1258
1403
|
|
1259
1404
|
if (node.type === 'MemberExpression') {
|
1405
|
+
// hack: if something.name, string type
|
1406
|
+
if (node.property.name === 'name') {
|
1407
|
+
if (hasFuncWithName(node.object.name)) {
|
1408
|
+
return TYPES.bytestring;
|
1409
|
+
} else {
|
1410
|
+
return TYPES.undefined;
|
1411
|
+
}
|
1412
|
+
}
|
1413
|
+
|
1260
1414
|
// hack: if something.length, number type
|
1261
1415
|
if (node.property.name === 'length') return TYPES.number;
|
1262
1416
|
|
1263
|
-
//
|
1417
|
+
// ts hack
|
1418
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1419
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1420
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1421
|
+
|
1422
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1423
|
+
|
1424
|
+
// presume
|
1264
1425
|
return TYPES.number;
|
1265
1426
|
}
|
1266
1427
|
|
1267
|
-
if (
|
1428
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1429
|
+
// hack
|
1430
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1434
|
|
1269
1435
|
// presume
|
1270
1436
|
// todo: warn here?
|
@@ -1277,28 +1443,11 @@ const getNodeType = (scope, node) => {
|
|
1277
1443
|
return ret;
|
1278
1444
|
};
|
1279
1445
|
|
1280
|
-
const toString = (scope, wasm, type) => {
|
1281
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1282
|
-
return [
|
1283
|
-
...wasm,
|
1284
|
-
[ Opcodes.local_set, tmp ],
|
1285
|
-
|
1286
|
-
...typeSwitch(scope, type, {
|
1287
|
-
[TYPES.string]: [
|
1288
|
-
[ Opcodes.local_get, tmp ]
|
1289
|
-
],
|
1290
|
-
[TYPES.undefined]: [
|
1291
|
-
// [ Opcodes.]
|
1292
|
-
]
|
1293
|
-
})
|
1294
|
-
]
|
1295
|
-
};
|
1296
|
-
|
1297
1446
|
const generateLiteral = (scope, decl, global, name) => {
|
1298
1447
|
if (decl.value === null) return number(NULL);
|
1299
1448
|
|
1449
|
+
// hack: just return 1 for regex literals
|
1300
1450
|
if (decl.regex) {
|
1301
|
-
scope.regex[name] = decl.regex;
|
1302
1451
|
return number(1);
|
1303
1452
|
}
|
1304
1453
|
|
@@ -1314,7 +1463,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1314
1463
|
return makeString(scope, decl.value, global, name);
|
1315
1464
|
|
1316
1465
|
default:
|
1317
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1466
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1318
1467
|
}
|
1319
1468
|
};
|
1320
1469
|
|
@@ -1323,6 +1472,8 @@ const countLeftover = wasm => {
|
|
1323
1472
|
|
1324
1473
|
for (let i = 0; i < wasm.length; i++) {
|
1325
1474
|
const inst = wasm[i];
|
1475
|
+
if (inst[0] == null) continue;
|
1476
|
+
|
1326
1477
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1327
1478
|
if (inst[0] === Opcodes.if) count--;
|
1328
1479
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1331,18 +1482,25 @@ const countLeftover = wasm => {
|
|
1331
1482
|
if (inst[0] === Opcodes.end) depth--;
|
1332
1483
|
|
1333
1484
|
if (depth === 0)
|
1334
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1485
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1335
1486
|
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)) {}
|
1336
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1487
|
+
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++;
|
1337
1488
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1338
1489
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1339
1490
|
else if (inst[0] === Opcodes.return) count = 0;
|
1340
1491
|
else if (inst[0] === Opcodes.call) {
|
1341
1492
|
let func = funcs.find(x => x.index === inst[1]);
|
1342
|
-
if (
|
1343
|
-
count
|
1344
|
-
} else
|
1345
|
-
|
1493
|
+
if (inst[1] === -1) {
|
1494
|
+
// todo: count for calling self
|
1495
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1496
|
+
count -= importedFuncs[inst[1]].params;
|
1497
|
+
count += importedFuncs[inst[1]].returns;
|
1498
|
+
} else {
|
1499
|
+
if (func) {
|
1500
|
+
count -= func.params.length;
|
1501
|
+
} else count--;
|
1502
|
+
if (func) count += func.returns.length;
|
1503
|
+
}
|
1346
1504
|
} else count--;
|
1347
1505
|
|
1348
1506
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1434,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1434
1592
|
name = func.name;
|
1435
1593
|
}
|
1436
1594
|
|
1437
|
-
if (name === 'eval' && decl.arguments[0]
|
1595
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1438
1596
|
// literal eval hack
|
1439
|
-
const code = decl.arguments[0]
|
1440
|
-
|
1597
|
+
const code = decl.arguments[0]?.value ?? '';
|
1598
|
+
|
1599
|
+
let parsed;
|
1600
|
+
try {
|
1601
|
+
parsed = parse(code, []);
|
1602
|
+
} catch (e) {
|
1603
|
+
if (e.name === 'SyntaxError') {
|
1604
|
+
// throw syntax errors of evals at runtime instead
|
1605
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1606
|
+
}
|
1607
|
+
|
1608
|
+
throw e;
|
1609
|
+
}
|
1441
1610
|
|
1442
1611
|
const out = generate(scope, {
|
1443
1612
|
type: 'BlockStatement',
|
@@ -1451,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1451
1620
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1452
1621
|
out.push(
|
1453
1622
|
...getNodeType(scope, finalStatement),
|
1454
|
-
setLastType(scope)
|
1623
|
+
...setLastType(scope)
|
1455
1624
|
);
|
1456
1625
|
} else if (countLeftover(out) === 0) {
|
1457
1626
|
out.push(...number(UNDEFINED));
|
1458
1627
|
out.push(
|
1459
1628
|
...number(TYPES.undefined, Valtype.i32),
|
1460
|
-
setLastType(scope)
|
1629
|
+
...setLastType(scope)
|
1461
1630
|
);
|
1462
1631
|
}
|
1463
1632
|
|
@@ -1479,29 +1648,39 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1479
1648
|
|
1480
1649
|
target = { ...decl.callee };
|
1481
1650
|
target.name = spl.slice(0, -1).join('_');
|
1651
|
+
|
1652
|
+
// failed to lookup name, abort
|
1653
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1482
1654
|
}
|
1483
1655
|
|
1484
1656
|
// literal.func()
|
1485
1657
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1486
1658
|
// megahack for /regex/.func()
|
1487
|
-
|
1488
|
-
|
1489
|
-
const
|
1659
|
+
const funcName = decl.callee.property.name;
|
1660
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1661
|
+
const regex = decl.callee.object.regex.pattern;
|
1662
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1663
|
+
|
1664
|
+
if (!funcIndex[rhemynName]) {
|
1665
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1490
1666
|
|
1491
|
-
|
1492
|
-
|
1667
|
+
funcIndex[func.name] = func.index;
|
1668
|
+
funcs.push(func);
|
1669
|
+
}
|
1493
1670
|
|
1671
|
+
const idx = funcIndex[rhemynName];
|
1494
1672
|
return [
|
1495
1673
|
// make string arg
|
1496
1674
|
...generate(scope, decl.arguments[0]),
|
1675
|
+
Opcodes.i32_to_u,
|
1676
|
+
...getNodeType(scope, decl.arguments[0]),
|
1497
1677
|
|
1498
1678
|
// call regex func
|
1499
|
-
Opcodes.
|
1500
|
-
[ Opcodes.call, func.index ],
|
1679
|
+
[ Opcodes.call, idx ],
|
1501
1680
|
Opcodes.i32_from_u,
|
1502
1681
|
|
1503
1682
|
...number(TYPES.boolean, Valtype.i32),
|
1504
|
-
setLastType(scope)
|
1683
|
+
...setLastType(scope)
|
1505
1684
|
];
|
1506
1685
|
}
|
1507
1686
|
|
@@ -1526,13 +1705,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1526
1705
|
// }
|
1527
1706
|
|
1528
1707
|
if (protoName) {
|
1708
|
+
const protoBC = {};
|
1709
|
+
|
1710
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1711
|
+
|
1712
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1713
|
+
for (const x of builtinProtoCands) {
|
1714
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1715
|
+
if (type == null) continue;
|
1716
|
+
|
1717
|
+
protoBC[type] = generateCall(scope, {
|
1718
|
+
callee: {
|
1719
|
+
type: 'Identifier',
|
1720
|
+
name: x
|
1721
|
+
},
|
1722
|
+
arguments: [ target, ...decl.arguments ],
|
1723
|
+
_protoInternalCall: true
|
1724
|
+
});
|
1725
|
+
}
|
1726
|
+
}
|
1727
|
+
|
1529
1728
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1530
|
-
|
1531
|
-
if (f) acc[x] = f;
|
1729
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1532
1730
|
return acc;
|
1533
1731
|
}, {});
|
1534
1732
|
|
1535
|
-
// no prototype function candidates, ignore
|
1536
1733
|
if (Object.keys(protoCands).length > 0) {
|
1537
1734
|
// use local for cached i32 length as commonly used
|
1538
1735
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1550,7 +1747,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1550
1747
|
|
1551
1748
|
let allOptUnused = true;
|
1552
1749
|
let lengthI32CacheUsed = false;
|
1553
|
-
const protoBC = {};
|
1554
1750
|
for (const x in protoCands) {
|
1555
1751
|
const protoFunc = protoCands[x];
|
1556
1752
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1558,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1558
1754
|
...RTArrayUtil.getLength(getPointer),
|
1559
1755
|
|
1560
1756
|
...number(TYPES.number, Valtype.i32),
|
1561
|
-
setLastType(scope)
|
1757
|
+
...setLastType(scope)
|
1562
1758
|
];
|
1563
1759
|
continue;
|
1564
1760
|
}
|
@@ -1595,7 +1791,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1595
1791
|
...protoOut,
|
1596
1792
|
|
1597
1793
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1598
|
-
setLastType(scope),
|
1794
|
+
...setLastType(scope),
|
1599
1795
|
[ Opcodes.end ]
|
1600
1796
|
];
|
1601
1797
|
}
|
@@ -1621,10 +1817,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1621
1817
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1622
1818
|
];
|
1623
1819
|
}
|
1820
|
+
|
1821
|
+
if (Object.keys(protoBC).length > 0) {
|
1822
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1823
|
+
...protoBC,
|
1824
|
+
|
1825
|
+
// TODO: error better
|
1826
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1827
|
+
}, valtypeBinary);
|
1828
|
+
}
|
1624
1829
|
}
|
1625
1830
|
|
1626
1831
|
// TODO: only allows callee as literal
|
1627
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1832
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1628
1833
|
|
1629
1834
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1630
1835
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1634,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1634
1839
|
idx = funcIndex[name];
|
1635
1840
|
|
1636
1841
|
// infer arguments types from builtins params
|
1637
|
-
const func = funcs.find(x => x.name === name);
|
1638
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
}
|
1842
|
+
// const func = funcs.find(x => x.name === name);
|
1843
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1844
|
+
// const arg = decl.arguments[i];
|
1845
|
+
// if (!arg.name) continue;
|
1846
|
+
|
1847
|
+
// const local = scope.locals[arg.name];
|
1848
|
+
// if (!local) continue;
|
1849
|
+
|
1850
|
+
// local.type = func.params[i];
|
1851
|
+
// if (local.type === Valtype.v128) {
|
1852
|
+
// // specify vec subtype inferred from last vec type in function name
|
1853
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1854
|
+
// }
|
1855
|
+
// }
|
1651
1856
|
}
|
1652
1857
|
|
1653
1858
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1657,16 +1862,62 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1657
1862
|
idx = -1;
|
1658
1863
|
}
|
1659
1864
|
|
1865
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1866
|
+
const wasmOps = {
|
1867
|
+
// pointer, align, offset
|
1868
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1869
|
+
// pointer, value, align, offset
|
1870
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1871
|
+
// pointer, align, offset
|
1872
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1873
|
+
// pointer, value, align, offset
|
1874
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1875
|
+
// pointer, align, offset
|
1876
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1877
|
+
// pointer, value, align, offset
|
1878
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1879
|
+
|
1880
|
+
// pointer, align, offset
|
1881
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1882
|
+
// pointer, value, align, offset
|
1883
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1884
|
+
|
1885
|
+
// value
|
1886
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1887
|
+
};
|
1888
|
+
|
1889
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1890
|
+
|
1891
|
+
if (wasmOps[opName]) {
|
1892
|
+
const op = wasmOps[opName];
|
1893
|
+
|
1894
|
+
const argOut = [];
|
1895
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1896
|
+
...generate(scope, decl.arguments[i]),
|
1897
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1898
|
+
);
|
1899
|
+
|
1900
|
+
// literals only
|
1901
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1902
|
+
|
1903
|
+
return [
|
1904
|
+
...argOut,
|
1905
|
+
[ Opcodes[opName], ...imms ],
|
1906
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1907
|
+
];
|
1908
|
+
}
|
1909
|
+
}
|
1910
|
+
|
1660
1911
|
if (idx === undefined) {
|
1661
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1662
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1912
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1913
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1663
1914
|
}
|
1664
1915
|
|
1665
1916
|
const func = funcs.find(x => x.index === idx);
|
1666
1917
|
|
1667
1918
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1668
1919
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1669
|
-
const
|
1920
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1670
1921
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1671
1922
|
|
1672
1923
|
let args = decl.arguments;
|
@@ -1683,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1683
1934
|
if (func && func.throws) scope.throws = true;
|
1684
1935
|
|
1685
1936
|
let out = [];
|
1686
|
-
for (
|
1937
|
+
for (let i = 0; i < args.length; i++) {
|
1938
|
+
const arg = args[i];
|
1687
1939
|
out = out.concat(generate(scope, arg));
|
1940
|
+
|
1941
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1942
|
+
out.push(Opcodes.i32_to);
|
1943
|
+
}
|
1944
|
+
|
1945
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1946
|
+
out.push(Opcodes.i32_to);
|
1947
|
+
}
|
1948
|
+
|
1688
1949
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1689
1950
|
}
|
1690
1951
|
|
1691
1952
|
out.push([ Opcodes.call, idx ]);
|
1692
1953
|
|
1693
|
-
if (!
|
1954
|
+
if (!typedReturns) {
|
1694
1955
|
// let type;
|
1695
1956
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1696
1957
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1700,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1700
1961
|
// ...number(type, Valtype.i32),
|
1701
1962
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1702
1963
|
// );
|
1703
|
-
} else out.push(setLastType(scope));
|
1964
|
+
} else out.push(...setLastType(scope));
|
1965
|
+
|
1966
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1967
|
+
out.push(Opcodes.i32_from);
|
1968
|
+
}
|
1704
1969
|
|
1705
1970
|
return out;
|
1706
1971
|
};
|
@@ -1708,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1708
1973
|
const generateNew = (scope, decl, _global, _name) => {
|
1709
1974
|
// hack: basically treat this as a normal call for builtins for now
|
1710
1975
|
const name = mapName(decl.callee.name);
|
1976
|
+
|
1711
1977
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1712
|
-
|
1978
|
+
|
1979
|
+
if (builtinFuncs[name + '$constructor']) {
|
1980
|
+
// custom ...$constructor override builtin func
|
1981
|
+
return generateCall(scope, {
|
1982
|
+
...decl,
|
1983
|
+
callee: {
|
1984
|
+
type: 'Identifier',
|
1985
|
+
name: name + '$constructor'
|
1986
|
+
}
|
1987
|
+
}, _global, _name);
|
1988
|
+
}
|
1989
|
+
|
1990
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1713
1991
|
|
1714
1992
|
return generateCall(scope, decl, _global, _name);
|
1715
1993
|
};
|
@@ -1826,14 +2104,14 @@ const brTable = (input, bc, returns) => {
|
|
1826
2104
|
};
|
1827
2105
|
|
1828
2106
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1829
|
-
if (!
|
2107
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
1830
2108
|
|
1831
2109
|
const known = knownType(scope, type);
|
1832
2110
|
if (known != null) {
|
1833
2111
|
return bc[known] ?? bc.default;
|
1834
2112
|
}
|
1835
2113
|
|
1836
|
-
if (
|
2114
|
+
if (Prefs.typeswitchUseBrtable)
|
1837
2115
|
return brTable(type, bc, returns);
|
1838
2116
|
|
1839
2117
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1843,8 +2121,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1843
2121
|
[ Opcodes.block, returns ]
|
1844
2122
|
];
|
1845
2123
|
|
1846
|
-
// todo: use br_table?
|
1847
|
-
|
1848
2124
|
for (const x in bc) {
|
1849
2125
|
if (x === 'default') continue;
|
1850
2126
|
|
@@ -1868,7 +2144,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1868
2144
|
return out;
|
1869
2145
|
};
|
1870
2146
|
|
1871
|
-
const allocVar = (scope, name, global = false) => {
|
2147
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1872
2148
|
const target = global ? globals : scope.locals;
|
1873
2149
|
|
1874
2150
|
// already declared
|
@@ -1882,8 +2158,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1882
2158
|
let idx = global ? globalInd++ : scope.localInd++;
|
1883
2159
|
target[name] = { idx, type: valtypeBinary };
|
1884
2160
|
|
1885
|
-
|
1886
|
-
|
2161
|
+
if (type) {
|
2162
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2163
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2164
|
+
}
|
1887
2165
|
|
1888
2166
|
return idx;
|
1889
2167
|
};
|
@@ -1898,11 +2176,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1898
2176
|
};
|
1899
2177
|
|
1900
2178
|
const typeAnnoToPorfType = x => {
|
1901
|
-
if (
|
1902
|
-
if (TYPES[
|
2179
|
+
if (!x) return null;
|
2180
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2181
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1903
2182
|
|
1904
2183
|
switch (x) {
|
1905
2184
|
case 'i32':
|
2185
|
+
case 'i64':
|
2186
|
+
case 'f64':
|
1906
2187
|
return TYPES.number;
|
1907
2188
|
}
|
1908
2189
|
|
@@ -1913,7 +2194,7 @@ const extractTypeAnnotation = decl => {
|
|
1913
2194
|
let a = decl;
|
1914
2195
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1915
2196
|
|
1916
|
-
let type, elementType;
|
2197
|
+
let type = null, elementType = null;
|
1917
2198
|
if (a.typeName) {
|
1918
2199
|
type = a.typeName.name;
|
1919
2200
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1926,6 +2207,8 @@ const extractTypeAnnotation = decl => {
|
|
1926
2207
|
const typeName = type;
|
1927
2208
|
type = typeAnnoToPorfType(type);
|
1928
2209
|
|
2210
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
2211
|
+
|
1929
2212
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1930
2213
|
|
1931
2214
|
return { type, typeName, elementType };
|
@@ -1938,10 +2221,13 @@ const generateVar = (scope, decl) => {
|
|
1938
2221
|
|
1939
2222
|
// global variable if in top scope (main) and var ..., or if wanted
|
1940
2223
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2224
|
+
const target = global ? globals : scope.locals;
|
1941
2225
|
|
1942
2226
|
for (const x of decl.declarations) {
|
1943
2227
|
const name = mapName(x.id.name);
|
1944
2228
|
|
2229
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2230
|
+
|
1945
2231
|
if (x.init && isFuncType(x.init.type)) {
|
1946
2232
|
// hack for let a = function () { ... }
|
1947
2233
|
x.init.id = { name };
|
@@ -1957,16 +2243,29 @@ const generateVar = (scope, decl) => {
|
|
1957
2243
|
continue; // always ignore
|
1958
2244
|
}
|
1959
2245
|
|
1960
|
-
|
2246
|
+
// // generate init before allocating var
|
2247
|
+
// let generated;
|
2248
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
1961
2249
|
|
1962
|
-
|
2250
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2251
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2252
|
+
|
2253
|
+
if (typed) {
|
1963
2254
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1964
2255
|
}
|
1965
2256
|
|
1966
2257
|
if (x.init) {
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
2258
|
+
const generated = generate(scope, x.init, global, name);
|
2259
|
+
if (scope.arrays?.get(name) != null) {
|
2260
|
+
// hack to set local as pointer before
|
2261
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2262
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2263
|
+
generated.pop();
|
2264
|
+
out = out.concat(generated);
|
2265
|
+
} else {
|
2266
|
+
out = out.concat(generated);
|
2267
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2268
|
+
}
|
1970
2269
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1971
2270
|
}
|
1972
2271
|
|
@@ -1977,7 +2276,8 @@ const generateVar = (scope, decl) => {
|
|
1977
2276
|
return out;
|
1978
2277
|
};
|
1979
2278
|
|
1980
|
-
|
2279
|
+
// todo: optimize this func for valueUnused
|
2280
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1981
2281
|
const { type, name } = decl.left;
|
1982
2282
|
|
1983
2283
|
if (type === 'ObjectPattern') {
|
@@ -1992,22 +2292,30 @@ const generateAssign = (scope, decl) => {
|
|
1992
2292
|
return [];
|
1993
2293
|
}
|
1994
2294
|
|
2295
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2296
|
+
|
1995
2297
|
// hack: .length setter
|
1996
2298
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1997
2299
|
const name = decl.left.object.name;
|
1998
|
-
const pointer = arrays
|
2300
|
+
const pointer = scope.arrays?.get(name);
|
1999
2301
|
|
2000
|
-
const aotPointer = pointer != null;
|
2302
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2001
2303
|
|
2002
2304
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2305
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2003
2306
|
|
2004
2307
|
return [
|
2005
2308
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2006
2309
|
...generate(scope, decl.left.object),
|
2007
2310
|
Opcodes.i32_to_u
|
2008
2311
|
]),
|
2312
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2009
2313
|
|
2010
|
-
...generate(scope, decl.right),
|
2314
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2315
|
+
[ Opcodes.local_get, pointerTmp ],
|
2316
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2317
|
+
Opcodes.i32_from_u
|
2318
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2011
2319
|
[ Opcodes.local_tee, newValueTmp ],
|
2012
2320
|
|
2013
2321
|
Opcodes.i32_to_u,
|
@@ -2017,21 +2325,19 @@ const generateAssign = (scope, decl) => {
|
|
2017
2325
|
];
|
2018
2326
|
}
|
2019
2327
|
|
2020
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2021
|
-
|
2022
2328
|
// arr[i]
|
2023
2329
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2024
2330
|
const name = decl.left.object.name;
|
2025
|
-
const pointer = arrays
|
2331
|
+
const pointer = scope.arrays?.get(name);
|
2026
2332
|
|
2027
|
-
const aotPointer = pointer != null;
|
2333
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2028
2334
|
|
2029
2335
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2030
2336
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2031
2337
|
|
2032
2338
|
return [
|
2033
2339
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
2034
|
-
[TYPES.
|
2340
|
+
[TYPES.array]: [
|
2035
2341
|
...(aotPointer ? [] : [
|
2036
2342
|
...generate(scope, decl.left.object),
|
2037
2343
|
Opcodes.i32_to_u
|
@@ -2080,6 +2386,8 @@ const generateAssign = (scope, decl) => {
|
|
2080
2386
|
];
|
2081
2387
|
}
|
2082
2388
|
|
2389
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2390
|
+
|
2083
2391
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2084
2392
|
|
2085
2393
|
if (local === undefined) {
|
@@ -2126,9 +2434,7 @@ const generateAssign = (scope, decl) => {
|
|
2126
2434
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2127
2435
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2128
2436
|
|
2129
|
-
getLastType(scope)
|
2130
|
-
// hack: type is idx+1
|
2131
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2437
|
+
...setType(scope, name, getLastType(scope))
|
2132
2438
|
];
|
2133
2439
|
}
|
2134
2440
|
|
@@ -2139,9 +2445,7 @@ const generateAssign = (scope, decl) => {
|
|
2139
2445
|
|
2140
2446
|
// todo: string concat types
|
2141
2447
|
|
2142
|
-
|
2143
|
-
...number(TYPES.number, Valtype.i32),
|
2144
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2448
|
+
...setType(scope, name, TYPES.number)
|
2145
2449
|
];
|
2146
2450
|
};
|
2147
2451
|
|
@@ -2187,7 +2491,7 @@ const generateUnary = (scope, decl) => {
|
|
2187
2491
|
return out;
|
2188
2492
|
}
|
2189
2493
|
|
2190
|
-
case 'delete':
|
2494
|
+
case 'delete': {
|
2191
2495
|
let toReturn = true, toGenerate = true;
|
2192
2496
|
|
2193
2497
|
if (decl.argument.type === 'Identifier') {
|
@@ -2209,40 +2513,60 @@ const generateUnary = (scope, decl) => {
|
|
2209
2513
|
|
2210
2514
|
out.push(...number(toReturn ? 1 : 0));
|
2211
2515
|
return out;
|
2516
|
+
}
|
2212
2517
|
|
2213
|
-
case 'typeof':
|
2214
|
-
|
2518
|
+
case 'typeof': {
|
2519
|
+
let overrideType, toGenerate = true;
|
2520
|
+
|
2521
|
+
if (decl.argument.type === 'Identifier') {
|
2522
|
+
const out = generateIdent(scope, decl.argument);
|
2523
|
+
|
2524
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2525
|
+
if (out[1]) {
|
2526
|
+
// does not exist (2 ops from throw)
|
2527
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2528
|
+
toGenerate = false;
|
2529
|
+
}
|
2530
|
+
}
|
2531
|
+
|
2532
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2533
|
+
disposeLeftover(out);
|
2534
|
+
|
2535
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2215
2536
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2216
2537
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2217
2538
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2218
2539
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2219
2540
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2220
2541
|
|
2221
|
-
[TYPES.
|
2542
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2222
2543
|
|
2223
2544
|
// object and internal types
|
2224
2545
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2225
|
-
});
|
2546
|
+
}));
|
2547
|
+
|
2548
|
+
return out;
|
2549
|
+
}
|
2226
2550
|
|
2227
2551
|
default:
|
2228
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2552
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2229
2553
|
}
|
2230
2554
|
};
|
2231
2555
|
|
2232
|
-
const generateUpdate = (scope, decl) => {
|
2556
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2233
2557
|
const { name } = decl.argument;
|
2234
2558
|
|
2235
2559
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2236
2560
|
|
2237
2561
|
if (local === undefined) {
|
2238
|
-
return todo(`update expression with undefined variable
|
2562
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2239
2563
|
}
|
2240
2564
|
|
2241
2565
|
const idx = local.idx;
|
2242
2566
|
const out = [];
|
2243
2567
|
|
2244
2568
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2245
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2569
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2246
2570
|
|
2247
2571
|
switch (decl.operator) {
|
2248
2572
|
case '++':
|
@@ -2255,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
|
|
2255
2579
|
}
|
2256
2580
|
|
2257
2581
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2258
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2582
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2259
2583
|
|
2260
2584
|
return out;
|
2261
2585
|
};
|
@@ -2295,7 +2619,7 @@ const generateConditional = (scope, decl) => {
|
|
2295
2619
|
// note type
|
2296
2620
|
out.push(
|
2297
2621
|
...getNodeType(scope, decl.consequent),
|
2298
|
-
setLastType(scope)
|
2622
|
+
...setLastType(scope)
|
2299
2623
|
);
|
2300
2624
|
|
2301
2625
|
out.push([ Opcodes.else ]);
|
@@ -2304,7 +2628,7 @@ const generateConditional = (scope, decl) => {
|
|
2304
2628
|
// note type
|
2305
2629
|
out.push(
|
2306
2630
|
...getNodeType(scope, decl.alternate),
|
2307
|
-
setLastType(scope)
|
2631
|
+
...setLastType(scope)
|
2308
2632
|
);
|
2309
2633
|
|
2310
2634
|
out.push([ Opcodes.end ]);
|
@@ -2318,15 +2642,17 @@ const generateFor = (scope, decl) => {
|
|
2318
2642
|
const out = [];
|
2319
2643
|
|
2320
2644
|
if (decl.init) {
|
2321
|
-
out.push(...generate(scope, decl.init));
|
2645
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2322
2646
|
disposeLeftover(out);
|
2323
2647
|
}
|
2324
2648
|
|
2325
2649
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2326
2650
|
depth.push('for');
|
2327
2651
|
|
2328
|
-
out.push(...generate(scope, decl.test));
|
2329
|
-
|
2652
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2653
|
+
else out.push(...number(1, Valtype.i32));
|
2654
|
+
|
2655
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2330
2656
|
depth.push('if');
|
2331
2657
|
|
2332
2658
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2334,8 +2660,7 @@ const generateFor = (scope, decl) => {
|
|
2334
2660
|
out.push(...generate(scope, decl.body));
|
2335
2661
|
out.push([ Opcodes.end ]);
|
2336
2662
|
|
2337
|
-
out.push(...generate(scope, decl.update));
|
2338
|
-
depth.pop();
|
2663
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2339
2664
|
|
2340
2665
|
out.push([ Opcodes.br, 1 ]);
|
2341
2666
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2363,6 +2688,36 @@ const generateWhile = (scope, decl) => {
|
|
2363
2688
|
return out;
|
2364
2689
|
};
|
2365
2690
|
|
2691
|
+
const generateDoWhile = (scope, decl) => {
|
2692
|
+
const out = [];
|
2693
|
+
|
2694
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2695
|
+
depth.push('dowhile');
|
2696
|
+
|
2697
|
+
// block for break (includes all)
|
2698
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2699
|
+
depth.push('block');
|
2700
|
+
|
2701
|
+
// block for continue
|
2702
|
+
// includes body but not test+loop so we can exit body at anytime
|
2703
|
+
// and still test+loop after
|
2704
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2705
|
+
depth.push('block');
|
2706
|
+
|
2707
|
+
out.push(...generate(scope, decl.body));
|
2708
|
+
|
2709
|
+
out.push([ Opcodes.end ]);
|
2710
|
+
depth.pop();
|
2711
|
+
|
2712
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2713
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2714
|
+
|
2715
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2716
|
+
depth.pop(); depth.pop();
|
2717
|
+
|
2718
|
+
return out;
|
2719
|
+
};
|
2720
|
+
|
2366
2721
|
const generateForOf = (scope, decl) => {
|
2367
2722
|
const out = [];
|
2368
2723
|
|
@@ -2392,8 +2747,17 @@ const generateForOf = (scope, decl) => {
|
|
2392
2747
|
// setup local for left
|
2393
2748
|
generate(scope, decl.left);
|
2394
2749
|
|
2395
|
-
|
2750
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2751
|
+
if (!leftName && decl.left.name) {
|
2752
|
+
leftName = decl.left.name;
|
2753
|
+
|
2754
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2755
|
+
}
|
2756
|
+
|
2757
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2758
|
+
|
2396
2759
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2760
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2397
2761
|
|
2398
2762
|
depth.push('block');
|
2399
2763
|
depth.push('block');
|
@@ -2401,7 +2765,8 @@ const generateForOf = (scope, decl) => {
|
|
2401
2765
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2402
2766
|
// hack: this is naughty and will break things!
|
2403
2767
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2404
|
-
if (pages.
|
2768
|
+
if (pages.hasAnyString) {
|
2769
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2405
2770
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2406
2771
|
rawElements: new Array(1)
|
2407
2772
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2410,7 +2775,7 @@ const generateForOf = (scope, decl) => {
|
|
2410
2775
|
// set type for local
|
2411
2776
|
// todo: optimize away counter and use end pointer
|
2412
2777
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2413
|
-
[TYPES.
|
2778
|
+
[TYPES.array]: [
|
2414
2779
|
...setType(scope, leftName, TYPES.number),
|
2415
2780
|
|
2416
2781
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2493,6 +2858,56 @@ const generateForOf = (scope, decl) => {
|
|
2493
2858
|
[ Opcodes.end ],
|
2494
2859
|
[ Opcodes.end ]
|
2495
2860
|
],
|
2861
|
+
[TYPES.bytestring]: [
|
2862
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2863
|
+
|
2864
|
+
[ Opcodes.loop, Blocktype.void ],
|
2865
|
+
|
2866
|
+
// setup new/out array
|
2867
|
+
...newOut,
|
2868
|
+
[ Opcodes.drop ],
|
2869
|
+
|
2870
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2871
|
+
|
2872
|
+
// load current string ind {arg}
|
2873
|
+
[ Opcodes.local_get, pointer ],
|
2874
|
+
[ Opcodes.local_get, counter ],
|
2875
|
+
[ Opcodes.i32_add ],
|
2876
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2877
|
+
|
2878
|
+
// store to new string ind 0
|
2879
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2880
|
+
|
2881
|
+
// return new string (page)
|
2882
|
+
...number(newPointer),
|
2883
|
+
|
2884
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2885
|
+
|
2886
|
+
[ Opcodes.block, Blocktype.void ],
|
2887
|
+
[ Opcodes.block, Blocktype.void ],
|
2888
|
+
...generate(scope, decl.body),
|
2889
|
+
[ Opcodes.end ],
|
2890
|
+
|
2891
|
+
// increment iter pointer
|
2892
|
+
// [ Opcodes.local_get, pointer ],
|
2893
|
+
// ...number(1, Valtype.i32),
|
2894
|
+
// [ Opcodes.i32_add ],
|
2895
|
+
// [ Opcodes.local_set, pointer ],
|
2896
|
+
|
2897
|
+
// increment counter by 1
|
2898
|
+
[ Opcodes.local_get, counter ],
|
2899
|
+
...number(1, Valtype.i32),
|
2900
|
+
[ Opcodes.i32_add ],
|
2901
|
+
[ Opcodes.local_tee, counter ],
|
2902
|
+
|
2903
|
+
// loop if counter != length
|
2904
|
+
[ Opcodes.local_get, length ],
|
2905
|
+
[ Opcodes.i32_ne ],
|
2906
|
+
[ Opcodes.br_if, 1 ],
|
2907
|
+
|
2908
|
+
[ Opcodes.end ],
|
2909
|
+
[ Opcodes.end ]
|
2910
|
+
],
|
2496
2911
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2497
2912
|
}, Blocktype.void));
|
2498
2913
|
|
@@ -2503,28 +2918,65 @@ const generateForOf = (scope, decl) => {
|
|
2503
2918
|
return out;
|
2504
2919
|
};
|
2505
2920
|
|
2921
|
+
// find the nearest loop in depth map by type
|
2506
2922
|
const getNearestLoop = () => {
|
2507
2923
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2508
|
-
if (
|
2924
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2509
2925
|
}
|
2510
2926
|
|
2511
2927
|
return -1;
|
2512
2928
|
};
|
2513
2929
|
|
2514
2930
|
const generateBreak = (scope, decl) => {
|
2515
|
-
const
|
2931
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2932
|
+
const type = depth[target];
|
2933
|
+
|
2934
|
+
// different loop types have different branch offsets
|
2935
|
+
// as they have different wasm block/loop/if structures
|
2936
|
+
// we need to use the right offset by type to branch to the one we want
|
2937
|
+
// for a break: exit the loop without executing anything else inside it
|
2938
|
+
const offset = ({
|
2939
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2940
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2941
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2942
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2943
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2944
|
+
})[type];
|
2945
|
+
|
2516
2946
|
return [
|
2517
|
-
[ Opcodes.br, ...signedLEB128(
|
2947
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2518
2948
|
];
|
2519
2949
|
};
|
2520
2950
|
|
2521
2951
|
const generateContinue = (scope, decl) => {
|
2522
|
-
const
|
2952
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2953
|
+
const type = depth[target];
|
2954
|
+
|
2955
|
+
// different loop types have different branch offsets
|
2956
|
+
// as they have different wasm block/loop/if structures
|
2957
|
+
// we need to use the right offset by type to branch to the one we want
|
2958
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2959
|
+
const offset = ({
|
2960
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2961
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2962
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2963
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2964
|
+
})[type];
|
2965
|
+
|
2523
2966
|
return [
|
2524
|
-
[ Opcodes.br, ...signedLEB128(
|
2967
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2525
2968
|
];
|
2526
2969
|
};
|
2527
2970
|
|
2971
|
+
const generateLabel = (scope, decl) => {
|
2972
|
+
scope.labels ??= new Map();
|
2973
|
+
|
2974
|
+
const name = decl.label.name;
|
2975
|
+
scope.labels.set(name, depth.length);
|
2976
|
+
|
2977
|
+
return generate(scope, decl.body);
|
2978
|
+
};
|
2979
|
+
|
2528
2980
|
const generateThrow = (scope, decl) => {
|
2529
2981
|
scope.throws = true;
|
2530
2982
|
|
@@ -2533,7 +2985,7 @@ const generateThrow = (scope, decl) => {
|
|
2533
2985
|
// hack: throw new X("...") -> throw "..."
|
2534
2986
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2535
2987
|
constructor = decl.argument.callee.name;
|
2536
|
-
message = decl.argument.arguments[0]
|
2988
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2537
2989
|
}
|
2538
2990
|
|
2539
2991
|
if (tags.length === 0) tags.push({
|
@@ -2545,6 +2997,9 @@ const generateThrow = (scope, decl) => {
|
|
2545
2997
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2546
2998
|
let tagIdx = tags[0].idx;
|
2547
2999
|
|
3000
|
+
scope.exceptions ??= [];
|
3001
|
+
scope.exceptions.push(exceptId);
|
3002
|
+
|
2548
3003
|
// todo: write a description of how this works lol
|
2549
3004
|
|
2550
3005
|
return [
|
@@ -2554,7 +3009,7 @@ const generateThrow = (scope, decl) => {
|
|
2554
3009
|
};
|
2555
3010
|
|
2556
3011
|
const generateTry = (scope, decl) => {
|
2557
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3012
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2558
3013
|
|
2559
3014
|
const out = [];
|
2560
3015
|
|
@@ -2585,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2585
3040
|
// TODO
|
2586
3041
|
// if identifier declared, use that
|
2587
3042
|
// else, use default (right)
|
2588
|
-
return todo('assignment pattern (optional arg)');
|
3043
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2589
3044
|
};
|
2590
3045
|
|
2591
3046
|
let pages = new Map();
|
2592
|
-
const allocPage = (reason, type) => {
|
3047
|
+
const allocPage = (scope, reason, type) => {
|
2593
3048
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2594
3049
|
|
2595
3050
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2596
3051
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3052
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3053
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2597
3054
|
|
2598
3055
|
const ind = pages.size;
|
2599
3056
|
pages.set(reason, { ind, type });
|
2600
3057
|
|
2601
|
-
|
3058
|
+
scope.pages ??= new Map();
|
3059
|
+
scope.pages.set(reason, { ind, type });
|
3060
|
+
|
3061
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2602
3062
|
|
2603
3063
|
return ind;
|
2604
3064
|
};
|
2605
3065
|
|
3066
|
+
// todo: add scope.pages
|
2606
3067
|
const freePage = reason => {
|
2607
3068
|
const { ind } = pages.get(reason);
|
2608
3069
|
pages.delete(reason);
|
2609
3070
|
|
2610
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3071
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2611
3072
|
|
2612
3073
|
return ind;
|
2613
3074
|
};
|
@@ -2633,15 +3094,14 @@ const StoreOps = {
|
|
2633
3094
|
|
2634
3095
|
let data = [];
|
2635
3096
|
|
2636
|
-
const compileBytes = (val, itemType
|
3097
|
+
const compileBytes = (val, itemType) => {
|
2637
3098
|
// todo: this is a mess and needs confirming / ????
|
2638
3099
|
switch (itemType) {
|
2639
3100
|
case 'i8': return [ val % 256 ];
|
2640
|
-
case 'i16': return [ val % 256,
|
2641
|
-
|
2642
|
-
case 'i32':
|
2643
|
-
|
2644
|
-
return enforceFourBytes(signedLEB128(val));
|
3101
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3102
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3103
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3104
|
+
// todo: i64
|
2645
3105
|
|
2646
3106
|
case 'f64': return ieee754_binary64(val);
|
2647
3107
|
}
|
@@ -2659,16 +3119,22 @@ const getAllocType = itemType => {
|
|
2659
3119
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2660
3120
|
const out = [];
|
2661
3121
|
|
3122
|
+
scope.arrays ??= new Map();
|
3123
|
+
|
2662
3124
|
let firstAssign = false;
|
2663
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3125
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2664
3126
|
firstAssign = true;
|
2665
3127
|
|
2666
3128
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2667
3129
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2668
|
-
|
3130
|
+
|
3131
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3132
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2669
3133
|
}
|
2670
3134
|
|
2671
|
-
const pointer = arrays.get(name);
|
3135
|
+
const pointer = scope.arrays.get(name);
|
3136
|
+
|
3137
|
+
const local = global ? globals[name] : scope.locals[name];
|
2672
3138
|
|
2673
3139
|
const useRawElements = !!decl.rawElements;
|
2674
3140
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2676,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2676
3142
|
const valtype = itemTypeToValtype[itemType];
|
2677
3143
|
const length = elements.length;
|
2678
3144
|
|
2679
|
-
if (firstAssign && useRawElements) {
|
2680
|
-
|
3145
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3146
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3147
|
+
if (length !== 0) {
|
3148
|
+
let bytes = compileBytes(length, 'i32');
|
2681
3149
|
|
2682
|
-
|
2683
|
-
|
3150
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3151
|
+
if (elements[i] == null) continue;
|
2684
3152
|
|
2685
|
-
|
2686
|
-
|
3153
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3154
|
+
}
|
2687
3155
|
|
2688
|
-
|
2689
|
-
|
2690
|
-
|
2691
|
-
|
3156
|
+
const ind = data.push({
|
3157
|
+
offset: pointer,
|
3158
|
+
bytes
|
3159
|
+
}) - 1;
|
3160
|
+
|
3161
|
+
scope.data ??= [];
|
3162
|
+
scope.data.push(ind);
|
3163
|
+
}
|
2692
3164
|
|
2693
3165
|
// local value as pointer
|
2694
3166
|
out.push(...number(pointer));
|
@@ -2696,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2696
3168
|
return [ out, pointer ];
|
2697
3169
|
}
|
2698
3170
|
|
3171
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3172
|
+
if (pointerTmp != null) {
|
3173
|
+
out.push(
|
3174
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3175
|
+
Opcodes.i32_to_u,
|
3176
|
+
[ Opcodes.local_set, pointerTmp ]
|
3177
|
+
);
|
3178
|
+
}
|
3179
|
+
|
3180
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3181
|
+
|
2699
3182
|
// store length as 0th array
|
2700
3183
|
out.push(
|
2701
|
-
...
|
3184
|
+
...pointerWasm,
|
2702
3185
|
...number(length, Valtype.i32),
|
2703
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3186
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2704
3187
|
);
|
2705
3188
|
|
2706
3189
|
const storeOp = StoreOps[itemType];
|
@@ -2709,20 +3192,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2709
3192
|
if (elements[i] == null) continue;
|
2710
3193
|
|
2711
3194
|
out.push(
|
2712
|
-
...
|
3195
|
+
...pointerWasm,
|
2713
3196
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2714
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3197
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2715
3198
|
);
|
2716
3199
|
}
|
2717
3200
|
|
2718
3201
|
// local value as pointer
|
2719
|
-
out.push(...
|
3202
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2720
3203
|
|
2721
3204
|
return [ out, pointer ];
|
2722
3205
|
};
|
2723
3206
|
|
2724
3207
|
const byteStringable = str => {
|
2725
|
-
if (!
|
3208
|
+
if (!Prefs.bytestring) return false;
|
2726
3209
|
|
2727
3210
|
for (let i = 0; i < str.length; i++) {
|
2728
3211
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2731,9 +3214,9 @@ const byteStringable = str => {
|
|
2731
3214
|
return true;
|
2732
3215
|
};
|
2733
3216
|
|
2734
|
-
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
3217
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2735
3218
|
const rawElements = new Array(str.length);
|
2736
|
-
let byteStringable =
|
3219
|
+
let byteStringable = Prefs.bytestring;
|
2737
3220
|
for (let i = 0; i < str.length; i++) {
|
2738
3221
|
const c = str.charCodeAt(i);
|
2739
3222
|
rawElements[i] = c;
|
@@ -2741,25 +3224,60 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
|
|
2741
3224
|
if (byteStringable && c > 0xFF) byteStringable = false;
|
2742
3225
|
}
|
2743
3226
|
|
3227
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3228
|
+
|
2744
3229
|
return makeArray(scope, {
|
2745
3230
|
rawElements
|
2746
3231
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2747
3232
|
};
|
2748
3233
|
|
2749
|
-
let arrays = new Map();
|
2750
3234
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2751
3235
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2752
3236
|
};
|
2753
3237
|
|
2754
3238
|
export const generateMember = (scope, decl, _global, _name) => {
|
2755
3239
|
const name = decl.object.name;
|
2756
|
-
const pointer = arrays
|
3240
|
+
const pointer = scope.arrays?.get(name);
|
3241
|
+
|
3242
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2757
3243
|
|
2758
|
-
|
3244
|
+
// hack: .name
|
3245
|
+
if (decl.property.name === 'name') {
|
3246
|
+
if (hasFuncWithName(name)) {
|
3247
|
+
let nameProp = name;
|
3248
|
+
|
3249
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3250
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3251
|
+
|
3252
|
+
return makeString(scope, nameProp, _global, _name, true);
|
3253
|
+
} else {
|
3254
|
+
return generate(scope, DEFAULT_VALUE);
|
3255
|
+
}
|
3256
|
+
}
|
2759
3257
|
|
2760
3258
|
// hack: .length
|
2761
3259
|
if (decl.property.name === 'length') {
|
2762
|
-
|
3260
|
+
const func = funcs.find(x => x.name === name);
|
3261
|
+
if (func) {
|
3262
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3263
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3264
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3265
|
+
}
|
3266
|
+
|
3267
|
+
if (builtinFuncs[name + '$constructor']) {
|
3268
|
+
const regularFunc = builtinFuncs[name];
|
3269
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3270
|
+
|
3271
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3272
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3273
|
+
|
3274
|
+
return number(Math.max(regularParams, constructorParams));
|
3275
|
+
}
|
3276
|
+
|
3277
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3278
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3279
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3280
|
+
|
2763
3281
|
return [
|
2764
3282
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2765
3283
|
...generate(scope, decl.object),
|
@@ -2771,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2771
3289
|
];
|
2772
3290
|
}
|
2773
3291
|
|
3292
|
+
const object = generate(scope, decl.object);
|
3293
|
+
const property = generate(scope, decl.property);
|
3294
|
+
|
2774
3295
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2775
3296
|
// hack: this is naughty and will break things!
|
2776
3297
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2777
|
-
if (pages.
|
3298
|
+
if (pages.hasAnyString) {
|
2778
3299
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2779
3300
|
rawElements: new Array(1)
|
2780
3301
|
}, _global, _name, true, 'i16');
|
2781
3302
|
}
|
2782
3303
|
|
2783
3304
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2784
|
-
[TYPES.
|
3305
|
+
[TYPES.array]: [
|
2785
3306
|
// get index as valtype
|
2786
|
-
...
|
3307
|
+
...property,
|
2787
3308
|
|
2788
3309
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2789
3310
|
Opcodes.i32_to_u,
|
@@ -2791,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2791
3312
|
[ Opcodes.i32_mul ],
|
2792
3313
|
|
2793
3314
|
...(aotPointer ? [] : [
|
2794
|
-
...
|
3315
|
+
...object,
|
2795
3316
|
Opcodes.i32_to_u,
|
2796
3317
|
[ Opcodes.i32_add ]
|
2797
3318
|
]),
|
@@ -2800,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2800
3321
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2801
3322
|
|
2802
3323
|
...number(TYPES.number, Valtype.i32),
|
2803
|
-
setLastType(scope)
|
3324
|
+
...setLastType(scope)
|
2804
3325
|
],
|
2805
3326
|
|
2806
3327
|
[TYPES.string]: [
|
@@ -2810,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2810
3331
|
|
2811
3332
|
...number(0, Valtype.i32), // base 0 for store later
|
2812
3333
|
|
2813
|
-
...
|
2814
|
-
|
3334
|
+
...property,
|
2815
3335
|
Opcodes.i32_to_u,
|
3336
|
+
|
2816
3337
|
...number(ValtypeSize.i16, Valtype.i32),
|
2817
3338
|
[ Opcodes.i32_mul ],
|
2818
3339
|
|
2819
3340
|
...(aotPointer ? [] : [
|
2820
|
-
...
|
3341
|
+
...object,
|
2821
3342
|
Opcodes.i32_to_u,
|
2822
3343
|
[ Opcodes.i32_add ]
|
2823
3344
|
]),
|
@@ -2832,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2832
3353
|
...number(newPointer),
|
2833
3354
|
|
2834
3355
|
...number(TYPES.string, Valtype.i32),
|
2835
|
-
setLastType(scope)
|
3356
|
+
...setLastType(scope)
|
3357
|
+
],
|
3358
|
+
[TYPES.bytestring]: [
|
3359
|
+
// setup new/out array
|
3360
|
+
...newOut,
|
3361
|
+
[ Opcodes.drop ],
|
3362
|
+
|
3363
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3364
|
+
|
3365
|
+
...property,
|
3366
|
+
Opcodes.i32_to_u,
|
3367
|
+
|
3368
|
+
...(aotPointer ? [] : [
|
3369
|
+
...object,
|
3370
|
+
Opcodes.i32_to_u,
|
3371
|
+
[ Opcodes.i32_add ]
|
3372
|
+
]),
|
3373
|
+
|
3374
|
+
// load current string ind {arg}
|
3375
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3376
|
+
|
3377
|
+
// store to new string ind 0
|
3378
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3379
|
+
|
3380
|
+
// return new string (page)
|
3381
|
+
...number(newPointer),
|
3382
|
+
|
3383
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3384
|
+
...setLastType(scope)
|
2836
3385
|
],
|
2837
3386
|
|
2838
|
-
default:
|
3387
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2839
3388
|
});
|
2840
3389
|
};
|
2841
3390
|
|
@@ -2845,25 +3394,36 @@ const objectHack = node => {
|
|
2845
3394
|
if (!node) return node;
|
2846
3395
|
|
2847
3396
|
if (node.type === 'MemberExpression') {
|
2848
|
-
|
3397
|
+
const out = (() => {
|
3398
|
+
if (node.computed || node.optional) return;
|
3399
|
+
|
3400
|
+
let objectName = node.object.name;
|
2849
3401
|
|
2850
|
-
|
3402
|
+
// if object is not identifier or another member exp, give up
|
3403
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3404
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2851
3405
|
|
2852
|
-
|
2853
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3406
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2854
3407
|
|
2855
|
-
|
3408
|
+
// if .name or .length, give up (hack within a hack!)
|
3409
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3410
|
+
node.object = objectHack(node.object);
|
3411
|
+
return;
|
3412
|
+
}
|
2856
3413
|
|
2857
|
-
|
2858
|
-
|
3414
|
+
// no object name, give up
|
3415
|
+
if (!objectName) return;
|
2859
3416
|
|
2860
|
-
|
2861
|
-
|
3417
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3418
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2862
3419
|
|
2863
|
-
|
2864
|
-
|
2865
|
-
|
2866
|
-
|
3420
|
+
return {
|
3421
|
+
type: 'Identifier',
|
3422
|
+
name
|
3423
|
+
};
|
3424
|
+
})();
|
3425
|
+
|
3426
|
+
if (out) return out;
|
2867
3427
|
}
|
2868
3428
|
|
2869
3429
|
for (const x in node) {
|
@@ -2877,8 +3437,8 @@ const objectHack = node => {
|
|
2877
3437
|
};
|
2878
3438
|
|
2879
3439
|
const generateFunc = (scope, decl) => {
|
2880
|
-
if (decl.async) return todo('async functions are not supported');
|
2881
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3440
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3441
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2882
3442
|
|
2883
3443
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2884
3444
|
const params = decl.params ?? [];
|
@@ -2894,6 +3454,11 @@ const generateFunc = (scope, decl) => {
|
|
2894
3454
|
name
|
2895
3455
|
};
|
2896
3456
|
|
3457
|
+
if (typedInput && decl.returnType) {
|
3458
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3459
|
+
innerScope.returns = [ valtypeBinary ];
|
3460
|
+
}
|
3461
|
+
|
2897
3462
|
for (let i = 0; i < params.length; i++) {
|
2898
3463
|
allocVar(innerScope, params[i].name, false);
|
2899
3464
|
|
@@ -2915,13 +3480,13 @@ const generateFunc = (scope, decl) => {
|
|
2915
3480
|
const func = {
|
2916
3481
|
name,
|
2917
3482
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2918
|
-
|
2919
|
-
|
2920
|
-
throws: innerScope.throws,
|
2921
|
-
index: currentFuncIndex++
|
3483
|
+
index: currentFuncIndex++,
|
3484
|
+
...innerScope
|
2922
3485
|
};
|
2923
3486
|
funcIndex[name] = func.index;
|
2924
3487
|
|
3488
|
+
if (name === 'main') func.gotLastType = true;
|
3489
|
+
|
2925
3490
|
// quick hack fixes
|
2926
3491
|
for (const inst of wasm) {
|
2927
3492
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2973,7 +3538,7 @@ const internalConstrs = {
|
|
2973
3538
|
|
2974
3539
|
// todo: check in wasm instead of here
|
2975
3540
|
const literalValue = arg.value ?? 0;
|
2976
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3541
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2977
3542
|
|
2978
3543
|
return [
|
2979
3544
|
...number(0, Valtype.i32),
|
@@ -2984,7 +3549,8 @@ const internalConstrs = {
|
|
2984
3549
|
...number(pointer)
|
2985
3550
|
];
|
2986
3551
|
},
|
2987
|
-
type: TYPES.
|
3552
|
+
type: TYPES.array,
|
3553
|
+
length: 1
|
2988
3554
|
},
|
2989
3555
|
|
2990
3556
|
__Array_of: {
|
@@ -2995,27 +3561,134 @@ const internalConstrs = {
|
|
2995
3561
|
elements: decl.arguments
|
2996
3562
|
}, global, name);
|
2997
3563
|
},
|
2998
|
-
type: TYPES.
|
3564
|
+
type: TYPES.array,
|
3565
|
+
notConstr: true,
|
3566
|
+
length: 0
|
3567
|
+
},
|
3568
|
+
|
3569
|
+
__Porffor_fastOr: {
|
3570
|
+
generate: (scope, decl) => {
|
3571
|
+
const out = [];
|
3572
|
+
|
3573
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3574
|
+
out.push(
|
3575
|
+
...generate(scope, decl.arguments[i]),
|
3576
|
+
Opcodes.i32_to_u,
|
3577
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3578
|
+
);
|
3579
|
+
}
|
3580
|
+
|
3581
|
+
out.push(Opcodes.i32_from_u);
|
3582
|
+
|
3583
|
+
return out;
|
3584
|
+
},
|
3585
|
+
type: TYPES.boolean,
|
2999
3586
|
notConstr: true
|
3000
|
-
}
|
3001
|
-
|
3587
|
+
},
|
3588
|
+
|
3589
|
+
__Porffor_fastAnd: {
|
3590
|
+
generate: (scope, decl) => {
|
3591
|
+
const out = [];
|
3002
3592
|
|
3003
|
-
|
3004
|
-
|
3005
|
-
|
3006
|
-
|
3007
|
-
|
3008
|
-
|
3009
|
-
|
3010
|
-
// }
|
3011
|
-
// if (Array.isArray(x)) check(x);
|
3012
|
-
// }
|
3013
|
-
// };
|
3014
|
-
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
3015
|
-
// // if (Array.isArray(a)) check(a);
|
3593
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3594
|
+
out.push(
|
3595
|
+
...generate(scope, decl.arguments[i]),
|
3596
|
+
Opcodes.i32_to_u,
|
3597
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3598
|
+
);
|
3599
|
+
}
|
3016
3600
|
|
3017
|
-
|
3018
|
-
|
3601
|
+
out.push(Opcodes.i32_from_u);
|
3602
|
+
|
3603
|
+
return out;
|
3604
|
+
},
|
3605
|
+
type: TYPES.boolean,
|
3606
|
+
notConstr: true
|
3607
|
+
},
|
3608
|
+
|
3609
|
+
Boolean: {
|
3610
|
+
generate: (scope, decl) => {
|
3611
|
+
// todo: boolean object when used as constructor
|
3612
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3613
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3614
|
+
},
|
3615
|
+
type: TYPES.boolean,
|
3616
|
+
length: 1
|
3617
|
+
},
|
3618
|
+
|
3619
|
+
__Math_max: {
|
3620
|
+
generate: (scope, decl) => {
|
3621
|
+
const out = [
|
3622
|
+
...number(-Infinity)
|
3623
|
+
];
|
3624
|
+
|
3625
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3626
|
+
out.push(
|
3627
|
+
...generate(scope, decl.arguments[i]),
|
3628
|
+
[ Opcodes.f64_max ]
|
3629
|
+
);
|
3630
|
+
}
|
3631
|
+
|
3632
|
+
return out;
|
3633
|
+
},
|
3634
|
+
type: TYPES.number,
|
3635
|
+
notConstr: true,
|
3636
|
+
length: 2
|
3637
|
+
},
|
3638
|
+
|
3639
|
+
__Math_min: {
|
3640
|
+
generate: (scope, decl) => {
|
3641
|
+
const out = [
|
3642
|
+
...number(Infinity)
|
3643
|
+
];
|
3644
|
+
|
3645
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3646
|
+
out.push(
|
3647
|
+
...generate(scope, decl.arguments[i]),
|
3648
|
+
[ Opcodes.f64_min ]
|
3649
|
+
);
|
3650
|
+
}
|
3651
|
+
|
3652
|
+
return out;
|
3653
|
+
},
|
3654
|
+
type: TYPES.number,
|
3655
|
+
notConstr: true,
|
3656
|
+
length: 2
|
3657
|
+
},
|
3658
|
+
|
3659
|
+
__console_log: {
|
3660
|
+
generate: (scope, decl) => {
|
3661
|
+
const out = [];
|
3662
|
+
|
3663
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3664
|
+
out.push(
|
3665
|
+
...generateCall(scope, {
|
3666
|
+
callee: {
|
3667
|
+
type: 'Identifier',
|
3668
|
+
name: '__Porffor_print'
|
3669
|
+
},
|
3670
|
+
arguments: [ decl.arguments[i] ]
|
3671
|
+
}),
|
3672
|
+
|
3673
|
+
// print space
|
3674
|
+
...number(32),
|
3675
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3676
|
+
);
|
3677
|
+
}
|
3678
|
+
|
3679
|
+
// print newline
|
3680
|
+
out.push(
|
3681
|
+
...number(10),
|
3682
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3683
|
+
);
|
3684
|
+
|
3685
|
+
return out;
|
3686
|
+
},
|
3687
|
+
type: TYPES.undefined,
|
3688
|
+
notConstr: true,
|
3689
|
+
length: 0
|
3690
|
+
}
|
3691
|
+
};
|
3019
3692
|
|
3020
3693
|
export default program => {
|
3021
3694
|
globals = {};
|
@@ -3025,20 +3698,23 @@ export default program => {
|
|
3025
3698
|
funcs = [];
|
3026
3699
|
funcIndex = {};
|
3027
3700
|
depth = [];
|
3028
|
-
arrays = new Map();
|
3029
3701
|
pages = new Map();
|
3030
3702
|
data = [];
|
3031
3703
|
currentFuncIndex = importedFuncs.length;
|
3032
3704
|
|
3033
3705
|
globalThis.valtype = 'f64';
|
3034
3706
|
|
3035
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3707
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3036
3708
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3037
3709
|
|
3038
3710
|
globalThis.valtypeBinary = Valtype[valtype];
|
3039
3711
|
|
3040
3712
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3041
3713
|
|
3714
|
+
globalThis.pageSize = PageSize;
|
3715
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3716
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3717
|
+
|
3042
3718
|
// set generic opcodes for current valtype
|
3043
3719
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3044
3720
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3047,10 +3723,10 @@ export default program => {
|
|
3047
3723
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3048
3724
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3049
3725
|
|
3050
|
-
Opcodes.i32_to = [ [
|
3051
|
-
Opcodes.i32_to_u = [ [
|
3052
|
-
Opcodes.i32_from = [ [
|
3053
|
-
Opcodes.i32_from_u = [ [
|
3726
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3727
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3728
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3729
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3054
3730
|
|
3055
3731
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3056
3732
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3063,10 +3739,6 @@ export default program => {
|
|
3063
3739
|
|
3064
3740
|
program.id = { name: 'main' };
|
3065
3741
|
|
3066
|
-
globalThis.pageSize = PageSize;
|
3067
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3068
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3069
|
-
|
3070
3742
|
const scope = {
|
3071
3743
|
locals: {},
|
3072
3744
|
localInd: 0
|
@@ -3077,7 +3749,7 @@ export default program => {
|
|
3077
3749
|
body: program.body
|
3078
3750
|
};
|
3079
3751
|
|
3080
|
-
if (
|
3752
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3081
3753
|
|
3082
3754
|
generateFunc(scope, program);
|
3083
3755
|
|
@@ -3094,7 +3766,11 @@ export default program => {
|
|
3094
3766
|
}
|
3095
3767
|
|
3096
3768
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3097
|
-
|
3769
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3770
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3771
|
+
} else {
|
3772
|
+
main.returns = [];
|
3773
|
+
}
|
3098
3774
|
}
|
3099
3775
|
|
3100
3776
|
if (lastInst[0] === Opcodes.call) {
|