-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathRegisterAllocator.class.ts
1640 lines (1501 loc) · 61.4 KB
/
RegisterAllocator.class.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Copyright 2023 University of Adelaide
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uniq } from "lodash-es";
import {
AllocationFlags,
ByteRegister,
C_DI_IMM,
C_DI_SPILL_LOCATION,
Flags,
FlagState,
Register,
XmmRegister,
} from "@/enums";
import {
ALL_XMM_REGISTERS,
CALLER_SAVE_PREFIX,
CALLER_SAVE_REGISTERS,
CALLING_CONVENTION_REGISTER_ORDER,
IMM_VAL_PREFIX,
LSB_MAPPING,
SETX,
RED_ZONE_SIZE_IN_ELEMENTS,
TEMP_VARNAME,
} from "@/helper/constants";
import {
delimbify,
isByteRegister,
isCallerSave,
isFlag,
isImm,
isMem,
isRegister,
isU1,
isU64,
isXD,
isXmmRegister,
limbify,
limbifyImm,
matchArg,
matchArgPrefix,
matchMem,
matchXD,
setToString,
toImm,
toMem,
zx,
} from "@/helper/lamdas";
import Logger from "@/helper/Logger.class";
import { getByteRegFromQwReg, getQwRegFromByteReg } from "@/helper/reg-conversion";
import { Model } from "@/model";
import { Paul } from "@/paul";
import type {
Allocation,
AllocationReq,
AllocationRes,
Allocations,
asm,
CryptOpt,
imm,
mem,
OptimizerArgs,
PointerAllocation,
RegisterAllocation,
U1FlagAllocation,
ValueAllocation,
} from "@/types";
import { populateClobbers } from "./RegisterAllocator.helper";
function assertValueAllocation(a: Allocation): asserts a is ValueAllocation | PointerAllocation {
if (a.datatype === "u128") {
throw new Error(
`given allocation has datatype u128., which is not allowed for a ValueAllocation. Assertion failed. ${a}`,
);
}
}
// produce<T extends ProduceConditions, U extends { [key in keyof T]: any }>(conditions: T, input: any): Promise<U> | U
export class RegisterAllocator {
private static _instance: RegisterAllocator | null;
private static _options: OptimizerArgs | null = null;
private _preInstructions: asm[] = [];
private _ALL_REGISTERS: Register[] = [];
private _allocations: Allocations = {};
private _stack: { size: number; name: string }[] = [];
private _clobbers = new Set<string>();
private _flagState: {
[f in Flags]: FlagState;
} = {
[Flags.CF]: FlagState.KILLED,
[Flags.OF]: FlagState.KILLED,
};
private get availableRedzoneSize(): number {
return RegisterAllocator._options?.redzone ? RED_ZONE_SIZE_IN_ELEMENTS : 0;
}
public static getInstance(): RegisterAllocator {
if (RegisterAllocator._instance) {
return RegisterAllocator._instance;
}
return new RegisterAllocator();
}
public static reset(): RegisterAllocator {
Logger.log("getting RA instance");
const ra = RegisterAllocator.getInstance();
ra._preInstructions = [];
ra._stack = [];
ra._allocations = {};
ra._flagState = {
[Flags.CF]: FlagState.KILLED,
[Flags.OF]: FlagState.KILLED,
};
const _CALLER_SAVE_REGISTERS =
// pretend that rbp does not exist, and handle special in pre/post instruction in finalize().
this._options?.framePointer !== "omit"
? CALLER_SAVE_REGISTERS.filter((r) => r !== Register.rbp)
: CALLER_SAVE_REGISTERS;
ra._ALL_REGISTERS = [Register.rax, Register.r10, Register.r11]
.concat(...CALLING_CONVENTION_REGISTER_ORDER)
.concat(..._CALLER_SAVE_REGISTERS);
const _CALLING_CONVENTION_REGISTER_ORDER = [
...CALLING_CONVENTION_REGISTER_ORDER, // working copy
];
// this loop initializes the 'allocations'-member field to the arguments which have been passed to the method.
// Other Registers are considered `empty` and will be overwritten (callersave are pushed / popped to/from stack)
Logger.log("setting up args in calling convention to registers");
Model.methodParametes.map(({ name, datatype }) => {
const reg = _CALLING_CONVENTION_REGISTER_ORDER.shift();
if (!reg) {
throw new Error(
"Unsupported. there are more registers occupied then specified in the calling convention. Reading Arguments from the stack is not yet implements.",
);
}
ra._allocations[name] = {
datatype,
store: reg,
};
ra._preInstructions.push(`; ${reg} contains ${name}`);
});
Logger.log("setting up callersaves");
_CALLER_SAVE_REGISTERS.forEach((register) => {
ra._allocations[`${CALLER_SAVE_PREFIX}${register}`] = { datatype: "u64", store: register };
});
// treat remaining registes are free registers
ra._ALL_REGISTERS.push(..._CALLING_CONVENTION_REGISTER_ORDER);
return ra;
}
public static set options(op: OptimizerArgs) {
RegisterAllocator._options = op;
}
private get canXmm(): boolean {
if (RegisterAllocator._options?.xmm) {
return RegisterAllocator._options.xmm == true;
}
return false;
}
private constructor() {
RegisterAllocator._instance = this;
RegisterAllocator.reset();
}
/*
* Will return a register declared to the @param variableName or false, if no FR was available
*/
public getFreeRegister(variableName: string): Register | false {
this.addToPreInstructions(Logger.log(this.allocationString()) ?? "");
const allocatedRegs = this.valuesAllocations
.map(({ store }) => store)
.filter((r) => isRegister(r) || isByteRegister(r)) as Array<ByteRegister | Register>;
const frs = this._ALL_REGISTERS.filter(
(r) => !(allocatedRegs.includes(r) || allocatedRegs.includes(getByteRegFromQwReg(r))),
);
if (frs.length == 0) {
this.addToPreInstructions(`; fr: none.`);
return false;
}
this.addToPreInstructions(`; fr: ${frs}`);
const fr = frs[0];
this._allocations[variableName] = { datatype: "u64", store: fr };
return fr;
}
/*
* will not persist to _this._allocatons
*/
private getFreeXmmRegister(): XmmRegister | false {
this.addToPreInstructions(Logger.log(this.allocationString()) ?? "");
const allocatedXmms = this.valuesAllocations
.map(({ store }) => store)
.filter((r) => isXmmRegister(r)) as XmmRegister[];
const freeXmms = ALL_XMM_REGISTERS.filter((r) => !allocatedXmms.includes(r));
if (freeXmms.length == 0) {
this.addToPreInstructions(`; free xmm's: none`);
return false;
}
this.addToPreInstructions(`; free xmm's: ${freeXmms}`);
return freeXmms[0];
}
private get valuesAllocations(): ValueAllocation[] {
return Object.values(this._allocations).filter((a) => "store" in a) as ValueAllocation[];
}
// Entries of [x5, ValueAllocation]
private get entriesAllocations(): [string, ValueAllocation][] {
return Object.entries(this._allocations).filter(([, a]) => "store" in a) as [string, ValueAllocation][];
}
/* will issue the instruction to preinst
* will update this._allocations
*/
public static xmm2reg({ store }: Pick<ValueAllocation, "store">): RegisterAllocation {
// spareVariableName: string;
// targetReg: Register;
// spillingXmmReg: XmmRegister;
// }): void {
return RegisterAllocator.getInstance().moveXmmToReg({ store });
}
private moveXmmToReg({ store }: Pick<ValueAllocation, "store">): RegisterAllocation {
const varname = this.getVarnameFromStore({ store });
const dest = this.getW(varname);
this.addToPreInstructions(`movq ${dest}, ${store}; un-xmm-ify ${varname} `);
return this._allocations[varname] as RegisterAllocation;
}
/*
* checks it byte / qword
* will issue the instruction to preinst
* will update this._allocations
* */
private movRegToMem({
spareVariableName,
targetMem,
spillingReg,
}: {
spareVariableName: string;
targetMem: mem;
spillingReg: Register | ByteRegister;
}): void {
// TODO: optimize here, if spilling reg was u1, use movzx instead to avoid possible movzx later on when this val is read.
if (isU1(this._allocations[spareVariableName])) {
this.addToPreInstructions(
`mov byte ${targetMem}, ${spillingReg}; spilling byte ${spareVariableName} to mem`,
);
} else {
this.addToPreInstructions(`mov ${targetMem}, ${spillingReg}; spilling ${spareVariableName} to mem`);
}
// update the state
this._allocations[spareVariableName].store = targetMem;
}
public getW(name: string): Register | ByteRegister {
const fr = this.getFreeRegister(name);
if (fr) {
return fr;
}
const allocationEntries = this.entriesAllocations; // working copy
// need to reuse other registers.
// try to find some var, which has no deps anymore
let spareVariableName = allocationEntries.find(
([varname, { store }]) =>
(isRegister(store) || isByteRegister(store)) &&
matchXD(varname) &&
!Model.hasDependants(varname) &&
!this._clobbers.has(varname),
)?.[0];
if (spareVariableName) {
const valueacc = this._allocations[spareVariableName] as ValueAllocation;
this.addToPreInstructions(
`; freeing ${spareVariableName} (${valueacc.store}) no dependants anymore`,
// `If you dont trust me: here: clobbers: ${ this.clobbers }; allocs: ${JSON.stringify(this.allocations)}`
);
} else {
spareVariableName = Object.keys(this._allocations).find(
(varname) =>
varname.startsWith(IMM_VAL_PREFIX) ||
(["0x0", "0x1"].includes(varname) && !this._clobbers.has(varname)),
);
if (spareVariableName) {
const valuealloc = this._allocations[spareVariableName] as ValueAllocation;
this.addToPreInstructions(
`; freeing ${spareVariableName} (${valuealloc.store}, since all are neeed, but this one is just an immediate value.`,
// `.. so im fine with sparing it. If you dont trust me(rly?): here: clobbers: ${ this.clobbers }; and allocs:${JSON.stringify(this.allocations)}`
);
}
}
let checkToSpill = false;
if (!spareVariableName) {
// there is no useless var right now ->
// fallback to choose one which is not being used atm and spill them to mem if necessary down below
// string[] of argN/xNN's which can potentially be spilled
const allocs = allocationEntries
// only spill regs. Does not make sense to spill mem to mem.
.filter(([, { store }]) => isRegister(store) || isByteRegister(store))
.map(([name]) => name);
const clobs = new Set<string>();
this._clobbers.forEach((clob) => {
clobs.add(clob);
const match = matchArg(clob);
if (match) clobs.add(match[1]);
});
// try to find some arg1[n] first, i.e. 'not-xNN'
const candidatesInArguments = allocs.filter(
(varname) => !clobs.has(varname) && !(matchXD(varname) || matchArgPrefix(varname)),
);
if (candidatesInArguments.length > 0) {
spareVariableName = Model.chooseSpillValue(candidatesInArguments);
} else {
// choose one which is just right now not being used.
const candidates = allocs.filter((varname) => !clobs.has(varname));
if (candidates.length == 0) {
throw new Error("wow... no candidates which are not clobbed.");
}
spareVariableName = Model.chooseSpillValue(candidates);
}
this.addToPreInstructions(
Logger.log(
[
`; freeing, i.e. spilling ${spareVariableName}, because I am out of ideas`,
`; allocs: ${allocs.map((a) => `${a}(${this._allocations[a].store})`).join(" ,")}`,
`; clobs ${setToString(clobs)}; will spare: ${spareVariableName} `,
].join("\n"),
) ?? "",
);
checkToSpill = true;
}
const valuealloc = this._allocations[spareVariableName] as ValueAllocation;
// find according register
let spilling_reg = valuealloc.store as Register | ByteRegister;
if (
checkToSpill &&
(matchXD(spareVariableName) || isCallerSave(spareVariableName) || matchArgPrefix(spareVariableName))
) {
const freeXmm = RegisterAllocator._options?.xmm && this.getFreeXmmRegister();
// we cant always spill to xmms
let choice = C_DI_SPILL_LOCATION.C_DI_MEM; // fallback
// if we can and have a free xmm
if (this.canXmm && freeXmm) {
// consider XMMs.
// we always want xmms if we prefer
if (RegisterAllocator._options?.preferXmm) {
choice = C_DI_SPILL_LOCATION.C_DI_XMM_REG;
this.addToPreInstructions(`; spilling ${spareVariableName} to xmm because we prefer that`);
} else if (matchXD(spareVariableName)) {
// if we dont prefer, we want to ask Paul, but then we need to be xdd (cuz they are 'nodes', where we can save the decision to)
choice = Paul.chooseSpillLocation(Model.operationByName(spareVariableName));
this.addToPreInstructions(`; spilling of ${spareVariableName} is decided by Paul`);
}
}
if (choice == C_DI_SPILL_LOCATION.C_DI_MEM) {
// if its worth to save, save it to mem.
const { isNew, targetMem } = this._varToMemStr(spareVariableName);
if (isNew) {
this.movRegToMem({ targetMem, spareVariableName, spillingReg: spilling_reg });
} else {
// only need to update the state, such that subsequent reads read from memory
this._allocations[spareVariableName].store = targetMem;
}
} else {
// spill to xmm
// in case we rant to spill dl to xmm4
if (isByteRegister(spilling_reg)) {
// we first need to spill movzx rdx, dl, and then movq xmm4, rdx
const { inst, reg } = zx(spilling_reg);
this.addToPreInstructions(inst);
spilling_reg = reg;
}
this.addToPreInstructions(`movq ${freeXmm}, ${spilling_reg}; spilling ${spareVariableName} to xmm`);
this._allocations[spareVariableName].store = freeXmm as XmmRegister;
}
} else {
// no need to spill (either unused or memory)
delete this._allocations[spareVariableName];
}
if (isByteRegister(spilling_reg)) {
spilling_reg = getQwRegFromByteReg(spilling_reg);
}
this._clobbers.add(name);
this._allocations[name] = {
datatype: "u64",
store: spilling_reg,
};
return spilling_reg as Register;
}
public loadImmToReg64(readVariable: string): Register {
// TODO: Should this function check if @param readVariable is
// already in clobbers and if so, dont return currentLocation.sore?
this._clobbers.add(readVariable);
let currentLocation = this._allocations[readVariable];
if (!currentLocation) {
if (!isImm(readVariable)) {
console.debug(this._allocations);
console.debug(this._currentInst);
throw new Error(
`tried to read ${readVariable} from allocations and it is not an immediate, but could not be found.`,
);
}
const imm = toImm(readVariable);
const r = this.getW(readVariable) as Register;
this._preInstructions.push(`mov ${r}, ${imm} ; moving imm to reg`);
return r;
}
if (currentLocation.datatype === "u128") {
throw new Error(`immediate '${readVariable}' is in u128. Not supported athm.`);
}
currentLocation = currentLocation as ValueAllocation;
if (isMem(currentLocation.store)) {
return this.loadMemoryToReg(currentLocation.store, readVariable, "movzx") as Register;
}
return currentLocation.store;
}
public allocate(allocationReq: AllocationReq): AllocationRes {
populateClobbers(this._clobbers, allocationReq);
if (uniq(allocationReq.in).length != allocationReq.in.length) {
throw new Error("not supported to in-the same value ");
}
// caf is short for "check Allocation Flags" and is a convenience method, to check wether a particular Allocation Flag is set.
const caf = (flagToCheck: AllocationFlags): boolean =>
((allocationReq.allocationFlags ?? AllocationFlags.NONE) & flagToCheck) === flagToCheck;
const inAllocationsTemp = allocationReq.in.map((readVariable) => {
const argMatchRes = matchArg(readVariable);
if (argMatchRes) {
// if we read from an argument such as arg1[3], we actually want to find arg1 in the allocations.
const baseVar = argMatchRes.groups && argMatchRes.groups.base;
if (baseVar) {
// should never be false though, since it matched above
readVariable = baseVar;
} else {
throw new Error(`${readVariable} matched ~arg, but has no baseVar? wtf. Giving up.`);
}
}
const currentLocation = this._allocations[readVariable];
if (!currentLocation) {
// if is it not already allocated, it must be an immval, cause 'arg1' is always somewhere
if (caf(AllocationFlags.DISALLOW_IMM)) {
return this.loadImmToReg64(readVariable);
}
return readVariable as imm;
}
if (currentLocation.datatype === "u128") {
throw new Error(`U128 is not supported, when reading ${readVariable}`);
}
assertValueAllocation(currentLocation);
const store = currentLocation.store;
const disMem = caf(AllocationFlags.DISALLOW_MEM);
if (isFlag(store)) {
// this makes sure, that even if the current flag has no _OTHER_ deps, it will be spilled.
Model.hardDependencies.add(readVariable);
const bytereg = this._spillFlag(readVariable);
Model.hardDependencies.delete(readVariable);
if (bytereg === false) {
throw new Error(`Cannot read a flag which is not alive`);
} else {
return bytereg;
}
}
// This little lambda takes a base register which has the address of arg1
// Then it checks whether it is allowed to just reutrn [breg + 0x8 * offset]
// or if that needs to be loaded to register as well.
const handleArgReturnValue = (baseRegister: Register): mem | Register => {
if (!argMatchRes?.groups?.offset) {
throw new Error(`cannot calculate memory offset of argument.`);
}
const offset = argMatchRes.groups.offset;
// could be smth. like [ rax + 0x24 ]
const memory = toMem(offset, baseRegister);
if (!disMem) {
// if we can return memory, do it
return memory;
}
// left with the arg1[2] must be in register.
return this.loadMemoryToReg(
memory,
`${readVariable}[${offset}]`, // could also have saved them earlier
);
};
if (isMem(store)) {
// if we allow memory and we are not requesting a arg1[2], just return the memory
if (!disMem && !argMatchRes) {
return store;
}
// in any other case we need the readVar in a register.
const reg = this.loadVarToReg(readVariable, "movzx");
if (!isRegister(reg)) {
throw new Error("TSNH. arg/out should always be u64, esp. after movzx'ing it");
}
// if we have no arg, we can just return the reg.
if (disMem && !argMatchRes) {
return reg;
}
// so we are left with args.
return handleArgReturnValue(reg);
}
// only other possiblity is, that the store is a register.
if (!argMatchRes) {
// and we can return it, if we are not requesting an argument.
return store;
} else {
return handleArgReturnValue(store);
}
});
// move to reg if necessary
if (caf(AllocationFlags.DISALLOW_ALL_MEM) && inAllocationsTemp.every((a) => isMem(a))) {
const r = this.moveOneMemoryToRegister(
inAllocationsTemp.map((store) => ({ store })),
"movzx",
).map(({ store }) => store);
// now we need to check, which one has been moved to reg
// so we want to find the index in inAllocationsTemp, which is not in the result, i.e. has been moved to reg
const i = inAllocationsTemp.findIndex((s) => !r.includes(s));
// and now we can splice that into the inAllocationsTemp.
inAllocationsTemp.splice(i, 1, r[0]);
}
// If we have any xmm's, and we shouldn't have,
if (caf(AllocationFlags.DISALLOW_XMM) && inAllocationsTemp.some((a) => isXmmRegister(a))) {
// we go though the current allocations
inAllocationsTemp.forEach((store, i, arr) => {
// and for each xmm reg
if (isXmmRegister(store)) {
// we move it to a reg
const newReg = this.moveXmmToReg({ store });
// and splice that info into the register
arr.splice(i, 1, newReg.store);
}
});
}
// now we want to make sure, that, if we need, we read all the same sizes.
const inAllocations = caf(AllocationFlags.SAME_SIZE_READ)
? this._unifySizes(inAllocationsTemp)
: inAllocationsTemp;
const localFlags = this._flags;
if (caf(AllocationFlags.SAVE_FLAG_CF)) {
this.spillFlag(Flags.CF, localFlags.CF);
if ([TEMP_VARNAME, "_"].includes(localFlags.CF)) {
console.warn(`well, we may not have needed to save CF${localFlags.CF}`);
}
}
if (caf(AllocationFlags.SAVE_FLAG_OF)) {
this.spillFlag(Flags.OF, localFlags.OF);
if ([TEMP_VARNAME, "_"].includes(localFlags.OF)) {
console.warn(`well, we may not have needed to save OF${localFlags.OF}`);
}
}
if (caf(AllocationFlags.ONE_IN_MUST_BE_IN_RDX) && !inAllocations.includes(Register.rdx)) {
// since in inAllocations there is no rdx (otherwise we wont be in this branch)
// we need to move one of inAllocations to rdx
// suppose inAllocations is ["[rsi + 0x08 * 4]", "rax"]
// suppose allocation.in is ["arg1[4]" , "x12"]
// that'd mean, that
// _allocations["x12"].store is "rax"
// _allocations["arg1[4]"].store is "[rsi + 0x08 * 4]"
// we want now change any of those inAllocations with rdx.
// Paul chooses an element, which we'll move to rdx.
const element = Paul.chooseArg(allocationReq.in);
const idx = allocationReq.in.indexOf(element);
//TODO: refactor that a lil bit
// element is x12|arg1[4]
// idx is 0|1
// start with the return value to be correct.
const oldAllocatedStore = inAllocations.splice(idx, 1, Register.rdx)[0];
// oldAllocatedStore is rax| [rsi + 0x8 * 4]
if (isFlag(oldAllocatedStore)) {
throw new Error("unsupported to put a flag into rdx");
}
// get varname of value in rdx\
let isByte = false;
let dVarname: string | false = false;
const rdxSpillVarname = this.getVarnameFromStore({ store: Register.rdx }, false);
const dlSpillVarname = this.getVarnameFromStore({ store: ByteRegister.dl }, false);
// we want to move element ('x11') from oldAllocatedStore ("rax") to rdx,
// but in rdx, currently there is {rdx,dl}SpillVarname ('x12')
if (rdxSpillVarname === "" && dlSpillVarname !== "") {
// if there is nothing in rdx, but there is something in dl
isByte = true;
dVarname = dlSpillVarname;
} else if (rdxSpillVarname !== "" && dlSpillVarname === "") {
// if there is nothing in rdx, but there is something in dl
isByte = false;
dVarname = rdxSpillVarname;
} else if (rdxSpillVarname !== "" && dlSpillVarname !== "") {
throw new Error(
`there is something in dl:${dlSpillVarname} and in rdx: ${rdxSpillVarname}. TSNH. Abort`,
);
} // else, actually rdx is empty
// next is to fix the _allocations obj.
// NOTE: we need to create a new obj here, since element could be "arg1[0]",
// which itself may not be allocated in a register at this point, thus not in this._allocations
this._allocations[element] = {
datatype: "u64",
store: Register.rdx,
};
let movInst = "mov";
if (isXmmRegister(oldAllocatedStore)) movInst = "movq";
if (isByteRegister(oldAllocatedStore)) movInst = "movzx"; // not using zx() here, because we may want to movzx rdx, r9b
// now check if rdx can be discarded:
// which it can,
if (
dVarname === false || // if it has been free
(!isByte && matchArg(rdxSpillVarname)) || // of if it is not a byte and matches an arg: arg1[\d]|out1[\d]
isImm(dVarname) || // its an Immediate
!Model.hasDependants(dVarname) // or if it just has no dependencies
) {
this._preInstructions.push(`${movInst} ${Register.rdx}, ${oldAllocatedStore}; ${element} to rdx`);
// beacuse we are overwriting the value, which used to be in rdx
// remove the allocation information, if it existed
dVarname && delete this._allocations[dVarname];
} else {
// register rdx was not free, we may need to save the current value.
// if that old store is a register,
if (isRegister(oldAllocatedStore)) {
// we want to swap the values. (swapreg is definetely a r64)
this.addToPreInstructions(
`xchg ${Register.rdx}, ${oldAllocatedStore}; ${element}, swapping with ${dVarname}, which is currently in rdx`,
);
(this._allocations[dVarname] as ValueAllocation).store = isU1(this._allocations[dVarname])
? getByteRegFromQwReg(oldAllocatedStore)
: oldAllocatedStore;
}
// if instead target to be in RDX is currently in memory (such as an arg1[0]) or in an Xmm,
else if (isMem(oldAllocatedStore) || isXmmRegister(oldAllocatedStore)) {
this._clobbers.add(dVarname);
// then we cant use xchg (would would kill mem) or is not defined for xmm
// instead want to save current rdx in another reg
let reg = this.getW(dVarname);
// then fix the allocation, for the case that we are actually only using u1
if (isByte) {
reg = getByteRegFromQwReg(reg as Register);
(this._allocations[dVarname] as ValueAllocation).store = reg;
this._allocations[dVarname].datatype = "u1";
}
const src = isByte ? ByteRegister.dl : Register.rdx;
this._preInstructions.push(
`mov ${reg}, ${src}; preserving value of ${dVarname} into a new reg`,
`${movInst} ${Register.rdx}, ${oldAllocatedStore}; saving ${element} in rdx.`,
);
}
// now for some very rare cases, if in rdx was an argPrefix (can only happen if its not a byte)
if (!isByte && matchArgPrefix(rdxSpillVarname)) {
// and in any inAllocations there
inAllocations.forEach((inAlloc, idx, arr) => {
// is a memory-access
const argMatch = matchMem(inAlloc);
if (
// using that base ptr, which used to be in rdx
argMatch?.groups?.base === Register.rdx
) {
// then that in allocation is of the form `[rdx + 0x08 * N]`
// and since rdx has not another value, we need to change the base
const newBase = (this._allocations[rdxSpillVarname] as ValueAllocation).store as Register;
arr[idx] = toMem(Number(argMatch.groups.offset) / 8, newBase);
}
});
}
}
}
let oReg = [] as Register[];
if (caf(AllocationFlags.DONT_USE_IN_REGS_AS_OUT)) {
oReg = allocationReq.oReg.map((outReg) => this.getW(outReg)) as Register[];
} else {
// if first in needs to be in out (e.g. for sub)
if (caf(AllocationFlags.IN_0_AS_OUT_REGISTER)) {
// get names from in[0] and current out-name
if (matchArg(allocationReq.in[0])) {
// e.g.: x4 = arg[0]
oReg.push(this.loadMemoryToReg(inAllocations[0], allocationReq.oReg[0], "movzx") as Register);
} else {
const regforoutvarname = this.backupIfVarHasDependencies(
allocationReq.in[0],
allocationReq.oReg[0],
);
oReg.push(regforoutvarname);
}
}
// check for reusable registes
// reusable are _registers_ of in, which have no dependants other the the currently
// calculated value
const reusable = inAllocations.filter(
(inA, i) =>
isRegister(inA) &&
!this._clobbers.has(this.getVarnameFromStore({ store: inA })) &&
!Model.hasDependants(allocationReq.in[i]),
) as Register[];
this.addToPreInstructions(Logger.log(`; currently considered reusable: ${reusable.join(", ")}`) ?? "");
// now for each protentially reusable register,
reusable.forEach((r, i) => {
if (!isRegister(r)) {
throw new Error(`${r} should be a register. What happend? TSNH`);
}
// if less registers are requested, then we are considering to resuse right now,
const amountOfRegsStillNeeded = allocationReq.oReg.length - oReg.length;
if (amountOfRegsStillNeeded <= i) {
// skip those.
return;
}
// register `r` is reusable. in that reg is var `nameReadReusable`.
const nameReadReusable = allocationReq.in[inAllocations.indexOf(r)];
// then we will delete that allocation (because its been consumed (ssa notation.))
delete this._allocations[nameReadReusable];
// then the name of the requested var.
const nameWriteReusable = allocationReq.oReg[i];
// is meant to be allocated.
// In other words: We say, now var in `r` is called `nameWriteReusable` instead of `nameReadReusable`
this._allocations[nameWriteReusable] = {
datatype: "u64",
store: r,
};
// then this will make its way into the out-registers
oReg.push(r);
});
// fill in remaining needed write-spots with fresh regs
while (oReg.length < allocationReq.oReg.length) {
oReg.push(this.getW(allocationReq.oReg[oReg.length]) as Register);
}
}
this.addToPreInstructions(Logger.log(this.allocationString()) ?? "");
return {
oReg,
in: inAllocations,
} as AllocationRes;
}
/*
* same as backupIfVarHasDependencies, but gets varname based on store first
*/
public backupIfStoreHasDependencies(store: Allocation, destinationName: string): Register | ByteRegister {
assertValueAllocation(store);
const varname = this.getVarnameFromStore(store);
return this.backupIfVarHasDependencies(varname, destinationName);
}
/*
* Will also zx if needed, even flags.
*
* this will check a given store, if it has dependencies.
*
* It will return a Register, which can be destructively used without destroying
* the value.
* If it has deps, it will copy it to a new reg, allocated for name "destinationName"
* this will check a given variableName, if it has dependencies.
*/
public backupIfVarHasDependencies(inVarname: string, outVarname: string): Register {
this._clobbers.add(inVarname);
const deps = new Set<string>();
const allocation = this._allocations[inVarname];
if (typeof allocation === "undefined") {
if (isImm(inVarname)) {
const store = this.loadImmToReg64(inVarname);
// because we will overwrite outVarname (since we are in the backup method),
// we need to delete the imm-reference (as in: this._allocations[0x0]=>{. store})
// cause the store will shortly not have this value anymore,
// TODO: maybe we sould give loadImmoReg64 a dedicated out-var for this particular case...
// usually immtoreg is just being read, but in the case of some cmov's we want a
// imm somwehere and then cmov amother imm
delete this._allocations[inVarname];
if (isByteRegister(store)) {
throw new Error(`implement handling for byte reg here.`);
}
this._allocations[outVarname] = {
datatype: "u64",
store,
};
return store;
}
throw new Error(
`TSNH. there is no allocation for ${inVarname}. Maybe it has never been calculated (i.e. has been in 'name:[${inVarname},...])`,
);
}
assertValueAllocation(allocation);
const hasDeps = Model.hasDependants(inVarname, deps);
if (isFlag(allocation.store)) {
// so if we have a flag, we need to put it into a reg, but also put it into another reg, if it has other deps as well.
if (!hasDeps) {
// if it has no other deps, declare it as outVarname
const bytereg = this.spillFlag(allocation.store, outVarname);
if (!bytereg) {
throw new Error("TSNH. it is a flag but somehow cannot be spilled?");
}
const { reg, inst } = zx(bytereg);
this.addToPreInstructions(`${inst}; spilling a flag to reg cause it has deps `);
// since we extended the reg manually, we need to update the allocations manually.
(this._allocations[outVarname] as ValueAllocation).store = reg;
this._allocations[outVarname].datatype = "u64";
return reg;
} else {
// else, (it has other deps) just spill the flag to a bytereg and continue down
const bytereg = this.spillFlag(allocation.store);
if (!bytereg) {
throw new Error("TSNH. it is a flag but somehow cannot be spilled?");
}
}
}
this.addToPreInstructions(
Logger.log(
`; to calculate ${outVarname}, i'll backup ${inVarname} from (${
allocation.store
}) if it has deps. has it: ${hasDeps}: ${setToString(deps, 10)}`,
) ?? "",
);
if (
hasDeps || // if it has deps, then back it up
!isRegister(allocation.store) // or if its not a register already (usually its a mem such as arg1[2], or has been spilled before).
// because of AllocationFlags.IN_0_AS_OUT_REGISTER, we want to have a register.
) {
const isByte = isU1(allocation);
const inst = isByte ? "movzx" : isXmmRegister(allocation.store) ? "movq" : "mov";
// allocate destination register for out[0]
const backupReg = this.getW(outVarname) as Register;
// and copy the value
const comment = Logger.log(
`${outVarname}, copying ${inVarname} here, cause ${inVarname} is needed in a reg. It has those deps: ${setToString(
deps,
10,
)}, current hard deps: ${setToString(Model.hardDependencies, 4)}`,
);
this._preInstructions.push(
`${inst} ${backupReg}, ${isByte && isMem(allocation.store) ? "byte " : ""}${allocation.store};${
comment ?? ""
}`,
);
return backupReg;
} else {
delete this._allocations[inVarname];
this._allocations[outVarname] = {
datatype: allocation.datatype,
store: allocation.store,
};
return allocation.store;
}
}
public declareFlagState(flag: Flags, fs: FlagState): void {
this._flagState[flag] = fs;
}
public declareVarForFlag(flag: Flags, name: string): void {
const current = this._flags[flag];
if (current) {
delete this._allocations[current];
}
this._flagState[flag] = FlagState.ALIVE;
this._allocations[name] = {
datatype: "u1",
store: flag,
};
}
public moveOneMemoryToRegister<MA extends { store: mem }>(
args: Array<MA>,
moveInstruction = "mov",
): [RegisterAllocation, ...(MA | RegisterAllocation)[]] {
let theChosenOne: MA;
if (args.length === 1) {
theChosenOne = args.splice(0, 1)[0];
} else if (args.length === 2 || args.length === 3) {
// we want to splice one from args. But by name, not from their memory position!
// thus: get their respective names
const names = args.map((a) => this.getVarnameFromStore(a));
// decide on a name, (either randomly or by descision on that instruction)
const chosenName = Paul.chooseArg(names);
// then splice the chosen one from the args
theChosenOne = args.splice(names.indexOf(chosenName), 1)[0];
} else {
theChosenOne = Paul.pick(args);
console.warn(`choice of ${theChosenOne} will not be saved.`);
}
const varname = this.getVarnameFromStore(theChosenOne);
this.loadVarToReg(varname, moveInstruction);
const u = uniq(args.map(({ store }) => store));
if (u.length === 1 && u[0] === theChosenOne.store) {
// if all the args are at the same location. the loadVarToReg will have moved that one variable to a register.
// Therefore the old allocation of the var, the memory address technically, is still valid, but the allocation entry has been lost.
// thus, its better to use the target reg as a return value
this._preInstructions.push(
Logger.log(
`;nop ; this is experimental. Moving ${varname} to reg in moveOneMemToReg function. since all args are the same, returning basically only that reg ${JSON.stringify(
theChosenOne,
)}.`,
) ?? "",
);
return [
this._allocations[varname] as RegisterAllocation,
...args.map((_) => this._allocations[varname] as RegisterAllocation),
];
}
return [this._allocations[varname] as RegisterAllocation, ...args];
}
/*
* Given a store, will return the name of the varibale stored in that store.
* It will throw an error or return emptystring (depending on @param throwIfNotFound), if nothing or more than one has been found.
*/
public getVarnameFromStore(
{ store: storeNeedle }: Pick<ValueAllocation, "store">,
throwIfNotFound = true,
): string {
const findings = this.entriesAllocations
.filter(([_key, { store }]) => store === storeNeedle)
?.map(([key]) => key);
if (findings.length === 1) {
return findings[0];
}
if (findings.length !== 0 || throwIfNotFound) {
throw new Error(
`Tried to find the name of data being stored at ${storeNeedle}, but found ${
findings.length
}: ${JSON.stringify(findings)} instead of ONE in the current Allocations. TSNH. Abort.`,
);
} else {
return "";
}
}
public getCurrentAllocations(): Allocations {
return this._allocations;
}
/**
* will spill @param nameOfVar, which must be stored in a flag
* to a newly aquired register.
* Then it'll set the flag-reference of that flag to 'null'
* @returns the register, where the variable is now spilled to
*/
private _spillFlag(nameOfVar: string): ByteRegister | false {
const { store } = this._allocations[nameOfVar] as U1FlagAllocation;
if (!isFlag(store)) {
throw new Error(`Wanted to spill ${nameOfVar}, but thats not a flag. Giving up.`);
}
return this.spillFlag(store, nameOfVar);
}