<> <> <> <> <> <> DIRECTORY Basics USING [BITAND], BcdDefs USING [Base, BcdBase, Link, CTHandle, CTIndex, CTNull, EVNull, FPHandle, FPIndex, ModuleIndex, MTHandle, MTIndex, NameString, VarLimit, VersionID], BcdOps USING [ProcessConfigs, ProcessFramePacks, ProcessModules], FS USING [Close, GetInfo, Open, OpenFile, PagesForBytes, Read], IO USING [PutChar, PutF, PutF1, PutFR1, PutRope, STREAM], MB USING [Abort, AllocateFrames, BHandle, BIndex, EnumerateControlList, EnumerateFramePacks, EnumerateGlobalFrames, Error, Handle, ModuleInfo, ModuleInfoSequence, MT], MBLoaderOps USING [Bind, ConfigIndex, EnterModule, FileNotFound, FindFiles, FindCode, GetModule, GetNextGFI, GetVirtualLinks, InputLoadState, LinkFragLength, ModuleInfo, ProcessUnboundImports, RealLinks, RealLinksTable, SetGFTEntry, UpdateLoadState, VersionMismatch, VirtualLinks, WriteLoadState, WriteLinks], MBVM USING [AllocFile, Base, CopyWrite, FileSeg, HyperSpace, Links, LinksObject, Read, Write], PrincOps USING [AV, ControlLink, ControlModule, FrameCodeBase, GFTIndex, GlobalFrame, GlobalFrameHandle, NullControl, NullLink, UnboundLink], PrincOpsUtils USING [MakeFsi], VM USING [AddressForPageNumber, Allocate, Free, Interval, PageNumberForAddress]; MBLoaderCore: CEDAR PROGRAM IMPORTS Basics, BcdOps, FS, IO, MB, MBLoaderOps, MBVM, PrincOpsUtils, VM EXPORTS MB, MBLoaderOps = BEGIN CMTable: TYPE = RECORD [SEQUENCE length: NAT OF CMEntry]; CMEntry: TYPE = RECORD [bh: MB.BHandle, cm: PrincOps.ControlModule]; data: MB.Handle _ NIL; controlModules: REF CMTable _ 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]; IF loadee.bcd ~= NIL THEN TRUSTED { VM.Free[[page: VM.PageNumberForAddress[loadee.bcd], count: loadee.pages]]; loadee.bcd _ NIL; }; ENDLOOP; data _ NIL; }; Load: PUBLIC PROC = TRUSTED { typescript: IO.STREAM = data.typescript; loadmap: IO.STREAM = data.loadmap; versionError: BOOL _ FALSE; controlModules _ NEW[CMTable[data.inputBCDs.nBcds]]; data.nModules _ 0; FOR i: MB.BIndex IN [0..data.inputBCDs.nBcds) DO loadee: MB.BHandle = data.inputBCDs.bcds[i]; missingCodeFile: BOOL _ FALSE; config: MBLoaderOps.ConfigIndex; IF i # 0 THEN IO.PutRope[typescript, ", "]; IO.PutRope[typescript, loadee.name]; IF ~LoadBcd[loadee] THEN { IO.PutF1[typescript, "\N! Invalid file %g", [rope[loadee.name]]]; ERROR MB.Abort }; IO.PutF1[loadmap, "Global Frames for Modules in %g:\N", [rope[loadee.name]]]; IF ~data.germ THEN { loadee.bcdSeg _ MBVM.AllocFile[ file: loadee.name, fileBase: 0, base: MBVM.HyperSpace, pages: loadee.bcd.nPages]; loadee.bcdSeg.bIndex _ i; }; MBLoaderOps.FindFiles[loadee ! MBLoaderOps.FileNotFound => { IO.PutF1[typescript, "\N! Can't find file %g", [rope[name]]]; missingCodeFile _ TRUE; RESUME } ]; IF missingCodeFile THEN { IO.PutChar[typescript, '\N]; MB.Error["One or more missing code files"]; }; config _ LoadModules[loadee]; MBLoaderOps.FindCode[loadee]; controlModules[i] _ [bh: loadee, cm: AssignControlModules[loadee]]; IF config = FIRST[MBLoaderOps.ConfigIndex] THEN JustRelocateLinks[loadee] ELSE MBLoaderOps.Bind[loadee, config ! MBLoaderOps.VersionMismatch => { IO.PutF[typescript, "\N! Version mismatch: %g, [%g, %g]", [rope[interface]], [rope[ref1]], [rope[ref2]]]; versionError _ TRUE; RESUME } ]; IO.PutChar[loadmap, '\N]; ENDLOOP; IO.PutRope[typescript, "\N"]; data.header.controlList _ BuildTopLevelControlList[]; MBLoaderOps.WriteLoadState[]; data.nGFIs _ MBLoaderOps.GetNextGFI[reserve: 0]; IO.PutF[loadmap, "\NTotal of %d modules, %d GFIs\N", [cardinal[data.nModules]], [cardinal[data.nGFIs]]]; IF versionError THEN MB.Error["One or more version mismatches"]; MBLoaderOps.ProcessUnboundImports[]; IO.PutRope[typescript, "Finished loading.\N"]; }; LoadBcd: PROC [loadee: MB.BHandle] RETURNS [ok: BOOL _ TRUE] = { file: FS.OpenFile = FS.Open[name: loadee.name]; bcdPages: INT _ MIN[FS.PagesForBytes[FS.GetInfo[file].bytes], 10]; buffer: VM.Interval _ VM.Allocate[bcdPages]; bcd: BcdDefs.BcdBase; TRUSTED { bcd _ VM.AddressForPageNumber[buffer.page]; FS.Read[file: file, from: 0, nPages: buffer.count, to: bcd]; IF (ok _ bcd.versionIdent = BcdDefs.VersionID AND ~bcd.definitions) THEN { bcdPages _ bcd.nPages - (IF bcd.extended THEN bcd.rtPages.pages ELSE 0); SELECT bcdPages FROM < buffer.count => <> VM.Free[[buffer.page+bcdPages, buffer.count - bcdPages]]; > buffer.count => { <> VM.Free[buffer]; buffer _ VM.Allocate[bcdPages]; bcd _ VM.AddressForPageNumber[buffer.page]; FS.Read[file: file, from: 0, nPages: buffer.count, to: bcd]; }; ENDCASE; loadee.bcd _ bcd; loadee.pages _ bcdPages; } ELSE VM.Free[buffer]; }; file.Close[]; }; SetupModuleTable: PROC [loadee: MB.BHandle] RETURNS [ngfi: CARDINAL _ 0] = TRUSTED { <> CountEntries: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL _ FALSE] = TRUSTED { ngfi _ ngfi + mth.ngfi; }; SetEntriesForOneModule: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL _ FALSE] = TRUSTED { FOR i: CARDINAL IN [mth.gfi..mth.gfi+mth.ngfi) DO loadee.mt[i] _ MB.ModuleInfo[mth: mth, frame: NIL, code: NIL, codeLinks: NIL]; ENDLOOP; }; [] _ BcdOps.ProcessModules[loadee.bcd, CountEntries]; loadee.mt _ NEW[MB.ModuleInfoSequence[ngfi+1]]; -- +1 because index 0 isn't used loadee.mt[0] _ MB.ModuleInfo[mth: NIL, frame: NIL, code: NIL]; [] _ BcdOps.ProcessModules[loadee.bcd, SetEntriesForOneModule]; }; LoadModules: PROC [loadee: MB.BHandle] RETURNS [config: MBLoaderOps.ConfigIndex] = TRUSTED { bcd: BcdDefs.BcdBase = loadee.bcd; mtb: BcdDefs.Base = LOOPHOLE[bcd + bcd.mtOffset]; mt: MB.MT; ngfi: CARDINAL; gfiOffset: CARDINAL; framePtr: POINTER; frameSpace: CARDINAL _ 0; useFrameHeap: BOOL = (bcd.nModules = 1); inFrameHeap: BOOL; framesResident: BOOL _ FALSE; ProcessOneFramePack: PROC [fph: BcdDefs.FPHandle, fpi: BcdDefs.FPIndex] RETURNS [BOOL] = TRUSTED { 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[fph.modules[i]]; ENDLOOP; RETURN[FALSE] }; GetFrameSize: PROC [mth: BcdDefs.MTHandle] = TRUSTED { 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 [mti: BcdDefs.MTIndex] = TRUSTED { mth: BcdDefs.MTHandle = @mtb[mti]; 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); codeLinks: MBVM.Links _ NIL; nLinks: CARDINAL = MBLoaderOps.LinkFragLength[loadee, mth]; data.nModules _ data.nModules + 1; <> IF framelinks THEN framePtr _ (framePtr + nLinks) ELSE codeLinks _ NEW[MBVM.LinksObject[nLinks]]; frame _ LOOPHOLE[NextMultipleOfFour[LOOPHOLE[framePtr, CARDINAL]]]; framePtr _ (frame + mth.framesize); <> FOR i: CARDINAL IN [0..mth.ngfi) DO mt[mth.gfi + i].frame _ frame; mt[mth.gfi + i].codeLinks _ codeLinks; ENDLOOP; <> MBLoaderOps.SetGFTEntry[frame: frame, gfi: gfi, ngfi: mth.ngfi]; FOR i: CARDINAL IN [0..mth.ngfi) DO MBLoaderOps.EnterModule[ rgfi: (gfi + i), module: MBLoaderOps.ModuleInfo[ config: config, module: mth.gfi + i, resolved: nLinks = 0], mti: mti ]; ENDLOOP; <> gf.gfi _ gfi; gf.copied _ gf.shared _ gf.started _ gf.trapxfers _ FALSE; gf.codelinks _ NOT framelinks; gf.alloced _ inFrameHeap; gf.code.longbase _ NIL; -- to initialize both halves gf.code.out _ TRUE; gf.code.fill _ mth.code.offset; 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: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] =TRUSTED { IF mt[mth.gfi].frame = NIL THEN GetFrameSize[mth]; RETURN[FALSE] }; OtherFrameInit: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = TRUSTED { IF mt[mth.gfi].frame = NIL THEN FrameInit[mti]; RETURN[FALSE] }; ngfi _ SetupModuleTable[loadee]; mt _ loadee.mt; config _ MBLoaderOps.InputLoadState[]; <> 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]}; }; ResidentFramePack: PROC [f: BcdDefs.FPHandle] RETURNS [resident: BOOL] = TRUSTED { CheckOne: PROC [bh: MB.BHandle, fph: BcdDefs.FPHandle] RETURNS [BOOL] = CHECKED { RETURN[resident _ (f = fph)]}; MB.EnumerateFramePacks[resident, CheckOne]; }; AnyResidentGlobalFrames: PROC [loadee: MB.BHandle] RETURNS [resident: BOOL] = TRUSTED { CheckOne: PROC [bh: MB.BHandle, mth: BcdDefs.MTHandle] RETURNS [BOOL] = CHECKED { RETURN[resident _ (bh = loadee)]}; MB.EnumerateGlobalFrames[resident, CheckOne]; }; NextMultipleOfFour: PROC [x: CARDINAL] RETURNS [CARDINAL] = TRUSTED INLINE { RETURN [Basics.BITAND[x+3, 177774B]]; }; PrintLoadmapEntry: PROC [ bcd: BcdDefs.BcdBase, mth: BcdDefs.MTHandle, frame: POINTER, gfiOffset: CARDINAL] = TRUSTED { ssb: BcdDefs.NameString _ LOOPHOLE[bcd + bcd.ssOffset]; map: IO.STREAM _ data.loadmap; map.PutF1[" New: g = %06n ", [cardinal[LOOPHOLE[frame, CARDINAL]]]]; FOR i: CARDINAL IN [mth.name .. mth.name+ssb.size[mth.name]) DO map.PutChar[ssb.string.text[i]]; ENDLOOP; map.PutF1[" [%n]\N", [cardinal[LOOPHOLE[PrincOps.ControlLink[procedure[gfi: gfiOffset + mth.gfi, ep: 0, tag: FALSE]], CARDINAL] ]]]; }; <> CMMapItem: TYPE = RECORD [ cti: BcdDefs.CTIndex, cm: PrincOps.ControlModule, depth: CARDINAL ]; AssignControlModules: PROC [loadee: MB.BHandle] RETURNS [cm: PrincOps.ControlModule] = TRUSTED { bcd: BcdDefs.BcdBase = loadee.bcd; mt: MB.MT = loadee.mt; ctb: BcdDefs.Base _ LOOPHOLE[bcd + bcd.ctOffset]; mtb: BcdDefs.Base _ LOOPHOLE[bcd + bcd.mtOffset]; CMMap: TYPE = RECORD [ nConfigs: CARDINAL, configs: SEQUENCE length: CARDINAL --subconfig#-- OF CMMapItem]; cmMap: REF CMMap; maxDepth: CARDINAL _ 0; cti: BcdDefs.CTIndex; EnterControlInfoForConfig: PROC [ cth: BcdDefs.CTHandle, cti: BcdDefs.CTIndex] RETURNS [stop: BOOL] = TRUSTED { cm: PrincOps.ControlModule; depth: CARDINAL _ 0; -- depth of config in Bcd's config structure IF cth.nControls = 0 THEN cm _ PrincOps.NullControl ELSE { <> cm _ AllocateControlList[cth.nControls]; 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."]; 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.configs[cmMap.nConfigs] _ CMMapItem[cti: cti, cm: cm, depth: depth]; cmMap.nConfigs _ cmMap.nConfigs + 1; RETURN[FALSE] }; IF bcd.nModules = 1 THEN { frame: PrincOps.GlobalFrameHandle _ mt[1].frame; MBVM.Write[@frame.global[0], PrincOps.NullControl]; RETURN [[frame[frame]]] }; < (subconfig & control module info) map>> cmMap _ NEW[CMMap[bcd.nConfigs]]; cmMap.nConfigs _ 0; [] _ BcdOps.ProcessConfigs[bcd, EnterControlInfoForConfig]; FOR depth: CARDINAL DECREASING IN [0..maxDepth] DO FOR index: CARDINAL IN [0..cmMap.nConfigs) DO list, listHead, oldCm: PrincOps.ControlModule; SetModulesControl: PROC [ mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL] = TRUSTED { 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.configs[index].depth ~= depth OR (cm _ cmMap.configs[index].cm) = PrincOps.NullControl THEN LOOP; list _ cm; list.multiple _ FALSE; cti _ cmMap.configs[index].cti; <> 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; <> [] _ BcdOps.ProcessModules[bcd, SetModulesControl]; ENDLOOP; ENDLOOP; <> FOR index: CARDINAL IN [0..cmMap.nConfigs) DO parent: CARDINAL; list: PrincOps.ControlModule _ cmMap.configs[index].cm; IF list = PrincOps.NullControl THEN LOOP; list.multiple _ FALSE; IF (cti _ ctb[cmMap.configs[index].cti].config) = BcdDefs.CTNull THEN cm _ PrincOps.NullControl ELSE { FOR parent IN [0..cmMap.nConfigs) DO IF cmMap.configs[parent].cti = cti THEN EXIT; ENDLOOP; cm _ GetLink[cmMap.configs[parent].cm]; }; MBVM.Write[@list.list.frames[0], cm]; ENDLOOP; <> FOR i: CARDINAL IN [0..cmMap.nConfigs) DO IF ctb[cmMap.configs[i].cti].config = BcdDefs.CTNull THEN { cm _ GetLink[cmMap.configs[i].cm]; EXIT }; ENDLOOP; }; AllocFault: PUBLIC ERROR = CODE; Alloc: PUBLIC PROC [fsi: CARDINAL] RETURNS [p: POINTER] = TRUSTED { 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] }; AllocateControlList: PUBLIC PROC [nControls: CARDINAL] RETURNS [cm: PrincOps.ControlModule] = TRUSTED { fsi: CARDINAL = PrincOpsUtils.MakeFsi[words: (nControls + 1) + 1]; cm.list _ Alloc[fsi ! AllocFault => MB.Error[IO.PutFR1["Larger frame heap needed (fsi=%d) for control list", [cardinal[fsi]]]] ]; }; GetLink: PROC [ cm: PrincOps.ControlModule] RETURNS [--frame--PrincOps.ControlModule] = TRUSTED { 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] = TRUSTED { 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] = TRUSTED { bcd: BcdDefs.BcdBase = loadee.bcd; mt: MB.MT = loadee.mt; realLinks: MBLoaderOps.RealLinks = NEW[MBLoaderOps.RealLinksTable]; RelocateLinksForModule: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = TRUSTED { mi: BcdDefs.ModuleIndex = mth.gfi; frame: PrincOps.GlobalFrameHandle _ mt[mi].frame; resolved: BOOL _ TRUE; 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 [mi..mi+mth.ngfi) DO info: MBLoaderOps.ModuleInfo _ MBLoaderOps.GetModule[i]; info.resolved _ resolved; MBLoaderOps.EnterModule[i, info]; ENDLOOP; RETURN[FALSE] }; ConvertVariableLink: PROC [link: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = TRUSTED { mth: BcdDefs.MTHandle; frame: PrincOps.GlobalFrameHandle; mi: BcdDefs.ModuleIndex = link.gfi; evb: BcdDefs.Base = LOOPHOLE[bcd + bcd.evOffset]; vp: CARDINAL; [mth: mth, frame: frame] _ mt[mi]; IF mi >= bcd.firstdummy THEN RETURN[PrincOps.NullLink]; vp _ BcdDefs.VarLimit*(mi - 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[MBLoaderOps.ConfigIndex], loadee]; [] _ BcdOps.ProcessModules[bcd, RelocateLinksForModule]; }; ConvertLink: PROC [bl: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = INLINE { RETURN[LOOPHOLE[bl]]; }; BuildTopLevelControlList: PROC RETURNS [cm: PrincOps.ControlModule] = TRUSTED { nControls: CARDINAL _ 0; index: CARDINAL _ 1; CountControls: PROC [bh: MB.BHandle, cth: BcdDefs.CTHandle] RETURNS [BOOL _ FALSE] = TRUSTED { IF cth.nControls ~= 0 THEN nControls _ nControls + 1; }; FillInControls: PROC [bh: MB.BHandle, cth: BcdDefs.CTHandle] RETURNS [BOOL _ FALSE] = TRUSTED { IF cth.nControls ~= 0 THEN { FOR i: NAT IN [0..controlModules.length) DO IF controlModules[i].bh = bh THEN { MBVM.Write[@cm.list.frames[index], controlModules[i].cm]; index _ index + 1; EXIT }; ENDLOOP; }; }; [] _ MB.EnumerateControlList[CountControls]; IF nControls = 0 THEN RETURN[PrincOps.NullControl]; cm _ AllocateControlList[nControls]; MBVM.Write[@cm.list.nModules, nControls + 1]; MBVM.Write[@cm.list.frames[0], PrincOps.NullControl]; [] _ MB.EnumerateControlList[FillInControls]; cm.multiple _ TRUE; }; END.