porffor 0.2.0-f2bbe1f → 0.2.0-f435128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +181 -0
- package/LICENSE +20 -20
- package/README.md +154 -89
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +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 +1856 -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} +1156 -418
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1445 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +177 -37
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +56 -40
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,41 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
62
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
63
|
switch (decl.type) {
|
60
64
|
case 'BinaryExpression':
|
61
65
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -86,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
86
90
|
return generateExp(scope, decl);
|
87
91
|
|
88
92
|
case 'CallExpression':
|
89
|
-
return generateCall(scope, decl, global, name);
|
93
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
90
94
|
|
91
95
|
case 'NewExpression':
|
92
96
|
return generateNew(scope, decl, global, name);
|
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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,52 +195,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
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
|
}
|
186
202
|
|
187
203
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
204
|
+
if (inst == null) 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;
|
216
243
|
|
217
|
-
|
218
|
-
|
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
|
+
}
|
254
|
+
|
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
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
463
|
+
if (assign && Prefs.aotPointerOpt) {
|
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
|
@@ -685,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
739
|
[ Opcodes.i32_eqz ], */
|
686
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
741
|
],
|
742
|
+
[TYPES._bytestring]: [ // duplicate of string
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
744
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
745
|
+
|
746
|
+
// get length
|
747
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
748
|
+
|
749
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
750
|
+
],
|
688
751
|
default: def
|
689
752
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
690
753
|
];
|
691
754
|
};
|
692
755
|
|
693
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
694
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
695
760
|
return [
|
696
761
|
...wasm,
|
697
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
698
763
|
|
699
764
|
...typeSwitch(scope, type, {
|
700
765
|
[TYPES._array]: [
|
@@ -702,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
702
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
703
768
|
],
|
704
769
|
[TYPES.string]: [
|
705
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
771
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
772
|
+
|
773
|
+
// get length
|
774
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
775
|
+
|
776
|
+
// if length == 0
|
777
|
+
[ Opcodes.i32_eqz ],
|
778
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
779
|
+
],
|
780
|
+
[TYPES._bytestring]: [ // duplicate of string
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
706
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
707
783
|
|
708
784
|
// get length
|
@@ -714,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
714
790
|
],
|
715
791
|
default: [
|
716
792
|
// if value == 0
|
717
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
718
794
|
|
719
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
720
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -724,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
724
800
|
};
|
725
801
|
|
726
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
727
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
728
806
|
return [
|
729
807
|
...wasm,
|
730
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
731
809
|
|
732
810
|
...typeSwitch(scope, type, {
|
733
811
|
[TYPES.undefined]: [
|
@@ -736,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
736
814
|
],
|
737
815
|
[TYPES.object]: [
|
738
816
|
// object, null if == 0
|
739
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
740
818
|
|
741
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
742
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -765,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
765
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
766
844
|
}
|
767
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
768
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
769
850
|
const strictOp = op === '===' || op === '!==';
|
770
851
|
|
771
852
|
const startOut = [], endOut = [];
|
772
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
773
854
|
|
774
855
|
// if strict (in)equal check types match
|
775
856
|
if (strictOp) {
|
@@ -814,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
814
895
|
// todo: if equality op and an operand is undefined, return false
|
815
896
|
// todo: niche null hell with 0
|
816
897
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
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
|
+
}
|
842
951
|
|
843
952
|
let ops = operatorOpcode[valtype][op];
|
844
953
|
|
@@ -848,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
848
957
|
includeBuiltin(scope, builtinName);
|
849
958
|
const idx = funcIndex[builtinName];
|
850
959
|
|
851
|
-
return
|
960
|
+
return finalize([
|
852
961
|
...left,
|
853
962
|
...right,
|
854
963
|
[ Opcodes.call, idx ]
|
855
964
|
]);
|
856
965
|
}
|
857
966
|
|
858
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
859
968
|
|
860
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
861
970
|
ops = [ ops ];
|
862
971
|
|
863
972
|
let tmpLeft, tmpRight;
|
864
973
|
// if equal op, check if strings for compareStrings
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
// todo: intelligent partial skip later
|
870
|
-
// if neither known are string, stop this madness
|
871
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
872
|
-
return;
|
873
|
-
}
|
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
|
874
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
875
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
876
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
877
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)
|
878
1023
|
ops.unshift(...stringOnly([
|
879
1024
|
// if left is string
|
880
1025
|
...leftType,
|
@@ -886,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
886
1031
|
...number(TYPES.string, Valtype.i32),
|
887
1032
|
[ Opcodes.i32_eq ],
|
888
1033
|
|
889
|
-
// if
|
890
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
891
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 ],
|
892
1041
|
|
893
|
-
//
|
894
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
895
1043
|
...leftType,
|
896
|
-
...number(TYPES.
|
897
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
898
1046
|
|
899
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
900
1048
|
...rightType,
|
901
|
-
...number(TYPES.
|
902
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
903
1051
|
|
904
|
-
// if
|
905
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
906
1054
|
[ Opcodes.if, Blocktype.void ],
|
907
|
-
...
|
908
|
-
[ Opcodes.br, 1 ],
|
909
|
-
[ Opcodes.end ],
|
910
|
-
|
911
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
912
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
913
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
914
1057
|
[ Opcodes.br, 1 ],
|
915
1058
|
[ Opcodes.end ],
|
@@ -921,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
921
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
922
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
923
1066
|
// }
|
924
|
-
}
|
1067
|
+
}
|
925
1068
|
|
926
|
-
return
|
1069
|
+
return finalize([
|
927
1070
|
...left,
|
928
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
929
1072
|
...right,
|
@@ -940,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
940
1083
|
return out;
|
941
1084
|
};
|
942
1085
|
|
943
|
-
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 = [] }) => {
|
944
1102
|
const existing = funcs.find(x => x.name === name);
|
945
1103
|
if (existing) return existing;
|
946
1104
|
|
@@ -952,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
952
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
953
1111
|
}
|
954
1112
|
|
955
|
-
|
956
|
-
const
|
957
|
-
|
958
|
-
|
959
|
-
locals,
|
960
|
-
returns,
|
961
|
-
localInd: allLocals.length,
|
962
|
-
};
|
963
|
-
|
964
|
-
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);
|
965
1117
|
}
|
966
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
967
1121
|
let baseGlobalIdx, i = 0;
|
968
1122
|
for (const type of globalTypes) {
|
969
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -986,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
986
1140
|
params,
|
987
1141
|
locals,
|
988
1142
|
returns,
|
989
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
990
1144
|
wasm,
|
991
1145
|
internal: true,
|
992
1146
|
index: currentFuncIndex++
|
@@ -1009,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1009
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1010
1164
|
};
|
1011
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1012
1167
|
// T = JS type, V = value/pointer
|
1013
1168
|
// 0bTTT
|
1014
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1032,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1032
1187
|
// 4: internal type
|
1033
1188
|
// 5: pointer
|
1034
1189
|
|
1035
|
-
const
|
1036
|
-
|
1037
|
-
|
1038
|
-
string: 0x02,
|
1039
|
-
undefined: 0x03,
|
1040
|
-
object: 0x04,
|
1041
|
-
function: 0x05,
|
1042
|
-
symbol: 0x06,
|
1043
|
-
bigint: 0x07,
|
1190
|
+
const isExistingProtoFunc = name => {
|
1191
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
|
1192
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1044
1193
|
|
1045
|
-
|
1046
|
-
_array: 0x10,
|
1047
|
-
_regexp: 0x11
|
1048
|
-
};
|
1049
|
-
|
1050
|
-
const TYPE_NAMES = {
|
1051
|
-
[TYPES.number]: 'Number',
|
1052
|
-
[TYPES.boolean]: 'Boolean',
|
1053
|
-
[TYPES.string]: 'String',
|
1054
|
-
[TYPES.undefined]: 'undefined',
|
1055
|
-
[TYPES.object]: 'Object',
|
1056
|
-
[TYPES.function]: 'Function',
|
1057
|
-
[TYPES.symbol]: 'Symbol',
|
1058
|
-
[TYPES.bigint]: 'BigInt',
|
1059
|
-
|
1060
|
-
[TYPES._array]: 'Array',
|
1061
|
-
[TYPES._regexp]: 'RegExp'
|
1194
|
+
return false;
|
1062
1195
|
};
|
1063
1196
|
|
1064
1197
|
const getType = (scope, _name) => {
|
1065
1198
|
const name = mapName(_name);
|
1066
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);
|
1067
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);
|
1068
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1069
1207
|
|
1070
1208
|
let type = TYPES.undefined;
|
1071
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1072
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1073
1211
|
|
1074
|
-
if (name
|
1075
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1076
1213
|
|
1077
1214
|
return number(type, Valtype.i32);
|
1078
1215
|
};
|
@@ -1095,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1095
1232
|
];
|
1096
1233
|
|
1097
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1098
1236
|
};
|
1099
1237
|
|
1100
1238
|
const getLastType = scope => {
|
1101
1239
|
scope.gotLastType = true;
|
1102
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1103
1241
|
};
|
1104
1242
|
|
1105
1243
|
const setLastType = scope => {
|
1106
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1107
1245
|
};
|
1108
1246
|
|
1109
1247
|
const getNodeType = (scope, node) => {
|
@@ -1111,6 +1249,8 @@ const getNodeType = (scope, node) => {
|
|
1111
1249
|
if (node.type === 'Literal') {
|
1112
1250
|
if (node.regex) return TYPES._regexp;
|
1113
1251
|
|
1252
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1253
|
+
|
1114
1254
|
return TYPES[typeof node.value];
|
1115
1255
|
}
|
1116
1256
|
|
@@ -1124,6 +1264,27 @@ const getNodeType = (scope, node) => {
|
|
1124
1264
|
|
1125
1265
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1126
1266
|
const name = node.callee.name;
|
1267
|
+
if (!name) {
|
1268
|
+
// iife
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1270
|
+
|
1271
|
+
// presume
|
1272
|
+
// todo: warn here?
|
1273
|
+
return TYPES.number;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1127
1288
|
const func = funcs.find(x => x.name === name);
|
1128
1289
|
|
1129
1290
|
if (func) {
|
@@ -1131,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1131
1292
|
if (func.returnType) return func.returnType;
|
1132
1293
|
}
|
1133
1294
|
|
1134
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1135
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1136
1297
|
|
1137
1298
|
// check if this is a prototype function
|
@@ -1142,11 +1303,16 @@ const getNodeType = (scope, node) => {
|
|
1142
1303
|
const spl = name.slice(2).split('_');
|
1143
1304
|
|
1144
1305
|
const func = spl[spl.length - 1];
|
1145
|
-
const protoFuncs = Object.
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1146
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1147
1308
|
}
|
1148
1309
|
|
1149
|
-
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);
|
1150
1316
|
|
1151
1317
|
// presume
|
1152
1318
|
// todo: warn here?
|
@@ -1194,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1194
1360
|
|
1195
1361
|
if (node.type === 'BinaryExpression') {
|
1196
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
|
+
|
1197
1372
|
return TYPES.number;
|
1198
1373
|
|
1199
1374
|
// todo: string concat types
|
@@ -1218,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1219
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1220
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1221
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1222
1397
|
|
1223
1398
|
return TYPES.number;
|
1224
1399
|
}
|
@@ -1227,11 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1227
1402
|
// hack: if something.length, number type
|
1228
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1229
1404
|
|
1230
|
-
//
|
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
|
1231
1413
|
return TYPES.number;
|
1232
1414
|
}
|
1233
1415
|
|
1234
|
-
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);
|
1235
1422
|
|
1236
1423
|
// presume
|
1237
1424
|
// todo: warn here?
|
@@ -1244,28 +1431,11 @@ const getNodeType = (scope, node) => {
|
|
1244
1431
|
return ret;
|
1245
1432
|
};
|
1246
1433
|
|
1247
|
-
const toString = (scope, wasm, type) => {
|
1248
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1249
|
-
return [
|
1250
|
-
...wasm,
|
1251
|
-
[ Opcodes.local_set, tmp ],
|
1252
|
-
|
1253
|
-
...typeSwitch(scope, type, {
|
1254
|
-
[TYPES.string]: [
|
1255
|
-
[ Opcodes.local_get, tmp ]
|
1256
|
-
],
|
1257
|
-
[TYPES.undefined]: [
|
1258
|
-
// [ Opcodes.]
|
1259
|
-
]
|
1260
|
-
})
|
1261
|
-
]
|
1262
|
-
};
|
1263
|
-
|
1264
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1265
1435
|
if (decl.value === null) return number(NULL);
|
1266
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1267
1438
|
if (decl.regex) {
|
1268
|
-
scope.regex[name] = decl.regex;
|
1269
1439
|
return number(1);
|
1270
1440
|
}
|
1271
1441
|
|
@@ -1281,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1281
1451
|
return makeString(scope, decl.value, global, name);
|
1282
1452
|
|
1283
1453
|
default:
|
1284
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1285
1455
|
}
|
1286
1456
|
};
|
1287
1457
|
|
@@ -1290,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1290
1460
|
|
1291
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1292
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1293
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1294
1466
|
if (inst[0] === Opcodes.if) count--;
|
1295
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1298,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1298
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1299
1471
|
|
1300
1472
|
if (depth === 0)
|
1301
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1302
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1303
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1304
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1474
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1475
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1476
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1305
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1306
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1307
1479
|
else if (inst[0] === Opcodes.call) {
|
1308
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1309
|
-
if (
|
1310
|
-
count
|
1311
|
-
} else
|
1312
|
-
|
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
|
+
}
|
1313
1492
|
} else count--;
|
1314
1493
|
|
1315
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1327,7 +1506,7 @@ const disposeLeftover = wasm => {
|
|
1327
1506
|
const generateExp = (scope, decl) => {
|
1328
1507
|
const expression = decl.expression;
|
1329
1508
|
|
1330
|
-
const out = generate(scope, expression);
|
1509
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1331
1510
|
disposeLeftover(out);
|
1332
1511
|
|
1333
1512
|
return out;
|
@@ -1385,7 +1564,7 @@ const RTArrayUtil = {
|
|
1385
1564
|
]
|
1386
1565
|
};
|
1387
1566
|
|
1388
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1567
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1389
1568
|
/* const callee = decl.callee;
|
1390
1569
|
const args = decl.arguments;
|
1391
1570
|
|
@@ -1401,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1401
1580
|
name = func.name;
|
1402
1581
|
}
|
1403
1582
|
|
1404
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1405
1584
|
// literal eval hack
|
1406
|
-
const code = decl.arguments[0]
|
1407
|
-
|
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
|
+
}
|
1408
1598
|
|
1409
1599
|
const out = generate(scope, {
|
1410
1600
|
type: 'BlockStatement',
|
@@ -1418,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1418
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1419
1609
|
out.push(
|
1420
1610
|
...getNodeType(scope, finalStatement),
|
1421
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1422
1612
|
);
|
1423
1613
|
} else if (countLeftover(out) === 0) {
|
1424
1614
|
out.push(...number(UNDEFINED));
|
1425
1615
|
out.push(
|
1426
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1427
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1428
1618
|
);
|
1429
1619
|
}
|
1430
1620
|
|
@@ -1446,29 +1636,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1446
1636
|
|
1447
1637
|
target = { ...decl.callee };
|
1448
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1449
1642
|
}
|
1450
1643
|
|
1451
1644
|
// literal.func()
|
1452
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1453
1646
|
// megahack for /regex/.func()
|
1454
|
-
|
1455
|
-
|
1456
|
-
const
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1649
|
+
const regex = decl.callee.object.regex.pattern;
|
1650
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1457
1651
|
|
1458
|
-
funcIndex[
|
1459
|
-
|
1652
|
+
if (!funcIndex[rhemynName]) {
|
1653
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1460
1654
|
|
1655
|
+
funcIndex[func.name] = func.index;
|
1656
|
+
funcs.push(func);
|
1657
|
+
}
|
1658
|
+
|
1659
|
+
const idx = funcIndex[rhemynName];
|
1461
1660
|
return [
|
1462
1661
|
// make string arg
|
1463
1662
|
...generate(scope, decl.arguments[0]),
|
1663
|
+
Opcodes.i32_to_u,
|
1664
|
+
...getNodeType(scope, decl.arguments[0]),
|
1464
1665
|
|
1465
1666
|
// call regex func
|
1466
|
-
Opcodes.
|
1467
|
-
[ Opcodes.call, func.index ],
|
1667
|
+
[ Opcodes.call, idx ],
|
1468
1668
|
Opcodes.i32_from_u,
|
1469
1669
|
|
1470
1670
|
...number(TYPES.boolean, Valtype.i32),
|
1471
|
-
setLastType(scope)
|
1671
|
+
...setLastType(scope)
|
1472
1672
|
];
|
1473
1673
|
}
|
1474
1674
|
|
@@ -1493,23 +1693,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1493
1693
|
// }
|
1494
1694
|
|
1495
1695
|
if (protoName) {
|
1696
|
+
const protoBC = {};
|
1697
|
+
|
1698
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1699
|
+
|
1700
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1701
|
+
for (const x of builtinProtoCands) {
|
1702
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1703
|
+
if (type == null) continue;
|
1704
|
+
|
1705
|
+
protoBC[type] = generateCall(scope, {
|
1706
|
+
callee: {
|
1707
|
+
type: 'Identifier',
|
1708
|
+
name: x
|
1709
|
+
},
|
1710
|
+
arguments: [ target, ...decl.arguments ],
|
1711
|
+
_protoInternalCall: true
|
1712
|
+
});
|
1713
|
+
}
|
1714
|
+
}
|
1715
|
+
|
1496
1716
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1497
|
-
|
1498
|
-
if (f) acc[x] = f;
|
1717
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1499
1718
|
return acc;
|
1500
1719
|
}, {});
|
1501
1720
|
|
1502
|
-
// no prototype function candidates, ignore
|
1503
1721
|
if (Object.keys(protoCands).length > 0) {
|
1504
1722
|
// use local for cached i32 length as commonly used
|
1505
1723
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1506
1724
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1507
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1508
1725
|
|
1509
1726
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1510
1727
|
|
1728
|
+
const rawPointer = [
|
1729
|
+
...generate(scope, target),
|
1730
|
+
Opcodes.i32_to_u
|
1731
|
+
];
|
1732
|
+
|
1733
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1734
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1735
|
+
|
1736
|
+
let allOptUnused = true;
|
1511
1737
|
let lengthI32CacheUsed = false;
|
1512
|
-
const protoBC = {};
|
1513
1738
|
for (const x in protoCands) {
|
1514
1739
|
const protoFunc = protoCands[x];
|
1515
1740
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1517,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1517
1742
|
...RTArrayUtil.getLength(getPointer),
|
1518
1743
|
|
1519
1744
|
...number(TYPES.number, Valtype.i32),
|
1520
|
-
setLastType(scope)
|
1745
|
+
...setLastType(scope)
|
1521
1746
|
];
|
1522
1747
|
continue;
|
1523
1748
|
}
|
@@ -1527,6 +1752,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1527
1752
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1528
1753
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1529
1754
|
|
1755
|
+
let optUnused = false;
|
1530
1756
|
const protoOut = protoFunc(getPointer, {
|
1531
1757
|
getCachedI32: () => {
|
1532
1758
|
lengthI32CacheUsed = true;
|
@@ -1541,23 +1767,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1541
1767
|
return makeArray(scope, {
|
1542
1768
|
rawElements: new Array(length)
|
1543
1769
|
}, _global, _name, true, itemType);
|
1770
|
+
}, () => {
|
1771
|
+
optUnused = true;
|
1772
|
+
return unusedValue;
|
1544
1773
|
});
|
1545
1774
|
|
1775
|
+
if (!optUnused) allOptUnused = false;
|
1776
|
+
|
1546
1777
|
protoBC[x] = [
|
1547
|
-
[ Opcodes.block, valtypeBinary ],
|
1778
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1548
1779
|
...protoOut,
|
1549
1780
|
|
1550
1781
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1551
|
-
setLastType(scope),
|
1782
|
+
...setLastType(scope),
|
1552
1783
|
[ Opcodes.end ]
|
1553
1784
|
];
|
1554
1785
|
}
|
1555
1786
|
|
1556
|
-
|
1557
|
-
...generate(scope, target),
|
1787
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1558
1788
|
|
1559
|
-
|
1560
|
-
|
1789
|
+
return [
|
1790
|
+
...(usePointerCache ? [
|
1791
|
+
...rawPointer,
|
1792
|
+
[ Opcodes.local_set, pointerLocal ],
|
1793
|
+
] : []),
|
1561
1794
|
|
1562
1795
|
...(!lengthI32CacheUsed ? [] : [
|
1563
1796
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1569,13 +1802,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1569
1802
|
|
1570
1803
|
// TODO: error better
|
1571
1804
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1572
|
-
}, valtypeBinary),
|
1805
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1573
1806
|
];
|
1574
1807
|
}
|
1808
|
+
|
1809
|
+
if (Object.keys(protoBC).length > 0) {
|
1810
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1811
|
+
...protoBC,
|
1812
|
+
|
1813
|
+
// TODO: error better
|
1814
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1815
|
+
}, valtypeBinary);
|
1816
|
+
}
|
1575
1817
|
}
|
1576
1818
|
|
1577
1819
|
// TODO: only allows callee as literal
|
1578
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1820
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1579
1821
|
|
1580
1822
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1581
1823
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1585,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1585
1827
|
idx = funcIndex[name];
|
1586
1828
|
|
1587
1829
|
// infer arguments types from builtins params
|
1588
|
-
const func = funcs.find(x => x.name === name);
|
1589
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
}
|
1830
|
+
// const func = funcs.find(x => x.name === name);
|
1831
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1832
|
+
// const arg = decl.arguments[i];
|
1833
|
+
// if (!arg.name) continue;
|
1834
|
+
|
1835
|
+
// const local = scope.locals[arg.name];
|
1836
|
+
// if (!local) continue;
|
1837
|
+
|
1838
|
+
// local.type = func.params[i];
|
1839
|
+
// if (local.type === Valtype.v128) {
|
1840
|
+
// // specify vec subtype inferred from last vec type in function name
|
1841
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1842
|
+
// }
|
1843
|
+
// }
|
1602
1844
|
}
|
1603
1845
|
|
1604
1846
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1608,16 +1850,65 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1608
1850
|
idx = -1;
|
1609
1851
|
}
|
1610
1852
|
|
1853
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1854
|
+
const wasmOps = {
|
1855
|
+
// pointer, align, offset
|
1856
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1859
|
+
// pointer, align, offset
|
1860
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1861
|
+
// pointer, value, align, offset
|
1862
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1863
|
+
// pointer, align, offset
|
1864
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1865
|
+
// pointer, value, align, offset
|
1866
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1867
|
+
|
1868
|
+
// pointer, align, offset
|
1869
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1870
|
+
// pointer, value, align, offset
|
1871
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1872
|
+
|
1873
|
+
// value
|
1874
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1875
|
+
|
1876
|
+
// a, b
|
1877
|
+
i32_or: { imms: 0, args: [ true, true ], returns: 1 },
|
1878
|
+
};
|
1879
|
+
|
1880
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1881
|
+
|
1882
|
+
if (wasmOps[opName]) {
|
1883
|
+
const op = wasmOps[opName];
|
1884
|
+
|
1885
|
+
const argOut = [];
|
1886
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1887
|
+
...generate(scope, decl.arguments[i]),
|
1888
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1889
|
+
);
|
1890
|
+
|
1891
|
+
// literals only
|
1892
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1893
|
+
|
1894
|
+
return [
|
1895
|
+
...argOut,
|
1896
|
+
[ Opcodes[opName], ...imms ],
|
1897
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1898
|
+
];
|
1899
|
+
}
|
1900
|
+
}
|
1901
|
+
|
1611
1902
|
if (idx === undefined) {
|
1612
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1613
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1903
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1904
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1614
1905
|
}
|
1615
1906
|
|
1616
1907
|
const func = funcs.find(x => x.index === idx);
|
1617
1908
|
|
1618
1909
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1619
1910
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1620
|
-
const
|
1911
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1621
1912
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1622
1913
|
|
1623
1914
|
let args = decl.arguments;
|
@@ -1634,14 +1925,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1634
1925
|
if (func && func.throws) scope.throws = true;
|
1635
1926
|
|
1636
1927
|
let out = [];
|
1637
|
-
for (
|
1928
|
+
for (let i = 0; i < args.length; i++) {
|
1929
|
+
const arg = args[i];
|
1638
1930
|
out = out.concat(generate(scope, arg));
|
1931
|
+
|
1932
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1933
|
+
out.push(Opcodes.i32_to);
|
1934
|
+
}
|
1935
|
+
|
1936
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1937
|
+
out.push(Opcodes.i32_to);
|
1938
|
+
}
|
1939
|
+
|
1639
1940
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1640
1941
|
}
|
1641
1942
|
|
1642
1943
|
out.push([ Opcodes.call, idx ]);
|
1643
1944
|
|
1644
|
-
if (!
|
1945
|
+
if (!typedReturns) {
|
1645
1946
|
// let type;
|
1646
1947
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1647
1948
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1651,7 +1952,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1651
1952
|
// ...number(type, Valtype.i32),
|
1652
1953
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1653
1954
|
// );
|
1654
|
-
} else out.push(setLastType(scope));
|
1955
|
+
} else out.push(...setLastType(scope));
|
1956
|
+
|
1957
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1958
|
+
out.push(Opcodes.i32_from);
|
1959
|
+
}
|
1655
1960
|
|
1656
1961
|
return out;
|
1657
1962
|
};
|
@@ -1659,8 +1964,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1659
1964
|
const generateNew = (scope, decl, _global, _name) => {
|
1660
1965
|
// hack: basically treat this as a normal call for builtins for now
|
1661
1966
|
const name = mapName(decl.callee.name);
|
1967
|
+
|
1662
1968
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1663
|
-
|
1969
|
+
|
1970
|
+
if (builtinFuncs[name + '$constructor']) {
|
1971
|
+
// custom ...$constructor override builtin func
|
1972
|
+
return generateCall(scope, {
|
1973
|
+
...decl,
|
1974
|
+
callee: {
|
1975
|
+
type: 'Identifier',
|
1976
|
+
name: name + '$constructor'
|
1977
|
+
}
|
1978
|
+
}, _global, _name);
|
1979
|
+
}
|
1980
|
+
|
1981
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1664
1982
|
|
1665
1983
|
return generateCall(scope, decl, _global, _name);
|
1666
1984
|
};
|
@@ -1777,12 +2095,14 @@ const brTable = (input, bc, returns) => {
|
|
1777
2095
|
};
|
1778
2096
|
|
1779
2097
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2098
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
2099
|
+
|
1780
2100
|
const known = knownType(scope, type);
|
1781
2101
|
if (known != null) {
|
1782
2102
|
return bc[known] ?? bc.default;
|
1783
2103
|
}
|
1784
2104
|
|
1785
|
-
if (
|
2105
|
+
if (Prefs.typeswitchUseBrtable)
|
1786
2106
|
return brTable(type, bc, returns);
|
1787
2107
|
|
1788
2108
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1792,8 +2112,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1792
2112
|
[ Opcodes.block, returns ]
|
1793
2113
|
];
|
1794
2114
|
|
1795
|
-
// todo: use br_table?
|
1796
|
-
|
1797
2115
|
for (const x in bc) {
|
1798
2116
|
if (x === 'default') continue;
|
1799
2117
|
|
@@ -1817,7 +2135,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1817
2135
|
return out;
|
1818
2136
|
};
|
1819
2137
|
|
1820
|
-
const allocVar = (scope, name, global = false) => {
|
2138
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1821
2139
|
const target = global ? globals : scope.locals;
|
1822
2140
|
|
1823
2141
|
// already declared
|
@@ -1831,8 +2149,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1831
2149
|
let idx = global ? globalInd++ : scope.localInd++;
|
1832
2150
|
target[name] = { idx, type: valtypeBinary };
|
1833
2151
|
|
1834
|
-
|
1835
|
-
|
2152
|
+
if (type) {
|
2153
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2154
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2155
|
+
}
|
1836
2156
|
|
1837
2157
|
return idx;
|
1838
2158
|
};
|
@@ -1847,11 +2167,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1847
2167
|
};
|
1848
2168
|
|
1849
2169
|
const typeAnnoToPorfType = x => {
|
1850
|
-
if (
|
1851
|
-
if (TYPES[
|
2170
|
+
if (!x) return null;
|
2171
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2172
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1852
2173
|
|
1853
2174
|
switch (x) {
|
1854
2175
|
case 'i32':
|
2176
|
+
case 'i64':
|
2177
|
+
case 'f64':
|
1855
2178
|
return TYPES.number;
|
1856
2179
|
}
|
1857
2180
|
|
@@ -1862,7 +2185,7 @@ const extractTypeAnnotation = decl => {
|
|
1862
2185
|
let a = decl;
|
1863
2186
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1864
2187
|
|
1865
|
-
let type, elementType;
|
2188
|
+
let type = null, elementType = null;
|
1866
2189
|
if (a.typeName) {
|
1867
2190
|
type = a.typeName.name;
|
1868
2191
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1875,6 +2198,8 @@ const extractTypeAnnotation = decl => {
|
|
1875
2198
|
const typeName = type;
|
1876
2199
|
type = typeAnnoToPorfType(type);
|
1877
2200
|
|
2201
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2202
|
+
|
1878
2203
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1879
2204
|
|
1880
2205
|
return { type, typeName, elementType };
|
@@ -1887,10 +2212,13 @@ const generateVar = (scope, decl) => {
|
|
1887
2212
|
|
1888
2213
|
// global variable if in top scope (main) and var ..., or if wanted
|
1889
2214
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2215
|
+
const target = global ? globals : scope.locals;
|
1890
2216
|
|
1891
2217
|
for (const x of decl.declarations) {
|
1892
2218
|
const name = mapName(x.id.name);
|
1893
2219
|
|
2220
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2221
|
+
|
1894
2222
|
if (x.init && isFuncType(x.init.type)) {
|
1895
2223
|
// hack for let a = function () { ... }
|
1896
2224
|
x.init.id = { name };
|
@@ -1906,16 +2234,29 @@ const generateVar = (scope, decl) => {
|
|
1906
2234
|
continue; // always ignore
|
1907
2235
|
}
|
1908
2236
|
|
1909
|
-
|
2237
|
+
// // generate init before allocating var
|
2238
|
+
// let generated;
|
2239
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2240
|
+
|
2241
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2242
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1910
2243
|
|
1911
|
-
if (
|
2244
|
+
if (typed) {
|
1912
2245
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1913
2246
|
}
|
1914
2247
|
|
1915
2248
|
if (x.init) {
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
2249
|
+
const generated = generate(scope, x.init, global, name);
|
2250
|
+
if (scope.arrays?.get(name) != null) {
|
2251
|
+
// hack to set local as pointer before
|
2252
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2253
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2254
|
+
generated.pop();
|
2255
|
+
out = out.concat(generated);
|
2256
|
+
} else {
|
2257
|
+
out = out.concat(generated);
|
2258
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2259
|
+
}
|
1919
2260
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1920
2261
|
}
|
1921
2262
|
|
@@ -1926,7 +2267,8 @@ const generateVar = (scope, decl) => {
|
|
1926
2267
|
return out;
|
1927
2268
|
};
|
1928
2269
|
|
1929
|
-
|
2270
|
+
// todo: optimize this func for valueUnused
|
2271
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1930
2272
|
const { type, name } = decl.left;
|
1931
2273
|
|
1932
2274
|
if (type === 'ObjectPattern') {
|
@@ -1941,22 +2283,30 @@ const generateAssign = (scope, decl) => {
|
|
1941
2283
|
return [];
|
1942
2284
|
}
|
1943
2285
|
|
2286
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2287
|
+
|
1944
2288
|
// hack: .length setter
|
1945
2289
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1946
2290
|
const name = decl.left.object.name;
|
1947
|
-
const pointer = arrays
|
2291
|
+
const pointer = scope.arrays?.get(name);
|
1948
2292
|
|
1949
|
-
const aotPointer = pointer != null;
|
2293
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1950
2294
|
|
1951
2295
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2296
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1952
2297
|
|
1953
2298
|
return [
|
1954
2299
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1955
2300
|
...generate(scope, decl.left.object),
|
1956
2301
|
Opcodes.i32_to_u
|
1957
2302
|
]),
|
2303
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1958
2304
|
|
1959
|
-
...generate(scope, decl.right),
|
2305
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2306
|
+
[ Opcodes.local_get, pointerTmp ],
|
2307
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2308
|
+
Opcodes.i32_from_u
|
2309
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
1960
2310
|
[ Opcodes.local_tee, newValueTmp ],
|
1961
2311
|
|
1962
2312
|
Opcodes.i32_to_u,
|
@@ -1966,14 +2316,12 @@ const generateAssign = (scope, decl) => {
|
|
1966
2316
|
];
|
1967
2317
|
}
|
1968
2318
|
|
1969
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1970
|
-
|
1971
2319
|
// arr[i]
|
1972
2320
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1973
2321
|
const name = decl.left.object.name;
|
1974
|
-
const pointer = arrays
|
2322
|
+
const pointer = scope.arrays?.get(name);
|
1975
2323
|
|
1976
|
-
const aotPointer = pointer != null;
|
2324
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1977
2325
|
|
1978
2326
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1979
2327
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2029,6 +2377,8 @@ const generateAssign = (scope, decl) => {
|
|
2029
2377
|
];
|
2030
2378
|
}
|
2031
2379
|
|
2380
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2381
|
+
|
2032
2382
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2033
2383
|
|
2034
2384
|
if (local === undefined) {
|
@@ -2075,9 +2425,7 @@ const generateAssign = (scope, decl) => {
|
|
2075
2425
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2076
2426
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2077
2427
|
|
2078
|
-
getLastType(scope)
|
2079
|
-
// hack: type is idx+1
|
2080
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2428
|
+
...setType(scope, name, getLastType(scope))
|
2081
2429
|
];
|
2082
2430
|
}
|
2083
2431
|
|
@@ -2088,9 +2436,7 @@ const generateAssign = (scope, decl) => {
|
|
2088
2436
|
|
2089
2437
|
// todo: string concat types
|
2090
2438
|
|
2091
|
-
|
2092
|
-
...number(TYPES.number, Valtype.i32),
|
2093
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2439
|
+
...setType(scope, name, TYPES.number)
|
2094
2440
|
];
|
2095
2441
|
};
|
2096
2442
|
|
@@ -2136,7 +2482,7 @@ const generateUnary = (scope, decl) => {
|
|
2136
2482
|
return out;
|
2137
2483
|
}
|
2138
2484
|
|
2139
|
-
case 'delete':
|
2485
|
+
case 'delete': {
|
2140
2486
|
let toReturn = true, toGenerate = true;
|
2141
2487
|
|
2142
2488
|
if (decl.argument.type === 'Identifier') {
|
@@ -2158,38 +2504,60 @@ const generateUnary = (scope, decl) => {
|
|
2158
2504
|
|
2159
2505
|
out.push(...number(toReturn ? 1 : 0));
|
2160
2506
|
return out;
|
2507
|
+
}
|
2508
|
+
|
2509
|
+
case 'typeof': {
|
2510
|
+
let overrideType, toGenerate = true;
|
2511
|
+
|
2512
|
+
if (decl.argument.type === 'Identifier') {
|
2513
|
+
const out = generateIdent(scope, decl.argument);
|
2514
|
+
|
2515
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2516
|
+
if (out[1]) {
|
2517
|
+
// does not exist (2 ops from throw)
|
2518
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2519
|
+
toGenerate = false;
|
2520
|
+
}
|
2521
|
+
}
|
2161
2522
|
|
2162
|
-
|
2163
|
-
|
2523
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2524
|
+
disposeLeftover(out);
|
2525
|
+
|
2526
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2164
2527
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2165
2528
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2166
2529
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2167
2530
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2168
2531
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2169
2532
|
|
2533
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2534
|
+
|
2170
2535
|
// object and internal types
|
2171
2536
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2172
|
-
});
|
2537
|
+
}));
|
2538
|
+
|
2539
|
+
return out;
|
2540
|
+
}
|
2173
2541
|
|
2174
2542
|
default:
|
2175
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2543
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2176
2544
|
}
|
2177
2545
|
};
|
2178
2546
|
|
2179
|
-
const generateUpdate = (scope, decl) => {
|
2547
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2180
2548
|
const { name } = decl.argument;
|
2181
2549
|
|
2182
2550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2183
2551
|
|
2184
2552
|
if (local === undefined) {
|
2185
|
-
return todo(`update expression with undefined variable
|
2553
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2186
2554
|
}
|
2187
2555
|
|
2188
2556
|
const idx = local.idx;
|
2189
2557
|
const out = [];
|
2190
2558
|
|
2191
2559
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2192
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2560
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2193
2561
|
|
2194
2562
|
switch (decl.operator) {
|
2195
2563
|
case '++':
|
@@ -2202,7 +2570,7 @@ const generateUpdate = (scope, decl) => {
|
|
2202
2570
|
}
|
2203
2571
|
|
2204
2572
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2205
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2573
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2206
2574
|
|
2207
2575
|
return out;
|
2208
2576
|
};
|
@@ -2242,7 +2610,7 @@ const generateConditional = (scope, decl) => {
|
|
2242
2610
|
// note type
|
2243
2611
|
out.push(
|
2244
2612
|
...getNodeType(scope, decl.consequent),
|
2245
|
-
setLastType(scope)
|
2613
|
+
...setLastType(scope)
|
2246
2614
|
);
|
2247
2615
|
|
2248
2616
|
out.push([ Opcodes.else ]);
|
@@ -2251,7 +2619,7 @@ const generateConditional = (scope, decl) => {
|
|
2251
2619
|
// note type
|
2252
2620
|
out.push(
|
2253
2621
|
...getNodeType(scope, decl.alternate),
|
2254
|
-
setLastType(scope)
|
2622
|
+
...setLastType(scope)
|
2255
2623
|
);
|
2256
2624
|
|
2257
2625
|
out.push([ Opcodes.end ]);
|
@@ -2265,15 +2633,17 @@ const generateFor = (scope, decl) => {
|
|
2265
2633
|
const out = [];
|
2266
2634
|
|
2267
2635
|
if (decl.init) {
|
2268
|
-
out.push(...generate(scope, decl.init));
|
2636
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2269
2637
|
disposeLeftover(out);
|
2270
2638
|
}
|
2271
2639
|
|
2272
2640
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2273
2641
|
depth.push('for');
|
2274
2642
|
|
2275
|
-
out.push(...generate(scope, decl.test));
|
2276
|
-
|
2643
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2644
|
+
else out.push(...number(1, Valtype.i32));
|
2645
|
+
|
2646
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2277
2647
|
depth.push('if');
|
2278
2648
|
|
2279
2649
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2281,8 +2651,7 @@ const generateFor = (scope, decl) => {
|
|
2281
2651
|
out.push(...generate(scope, decl.body));
|
2282
2652
|
out.push([ Opcodes.end ]);
|
2283
2653
|
|
2284
|
-
out.push(...generate(scope, decl.update));
|
2285
|
-
depth.pop();
|
2654
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2286
2655
|
|
2287
2656
|
out.push([ Opcodes.br, 1 ]);
|
2288
2657
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2310,6 +2679,36 @@ const generateWhile = (scope, decl) => {
|
|
2310
2679
|
return out;
|
2311
2680
|
};
|
2312
2681
|
|
2682
|
+
const generateDoWhile = (scope, decl) => {
|
2683
|
+
const out = [];
|
2684
|
+
|
2685
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2686
|
+
depth.push('dowhile');
|
2687
|
+
|
2688
|
+
// block for break (includes all)
|
2689
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2690
|
+
depth.push('block');
|
2691
|
+
|
2692
|
+
// block for continue
|
2693
|
+
// includes body but not test+loop so we can exit body at anytime
|
2694
|
+
// and still test+loop after
|
2695
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2696
|
+
depth.push('block');
|
2697
|
+
|
2698
|
+
out.push(...generate(scope, decl.body));
|
2699
|
+
|
2700
|
+
out.push([ Opcodes.end ]);
|
2701
|
+
depth.pop();
|
2702
|
+
|
2703
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2704
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2705
|
+
|
2706
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2707
|
+
depth.pop(); depth.pop();
|
2708
|
+
|
2709
|
+
return out;
|
2710
|
+
};
|
2711
|
+
|
2313
2712
|
const generateForOf = (scope, decl) => {
|
2314
2713
|
const out = [];
|
2315
2714
|
|
@@ -2339,8 +2738,17 @@ const generateForOf = (scope, decl) => {
|
|
2339
2738
|
// setup local for left
|
2340
2739
|
generate(scope, decl.left);
|
2341
2740
|
|
2342
|
-
|
2741
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2742
|
+
if (!leftName && decl.left.name) {
|
2743
|
+
leftName = decl.left.name;
|
2744
|
+
|
2745
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2746
|
+
}
|
2747
|
+
|
2748
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2749
|
+
|
2343
2750
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2751
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2344
2752
|
|
2345
2753
|
depth.push('block');
|
2346
2754
|
depth.push('block');
|
@@ -2348,13 +2756,15 @@ const generateForOf = (scope, decl) => {
|
|
2348
2756
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2349
2757
|
// hack: this is naughty and will break things!
|
2350
2758
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2351
|
-
if (pages.
|
2759
|
+
if (pages.hasAnyString) {
|
2760
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2352
2761
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2353
2762
|
rawElements: new Array(1)
|
2354
2763
|
}, isGlobal, leftName, true, 'i16');
|
2355
2764
|
}
|
2356
2765
|
|
2357
2766
|
// set type for local
|
2767
|
+
// todo: optimize away counter and use end pointer
|
2358
2768
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2359
2769
|
[TYPES._array]: [
|
2360
2770
|
...setType(scope, leftName, TYPES.number),
|
@@ -2439,6 +2849,56 @@ const generateForOf = (scope, decl) => {
|
|
2439
2849
|
[ Opcodes.end ],
|
2440
2850
|
[ Opcodes.end ]
|
2441
2851
|
],
|
2852
|
+
[TYPES._bytestring]: [
|
2853
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2854
|
+
|
2855
|
+
[ Opcodes.loop, Blocktype.void ],
|
2856
|
+
|
2857
|
+
// setup new/out array
|
2858
|
+
...newOut,
|
2859
|
+
[ Opcodes.drop ],
|
2860
|
+
|
2861
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2862
|
+
|
2863
|
+
// load current string ind {arg}
|
2864
|
+
[ Opcodes.local_get, pointer ],
|
2865
|
+
[ Opcodes.local_get, counter ],
|
2866
|
+
[ Opcodes.i32_add ],
|
2867
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2868
|
+
|
2869
|
+
// store to new string ind 0
|
2870
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2871
|
+
|
2872
|
+
// return new string (page)
|
2873
|
+
...number(newPointer),
|
2874
|
+
|
2875
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2876
|
+
|
2877
|
+
[ Opcodes.block, Blocktype.void ],
|
2878
|
+
[ Opcodes.block, Blocktype.void ],
|
2879
|
+
...generate(scope, decl.body),
|
2880
|
+
[ Opcodes.end ],
|
2881
|
+
|
2882
|
+
// increment iter pointer
|
2883
|
+
// [ Opcodes.local_get, pointer ],
|
2884
|
+
// ...number(1, Valtype.i32),
|
2885
|
+
// [ Opcodes.i32_add ],
|
2886
|
+
// [ Opcodes.local_set, pointer ],
|
2887
|
+
|
2888
|
+
// increment counter by 1
|
2889
|
+
[ Opcodes.local_get, counter ],
|
2890
|
+
...number(1, Valtype.i32),
|
2891
|
+
[ Opcodes.i32_add ],
|
2892
|
+
[ Opcodes.local_tee, counter ],
|
2893
|
+
|
2894
|
+
// loop if counter != length
|
2895
|
+
[ Opcodes.local_get, length ],
|
2896
|
+
[ Opcodes.i32_ne ],
|
2897
|
+
[ Opcodes.br_if, 1 ],
|
2898
|
+
|
2899
|
+
[ Opcodes.end ],
|
2900
|
+
[ Opcodes.end ]
|
2901
|
+
],
|
2442
2902
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2443
2903
|
}, Blocktype.void));
|
2444
2904
|
|
@@ -2449,28 +2909,65 @@ const generateForOf = (scope, decl) => {
|
|
2449
2909
|
return out;
|
2450
2910
|
};
|
2451
2911
|
|
2912
|
+
// find the nearest loop in depth map by type
|
2452
2913
|
const getNearestLoop = () => {
|
2453
2914
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2454
|
-
if (
|
2915
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2455
2916
|
}
|
2456
2917
|
|
2457
2918
|
return -1;
|
2458
2919
|
};
|
2459
2920
|
|
2460
2921
|
const generateBreak = (scope, decl) => {
|
2461
|
-
const
|
2922
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2923
|
+
const type = depth[target];
|
2924
|
+
|
2925
|
+
// different loop types have different branch offsets
|
2926
|
+
// as they have different wasm block/loop/if structures
|
2927
|
+
// we need to use the right offset by type to branch to the one we want
|
2928
|
+
// for a break: exit the loop without executing anything else inside it
|
2929
|
+
const offset = ({
|
2930
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2931
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2932
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2933
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2934
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2935
|
+
})[type];
|
2936
|
+
|
2462
2937
|
return [
|
2463
|
-
[ Opcodes.br, ...signedLEB128(
|
2938
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2464
2939
|
];
|
2465
2940
|
};
|
2466
2941
|
|
2467
2942
|
const generateContinue = (scope, decl) => {
|
2468
|
-
const
|
2943
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2944
|
+
const type = depth[target];
|
2945
|
+
|
2946
|
+
// different loop types have different branch offsets
|
2947
|
+
// as they have different wasm block/loop/if structures
|
2948
|
+
// we need to use the right offset by type to branch to the one we want
|
2949
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2950
|
+
const offset = ({
|
2951
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2952
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2953
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2954
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2955
|
+
})[type];
|
2956
|
+
|
2469
2957
|
return [
|
2470
|
-
[ Opcodes.br, ...signedLEB128(
|
2958
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2471
2959
|
];
|
2472
2960
|
};
|
2473
2961
|
|
2962
|
+
const generateLabel = (scope, decl) => {
|
2963
|
+
scope.labels ??= new Map();
|
2964
|
+
|
2965
|
+
const name = decl.label.name;
|
2966
|
+
scope.labels.set(name, depth.length);
|
2967
|
+
|
2968
|
+
return generate(scope, decl.body);
|
2969
|
+
};
|
2970
|
+
|
2474
2971
|
const generateThrow = (scope, decl) => {
|
2475
2972
|
scope.throws = true;
|
2476
2973
|
|
@@ -2479,7 +2976,7 @@ const generateThrow = (scope, decl) => {
|
|
2479
2976
|
// hack: throw new X("...") -> throw "..."
|
2480
2977
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2481
2978
|
constructor = decl.argument.callee.name;
|
2482
|
-
message = decl.argument.arguments[0]
|
2979
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2483
2980
|
}
|
2484
2981
|
|
2485
2982
|
if (tags.length === 0) tags.push({
|
@@ -2491,6 +2988,9 @@ const generateThrow = (scope, decl) => {
|
|
2491
2988
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2492
2989
|
let tagIdx = tags[0].idx;
|
2493
2990
|
|
2991
|
+
scope.exceptions ??= [];
|
2992
|
+
scope.exceptions.push(exceptId);
|
2993
|
+
|
2494
2994
|
// todo: write a description of how this works lol
|
2495
2995
|
|
2496
2996
|
return [
|
@@ -2500,7 +3000,7 @@ const generateThrow = (scope, decl) => {
|
|
2500
3000
|
};
|
2501
3001
|
|
2502
3002
|
const generateTry = (scope, decl) => {
|
2503
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3003
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2504
3004
|
|
2505
3005
|
const out = [];
|
2506
3006
|
|
@@ -2531,29 +3031,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2531
3031
|
// TODO
|
2532
3032
|
// if identifier declared, use that
|
2533
3033
|
// else, use default (right)
|
2534
|
-
return todo('assignment pattern (optional arg)');
|
3034
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2535
3035
|
};
|
2536
3036
|
|
2537
3037
|
let pages = new Map();
|
2538
|
-
const allocPage = (reason, type) => {
|
3038
|
+
const allocPage = (scope, reason, type) => {
|
2539
3039
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2540
3040
|
|
2541
3041
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2542
3042
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3043
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3044
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2543
3045
|
|
2544
3046
|
const ind = pages.size;
|
2545
3047
|
pages.set(reason, { ind, type });
|
2546
3048
|
|
2547
|
-
|
3049
|
+
scope.pages ??= new Map();
|
3050
|
+
scope.pages.set(reason, { ind, type });
|
3051
|
+
|
3052
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2548
3053
|
|
2549
3054
|
return ind;
|
2550
3055
|
};
|
2551
3056
|
|
3057
|
+
// todo: add scope.pages
|
2552
3058
|
const freePage = reason => {
|
2553
3059
|
const { ind } = pages.get(reason);
|
2554
3060
|
pages.delete(reason);
|
2555
3061
|
|
2556
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3062
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2557
3063
|
|
2558
3064
|
return ind;
|
2559
3065
|
};
|
@@ -2573,38 +3079,53 @@ const StoreOps = {
|
|
2573
3079
|
f64: Opcodes.f64_store,
|
2574
3080
|
|
2575
3081
|
// expects i32 input!
|
2576
|
-
|
3082
|
+
i8: Opcodes.i32_store8,
|
3083
|
+
i16: Opcodes.i32_store16,
|
2577
3084
|
};
|
2578
3085
|
|
2579
3086
|
let data = [];
|
2580
3087
|
|
2581
|
-
const compileBytes = (val, itemType
|
3088
|
+
const compileBytes = (val, itemType) => {
|
2582
3089
|
// todo: this is a mess and needs confirming / ????
|
2583
3090
|
switch (itemType) {
|
2584
3091
|
case 'i8': return [ val % 256 ];
|
2585
|
-
case 'i16': return [ val % 256,
|
2586
|
-
|
2587
|
-
case 'i32':
|
2588
|
-
|
2589
|
-
return enforceFourBytes(signedLEB128(val));
|
3092
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3093
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3094
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3095
|
+
// todo: i64
|
2590
3096
|
|
2591
3097
|
case 'f64': return ieee754_binary64(val);
|
2592
3098
|
}
|
2593
3099
|
};
|
2594
3100
|
|
3101
|
+
const getAllocType = itemType => {
|
3102
|
+
switch (itemType) {
|
3103
|
+
case 'i8': return 'bytestring';
|
3104
|
+
case 'i16': return 'string';
|
3105
|
+
|
3106
|
+
default: return 'array';
|
3107
|
+
}
|
3108
|
+
};
|
3109
|
+
|
2595
3110
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2596
3111
|
const out = [];
|
2597
3112
|
|
3113
|
+
scope.arrays ??= new Map();
|
3114
|
+
|
2598
3115
|
let firstAssign = false;
|
2599
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3116
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2600
3117
|
firstAssign = true;
|
2601
3118
|
|
2602
3119
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2603
3120
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2604
|
-
|
3121
|
+
|
3122
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3123
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2605
3124
|
}
|
2606
3125
|
|
2607
|
-
const pointer = arrays.get(name);
|
3126
|
+
const pointer = scope.arrays.get(name);
|
3127
|
+
|
3128
|
+
const local = global ? globals[name] : scope.locals[name];
|
2608
3129
|
|
2609
3130
|
const useRawElements = !!decl.rawElements;
|
2610
3131
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2612,19 +3133,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2612
3133
|
const valtype = itemTypeToValtype[itemType];
|
2613
3134
|
const length = elements.length;
|
2614
3135
|
|
2615
|
-
if (firstAssign && useRawElements) {
|
2616
|
-
|
3136
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3137
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3138
|
+
if (length !== 0) {
|
3139
|
+
let bytes = compileBytes(length, 'i32');
|
2617
3140
|
|
2618
|
-
|
2619
|
-
|
3141
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3142
|
+
if (elements[i] == null) continue;
|
2620
3143
|
|
2621
|
-
|
2622
|
-
|
3144
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3145
|
+
}
|
2623
3146
|
|
2624
|
-
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
3147
|
+
const ind = data.push({
|
3148
|
+
offset: pointer,
|
3149
|
+
bytes
|
3150
|
+
}) - 1;
|
3151
|
+
|
3152
|
+
scope.data ??= [];
|
3153
|
+
scope.data.push(ind);
|
3154
|
+
}
|
2628
3155
|
|
2629
3156
|
// local value as pointer
|
2630
3157
|
out.push(...number(pointer));
|
@@ -2632,11 +3159,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2632
3159
|
return [ out, pointer ];
|
2633
3160
|
}
|
2634
3161
|
|
3162
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3163
|
+
if (pointerTmp != null) {
|
3164
|
+
out.push(
|
3165
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3166
|
+
Opcodes.i32_to_u,
|
3167
|
+
[ Opcodes.local_set, pointerTmp ]
|
3168
|
+
);
|
3169
|
+
}
|
3170
|
+
|
3171
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3172
|
+
|
2635
3173
|
// store length as 0th array
|
2636
3174
|
out.push(
|
2637
|
-
...
|
3175
|
+
...pointerWasm,
|
2638
3176
|
...number(length, Valtype.i32),
|
2639
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3177
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2640
3178
|
);
|
2641
3179
|
|
2642
3180
|
const storeOp = StoreOps[itemType];
|
@@ -2645,43 +3183,68 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2645
3183
|
if (elements[i] == null) continue;
|
2646
3184
|
|
2647
3185
|
out.push(
|
2648
|
-
...
|
3186
|
+
...pointerWasm,
|
2649
3187
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2650
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3188
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2651
3189
|
);
|
2652
3190
|
}
|
2653
3191
|
|
2654
3192
|
// local value as pointer
|
2655
|
-
out.push(...
|
3193
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2656
3194
|
|
2657
3195
|
return [ out, pointer ];
|
2658
3196
|
};
|
2659
3197
|
|
2660
|
-
const
|
3198
|
+
const byteStringable = str => {
|
3199
|
+
if (!Prefs.bytestring) return false;
|
3200
|
+
|
3201
|
+
for (let i = 0; i < str.length; i++) {
|
3202
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3203
|
+
}
|
3204
|
+
|
3205
|
+
return true;
|
3206
|
+
};
|
3207
|
+
|
3208
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2661
3209
|
const rawElements = new Array(str.length);
|
3210
|
+
let byteStringable = Prefs.bytestring;
|
2662
3211
|
for (let i = 0; i < str.length; i++) {
|
2663
|
-
|
3212
|
+
const c = str.charCodeAt(i);
|
3213
|
+
rawElements[i] = c;
|
3214
|
+
|
3215
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2664
3216
|
}
|
2665
3217
|
|
3218
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3219
|
+
|
2666
3220
|
return makeArray(scope, {
|
2667
3221
|
rawElements
|
2668
|
-
}, global, name, false, 'i16')[0];
|
3222
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2669
3223
|
};
|
2670
3224
|
|
2671
|
-
let arrays = new Map();
|
2672
3225
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2673
3226
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2674
3227
|
};
|
2675
3228
|
|
2676
3229
|
export const generateMember = (scope, decl, _global, _name) => {
|
2677
3230
|
const name = decl.object.name;
|
2678
|
-
const pointer = arrays
|
3231
|
+
const pointer = scope.arrays?.get(name);
|
2679
3232
|
|
2680
|
-
const aotPointer = pointer != null;
|
3233
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2681
3234
|
|
2682
3235
|
// hack: .length
|
2683
3236
|
if (decl.property.name === 'length') {
|
2684
|
-
|
3237
|
+
const func = funcs.find(x => x.name === name);
|
3238
|
+
if (func) {
|
3239
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3240
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3241
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3242
|
+
}
|
3243
|
+
|
3244
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3245
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3246
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3247
|
+
|
2685
3248
|
return [
|
2686
3249
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2687
3250
|
...generate(scope, decl.object),
|
@@ -2693,10 +3256,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2693
3256
|
];
|
2694
3257
|
}
|
2695
3258
|
|
3259
|
+
const object = generate(scope, decl.object);
|
3260
|
+
const property = generate(scope, decl.property);
|
3261
|
+
|
2696
3262
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2697
3263
|
// hack: this is naughty and will break things!
|
2698
3264
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2699
|
-
if (pages.
|
3265
|
+
if (pages.hasAnyString) {
|
2700
3266
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2701
3267
|
rawElements: new Array(1)
|
2702
3268
|
}, _global, _name, true, 'i16');
|
@@ -2705,7 +3271,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2705
3271
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2706
3272
|
[TYPES._array]: [
|
2707
3273
|
// get index as valtype
|
2708
|
-
...
|
3274
|
+
...property,
|
2709
3275
|
|
2710
3276
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2711
3277
|
Opcodes.i32_to_u,
|
@@ -2713,7 +3279,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2713
3279
|
[ Opcodes.i32_mul ],
|
2714
3280
|
|
2715
3281
|
...(aotPointer ? [] : [
|
2716
|
-
...
|
3282
|
+
...object,
|
2717
3283
|
Opcodes.i32_to_u,
|
2718
3284
|
[ Opcodes.i32_add ]
|
2719
3285
|
]),
|
@@ -2722,7 +3288,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
3288
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2723
3289
|
|
2724
3290
|
...number(TYPES.number, Valtype.i32),
|
2725
|
-
setLastType(scope)
|
3291
|
+
...setLastType(scope)
|
2726
3292
|
],
|
2727
3293
|
|
2728
3294
|
[TYPES.string]: [
|
@@ -2732,14 +3298,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2732
3298
|
|
2733
3299
|
...number(0, Valtype.i32), // base 0 for store later
|
2734
3300
|
|
2735
|
-
...
|
2736
|
-
|
3301
|
+
...property,
|
2737
3302
|
Opcodes.i32_to_u,
|
3303
|
+
|
2738
3304
|
...number(ValtypeSize.i16, Valtype.i32),
|
2739
3305
|
[ Opcodes.i32_mul ],
|
2740
3306
|
|
2741
3307
|
...(aotPointer ? [] : [
|
2742
|
-
...
|
3308
|
+
...object,
|
2743
3309
|
Opcodes.i32_to_u,
|
2744
3310
|
[ Opcodes.i32_add ]
|
2745
3311
|
]),
|
@@ -2754,10 +3320,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2754
3320
|
...number(newPointer),
|
2755
3321
|
|
2756
3322
|
...number(TYPES.string, Valtype.i32),
|
2757
|
-
setLastType(scope)
|
3323
|
+
...setLastType(scope)
|
2758
3324
|
],
|
3325
|
+
[TYPES._bytestring]: [
|
3326
|
+
// setup new/out array
|
3327
|
+
...newOut,
|
3328
|
+
[ Opcodes.drop ],
|
3329
|
+
|
3330
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3331
|
+
|
3332
|
+
...property,
|
3333
|
+
Opcodes.i32_to_u,
|
3334
|
+
|
3335
|
+
...(aotPointer ? [] : [
|
3336
|
+
...object,
|
3337
|
+
Opcodes.i32_to_u,
|
3338
|
+
[ Opcodes.i32_add ]
|
3339
|
+
]),
|
3340
|
+
|
3341
|
+
// load current string ind {arg}
|
3342
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2759
3343
|
|
2760
|
-
|
3344
|
+
// store to new string ind 0
|
3345
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3346
|
+
|
3347
|
+
// return new string (page)
|
3348
|
+
...number(newPointer),
|
3349
|
+
|
3350
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3351
|
+
...setLastType(scope)
|
3352
|
+
],
|
3353
|
+
|
3354
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2761
3355
|
});
|
2762
3356
|
};
|
2763
3357
|
|
@@ -2767,25 +3361,36 @@ const objectHack = node => {
|
|
2767
3361
|
if (!node) return node;
|
2768
3362
|
|
2769
3363
|
if (node.type === 'MemberExpression') {
|
2770
|
-
|
3364
|
+
const out = (() => {
|
3365
|
+
if (node.computed || node.optional) return;
|
2771
3366
|
|
2772
|
-
|
3367
|
+
let objectName = node.object.name;
|
2773
3368
|
|
2774
|
-
|
2775
|
-
|
3369
|
+
// if object is not identifier or another member exp, give up
|
3370
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3371
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2776
3372
|
|
2777
|
-
|
3373
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2778
3374
|
|
2779
|
-
|
2780
|
-
|
3375
|
+
// if .length, give up (hack within a hack!)
|
3376
|
+
if (node.property.name === 'length') {
|
3377
|
+
node.object = objectHack(node.object);
|
3378
|
+
return;
|
3379
|
+
}
|
2781
3380
|
|
2782
|
-
|
2783
|
-
|
3381
|
+
// no object name, give up
|
3382
|
+
if (!objectName) return;
|
2784
3383
|
|
2785
|
-
|
2786
|
-
|
2787
|
-
|
2788
|
-
|
3384
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3385
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
3386
|
+
|
3387
|
+
return {
|
3388
|
+
type: 'Identifier',
|
3389
|
+
name
|
3390
|
+
};
|
3391
|
+
})();
|
3392
|
+
|
3393
|
+
if (out) return out;
|
2789
3394
|
}
|
2790
3395
|
|
2791
3396
|
for (const x in node) {
|
@@ -2799,8 +3404,8 @@ const objectHack = node => {
|
|
2799
3404
|
};
|
2800
3405
|
|
2801
3406
|
const generateFunc = (scope, decl) => {
|
2802
|
-
if (decl.async) return todo('async functions are not supported');
|
2803
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3407
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3408
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2804
3409
|
|
2805
3410
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2806
3411
|
const params = decl.params ?? [];
|
@@ -2816,6 +3421,11 @@ const generateFunc = (scope, decl) => {
|
|
2816
3421
|
name
|
2817
3422
|
};
|
2818
3423
|
|
3424
|
+
if (typedInput && decl.returnType) {
|
3425
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3426
|
+
innerScope.returns = [ valtypeBinary ];
|
3427
|
+
}
|
3428
|
+
|
2819
3429
|
for (let i = 0; i < params.length; i++) {
|
2820
3430
|
allocVar(innerScope, params[i].name, false);
|
2821
3431
|
|
@@ -2837,13 +3447,13 @@ const generateFunc = (scope, decl) => {
|
|
2837
3447
|
const func = {
|
2838
3448
|
name,
|
2839
3449
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2840
|
-
|
2841
|
-
|
2842
|
-
throws: innerScope.throws,
|
2843
|
-
index: currentFuncIndex++
|
3450
|
+
index: currentFuncIndex++,
|
3451
|
+
...innerScope
|
2844
3452
|
};
|
2845
3453
|
funcIndex[name] = func.index;
|
2846
3454
|
|
3455
|
+
if (name === 'main') func.gotLastType = true;
|
3456
|
+
|
2847
3457
|
// quick hack fixes
|
2848
3458
|
for (const inst of wasm) {
|
2849
3459
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2895,7 +3505,7 @@ const internalConstrs = {
|
|
2895
3505
|
|
2896
3506
|
// todo: check in wasm instead of here
|
2897
3507
|
const literalValue = arg.value ?? 0;
|
2898
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3508
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2899
3509
|
|
2900
3510
|
return [
|
2901
3511
|
...number(0, Valtype.i32),
|
@@ -2906,7 +3516,8 @@ const internalConstrs = {
|
|
2906
3516
|
...number(pointer)
|
2907
3517
|
];
|
2908
3518
|
},
|
2909
|
-
type: TYPES._array
|
3519
|
+
type: TYPES._array,
|
3520
|
+
length: 1
|
2910
3521
|
},
|
2911
3522
|
|
2912
3523
|
__Array_of: {
|
@@ -2918,7 +3529,131 @@ const internalConstrs = {
|
|
2918
3529
|
}, global, name);
|
2919
3530
|
},
|
2920
3531
|
type: TYPES._array,
|
3532
|
+
notConstr: true,
|
3533
|
+
length: 0
|
3534
|
+
},
|
3535
|
+
|
3536
|
+
__Porffor_fastOr: {
|
3537
|
+
generate: (scope, decl) => {
|
3538
|
+
const out = [];
|
3539
|
+
|
3540
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3541
|
+
out.push(
|
3542
|
+
...generate(scope, decl.arguments[i]),
|
3543
|
+
Opcodes.i32_to_u,
|
3544
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3545
|
+
);
|
3546
|
+
}
|
3547
|
+
|
3548
|
+
out.push(Opcodes.i32_from_u);
|
3549
|
+
|
3550
|
+
return out;
|
3551
|
+
},
|
3552
|
+
type: TYPES.boolean,
|
2921
3553
|
notConstr: true
|
3554
|
+
},
|
3555
|
+
|
3556
|
+
__Porffor_fastAnd: {
|
3557
|
+
generate: (scope, decl) => {
|
3558
|
+
const out = [];
|
3559
|
+
|
3560
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3561
|
+
out.push(
|
3562
|
+
...generate(scope, decl.arguments[i]),
|
3563
|
+
Opcodes.i32_to_u,
|
3564
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3565
|
+
);
|
3566
|
+
}
|
3567
|
+
|
3568
|
+
out.push(Opcodes.i32_from_u);
|
3569
|
+
|
3570
|
+
return out;
|
3571
|
+
},
|
3572
|
+
type: TYPES.boolean,
|
3573
|
+
notConstr: true
|
3574
|
+
},
|
3575
|
+
|
3576
|
+
Boolean: {
|
3577
|
+
generate: (scope, decl) => {
|
3578
|
+
// todo: boolean object when used as constructor
|
3579
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3580
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3581
|
+
},
|
3582
|
+
type: TYPES.boolean,
|
3583
|
+
length: 1
|
3584
|
+
},
|
3585
|
+
|
3586
|
+
__Math_max: {
|
3587
|
+
generate: (scope, decl) => {
|
3588
|
+
const out = [
|
3589
|
+
...number(-Infinity)
|
3590
|
+
];
|
3591
|
+
|
3592
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3593
|
+
out.push(
|
3594
|
+
...generate(scope, decl.arguments[i]),
|
3595
|
+
[ Opcodes.f64_max ]
|
3596
|
+
);
|
3597
|
+
}
|
3598
|
+
|
3599
|
+
return out;
|
3600
|
+
},
|
3601
|
+
type: TYPES.number,
|
3602
|
+
notConstr: true,
|
3603
|
+
length: 2
|
3604
|
+
},
|
3605
|
+
|
3606
|
+
__Math_min: {
|
3607
|
+
generate: (scope, decl) => {
|
3608
|
+
const out = [
|
3609
|
+
...number(Infinity)
|
3610
|
+
];
|
3611
|
+
|
3612
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3613
|
+
out.push(
|
3614
|
+
...generate(scope, decl.arguments[i]),
|
3615
|
+
[ Opcodes.f64_min ]
|
3616
|
+
);
|
3617
|
+
}
|
3618
|
+
|
3619
|
+
return out;
|
3620
|
+
},
|
3621
|
+
type: TYPES.number,
|
3622
|
+
notConstr: true,
|
3623
|
+
length: 2
|
3624
|
+
},
|
3625
|
+
|
3626
|
+
__console_log: {
|
3627
|
+
generate: (scope, decl) => {
|
3628
|
+
const out = [];
|
3629
|
+
|
3630
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3631
|
+
out.push(
|
3632
|
+
...generateCall(scope, {
|
3633
|
+
callee: {
|
3634
|
+
type: 'Identifier',
|
3635
|
+
name: '__Porffor_print'
|
3636
|
+
},
|
3637
|
+
arguments: [ decl.arguments[i] ]
|
3638
|
+
}),
|
3639
|
+
|
3640
|
+
// print space
|
3641
|
+
...number(32),
|
3642
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3643
|
+
);
|
3644
|
+
}
|
3645
|
+
|
3646
|
+
// print newline
|
3647
|
+
out.push(
|
3648
|
+
...number(10),
|
3649
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3650
|
+
);
|
3651
|
+
|
3652
|
+
return out;
|
3653
|
+
},
|
3654
|
+
type: TYPES.undefined,
|
3655
|
+
notConstr: true,
|
3656
|
+
length: 0
|
2922
3657
|
}
|
2923
3658
|
};
|
2924
3659
|
|
@@ -2947,20 +3682,23 @@ export default program => {
|
|
2947
3682
|
funcs = [];
|
2948
3683
|
funcIndex = {};
|
2949
3684
|
depth = [];
|
2950
|
-
arrays = new Map();
|
2951
3685
|
pages = new Map();
|
2952
3686
|
data = [];
|
2953
3687
|
currentFuncIndex = importedFuncs.length;
|
2954
3688
|
|
2955
3689
|
globalThis.valtype = 'f64';
|
2956
3690
|
|
2957
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3691
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2958
3692
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2959
3693
|
|
2960
3694
|
globalThis.valtypeBinary = Valtype[valtype];
|
2961
3695
|
|
2962
3696
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2963
3697
|
|
3698
|
+
globalThis.pageSize = PageSize;
|
3699
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3700
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3701
|
+
|
2964
3702
|
// set generic opcodes for current valtype
|
2965
3703
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2966
3704
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2969,10 +3707,10 @@ export default program => {
|
|
2969
3707
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2970
3708
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2971
3709
|
|
2972
|
-
Opcodes.i32_to = [ [
|
2973
|
-
Opcodes.i32_to_u = [ [
|
2974
|
-
Opcodes.i32_from = [ [
|
2975
|
-
Opcodes.i32_from_u = [ [
|
3710
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3711
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3712
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3713
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2976
3714
|
|
2977
3715
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2978
3716
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2985,10 +3723,6 @@ export default program => {
|
|
2985
3723
|
|
2986
3724
|
program.id = { name: 'main' };
|
2987
3725
|
|
2988
|
-
globalThis.pageSize = PageSize;
|
2989
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2990
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2991
|
-
|
2992
3726
|
const scope = {
|
2993
3727
|
locals: {},
|
2994
3728
|
localInd: 0
|
@@ -2999,7 +3733,7 @@ export default program => {
|
|
2999
3733
|
body: program.body
|
3000
3734
|
};
|
3001
3735
|
|
3002
|
-
if (
|
3736
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3003
3737
|
|
3004
3738
|
generateFunc(scope, program);
|
3005
3739
|
|
@@ -3016,7 +3750,11 @@ export default program => {
|
|
3016
3750
|
}
|
3017
3751
|
|
3018
3752
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3019
|
-
|
3753
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3754
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3755
|
+
} else {
|
3756
|
+
main.returns = [];
|
3757
|
+
}
|
3020
3758
|
}
|
3021
3759
|
|
3022
3760
|
if (lastInst[0] === Opcodes.call) {
|