Below is a short sequence of fictional assembly-like instructions — the kind a naive code generator might emit. Click Optimize to run the peephole pass: the optimizer slides a window of two instructions at a time and fires any matching rewrite rule.
<!-- Main layout for the peephole optimizer demo -->
<p class="hint">This sequence was emitted by a naive code generator. Rules fire on a sliding window of 1–2 adjacent instructions. Click <b>Optimize</b> to apply the peephole pass and watch redundant instructions vanish.</p>
<div class="panel">
<div class="col">
<div class="col-label">Before</div>
<ol id="before-list" class="instr-list"></ol>
</div>
<div class="arrow-col">→</div>
<div class="col">
<div class="col-label">After</div>
<ol id="after-list" class="instr-list"></ol>
</div>
</div>
<div class="trace-box">
<div class="trace-label">Optimization trace</div>
<ul id="trace-list"></ul>
</div>
<div class="btns">
<button id="btn-optimize" type="button">Optimize</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the peephole optimizer demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.panel { display: flex; align-items: flex-start; gap: .4rem; margin-bottom: .6rem; }
.col { flex: 1; min-width: 0; }
.col-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .3rem; }
.arrow-col { font-size: 1.4rem; color: #5a7088; padding-top: 1.6rem; flex: 0 0 auto; }
.instr-list { margin: 0; padding: 0; list-style: none; }
.instr-list li { font: 13px/1.7 ui-monospace, monospace; padding: .15rem .4rem;
border-radius: 5px; border: 1px solid transparent; margin-bottom: 2px; }
.instr-list li.removed { background: #fde8e8; border-color: #f5b7b7; color: #a02020; text-decoration: line-through; }
.instr-list li.kept { background: #e8f5e9; border-color: #a5d6a7; }
.instr-list li.normal { background: #f0f4f8; border-color: #d0d8e4; }
.trace-box { background: #f8f9fb; border: 1px solid #d0d8e4; border-radius: 8px;
padding: .5rem .7rem; margin-bottom: .7rem; min-height: 2.5rem; }
.trace-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .3rem; }
#trace-list { margin: 0; padding-left: 1.1rem; }
#trace-list li { font-size: .82rem; color: #333; line-height: 1.55; }
#trace-list li.rule-fired { color: #0a7d33; }
#trace-list li.no-change { color: #888; font-style: italic; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Peephole optimizer demo logic
// Rewrite rules applied in the peephole window
// Rule 1: eliminate back-to-back redundant moves (MOV A,B followed by MOV B,A)
// Rule 2: remove ADD with zero (ADD R, 0 is a no-op)
// Rule 3: remove MUL with one (MUL R, 1 is a no-op)
// Rule 4: remove jump to immediately following label (JMP L / L:)
const RULES = [
{
name: 'redundant_move',
label: `Redundant move pair (MOV A,B / MOV B,A)`,
match: (a, b) => {
// Both must be MOV; src/dst must be swapped
const ma = a.match(/^MOV\s+(\w+),\s*(\w+)$/);
const mb = b && b.match(/^MOV\s+(\w+),\s*(\w+)$/);
return ma && mb && ma[1] === mb[2] && ma[2] === mb[1];
},
rewrite: () => []
},
{
name: 'add_zero',
label: `Identity: ADD x, 0`,
match: (a) => /^ADD\s+\w+,\s*0$/.test(a),
rewrite: () => []
},
{
name: 'mul_one',
label: `Identity: MUL x, 1`,
match: (a) => /^MUL\s+\w+,\s*1$/.test(a),
rewrite: () => []
},
{
name: 'jump_next',
label: `Jump to next label`,
match: (a, b) => {
// JMP target must equal the label on the very next line
const mj = a.match(/^JMP\s+(\w+)$/);
const ml = b && b.match(/^(\w+):$/);
return mj && ml && mj[1] === ml[1];
},
rewrite: (_a, b) => [b]
}
];
// Initial instruction sequence as a naive compiler might emit
const INITIAL = [
'MOV R1, R2',
'MOV R2, R1',
'ADD R3, 0',
'MUL R4, 1',
'JMP label1',
'label1:',
'MOV R5, R6'
];
let current = [...INITIAL];
function renderList(id, items, classes) {
const ul = document.getElementById(id);
ul.innerHTML = '';
items.forEach((text, i) => {
const li = document.createElement('li');
li.textContent = text;
li.className = classes[i] || 'normal';
ul.appendChild(li);
});
}
function optimize() {
const trace = document.getElementById('trace-list');
trace.innerHTML = '';
const before = [...current];
let out = [...before];
let i = 0;
let fired = false;
while (i < out.length) {
let matched = false;
for (const rule of RULES) {
const a = out[i], b = out[i + 1];
if (rule.match(a, b)) {
const replacement = rule.rewrite(a, b);
out.splice(i, b !== undefined && rule.match(a, b) && rule.name === 'jump_next' ? 2 : (rule.name === 'redundant_move' ? 2 : 1), ...replacement);
const li = document.createElement('li');
li.className = 'rule-fired';
li.textContent = i18nFmt(`Rule fired at position %pos%: %rule%`, { rule: rule.label, pos: i + 1 });
trace.appendChild(li);
fired = true;
matched = true;
break;
}
}
if (!matched) i++;
}
if (!fired) {
const li = document.createElement('li');
li.className = 'no-change';
li.textContent = `No more rules apply — the program is fully optimized.`;
trace.appendChild(li);
}
// Color each instruction: kept (green) or removed (red)
const beforeClasses = before.map(x => out.includes(x) ? 'kept' : 'removed');
const afterClasses = out.map(() => 'kept');
renderList('before-list', before, beforeClasses);
renderList('after-list', out, afterClasses);
current = out;
}
function reset() {
current = [...INITIAL];
renderList('before-list', INITIAL, INITIAL.map(() => 'normal'));
renderList('after-list', [], []);
document.getElementById('trace-list').innerHTML = '';
}
document.getElementById('btn-optimize').addEventListener('click', optimize);
document.getElementById('btn-reset').addEventListener('click', reset);
// Show initial state without running optimizer yet
renderList('before-list', INITIAL, INITIAL.map(() => 'normal'));
renderList('after-list', [], []);
<!-- Main layout for the peephole optimizer demo -->
<p class="hint">This sequence was emitted by a naive code generator. Rules fire on a sliding window of 1–2 adjacent instructions. Click <b>Optimize</b> to apply the peephole pass and watch redundant instructions vanish.</p>
<div class="panel">
<div class="col">
<div class="col-label">Before</div>
<ol id="before-list" class="instr-list"></ol>
</div>
<div class="arrow-col">→</div>
<div class="col">
<div class="col-label">After</div>
<ol id="after-list" class="instr-list"></ol>
</div>
</div>
<div class="trace-box">
<div class="trace-label">Optimization trace</div>
<ul id="trace-list"></ul>
</div>
<div class="btns">
<button id="btn-optimize" type="button">Optimize</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the peephole optimizer demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.panel { display: flex; align-items: flex-start; gap: .4rem; margin-bottom: .6rem; }
.col { flex: 1; min-width: 0; }
.col-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .3rem; }
.arrow-col { font-size: 1.4rem; color: #5a7088; padding-top: 1.6rem; flex: 0 0 auto; }
.instr-list { margin: 0; padding: 0; list-style: none; }
.instr-list li { font: 13px/1.7 ui-monospace, monospace; padding: .15rem .4rem;
border-radius: 5px; border: 1px solid transparent; margin-bottom: 2px; }
.instr-list li.removed { background: #fde8e8; border-color: #f5b7b7; color: #a02020; text-decoration: line-through; }
.instr-list li.kept { background: #e8f5e9; border-color: #a5d6a7; }
.instr-list li.normal { background: #f0f4f8; border-color: #d0d8e4; }
.trace-box { background: #f8f9fb; border: 1px solid #d0d8e4; border-radius: 8px;
padding: .5rem .7rem; margin-bottom: .7rem; min-height: 2.5rem; }
.trace-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .3rem; }
#trace-list { margin: 0; padding-left: 1.1rem; }
#trace-list li { font-size: .82rem; color: #333; line-height: 1.55; }
#trace-list li.rule-fired { color: #0a7d33; }
#trace-list li.no-change { color: #888; font-style: italic; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Peephole optimizer demo logic
// Rewrite rules applied in the peephole window
// Rule 1: eliminate back-to-back redundant moves (MOV A,B followed by MOV B,A)
// Rule 2: remove ADD with zero (ADD R, 0 is a no-op)
// Rule 3: remove MUL with one (MUL R, 1 is a no-op)
// Rule 4: remove jump to immediately following label (JMP L / L:)
const RULES = [
{
name: 'redundant_move',
label: `Redundant move pair (MOV A,B / MOV B,A)`,
match: (a, b) => {
// Both must be MOV; src/dst must be swapped
const ma = a.match(/^MOV\s+(\w+),\s*(\w+)$/);
const mb = b && b.match(/^MOV\s+(\w+),\s*(\w+)$/);
return ma && mb && ma[1] === mb[2] && ma[2] === mb[1];
},
rewrite: () => []
},
{
name: 'add_zero',
label: `Identity: ADD x, 0`,
match: (a) => /^ADD\s+\w+,\s*0$/.test(a),
rewrite: () => []
},
{
name: 'mul_one',
label: `Identity: MUL x, 1`,
match: (a) => /^MUL\s+\w+,\s*1$/.test(a),
rewrite: () => []
},
{
name: 'jump_next',
label: `Jump to next label`,
match: (a, b) => {
// JMP target must equal the label on the very next line
const mj = a.match(/^JMP\s+(\w+)$/);
const ml = b && b.match(/^(\w+):$/);
return mj && ml && mj[1] === ml[1];
},
rewrite: (_a, b) => [b]
}
];
// Initial instruction sequence as a naive compiler might emit
const INITIAL = [
'MOV R1, R2',
'MOV R2, R1',
'ADD R3, 0',
'MUL R4, 1',
'JMP label1',
'label1:',
'MOV R5, R6'
];
let current = [...INITIAL];
function renderList(id, items, classes) {
const ul = document.getElementById(id);
ul.innerHTML = '';
items.forEach((text, i) => {
const li = document.createElement('li');
li.textContent = text;
li.className = classes[i] || 'normal';
ul.appendChild(li);
});
}
function optimize() {
const trace = document.getElementById('trace-list');
trace.innerHTML = '';
const before = [...current];
let out = [...before];
let i = 0;
let fired = false;
while (i < out.length) {
let matched = false;
for (const rule of RULES) {
const a = out[i], b = out[i + 1];
if (rule.match(a, b)) {
const replacement = rule.rewrite(a, b);
out.splice(i, b !== undefined && rule.match(a, b) && rule.name === 'jump_next' ? 2 : (rule.name === 'redundant_move' ? 2 : 1), ...replacement);
const li = document.createElement('li');
li.className = 'rule-fired';
li.textContent = i18nFmt(`Rule fired at position %pos%: %rule%`, { rule: rule.label, pos: i + 1 });
trace.appendChild(li);
fired = true;
matched = true;
break;
}
}
if (!matched) i++;
}
if (!fired) {
const li = document.createElement('li');
li.className = 'no-change';
li.textContent = `No more rules apply — the program is fully optimized.`;
trace.appendChild(li);
}
// Color each instruction: kept (green) or removed (red)
const beforeClasses = before.map(x => out.includes(x) ? 'kept' : 'removed');
const afterClasses = out.map(() => 'kept');
renderList('before-list', before, beforeClasses);
renderList('after-list', out, afterClasses);
current = out;
}
function reset() {
current = [...INITIAL];
renderList('before-list', INITIAL, INITIAL.map(() => 'normal'));
renderList('after-list', [], []);
document.getElementById('trace-list').innerHTML = '';
}
document.getElementById('btn-optimize').addEventListener('click', optimize);
document.getElementById('btn-reset').addEventListener('click', reset);
// Show initial state without running optimizer yet
renderList('before-list', INITIAL, INITIAL.map(() => 'normal'));
renderList('after-list', [], []);
Each fired rule is highlighted in the trace. Notice how every optimization is local: the pass never needs to look at the whole program, just two adjacent instructions. That locality is both the strength (linear time, single pass) and the limit (patterns that span more than a few instructions escape the window).
Comments
Loading comments...