The demo below simulates two threads sharing two variables, flag and data, on a weak memory model. Thread A writes data first, then sets flag. Thread B spins until it sees flag set, then reads data.
<!-- Memory barrier demo: two threads share 'data' and 'flag'. Without a fence the CPU -->
<div class="panel">
<div class="legend">
<span class="dot thread-a"></span> Thread A (writer)
<span class="dot thread-b"></span> Thread B (reader)
</div>
<div class="vars-row">
<div class="var-box" id="box-data">
<div class="var-name">data</div>
<div class="var-val" id="val-data">0</div>
</div>
<div class="fence-col" id="fence-col">
<div class="fence-label" id="fence-label">no fence</div>
<div class="fence-line" id="fence-line"></div>
</div>
<div class="var-box" id="box-flag">
<div class="var-name">flag</div>
<div class="var-val" id="val-flag">0</div>
</div>
</div>
<div class="step-row" id="step-row">
<div class="step" id="step-a"></div>
<div class="step" id="step-b"></div>
</div>
<div class="status" id="status">Press a button to start a run.</div>
<div class="btns">
<button id="btn-no-fence" type="button">Run without barrier</button>
<button id="btn-fence" type="button" class="safe">Run with barrier</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="scoreboard" id="scoreboard">
<div class="score-item">
<div class="score-label">Runs</div>
<div class="score-val" id="sc-runs">0</div>
</div>
<div class="score-item bad">
<div class="score-label">Bugs</div>
<div class="score-val" id="sc-bugs">0</div>
</div>
<div class="score-item ok">
<div class="score-label">Correct</div>
<div class="score-val" id="sc-ok">0</div>
</div>
</div>
</div>
/* global reset */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; color: #222; background: #f5f7fa; }
.panel { max-width: 420px; margin: 0 auto; padding: 1rem; }
.legend { display: flex; align-items: center; gap: .8rem; font-size: .85rem; color: #555; margin-bottom: .8rem; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.thread-a { background: #1d3557; }
.thread-b { background: #e76f51; }
/* shared variable boxes */
.vars-row { display: flex; align-items: center; justify-content: center; gap: 0; margin: .5rem 0; }
.var-box { background: #fff; border: 2px solid #cdd9e3; border-radius: 10px; padding: .6rem 1.2rem;
text-align: center; min-width: 90px; }
.var-name { font-size: .75rem; color: #888; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.var-val { font-size: 2rem; font-weight: 700; color: #1d3557; line-height: 1.2; transition: color .2s; }
.var-val.flash-ok { color: #0a7d33; }
.var-val.flash-bad { color: #c92f3c; }
/* fence column between the two variables */
.fence-col { display: flex; flex-direction: column; align-items: center; width: 60px; }
.fence-label { font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
color: #aaa; margin-bottom: 2px; }
.fence-line { width: 2px; height: 60px; background: #ccc; transition: background .3s; }
.fence-col.active .fence-label { color: #0a7d33; }
.fence-col.active .fence-line { background: #0a7d33; box-shadow: 0 0 6px #0a7d3380; }
/* per-thread step labels */
.step-row { display: flex; gap: .5rem; min-height: 2.4rem; margin: .5rem 0; }
.step { flex: 1; font-size: .82rem; color: #333; background: #e8eef3; border-radius: 7px;
padding: .35rem .5rem; text-align: center; min-height: 2.2rem; display: flex;
align-items: center; justify-content: center; transition: background .2s; }
.step.a { background: #d0d9e8; }
.step.b { background: #fde8e0; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 13px system-ui; padding: .45rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.safe { background: #0a7d33; border-color: #0a7d33; }
button.ghost { background: #fff; color: #1d3557; }
/* run scoreboard */
.scoreboard { display: flex; gap: .5rem; }
.score-item { flex: 1; background: #fff; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .4rem .3rem; text-align: center; }
.score-item.bad .score-val { color: #c92f3c; }
.score-item.ok .score-val { color: #0a7d33; }
.score-label { font-size: .68rem; color: #888; font-weight: 600; text-transform: uppercase; }
.score-val { font-size: 1.4rem; font-weight: 700; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulation model: on weak memory models a CPU may deliver a store to 'flag'
// before the store to 'data' reaches the other core (store-store reordering).
let runs = 0, bugs = 0, oks = 0;
let running = false;
let useFence = false;
function el(id){ return document.getElementById(id); }
function setVal(id, v, cls){
// flash the value box green or red
const e = el(id); e.textContent = v;
e.classList.remove('flash-ok','flash-bad');
if(cls) { void e.offsetWidth; e.classList.add(cls); }
}
function setStep(side, text){
// update the step label for side = 'a' or 'b'
const s = el('step-' + side); s.textContent = text; s.className = 'step ' + side;
}
function setStatus(t, cls){ const s = el('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function updateScore(){
el('sc-runs').textContent = runs;
el('sc-bugs').textContent = bugs;
el('sc-ok').textContent = oks;
}
// Simulate a store-store reorder: without a fence, the write to flag can
// arrive at the other core before the write to data (45% probability).
function simulateRun(fenced){
// shared variables
let data = 0, flag = 0;
// On a weak memory model (ARM/POWER) stores can be reordered. We model
// this as a 45% chance when no fence is present.
const reordered = !fenced && Math.random() < 0.45;
if(reordered){
// flag store beats data store to the other core
flag = 1; // reordered: flag visible first
data = 42;
} else {
// correct order: data is visible before flag
data = 42;
flag = 1;
}
// Thread B: spin until flag=1 then read data
// In the reordered path flag reached Thread B before data did, so B reads data=0.
const readData = reordered ? 0 : data;
return { data, flag, readData, reordered };
}
async function animateRun(fenced){
if(running) return;
running = true;
el('btn-no-fence').disabled = true;
el('btn-fence').disabled = true;
el('btn-reset').disabled = true;
useFence = fenced;
const fenceCol = el('fence-col');
const fenceLabel = el('fence-label');
if(fenced){
fenceCol.classList.add('active');
fenceLabel.textContent = 'fence active';
} else {
fenceCol.classList.remove('active');
fenceLabel.textContent = 'no fence';
}
// reset variable display
setVal('val-data','0','');
setVal('val-flag','0','');
setStep('a','waiting...');
setStep('b','waiting...');
setStatus('Running...','');
await delay(300);
// Thread A: write data=42
setStep('a','A: write data = 42');
await delay(280);
setVal('val-data','42','flash-ok');
await delay(200);
if(fenced){
// Thread A: emit memory fence (store-release)
setStep('a','A: memory fence');
await delay(350);
}
// Thread A: set flag=1
setStep('a','A: write flag = 1');
await delay(250);
const result = simulateRun(fenced);
const storeOrder = result.reordered;
if(storeOrder){
// visualize reordering: flag arrives before data
setVal('val-flag','1','flash-bad');
await delay(150);
setVal('val-data','42','flash-ok');
} else {
setVal('val-data','42','flash-ok');
await delay(100);
setVal('val-flag','1','flash-ok');
}
await delay(220);
// Thread B: spin-wait on flag
setStep('b','B: spinning on flag...');
await delay(280);
// Thread B: flag=1 seen, read data
setStep('b','B: flag = 1, read data');
await delay(250);
runs++;
const buggy = storeOrder && result.readData === 0;
if(buggy){ bugs++; } else { oks++; }
updateScore();
if(buggy){
setVal('val-data','0','flash-bad');
setStatus(i18nFmt('BUG: Thread B read data = %readData% despite flag = 1.', { readData: result.readData }),'bad');
setStep('b','B: read data = 0 (BUG!)');
} else {
setStatus(i18nFmt('OK: Thread B correctly read data = %readData%.', { readData: result.readData }),'ok');
setStep('b','B: read data = 42 ✓');
}
el('btn-no-fence').disabled = false;
el('btn-fence').disabled = false;
el('btn-reset').disabled = false;
running = false;
}
function delay(ms){ return new Promise(r => setTimeout(r, ms)); }
function reset(){
runs = 0; bugs = 0; oks = 0;
updateScore();
setVal('val-data','0','');
setVal('val-flag','0','');
setStep('a','');
setStep('b','');
setStatus('Press a button to start a run.','');
el('fence-col').classList.remove('active');
el('fence-label').textContent = 'no fence';
}
el('btn-no-fence').addEventListener('click', () => animateRun(false));
el('btn-fence').addEventListener('click', () => animateRun(true));
el('btn-reset').addEventListener('click', reset);
<!-- Memory barrier demo: two threads share 'data' and 'flag'. Without a fence the CPU -->
<div class="panel">
<div class="legend">
<span class="dot thread-a"></span> Thread A (writer)
<span class="dot thread-b"></span> Thread B (reader)
</div>
<div class="vars-row">
<div class="var-box" id="box-data">
<div class="var-name">data</div>
<div class="var-val" id="val-data">0</div>
</div>
<div class="fence-col" id="fence-col">
<div class="fence-label" id="fence-label">no fence</div>
<div class="fence-line" id="fence-line"></div>
</div>
<div class="var-box" id="box-flag">
<div class="var-name">flag</div>
<div class="var-val" id="val-flag">0</div>
</div>
</div>
<div class="step-row" id="step-row">
<div class="step" id="step-a"></div>
<div class="step" id="step-b"></div>
</div>
<div class="status" id="status">Press a button to start a run.</div>
<div class="btns">
<button id="btn-no-fence" type="button">Run without barrier</button>
<button id="btn-fence" type="button" class="safe">Run with barrier</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="scoreboard" id="scoreboard">
<div class="score-item">
<div class="score-label">Runs</div>
<div class="score-val" id="sc-runs">0</div>
</div>
<div class="score-item bad">
<div class="score-label">Bugs</div>
<div class="score-val" id="sc-bugs">0</div>
</div>
<div class="score-item ok">
<div class="score-label">Correct</div>
<div class="score-val" id="sc-ok">0</div>
</div>
</div>
</div>
/* global reset */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; color: #222; background: #f5f7fa; }
.panel { max-width: 420px; margin: 0 auto; padding: 1rem; }
.legend { display: flex; align-items: center; gap: .8rem; font-size: .85rem; color: #555; margin-bottom: .8rem; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.thread-a { background: #1d3557; }
.thread-b { background: #e76f51; }
/* shared variable boxes */
.vars-row { display: flex; align-items: center; justify-content: center; gap: 0; margin: .5rem 0; }
.var-box { background: #fff; border: 2px solid #cdd9e3; border-radius: 10px; padding: .6rem 1.2rem;
text-align: center; min-width: 90px; }
.var-name { font-size: .75rem; color: #888; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.var-val { font-size: 2rem; font-weight: 700; color: #1d3557; line-height: 1.2; transition: color .2s; }
.var-val.flash-ok { color: #0a7d33; }
.var-val.flash-bad { color: #c92f3c; }
/* fence column between the two variables */
.fence-col { display: flex; flex-direction: column; align-items: center; width: 60px; }
.fence-label { font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
color: #aaa; margin-bottom: 2px; }
.fence-line { width: 2px; height: 60px; background: #ccc; transition: background .3s; }
.fence-col.active .fence-label { color: #0a7d33; }
.fence-col.active .fence-line { background: #0a7d33; box-shadow: 0 0 6px #0a7d3380; }
/* per-thread step labels */
.step-row { display: flex; gap: .5rem; min-height: 2.4rem; margin: .5rem 0; }
.step { flex: 1; font-size: .82rem; color: #333; background: #e8eef3; border-radius: 7px;
padding: .35rem .5rem; text-align: center; min-height: 2.2rem; display: flex;
align-items: center; justify-content: center; transition: background .2s; }
.step.a { background: #d0d9e8; }
.step.b { background: #fde8e0; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 13px system-ui; padding: .45rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.safe { background: #0a7d33; border-color: #0a7d33; }
button.ghost { background: #fff; color: #1d3557; }
/* run scoreboard */
.scoreboard { display: flex; gap: .5rem; }
.score-item { flex: 1; background: #fff; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .4rem .3rem; text-align: center; }
.score-item.bad .score-val { color: #c92f3c; }
.score-item.ok .score-val { color: #0a7d33; }
.score-label { font-size: .68rem; color: #888; font-weight: 600; text-transform: uppercase; }
.score-val { font-size: 1.4rem; font-weight: 700; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulation model: on weak memory models a CPU may deliver a store to 'flag'
// before the store to 'data' reaches the other core (store-store reordering).
let runs = 0, bugs = 0, oks = 0;
let running = false;
let useFence = false;
function el(id){ return document.getElementById(id); }
function setVal(id, v, cls){
// flash the value box green or red
const e = el(id); e.textContent = v;
e.classList.remove('flash-ok','flash-bad');
if(cls) { void e.offsetWidth; e.classList.add(cls); }
}
function setStep(side, text){
// update the step label for side = 'a' or 'b'
const s = el('step-' + side); s.textContent = text; s.className = 'step ' + side;
}
function setStatus(t, cls){ const s = el('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function updateScore(){
el('sc-runs').textContent = runs;
el('sc-bugs').textContent = bugs;
el('sc-ok').textContent = oks;
}
// Simulate a store-store reorder: without a fence, the write to flag can
// arrive at the other core before the write to data (45% probability).
function simulateRun(fenced){
// shared variables
let data = 0, flag = 0;
// On a weak memory model (ARM/POWER) stores can be reordered. We model
// this as a 45% chance when no fence is present.
const reordered = !fenced && Math.random() < 0.45;
if(reordered){
// flag store beats data store to the other core
flag = 1; // reordered: flag visible first
data = 42;
} else {
// correct order: data is visible before flag
data = 42;
flag = 1;
}
// Thread B: spin until flag=1 then read data
// In the reordered path flag reached Thread B before data did, so B reads data=0.
const readData = reordered ? 0 : data;
return { data, flag, readData, reordered };
}
async function animateRun(fenced){
if(running) return;
running = true;
el('btn-no-fence').disabled = true;
el('btn-fence').disabled = true;
el('btn-reset').disabled = true;
useFence = fenced;
const fenceCol = el('fence-col');
const fenceLabel = el('fence-label');
if(fenced){
fenceCol.classList.add('active');
fenceLabel.textContent = 'fence active';
} else {
fenceCol.classList.remove('active');
fenceLabel.textContent = 'no fence';
}
// reset variable display
setVal('val-data','0','');
setVal('val-flag','0','');
setStep('a','waiting...');
setStep('b','waiting...');
setStatus('Running...','');
await delay(300);
// Thread A: write data=42
setStep('a','A: write data = 42');
await delay(280);
setVal('val-data','42','flash-ok');
await delay(200);
if(fenced){
// Thread A: emit memory fence (store-release)
setStep('a','A: memory fence');
await delay(350);
}
// Thread A: set flag=1
setStep('a','A: write flag = 1');
await delay(250);
const result = simulateRun(fenced);
const storeOrder = result.reordered;
if(storeOrder){
// visualize reordering: flag arrives before data
setVal('val-flag','1','flash-bad');
await delay(150);
setVal('val-data','42','flash-ok');
} else {
setVal('val-data','42','flash-ok');
await delay(100);
setVal('val-flag','1','flash-ok');
}
await delay(220);
// Thread B: spin-wait on flag
setStep('b','B: spinning on flag...');
await delay(280);
// Thread B: flag=1 seen, read data
setStep('b','B: flag = 1, read data');
await delay(250);
runs++;
const buggy = storeOrder && result.readData === 0;
if(buggy){ bugs++; } else { oks++; }
updateScore();
if(buggy){
setVal('val-data','0','flash-bad');
setStatus(i18nFmt('BUG: Thread B read data = %readData% despite flag = 1.', { readData: result.readData }),'bad');
setStep('b','B: read data = 0 (BUG!)');
} else {
setStatus(i18nFmt('OK: Thread B correctly read data = %readData%.', { readData: result.readData }),'ok');
setStep('b','B: read data = 42 ✓');
}
el('btn-no-fence').disabled = false;
el('btn-fence').disabled = false;
el('btn-reset').disabled = false;
running = false;
}
function delay(ms){ return new Promise(r => setTimeout(r, ms)); }
function reset(){
runs = 0; bugs = 0; oks = 0;
updateScore();
setVal('val-data','0','');
setVal('val-flag','0','');
setStep('a','');
setStep('b','');
setStatus('Press a button to start a run.','');
el('fence-col').classList.remove('active');
el('fence-label').textContent = 'no fence';
}
el('btn-no-fence').addEventListener('click', () => animateRun(false));
el('btn-fence').addEventListener('click', () => animateRun(true));
el('btn-reset').addEventListener('click', reset);
Without a barrier, the CPU is free to deliver the write to flag before the write to data reaches the other core — so Thread B can read flag = 1 and still see data = 0. Click Run without barrier to trigger the bug, then click Run with barrier to see the fence restore ordering. The numbers update live across many simulated runs.
Comments
Loading comments...