MBLoaderCore.mesa
Last modified by Sandman on 6-Aug-81 15:41:04
Last modified by Lewis on 23-Sep-81 15:59:10
Last modified by Levin on April 13, 1983 4:36 pm
DIRECTORY
BcdDefs USING [
Base, Link, CTIndex, CTNull, EVNull, FPIndex, FPRecord, GFTIndex, MTIndex, VarLimit, VersionID],
BcdOps USING [
BcdBase, CTHandle, FPHandle, MTHandle, NameString, ProcessConfigs, ProcessFramePacks, ProcessModules],
Inline USING [BITAND],
LongString USING [AppendString],
MB USING [
Abort, AllocateFrames, BHandle, BIndex, EnumerateFramePacks, EnumerateGlobalFrames, Error, Handle, ModuleInfo, MT, Zero],
MBLoaderOps USING [
Bind, EnterModule, FileNotFound, FindFiles, FindCode, GetModule, GetNextGFI, GetVirtualLinks, InputLoadState, LinkFragLength, ProcessUnboundImports, SetGFTEntry, UpdateLoadState, VirtualLinks, WriteLoadState, WriteLinks],
MBOut USING [Char, CR, Decimal, Line, Number, NumberFormat, Octal, SP, Text],
MBStorage USING [Pages, FreePages, FreeWords, Words],
MBTTY USING [Handle, PutCR, PutLine, PutString],
MBVM USING [AllocFile, Base, CopyWrite, FileSeg, HyperSpace, Read, Write],
PilotLoadStateFormat USING [ConfigIndex, ModuleInfo],
PrincOps USING [
AV, ControlLink, ControlModule, FrameCodeBase, GFTIndex, GlobalFrame, GlobalFrameHandle, NullControl, NullLink, UnboundLink],
RuntimeInternal USING [MakeFsi],
Segments USING [
BaseFromSegment, DeleteSegment, FHandle, FileFromSegment, HardUp, MoveSegment, NewFile, NewSegment, PagesFromSegment, Read, SegmentAddress, SHandle, SwapIn, Unlock];
MBLoaderCore: PROGRAM
IMPORTS
BcdOps, Inline, MB, MBLoaderOps, MBOut, MBStorage, MBTTY, MBVM, RuntimeInternal, Segments, String: LongString
EXPORTS MB, MBLoaderOps =
BEGIN
OPEN MB;
data: MB.Handle ← NIL;
InitLoaderCore: PUBLIC PROC [h: MB.Handle] = {data ← h};
FinishLoaderCore: PUBLIC PROC = {
FOR i: MB.BIndex IN [0..data.inputBCDs.nBcds) DO
loadee: MB.BHandle = data.inputBCDs.bcds[i];
ReleaseModuleTable[loadee];
IF loadee.bcdSegment ~= NIL THEN {
Segments.Unlock[loadee.bcdSegment];
Segments.DeleteSegment[loadee.bcdSegment];
};
ENDLOOP;
data ← NIL;
};
Load: PUBLIC PROC = {
tty: MBTTY.Handle = data.ttyHandle;
data.nModules ← 0;
FOR i: MB.BIndex IN [0..data.inputBCDs.nBcds) DO
loadee: MB.BHandle = data.inputBCDs.bcds[i];
name: STRING ← [40];
missingCodeFile: BOOLFALSE;
config: PilotLoadStateFormat.ConfigIndex;
String.AppendString[name, loadee.name];
MBTTY.PutString[tty, "Loading "];
MBTTY.PutString[tty, name];
MBTTY.PutString[tty, "..."L];
IF (loadee.bcdSegment ← LoadBcd[Segments.NewFile[name]]) = NIL THEN {
MBTTY.PutCR[tty];
MBTTY.PutString[tty, "!Invalid file "L];
MBTTY.PutString[tty, name];
ERROR MB.Abort
};
MBOut.Text["Global Frames for Modules in "L];
MBOut.Text[name];
MBOut.Char[':]; MBOut.CR[];
loadee.bcd ← Segments.SegmentAddress[loadee.bcdSegment];
IF ~data.germ THEN {
loadee.bcdSeg ← MBVM.AllocFile[
file: Segments.FileFromSegment[loadee.bcdSegment],
fileBase: Segments.BaseFromSegment[loadee.bcdSegment], base: MBVM.HyperSpace,
pages: Segments.PagesFromSegment[loadee.bcdSegment]];
loadee.bcdSeg.segment ← loadee.bcdSegment;
loadee.bcdSeg.bIndex ← i;
};
MBLoaderOps.FindFiles[loadee
! MBLoaderOps.FileNotFound => {
MBTTY.PutCR[tty];
MBTTY.PutString[tty, "!Can't find file "L]; MBTTY.PutString[tty, name];
missingCodeFile ← TRUE;
RESUME
}
];
IF missingCodeFile THEN {MBTTY.PutCR[tty]; MB.Error["Missing code files"L]};
config ← LoadModules[loadee];
MBTTY.PutString[tty, "looking up code..."L];
MBLoaderOps.FindCode[loadee];
MBTTY.PutString[tty, "binding modules..."L];
[] ← AssignControlModules[loadee];
IF config = FIRST[PilotLoadStateFormat.ConfigIndex] THEN JustRelocateLinks[loadee]
ELSE MBLoaderOps.Bind[loadee, config];
MBOut.CR[];
MBTTY.PutLine[tty, "done"L];
ENDLOOP;
MBLoaderOps.WriteLoadState[];
data.nGFIs ← MBLoaderOps.GetNextGFI[reserve: 0];
MBOut.CR[]; MBOut.Text["Total of "L];
MBOut.Decimal[data.nModules]; MBOut.Text[" modules, "L];
MBOut.Decimal[data.nGFIs]; MBOut.Line[" GFIs"L];
MBLoaderOps.ProcessUnboundImports[];
MBTTY.PutLine[tty, "Finished loading."L];
};
LoadBcd: PROC [bcdfile: Segments.FHandle] RETURNS [bcdseg: Segments.SHandle] = {
b: BcdOps.BcdBase;
pages: CARDINAL;
bcdseg ← Segments.NewSegment[file: bcdfile, base: 1, pages: 1, access: Segments.Read];
Segments.SwapIn[seg: bcdseg, info: Segments.HardUp];
b ← Segments.SegmentAddress[bcdseg];
IF b.versionIdent # BcdDefs.VersionID OR b.definitions --OR ~b.spare1-- THEN {
Segments.Unlock[bcdseg];
Segments.DeleteSegment[bcdseg];
RETURN[NIL]
};
IF (pages ← b.nPages) > 1 THEN {
Segments.Unlock[bcdseg];
Segments.MoveSegment[bcdseg, 1, pages];
Segments.SwapIn[seg: bcdseg, info: Segments.HardUp];
};
};
SetupModuleTable: PROC [loadee: MB.BHandle] RETURNS [ngfi: CARDINAL ← 0] = {
There is some non-intuitive handling of the firstdummy field in the bcd header. If the bcd consists of a single module, it was probably generated by the Compiler. The Compiler sets the firstdummy field to an arbitrary value which is higher than the number of gfis needed to load the module. The ngfi field of the module table entry is the correct one to use. For a multi-module bcd (created by the Binder), bcd.firstdummy-1 is the number of gfis needed. The code below is intended to be the only place that this ugliness is known.
words: CARDINAL = loadee.bcd.firstdummy * SIZE[MB.ModuleInfo];
SetEntriesForOneModule: PROC [
mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL] = {
ngfi ← ngfi + mth.ngfi;
FOR i: CARDINAL IN [mth.gfi..mth.gfi+mth.ngfi) DO
loadee.mt[i] ← MB.ModuleInfo[mth: mth, frame: NIL, code: NIL];
ENDLOOP;
RETURN[FALSE]
};
The following is a hint, which is guaranteed big enough. (See comment above)
loadee.mt ← DESCRIPTOR[MBStorage.Words[words], loadee.bcd.firstdummy];
MB.Zero[BASE[loadee.mt], words];
loadee.mt[0] ← MB.ModuleInfo[mth: NIL, frame: NIL, code: NIL];
[] ← BcdOps.ProcessModules[loadee.bcd, SetEntriesForOneModule];
loadee.mt ← DESCRIPTOR[BASE[loadee.mt], ngfi+1];
};
ReleaseModuleTable: PUBLIC PROC [loadee: MB.BHandle] = {
IF BASE[loadee.mt] # NIL THEN {
MBStorage.FreeWords[BASE[loadee.mt]];
loadee.mt ← DESCRIPTOR[NIL,0];
};
};
LoadModules: PROC [loadee: MB.BHandle] RETURNS [config: PilotLoadStateFormat.ConfigIndex] =
BEGIN
bcd: BcdOps.BcdBase = loadee.bcd;
mt: MT;
ngfi: CARDINAL;
gfiOffset: CARDINAL;
framePtr: POINTER;
frameSpace: CARDINAL ← 0;
useFrameHeap: BOOL = (bcd.nModules = 1);
inFrameHeap: BOOL;
framesResident: BOOLFALSE;
ProcessOneFramePack: PROC [fph: BcdOps.FPHandle, fpi: BcdDefs.FPIndex]
RETURNS [BOOL] = {
mtb: BcdDefs.Base = LOOPHOLE[bcd + bcd.mtOffset];
frameSpace ← 0;
framesResident ← FALSE;
FOR i: CARDINAL IN [0..fph.length) DO
GetFrameSize[@mtb[fph.modules[i]]];
ENDLOOP;
[framePtr, inFrameHeap] ← MB.AllocateFrames[
size: NextMultipleOfFour[frameSpace],
single: useFrameHeap,
resident: framesResident OR ResidentFramePack[fph] -- specified in bootmesa file--];
FOR i: CARDINAL IN [0..fph.length) DO
FrameInit[@mtb[fph.modules[i]]];
ENDLOOP;
RETURN[FALSE]
};
GetFrameSize: PROC [mth: BcdOps.MTHandle] = {
IF ~(mth.linkLoc = code AND mth.code.linkspace) THEN -- frame links
frameSpace ← frameSpace + MBLoaderOps.LinkFragLength[loadee, mth];
frameSpace ← NextMultipleOfFour[frameSpace] + mth.framesize;
framesResident ← framesResident OR mth.residentFrame;
};
FrameInit: PROC [mth: BcdOps.MTHandle] = {
frame: PrincOps.GlobalFrameHandle;
gf: PrincOps.GlobalFrame;
gfi: PrincOps.GFTIndex = gfiOffset + mth.gfi; -- module's biased gfi
framelinks: BOOL = ~(mth.linkLoc = code AND mth.code.linkspace);
nLinks: CARDINAL = MBLoaderOps.LinkFragLength[loadee, mth];
data.nModules ← data.nModules + 1;
allocate global frame
IF framelinks THEN framePtr ← (framePtr + nLinks);
frame ← NextMultipleOfFour[framePtr];
framePtr ← (frame + mth.framesize);
record global frame handle in module table (mt)
FOR i: CARDINAL IN [0..mth.ngfi) DO
mt[mth.gfi + i].frame ← frame;
ENDLOOP;
enter into GFT, and loadstate if not making germ
MBLoaderOps.SetGFTEntry[frame: frame, gfi: gfi, ngfi: mth.ngfi];
FOR i: CARDINAL IN [0..mth.ngfi) DO
MBLoaderOps.EnterModule[
rgfi: (gfi + i),
module: PilotLoadStateFormat.ModuleInfo[
config: config, gfi: (mth.gfi + i), -- local gfi
resolved: (nLinks = 0)]
];
ENDLOOP;
initialize global frame
gf ← PrincOps.GlobalFrame[
gfi: gfi, copied: FALSE, alloced: inFrameHeap, shared: FALSE,
started: FALSE, trapxfers: FALSE, codelinks: ~framelinks, global: ,
code: PrincOps.FrameCodeBase[
offset[offset: mth.code.offset, highHalf: NIL]]
];
gf.code.out ← TRUE;
MBVM.CopyWrite[from: @gf, to: frame, nwords: SIZE[PrincOps.GlobalFrame]];
MBVM.Write[@frame.global[0], PrincOps.NullControl]; -- no control module
PrintLoadmapEntry[bcd: bcd, mth: mth, frame: frame, gfiOffset: gfiOffset];
};
OtherFrameSizes: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = {
IF mt[mth.gfi].frame = NIL THEN GetFrameSize[mth];
RETURN[FALSE]
};
OtherFrameInit: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = {
IF mt[mth.gfi].frame = NIL THEN FrameInit[mth];
RETURN[FALSE]
};
ngfi ← SetupModuleTable[loadee];
mt ← loadee.mt;
config ← MBLoaderOps.InputLoadState[];
gfi 0 in each input BCD is not used. Thus, gfiOffset is set one less than the first gfi actually allocated to us, since all subsequent indexing uses the 0-origin gfi's in the input BCD.
loadee.gfiOffset ← gfiOffset ← MBLoaderOps.GetNextGFI[reserve: ngfi] - 1;
[] ← BcdOps.ProcessFramePacks[bcd, ProcessOneFramePack];
frameSpace ← 0; framesResident ← FALSE;
[] ← BcdOps.ProcessModules[bcd, OtherFrameSizes];
IF frameSpace # 0 THEN { -- there are frames not in frame packs
[framePtr, inFrameHeap] ← MB.AllocateFrames[
size: NextMultipleOfFour[frameSpace],
single: useFrameHeap,
resident: framesResident OR AnyResidentGlobalFrames[loadee]];
[] ← BcdOps.ProcessModules[bcd, OtherFrameInit]};
END;
ResidentFramePack: PROC [f: BcdOps.FPHandle] RETURNS [resident: BOOL] = {
CheckOne: PROC [bh: MB.BHandle, fph: BcdOps.FPHandle] RETURNS [BOOL] = {
RETURN[resident ← (f = fph)]};
MB.EnumerateFramePacks[resident, CheckOne];
};
AnyResidentGlobalFrames: PROC [loadee: MB.BHandle] RETURNS [resident: BOOL] = {
CheckOne: PROC [bh: MB.BHandle, mth: BcdOps.MTHandle] RETURNS [BOOL] = {
RETURN[resident ← (bh = loadee)]};
MB.EnumerateGlobalFrames[resident, CheckOne];
};
NextMultipleOfFour: PROC [x: UNSPECIFIED] RETURNS [UNSPECIFIED] = INLINE {
RETURN[x + Inline.BITAND[-LOOPHOLE[x, INTEGER], 3B]]};
PrintLoadmapEntry: PROC [
bcd: BcdOps.BcdBase,
mth: BcdOps.MTHandle, frame: POINTER, gfiOffset: CARDINAL] = {
ssb: BcdOps.NameString ← LOOPHOLE[bcd + bcd.ssOffset];
MBOut.Text[" New: g = "L];
MBOut.Number[frame, MBOut.NumberFormat[8,FALSE,TRUE,6]];
MBOut.SP[];
FOR i: CARDINAL IN [mth.name .. mth.name+ssb.size[mth.name]) DO
MBOut.Char[ssb.string.text[i]];
ENDLOOP;
MBOut.Text[" ["L];
MBOut.Octal[PrincOps.ControlLink[
procedure[gfi: (gfiOffset + mth.gfi), ep: 0, tag: FALSE]]];
MBOut.Char[']];
MBOut.CR[];
};
control module information for each (sub)config in bcd
CMMapItem: TYPE = RECORD [
cti: BcdDefs.CTIndex,
cm: PrincOps.ControlModule,
depth: CARDINAL
];
AssignControlModules: PROC [loadee: MB.BHandle] RETURNS [cm: PrincOps.ControlModule] = {
bcd: BcdOps.BcdBase = loadee.bcd;
mt: MB.MT = loadee.mt;
ctb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.ctOffset];
mtb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.mtOffset];
cmMap: LONG POINTER TO ARRAY [0..0) --subconfig#-- OF CMMapItem;
lastConfig, maxDepth: CARDINAL ← 0;
cti: BcdDefs.CTIndex;
EnterControlInfoForConfig: PROC [
cth: BcdOps.CTHandle, cti: BcdDefs.CTIndex] RETURNS [stop: BOOL] = {
cm: PrincOps.ControlModule;
depth: CARDINAL ← 0; -- depth of config in Bcd's config structure
IF cth.nControls = 0 THEN cm ← PrincOps.NullControl
ELSE {
list.frames[0] is bkwds pointer to cm for enclosing config (st. first)
cm.list ← Alloc[RuntimeInternal.MakeFsi[words: (cth.nControls + 1) + 1]
! AllocFault => MB.Error["Larger frame heap needed (for control list)"L]
];
MBVM.Write[@cm.list.nModules, (cth.nControls + 1)];
FOR i: CARDINAL IN [0..cth.nControls) DO
WITH cItem: cth.controls[i] SELECT FROM
module => MBVM.Write[@cm.list.frames[i+1], mt[mtb[cItem.mti].gfi].frame];
config => MB.Error["Configurations in control lists aren't supported yet."L];
ENDCASE;
ENDLOOP;
cm.multiple ← TRUE;
FOR c: BcdDefs.CTIndex ← ctb[cti].config, ctb[c].config
UNTIL c = BcdDefs.CTNull DO
depth ← depth + 1;
ENDLOOP;
};
maxDepth ← MAX[maxDepth, depth];
cmMap[lastConfig] ← CMMapItem[cti: cti, cm: cm, depth: depth];
lastConfig ← lastConfig + 1;
RETURN[FALSE]
};
IF bcd.nModules = 1 THEN {
frame: PrincOps.GlobalFrameHandle ← mt[1].frame;
MBVM.Write[@frame.global[0], PrincOps.NullControl];
RETURN
};
create subconfig# -> (subconfig & control module info) map
cmMap ← MBStorage.Words[bcd.nConfigs * SIZE[CMMapItem]];
lastConfig ← 0;
[] ← BcdOps.ProcessConfigs[bcd, EnterControlInfoForConfig];
FOR depth: CARDINAL DECREASING IN [0..maxDepth] DO
FOR index: CARDINAL IN [0..lastConfig) DO
list, listHead, oldCm: PrincOps.ControlModule;
SetModulesControl: PROC [
mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL] = {
frame: PrincOps.GlobalFrameHandle ← mt[mth.gfi].frame;
IF mth.config # cti THEN RETURN[FALSE];
IF MBVM.Read[@frame.global[0]] = PrincOps.NullControl THEN
MBVM.Write[@frame.global[0], GetLink[cm]];
RETURN[FALSE]
};
IF cmMap[index].depth # depth OR (cm ← cmMap[index].cm) = PrincOps.NullControl THEN
LOOP;
list ← cm;
list.multiple ← FALSE;
cti ← cmMap[index].cti;
list.frames[1]'s global[0] has ControlModule (possibly list) for config
listHead ← SetLink[cm: cm, frame: MBVM.Read[@list.list.frames[1]]];
MBVM.Write[@list.list.frames[1], listHead]; -- restore old value
FOR i: CARDINAL IN [2..ctb[cti].nControls+1) DO
oldCm ← SetLink[cm: GetLink[listHead], frame: MBVM.Read[@list.list.frames[i]]];
MBVM.Write[@list.list.frames[i], oldCm];
ENDLOOP;
for each module, set global[0] to the actual cm frame for config
[] ← BcdOps.ProcessModules[bcd, SetModulesControl];
ENDLOOP;
ENDLOOP;
for each config, set backwards pointer to cm frame for the enclosing config
FOR index: CARDINAL IN [0..lastConfig) DO
parent: CARDINAL;
list: PrincOps.ControlModule ← cmMap[index].cm;
IF list = PrincOps.NullControl THEN LOOP;
list.multiple ← FALSE;
IF (cti ← ctb[cmMap[index].cti].config) = BcdDefs.CTNull THEN
cm ← PrincOps.NullControl
ELSE {
FOR parent IN [0..lastConfig) DO IF cmMap[parent].cti = cti THEN EXIT ENDLOOP;
cm ← GetLink[cmMap[parent].cm];
};
MBVM.Write[@list.list.frames[0], cm];
ENDLOOP;
set "cm" to the actual cm frame for the outermost config
FOR i: CARDINAL IN [0..lastConfig) DO
IF ctb[cmMap[i].cti].config = BcdDefs.CTNull THEN {
cm ← GetLink[cmMap[i].cm]; EXIT};
ENDLOOP;
MBStorage.FreeWords[cmMap];
};
AllocFault: PUBLIC ERROR = CODE;
Alloc: PUBLIC PROC [fsi: CARDINAL] RETURNS [p: POINTER] = {
DO
p ← MBVM.Read[PrincOps.AV + fsi];
SELECT LOOPHOLE[p, CARDINAL] MOD 4 FROM
0 => EXIT; -- a free frame
1, 3 => ERROR AllocFault;
2 => fsi ← LOOPHOLE[p, CARDINAL]/4; -- use an fsi for larger frames
ENDCASE;
ENDLOOP;
MBVM.Write[PrincOps.AV + fsi, MBVM.Read[p]];
RETURN[p]
};
GetLink: PROC [
cm: PrincOps.ControlModule] RETURNS [--frame--PrincOps.ControlModule] = {
list: PrincOps.ControlModule;
DO -- search up backward pointers for the actual frame to start
IF ~cm.multiple THEN RETURN[cm];
list ← cm;
list.multiple ← FALSE;
cm ← MBVM.Read[@list.list.frames[1]];
ENDLOOP;
};
SetLink: PROC [cm: PrincOps.ControlModule, frame: PrincOps.GlobalFrameHandle]
RETURNS [PrincOps.ControlModule] = {
old: PrincOps.ControlModule = MBVM.Read[@frame.global[0]];
MBVM.Write[@frame.global[0], cm];
RETURN[IF old # PrincOps.NullControl THEN old ELSE [frame[frame]]]
};
JustRelocateLinks: PROC [loadee: MB.BHandle] = {
bcd: BcdOps.BcdBase = loadee.bcd;
mt: MB.MT = loadee.mt;
realLinks: LONG POINTER TO ARRAY [0..0) OF PrincOps.ControlLink = MBStorage.Pages[1];
RelocateLinksForModule: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
RETURNS [BOOL] = {
gfi: BcdDefs.GFTIndex = mth.gfi;
frame: PrincOps.GlobalFrameHandle ← mt[gfi].frame;
resolved: BOOLTRUE;
virtualLinks: MBLoaderOps.VirtualLinks ← MBLoaderOps.GetVirtualLinks[loadee, mth];
IF LENGTH[virtualLinks] # 0 THEN { -- relocate external links
FOR i: CARDINAL IN [0..LENGTH[virtualLinks]) DO
link: BcdDefs.Link ← virtualLinks[i];
SELECT link.vtag FROM
proc1, proc0 =>
IF link.gfi >= bcd.firstdummy THEN
{resolved ← FALSE; realLinks[i] ← PrincOps.UnboundLink}
ELSE realLinks[i] ← ConvertLink[link];
var =>
IF link.gfi >= bcd.firstdummy THEN
{resolved ← FALSE; realLinks[i] ← PrincOps.NullLink}
ELSE realLinks[i] ← ConvertVariableLink[link];
ENDCASE => {resolved ← FALSE; realLinks[i] ← PrincOps.NullLink};
ENDLOOP;
MBLoaderOps.WriteLinks[loadee: loadee, mth: mth, links: realLinks];
};
FOR i: CARDINAL IN [gfi..gfi+mth.ngfi) DO
info: PilotLoadStateFormat.ModuleInfo ← MBLoaderOps.GetModule[i];
info.resolved ← resolved;
MBLoaderOps.EnterModule[i, info];
ENDLOOP;
RETURN[FALSE]
};
ConvertVariableLink: PROC [link: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = {
mth: BcdOps.MTHandle;
frame: PrincOps.GlobalFrameHandle;
gfi: BcdDefs.GFTIndex = link.gfi;
evb: BcdDefs.Base = LOOPHOLE[bcd + bcd.evOffset];
vp: CARDINAL;
[mth: mth, frame: frame] ← mt[gfi];
IF gfi >= bcd.firstdummy THEN RETURN[PrincOps.NullLink];
vp ← BcdDefs.VarLimit*(gfi - mth.gfi) + link.var;
IF vp = 0 THEN RETURN[LOOPHOLE[frame]];
IF mth.variables = BcdDefs.EVNull THEN RETURN[PrincOps.NullLink]
ELSE RETURN[LOOPHOLE[frame + evb[mth.variables].offsets[vp]]]
};
MBLoaderOps.UpdateLoadState[FIRST[PilotLoadStateFormat.ConfigIndex], loadee];
[] ← BcdOps.ProcessModules[bcd, RelocateLinksForModule];
MBStorage.FreePages[realLinks];
};
ConvertLink: PROC [bl: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = INLINE {
RETURN[LOOPHOLE[bl]]};
END.