The demo below simulates a miniature escape-analysis pass. Each object has a scope (the function that creates it) and may or may not be exported (returned, stored globally, or passed to another thread). Toggle the checkboxes to change what each object does and watch the allocator decide.
<!-- Interactive escape-analysis demo — toggle escape conditions per object -->
<p class="hint">Each object is created inside a function. Check any box that applies — if the object is <b>returned</b>, stored in a <b>global</b>, or passed to another <b>thread</b>, it <i>escapes</i> and must go on the heap. Otherwise the compiler keeps it on the stack. Hit <b>Analyze</b> to see the verdict.</p>
<div id="objects-list"></div>
<div class="summary" id="summary"></div>
<div class="btns">
<button id="btn-reset" type="button" class="ghost">Reset</button>
<button id="btn-analyze" type="button">Analyze</button>
</div>
/* Layout for the escape-analysis demo widget */
* { 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; }
.obj-card {
border: 1.5px solid #cdd9e3;
border-radius: 10px;
padding: .55rem .8rem;
margin-bottom: .55rem;
background: #f4f7fa;
display: flex;
align-items: center;
gap: .6rem;
flex-wrap: wrap;
}
.obj-card.stack { border-color: #2a9d5c; background: #eaf7f0; }
.obj-card.heap { border-color: #c92f3c; background: #fdf0f1; }
.obj-name { font: 700 14px ui-monospace, monospace; min-width: 7rem; }
.obj-checks { display: flex; gap: 1rem; font-size: .85rem; flex: 1; flex-wrap: wrap; }
.obj-checks label { display: flex; align-items: center; gap: .3rem; cursor: pointer; }
.verdict { margin-left: auto; font: 700 13px system-ui; padding: .2rem .55rem; border-radius: 6px; }
.verdict.stack { background: #2a9d5c; color: #fff; }
.verdict.heap { background: #c92f3c; color: #fff; }
.verdict.unknown { background: #8a99a8; color: #fff; }
.summary { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.summary.good { color: #0a7d33; }
.summary.bad { color: #c92f3c; }
.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] ?? ''));
/* Escape analysis simulation: each object has three possible escape paths. */
/* Five sample objects with initial escape state (mirrors common real-world patterns) */
const OBJECTS = [
{ id: 'point', label: 'Point', scope: 'computeArea()', returned: false, global: false, thread: false },
{ id: 'buffer', label: 'Buffer', scope: 'parseInput()', returned: false, global: false, thread: false },
{ id: 'result', label: 'Result', scope: 'compute()', returned: true, global: false, thread: false },
{ id: 'counter', label: 'Counter', scope: 'runWorker()', returned: false, global: false, thread: true },
{ id: 'config', label: 'Config', scope: 'init()', returned: false, global: true, thread: false }
];
/* An object escapes iff it is returned, stored in a global, or sent to another thread. */
function doesEscape(obj) {
return obj.returned || obj.global || obj.thread;
}
function getState(id, field) {
const el = document.getElementById('chk-' + id + '-' + field);
return el ? el.checked : false;
}
function buildCard(obj) {
const card = document.createElement('div');
card.className = 'obj-card';
card.id = 'card-' + obj.id;
const name = document.createElement('div');
name.className = 'obj-name';
name.textContent = obj.label + '()';
card.appendChild(name);
const checks = document.createElement('div');
checks.className = 'obj-checks';
/* Three escape-condition checkboxes per object */
const fields = [
{ key: 'returned', label: 'returned' },
{ key: 'global', label: 'stored globally' },
{ key: 'thread', label: 'crosses thread' }
];
fields.forEach(f => {
const lbl = document.createElement('label');
const chk = document.createElement('input');
chk.type = 'checkbox';
chk.id = 'chk-' + obj.id + '-' + f.key;
chk.checked = obj[f.key];
chk.addEventListener('change', () => { clearVerdicts(); });
lbl.appendChild(chk);
lbl.appendChild(document.createTextNode(' ' + f.label));
checks.appendChild(lbl);
});
card.appendChild(checks);
const verdict = document.createElement('div');
verdict.className = 'verdict unknown';
verdict.id = 'verdict-' + obj.id;
verdict.textContent = '?';
card.appendChild(verdict);
return card;
}
function clearVerdicts() {
OBJECTS.forEach(obj => {
const v = document.getElementById('verdict-' + obj.id);
const c = document.getElementById('card-' + obj.id);
if (v) { v.className = 'verdict unknown'; v.textContent = '?'; }
if (c) { c.className = 'obj-card'; }
});
const sum = document.getElementById('summary');
sum.textContent = '';
sum.className = 'summary';
}
function analyze() {
/* Walk every object and decide stack vs heap based on current checkbox state */
let stackCount = 0, heapCount = 0;
OBJECTS.forEach(obj => {
const current = {
returned: getState(obj.id, 'returned'),
global: getState(obj.id, 'global'),
thread: getState(obj.id, 'thread')
};
const escapes = current.returned || current.global || current.thread;
const v = document.getElementById('verdict-' + obj.id);
const c = document.getElementById('card-' + obj.id);
if (escapes) {
v.className = 'verdict heap';
v.textContent = '→ heap';
c.className = 'obj-card heap';
heapCount++;
} else {
v.className = 'verdict stack';
v.textContent = '→ stack';
c.className = 'obj-card stack';
stackCount++;
}
});
const sum = document.getElementById('summary');
sum.textContent = i18nFmt('%stack% on stack, %heap% on heap.', { stack: stackCount, heap: heapCount });
sum.className = heapCount === 0 ? 'summary good' : (stackCount === 0 ? 'summary bad' : 'summary');
}
function reset() {
/* Restore all checkboxes to their original (default) state */
OBJECTS.forEach(obj => {
['returned', 'global', 'thread'].forEach(f => {
const el = document.getElementById('chk-' + obj.id + '-' + f);
if (el) el.checked = obj[f];
});
});
clearVerdicts();
}
/* Build the object cards and wire up buttons */
const list = document.getElementById('objects-list');
OBJECTS.forEach(obj => list.appendChild(buildCard(obj)));
document.getElementById('btn-analyze').addEventListener('click', analyze);
document.getElementById('btn-reset').addEventListener('click', reset);
<!-- Interactive escape-analysis demo — toggle escape conditions per object -->
<p class="hint">Each object is created inside a function. Check any box that applies — if the object is <b>returned</b>, stored in a <b>global</b>, or passed to another <b>thread</b>, it <i>escapes</i> and must go on the heap. Otherwise the compiler keeps it on the stack. Hit <b>Analyze</b> to see the verdict.</p>
<div id="objects-list"></div>
<div class="summary" id="summary"></div>
<div class="btns">
<button id="btn-reset" type="button" class="ghost">Reset</button>
<button id="btn-analyze" type="button">Analyze</button>
</div>
/* Layout for the escape-analysis demo widget */
* { 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; }
.obj-card {
border: 1.5px solid #cdd9e3;
border-radius: 10px;
padding: .55rem .8rem;
margin-bottom: .55rem;
background: #f4f7fa;
display: flex;
align-items: center;
gap: .6rem;
flex-wrap: wrap;
}
.obj-card.stack { border-color: #2a9d5c; background: #eaf7f0; }
.obj-card.heap { border-color: #c92f3c; background: #fdf0f1; }
.obj-name { font: 700 14px ui-monospace, monospace; min-width: 7rem; }
.obj-checks { display: flex; gap: 1rem; font-size: .85rem; flex: 1; flex-wrap: wrap; }
.obj-checks label { display: flex; align-items: center; gap: .3rem; cursor: pointer; }
.verdict { margin-left: auto; font: 700 13px system-ui; padding: .2rem .55rem; border-radius: 6px; }
.verdict.stack { background: #2a9d5c; color: #fff; }
.verdict.heap { background: #c92f3c; color: #fff; }
.verdict.unknown { background: #8a99a8; color: #fff; }
.summary { font-size: .95rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.summary.good { color: #0a7d33; }
.summary.bad { color: #c92f3c; }
.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] ?? ''));
/* Escape analysis simulation: each object has three possible escape paths. */
/* Five sample objects with initial escape state (mirrors common real-world patterns) */
const OBJECTS = [
{ id: 'point', label: 'Point', scope: 'computeArea()', returned: false, global: false, thread: false },
{ id: 'buffer', label: 'Buffer', scope: 'parseInput()', returned: false, global: false, thread: false },
{ id: 'result', label: 'Result', scope: 'compute()', returned: true, global: false, thread: false },
{ id: 'counter', label: 'Counter', scope: 'runWorker()', returned: false, global: false, thread: true },
{ id: 'config', label: 'Config', scope: 'init()', returned: false, global: true, thread: false }
];
/* An object escapes iff it is returned, stored in a global, or sent to another thread. */
function doesEscape(obj) {
return obj.returned || obj.global || obj.thread;
}
function getState(id, field) {
const el = document.getElementById('chk-' + id + '-' + field);
return el ? el.checked : false;
}
function buildCard(obj) {
const card = document.createElement('div');
card.className = 'obj-card';
card.id = 'card-' + obj.id;
const name = document.createElement('div');
name.className = 'obj-name';
name.textContent = obj.label + '()';
card.appendChild(name);
const checks = document.createElement('div');
checks.className = 'obj-checks';
/* Three escape-condition checkboxes per object */
const fields = [
{ key: 'returned', label: 'returned' },
{ key: 'global', label: 'stored globally' },
{ key: 'thread', label: 'crosses thread' }
];
fields.forEach(f => {
const lbl = document.createElement('label');
const chk = document.createElement('input');
chk.type = 'checkbox';
chk.id = 'chk-' + obj.id + '-' + f.key;
chk.checked = obj[f.key];
chk.addEventListener('change', () => { clearVerdicts(); });
lbl.appendChild(chk);
lbl.appendChild(document.createTextNode(' ' + f.label));
checks.appendChild(lbl);
});
card.appendChild(checks);
const verdict = document.createElement('div');
verdict.className = 'verdict unknown';
verdict.id = 'verdict-' + obj.id;
verdict.textContent = '?';
card.appendChild(verdict);
return card;
}
function clearVerdicts() {
OBJECTS.forEach(obj => {
const v = document.getElementById('verdict-' + obj.id);
const c = document.getElementById('card-' + obj.id);
if (v) { v.className = 'verdict unknown'; v.textContent = '?'; }
if (c) { c.className = 'obj-card'; }
});
const sum = document.getElementById('summary');
sum.textContent = '';
sum.className = 'summary';
}
function analyze() {
/* Walk every object and decide stack vs heap based on current checkbox state */
let stackCount = 0, heapCount = 0;
OBJECTS.forEach(obj => {
const current = {
returned: getState(obj.id, 'returned'),
global: getState(obj.id, 'global'),
thread: getState(obj.id, 'thread')
};
const escapes = current.returned || current.global || current.thread;
const v = document.getElementById('verdict-' + obj.id);
const c = document.getElementById('card-' + obj.id);
if (escapes) {
v.className = 'verdict heap';
v.textContent = '→ heap';
c.className = 'obj-card heap';
heapCount++;
} else {
v.className = 'verdict stack';
v.textContent = '→ stack';
c.className = 'obj-card stack';
stackCount++;
}
});
const sum = document.getElementById('summary');
sum.textContent = i18nFmt('%stack% on stack, %heap% on heap.', { stack: stackCount, heap: heapCount });
sum.className = heapCount === 0 ? 'summary good' : (stackCount === 0 ? 'summary bad' : 'summary');
}
function reset() {
/* Restore all checkboxes to their original (default) state */
OBJECTS.forEach(obj => {
['returned', 'global', 'thread'].forEach(f => {
const el = document.getElementById('chk-' + obj.id + '-' + f);
if (el) el.checked = obj[f];
});
});
clearVerdicts();
}
/* Build the object cards and wire up buttons */
const list = document.getElementById('objects-list');
OBJECTS.forEach(obj => list.appendChild(buildCard(obj)));
document.getElementById('btn-analyze').addEventListener('click', analyze);
document.getElementById('btn-reset').addEventListener('click', reset);
The key insight: the checker can decide stack-vs-heap in a single linear scan of each object's use sites. Finding all possible escape paths in a large program is harder — references can flow through pointer analysis chains of arbitrary depth — but the core rule is always the same: no reference escapes the scope, no heap allocation needed.
Comments
Loading comments...