// MDassign -- assign locations to instructions
// last edited August 16, 1980  9:47 PM

	get "mddecl.d"
	get "mdadefs.d"

external [	// defined here
	Assign	// (zone)
		// Statics for MDaring
	@lBT; @lPage
	@PageBase
]

external [
		// OS
	Allocate
	MoveBlock; SetBlock; Zero
	CallSwat
		// MDmain
	@IP
	@DMachine
	@IM; @IMlocked; @NInstructions
	AbortCode
		// MDerr
	Err
	PutAddress
	PutAddrData
	PutRing
		// MDaring
	AssignRing
		// MDplace
	SetupMasks
	CountBits
		// MDprescan
	@PageSize; @nPages; @WordMask
	@globalZero; @nGlobalPages
	@ifuZero; @nIfuPages; @pageIfuMax; @ifuMask
	@calledMask; @goedtoMask; @jbctMask
		// MDasm
	Set1Bit
]


static
[	@PageTab
	@PageBase
	@lBT; @lPage
	@firstPage; @lastPage; @thisPage
	@failMsg; @fullMsg; @total = 0
]

let Assign(zone) be
[	Err(PassMessage, "Assigning locations...")
	lBT = PageSize/16
	let m1, m2, m3, m4, m5 = nil, nil, nil, nil, -1
	test DMachine eq 0
	ifso
	[ SetupMasks(zone, lv m5, 1, lBT)
	]
	ifnot
	[ let jbceMask = jbctMask % (jbctMask rshift 1)
	  m1, m2, m3, m4 =
	    not (calledMask % ifuMask % jbceMask),
	    not (calledMask % jbceMask),
	    not (calledMask % (ifuMask & jbceMask)),
	    not (calledMask)
	  SetupMasks(zone, lv m1, 5, lBT)
	]
	lPage = lPageHd+lBT
	PageTab = Allocate(zone, nPages)
	PageBase = Allocate(zone, nPages*lPage)
	for i = 0 to nPages-1 do
	[ let page = PageBase+i*lPage
	  PageTab!i = page
	  let lbase = IMlocked+i*lBT
	  for j = 0 to lBT-1 do
	  [ let b = lbase!j
	    if b eq 0 loop
	    page>>Page.BT↑j = b
	    page>>Page.used = page>>Page.used+CountBits(b)
	  ]
	]
	let Tab = vec lIbuf	// working vector
	let ring = vec maxlPage
// Classify instructions by group
	let groups = vec 8
	SetBlock(groups, -1, 8)
	let gAbs, gGlobal, gIFUE = -1, -1, -1
	for i = NInstructions-1 by -1 to 0 do	// so lists wind up in ascending order
	[ let ip = IP(i)
	  ip>>IM.marked = 0
	  let lvg = nil
	  test ip>>IM.onPage  ifso lvg = lv gAbs  ifnot
	  test ip>>IM.global  ifso lvg = lv gGlobal  ifnot
	  test ip>>IM.IFUE  ifso lvg = lv gIFUE  ifnot
	  [ lvg = (ip>>IM.aLinked ne 0? 4, 0)+groups	  // Do alists first
	    if (not calledMask & ip>>IM.mask) eq 0 then lvg = lvg+2	  // Then subroutine entries
	    if ip>>IM.bLink ne i then lvg = lvg+1	  // Then non-unit rings
	  ]
	  ip>>IM.groupLink = @lvg
	  @lvg = i
	]
// Process pages with known page number
	failMsg = "Can't assign absolutely placed ring"
	fullMsg = "Too many instructions on page"
	total = 0
	until gAbs eq -1 do
	[ let i = gAbs
	  let ip = IP(i)
	  gAbs = ip>>IM.groupLink
	  if ip>>IM.marked loop	// already processed
	  let pn = ip>>IM.W0/PageSize
	  let pa = pn*PageSize
	  total = CollectRing(i, Tab, ring) + total
	  if (ring>>Page.global ne 0) & ((pa&globalZero) ne 0) then
	    Err(PassFatal, "Can't have GLOBAL on page $O", pa)
	  if (ring>>Page.IFUE ne 0) & ((pa&ifuZero) ne 0) then
	    Err(PassFatal, "Can't have IFU entry on page $O", pa)
	  pagelimits(pn, pn)
	  assignpage(Tab, ring)
	]
	writetotal("rings involving ONPAGE or AT")
// Process global and IFU entry rings (none for D0)
	pagelimits(0, nGlobalPages-1)
	failMsg = "Can't assign GLOBAL ring"
	fullMsg = 0
	total = 0
	until gGlobal eq -1 do
	[ let i = gGlobal
	  let ip = IP(i)
	  gGlobal = ip>>IM.groupLink
	  if ip>>IM.marked loop	// already processed
	  total = CollectRing(i, Tab, ring) + total
	  assignpage(Tab, ring)
	]
	writetotal("rings with a GLOBAL")
	pagelimits(0, nIfuPages-1)
	failMsg = "Can't assign IFU entry ring"
	total = 0
	until gIFUE eq -1 do
	[ let i = gIFUE
	  let ip = IP(i)
	  gIFUE = ip>>IM.groupLink
	  if ip>>IM.marked loop	// already processed
	  total = CollectRing(i, Tab, ring) + total
	  assignpage(Tab, ring)
	]
	writetotal("rings with an IFU entry")
// Process other rings
	pagelimits(0, nPages-1)
	failMsg = "Ran out of pages trying to assign ring"
	for a = 7 to 0 by -1 do
	[ total = 0
	  let i = groups!a
	  until i eq -1 do
	  [ let ip = IP(i)
	    unless ip>>IM.marked do	  // already processed
	    [ total = CollectRing(i, Tab, ring) + total
	      assignpage(Tab, ring)
	    ]
	    i = ip>>IM.groupLink
	  ]
	  writetotal(selecton a into
	  [ case 7: "CALLed rings including a CALL/conditional"
	    case 6: 0	// Can't have a 1-instruction alist
	    case 5: "other rings including a CALL/conditional"
	    case 4: 0	// Can't have a 1-instruction alist
	    case 3: "CALLed multi-instruction rings"
	    case 2: "CALLed 1-instruction rings"
	    case 1: "other multi-instruction rings"
	    case 0: "other 1-instruction rings"
	  ])
	]
// Check to make sure assignment was completed
// (strictly an internal consistency check)
	let ec, str, acode = PassFatal, "******The following had no address assigned:*N*T$P", AbortCode
	for i = 0 to NInstructions-1 do
	[ let ip = IP(i)
	  let placed = ip>>IM.onPage & ip>>IM.atWord
	  ip>>IM.placed = placed
	  if (placed eq 0) & (acode ls 0) then
	  [ Err(ec, str, PutAddrData, i); ec, str = PassMessage, "*T$P" ]
	]
]

and pagelimits(first, last) be
	firstPage, lastPage, thisPage = first, last, first

and assignpage(tab, ring) be
[	static [ @atrue = 0; @afalse = 0 ]	// statistics
	let max = PageSize-ring>>Page.used
	let pn = thisPage
	let af = afalse
	let fptr, fused = nil, PageSize
	[ let page = PageTab!pn
	  if page>>Page.used le max then
	  [ let fp = AssignRing(tab, ring, page)
	    test fp eq 0
	     ifso
	    [ thisPage = pn; atrue = atrue+1; return ]
	     ifnot
	    [ if page>>Page.used ls fused then
	        fptr, fused = fp, page>>Page.used
	      afalse = afalse+1
	    ]
	  ]
	  pn = pn-1
	] repeatuntil pn ls firstPage
	pn = thisPage+1
	until pn gr lastPage do
	[ let page = PageTab!pn
	  if page>>Page.used le max then
	  [ let fp = AssignRing(tab, ring, page)
	    test fp eq 0
	     ifso
	    [ thisPage = pn; atrue = atrue+1; return ]
	     ifnot
	    [ if page>>Page.used ls fused then
	        fptr, fused = fp, page>>Page.used
	      afalse = afalse+1
	    ]
	  ]
	  pn = pn+1
	]
	let msg = ((af eq afalse) &	// no page had enough room
	  (fullMsg ne 0)? fullMsg, failMsg)
	Err(PassMessage, (fptr eq -1? "$S:", "$S,*N  while trying to place instruction(s):"), msg)
	if fptr ne -1 then	// might not be any culprit
	 test (fptr-lv tab>>SubPage.data-tab>>SubPage.length) ls 0
	 ifso	// points to an isolated instruction
	  Err(PassMessage, "*T$P", PutAddrData, @fptr)
	 ifnot	// points to a subpage
	  for j = 0 to fptr>>SubPage.length-1 do
	    Err(PassMessage, "*T$P", PutAddrData, fptr>>SubPage.data↑j)
	Err(PassFatal, "  Ring consists of:*N$P", PutRing, firstins(tab))
]

and firstins(tab) =
	(@tab eq 0? tab+lSubPageHd, tab)>>SubPage.data↑0

and writetotal(msg) be
[	if total ne 0 then
	 test msg eq 0
	  ifso CallSwat("MicroD bug")
	  ifnot Err(PassMessage, "$6Ob instructions in $S", total, msg)
]

and CollectRing(i, tab, ring) = valof
// Collect instructions starting at i
// Set bit table and counts in ring (Page structure)
// Put instructions in tab (fake SubPage for isolated instructions, sequence of SubPage structures, 0)
// Return # of instructions collected
[	Zero(ring, lPage)
	let Atab = vec maxPageSize
	@Atab = -2
	// Don't start ring inside subpage sequence
	let ip = nil
	[ ip = IP(i)
	  i = ip>>IM.bLink
	] repeatuntil ip>>IM.jbcLinked eq 0
	let j = i
	let top = tab+(lIbuf-1)
	let bot = tab+lSubPageHd
	let ni, ptr, end = 0, bot, top
	let nsp = 0
	let lastLinked = false
	[ ip = IP(j)
	  ip>>IM.marked = 1
	  if ip>>IM.global then
	  [ ring>>Page.global = ring>>Page.global+1
	    ring>>Page.slow = true
	    if ring>>Page.global gr pageGlobalMax then
	      Err(PassFatal, "More than $D GLOBAL(s) on same page:*N$P", pageGlobalMax, PutRing, j)
	  ]
	  if ip>>IM.IFUE then
	  [ ring>>Page.IFUE = ring>>Page.IFUE+1
	    ring>>Page.slow = true
	    if ring>>Page.IFUE gr pageIfuMax then
	      Err(PassFatal, "More than $D IFU entries on same page:*N$P", pageIfuMax, PutRing, j)
	  ]
	  if ip>>IM.atWord then
	  [ if @Atab eq -2 then SetBlock(Atab, -1, PageSize)
	    let wn = ip>>IM.W0 & WordMask
	    test Atab!wn eq -1
	    ifnot
	      Err(PassFatal, "$P....assigned to same location as $P", PutAddress, j, PutAddress, Atab!wn)
	    ifso
	    [ Set1Bit(lv ring>>Page.BT, wn, 1)
	      Atab!wn = j
	      ring>>Page.slow = true
	    ]
	  ]
	  unless lastLinked do
	   test ip>>IM.jbcLinked
	   ifnot
	  if end ne ptr then
	  [ @ptr = j
	    ptr = ptr+1
	  ]
	   ifso
	  [ let len = CollectSubpage(j, end, end-ptr)
	    end = end-len
	    if len ne 0 then nsp = nsp+len-lSubPageHd
	  ]
	  j = ip>>IM.bLink
	  lastLinked = ip>>IM.jbcLinked
	  ni = ni+1
	] repeatuntil j eq i
	if ni gr PageSize then
	[ Err(PassFatal, "$D instructions had to go on the same page (limit is $D):*N$P", ni, PageSize, PutRing, i)
	  ni = ptr-bot+nsp
	]
	ring>>Page.used = ni
	// Rearrange the tables
	@tab = ptr-bot
	@top = 0
	MoveBlock(ptr, end, top-end+1)
	resultis ni
]

and CollectSubpage(start, end, len) = valof
// Start is first instruction of subpage in ring
// End is the end of a SubPage structure for the result
// Put addresses in the structure with +1 lists first
// Set length, spn1, alists
// Return amount used if enough room, 0 if not enough
[	if len le lSubPageHd resultis 0
	let i = start
	let data = vec SubPageSize
	let jbot, jtop = end-len, end
	let dbot, dtop = data, data+SubPageSize
	let spn, iabs = -1, nil
	let ip = nil	// Compiler bug (!), should be "let ip" in next line
	[ ip = IP(i)
	  if ip>>IM.atWord then	// absolute placement
	  [ let spa = (ip>>IM.W0 & (PageSize-20b)) rshift 4
	    test spn eq -1
	     ifso spn, iabs = spa, i
	     ifnot if spn ne spa then
	      Err(PassFatal, "$P....must be in subpage with $P, but has conflicting assignment", PutAddress, i, PutAddress, iabs)
	  ]
	  test ip>>IM.aLink eq i
	  ifso	// not on a +1 list, put at end
	  [ if jtop eq jbot resultis 0
	    jtop = jtop-1
	    @jtop = i
	  ]
	  ifnot	// on a +1 list, put the list at the beginning
	  [ let i0, i1 = nil, nil
	    for j = data to dbot-1 do
	      if @j eq i goto ska	// already got it
	    i1 = i
	    while ip>>IM.aLinked do	// find beginning of alist
	    [ i1 = ip>>IM.aLink
	      ip = IP(i1)
	    ]
	    i0 = i1
	    [ if dbot eq dtop resultis false
	      @dbot = i1
	      i1 = ip>>IM.aLink
	      ip = IP(i1)
	      dbot = dbot+1
	    ] repeatuntil i1 eq i0
	    ip = IP(i)
ska:	  ]
	  i = ip>>IM.bLink
	] repeatwhile ip>>IM.jbcLinked
	// Join sections of table
	let V = jtop-(dbot-data)-lSubPageHd
	if (V-jbot) ls 0 resultis 0
	MoveBlock(V+lSubPageHd, data, dbot-data)
	let nw = end-V-lSubPageHd
	if nw gr 16 then
	  Err(PassFatal, "$P....more than 16 instructions in subpage", PutAddress, start)
	V>>SubPage.alists = dbot ne data
	V>>SubPage.spn1 = spn+1
	V>>SubPage.length = nw
	resultis end-V
]