1function initInterlock(self)
2% INITINTERLOCK Build interlock table and find common entries/sources
4% Implements the LQNS V5
static interlock analysis:
5% Phase A: Build interlock reachability table (entry-to-entry)
6% Phase B: Find common parent entries (branch points) per server
7% Phase C: Find source tasks per server (
for interlock flow computation)
9% The interlock table
is built once at solver initialization and reused
10% across iterations. Only the interlock flow computation (in updatePopulations)
11% uses iteration-dependent throughput values.
15% Phase A: Build interlock reachability table
16% il_all(src_e, dst_e) = total reachability probability (all phases)
17% il_ph1(src_e, dst_e) = phase-1 only reachability probability
18nentries = lqn.nentries;
19il_all = zeros(nentries, nentries);
20il_ph1 = zeros(nentries, nentries);
23 eidx = lqn.eshift + e;
24 visited =
false(nentries, 1);
25 [il_all, il_ph1] = traceInterlockPaths(lqn, eidx, e, 1.0, 1.0, visited, il_all, il_ph1, 0);
28self.il_table_all = il_all;
29self.il_table_ph1 = il_ph1;
31% Phase B+C: Find common entries and sources per server entity
32nslots = lqn.nhosts + lqn.ntasks;
33self.il_common_entries = cell(lqn.tshift + lqn.ntasks, 1);
34self.il_source_tasks_all = cell(lqn.tshift + lqn.ntasks, 1);
35self.il_source_tasks_ph2 = cell(lqn.tshift + lqn.ntasks, 1);
36self.il_num_sources = zeros(lqn.tshift + lqn.ntasks, 1);
40 tidx = lqn.tshift + t;
41 if lqn.isref(tidx) || lqn.sched(tidx) == SchedStrategy.INF
44 [ce, sa, sp, ns] = findInterlockForServer(lqn, tidx, il_all, il_ph1);
45 self.il_common_entries{tidx} = ce;
46 self.il_source_tasks_all{tidx} = sa;
47 self.il_source_tasks_ph2{tidx} = sp;
48 self.il_num_sources(tidx) = ns;
54 if lqn.sched(hidx) == SchedStrategy.INF
57 [ce, sa, sp, ns] = findInterlockForServer(lqn, hidx, il_all, il_ph1);
58 self.il_common_entries{hidx} = ce;
59 self.il_source_tasks_all{hidx} = sa;
60 self.il_source_tasks_ph2{hidx} = sp;
61 self.il_num_sources(hidx) = ns;
66%% Phase A: Recursive path tracing (returns modified tables)
67function [il_all, il_ph1] = traceInterlockPaths(lqn, eidx, root_e, prob_all, prob_ph1, visited, il_all, il_ph1, depth)
69if e < 1 || e > lqn.nentries
77% Record reachability from root to
this entry
78% At depth 0 (root entry), record self-reachability (matches LQNS)
79il_all(root_e, e) = il_all(root_e, e) + prob_all;
80il_ph1(root_e, e) = il_ph1(root_e, e) + prob_ph1;
82% Follow synchronous calls from activities of
this entry
83acts = lqn.actsof{eidx};
85 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
88 a = aidx - lqn.ashift;
90 % Pruning: at non-root entries (depth > 0), skip phase-2+ activities
91 if depth > 0 && isfield(lqn, 'actphase
') && lqn.actphase(a) > 1
96 if isfield(lqn, 'actphase
') && lqn.actphase(a) > 1
100 % Follow calls from this activity
101 if aidx <= length(lqn.callsof)
102 calls_from_act = lqn.callsof{aidx};
106 for cidx = calls_from_act(:)'
107 if cidx < 1 || cidx > lqn.ncalls
110 if lqn.calltype(cidx) ~= CallType.SYNC
113 call_mean = lqn.callproc_mean(cidx);
117 dst_eidx = lqn.callpair(cidx, 2);
118 dst_e = dst_eidx - lqn.eshift;
119 if dst_e < 1 || dst_e > lqn.nentries
123 next_all = prob_all * call_mean;
125 next_ph1 = prob_ph1 * call_mean;
130 [il_all, il_ph1] = traceInterlockPaths(lqn, dst_eidx, root_e, next_all, next_ph1, visited, il_all, il_ph1, depth + 1);
137%% Phase B+C: Find interlock
for a single server
138function [commonEntries, srcAll, srcPh2, numSources] = findInterlockForServer(lqn, serverIdx, il_all, il_ph1)
144% Get server entry numbers
145serverEntryNums = getServerEntryNums(lqn, serverIdx);
146if isempty(serverEntryNums)
151clientTasks = getClientTasks(lqn, serverIdx);
152if length(clientTasks) < 1
156% Get client entries that reach the server
157clientEntryPairs = []; % [taskIdx, entryNum]
158for ct = clientTasks(:)
'
159 for ce = lqn.entriesof{ct}(:)'
160 ce_num = ce - lqn.eshift;
161 if ce_num < 1 || ce_num > lqn.nentries
164 for se_num = serverEntryNums(:)
'
165 if il_all(ce_num, se_num) > 0
166 clientEntryPairs(end+1, :) = [ct, ce_num]; %#ok<AGROW>
173if size(clientEntryPairs, 1) < 2
177% Find common parent entries (branch points)
178commonEntriesSet = [];
179nPairs = size(clientEntryPairs, 1);
182 if clientEntryPairs(i, 1) == clientEntryPairs(j, 1)
183 continue; % Same task
185 entryA_num = clientEntryPairs(i, 2);
186 entryC_num = clientEntryPairs(j, 2);
188 % Search all tasks for common parents
190 tidx = lqn.tshift + t;
191 entries_of_task = lqn.entriesof{tidx};
192 for ex = entries_of_task(:)'
193 for ey = entries_of_task(:)
'
194 ex_num = ex - lqn.eshift;
195 ey_num = ey - lqn.eshift;
196 if ex_num < 1 || ey_num < 1 || ex_num > lqn.nentries || ey_num > lqn.nentries
199 if il_all(ex_num, entryA_num) > 0 && il_all(ey_num, entryC_num) > 0
200 if isBranchPointCheck(lqn, ex, entryA_num + lqn.eshift, ey, entryC_num + lqn.eshift, il_all)
201 commonEntriesSet(end+1) = ex; %#ok<AGROW>
209commonEntriesSet = unique(commonEntriesSet);
211if isempty(commonEntriesSet)
215% Prune: remove entries whose owner is itself an interlocked server
216% (matching LQNS pruneInterlock: if the owner entity's common entries
217% include this entry, remove it)
218% Simplified: for now skip this step since it requires multi-pass
220commonEntries = commonEntriesSet;
222% Phase C: Find source tasks
223interlockedTasks = [];
224for ce_eidx = commonEntries(:)
'
225 itasks = findInterlockedTasks(lqn, ce_eidx, serverIdx, il_all);
226 interlockedTasks = union(interlockedTasks, itasks);
229% All source tasks = tasks owning common entries
232for ce_eidx = commonEntries(:)'
233 owner_tidx = lqn.parent(ce_eidx);
234 allSrcTasks(end+1) = owner_tidx; %#ok<AGROW>
235 if hasPhase2Activities(lqn, ce_eidx)
236 ph2SrcTasks(end+1) = owner_tidx; %#ok<AGROW>
239allSrcTasks = unique(allSrcTasks);
240ph2SrcTasks = unique(ph2SrcTasks);
242% Remove interlocked tasks from allSrcTasks
243allSrcTasks = setdiff(allSrcTasks, interlockedTasks);
245% Ph2 sources: interlocked tasks with phase-2 activities reaching server
247for it = interlockedTasks(:)
'
248 for ie = lqn.entriesof{it}(:)'
249 if hasPhase2Activities(lqn, ie)
250 ie_num = ie - lqn.eshift;
251 if ie_num >= 1 && ie_num <= lqn.nentries
252 for se_num = serverEntryNums(:)
'
253 if il_all(ie_num, se_num) - il_ph1(ie_num, se_num) > 0
254 ph2SrcTasks(end+1) = it; %#ok<AGROW>
262ph2SrcTasks = unique(ph2SrcTasks);
264% Add external sources (tasks calling into interlocked paths from outside)
265for it = interlockedTasks(:)'
266 for ie = lqn.entriesof{it}(:)
'
267 [calling_idx, ~] = find(lqn.iscaller(:, ie));
268 for ci = calling_idx(:)'
269 if ci > lqn.tshift && ci <= lqn.tshift + lqn.ntasks
270 if ~ismember(ci, interlockedTasks)
271 allSrcTasks(end+1) = ci; %#ok<AGROW>
277allSrcTasks = unique(allSrcTasks);
279% Count total source multiplicity
281for st = allSrcTasks(:)
'
282 nsrc = nsrc + lqn.mult(st);
285% Note: Phase-2 source copies from interlocked tasks are NOT counted
286% because LINE's LN decomposition does not model phase-2 concurrency.
287% When LN phase-2 support
is added, enable this block.
288dstEntryNums = serverEntryNums;
289if false %#ok<BDLOG> % disabled until LN supports phase-2
290for it = interlockedTasks(:)
'
291 for ie = lqn.entriesof{it}(:)'
292 ie_num = ie - lqn.eshift;
293 if ie_num < 1 || ie_num > lqn.nentries
296 for de = dstEntryNums(:)
'
297 ph2_flow = il_all(ie_num, de) - il_ph1(ie_num, de);
305end % if false (phase-2 source counting disabled)
312%% Get server entry numbers
313function nums = getServerEntryNums(lqn, serverIdx)
315if serverIdx <= lqn.nhosts
316 for tidx = lqn.tasksof{serverIdx}(:)'
317 for se = lqn.entriesof{tidx}(:)
'
318 nums(end+1) = se - lqn.eshift; %#ok<AGROW>
322 for se = lqn.entriesof{serverIdx}(:)'
323 nums(end+1) = se - lqn.eshift; %#ok<AGROW>
328%% Get client tasks
for a server
329function clientTasks = getClientTasks(lqn, serverIdx)
330if serverIdx <= lqn.nhosts
331 clientTasks = lqn.tasksof{serverIdx};
333 server_entries = lqn.entriesof{serverIdx};
335 for se = server_entries(:)
'
336 [calling_idx, ~] = find(lqn.iscaller(:, se));
337 for ci = calling_idx(:)'
338 if ci > lqn.tshift && ci <= lqn.tshift + lqn.ntasks
339 clientTasks(end+1) = ci; %#ok<AGROW>
343 clientTasks = unique(clientTasks);
348function result = isBranchPointCheck(lqn, srcX_eidx, entryA_eidx, srcY_eidx, entryB_eidx, il_all)
349entryA_num = entryA_eidx - lqn.eshift;
350entryB_num = entryB_eidx - lqn.eshift;
351taskA = lqn.parent(entryA_eidx);
352taskB = lqn.parent(entryB_eidx);
353taskX = lqn.parent(srcX_eidx);
355% Multiserver client:
if X, A, B same task => not branch point
356if taskX == taskA && taskX == taskB
361% Quick check: direct call
362if srcX_eidx == entryA_eidx || srcY_eidx == entryB_eidx
367% Check downstream calls diverge to different tasks
368dstTasks_X = getCallDstTasks(lqn, srcX_eidx, entryA_num, il_all);
369dstTasks_Y = getCallDstTasks(lqn, srcY_eidx, entryB_num, il_all);
371for dx = dstTasks_X(:)
'
372 for dy = dstTasks_Y(:)'
382%% Get destination tasks of sync calls from an entry reaching a target
383function dstTasks = getCallDstTasks(lqn, src_eidx, target_e_num, il_all)
385acts = lqn.actsof{src_eidx};
387 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
390 if aidx <= length(lqn.callsof)
391 calls = lqn.callsof{aidx};
396 if cidx < 1 || cidx > lqn.ncalls
399 if lqn.calltype(cidx) ~= CallType.SYNC
402 dst_eidx = lqn.callpair(cidx, 2);
403 dst_e = dst_eidx - lqn.eshift;
404 if dst_e >= 1 && dst_e <= lqn.nentries && il_all(dst_e, target_e_num) > 0
405 dstTasks(end+1) = lqn.parent(dst_eidx); %#ok<AGROW>
409dstTasks = unique(dstTasks);
412%% Get interlocked tasks on paths from an entry to a server
413function itasks = findInterlockedTasks(lqn, src_eidx, serverIdx, il_all)
414visited =
false(lqn.nentries, 1);
415itasks = traceToServerRec(lqn, src_eidx, serverIdx, il_all, visited, [],
true);
418function itasks = traceToServerRec(lqn, eidx, serverIdx, il_all, visited, itasks, isHead)
419e = eidx - lqn.eshift;
420if e < 1 || e > lqn.nentries || visited(e)
424ownerTask = lqn.parent(eidx);
426% Check
if we reached the server
427if ownerTask == serverIdx
430if serverIdx <= lqn.nhosts && lqn.parent(ownerTask) == serverIdx
436% Follow synchronous calls from ALL phases (unlike traceInterlockPaths,
437% interlocked task detection needs to follow phase-2 paths to find
438% intermediate tasks that are sources of non-interlocked traffic)
439acts = lqn.actsof{eidx};
442 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
446 if aidx <= length(lqn.callsof)
447 calls = lqn.callsof{aidx};
452 if cidx < 1 || cidx > lqn.ncalls || lqn.calltype(cidx) ~= CallType.SYNC
455 dst_eidx = lqn.callpair(cidx, 2);
456 dst_task = lqn.parent(dst_eidx);
458 % Check
if destination reaches server
459 reachesServer =
false;
460 if dst_task == serverIdx
461 reachesServer =
true;
462 elseif serverIdx <= lqn.nhosts && lqn.parent(dst_task) == serverIdx
463 reachesServer =
true;
465 dst_e = dst_eidx - lqn.eshift;
466 serverEntryNums = getServerEntryNums(lqn, serverIdx);
467 for se_num = serverEntryNums(:)
'
468 if il_all(dst_e, se_num) > 0
469 reachesServer = true;
476 itasks = traceToServerRec(lqn, dst_eidx, serverIdx, il_all, visited, itasks, false);
483 itasks(end+1) = ownerTask;
484 itasks = unique(itasks);
490%% Check if entry has phase-2 activities
491function result = hasPhase2Activities(lqn, eidx)
493if ~isfield(lqn, 'actphase
')
496for aidx = lqn.actsof{eidx}(:)'
497 a = aidx - lqn.ashift;
498 if a > 0 && a <= lqn.nacts && lqn.actphase(a) > 1