-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
133 lines (127 loc) · 4.62 KB
/
index.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
/**
* Peephole optimizations for MIPS and x64 instructions
*/
import type { RA, WritableArray } from '../../utils/types.js';
import { Jmp } from '../definitions/amd/Jmp.js';
import type { Instruction } from '../definitions/index.js';
import { J } from '../definitions/mips/J.js';
import { Line } from '../index.js';
import { MovQ } from '../definitions/amd/MovQ.js';
import { filterArray } from '../../utils/types.js';
import { Sw } from '../definitions/mips/Sw.js';
import { Lw } from '../definitions/mips/Lw.js';
import { Move } from '../definitions/mips/Move.js';
/** A typescript helper for increased type safety */
const define = <
T extends new (...args: RA<any>) => Instruction = new (
...args: RA<any>
) => Instruction
>(
instruction: T,
callback: (instruction: InstanceType<T>, lines: RA<Line>) => RA<Line>
) => ({
instruction: instruction as new (...args: RA<any>) => Instruction,
callback: callback as (
instruction: Instruction,
lines: RA<Line>
) => WritableArray<Line>,
});
/**
* This includes optimizations for both MIPS and x64 instructions
*/
const peepHoleOptimizations: RA<ReturnType<typeof define>> = [
// Remove jumps to next instruction
define(J, (instruction, lines) =>
lines.at(1)?.label === instruction.label ? lines.slice(1) : lines
),
// Remove jumps to next instruction
define(Jmp, (instruction, lines) =>
lines.at(1)?.label === instruction.label ? lines.slice(1) : lines
),
// Remove save followed by load (where safe)
define(MovQ, (instruction, lines) => {
const nextInstruction = lines.at(1)?.instruction;
return nextInstruction instanceof MovQ &&
instruction.destination === nextInstruction.source &&
isAmdRegister(instruction.destination) &&
(isAmdRegister(instruction.source) ||
isAmdRegister(nextInstruction.destination))
? [
mergeLines(
lines.slice(0, 2),
new MovQ(instruction.source, nextInstruction.destination)
),
...lines.slice(2),
]
: lines;
}),
// Remove save followed by load (where safe)
define(Sw, (instruction, lines) => {
const nextInstruction = lines.at(1)?.instruction;
return nextInstruction instanceof Lw &&
instruction.destination === nextInstruction.source &&
isMipsRegister(instruction.destination) &&
(isMipsRegister(instruction.source) ||
isMipsRegister(nextInstruction.destination))
? [
mergeLines(
lines.slice(0, 2),
new MovQ(instruction.source, nextInstruction.destination)
),
...lines.slice(2),
]
: lines;
}),
// Get rid of useless movq
define(MovQ, (instruction, lines) =>
instruction.destination === instruction.source ? lines.slice(1) : lines
),
// Get rid of useless move
define(Move, (instruction, lines) =>
instruction.destination === instruction.source ? lines.slice(1) : lines
),
];
/**
* Helps determine whether a given movq/sw instruction is safe to be eliminated
* (direct memory to memory moves are not supported, thus intermediate moves
* can't be eliminated. Additionally, moves that include stack or global
* variables are unsafe to optimize as they may be used in other places)
*/
const isAmdRegister = (name: string): boolean =>
name.startsWith('%') || name.startsWith('$');
const isMipsRegister = (name: string): boolean => name.startsWith('$');
/**
* Run peephole optimization line by line using the defined optimizations
*/
export function optimizeInstructions(lines: RA<Line>): RA<Line> {
const optimizedLines: WritableArray<Line> = [];
let rawLines = lines.slice();
while (rawLines.length > 0) {
let changed = false;
peepHoleOptimizations.forEach(({ instruction, callback }) => {
if (rawLines[0].instruction instanceof instruction) {
const newLines = callback(rawLines[0].instruction, rawLines);
changed ||= newLines !== rawLines;
rawLines = newLines;
}
});
// Redo the optimization if we changed something
if (changed) continue;
const nextLine = rawLines.shift();
if (nextLine !== undefined) optimizedLines.push(nextLine);
}
return optimizedLines;
}
/**
* Replace several lines with a new line, while carrying over the comments and
* the label if present
*/
function mergeLines(lines: RA<Line>, instruction: Instruction): Line {
const comments = lines.flatMap(({ comments }) => comments);
const labels = filterArray(lines.map(({ label }) => label)).filter(
(label) => label.length > 0
);
if (labels.length > 1)
throw new Error('Unable to merge lines as they have more than one label');
return new Line(labels[0] ?? '', instruction, comments);
}