-- CTLoadImpl.Mesa, last edit March 14, 1983 2:09 pm
-- Mesa 7.0/ Pilot 6.0
-- procedures to load and start modules in the compiler tool

-- links:
--	IF gfi > firstdummy, then gfi is index into Import table 
--		and ep is index into the export record pared with that import
--		binding is simply to copy control link in the export record
--		into this link
-- 	IF gfi < firstdummy, then gfi in this link is an index into the config's
--		moduletable.  Do not alter the ep

DIRECTORY
  BcdDefs: TYPE USING [Base, FTIndex, FTSelf,
   GFTIndex, Link, MTIndex, NameRecord, NullLink,
   UnboundLink, VersionID],
  BcdOps: TYPE USING [BcdBase, EXPHandle, FTHandle, MTHandle, NameString,
   ProcessModules],
  CedarLinkerOps: TYPE USING[FindVariableLink],
  CTLoad: TYPE USING [DummyMapSeq, DummyMapSeqRecord, ImpExpSeq, ImpExpSeqRecord,
   InterfaceSeq, InterfaceSeqRecord, LoadInfoSeq, LoadInfoSeqRecord, ReplaceResult],
  Directory: TYPE USING [UpdateDates],
  Environment: TYPE USING [PageCount, PageNumber],
  File: TYPE USING [Capability, PageCount, PageNumber, read, write],
  Frame: TYPE USING [GetReturnLink],
  Inline: TYPE USING [BITAND],
  IO: TYPE USING[card, Handle, PutF, rope],
  LoaderPrivate: TYPE USING[AssignCodeToFrames, AssignControlModules, CreateGlobalFrames,
  	FindMappedSpace, NextMultipleOfFour],
  PilotLoaderOps: TYPE USING[DestroyMap,  
  	InitializeMap, IthLink, LinkSegmentLength],
  PilotLoadStateOps: TYPE USING[ConfigIndex],
  PrincOps: TYPE USING [ControlLink, ControlModule, Frame, GFTIndex,
   GlobalFrameHandle, NullLink, StateVector, UnboundLink],
  PrincOpsRuntime: TYPE USING [GetFrame, GFT],
  Rope: TYPE USING[Cat, Flatten, FromChar, ROPE, Text],
  RTOS: TYPE USING [CheckForModuleReplacement],
  Runtime: TYPE USING [ValidateGlobalFrame],
  RuntimeInternal: TYPE USING [Codebase],
  SDDefs: TYPE USING [SD, sStart, sSwapTrap],
  Space: TYPE USING [Create, Delete, Error, 
  	GetHandle, GetWindow, Handle, 
  	LongPointer, MakeReadOnly, MakeWritable, Map, 
	PageFromLongPointer, virtualMemory],
  TimeStamp: TYPE USING [Stamp],
  Trap: TYPE USING [ReadOTP];

CTLoadImpl: MONITOR 
IMPORTS BcdOps, CedarLinkerOps, Directory, Frame, Inline, IO, LoaderPrivate, PilotLoaderOps,
	PrincOpsRuntime, Rope, RTOS, Runtime, RuntimeInternal, Space, Trap
EXPORTS CTLoad
SHARES File = {

-- MDS Usage!

waitCodeTrapCV: CONDITION;
-- link space data
links: LONG POINTER TO ARRAY[0 .. 0) OF PrincOps.ControlLink ← NIL;
writeable: BOOL ← FALSE;
long: BOOL ← FALSE;
-- end of MDS usage

LoadGlobalFrames: PUBLIC PROC[cap: File.Capability, config: PilotLoadStateOps.ConfigIndex,
	oldConfigGfi: PrincOps.GFTIndex, out: IO.Handle] 
	RETURNS[loadinfoseq: CTLoad.LoadInfoSeq, newConfigGfi: PrincOps.GFTIndex] = {
	bcdbase: BcdOps.BcdBase;
	mod: CARDINAL ← 0;
	dummymapseq: CTLoad.DummyMapSeq;
	
		-- mod, dummymapseq, map is passed in
		ForEachModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
	 		RETURNS[stop: BOOL] = {
			gfi: CARDINAL;
			frame: PrincOps.GlobalFrameHandle;
			stop ← FALSE;
			frame ← PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[loadinfoseq.map[mth.gfi]]];
			loadinfoseq[mod] ← [frame: frame, framesize: mth.framesize, ngfi: mth.ngfi];
			newConfigGfi ← newConfigGfi + mth.ngfi;
			-- now store in cgfi to rgfi map
			gfi ← loadinfoseq.map[mth.gfi];
			FOR i: CARDINAL IN [0 .. mth.ngfi) DO
				dummymapseq[mth.gfi + i] ← [ind: gfi, whichone: i];
				ENDLOOP;
			dummymapseq.size ← dummymapseq.size + mth.ngfi;
			mod ← mod + 1;
			out.PutF["%s: gfi = %bB, gfh = %bB, framesize = %d, ", 
				IO.rope[NSToRope[bcdbase, mth.name]], IO.card[gfi],
				IO.card[LOOPHOLE[frame, CARDINAL]], IO.card[mth.framesize]];
			out.PutF["#%slinks = %d\n", 
				IO.rope[IF frame.codelinks THEN "code" ELSE "frame"],
				IO.card[PilotLoaderOps.LinkSegmentLength[mth, bcdbase]]];
			};
			
	{
	cap ← Directory.UpdateDates[cap, File.read];
	[bcd: bcdbase] ← LoadUpBcd[cap, 1];
	loadinfoseq ← AllocateLoadInfoSeq[bcdbase];
	loadinfoseq.bcdbase ← bcdbase;
	loadinfoseq.size ← 0;
	loadinfoseq.configGfi ← oldConfigGfi;
	newConfigGfi ← oldConfigGfi;
	dummymapseq ← loadinfoseq.dummymapseq;
	dummymapseq[0] ← [0, 0];
	dummymapseq.size ← 1;	-- dummy module indices start at 1, not 0
	loadinfoseq.map ← PilotLoaderOps.InitializeMap[bcdbase];
	-- map is filled in by CreateGlobalFrames
	loadinfoseq.frameList ← LoaderPrivate.CreateGlobalFrames[bcdbase, loadinfoseq.map,
			config, FALSE];
	[] ← BcdOps.ProcessModules[bcdbase, ForEachModule];
	LoaderPrivate.AssignCodeToFrames[bcdbase, cap, 1, loadinfoseq.map];
	SetLinksToNull[bcdbase, loadinfoseq];
	loadinfoseq.cm ← LoaderPrivate.AssignControlModules[bcdbase, loadinfoseq.map];
	loadinfoseq.size ← mod;
	}};
	
WaitForBroadcast: ENTRY PROC[frame: PrincOps.GlobalFrameHandle] = {
WHILE frame.code.out DO
	WAIT waitCodeTrapCV;
	ENDLOOP;
};

-- can only be called for modules (not configs)
LoadIncremental: PUBLIC ENTRY PROC[bcdcap: File.Capability, 
	loadinfoseq: CTLoad.LoadInfoSeq, window: IO.Handle] 
	RETURNS[replaceResult: CTLoad.ReplaceResult] = {
savemodellercode: PROC ← NIL;
codetrapframe: PrincOps.GlobalFrameHandle ← NIL;
{
ENABLE UNWIND => IF savemodellercode ~= NIL THEN
			SDDefs.SD[SDDefs.sSwapTrap] ← savemodellercode;
	
	ModellerCodeTrap: PROC = { 
    start: PROC[PrincOps.ControlModule];
    dest: PrincOps.ControlLink;
    state: PrincOps.StateVector;
    frame: PrincOps.GlobalFrameHandle;
    state ← STATE;
    dest ← Trap.ReadOTP[];
    state.dest ← Frame.GetReturnLink[];
    DO
      IF dest.proc THEN {
      	frame ← PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[dest.gfi]]; 
	EXIT}
      ELSE
        IF dest.indirect THEN dest ← dest.link↑
        ELSE {frame ← dest.frame.accesslink; EXIT}; -- frame

      ENDLOOP;
    IF frame = codetrapframe THEN {
    	-- this halts outside process until my procedure is finished
	WaitForBroadcast[frame];
	RETURN;
    	};
    IF ~frame.started THEN {
    	start ← LOOPHOLE[SDDefs.SD[SDDefs.sStart]];
	start[[frame[frame]]];
    	};
    frame.code.out ← FALSE;
    RETURN WITH state
    };

n: CARDINAL;
mname: Rope.Text;
gfi: CARDINAL;
bcdbase: BcdOps.BcdBase;
mth: BcdOps.MTHandle;
frame: PrincOps.GlobalFrameHandle ← loadinfoseq[0].frame;

replaceResult ← ok;
IF loadinfoseq.size ~= 1 THEN RETURN[configNotReplaceable];
[bcdbase] ← LoadUpBcd[bcdcap, 1];
loadinfoseq.bcdbase ← bcdbase;
loadinfoseq.cm ← LOOPHOLE[frame];
-- recompute these since bcdbase.firstdummy, bcdbase.nDummies, and bcdbase.nImports
-- may have changed
ReSizeMaps[loadinfoseq];
mth ← @LOOPHOLE[bcdbase + bcdbase.mtOffset, BcdDefs.Base][FIRST[BcdDefs.MTIndex]];
-- checking
IF LOOPHOLE[frame + mth.framesize, CARDINAL] > 
	LOOPHOLE[LoaderPrivate.NextMultipleOfFour[frame + loadinfoseq[0].framesize], CARDINAL] THEN 
		RETURN[frameTooBig];
IF mth.ngfi > loadinfoseq[0].ngfi THEN RETURN[ngfiTooBig];

-- now think of monitor:
-- set lock by setting code trap, then call check procedure
-- then do replacement, then release lock
-- this is to avoid the case where some local frames
-- are created after the call on RTOS.CheckForModuleReplacemeent
-- but before I swap the code
codetrapframe ← frame;
savemodellercode ← SDDefs.SD[SDDefs.sSwapTrap];
SDDefs.SD[SDDefs.sSwapTrap] ← ModellerCodeTrap;
frame.code.out ← TRUE;	-- force code trap
IF NOT RTOS.CheckForModuleReplacement[frame] THEN {
	SDDefs.SD[SDDefs.sSwapTrap] ← savemodellercode;
	RETURN[checkForMRFailed];
	};
PilotLoaderOps.DestroyMap[loadinfoseq.map];		-- old map
loadinfoseq.map ← PilotLoaderOps.InitializeMap[bcdbase];
-- the dummy bcd #'s start at 1
FOR i: CARDINAL IN [0 .. bcdbase.firstdummy) DO
	loadinfoseq.map[i] ← loadinfoseq.dummymapseq[i].ind;
	ENDLOOP;
LoaderPrivate.AssignCodeToFrames[bcdbase, bcdcap, 1, loadinfoseq.map];
-- havinge set the code base, we can now release the lock
frame.code.out ← FALSE;	
BROADCAST waitCodeTrapCV;
SDDefs.SD[SDDefs.sSwapTrap] ← savemodellercode;
savemodellercode ← NIL;

SetLinksToNull[bcdbase, loadinfoseq];
mname ← NSToRope[bcdbase, mth.name];
n ← mth.framesize;
gfi ← frame.gfi;
window.PutF["%s: gfi = %bB, gfh = %bB, framesize = %d, ", 
	IO.rope[mname], IO.card[gfi], IO.card[LOOPHOLE[frame, CARDINAL]], IO.card[n]];
n ← PilotLoaderOps.LinkSegmentLength[mth, bcdbase];
window.PutF["#%slinks = %d\n", 
	IO.rope[IF frame.codelinks THEN "code" ELSE "frame"], IO.card[n]];
RETURN[ok];
}};

-- old contents are preserved
ReSizeMaps: PROC[loadinfoseq: CTLoad.LoadInfoSeq] =
	{
	olddummymapseq: CTLoad.DummyMapSeq ← loadinfoseq.dummymapseq;
	oldimpexpseq: CTLoad.ImpExpSeq ← loadinfoseq.impexpseq;
	minSize: CARDINAL;
	
	loadinfoseq.dummymapseq ← NEW[CTLoad.DummyMapSeqRecord[
					loadinfoseq.bcdbase.firstdummy +loadinfoseq.bcdbase.nDummies]];
	loadinfoseq.impexpseq ← NEW[
		CTLoad.ImpExpSeqRecord[loadinfoseq.bcdbase.nImports]];
	minSize ← MIN[olddummymapseq.size, loadinfoseq.dummymapseq.maxsize];
	FOR i: CARDINAL IN [0 .. minSize) DO
		loadinfoseq.dummymapseq[i] ← olddummymapseq[i];
		ENDLOOP;
	loadinfoseq.dummymapseq.size ← minSize;
	minSize ← MIN[oldimpexpseq.size, loadinfoseq.impexpseq.maxsize];
	FOR i: CARDINAL IN [0 .. minSize) DO
		loadinfoseq.impexpseq[i] ← oldimpexpseq[i];
		ENDLOOP;
	loadinfoseq.impexpseq.size ← minSize;
	FREE[@olddummymapseq];
	FREE[@oldimpexpseq];
	};

SetLinksToNull: PROC[bcdbase: BcdOps.BcdBase, loadinfoseq: CTLoad.LoadInfoSeq] = {

	ForEachModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
	 	RETURNS[stop: BOOL] = {
	-- set all the links to null
	frame: PrincOps.GlobalFrameHandle;
	stop ← FALSE;
	frame ← PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[
		loadinfoseq.dummymapseq[mth.gfi].ind]];
	Runtime.ValidateGlobalFrame[frame];
	[] ← OpenLinkSpace[frame, mth, bcdbase];
	FOR i: CARDINAL IN [0..PilotLoaderOps.LinkSegmentLength[mth, bcdbase]) DO
		WriteLink[
	    		offset: i,
	    		link: SELECT PilotLoaderOps.IthLink[mth, i, bcdbase].vtag FROM
	      			var, type => PrincOps.NullLink, 
	      			ENDCASE => PrincOps.UnboundLink];
		ENDLOOP;
	CloseLinkSpace[frame];
	};
	
[] ← BcdOps.ProcessModules[bcdbase, ForEachModule];
loadinfoseq.linksresolved ← FALSE;
};

BuildInterface: PUBLIC PROC[loadinfoseq: CTLoad.LoadInfoSeq, 
	eth: BcdOps.EXPHandle] RETURNS[interfaceseq: CTLoad.InterfaceSeq] = {
clink: PrincOps.ControlLink;
cgfi: PrincOps.GFTIndex;	-- dummy
bcdbase: BcdOps.BcdBase;
name: Rope.Text;
fth: BcdOps.FTHandle;
IF eth.size = 0 THEN RETURN[NIL];
bcdbase ← loadinfoseq.bcdbase;
name ← NSToRope[bcdbase, eth.name];
interfaceseq ← AllocateInterfaceSeq[name, eth.size];
interfaceseq.resolved ← TRUE;
FOR i: CARDINAL IN [0 .. eth.size) DO
	SELECT eth.links[i].vtag FROM
	var => {
		[link: clink] ← CedarLinkerOps.FindVariableLink[bcd: loadinfoseq.bcdbase, 
			mthLink: eth.links[i], rgfi: loadinfoseq.dummymapseq[eth.links[i].vgfi].ind];
		interfaceseq[i] ← [clink: clink, blink: BcdDefs.NullLink]
		};
	proc0, proc1 => {
		realgfi: PrincOps.GFTIndex;
		realgfi ← loadinfoseq.dummymapseq[eth.links[i].gfi].ind;
		[clink, cgfi] ← ConvertDummyLinkToControlLink[eth.links[i], 
			realgfi, bcdbase, loadinfoseq];
		interfaceseq[i] ← [clink: clink, 
			blink: [procedure[gfi: cgfi, ep: eth.links[i].ep, tag: eth.links[i].tag]]];
		};
	type => 	-- means no checking for exported type mismatches!!!
		interfaceseq[i] ← [clink: PrincOps.NullLink, blink: BcdDefs.UnboundLink];
	ENDCASE => ERROR;
	IF EmptyLink[interfaceseq[i].clink] THEN interfaceseq.resolved ← FALSE;
	ENDLOOP;
interfaceseq.size ← eth.size;
fth ← @LOOPHOLE[bcdbase + bcdbase.ftOffset, BcdDefs.Base][eth.file];
interfaceseq.bcdVers ← fth.version;
};
	
EmptyLink: PROC[link: PrincOps.ControlLink] RETURNS[empty: BOOL] = {
	RETURN[link = PrincOps.UnboundLink OR link = PrincOps.NullLink];
	};
		
	-- can't be used for configs
BuildFramePtrInterface: PUBLIC PROC[bcdbase: BcdOps.BcdBase, 
	frame: PrincOps.GlobalFrameHandle] 
	RETURNS[interfaceseq: CTLoad.InterfaceSeq] = {
name: Rope.Text;
mth: BcdOps.MTHandle;
IF bcdbase.nModules ~= 1 THEN ERROR;
mth ← @LOOPHOLE[bcdbase + bcdbase.mtOffset, BcdDefs.Base][FIRST[BcdDefs.MTIndex]];
name ← NSToRope[bcdbase, mth.name];
interfaceseq ← AllocateInterfaceSeq[name, 1];
IF mth.file = BcdDefs.FTSelf THEN
	interfaceseq.bcdVers ← bcdbase.version
ELSE {
	fth: BcdOps.FTHandle;
	fth ← @LOOPHOLE[bcdbase + bcdbase.ftOffset, BcdDefs.Base][mth.file];
	interfaceseq.bcdVers ← fth.version;
	};
interfaceseq[0] ← [clink: LOOPHOLE[frame], blink: BcdDefs.NullLink];
interfaceseq.size ← 1;
};
	
-- only works for exported BcdDefs.Links in the export table
ConvertDummyLinkToControlLink: PROC[bl: BcdDefs.Link, realgfi: CARDINAL,
	bcdbase: BcdOps.BcdBase, loadinfoseq: CTLoad.LoadInfoSeq] 
	RETURNS[cl: PrincOps.ControlLink, newcgfi: PrincOps.GFTIndex] = {
cgfi: PrincOps.GFTIndex;

	ForEachModule: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] 
		RETURNS [BOOL] =  BEGIN
	  mgfi: PrincOps.GFTIndex ← mth.gfi;
	  IF cgfi IN [mth.gfi..mgfi + mth.ngfi) THEN  {
	    	newcgfi ← newcgfi + (cgfi - mgfi);
		realgfi ← realgfi + (cgfi - mgfi); 
		RETURN[TRUE];
		};
	  RETURN[FALSE]
	  END;

newcgfi ← loadinfoseq.configGfi;
IF bl = BcdDefs.UnboundLink THEN RETURN[PrincOps.UnboundLink, newcgfi];
SELECT bl.vtag FROM
var => {
	cgfi ← bl.vgfi;
	IF BcdOps.ProcessModules[bcdbase, ForEachModule].mth = NIL THEN
		RETURN[PrincOps.NullLink, newcgfi];
	cl ← [procedure[gfi: realgfi, ep: bl.var, tag: FALSE]];
	};
proc0, proc1 => {
	cgfi ← bl.gfi;
	IF BcdOps.ProcessModules[bcdbase, ForEachModule].mth = NIL THEN
		RETURN[PrincOps.UnboundLink, newcgfi];
	cl ← [procedure[gfi: realgfi, ep: bl.ep, tag: TRUE]];
	};
type => cl ← LOOPHOLE[bl.typeID];
ENDCASE;
};

NSToRope: PUBLIC PROC[bcdbase: BcdOps.BcdBase, name: BcdDefs.NameRecord] 
  RETURNS[resultstr: Rope.Text] = {
	namestring: BcdOps.NameString;
	r: Rope.ROPE ← NIL;
	namestring ← LOOPHOLE[bcdbase + bcdbase.ssOffset];
	FOR i: CARDINAL IN [0 .. namestring.size[name]) DO
		r ← r.Cat[Rope.FromChar[namestring.string.text[name + i]]];
		ENDLOOP;
	resultstr ← Rope.Flatten[r];
	};

AllocateLoadInfoSeq: PUBLIC PROC[bcdbase: BcdOps.BcdBase] 
	RETURNS[loadinfoseq: CTLoad.LoadInfoSeq] = {
loadinfoseq ← NEW[CTLoad.LoadInfoSeqRecord[bcdbase.nModules]];
loadinfoseq.dummymapseq ← NEW[
	CTLoad.DummyMapSeqRecord[bcdbase.firstdummy +bcdbase.nDummies]];
loadinfoseq.impexpseq ← NEW[
	CTLoad.ImpExpSeqRecord[bcdbase.nImports]];
};

FreeLoadInfoSeq: PUBLIC PROC[loadinfoseq: CTLoad.LoadInfoSeq] RETURNS[CTLoad.LoadInfoSeq]= {
IF loadinfoseq = NIL THEN RETURN[NIL];
IF loadinfoseq.bcdbase ~= NIL THEN
	Space.Delete[Space.GetHandle[Space.PageFromLongPointer[loadinfoseq.bcdbase]]
		! Space.Error => CONTINUE];
loadinfoseq.impexpseq ← NIL;
loadinfoseq.dummymapseq ← NIL;
RETURN[NIL];
};

AllocateInterfaceSeq: PUBLIC PROC[name: Rope.Text, size: CARDINAL] 
	RETURNS[interfaceseq: CTLoad.InterfaceSeq] = {
interfaceseq ← NEW[CTLoad.InterfaceSeqRecord[size] ← [body: ]];
interfaceseq.name ← name;
FOR i: CARDINAL IN [0 .. size) DO
	interfaceseq[i] ← [PrincOps.NullLink, BcdDefs.NullLink];
	ENDLOOP;
};

FreeInterfaceSeq: PUBLIC PROC[interfaceseq: CTLoad.InterfaceSeq] RETURNS[CTLoad.InterfaceSeq] = {
IF interfaceseq = NIL THEN RETURN[NIL];
interfaceseq.name ← NIL;
FREE[@interfaceseq];
RETURN[NIL];
};


  InvalidFile: PUBLIC ERROR [File.Capability] = CODE;

  LoadUpBcd: PROC [file: File.Capability, offset: File.PageCount] 
  	RETURNS [bcd: BcdOps.BcdBase, pages: CARDINAL] =
    BEGIN
    bcdSpaceBase: File.PageNumber;
    bcdSpace: Space.Handle;
    bcdSpaceBase ← offset;
    bcdSpace ← Space.Create[size: 1, parent: Space.virtualMemory];
    Space.Map[space: bcdSpace, window: [file: file, base: bcdSpaceBase]];
    bcd ← Space.LongPointer[bcdSpace];
    pages ← bcd.nPages;
    IF bcd.versionIdent # BcdDefs.VersionID OR bcd.definitions 
    OR (NOT bcd.tableCompiled AND ~bcd.spare1) THEN
      ERROR InvalidFile[file ! UNWIND => Space.Delete[bcdSpace]];
    IF pages > 1 THEN
      BEGIN
      Space.Delete[bcdSpace];
      bcdSpace ← Space.Create[size: pages, parent: Space.virtualMemory];
      Space.Map[space: bcdSpace, window: [file: file, base: bcdSpaceBase]];
      bcd ← Space.LongPointer[bcdSpace];
      END;
    Space.MakeReadOnly[bcdSpace];	-- make bcd header spaces ReadOnly, as the code is already
    RETURN
    END;
  
-- link space operations

  OpenLinkSpace: PUBLIC PROC [frame: PrincOps.GlobalFrameHandle, 
  	mth: BcdOps.MTHandle, bcd: BcdOps.BcdBase ← NIL] 
	RETURNS[LONG POINTER] =
    BEGIN
    IF frame.codelinks THEN {
      long ← TRUE; 
      links ← RuntimeInternal.Codebase[LOOPHOLE[frame]];
      IF links = NIL THEN ERROR;
      }
    ELSE {
      long ← FALSE; 
      links ← LOOPHOLE[LONG[frame]]
      };
    links ← links - PilotLoaderOps.LinkSegmentLength[mth, bcd];
    writeable ← FALSE;
    RETURN[links];
    END;

  ReadLink: PUBLIC PROC [offset: CARDINAL] 
  RETURNS [link: PrincOps.ControlLink] =
    BEGIN RETURN[links[offset]]; END;

 WriteLink: PUBLIC PROC [offset: CARDINAL, link: PrincOps.ControlLink] = {
    IF long AND NOT writeable THEN {
      space: Space.Handle;
      cap: File.Capability;
      writeable ← TRUE;
      space ← LoaderPrivate.FindMappedSpace[Space.GetHandle[
      		Space.PageFromLongPointer[links]]];
      cap ← Space.GetWindow[space].file;
      -- questionable????
      -- IF Inline.BITAND[cap.permissions, File.write] = 0 THEN    
      cap.permissions ← cap.permissions + File.write;
      Space.MakeWritable[space, cap]
      };
    links[offset] ← link};

  CloseLinkSpace: PUBLIC PROC [frame: PrincOps.GlobalFrameHandle] = {
    IF long AND writeable THEN
      Space.MakeReadOnly[LoaderPrivate.FindMappedSpace[Space.GetHandle[
      		Space.PageFromLongPointer[links]]]]
  	};



ConvertLink: PUBLIC PROC [bl: BcdDefs.Link] RETURNS [cl: PrincOps.ControlLink] =
    BEGIN
    IF bl = BcdDefs.UnboundLink THEN RETURN[PrincOps.UnboundLink];
    SELECT bl.vtag FROM
      var => cl ← [procedure[gfi: bl.vgfi, ep: bl.var, tag: FALSE]];
      proc0, proc1 => cl ← [procedure[gfi: bl.gfi, ep: bl.ep, tag: TRUE]];
      type => cl ← LOOPHOLE[bl.typeID];
      ENDCASE;
    RETURN
    END;


}.