LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
initInterlock.m
1function initInterlock(self)
2% INITINTERLOCK Build interlock table and find common entries/sources
3%
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)
8%
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.
12
13lqn = self.lqn;
14
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);
21
22for e = 1: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);
26end
27
28self.il_table_all = il_all;
29self.il_table_ph1 = il_ph1;
30
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);
37
38% Process task servers
39for t = 1:lqn.ntasks
40 tidx = lqn.tshift + t;
41 if lqn.isref(tidx) || lqn.sched(tidx) == SchedStrategy.INF
42 continue;
43 end
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;
49end
50
51% Process host servers
52for h = 1:lqn.nhosts
53 hidx = h;
54 if lqn.sched(hidx) == SchedStrategy.INF
55 continue;
56 end
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;
62end
63
64end
65
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)
68e = eidx - lqn.eshift;
69if e < 1 || e > lqn.nentries
70 return;
71end
72if visited(e)
73 return;
74end
75visited(e) = true;
76
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;
81
82% Follow synchronous calls from activities of this entry
83acts = lqn.actsof{eidx};
84for aidx = acts(:)'
85 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
86 continue;
87 end
88 a = aidx - lqn.ashift;
89
90 % Pruning: at non-root entries (depth > 0), skip phase-2+ activities
91 if depth > 0 && isfield(lqn, 'actphase') && lqn.actphase(a) > 1
92 continue;
93 end
94
95 is_ph1 = true;
96 if isfield(lqn, 'actphase') && lqn.actphase(a) > 1
97 is_ph1 = false;
98 end
99
100 % Follow calls from this activity
101 if aidx <= length(lqn.callsof)
102 calls_from_act = lqn.callsof{aidx};
103 else
104 calls_from_act = [];
105 end
106 for cidx = calls_from_act(:)'
107 if cidx < 1 || cidx > lqn.ncalls
108 continue;
109 end
110 if lqn.calltype(cidx) ~= CallType.SYNC
111 continue;
112 end
113 call_mean = lqn.callproc_mean(cidx);
114 if call_mean <= 0
115 continue;
116 end
117 dst_eidx = lqn.callpair(cidx, 2);
118 dst_e = dst_eidx - lqn.eshift;
119 if dst_e < 1 || dst_e > lqn.nentries
120 continue;
121 end
122
123 next_all = prob_all * call_mean;
124 if is_ph1
125 next_ph1 = prob_ph1 * call_mean;
126 else
127 next_ph1 = 0;
128 end
129
130 [il_all, il_ph1] = traceInterlockPaths(lqn, dst_eidx, root_e, next_all, next_ph1, visited, il_all, il_ph1, depth + 1);
131 end
132end
133
134visited(e) = false;
135end
136
137%% Phase B+C: Find interlock for a single server
138function [commonEntries, srcAll, srcPh2, numSources] = findInterlockForServer(lqn, serverIdx, il_all, il_ph1)
139commonEntries = [];
140srcAll = [];
141srcPh2 = [];
142numSources = 0;
143
144% Get server entry numbers
145serverEntryNums = getServerEntryNums(lqn, serverIdx);
146if isempty(serverEntryNums)
147 return;
148end
149
150% Get client tasks
151clientTasks = getClientTasks(lqn, serverIdx);
152if length(clientTasks) < 1
153 return;
154end
155
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
162 continue;
163 end
164 for se_num = serverEntryNums(:)'
165 if il_all(ce_num, se_num) > 0
166 clientEntryPairs(end+1, :) = [ct, ce_num]; %#ok<AGROW>
167 break;
168 end
169 end
170 end
171end
172
173if size(clientEntryPairs, 1) < 2
174 return;
175end
176
177% Find common parent entries (branch points)
178commonEntriesSet = [];
179nPairs = size(clientEntryPairs, 1);
180for i = 1:nPairs
181 for j = (i+1):nPairs
182 if clientEntryPairs(i, 1) == clientEntryPairs(j, 1)
183 continue; % Same task
184 end
185 entryA_num = clientEntryPairs(i, 2);
186 entryC_num = clientEntryPairs(j, 2);
187
188 % Search all tasks for common parents
189 for t = 1:lqn.ntasks
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
197 continue;
198 end
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>
202 end
203 end
204 end
205 end
206 end
207 end
208end
209commonEntriesSet = unique(commonEntriesSet);
210
211if isempty(commonEntriesSet)
212 return;
213end
214
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
219
220commonEntries = commonEntriesSet;
221
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);
227end
228
229% All source tasks = tasks owning common entries
230allSrcTasks = [];
231ph2SrcTasks = [];
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>
237 end
238end
239allSrcTasks = unique(allSrcTasks);
240ph2SrcTasks = unique(ph2SrcTasks);
241
242% Remove interlocked tasks from allSrcTasks
243allSrcTasks = setdiff(allSrcTasks, interlockedTasks);
244
245% Ph2 sources: interlocked tasks with phase-2 activities reaching server
246ph2SrcTasks = [];
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>
255 break;
256 end
257 end
258 end
259 end
260 end
261end
262ph2SrcTasks = unique(ph2SrcTasks);
263
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>
272 end
273 end
274 end
275 end
276end
277allSrcTasks = unique(allSrcTasks);
278
279% Count total source multiplicity
280nsrc = 0;
281for st = allSrcTasks(:)'
282 nsrc = nsrc + lqn.mult(st);
283end
284
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
294 continue;
295 end
296 for de = dstEntryNums(:)'
297 ph2_flow = il_all(ie_num, de) - il_ph1(ie_num, de);
298 if ph2_flow > 0
299 nsrc = nsrc + 1;
300 break;
301 end
302 end
303 end
304end
305end % if false (phase-2 source counting disabled)
306
307srcAll = allSrcTasks;
308srcPh2 = ph2SrcTasks;
309numSources = nsrc;
310end
311
312%% Get server entry numbers
313function nums = getServerEntryNums(lqn, serverIdx)
314nums = [];
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>
319 end
320 end
321else
322 for se = lqn.entriesof{serverIdx}(:)'
323 nums(end+1) = se - lqn.eshift; %#ok<AGROW>
324 end
325end
326end
327
328%% Get client tasks for a server
329function clientTasks = getClientTasks(lqn, serverIdx)
330if serverIdx <= lqn.nhosts
331 clientTasks = lqn.tasksof{serverIdx};
332else
333 server_entries = lqn.entriesof{serverIdx};
334 clientTasks = [];
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>
340 end
341 end
342 end
343 clientTasks = unique(clientTasks);
344end
345end
346
347%% Branch point check
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);
354
355% Multiserver client: if X, A, B same task => not branch point
356if taskX == taskA && taskX == taskB
357 result = false;
358 return;
359end
360
361% Quick check: direct call
362if srcX_eidx == entryA_eidx || srcY_eidx == entryB_eidx
363 result = true;
364 return;
365end
366
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);
370
371for dx = dstTasks_X(:)'
372 for dy = dstTasks_Y(:)'
373 if dx ~= dy
374 result = true;
375 return;
376 end
377 end
378end
379result = false;
380end
381
382%% Get destination tasks of sync calls from an entry reaching a target
383function dstTasks = getCallDstTasks(lqn, src_eidx, target_e_num, il_all)
384dstTasks = [];
385acts = lqn.actsof{src_eidx};
386for aidx = acts(:)'
387 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
388 continue;
389 end
390 if aidx <= length(lqn.callsof)
391 calls = lqn.callsof{aidx};
392 else
393 calls = [];
394 end
395 for cidx = calls(:)'
396 if cidx < 1 || cidx > lqn.ncalls
397 continue;
398 end
399 if lqn.calltype(cidx) ~= CallType.SYNC
400 continue;
401 end
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>
406 end
407 end
408end
409dstTasks = unique(dstTasks);
410end
411
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);
416end
417
418function itasks = traceToServerRec(lqn, eidx, serverIdx, il_all, visited, itasks, isHead)
419e = eidx - lqn.eshift;
420if e < 1 || e > lqn.nentries || visited(e)
421 return;
422end
423
424ownerTask = lqn.parent(eidx);
425
426% Check if we reached the server
427if ownerTask == serverIdx
428 return;
429end
430if serverIdx <= lqn.nhosts && lqn.parent(ownerTask) == serverIdx
431 return;
432end
433
434visited(e) = true;
435
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};
440found = false;
441for aidx = acts(:)'
442 if aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts
443 continue;
444 end
445
446 if aidx <= length(lqn.callsof)
447 calls = lqn.callsof{aidx};
448 else
449 calls = [];
450 end
451 for cidx = calls(:)'
452 if cidx < 1 || cidx > lqn.ncalls || lqn.calltype(cidx) ~= CallType.SYNC
453 continue;
454 end
455 dst_eidx = lqn.callpair(cidx, 2);
456 dst_task = lqn.parent(dst_eidx);
457
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;
464 else
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;
470 break;
471 end
472 end
473 end
474
475 if reachesServer
476 itasks = traceToServerRec(lqn, dst_eidx, serverIdx, il_all, visited, itasks, false);
477 found = true;
478 end
479 end
480end
481
482if found && ~isHead
483 itasks(end+1) = ownerTask;
484 itasks = unique(itasks);
485end
486
487visited(e) = false;
488end
489
490%% Check if entry has phase-2 activities
491function result = hasPhase2Activities(lqn, eidx)
492result = false;
493if ~isfield(lqn, 'actphase')
494 return;
495end
496for aidx = lqn.actsof{eidx}(:)'
497 a = aidx - lqn.ashift;
498 if a > 0 && a <= lqn.nacts && lqn.actphase(a) > 1
499 result = true;
500 return;
501 end
502end
503end