Below is a small three-variable program. Each line is an instruction that either defines (writes) or uses (reads) a variable. The analysis runs backward from the bottom up, computing the set of live variables just before each instruction.
<!-- Main layout — program listing + live-variable chips -->
<div class="panel">
<div class="prog-header">
<span class="label">Program</span>
<span class="label live-legend"><span class="dot live"></span>Live</span>
<span class="label dead-legend"><span class="dot dead"></span>Dead</span>
</div>
<table id="prog" class="prog"></table>
</div>
<div class="panel info-panel">
<p class="hint">Each row shows one instruction. The colored chips show which variables are <b>live</b> (green = still needed) or <b>dead</b> (gray = safe to reclaim) just before that line executes. Step through line by line or run the full analysis.</p>
<div class="status" id="status">Press Step or Run analysis to begin.</div>
</div>
<div class="btns">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run analysis</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Layout and chip colors for the liveness visualizer */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.panel { background: #f5f7fa; border: 1px solid #dde2e8; border-radius: 10px; padding: .7rem 1rem; margin-bottom: .6rem; }
.prog-header { display: flex; gap: 1rem; align-items: center; margin-bottom: .4rem; flex-wrap: wrap; }
.label { font-size: .8rem; color: #555; font-weight: 600; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 3px; vertical-align: middle; }
.dot.live { background: #2a9d4e; }
.dot.dead { background: #adb1b8; }
.live-legend, .dead-legend { display: flex; align-items: center; gap: 2px; }
.prog { border-collapse: collapse; width: 100%; font-family: ui-monospace, monospace; font-size: .88rem; }
.prog tr { border-bottom: 1px solid #e4e8ed; }
.prog tr.current { background: #e8f0fe; }
.prog td { padding: .28rem .4rem; vertical-align: middle; }
.prog td.lineno { color: #999; width: 24px; text-align: right; user-select: none; }
.prog td.instr { color: #1d3557; white-space: pre; }
.prog td.vars { display: flex; gap: 4px; flex-wrap: wrap; padding: .2rem .4rem; }
.var-chip { padding: 2px 8px; border-radius: 12px; font-size: .8rem; font-weight: 600; transition: background .2s, color .2s; }
.var-chip.live { background: #d1fae5; color: #065f2e; border: 1px solid #6ee7b7; }
.var-chip.dead { background: #e5e7eb; color: #6b7280; border: 1px solid #d1d5db; }
.hint { font-size: .85rem; color: #444; margin: 0 0 .4rem; line-height: 1.5; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; color: #1d3557; }
.status.done { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .2rem; }
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; }
button:disabled { opacity: .45; cursor: not-allowed; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Sample program: 5 instructions over 3 variables a, b, c. */
/* Each entry: text (display), def (variable written), use (variables read). */
const INSTRS = [
{ text: 'a = 1', def: 'a', use: [] },
{ text: 'b = a + 2', def: 'b', use: ['a'] },
{ text: 'c = b * b', def: 'c', use: ['b'] },
{ text: 'a = c - 1', def: 'a', use: ['c'] },
{ text: 'print(a)', def: null, use: ['a'] }
];
const VARS = ['a', 'b', 'c'];
/* Backward liveness dataflow: iterate until fixed point. */
/* LiveIn(i) = use(i) ∪ (LiveOut(i) − def(i)) */
/* LiveOut(i) = LiveIn(i+1) (straight-line; no branches in this demo) */
function computeLiveness() {
const n = INSTRS.length;
const liveIn = Array.from({ length: n }, () => new Set());
const liveOut = Array.from({ length: n }, () => new Set());
/* Iterate backward until no set changes (fixed point). */
let changed = true;
while (changed) {
changed = false;
for (let i = n - 1; i >= 0; i--) {
/* LiveOut of line i = LiveIn of the next line (single successor). */
const newOut = new Set(i + 1 < n ? liveIn[i + 1] : []);
/* LiveIn of line i = (LiveOut − def) ∪ use. */
const newIn = new Set(liveOut[i]);
if (INSTRS[i].def) newIn.delete(INSTRS[i].def);
for (const u of INSTRS[i].use) newIn.add(u);
if (!eqSets(newIn, liveIn[i]) || !eqSets(newOut, liveOut[i])) {
liveIn[i] = newIn; liveOut[i] = newOut; changed = true;
}
}
}
return { liveIn, liveOut };
}
function eqSets(a, b) {
if (a.size !== b.size) return false;
for (const x of a) if (!b.has(x)) return false;
return true;
}
const result = computeLiveness();
/* Current highlighted line index (-1 = none). */
let step = -1;
function render(highlight) {
const tbody = document.getElementById('prog');
tbody.innerHTML = '';
INSTRS.forEach((instr, i) => {
const tr = document.createElement('tr');
if (i === highlight) tr.classList.add('current');
const live = highlight >= 0 ? result.liveIn[i] : new Set();
const tdN = document.createElement('td'); tdN.className = 'lineno'; tdN.textContent = i + 1;
const tdI = document.createElement('td'); tdI.className = 'instr'; tdI.textContent = instr.text;
const tdV = document.createElement('td'); tdV.className = 'vars';
VARS.forEach(v => {
const chip = document.createElement('span');
chip.className = 'var-chip ' + (live.has(v) ? 'live' : 'dead');
chip.textContent = v;
tdV.appendChild(chip);
});
tr.appendChild(tdN); tr.appendChild(tdI); tr.appendChild(tdV);
tbody.appendChild(tr);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls || '');
}
function syncButtons() {
document.getElementById('btn-step').disabled = step >= INSTRS.length - 1;
document.getElementById('btn-run').disabled = step >= INSTRS.length - 1;
}
document.getElementById('btn-step').addEventListener('click', () => {
if (step < INSTRS.length - 1) step++;
render(step);
const liveVars = [...result.liveIn[step]].join(', ') || '—';
setStatus(i18nFmt('Line %line%: live before = { %vars% }', { line: step + 1, vars: liveVars }));
syncButtons();
});
document.getElementById('btn-run').addEventListener('click', () => {
step = INSTRS.length - 1;
render(-2);
/* Repaint all rows with final liveness results. */
const tbody = document.getElementById('prog');
Array.from(tbody.rows).forEach((tr, i) => {
const live = result.liveIn[i];
const tdV = tr.querySelector('.vars'); tdV.innerHTML = '';
VARS.forEach(v => {
const chip = document.createElement('span');
chip.className = 'var-chip ' + (live.has(v) ? 'live' : 'dead');
chip.textContent = v;
tdV.appendChild(chip);
});
});
setStatus('Analysis complete — all live ranges computed.', 'done');
syncButtons();
});
document.getElementById('btn-reset').addEventListener('click', () => {
step = -1; render(-1); setStatus('Press Step or Run analysis to begin.'); syncButtons();
});
render(-1);
setStatus('Press Step or Run analysis to begin.');
syncButtons();
<!-- Main layout — program listing + live-variable chips -->
<div class="panel">
<div class="prog-header">
<span class="label">Program</span>
<span class="label live-legend"><span class="dot live"></span>Live</span>
<span class="label dead-legend"><span class="dot dead"></span>Dead</span>
</div>
<table id="prog" class="prog"></table>
</div>
<div class="panel info-panel">
<p class="hint">Each row shows one instruction. The colored chips show which variables are <b>live</b> (green = still needed) or <b>dead</b> (gray = safe to reclaim) just before that line executes. Step through line by line or run the full analysis.</p>
<div class="status" id="status">Press Step or Run analysis to begin.</div>
</div>
<div class="btns">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run analysis</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Layout and chip colors for the liveness visualizer */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.panel { background: #f5f7fa; border: 1px solid #dde2e8; border-radius: 10px; padding: .7rem 1rem; margin-bottom: .6rem; }
.prog-header { display: flex; gap: 1rem; align-items: center; margin-bottom: .4rem; flex-wrap: wrap; }
.label { font-size: .8rem; color: #555; font-weight: 600; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 3px; vertical-align: middle; }
.dot.live { background: #2a9d4e; }
.dot.dead { background: #adb1b8; }
.live-legend, .dead-legend { display: flex; align-items: center; gap: 2px; }
.prog { border-collapse: collapse; width: 100%; font-family: ui-monospace, monospace; font-size: .88rem; }
.prog tr { border-bottom: 1px solid #e4e8ed; }
.prog tr.current { background: #e8f0fe; }
.prog td { padding: .28rem .4rem; vertical-align: middle; }
.prog td.lineno { color: #999; width: 24px; text-align: right; user-select: none; }
.prog td.instr { color: #1d3557; white-space: pre; }
.prog td.vars { display: flex; gap: 4px; flex-wrap: wrap; padding: .2rem .4rem; }
.var-chip { padding: 2px 8px; border-radius: 12px; font-size: .8rem; font-weight: 600; transition: background .2s, color .2s; }
.var-chip.live { background: #d1fae5; color: #065f2e; border: 1px solid #6ee7b7; }
.var-chip.dead { background: #e5e7eb; color: #6b7280; border: 1px solid #d1d5db; }
.hint { font-size: .85rem; color: #444; margin: 0 0 .4rem; line-height: 1.5; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; color: #1d3557; }
.status.done { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .2rem; }
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; }
button:disabled { opacity: .45; cursor: not-allowed; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* Sample program: 5 instructions over 3 variables a, b, c. */
/* Each entry: text (display), def (variable written), use (variables read). */
const INSTRS = [
{ text: 'a = 1', def: 'a', use: [] },
{ text: 'b = a + 2', def: 'b', use: ['a'] },
{ text: 'c = b * b', def: 'c', use: ['b'] },
{ text: 'a = c - 1', def: 'a', use: ['c'] },
{ text: 'print(a)', def: null, use: ['a'] }
];
const VARS = ['a', 'b', 'c'];
/* Backward liveness dataflow: iterate until fixed point. */
/* LiveIn(i) = use(i) ∪ (LiveOut(i) − def(i)) */
/* LiveOut(i) = LiveIn(i+1) (straight-line; no branches in this demo) */
function computeLiveness() {
const n = INSTRS.length;
const liveIn = Array.from({ length: n }, () => new Set());
const liveOut = Array.from({ length: n }, () => new Set());
/* Iterate backward until no set changes (fixed point). */
let changed = true;
while (changed) {
changed = false;
for (let i = n - 1; i >= 0; i--) {
/* LiveOut of line i = LiveIn of the next line (single successor). */
const newOut = new Set(i + 1 < n ? liveIn[i + 1] : []);
/* LiveIn of line i = (LiveOut − def) ∪ use. */
const newIn = new Set(liveOut[i]);
if (INSTRS[i].def) newIn.delete(INSTRS[i].def);
for (const u of INSTRS[i].use) newIn.add(u);
if (!eqSets(newIn, liveIn[i]) || !eqSets(newOut, liveOut[i])) {
liveIn[i] = newIn; liveOut[i] = newOut; changed = true;
}
}
}
return { liveIn, liveOut };
}
function eqSets(a, b) {
if (a.size !== b.size) return false;
for (const x of a) if (!b.has(x)) return false;
return true;
}
const result = computeLiveness();
/* Current highlighted line index (-1 = none). */
let step = -1;
function render(highlight) {
const tbody = document.getElementById('prog');
tbody.innerHTML = '';
INSTRS.forEach((instr, i) => {
const tr = document.createElement('tr');
if (i === highlight) tr.classList.add('current');
const live = highlight >= 0 ? result.liveIn[i] : new Set();
const tdN = document.createElement('td'); tdN.className = 'lineno'; tdN.textContent = i + 1;
const tdI = document.createElement('td'); tdI.className = 'instr'; tdI.textContent = instr.text;
const tdV = document.createElement('td'); tdV.className = 'vars';
VARS.forEach(v => {
const chip = document.createElement('span');
chip.className = 'var-chip ' + (live.has(v) ? 'live' : 'dead');
chip.textContent = v;
tdV.appendChild(chip);
});
tr.appendChild(tdN); tr.appendChild(tdI); tr.appendChild(tdV);
tbody.appendChild(tr);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + (cls || '');
}
function syncButtons() {
document.getElementById('btn-step').disabled = step >= INSTRS.length - 1;
document.getElementById('btn-run').disabled = step >= INSTRS.length - 1;
}
document.getElementById('btn-step').addEventListener('click', () => {
if (step < INSTRS.length - 1) step++;
render(step);
const liveVars = [...result.liveIn[step]].join(', ') || '—';
setStatus(i18nFmt('Line %line%: live before = { %vars% }', { line: step + 1, vars: liveVars }));
syncButtons();
});
document.getElementById('btn-run').addEventListener('click', () => {
step = INSTRS.length - 1;
render(-2);
/* Repaint all rows with final liveness results. */
const tbody = document.getElementById('prog');
Array.from(tbody.rows).forEach((tr, i) => {
const live = result.liveIn[i];
const tdV = tr.querySelector('.vars'); tdV.innerHTML = '';
VARS.forEach(v => {
const chip = document.createElement('span');
chip.className = 'var-chip ' + (live.has(v) ? 'live' : 'dead');
chip.textContent = v;
tdV.appendChild(chip);
});
});
setStatus('Analysis complete — all live ranges computed.', 'done');
syncButtons();
});
document.getElementById('btn-reset').addEventListener('click', () => {
step = -1; render(-1); setStatus('Press Step or Run analysis to begin.'); syncButtons();
});
render(-1);
setStatus('Press Step or Run analysis to begin.');
syncButtons();
Step through the program or click Run analysis to see the fixed-point solution. A variable highlighted in green is live at that point — its current value might still be read. A gray variable is dead — its register is free to reuse.
Comments
Loading comments...