porffor 0.2.0-eaee2da → 0.2.0-ef043de
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/LICENSE +20 -20
- package/README.md +159 -88
- 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 +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +151 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +1370 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +527 -0
- package/compiler/builtins/porffor.d.ts +42 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +470 -269
- package/compiler/{codeGen.js → codegen.js} +999 -378
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1262 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +35 -27
- package/compiler/precompile.js +123 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +13 -28
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +54 -46
- package/fib.js +7 -0
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +5 -3
- package/rhemyn/parse.js +323 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +62 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +40 -7
- 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,35 +25,37 @@ 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';
|
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
108
|
return generateUnary(scope, decl);
|
105
109
|
|
106
110
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
112
|
|
109
113
|
case 'IfStatement':
|
110
114
|
return generateIf(scope, decl);
|
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
119
|
case 'WhileStatement':
|
116
120
|
return generateWhile(scope, decl);
|
117
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
118
125
|
case 'ForOfStatement':
|
119
126
|
return generateForOf(scope, decl);
|
120
127
|
|
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
131
|
case 'ContinueStatement':
|
125
132
|
return generateContinue(scope, decl);
|
126
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
127
137
|
case 'EmptyStatement':
|
128
138
|
return generateEmpty(scope, decl);
|
129
139
|
|
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
147
|
return generateTry(scope, decl);
|
138
148
|
|
139
149
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
141
151
|
return [];
|
142
152
|
|
143
153
|
case 'ArrayExpression':
|
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
161
|
const funcsBefore = funcs.length;
|
152
162
|
generate(scope, decl.declaration);
|
153
163
|
|
154
|
-
if (funcsBefore
|
164
|
+
if (funcsBefore !== funcs.length) {
|
165
|
+
// new func added
|
166
|
+
const newFunc = funcs[funcs.length - 1];
|
167
|
+
newFunc.export = true;
|
168
|
+
}
|
169
|
+
|
170
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
155
171
|
|
156
|
-
const newFunc = funcs[funcs.length - 1];
|
157
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
158
174
|
|
159
175
|
return [];
|
160
176
|
|
161
177
|
case 'TaggedTemplateExpression': {
|
162
178
|
const funcs = {
|
163
|
-
|
179
|
+
__Porffor_wasm: str => {
|
164
180
|
let out = [];
|
165
181
|
|
166
182
|
for (const line of str.split('\n')) {
|
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
184
|
if (asm[0] === '') continue; // blank
|
169
185
|
|
170
186
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
189
|
continue;
|
174
190
|
}
|
175
191
|
|
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
195
|
}
|
180
196
|
|
181
197
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
198
|
+
allocPage(scope, 'asm instrinsic');
|
183
199
|
// todo: add to store/load offset insts
|
184
200
|
continue;
|
185
201
|
}
|
@@ -188,43 +204,65 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
188
204
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
205
|
|
190
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
const immediates = asm.slice(1).map(x =>
|
207
|
+
const immediates = asm.slice(1).map(x => {
|
208
|
+
const int = parseInt(x);
|
209
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
210
|
+
return int;
|
211
|
+
});
|
192
212
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
214
|
}
|
195
215
|
|
196
216
|
return out;
|
197
217
|
},
|
198
218
|
|
199
|
-
|
200
|
-
|
219
|
+
__Porffor_bs: str => [
|
220
|
+
...makeString(scope, str, global, name, true),
|
201
221
|
|
202
|
-
|
203
|
-
...number(
|
204
|
-
|
222
|
+
...(name ? setType(scope, name, TYPES._bytestring) : [
|
223
|
+
...number(TYPES._bytestring, Valtype.i32),
|
224
|
+
...setLastType(scope)
|
225
|
+
])
|
226
|
+
],
|
227
|
+
__Porffor_s: str => [
|
228
|
+
...makeString(scope, str, global, name, false),
|
205
229
|
|
206
|
-
|
207
|
-
...number(
|
208
|
-
|
209
|
-
]
|
210
|
-
|
211
|
-
}
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
234
|
+
],
|
235
|
+
};
|
212
236
|
|
213
|
-
const
|
237
|
+
const func = decl.tag.name;
|
214
238
|
// hack for inline asm
|
215
|
-
if (!funcs[
|
239
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
240
|
+
|
241
|
+
const { quasis, expressions } = decl.quasi;
|
242
|
+
let str = quasis[0].value.raw;
|
243
|
+
|
244
|
+
for (let i = 0; i < expressions.length; i++) {
|
245
|
+
const e = expressions[i];
|
246
|
+
if (!e.name) {
|
247
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
248
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
249
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
250
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
251
|
+
|
252
|
+
str += quasis[i + 1].value.raw;
|
253
|
+
}
|
216
254
|
|
217
|
-
|
218
|
-
return funcs[name](str);
|
255
|
+
return funcs[func](str);
|
219
256
|
}
|
220
257
|
|
221
258
|
default:
|
222
|
-
|
223
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
262
|
return [];
|
225
263
|
}
|
226
264
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
266
|
}
|
229
267
|
};
|
230
268
|
|
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
252
290
|
return [ undefined, undefined ];
|
253
291
|
};
|
254
292
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
294
|
...generateThrow(scope, {
|
257
295
|
argument: {
|
258
296
|
type: 'NewExpression',
|
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
|
|
274
312
|
const name = mapName(rawName);
|
275
313
|
let local = scope.locals[rawName];
|
276
314
|
|
277
|
-
if (builtinVars
|
315
|
+
if (Object.hasOwn(builtinVars, name)) {
|
278
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
280
321
|
}
|
281
322
|
|
282
|
-
if (builtinFuncs
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
283
324
|
// todo: return an actual something
|
284
325
|
return number(1);
|
285
326
|
}
|
286
327
|
|
287
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
333
|
+
if (local?.idx === undefined) {
|
288
334
|
// no local var with name
|
289
|
-
if (
|
290
|
-
if (funcIndex
|
335
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
336
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
337
|
|
292
|
-
if (globals
|
338
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
293
339
|
}
|
294
340
|
|
295
|
-
if (local === undefined && rawName.startsWith('__')) {
|
341
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
296
342
|
// return undefined if unknown key in already known var
|
297
343
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
298
344
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
|
|
301
347
|
if (!parentLookup[1]) return number(UNDEFINED);
|
302
348
|
}
|
303
349
|
|
304
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
350
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
305
351
|
|
306
352
|
return [ [ Opcodes.local_get, local.idx ] ];
|
307
353
|
};
|
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
314
360
|
// just bare "return"
|
315
361
|
return [
|
316
362
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
318
366
|
[ Opcodes.return ]
|
319
367
|
];
|
320
368
|
}
|
321
369
|
|
322
370
|
return [
|
323
371
|
...generate(scope, decl.argument),
|
324
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
325
375
|
[ Opcodes.return ]
|
326
376
|
];
|
327
377
|
};
|
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
385
|
return idx;
|
336
386
|
};
|
337
387
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
388
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
389
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
390
|
|
340
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
392
|
const checks = {
|
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
395
|
'??': nullish
|
345
396
|
};
|
346
397
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
398
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
399
|
|
349
400
|
// generic structure for {a} OP {b}
|
350
401
|
// -->
|
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
403
|
|
353
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
405
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
408
|
|
358
409
|
const canInt = leftIsInt && rightIsInt;
|
359
410
|
|
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
421
|
...right,
|
371
422
|
// note type
|
372
423
|
...rightType,
|
373
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
374
425
|
[ Opcodes.else ],
|
375
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
427
|
// note type
|
377
428
|
...leftType,
|
378
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
379
430
|
[ Opcodes.end ],
|
380
431
|
Opcodes.i32_from
|
381
432
|
];
|
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
440
|
...right,
|
390
441
|
// note type
|
391
442
|
...rightType,
|
392
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
393
444
|
[ Opcodes.else ],
|
394
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
446
|
// note type
|
396
447
|
...leftType,
|
397
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
398
449
|
[ Opcodes.end ]
|
399
450
|
];
|
400
451
|
};
|
401
452
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
455
|
// todo: convert left and right to strings if not
|
405
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
462
|
|
412
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
413
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
414
|
-
|
415
463
|
if (assign) {
|
416
|
-
const pointer = arrays
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
465
|
|
418
466
|
return [
|
419
467
|
// setup right
|
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
487
|
|
440
488
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
491
|
|
444
492
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
494
|
[ Opcodes.i32_mul ],
|
447
495
|
[ Opcodes.i32_add ],
|
448
496
|
|
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
500
|
[ Opcodes.i32_add ],
|
453
501
|
|
454
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
455
503
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
505
|
[ Opcodes.i32_mul ],
|
458
506
|
|
459
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
560
|
|
513
561
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
564
|
|
517
565
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
567
|
[ Opcodes.i32_mul ],
|
520
568
|
[ Opcodes.i32_add ],
|
521
569
|
|
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
573
|
[ Opcodes.i32_add ],
|
526
574
|
|
527
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
528
576
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
578
|
[ Opcodes.i32_mul ],
|
531
579
|
|
532
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
584
|
];
|
537
585
|
};
|
538
586
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
588
|
// todo: this should be rewritten into a func
|
541
589
|
// todo: convert left and right to strings if not
|
542
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
596
|
|
550
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
573
620
|
|
574
621
|
[ Opcodes.local_get, rightPointer ],
|
575
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
577
623
|
|
578
624
|
// fast path: check leftLength != rightLength
|
579
625
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
634
|
...number(0, Valtype.i32),
|
589
635
|
[ Opcodes.local_set, index ],
|
590
636
|
|
591
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
638
|
// we do this instead of having to do mul/div each iter for perf™
|
593
639
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
596
644
|
[ Opcodes.local_set, indexEnd ],
|
597
645
|
|
598
646
|
// iterate over each char and check if eq
|
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
650
|
[ Opcodes.local_get, index ],
|
603
651
|
[ Opcodes.local_get, leftPointer ],
|
604
652
|
[ Opcodes.i32_add ],
|
605
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
656
|
|
607
657
|
// fetch right
|
608
658
|
[ Opcodes.local_get, index ],
|
609
659
|
[ Opcodes.local_get, rightPointer ],
|
610
660
|
[ Opcodes.i32_add ],
|
611
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
664
|
|
613
665
|
// not equal, "return" false
|
614
666
|
[ Opcodes.i32_ne ],
|
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
617
669
|
[ Opcodes.br, 2 ],
|
618
670
|
[ Opcodes.end ],
|
619
671
|
|
620
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
673
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
675
|
[ Opcodes.i32_add ],
|
624
676
|
[ Opcodes.local_tee, index ],
|
625
677
|
|
626
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
627
679
|
[ Opcodes.local_get, indexEnd ],
|
628
680
|
[ Opcodes.i32_ne ],
|
629
681
|
[ Opcodes.br_if, 0 ],
|
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
644
696
|
};
|
645
697
|
|
646
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
648
700
|
...wasm,
|
649
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
704
|
|
652
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
707
|
|
654
708
|
const def = [
|
655
709
|
// if value != 0
|
656
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
657
711
|
|
658
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
659
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
665
719
|
|
666
720
|
return [
|
667
721
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
723
|
|
670
724
|
...typeSwitch(scope, type, {
|
671
725
|
// [TYPES.number]: def,
|
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
674
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
729
|
],
|
676
730
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
733
|
|
680
734
|
// get length
|
@@ -686,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
686
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
741
|
],
|
688
742
|
[TYPES._bytestring]: [ // duplicate of string
|
689
|
-
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
744
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
745
|
|
692
746
|
// get length
|
@@ -700,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
754
|
};
|
701
755
|
|
702
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
704
760
|
return [
|
705
761
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
763
|
|
708
764
|
...typeSwitch(scope, type, {
|
709
765
|
[TYPES._array]: [
|
@@ -711,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
711
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
768
|
],
|
713
769
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
771
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
772
|
|
717
773
|
// get length
|
@@ -722,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
722
778
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
779
|
],
|
724
780
|
[TYPES._bytestring]: [ // duplicate of string
|
725
|
-
[ Opcodes.local_get, tmp ],
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
783
|
|
728
784
|
// get length
|
@@ -734,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
790
|
],
|
735
791
|
default: [
|
736
792
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
794
|
|
739
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
800
|
};
|
745
801
|
|
746
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
748
806
|
return [
|
749
807
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
809
|
|
752
810
|
...typeSwitch(scope, type, {
|
753
811
|
[TYPES.undefined]: [
|
@@ -756,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
814
|
],
|
757
815
|
[TYPES.object]: [
|
758
816
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
818
|
|
761
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
844
|
}
|
787
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
788
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
850
|
const strictOp = op === '===' || op === '!==';
|
790
851
|
|
791
852
|
const startOut = [], endOut = [];
|
792
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
854
|
|
794
855
|
// if strict (in)equal check types match
|
795
856
|
if (strictOp) {
|
@@ -834,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
895
|
// todo: if equality op and an operand is undefined, return false
|
835
896
|
// todo: niche null hell with 0
|
836
897
|
|
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
|
-
|
898
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
899
|
+
if (op === '+') {
|
900
|
+
// todo: this should be dynamic too but for now only static
|
901
|
+
// string concat (a + b)
|
902
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
903
|
+
}
|
904
|
+
|
905
|
+
// not an equality op, NaN
|
906
|
+
if (!eqOp) return number(NaN);
|
907
|
+
|
908
|
+
// else leave bool ops
|
909
|
+
// todo: convert string to number if string and number/bool
|
910
|
+
// todo: string (>|>=|<|<=) string
|
911
|
+
|
912
|
+
// string comparison
|
913
|
+
if (op === '===' || op === '==') {
|
914
|
+
return compareStrings(scope, left, right);
|
915
|
+
}
|
916
|
+
|
917
|
+
if (op === '!==' || op === '!=') {
|
918
|
+
return [
|
919
|
+
...compareStrings(scope, left, right),
|
920
|
+
[ Opcodes.i32_eqz ]
|
921
|
+
];
|
922
|
+
}
|
923
|
+
}
|
924
|
+
|
925
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
|
926
|
+
if (op === '+') {
|
927
|
+
// todo: this should be dynamic too but for now only static
|
928
|
+
// string concat (a + b)
|
929
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
930
|
+
}
|
931
|
+
|
932
|
+
// not an equality op, NaN
|
933
|
+
if (!eqOp) return number(NaN);
|
934
|
+
|
935
|
+
// else leave bool ops
|
936
|
+
// todo: convert string to number if string and number/bool
|
937
|
+
// todo: string (>|>=|<|<=) string
|
938
|
+
|
939
|
+
// string comparison
|
940
|
+
if (op === '===' || op === '==') {
|
941
|
+
return compareStrings(scope, left, right, true);
|
942
|
+
}
|
943
|
+
|
944
|
+
if (op === '!==' || op === '!=') {
|
945
|
+
return [
|
946
|
+
...compareStrings(scope, left, right, true),
|
947
|
+
[ Opcodes.i32_eqz ]
|
948
|
+
];
|
949
|
+
}
|
950
|
+
}
|
862
951
|
|
863
952
|
let ops = operatorOpcode[valtype][op];
|
864
953
|
|
@@ -868,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
957
|
includeBuiltin(scope, builtinName);
|
869
958
|
const idx = funcIndex[builtinName];
|
870
959
|
|
871
|
-
return
|
960
|
+
return finalize([
|
872
961
|
...left,
|
873
962
|
...right,
|
874
963
|
[ Opcodes.call, idx ]
|
875
964
|
]);
|
876
965
|
}
|
877
966
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
968
|
|
880
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
970
|
ops = [ ops ];
|
882
971
|
|
883
972
|
let tmpLeft, tmpRight;
|
884
973
|
// 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
|
-
}
|
974
|
+
// todo: intelligent partial skip later
|
975
|
+
// if neither known are string, stop this madness
|
976
|
+
// we already do known checks earlier, so don't need to recheck
|
894
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
981
|
|
982
|
+
// returns false for one string, one not - but more ops/slower
|
983
|
+
// ops.unshift(...stringOnly([
|
984
|
+
// // if left is string
|
985
|
+
// ...leftType,
|
986
|
+
// ...number(TYPES.string, Valtype.i32),
|
987
|
+
// [ Opcodes.i32_eq ],
|
988
|
+
|
989
|
+
// // if right is string
|
990
|
+
// ...rightType,
|
991
|
+
// ...number(TYPES.string, Valtype.i32),
|
992
|
+
// [ Opcodes.i32_eq ],
|
993
|
+
|
994
|
+
// // if either are true
|
995
|
+
// [ Opcodes.i32_or ],
|
996
|
+
// [ Opcodes.if, Blocktype.void ],
|
997
|
+
|
998
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
999
|
+
// // if left is not string
|
1000
|
+
// ...leftType,
|
1001
|
+
// ...number(TYPES.string, Valtype.i32),
|
1002
|
+
// [ Opcodes.i32_ne ],
|
1003
|
+
|
1004
|
+
// // if right is not string
|
1005
|
+
// ...rightType,
|
1006
|
+
// ...number(TYPES.string, Valtype.i32),
|
1007
|
+
// [ Opcodes.i32_ne ],
|
1008
|
+
|
1009
|
+
// // if either are true
|
1010
|
+
// [ Opcodes.i32_or ],
|
1011
|
+
// [ Opcodes.if, Blocktype.void ],
|
1012
|
+
// ...number(0, Valtype.i32),
|
1013
|
+
// [ Opcodes.br, 2 ],
|
1014
|
+
// [ Opcodes.end ],
|
1015
|
+
|
1016
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1017
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1018
|
+
// [ Opcodes.br, 1 ],
|
1019
|
+
// [ Opcodes.end ],
|
1020
|
+
// ]));
|
1021
|
+
|
1022
|
+
// does not handle one string, one not (such cases go past)
|
898
1023
|
ops.unshift(...stringOnly([
|
899
1024
|
// if left is string
|
900
1025
|
...leftType,
|
@@ -906,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
1031
|
...number(TYPES.string, Valtype.i32),
|
907
1032
|
[ Opcodes.i32_eq ],
|
908
1033
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
911
1036
|
[ Opcodes.if, Blocktype.void ],
|
1037
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1038
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1039
|
+
[ Opcodes.br, 1 ],
|
1040
|
+
[ Opcodes.end ],
|
912
1041
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
915
1043
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
918
1046
|
|
919
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
920
1048
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
923
1051
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
926
1054
|
[ 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 ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1057
|
[ Opcodes.br, 1 ],
|
935
1058
|
[ Opcodes.end ],
|
@@ -941,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1066
|
// }
|
944
|
-
}
|
1067
|
+
}
|
945
1068
|
|
946
|
-
return
|
1069
|
+
return finalize([
|
947
1070
|
...left,
|
948
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1072
|
...right,
|
@@ -960,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1083
|
return out;
|
961
1084
|
};
|
962
1085
|
|
963
|
-
const
|
1086
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1087
|
+
return func({ name, params, locals, returns, localInd }, {
|
1088
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1089
|
+
builtin: name => {
|
1090
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1091
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1092
|
+
includeBuiltin(null, name);
|
1093
|
+
idx = funcIndex[name];
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
return idx;
|
1097
|
+
}
|
1098
|
+
});
|
1099
|
+
};
|
1100
|
+
|
1101
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
964
1102
|
const existing = funcs.find(x => x.name === name);
|
965
1103
|
if (existing) return existing;
|
966
1104
|
|
@@ -972,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1111
|
}
|
974
1112
|
|
975
|
-
|
976
|
-
const
|
977
|
-
|
978
|
-
|
979
|
-
locals,
|
980
|
-
returns,
|
981
|
-
localInd: allLocals.length,
|
982
|
-
};
|
983
|
-
|
984
|
-
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
1113
|
+
for (const x of _data) {
|
1114
|
+
const copy = { ...x };
|
1115
|
+
copy.offset += pages.size * pageSize;
|
1116
|
+
data.push(copy);
|
985
1117
|
}
|
986
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
987
1121
|
let baseGlobalIdx, i = 0;
|
988
1122
|
for (const type of globalTypes) {
|
989
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1006,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1006
1140
|
params,
|
1007
1141
|
locals,
|
1008
1142
|
returns,
|
1009
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
1010
1144
|
wasm,
|
1011
1145
|
internal: true,
|
1012
1146
|
index: currentFuncIndex++
|
@@ -1029,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1164
|
};
|
1031
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1032
1167
|
// T = JS type, V = value/pointer
|
1033
1168
|
// 0bTTT
|
1034
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1052,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1187
|
// 4: internal type
|
1053
1188
|
// 5: pointer
|
1054
1189
|
|
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'
|
1190
|
+
const isExistingProtoFunc = name => {
|
1191
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
|
1192
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1193
|
+
|
1194
|
+
return false;
|
1084
1195
|
};
|
1085
1196
|
|
1086
1197
|
const getType = (scope, _name) => {
|
1087
1198
|
const name = mapName(_name);
|
1088
1199
|
|
1200
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1201
|
+
|
1202
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1089
1203
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1204
|
+
|
1205
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1090
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1207
|
|
1092
1208
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1211
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1213
|
|
1099
1214
|
return number(type, Valtype.i32);
|
1100
1215
|
};
|
@@ -1117,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1117
1232
|
];
|
1118
1233
|
|
1119
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1120
1236
|
};
|
1121
1237
|
|
1122
1238
|
const getLastType = scope => {
|
1123
1239
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1241
|
};
|
1126
1242
|
|
1127
1243
|
const setLastType = scope => {
|
1128
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1129
1245
|
};
|
1130
1246
|
|
1131
1247
|
const getNodeType = (scope, node) => {
|
@@ -1150,13 +1266,25 @@ const getNodeType = (scope, node) => {
|
|
1150
1266
|
const name = node.callee.name;
|
1151
1267
|
if (!name) {
|
1152
1268
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1270
|
|
1155
1271
|
// presume
|
1156
1272
|
// todo: warn here?
|
1157
1273
|
return TYPES.number;
|
1158
1274
|
}
|
1159
1275
|
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1160
1288
|
const func = funcs.find(x => x.name === name);
|
1161
1289
|
|
1162
1290
|
if (func) {
|
@@ -1164,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1292
|
if (func.returnType) return func.returnType;
|
1165
1293
|
}
|
1166
1294
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1297
|
|
1170
1298
|
// check if this is a prototype function
|
@@ -1175,11 +1303,16 @@ const getNodeType = (scope, node) => {
|
|
1175
1303
|
const spl = name.slice(2).split('_');
|
1176
1304
|
|
1177
1305
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1179
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
1308
|
}
|
1181
1309
|
|
1182
|
-
if (
|
1310
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1311
|
+
// todo: return undefined for non-returning ops
|
1312
|
+
return TYPES.number;
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1316
|
|
1184
1317
|
// presume
|
1185
1318
|
// todo: warn here?
|
@@ -1227,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1227
1360
|
|
1228
1361
|
if (node.type === 'BinaryExpression') {
|
1229
1362
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1363
|
+
if (node.operator !== '+') return TYPES.number;
|
1364
|
+
|
1365
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1366
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1367
|
+
|
1368
|
+
// todo: this should be dynamic but for now only static
|
1369
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1371
|
+
|
1230
1372
|
return TYPES.number;
|
1231
1373
|
|
1232
1374
|
// todo: string concat types
|
@@ -1251,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1251
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1255
1397
|
|
1256
1398
|
return TYPES.number;
|
1257
1399
|
}
|
@@ -1260,11 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1260
1402
|
// hack: if something.length, number type
|
1261
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1262
1404
|
|
1263
|
-
//
|
1405
|
+
// ts hack
|
1406
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1407
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1408
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1411
|
+
|
1412
|
+
// presume
|
1264
1413
|
return TYPES.number;
|
1265
1414
|
}
|
1266
1415
|
|
1267
|
-
if (
|
1416
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1417
|
+
// hack
|
1418
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1422
|
|
1269
1423
|
// presume
|
1270
1424
|
// todo: warn here?
|
@@ -1277,28 +1431,11 @@ const getNodeType = (scope, node) => {
|
|
1277
1431
|
return ret;
|
1278
1432
|
};
|
1279
1433
|
|
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
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1298
1435
|
if (decl.value === null) return number(NULL);
|
1299
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1300
1438
|
if (decl.regex) {
|
1301
|
-
scope.regex[name] = decl.regex;
|
1302
1439
|
return number(1);
|
1303
1440
|
}
|
1304
1441
|
|
@@ -1314,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1314
1451
|
return makeString(scope, decl.value, global, name);
|
1315
1452
|
|
1316
1453
|
default:
|
1317
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1318
1455
|
}
|
1319
1456
|
};
|
1320
1457
|
|
@@ -1323,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1323
1460
|
|
1324
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1325
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1326
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1327
1466
|
if (inst[0] === Opcodes.if) count--;
|
1328
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1331,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1331
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1332
1471
|
|
1333
1472
|
if (depth === 0)
|
1334
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1335
1474
|
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
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++;
|
1475
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1337
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1338
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1339
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1340
1479
|
else if (inst[0] === Opcodes.call) {
|
1341
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1342
|
-
if (
|
1343
|
-
count
|
1344
|
-
} else
|
1345
|
-
|
1481
|
+
if (inst[1] === -1) {
|
1482
|
+
// todo: count for calling self
|
1483
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1484
|
+
count -= importedFuncs[inst[1]].params;
|
1485
|
+
count += importedFuncs[inst[1]].returns;
|
1486
|
+
} else {
|
1487
|
+
if (func) {
|
1488
|
+
count -= func.params.length;
|
1489
|
+
} else count--;
|
1490
|
+
if (func) count += func.returns.length;
|
1491
|
+
}
|
1346
1492
|
} else count--;
|
1347
1493
|
|
1348
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1434,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1434
1580
|
name = func.name;
|
1435
1581
|
}
|
1436
1582
|
|
1437
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1438
1584
|
// literal eval hack
|
1439
|
-
const code = decl.arguments[0]
|
1440
|
-
|
1585
|
+
const code = decl.arguments[0]?.value ?? '';
|
1586
|
+
|
1587
|
+
let parsed;
|
1588
|
+
try {
|
1589
|
+
parsed = parse(code, []);
|
1590
|
+
} catch (e) {
|
1591
|
+
if (e.name === 'SyntaxError') {
|
1592
|
+
// throw syntax errors of evals at runtime instead
|
1593
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1594
|
+
}
|
1595
|
+
|
1596
|
+
throw e;
|
1597
|
+
}
|
1441
1598
|
|
1442
1599
|
const out = generate(scope, {
|
1443
1600
|
type: 'BlockStatement',
|
@@ -1451,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1451
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1452
1609
|
out.push(
|
1453
1610
|
...getNodeType(scope, finalStatement),
|
1454
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1455
1612
|
);
|
1456
1613
|
} else if (countLeftover(out) === 0) {
|
1457
1614
|
out.push(...number(UNDEFINED));
|
1458
1615
|
out.push(
|
1459
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1460
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1461
1618
|
);
|
1462
1619
|
}
|
1463
1620
|
|
@@ -1479,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1479
1636
|
|
1480
1637
|
target = { ...decl.callee };
|
1481
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1482
1642
|
}
|
1483
1643
|
|
1484
1644
|
// literal.func()
|
1485
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1486
1646
|
// megahack for /regex/.func()
|
1487
|
-
|
1488
|
-
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1489
1649
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1490
1650
|
|
1491
1651
|
funcIndex[func.name] = func.index;
|
@@ -1501,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1501
1661
|
Opcodes.i32_from_u,
|
1502
1662
|
|
1503
1663
|
...number(TYPES.boolean, Valtype.i32),
|
1504
|
-
setLastType(scope)
|
1664
|
+
...setLastType(scope)
|
1505
1665
|
];
|
1506
1666
|
}
|
1507
1667
|
|
@@ -1526,13 +1686,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1526
1686
|
// }
|
1527
1687
|
|
1528
1688
|
if (protoName) {
|
1689
|
+
const protoBC = {};
|
1690
|
+
|
1691
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1692
|
+
|
1693
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1694
|
+
for (const x of builtinProtoCands) {
|
1695
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1696
|
+
if (type == null) continue;
|
1697
|
+
|
1698
|
+
protoBC[type] = generateCall(scope, {
|
1699
|
+
callee: {
|
1700
|
+
type: 'Identifier',
|
1701
|
+
name: x
|
1702
|
+
},
|
1703
|
+
arguments: [ target, ...decl.arguments ],
|
1704
|
+
_protoInternalCall: true
|
1705
|
+
});
|
1706
|
+
}
|
1707
|
+
}
|
1708
|
+
|
1529
1709
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1530
|
-
|
1531
|
-
if (f) acc[x] = f;
|
1710
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1532
1711
|
return acc;
|
1533
1712
|
}, {});
|
1534
1713
|
|
1535
|
-
// no prototype function candidates, ignore
|
1536
1714
|
if (Object.keys(protoCands).length > 0) {
|
1537
1715
|
// use local for cached i32 length as commonly used
|
1538
1716
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1550,7 +1728,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1550
1728
|
|
1551
1729
|
let allOptUnused = true;
|
1552
1730
|
let lengthI32CacheUsed = false;
|
1553
|
-
const protoBC = {};
|
1554
1731
|
for (const x in protoCands) {
|
1555
1732
|
const protoFunc = protoCands[x];
|
1556
1733
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1558,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1558
1735
|
...RTArrayUtil.getLength(getPointer),
|
1559
1736
|
|
1560
1737
|
...number(TYPES.number, Valtype.i32),
|
1561
|
-
setLastType(scope)
|
1738
|
+
...setLastType(scope)
|
1562
1739
|
];
|
1563
1740
|
continue;
|
1564
1741
|
}
|
@@ -1595,7 +1772,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1595
1772
|
...protoOut,
|
1596
1773
|
|
1597
1774
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1598
|
-
setLastType(scope),
|
1775
|
+
...setLastType(scope),
|
1599
1776
|
[ Opcodes.end ]
|
1600
1777
|
];
|
1601
1778
|
}
|
@@ -1621,10 +1798,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1621
1798
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1622
1799
|
];
|
1623
1800
|
}
|
1801
|
+
|
1802
|
+
if (Object.keys(protoBC).length > 0) {
|
1803
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1804
|
+
...protoBC,
|
1805
|
+
|
1806
|
+
// TODO: error better
|
1807
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1808
|
+
}, valtypeBinary);
|
1809
|
+
}
|
1624
1810
|
}
|
1625
1811
|
|
1626
1812
|
// TODO: only allows callee as literal
|
1627
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1813
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1628
1814
|
|
1629
1815
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1630
1816
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1657,16 +1843,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1657
1843
|
idx = -1;
|
1658
1844
|
}
|
1659
1845
|
|
1846
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1847
|
+
const wasmOps = {
|
1848
|
+
// pointer, align, offset
|
1849
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1850
|
+
// pointer, value, align, offset
|
1851
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1852
|
+
// pointer, align, offset
|
1853
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1854
|
+
// pointer, value, align, offset
|
1855
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1856
|
+
// pointer, align, offset
|
1857
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1858
|
+
// pointer, value, align, offset
|
1859
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1860
|
+
|
1861
|
+
// pointer, align, offset
|
1862
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1863
|
+
// pointer, value, align, offset
|
1864
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1865
|
+
|
1866
|
+
// value
|
1867
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1868
|
+
|
1869
|
+
// a, b
|
1870
|
+
i32_or: { imms: 0, args: [ true, true ], returns: 1 },
|
1871
|
+
};
|
1872
|
+
|
1873
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1874
|
+
|
1875
|
+
if (wasmOps[opName]) {
|
1876
|
+
const op = wasmOps[opName];
|
1877
|
+
|
1878
|
+
const argOut = [];
|
1879
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1880
|
+
...generate(scope, decl.arguments[i]),
|
1881
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1882
|
+
);
|
1883
|
+
|
1884
|
+
// literals only
|
1885
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1886
|
+
|
1887
|
+
return [
|
1888
|
+
...argOut,
|
1889
|
+
[ Opcodes[opName], ...imms ],
|
1890
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1891
|
+
];
|
1892
|
+
}
|
1893
|
+
}
|
1894
|
+
|
1660
1895
|
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
|
1896
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1897
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1663
1898
|
}
|
1664
1899
|
|
1665
1900
|
const func = funcs.find(x => x.index === idx);
|
1666
1901
|
|
1667
1902
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1668
1903
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1669
|
-
const
|
1904
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1670
1905
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1671
1906
|
|
1672
1907
|
let args = decl.arguments;
|
@@ -1683,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1683
1918
|
if (func && func.throws) scope.throws = true;
|
1684
1919
|
|
1685
1920
|
let out = [];
|
1686
|
-
for (
|
1921
|
+
for (let i = 0; i < args.length; i++) {
|
1922
|
+
const arg = args[i];
|
1687
1923
|
out = out.concat(generate(scope, arg));
|
1924
|
+
|
1925
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1926
|
+
out.push(Opcodes.i32_to);
|
1927
|
+
}
|
1928
|
+
|
1929
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1930
|
+
out.push(Opcodes.i32_to);
|
1931
|
+
}
|
1932
|
+
|
1688
1933
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1689
1934
|
}
|
1690
1935
|
|
1691
1936
|
out.push([ Opcodes.call, idx ]);
|
1692
1937
|
|
1693
|
-
if (!
|
1938
|
+
if (!typedReturns) {
|
1694
1939
|
// let type;
|
1695
1940
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1696
1941
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1700,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1700
1945
|
// ...number(type, Valtype.i32),
|
1701
1946
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1702
1947
|
// );
|
1703
|
-
} else out.push(setLastType(scope));
|
1948
|
+
} else out.push(...setLastType(scope));
|
1949
|
+
|
1950
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1951
|
+
out.push(Opcodes.i32_from);
|
1952
|
+
}
|
1704
1953
|
|
1705
1954
|
return out;
|
1706
1955
|
};
|
@@ -1708,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1708
1957
|
const generateNew = (scope, decl, _global, _name) => {
|
1709
1958
|
// hack: basically treat this as a normal call for builtins for now
|
1710
1959
|
const name = mapName(decl.callee.name);
|
1960
|
+
|
1711
1961
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1712
|
-
|
1962
|
+
|
1963
|
+
if (builtinFuncs[name + '$constructor']) {
|
1964
|
+
// custom ...$constructor override builtin func
|
1965
|
+
return generateCall(scope, {
|
1966
|
+
...decl,
|
1967
|
+
callee: {
|
1968
|
+
type: 'Identifier',
|
1969
|
+
name: name + '$constructor'
|
1970
|
+
}
|
1971
|
+
}, _global, _name);
|
1972
|
+
}
|
1973
|
+
|
1974
|
+
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
1975
|
|
1714
1976
|
return generateCall(scope, decl, _global, _name);
|
1715
1977
|
};
|
@@ -1826,14 +2088,14 @@ const brTable = (input, bc, returns) => {
|
|
1826
2088
|
};
|
1827
2089
|
|
1828
2090
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1829
|
-
if (!
|
2091
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1830
2092
|
|
1831
2093
|
const known = knownType(scope, type);
|
1832
2094
|
if (known != null) {
|
1833
2095
|
return bc[known] ?? bc.default;
|
1834
2096
|
}
|
1835
2097
|
|
1836
|
-
if (
|
2098
|
+
if (Prefs.typeswitchUseBrtable)
|
1837
2099
|
return brTable(type, bc, returns);
|
1838
2100
|
|
1839
2101
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1843,8 +2105,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1843
2105
|
[ Opcodes.block, returns ]
|
1844
2106
|
];
|
1845
2107
|
|
1846
|
-
// todo: use br_table?
|
1847
|
-
|
1848
2108
|
for (const x in bc) {
|
1849
2109
|
if (x === 'default') continue;
|
1850
2110
|
|
@@ -1868,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1868
2128
|
return out;
|
1869
2129
|
};
|
1870
2130
|
|
1871
|
-
const allocVar = (scope, name, global = false) => {
|
2131
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1872
2132
|
const target = global ? globals : scope.locals;
|
1873
2133
|
|
1874
2134
|
// already declared
|
@@ -1882,8 +2142,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1882
2142
|
let idx = global ? globalInd++ : scope.localInd++;
|
1883
2143
|
target[name] = { idx, type: valtypeBinary };
|
1884
2144
|
|
1885
|
-
|
1886
|
-
|
2145
|
+
if (type) {
|
2146
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2147
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2148
|
+
}
|
1887
2149
|
|
1888
2150
|
return idx;
|
1889
2151
|
};
|
@@ -1898,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1898
2160
|
};
|
1899
2161
|
|
1900
2162
|
const typeAnnoToPorfType = x => {
|
1901
|
-
if (
|
1902
|
-
if (TYPES[
|
2163
|
+
if (!x) return null;
|
2164
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2165
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1903
2166
|
|
1904
2167
|
switch (x) {
|
1905
2168
|
case 'i32':
|
2169
|
+
case 'i64':
|
2170
|
+
case 'f64':
|
1906
2171
|
return TYPES.number;
|
1907
2172
|
}
|
1908
2173
|
|
@@ -1913,7 +2178,7 @@ const extractTypeAnnotation = decl => {
|
|
1913
2178
|
let a = decl;
|
1914
2179
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1915
2180
|
|
1916
|
-
let type, elementType;
|
2181
|
+
let type = null, elementType = null;
|
1917
2182
|
if (a.typeName) {
|
1918
2183
|
type = a.typeName.name;
|
1919
2184
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1926,6 +2191,8 @@ const extractTypeAnnotation = decl => {
|
|
1926
2191
|
const typeName = type;
|
1927
2192
|
type = typeAnnoToPorfType(type);
|
1928
2193
|
|
2194
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2195
|
+
|
1929
2196
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1930
2197
|
|
1931
2198
|
return { type, typeName, elementType };
|
@@ -1942,6 +2209,8 @@ const generateVar = (scope, decl) => {
|
|
1942
2209
|
for (const x of decl.declarations) {
|
1943
2210
|
const name = mapName(x.id.name);
|
1944
2211
|
|
2212
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2213
|
+
|
1945
2214
|
if (x.init && isFuncType(x.init.type)) {
|
1946
2215
|
// hack for let a = function () { ... }
|
1947
2216
|
x.init.id = { name };
|
@@ -1957,9 +2226,10 @@ const generateVar = (scope, decl) => {
|
|
1957
2226
|
continue; // always ignore
|
1958
2227
|
}
|
1959
2228
|
|
1960
|
-
|
2229
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2230
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1961
2231
|
|
1962
|
-
if (
|
2232
|
+
if (typed) {
|
1963
2233
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1964
2234
|
}
|
1965
2235
|
|
@@ -1977,7 +2247,8 @@ const generateVar = (scope, decl) => {
|
|
1977
2247
|
return out;
|
1978
2248
|
};
|
1979
2249
|
|
1980
|
-
|
2250
|
+
// todo: optimize this func for valueUnused
|
2251
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1981
2252
|
const { type, name } = decl.left;
|
1982
2253
|
|
1983
2254
|
if (type === 'ObjectPattern') {
|
@@ -1995,9 +2266,9 @@ const generateAssign = (scope, decl) => {
|
|
1995
2266
|
// hack: .length setter
|
1996
2267
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1997
2268
|
const name = decl.left.object.name;
|
1998
|
-
const pointer = arrays
|
2269
|
+
const pointer = scope.arrays?.get(name);
|
1999
2270
|
|
2000
|
-
const aotPointer = pointer != null;
|
2271
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2001
2272
|
|
2002
2273
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2003
2274
|
|
@@ -2022,9 +2293,9 @@ const generateAssign = (scope, decl) => {
|
|
2022
2293
|
// arr[i]
|
2023
2294
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2024
2295
|
const name = decl.left.object.name;
|
2025
|
-
const pointer = arrays
|
2296
|
+
const pointer = scope.arrays?.get(name);
|
2026
2297
|
|
2027
|
-
const aotPointer = pointer != null;
|
2298
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2028
2299
|
|
2029
2300
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2030
2301
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2080,6 +2351,8 @@ const generateAssign = (scope, decl) => {
|
|
2080
2351
|
];
|
2081
2352
|
}
|
2082
2353
|
|
2354
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2355
|
+
|
2083
2356
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2084
2357
|
|
2085
2358
|
if (local === undefined) {
|
@@ -2126,9 +2399,7 @@ const generateAssign = (scope, decl) => {
|
|
2126
2399
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2127
2400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2128
2401
|
|
2129
|
-
getLastType(scope)
|
2130
|
-
// hack: type is idx+1
|
2131
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2402
|
+
...setType(scope, name, getLastType(scope))
|
2132
2403
|
];
|
2133
2404
|
}
|
2134
2405
|
|
@@ -2139,9 +2410,7 @@ const generateAssign = (scope, decl) => {
|
|
2139
2410
|
|
2140
2411
|
// todo: string concat types
|
2141
2412
|
|
2142
|
-
|
2143
|
-
...number(TYPES.number, Valtype.i32),
|
2144
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2413
|
+
...setType(scope, name, TYPES.number)
|
2145
2414
|
];
|
2146
2415
|
};
|
2147
2416
|
|
@@ -2187,7 +2456,7 @@ const generateUnary = (scope, decl) => {
|
|
2187
2456
|
return out;
|
2188
2457
|
}
|
2189
2458
|
|
2190
|
-
case 'delete':
|
2459
|
+
case 'delete': {
|
2191
2460
|
let toReturn = true, toGenerate = true;
|
2192
2461
|
|
2193
2462
|
if (decl.argument.type === 'Identifier') {
|
@@ -2209,9 +2478,26 @@ const generateUnary = (scope, decl) => {
|
|
2209
2478
|
|
2210
2479
|
out.push(...number(toReturn ? 1 : 0));
|
2211
2480
|
return out;
|
2481
|
+
}
|
2482
|
+
|
2483
|
+
case 'typeof': {
|
2484
|
+
let overrideType, toGenerate = true;
|
2485
|
+
|
2486
|
+
if (decl.argument.type === 'Identifier') {
|
2487
|
+
const out = generateIdent(scope, decl.argument);
|
2488
|
+
|
2489
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2490
|
+
if (out[1]) {
|
2491
|
+
// does not exist (2 ops from throw)
|
2492
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2493
|
+
toGenerate = false;
|
2494
|
+
}
|
2495
|
+
}
|
2212
2496
|
|
2213
|
-
|
2214
|
-
|
2497
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2498
|
+
disposeLeftover(out);
|
2499
|
+
|
2500
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2215
2501
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2216
2502
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2217
2503
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2222,27 +2508,30 @@ const generateUnary = (scope, decl) => {
|
|
2222
2508
|
|
2223
2509
|
// object and internal types
|
2224
2510
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2225
|
-
});
|
2511
|
+
}));
|
2512
|
+
|
2513
|
+
return out;
|
2514
|
+
}
|
2226
2515
|
|
2227
2516
|
default:
|
2228
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2517
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2229
2518
|
}
|
2230
2519
|
};
|
2231
2520
|
|
2232
|
-
const generateUpdate = (scope, decl) => {
|
2521
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2233
2522
|
const { name } = decl.argument;
|
2234
2523
|
|
2235
2524
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2236
2525
|
|
2237
2526
|
if (local === undefined) {
|
2238
|
-
return todo(`update expression with undefined variable
|
2527
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2239
2528
|
}
|
2240
2529
|
|
2241
2530
|
const idx = local.idx;
|
2242
2531
|
const out = [];
|
2243
2532
|
|
2244
2533
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2245
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2534
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2246
2535
|
|
2247
2536
|
switch (decl.operator) {
|
2248
2537
|
case '++':
|
@@ -2255,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
|
|
2255
2544
|
}
|
2256
2545
|
|
2257
2546
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2258
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2547
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2259
2548
|
|
2260
2549
|
return out;
|
2261
2550
|
};
|
@@ -2295,7 +2584,7 @@ const generateConditional = (scope, decl) => {
|
|
2295
2584
|
// note type
|
2296
2585
|
out.push(
|
2297
2586
|
...getNodeType(scope, decl.consequent),
|
2298
|
-
setLastType(scope)
|
2587
|
+
...setLastType(scope)
|
2299
2588
|
);
|
2300
2589
|
|
2301
2590
|
out.push([ Opcodes.else ]);
|
@@ -2304,7 +2593,7 @@ const generateConditional = (scope, decl) => {
|
|
2304
2593
|
// note type
|
2305
2594
|
out.push(
|
2306
2595
|
...getNodeType(scope, decl.alternate),
|
2307
|
-
setLastType(scope)
|
2596
|
+
...setLastType(scope)
|
2308
2597
|
);
|
2309
2598
|
|
2310
2599
|
out.push([ Opcodes.end ]);
|
@@ -2318,15 +2607,17 @@ const generateFor = (scope, decl) => {
|
|
2318
2607
|
const out = [];
|
2319
2608
|
|
2320
2609
|
if (decl.init) {
|
2321
|
-
out.push(...generate(scope, decl.init));
|
2610
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2322
2611
|
disposeLeftover(out);
|
2323
2612
|
}
|
2324
2613
|
|
2325
2614
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2326
2615
|
depth.push('for');
|
2327
2616
|
|
2328
|
-
out.push(...generate(scope, decl.test));
|
2329
|
-
|
2617
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2618
|
+
else out.push(...number(1, Valtype.i32));
|
2619
|
+
|
2620
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2330
2621
|
depth.push('if');
|
2331
2622
|
|
2332
2623
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2334,8 +2625,7 @@ const generateFor = (scope, decl) => {
|
|
2334
2625
|
out.push(...generate(scope, decl.body));
|
2335
2626
|
out.push([ Opcodes.end ]);
|
2336
2627
|
|
2337
|
-
out.push(...generate(scope, decl.update));
|
2338
|
-
depth.pop();
|
2628
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2339
2629
|
|
2340
2630
|
out.push([ Opcodes.br, 1 ]);
|
2341
2631
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2363,6 +2653,36 @@ const generateWhile = (scope, decl) => {
|
|
2363
2653
|
return out;
|
2364
2654
|
};
|
2365
2655
|
|
2656
|
+
const generateDoWhile = (scope, decl) => {
|
2657
|
+
const out = [];
|
2658
|
+
|
2659
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2660
|
+
depth.push('dowhile');
|
2661
|
+
|
2662
|
+
// block for break (includes all)
|
2663
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2664
|
+
depth.push('block');
|
2665
|
+
|
2666
|
+
// block for continue
|
2667
|
+
// includes body but not test+loop so we can exit body at anytime
|
2668
|
+
// and still test+loop after
|
2669
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2670
|
+
depth.push('block');
|
2671
|
+
|
2672
|
+
out.push(...generate(scope, decl.body));
|
2673
|
+
|
2674
|
+
out.push([ Opcodes.end ]);
|
2675
|
+
depth.pop();
|
2676
|
+
|
2677
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2678
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2679
|
+
|
2680
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2681
|
+
depth.pop(); depth.pop();
|
2682
|
+
|
2683
|
+
return out;
|
2684
|
+
};
|
2685
|
+
|
2366
2686
|
const generateForOf = (scope, decl) => {
|
2367
2687
|
const out = [];
|
2368
2688
|
|
@@ -2392,8 +2712,17 @@ const generateForOf = (scope, decl) => {
|
|
2392
2712
|
// setup local for left
|
2393
2713
|
generate(scope, decl.left);
|
2394
2714
|
|
2395
|
-
|
2715
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2716
|
+
if (!leftName && decl.left.name) {
|
2717
|
+
leftName = decl.left.name;
|
2718
|
+
|
2719
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2720
|
+
}
|
2721
|
+
|
2722
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2723
|
+
|
2396
2724
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2725
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2397
2726
|
|
2398
2727
|
depth.push('block');
|
2399
2728
|
depth.push('block');
|
@@ -2401,7 +2730,8 @@ const generateForOf = (scope, decl) => {
|
|
2401
2730
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2402
2731
|
// hack: this is naughty and will break things!
|
2403
2732
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2404
|
-
if (pages.
|
2733
|
+
if (pages.hasAnyString) {
|
2734
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2405
2735
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2406
2736
|
rawElements: new Array(1)
|
2407
2737
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2493,6 +2823,56 @@ const generateForOf = (scope, decl) => {
|
|
2493
2823
|
[ Opcodes.end ],
|
2494
2824
|
[ Opcodes.end ]
|
2495
2825
|
],
|
2826
|
+
[TYPES._bytestring]: [
|
2827
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2828
|
+
|
2829
|
+
[ Opcodes.loop, Blocktype.void ],
|
2830
|
+
|
2831
|
+
// setup new/out array
|
2832
|
+
...newOut,
|
2833
|
+
[ Opcodes.drop ],
|
2834
|
+
|
2835
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2836
|
+
|
2837
|
+
// load current string ind {arg}
|
2838
|
+
[ Opcodes.local_get, pointer ],
|
2839
|
+
[ Opcodes.local_get, counter ],
|
2840
|
+
[ Opcodes.i32_add ],
|
2841
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2842
|
+
|
2843
|
+
// store to new string ind 0
|
2844
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2845
|
+
|
2846
|
+
// return new string (page)
|
2847
|
+
...number(newPointer),
|
2848
|
+
|
2849
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2850
|
+
|
2851
|
+
[ Opcodes.block, Blocktype.void ],
|
2852
|
+
[ Opcodes.block, Blocktype.void ],
|
2853
|
+
...generate(scope, decl.body),
|
2854
|
+
[ Opcodes.end ],
|
2855
|
+
|
2856
|
+
// increment iter pointer
|
2857
|
+
// [ Opcodes.local_get, pointer ],
|
2858
|
+
// ...number(1, Valtype.i32),
|
2859
|
+
// [ Opcodes.i32_add ],
|
2860
|
+
// [ Opcodes.local_set, pointer ],
|
2861
|
+
|
2862
|
+
// increment counter by 1
|
2863
|
+
[ Opcodes.local_get, counter ],
|
2864
|
+
...number(1, Valtype.i32),
|
2865
|
+
[ Opcodes.i32_add ],
|
2866
|
+
[ Opcodes.local_tee, counter ],
|
2867
|
+
|
2868
|
+
// loop if counter != length
|
2869
|
+
[ Opcodes.local_get, length ],
|
2870
|
+
[ Opcodes.i32_ne ],
|
2871
|
+
[ Opcodes.br_if, 1 ],
|
2872
|
+
|
2873
|
+
[ Opcodes.end ],
|
2874
|
+
[ Opcodes.end ]
|
2875
|
+
],
|
2496
2876
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2497
2877
|
}, Blocktype.void));
|
2498
2878
|
|
@@ -2503,28 +2883,65 @@ const generateForOf = (scope, decl) => {
|
|
2503
2883
|
return out;
|
2504
2884
|
};
|
2505
2885
|
|
2886
|
+
// find the nearest loop in depth map by type
|
2506
2887
|
const getNearestLoop = () => {
|
2507
2888
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2508
|
-
if (
|
2889
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2509
2890
|
}
|
2510
2891
|
|
2511
2892
|
return -1;
|
2512
2893
|
};
|
2513
2894
|
|
2514
2895
|
const generateBreak = (scope, decl) => {
|
2515
|
-
const
|
2896
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2897
|
+
const type = depth[target];
|
2898
|
+
|
2899
|
+
// different loop types have different branch offsets
|
2900
|
+
// as they have different wasm block/loop/if structures
|
2901
|
+
// we need to use the right offset by type to branch to the one we want
|
2902
|
+
// for a break: exit the loop without executing anything else inside it
|
2903
|
+
const offset = ({
|
2904
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2905
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2906
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2907
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2908
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2909
|
+
})[type];
|
2910
|
+
|
2516
2911
|
return [
|
2517
|
-
[ Opcodes.br, ...signedLEB128(
|
2912
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2518
2913
|
];
|
2519
2914
|
};
|
2520
2915
|
|
2521
2916
|
const generateContinue = (scope, decl) => {
|
2522
|
-
const
|
2917
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2918
|
+
const type = depth[target];
|
2919
|
+
|
2920
|
+
// different loop types have different branch offsets
|
2921
|
+
// as they have different wasm block/loop/if structures
|
2922
|
+
// we need to use the right offset by type to branch to the one we want
|
2923
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2924
|
+
const offset = ({
|
2925
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2926
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2927
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2928
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2929
|
+
})[type];
|
2930
|
+
|
2523
2931
|
return [
|
2524
|
-
[ Opcodes.br, ...signedLEB128(
|
2932
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2525
2933
|
];
|
2526
2934
|
};
|
2527
2935
|
|
2936
|
+
const generateLabel = (scope, decl) => {
|
2937
|
+
scope.labels ??= new Map();
|
2938
|
+
|
2939
|
+
const name = decl.label.name;
|
2940
|
+
scope.labels.set(name, depth.length);
|
2941
|
+
|
2942
|
+
return generate(scope, decl.body);
|
2943
|
+
};
|
2944
|
+
|
2528
2945
|
const generateThrow = (scope, decl) => {
|
2529
2946
|
scope.throws = true;
|
2530
2947
|
|
@@ -2533,7 +2950,7 @@ const generateThrow = (scope, decl) => {
|
|
2533
2950
|
// hack: throw new X("...") -> throw "..."
|
2534
2951
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2535
2952
|
constructor = decl.argument.callee.name;
|
2536
|
-
message = decl.argument.arguments[0]
|
2953
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2537
2954
|
}
|
2538
2955
|
|
2539
2956
|
if (tags.length === 0) tags.push({
|
@@ -2545,6 +2962,9 @@ const generateThrow = (scope, decl) => {
|
|
2545
2962
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2546
2963
|
let tagIdx = tags[0].idx;
|
2547
2964
|
|
2965
|
+
scope.exceptions ??= [];
|
2966
|
+
scope.exceptions.push(exceptId);
|
2967
|
+
|
2548
2968
|
// todo: write a description of how this works lol
|
2549
2969
|
|
2550
2970
|
return [
|
@@ -2554,7 +2974,7 @@ const generateThrow = (scope, decl) => {
|
|
2554
2974
|
};
|
2555
2975
|
|
2556
2976
|
const generateTry = (scope, decl) => {
|
2557
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2977
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2558
2978
|
|
2559
2979
|
const out = [];
|
2560
2980
|
|
@@ -2585,29 +3005,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2585
3005
|
// TODO
|
2586
3006
|
// if identifier declared, use that
|
2587
3007
|
// else, use default (right)
|
2588
|
-
return todo('assignment pattern (optional arg)');
|
3008
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2589
3009
|
};
|
2590
3010
|
|
2591
3011
|
let pages = new Map();
|
2592
|
-
const allocPage = (reason, type) => {
|
3012
|
+
const allocPage = (scope, reason, type) => {
|
2593
3013
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2594
3014
|
|
2595
3015
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2596
3016
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3017
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3018
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2597
3019
|
|
2598
3020
|
const ind = pages.size;
|
2599
3021
|
pages.set(reason, { ind, type });
|
2600
3022
|
|
2601
|
-
|
3023
|
+
scope.pages ??= new Map();
|
3024
|
+
scope.pages.set(reason, { ind, type });
|
3025
|
+
|
3026
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2602
3027
|
|
2603
3028
|
return ind;
|
2604
3029
|
};
|
2605
3030
|
|
3031
|
+
// todo: add scope.pages
|
2606
3032
|
const freePage = reason => {
|
2607
3033
|
const { ind } = pages.get(reason);
|
2608
3034
|
pages.delete(reason);
|
2609
3035
|
|
2610
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3036
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2611
3037
|
|
2612
3038
|
return ind;
|
2613
3039
|
};
|
@@ -2633,15 +3059,14 @@ const StoreOps = {
|
|
2633
3059
|
|
2634
3060
|
let data = [];
|
2635
3061
|
|
2636
|
-
const compileBytes = (val, itemType
|
3062
|
+
const compileBytes = (val, itemType) => {
|
2637
3063
|
// todo: this is a mess and needs confirming / ????
|
2638
3064
|
switch (itemType) {
|
2639
3065
|
case 'i8': return [ val % 256 ];
|
2640
|
-
case 'i16': return [ val % 256,
|
2641
|
-
|
2642
|
-
case 'i32':
|
2643
|
-
|
2644
|
-
return enforceFourBytes(signedLEB128(val));
|
3066
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3067
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3068
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3069
|
+
// todo: i64
|
2645
3070
|
|
2646
3071
|
case 'f64': return ieee754_binary64(val);
|
2647
3072
|
}
|
@@ -2659,16 +3084,20 @@ const getAllocType = itemType => {
|
|
2659
3084
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2660
3085
|
const out = [];
|
2661
3086
|
|
3087
|
+
scope.arrays ??= new Map();
|
3088
|
+
|
2662
3089
|
let firstAssign = false;
|
2663
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3090
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2664
3091
|
firstAssign = true;
|
2665
3092
|
|
2666
3093
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2667
3094
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2668
|
-
|
3095
|
+
|
3096
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3097
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2669
3098
|
}
|
2670
3099
|
|
2671
|
-
const pointer = arrays.get(name);
|
3100
|
+
const pointer = scope.arrays.get(name);
|
2672
3101
|
|
2673
3102
|
const useRawElements = !!decl.rawElements;
|
2674
3103
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2676,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2676
3105
|
const valtype = itemTypeToValtype[itemType];
|
2677
3106
|
const length = elements.length;
|
2678
3107
|
|
2679
|
-
if (firstAssign && useRawElements) {
|
2680
|
-
|
3108
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3109
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3110
|
+
if (length !== 0) {
|
3111
|
+
let bytes = compileBytes(length, 'i32');
|
2681
3112
|
|
2682
|
-
|
2683
|
-
|
3113
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3114
|
+
if (elements[i] == null) continue;
|
2684
3115
|
|
2685
|
-
|
2686
|
-
|
3116
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3117
|
+
}
|
2687
3118
|
|
2688
|
-
|
2689
|
-
|
2690
|
-
|
2691
|
-
|
3119
|
+
const ind = data.push({
|
3120
|
+
offset: pointer,
|
3121
|
+
bytes
|
3122
|
+
}) - 1;
|
3123
|
+
|
3124
|
+
scope.data ??= [];
|
3125
|
+
scope.data.push(ind);
|
3126
|
+
}
|
2692
3127
|
|
2693
3128
|
// local value as pointer
|
2694
3129
|
out.push(...number(pointer));
|
@@ -2722,7 +3157,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2722
3157
|
};
|
2723
3158
|
|
2724
3159
|
const byteStringable = str => {
|
2725
|
-
if (!
|
3160
|
+
if (!Prefs.bytestring) return false;
|
2726
3161
|
|
2727
3162
|
for (let i = 0; i < str.length; i++) {
|
2728
3163
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2731,9 +3166,9 @@ const byteStringable = str => {
|
|
2731
3166
|
return true;
|
2732
3167
|
};
|
2733
3168
|
|
2734
|
-
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
3169
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2735
3170
|
const rawElements = new Array(str.length);
|
2736
|
-
let byteStringable =
|
3171
|
+
let byteStringable = Prefs.bytestring;
|
2737
3172
|
for (let i = 0; i < str.length; i++) {
|
2738
3173
|
const c = str.charCodeAt(i);
|
2739
3174
|
rawElements[i] = c;
|
@@ -2741,25 +3176,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
|
|
2741
3176
|
if (byteStringable && c > 0xFF) byteStringable = false;
|
2742
3177
|
}
|
2743
3178
|
|
3179
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3180
|
+
|
2744
3181
|
return makeArray(scope, {
|
2745
3182
|
rawElements
|
2746
3183
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2747
3184
|
};
|
2748
3185
|
|
2749
|
-
let arrays = new Map();
|
2750
3186
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2751
3187
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2752
3188
|
};
|
2753
3189
|
|
2754
3190
|
export const generateMember = (scope, decl, _global, _name) => {
|
2755
3191
|
const name = decl.object.name;
|
2756
|
-
const pointer = arrays
|
3192
|
+
const pointer = scope.arrays?.get(name);
|
2757
3193
|
|
2758
|
-
const aotPointer = pointer != null;
|
3194
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2759
3195
|
|
2760
3196
|
// hack: .length
|
2761
3197
|
if (decl.property.name === 'length') {
|
2762
|
-
|
3198
|
+
const func = funcs.find(x => x.name === name);
|
3199
|
+
if (func) {
|
3200
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3201
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3202
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3203
|
+
}
|
3204
|
+
|
3205
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3206
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3207
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3208
|
+
|
2763
3209
|
return [
|
2764
3210
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2765
3211
|
...generate(scope, decl.object),
|
@@ -2771,10 +3217,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2771
3217
|
];
|
2772
3218
|
}
|
2773
3219
|
|
3220
|
+
const object = generate(scope, decl.object);
|
3221
|
+
const property = generate(scope, decl.property);
|
3222
|
+
|
2774
3223
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2775
3224
|
// hack: this is naughty and will break things!
|
2776
3225
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2777
|
-
if (pages.
|
3226
|
+
if (pages.hasAnyString) {
|
2778
3227
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2779
3228
|
rawElements: new Array(1)
|
2780
3229
|
}, _global, _name, true, 'i16');
|
@@ -2783,7 +3232,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2783
3232
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2784
3233
|
[TYPES._array]: [
|
2785
3234
|
// get index as valtype
|
2786
|
-
...
|
3235
|
+
...property,
|
2787
3236
|
|
2788
3237
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2789
3238
|
Opcodes.i32_to_u,
|
@@ -2791,7 +3240,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2791
3240
|
[ Opcodes.i32_mul ],
|
2792
3241
|
|
2793
3242
|
...(aotPointer ? [] : [
|
2794
|
-
...
|
3243
|
+
...object,
|
2795
3244
|
Opcodes.i32_to_u,
|
2796
3245
|
[ Opcodes.i32_add ]
|
2797
3246
|
]),
|
@@ -2800,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2800
3249
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2801
3250
|
|
2802
3251
|
...number(TYPES.number, Valtype.i32),
|
2803
|
-
setLastType(scope)
|
3252
|
+
...setLastType(scope)
|
2804
3253
|
],
|
2805
3254
|
|
2806
3255
|
[TYPES.string]: [
|
@@ -2810,14 +3259,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2810
3259
|
|
2811
3260
|
...number(0, Valtype.i32), // base 0 for store later
|
2812
3261
|
|
2813
|
-
...
|
2814
|
-
|
3262
|
+
...property,
|
2815
3263
|
Opcodes.i32_to_u,
|
3264
|
+
|
2816
3265
|
...number(ValtypeSize.i16, Valtype.i32),
|
2817
3266
|
[ Opcodes.i32_mul ],
|
2818
3267
|
|
2819
3268
|
...(aotPointer ? [] : [
|
2820
|
-
...
|
3269
|
+
...object,
|
2821
3270
|
Opcodes.i32_to_u,
|
2822
3271
|
[ Opcodes.i32_add ]
|
2823
3272
|
]),
|
@@ -2832,10 +3281,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2832
3281
|
...number(newPointer),
|
2833
3282
|
|
2834
3283
|
...number(TYPES.string, Valtype.i32),
|
2835
|
-
setLastType(scope)
|
3284
|
+
...setLastType(scope)
|
3285
|
+
],
|
3286
|
+
[TYPES._bytestring]: [
|
3287
|
+
// setup new/out array
|
3288
|
+
...newOut,
|
3289
|
+
[ Opcodes.drop ],
|
3290
|
+
|
3291
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3292
|
+
|
3293
|
+
...property,
|
3294
|
+
Opcodes.i32_to_u,
|
3295
|
+
|
3296
|
+
...(aotPointer ? [] : [
|
3297
|
+
...object,
|
3298
|
+
Opcodes.i32_to_u,
|
3299
|
+
[ Opcodes.i32_add ]
|
3300
|
+
]),
|
3301
|
+
|
3302
|
+
// load current string ind {arg}
|
3303
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3304
|
+
|
3305
|
+
// store to new string ind 0
|
3306
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3307
|
+
|
3308
|
+
// return new string (page)
|
3309
|
+
...number(newPointer),
|
3310
|
+
|
3311
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3312
|
+
...setLastType(scope)
|
2836
3313
|
],
|
2837
3314
|
|
2838
|
-
default:
|
3315
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2839
3316
|
});
|
2840
3317
|
};
|
2841
3318
|
|
@@ -2845,25 +3322,36 @@ const objectHack = node => {
|
|
2845
3322
|
if (!node) return node;
|
2846
3323
|
|
2847
3324
|
if (node.type === 'MemberExpression') {
|
2848
|
-
|
3325
|
+
const out = (() => {
|
3326
|
+
if (node.computed || node.optional) return;
|
2849
3327
|
|
2850
|
-
|
3328
|
+
let objectName = node.object.name;
|
2851
3329
|
|
2852
|
-
|
2853
|
-
|
3330
|
+
// if object is not identifier or another member exp, give up
|
3331
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3332
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2854
3333
|
|
2855
|
-
|
3334
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2856
3335
|
|
2857
|
-
|
2858
|
-
|
3336
|
+
// if .length, give up (hack within a hack!)
|
3337
|
+
if (node.property.name === 'length') {
|
3338
|
+
node.object = objectHack(node.object);
|
3339
|
+
return;
|
3340
|
+
}
|
2859
3341
|
|
2860
|
-
|
2861
|
-
|
3342
|
+
// no object name, give up
|
3343
|
+
if (!objectName) return;
|
2862
3344
|
|
2863
|
-
|
2864
|
-
|
2865
|
-
|
2866
|
-
|
3345
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3346
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
3347
|
+
|
3348
|
+
return {
|
3349
|
+
type: 'Identifier',
|
3350
|
+
name
|
3351
|
+
};
|
3352
|
+
})();
|
3353
|
+
|
3354
|
+
if (out) return out;
|
2867
3355
|
}
|
2868
3356
|
|
2869
3357
|
for (const x in node) {
|
@@ -2877,8 +3365,8 @@ const objectHack = node => {
|
|
2877
3365
|
};
|
2878
3366
|
|
2879
3367
|
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');
|
3368
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3369
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2882
3370
|
|
2883
3371
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2884
3372
|
const params = decl.params ?? [];
|
@@ -2894,6 +3382,11 @@ const generateFunc = (scope, decl) => {
|
|
2894
3382
|
name
|
2895
3383
|
};
|
2896
3384
|
|
3385
|
+
if (typedInput && decl.returnType) {
|
3386
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3387
|
+
innerScope.returns = [ valtypeBinary ];
|
3388
|
+
}
|
3389
|
+
|
2897
3390
|
for (let i = 0; i < params.length; i++) {
|
2898
3391
|
allocVar(innerScope, params[i].name, false);
|
2899
3392
|
|
@@ -2915,13 +3408,13 @@ const generateFunc = (scope, decl) => {
|
|
2915
3408
|
const func = {
|
2916
3409
|
name,
|
2917
3410
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2918
|
-
|
2919
|
-
|
2920
|
-
throws: innerScope.throws,
|
2921
|
-
index: currentFuncIndex++
|
3411
|
+
index: currentFuncIndex++,
|
3412
|
+
...innerScope
|
2922
3413
|
};
|
2923
3414
|
funcIndex[name] = func.index;
|
2924
3415
|
|
3416
|
+
if (name === 'main') func.gotLastType = true;
|
3417
|
+
|
2925
3418
|
// quick hack fixes
|
2926
3419
|
for (const inst of wasm) {
|
2927
3420
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2973,7 +3466,7 @@ const internalConstrs = {
|
|
2973
3466
|
|
2974
3467
|
// todo: check in wasm instead of here
|
2975
3468
|
const literalValue = arg.value ?? 0;
|
2976
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3469
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2977
3470
|
|
2978
3471
|
return [
|
2979
3472
|
...number(0, Valtype.i32),
|
@@ -2984,7 +3477,8 @@ const internalConstrs = {
|
|
2984
3477
|
...number(pointer)
|
2985
3478
|
];
|
2986
3479
|
},
|
2987
|
-
type: TYPES._array
|
3480
|
+
type: TYPES._array,
|
3481
|
+
length: 1
|
2988
3482
|
},
|
2989
3483
|
|
2990
3484
|
__Array_of: {
|
@@ -2996,7 +3490,131 @@ const internalConstrs = {
|
|
2996
3490
|
}, global, name);
|
2997
3491
|
},
|
2998
3492
|
type: TYPES._array,
|
3493
|
+
notConstr: true,
|
3494
|
+
length: 0
|
3495
|
+
},
|
3496
|
+
|
3497
|
+
__Porffor_fastOr: {
|
3498
|
+
generate: (scope, decl) => {
|
3499
|
+
const out = [];
|
3500
|
+
|
3501
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3502
|
+
out.push(
|
3503
|
+
...generate(scope, decl.arguments[i]),
|
3504
|
+
Opcodes.i32_to_u,
|
3505
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3506
|
+
);
|
3507
|
+
}
|
3508
|
+
|
3509
|
+
out.push(Opcodes.i32_from_u);
|
3510
|
+
|
3511
|
+
return out;
|
3512
|
+
},
|
3513
|
+
type: TYPES.boolean,
|
2999
3514
|
notConstr: true
|
3515
|
+
},
|
3516
|
+
|
3517
|
+
__Porffor_fastAnd: {
|
3518
|
+
generate: (scope, decl) => {
|
3519
|
+
const out = [];
|
3520
|
+
|
3521
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3522
|
+
out.push(
|
3523
|
+
...generate(scope, decl.arguments[i]),
|
3524
|
+
Opcodes.i32_to_u,
|
3525
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3526
|
+
);
|
3527
|
+
}
|
3528
|
+
|
3529
|
+
out.push(Opcodes.i32_from_u);
|
3530
|
+
|
3531
|
+
return out;
|
3532
|
+
},
|
3533
|
+
type: TYPES.boolean,
|
3534
|
+
notConstr: true
|
3535
|
+
},
|
3536
|
+
|
3537
|
+
Boolean: {
|
3538
|
+
generate: (scope, decl) => {
|
3539
|
+
// todo: boolean object when used as constructor
|
3540
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3541
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3542
|
+
},
|
3543
|
+
type: TYPES.boolean,
|
3544
|
+
length: 1
|
3545
|
+
},
|
3546
|
+
|
3547
|
+
__Math_max: {
|
3548
|
+
generate: (scope, decl) => {
|
3549
|
+
const out = [
|
3550
|
+
...number(-Infinity)
|
3551
|
+
];
|
3552
|
+
|
3553
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3554
|
+
out.push(
|
3555
|
+
...generate(scope, decl.arguments[i]),
|
3556
|
+
[ Opcodes.f64_max ]
|
3557
|
+
);
|
3558
|
+
}
|
3559
|
+
|
3560
|
+
return out;
|
3561
|
+
},
|
3562
|
+
type: TYPES.number,
|
3563
|
+
notConstr: true,
|
3564
|
+
length: 2
|
3565
|
+
},
|
3566
|
+
|
3567
|
+
__Math_min: {
|
3568
|
+
generate: (scope, decl) => {
|
3569
|
+
const out = [
|
3570
|
+
...number(Infinity)
|
3571
|
+
];
|
3572
|
+
|
3573
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3574
|
+
out.push(
|
3575
|
+
...generate(scope, decl.arguments[i]),
|
3576
|
+
[ Opcodes.f64_min ]
|
3577
|
+
);
|
3578
|
+
}
|
3579
|
+
|
3580
|
+
return out;
|
3581
|
+
},
|
3582
|
+
type: TYPES.number,
|
3583
|
+
notConstr: true,
|
3584
|
+
length: 2
|
3585
|
+
},
|
3586
|
+
|
3587
|
+
__console_log: {
|
3588
|
+
generate: (scope, decl) => {
|
3589
|
+
const out = [];
|
3590
|
+
|
3591
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3592
|
+
out.push(
|
3593
|
+
...generateCall(scope, {
|
3594
|
+
callee: {
|
3595
|
+
type: 'Identifier',
|
3596
|
+
name: '__Porffor_print'
|
3597
|
+
},
|
3598
|
+
arguments: [ decl.arguments[i] ]
|
3599
|
+
}),
|
3600
|
+
|
3601
|
+
// print space
|
3602
|
+
...number(32),
|
3603
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3604
|
+
);
|
3605
|
+
}
|
3606
|
+
|
3607
|
+
// print newline
|
3608
|
+
out.push(
|
3609
|
+
...number(10),
|
3610
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3611
|
+
);
|
3612
|
+
|
3613
|
+
return out;
|
3614
|
+
},
|
3615
|
+
type: TYPES.undefined,
|
3616
|
+
notConstr: true,
|
3617
|
+
length: 0
|
3000
3618
|
}
|
3001
3619
|
};
|
3002
3620
|
|
@@ -3025,7 +3643,6 @@ export default program => {
|
|
3025
3643
|
funcs = [];
|
3026
3644
|
funcIndex = {};
|
3027
3645
|
depth = [];
|
3028
|
-
arrays = new Map();
|
3029
3646
|
pages = new Map();
|
3030
3647
|
data = [];
|
3031
3648
|
currentFuncIndex = importedFuncs.length;
|
@@ -3039,6 +3656,10 @@ export default program => {
|
|
3039
3656
|
|
3040
3657
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3041
3658
|
|
3659
|
+
globalThis.pageSize = PageSize;
|
3660
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3661
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3662
|
+
|
3042
3663
|
// set generic opcodes for current valtype
|
3043
3664
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3044
3665
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3047,10 +3668,10 @@ export default program => {
|
|
3047
3668
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3048
3669
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3049
3670
|
|
3050
|
-
Opcodes.i32_to = [ [
|
3051
|
-
Opcodes.i32_to_u = [ [
|
3052
|
-
Opcodes.i32_from = [ [
|
3053
|
-
Opcodes.i32_from_u = [ [
|
3671
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3672
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3673
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3674
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3054
3675
|
|
3055
3676
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3056
3677
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3063,10 +3684,6 @@ export default program => {
|
|
3063
3684
|
|
3064
3685
|
program.id = { name: 'main' };
|
3065
3686
|
|
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
3687
|
const scope = {
|
3071
3688
|
locals: {},
|
3072
3689
|
localInd: 0
|
@@ -3077,7 +3694,7 @@ export default program => {
|
|
3077
3694
|
body: program.body
|
3078
3695
|
};
|
3079
3696
|
|
3080
|
-
if (
|
3697
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3081
3698
|
|
3082
3699
|
generateFunc(scope, program);
|
3083
3700
|
|
@@ -3094,7 +3711,11 @@ export default program => {
|
|
3094
3711
|
}
|
3095
3712
|
|
3096
3713
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3097
|
-
|
3714
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3715
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3716
|
+
} else {
|
3717
|
+
main.returns = [];
|
3718
|
+
}
|
3098
3719
|
}
|
3099
3720
|
|
3100
3721
|
if (lastInst[0] === Opcodes.call) {
|