-- file Pass4L.Mesa
-- last modified by Satterthwaite, February 22, 1983 4:09 pm

DIRECTORY
  Alloc: TYPE USING [Notifier],
  ComData: TYPE USING [
    idANY, importCtx, interface, linkCount, nBodies, nSigCodes, switches,
    textIndex, zone],
  CompilerUtil: TYPE USING [AppendBCDWord],
  Log: TYPE USING [Error, ErrorN, ErrorSei, WarningSei],
  P4: TYPE USING [],
  PrincOps: TYPE USING [EPRange, globalbase, localbase, MaxFrameSize, MaxNGfi],
  Symbols: TYPE USING [
    Base, BitAddress, BitCount, FieldBitCount, PackedBitCount, WordCount,
    Type, ISEIndex, CSEIndex, RecordSEIndex, CTXIndex, BTIndex, CBTIndex,
    nullName, ISENull, RecordSENull, CTXNull, BTNull, lL, RootBti, WordLength,
    bodyType, ctxType, seType],
  SymbolOps: TYPE USING [
    ArgCtx, ArgRecord, BitsForRange, Cardinality, FirstCtxSe, LinkMode,
    MakeCtxSe, NextSe, PackedSize, TypeForm, UnderType, XferMode],
  Tree: TYPE USING [Base, Index, Link, Scan, NullIndex, treeType],
  TreeOps: TYPE USING [ScanList];

Pass4L: PROGRAM
    IMPORTS
      CompilerUtil, Log, SymbolOps, TreeOps,
      dataPtr: ComData
    EXPORTS P4 = {
  OPEN SymbolOps, Symbols;

  tb: Tree.Base;	-- tree base (local copy)
  seb: Symbols.Base;	-- se table base (local copy)
  ctxb: Symbols.Base;	-- context table base (local copy)
  bb: Symbols.Base;	-- body table base (local copy)

  LayoutNotify: PUBLIC Alloc.Notifier = {
    -- called by allocator whenever table area is repacked
    tb ← base[Tree.treeType];
    seb ← base[seType];  ctxb ← base[ctxType];
    bb ← base[bodyType]};


 -- address assignment (machine sensitive and subject to change)

  wordFill: CARDINAL = WordLength-1;

  localOrigin: CARDINAL = PrincOps.localbase*WordLength;
  localSlots: CARDINAL = 8;
  globalOrigin: CARDINAL = PrincOps.globalbase*WordLength;
  frameLimit: CARDINAL = PrincOps.MaxFrameSize*WordLength;

  entryLimit: CARDINAL = PrincOps.MaxNGfi*PrincOps.EPRange;


  BitsForType: PUBLIC PROC [type: Type] RETURNS [nBits: BitCount] = {
    -- assumes (an attempt at) prior processing by P4.DeclItem
    sei: CSEIndex = UnderType[type];
    WITH seb[sei] SELECT FROM
      basic => nBits ← length;
      enumerated => nBits ← BitsForRange[Cardinality[sei]-1];
      ref => nBits ← WordLength;
      transfer => nBits ← IF mode = port THEN 2*WordLength ELSE WordLength;
      arraydesc => nBits ← 2*WordLength;
      relative => nBits ← BitsForType[offsetType];
      zone => nBits ← (IF mds THEN 1 ELSE 2)*WordLength;
      long => nBits ← ((BitsForType[rangeType] + wordFill)/WordLength + 1)*WordLength;
      real => nBits ← 2*WordLength;
      ENDCASE => {  -- processing of se entry must be complete
	IF ~mark4 THEN {	-- P4declitem has not been able to complete
	  Log.ErrorSei[typeLength,
		IF seb[type].seTag = id THEN LOOPHOLE[type, ISEIndex] ELSE ISENull];
	  RETURN [0]};
	WITH seb[sei] SELECT FROM
	  record => nBits ← length;
	  array => {
	    n: LONG CARDINAL = Cardinality[indexType];
	    b: BitCount ← BitsForType[componentType];
	    IF packed AND (b#0 AND b<=PackedBitCount.LAST) THEN { -- b IN PackedBitCount
	      itemsPerWord: CARDINAL = WordLength/PackedSize[b];
	      nBits ← IF n <= itemsPerWord
			THEN n*PackedSize[b]
			ELSE ((n+(itemsPerWord-1))/itemsPerWord)*WordLength}
	    ELSE {
	      b ← ((b + wordFill)/WordLength)*WordLength;
	      IF n > CARDINAL.LAST/b THEN Log.Error[fieldSize];
	      nBits ← n*b}};
	  opaque => nBits ← length;
	  subrange => nBits ← IF empty THEN 0 ELSE BitsForRange[Cardinality[sei]-1];
	  ENDCASE => nBits ← 0};
    RETURN};


 -- profile utilities

  VarLink: TYPE = RECORD [
    SELECT kind: * FROM
      symbol => [index: ISEIndex],
      body => [index: CBTIndex],
      empty => NULL,
      ENDCASE];

  VarInfo: TYPE = RECORD [link: VarLink, key: CARDINAL];
  VarInfoList: TYPE = RECORD [SEQUENCE length: NAT OF VarInfo];
  Profile: TYPE = LONG POINTER TO VarInfoList;
  
  AllocateProfile: PROC [n: CARDINAL] RETURNS [profile: Profile] = {
    profile ← (dataPtr.zone).NEW[VarInfoList[n]];
    FOR k: CARDINAL IN [0 .. n) DO profile[k].link ← [empty[]] ENDLOOP;
    RETURN};

  ReleaseProfile: PROC [profile: Profile] = {(dataPtr.zone).FREE[@profile]};

  SortProfile: PROC [v: Profile] = {  -- Shell sort --
    h, i, j: INTEGER;
    k: CARDINAL;
    t: VarInfo;
    h ← v.length;
    DO
      h ← h/2;
      FOR j IN [h .. v.length) DO
	i ← j-h;  k ← v[j].key;  t ← v[j];
	WHILE k > v[i].key DO
	  v[i+h] ← v[i];
	  IF (i ← i-h) < 0 THEN EXIT;
	  ENDLOOP;
	v[i+h] ← t;
	ENDLOOP;
      IF h <= 1 THEN EXIT;
      ENDLOOP};


 -- entry point assignment

  GenBodies: PROC [root: BTIndex, proc: PROC [CBTIndex]] = {
    bti, next: BTIndex;
    FOR bti ← root, next UNTIL bti = BTNull DO
      WITH bb[bti] SELECT FROM
	Callable => proc[LOOPHOLE[bti]];
	ENDCASE => NULL;
      IF bb[bti].firstSon # BTNull THEN next ← bb[bti].firstSon
      ELSE
	DO
	  next ← bb[bti].link.index;
	  IF next = BTNull OR bb[bti].link.which # parent THEN EXIT;
	  bti ← next;
	  ENDLOOP;
      ENDLOOP};


  BodyRefs: PROC [bti: CBTIndex] RETURNS [count: CARDINAL←0] = {
    sei: ISEIndex = bb[bti].id;

    CountRefs: Tree.Scan = {
      count ← count + seb[NARROW[t, Tree.Link.symbol].index].idInfo};

    IF sei # ISENull THEN {
      node: Tree.Index = seb[sei].idValue;
      TreeOps.ScanList[tb[node].son[1], CountRefs]};
    RETURN};


  AssignEntries: PUBLIC PROC [rootBti: BTIndex] = {
    i, k: INTEGER;
    profile: Profile;
    bti: CBTIndex;

    AssignSlot: PROC [bti: CBTIndex] = {
      IF ~bb[bti].inline AND bb[bti].info.mark = Internal  THEN {
	n: CARDINAL = BodyRefs[bti];
	profile[k].link ← [body[index: bti]];
	WITH body: bb[bti] SELECT FROM
	  Inner => {body.frameOffset ← n; profile[k].key ← 0};
	  ENDCASE => profile[k].key ← n;
	k ← k+1}};

    nEntries: CARDINAL = MAX[dataPtr.nBodies, dataPtr.nSigCodes];
    IF nEntries > entryLimit THEN Log.ErrorN[bodyEntries, nEntries-entryLimit];
    profile ← AllocateProfile[dataPtr.nBodies];
    k ← 0;  GenBodies[rootBti, AssignSlot];
    IF dataPtr.switches['s] THEN SortProfile[profile];
    i ← 1;
    FOR j: INTEGER IN [0..profile.length) DO
      bti ← NARROW[profile[j].link, VarLink.body].index;
      IF bti = RootBti THEN bb[bti].entryIndex ← 0
      ELSE {bb[bti].entryIndex ← i; i ← i+1};
      ENDLOOP;
    ReleaseProfile[profile]};


 -- frame layout

  FieldWordCount: TYPE = [0..FieldBitCount.LAST/WordLength];
  
  WordsForField: PROC [sei: ISEIndex] RETURNS [nW: FieldWordCount] = {
    nBits: BitCount = BitsForType[seb[sei].idType] + wordFill;
    IF nBits > FieldBitCount.LAST THEN  {
      Log.ErrorSei[addressOverflow, sei];  nW ← FieldWordCount.LAST}
    ELSE nW ← FieldBitCount[nBits]/WordLength;
    RETURN};

  VarScan: TYPE = PROC [sei: ISEIndex, output: BOOL];

  GenCtxVars: PROC [ctx: CTXIndex, p: VarScan, output: BOOL] = {
    FOR sei: ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = ISENull DO
      IF ~seb[sei].constant THEN p[sei, output] ENDLOOP};

  GenBodyVars: PROC [bti: CBTIndex, p: VarScan] = {
    type: Type = bb[bti].ioType;
    WITH se: seb[type] SELECT FROM
      cons =>
	WITH t: se SELECT FROM
	  transfer => {
	    GenCtxVars[ArgCtx[t.typeIn], p, FALSE];
	    GenCtxVars[ArgCtx[t.typeOut], p, TRUE]};
	  ENDCASE;
      ENDCASE;
    GenCtxVars[bb[bti].localCtx, p, FALSE]};

  GenBodyProcs: PROC [bti: BTIndex, proc: PROC [CBTIndex]] = {
    sonBti: BTIndex;
    IF (sonBti ← bb[bti].firstSon) # BTNull THEN
      DO
	WITH body: bb[sonBti] SELECT FROM
	  Callable => IF ~body.inline THEN proc[LOOPHOLE[sonBti]];
	  ENDCASE => NULL;
	IF bb[sonBti].link.which = parent THEN EXIT;
	sonBti ← bb[sonBti].link.index;
	ENDLOOP};

  GenImportedVars: PROC [p: VarScan] = {
    ctx: CTXIndex = dataPtr.importCtx;
    FOR sei: ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = ISENull DO
      IF ~seb[sei].constant THEN p[sei, FALSE]
      ELSE {
	type: CSEIndex = UnderType[seb[sei].idType];
	WITH seb[type] SELECT FROM definition => GenCtxVars[defCtx, p, FALSE] ENDCASE};
      ENDLOOP};


  MarkArg: VarScan = {seb[sei].mark4 ← TRUE};

  MarkArgs: PROC [sei: Type] = {
    type: CSEIndex = UnderType[sei];
    rSei: RecordSEIndex;
    WITH t: seb[type] SELECT FROM
      transfer => {
	IF (rSei ← ArgRecord[t.typeIn]) # RecordSENull THEN {
	  GenCtxVars[seb[rSei].fieldCtx, MarkArg, FALSE];
	  seb[rSei].length ← LayoutArgs[rSei, 0, TRUE]*WordLength;
	  seb[rSei].mark4 ← TRUE};
	IF (rSei ← ArgRecord[t.typeOut]) # RecordSENull THEN {
	  GenCtxVars[seb[rSei].fieldCtx, MarkArg, TRUE];
	  seb[rSei].length ← LayoutArgs[rSei, 0, TRUE]*WordLength;
	  seb[rSei].mark4 ← TRUE};
	t.mark4 ← TRUE};
      ENDCASE};


  LayoutLocals: PUBLIC PROC [bti: CBTIndex] RETURNS [length: CARDINAL] = {
    vProfile: Profile;
    vI: CARDINAL;
    
    CountVar: VarScan = {
      IF seb[sei].hash # nullName OR seb[sei].extended OR ~output THEN vI ← vI + 1};

    CountProc: PROC [bti: CBTIndex] = {
      IF bb[bti].info.mark = Internal THEN vI ← vI + 1};

    InsertVar: VarScan = {
      saveIndex: CARDINAL = dataPtr.textIndex;
      node: Tree.Index = LOOPHOLE[seb[sei].idValue];
      IF node # Tree.NullIndex THEN dataPtr.textIndex ← tb[node].info;
      IF seb[sei].hash # nullName OR seb[sei].extended OR ~output THEN {
        vProfile[vI] ← [link: [symbol[sei]], key: seb[sei].idInfo]; vI ← vI+1};
      IF seb[sei].idInfo = 0 AND seb[sei].hash # nullName
       AND ~output	-- suppress message for return record
       AND node # Tree.NullIndex THEN Log.WarningSei[unusedId, sei];
      seb[sei].idInfo ← WordsForField[sei]*WordLength;
      seb[sei].idValue ← 0;
      dataPtr.textIndex ← saveIndex};

    InsertProc: PROC [bti: CBTIndex] = {
      IF bb[bti].info.mark = Internal THEN {
	vProfile[vI] ← VarInfo[
	    link: [body[bti]],
	    key: WITH bb[bti] SELECT FROM Inner=>frameOffset, ENDCASE=>0];
	vI ← vI+1}};

    bodyType: Type = bb[bti].ioType;
    origin: CARDINAL ← IF bb[bti].level = lL THEN localOrigin ELSE localOrigin+WordLength;
    IF ~seb[bodyType].mark4 THEN MarkArgs[bodyType];
    vI ← 0;  GenBodyVars[bti, CountVar];  GenBodyProcs[bti, CountProc];
    vProfile ← AllocateProfile[vI];
    vI ← 0;  GenBodyVars[bti, InsertVar];  GenBodyProcs[bti, InsertProc];
    SortProfile[vProfile];
    origin ← AssignVars[vProfile, origin, localOrigin + localSlots*WordLength];
    length ← AssignVars[vProfile, origin, frameLimit];
    CheckFrameOverflow[vProfile];  ReleaseProfile[vProfile];
    RETURN};


  LayoutGlobals: PUBLIC PROC [bti: CBTIndex, stopping, fragments: BOOL]
      RETURNS [length: CARDINAL] = {
    vProfile, xProfile: Profile;
    vI, xI: CARDINAL;

    CountVar: VarScan = {
      ctx: CTXIndex = seb[sei].idCtx;
      IF ctxb[ctx].ctxType = imported OR ctx = dataPtr.importCtx THEN xI ← xI + 1
      ELSE IF seb[sei].hash # nullName OR seb[sei].extended OR ~output THEN vI ← vI + 1};

    InsertVar: VarScan = {
      ctx: CTXIndex = seb[sei].idCtx;
      IF ctxb[ctx].ctxType = imported OR ctx = dataPtr.importCtx THEN {
	xProfile[xI] ← [link: [symbol[sei]], key: seb[sei].idInfo];
	xI ← xI+1;
	IF seb[sei].idInfo = 0 AND ~seb[sei].public THEN Log.WarningSei[unusedId, sei];
	seb[sei].idInfo ← WordsForField[sei]*WordLength}
      ELSE {
	saveIndex: CARDINAL = dataPtr.textIndex;
	node: Tree.Index = LOOPHOLE[seb[sei].idValue];
	IF node # Tree.NullIndex THEN dataPtr.textIndex ← tb[node].info;
	IF seb[sei].hash # nullName OR seb[sei].extended OR ~output THEN {
	  vProfile[vI] ← [link: [symbol[sei]], key: seb[sei].idInfo]; vI ← vI + 1};
	IF seb[sei].idInfo = 0 AND ~dataPtr.interface
	 AND ~seb[sei].public AND seb[sei].hash # nullName
	 AND node # Tree.NullIndex THEN Log.WarningSei[unusedId, sei];
	seb[sei].idInfo ← WordsForField[sei]*WordLength;
	seb[sei].idValue ← 0;
	dataPtr.textIndex ← saveIndex}};

    origin: CARDINAL ← globalOrigin;
    IF ~seb[bb[bti].ioType].mark4 THEN ERROR;
    vI ← xI ← 0;  GenBodyVars[bti, CountVar];  GenImportedVars[CountVar];
    vProfile ← AllocateProfile[vI];  xProfile ← AllocateProfile[xI];
    vI ← xI ← 0;  GenBodyVars[bti, InsertVar];  GenImportedVars[InsertVar];
    IF dataPtr.switches['s] THEN {SortProfile[vProfile]; SortProfile[xProfile]};
    AssignImports[xProfile, 0, 256*WordLength];
    SELECT TRUE FROM	-- adjust for system uses of global 0
      stopping => origin ← origin + WordLength;
      fragments =>
	-- avoid fragment (length >= 2*WordLength) overlay of global 0 (used for start traps)
	origin ← MAX[
	  AssignVars[vProfile, origin, globalOrigin+WordLength],
	  globalOrigin+WordLength];
      ENDCASE;
    origin ← AssignVars[vProfile, origin, frameLimit];
    length ← MAX[origin, globalOrigin+WordLength];
    CheckFrameOverflow[vProfile];  ReleaseProfile[vProfile];
    CheckFrameOverflow[xProfile];  ReleaseProfile[xProfile];
    RETURN};


  CheckBlock: PUBLIC PROC [bti: BTIndex] = {

    CheckVar: VarScan = {
      saveIndex: CARDINAL = dataPtr.textIndex;
      node: Tree.Index = LOOPHOLE[seb[sei].idValue];
      IF node # Tree.NullIndex THEN {
	dataPtr.textIndex ← tb[node].info;
	IF seb[sei].idInfo = 0 THEN Log.WarningSei[unusedId, sei]};
      dataPtr.textIndex ← saveIndex};

    GenCtxVars[bb[bti].localCtx, CheckVar, FALSE]};

  LayoutBlock: PUBLIC PROC [bti: BTIndex, origin: CARDINAL]
      RETURNS [length: CARDINAL] = {
    vProfile: Profile;
    vI: CARDINAL;

    CountVar: VarScan = {vI ← vI + 1};

    CountProc: PROC [bti: CBTIndex] = {
      IF bb[bti].info.mark = Internal THEN vI ← vI + 1};

    InsertVar: VarScan = {
      vProfile[vI] ← [link: [symbol[sei]], key: seb[sei].idInfo]; vI ← vI+1;
      seb[sei].idInfo ← WordsForField[sei]*WordLength;
      seb[sei].idValue ← 0};

    InsertProc: PROC [bti: CBTIndex] = {
      IF bb[bti].info.mark = Internal THEN {
	vProfile[vI] ← VarInfo[
	    link: [body[bti]],
	    key: WITH bb[bti] SELECT FROM Inner=>frameOffset, ENDCASE=>0];
	vI ← vI+1}};

    vI ← 0;  GenCtxVars[bb[bti].localCtx, CountVar, FALSE];  GenBodyProcs[bti, CountProc];
    vProfile ← AllocateProfile[vI];
    vI ← 0;  GenCtxVars[bb[bti].localCtx, InsertVar, FALSE];  GenBodyProcs[bti, InsertProc];
    SortProfile[vProfile];
    length ← AssignVars[vProfile, origin, frameLimit];
    CheckFrameOverflow[vProfile];  ReleaseProfile[vProfile];
    RETURN};


  LayoutInterface: PUBLIC PROC [bti: CBTIndex] RETURNS [nEntries: CARDINAL] = {
    epN: CARDINAL ← 0;
    FOR sei: ISEIndex ← FirstCtxSe[bb[bti].localCtx], NextSe[sei] UNTIL sei = ISENull DO
      SELECT LinkMode[sei] FROM
	val, ref => {seb[sei].linkSpace ← TRUE; seb[sei].idValue ← epN; epN ← epN + 1};
	type => {seb[sei].idValue ← epN; epN ← epN + 1};
	ENDCASE;
      ENDLOOP;
    IF (nEntries←epN) > entryLimit THEN Log.ErrorN[interfaceEntries, nEntries-entryLimit];
    RETURN};


  CheckFrameOverflow: PROC [profile: Profile] = {
    FOR i: INTEGER IN [0 .. profile.length) DO
      WITH profile[i].link SELECT FROM
	symbol => Log.ErrorSei[addressOverflow, index];
	body => Log.ErrorSei[addressOverflow, bb[index].id];
	ENDCASE;
      ENDLOOP};


  Align: PROC [offset: CARDINAL, item: VarLink] RETURNS [CARDINAL] = {
    RETURN [WITH item SELECT FROM
      body => (offset+WordLength)/(4*WordLength)*(4*WordLength) + (2*WordLength),
      symbol =>
	SELECT XferMode[seb[index].idType] FROM
	  port => (offset+WordLength)/(4*WordLength)*(4*WordLength) + (2*WordLength),
	  ENDCASE => offset,
      ENDCASE => offset]};

  BitWidth: PROC [item: VarLink] RETURNS [CARDINAL] = {
    RETURN [WITH item SELECT FROM
      symbol => seb[index].idInfo,
      body => WordLength,
      ENDCASE => 0]};

  AssignBase: PROC [item: VarLink, base: CARDINAL] = {
    WITH item SELECT FROM
      symbol => {
	sei: ISEIndex = index;
        seb[sei].idValue ← BitAddress[wd:base/WordLength, bd:0];
        seb[sei].mark4 ← TRUE};
      body => {
	bti: CBTIndex = index;
	WITH bb[bti] SELECT FROM Inner => frameOffset ← base/WordLength ENDCASE => ERROR};
      ENDCASE};

  AssignVars: PROC [profile: Profile, origin, limit: CARDINAL] RETURNS [CARDINAL] = {
    start, base, length, remainder, delta: CARDINAL;
    i, j, next: INTEGER;
    t: VarLink;
    found, skips: BOOL;
    next ← 0;  start ← origin;
    remainder ← IF origin < limit THEN limit - origin ELSE 0;
    WHILE next < profile.length DO
      i ← next;  found ← skips ← FALSE;
      WHILE ~found AND i < profile.length DO
	IF (t ← profile[i].link) # [empty[]] THEN {
	  base ← Align[start, t];  length ← BitWidth[t];
	  delta ← base - start;
	  IF length + delta <= remainder THEN {
	    limit: CARDINAL = base + length;
	    subBase: CARDINAL ← start;
	    nRefs: CARDINAL ← 0;
	    FOR j ← i+1, j+1 WHILE j < profile.length AND subBase < limit DO
	      IF profile[j].link # [empty[]] THEN {
		subLength: CARDINAL = BitWidth[profile[j].link];
		subDelta: CARDINAL = Align[subBase, profile[j].link] - subBase;
		IF  (subDelta + subLength) > (limit - subBase) THEN EXIT;
		subBase ← subBase + (subDelta + subLength);
		nRefs ← nRefs + profile[j].key};
	      ENDLOOP;
	    IF nRefs <= profile[i].key OR ~dataPtr.switches['s] THEN {
	      found ← TRUE;
	      AssignBase[t, base];  profile[i].link ← [empty[]];
	      IF base # start AND dataPtr.switches['s] THEN
	        [] ← AssignVars[profile, start, base];
	      start ← limit;
	      remainder ← remainder - (length+delta)}
	    ELSE IF ~skips THEN {skips ← TRUE; next ← i}}};
	i ← i+1;
	IF ~skips THEN next ← i;
	ENDLOOP;
      ENDLOOP;
    RETURN [start]};

  AssignImports: PROC [profile: Profile, origin, limit: CARDINAL] = {
    i, nProcs: CARDINAL ← profile.length;
    next: CARDINAL;
    t: VarLink;
    v: VarInfo;
    UNTIL i = 0 DO
      i ← i-1;  t ← profile[i].link;
      WITH t SELECT FROM
	symbol =>
	  IF XferMode[seb[index].idType] # proc THEN {
	    nProcs ← nProcs-1;  v ← profile[i];
	    FOR j: CARDINAL IN [i..nProcs) DO profile[j] ← profile[j+1] ENDLOOP;
	    profile[nProcs] ← v};
	ENDCASE;
      ENDLOOP;
    -- the frame link fragment begins at origin
      dataPtr.linkCount ← profile.length;
      IF ~dataPtr.interface THEN CompilerUtil.AppendBCDWord[profile.length];
    i ← profile.length;
    next ← MIN[origin + profile.length*WordLength, limit];
    UNTIL i = 0 OR next = origin DO
      i ← i-1;  t ← profile[i].link;  profile[i].link ← [empty[]];
      IF ~dataPtr.interface THEN
	WITH t SELECT FROM
	  symbol => {
	    sei: ISEIndex = index;
	    next ← next - seb[sei].idInfo;
	    CompilerUtil.AppendBCDWord[seb[sei].idValue];
	    seb[sei].idValue ← BitAddress[wd: next/WordLength, bd: 0];
	    seb[sei].linkSpace ← TRUE};
	  ENDCASE;
      ENDLOOP};


 -- parameter record  layout

  LayoutArgs: PUBLIC PROC [argRecord: RecordSEIndex, origin: CARDINAL, body: BOOL]
      RETURNS [CARDINAL] = {
    w: CARDINAL ← origin;
    IF argRecord # RecordSENull THEN {
      ctx: CTXIndex = seb[argRecord].fieldCtx;
      FOR sei: ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = ISENull DO
	nW: FieldWordCount = WordsForField[sei];
	IF nW = 0 THEN Log.ErrorSei[sizeClash, sei];
	IF ~body THEN {
	  seb[sei].idInfo ← nW*WordLength;
	  seb[sei].idValue ← BitAddress[wd:w, bd:0]};
	w ← w + nW;
	ENDLOOP};
    RETURN [w]};


 -- record layout

  BitOffset: PROC [sei: ISEIndex] RETURNS [CARDINAL] = {
    t: BitAddress = seb[sei].idValue; RETURN [t.wd*WordLength + t.bd]};

  BitsForField: PROC [sei: ISEIndex] RETURNS [nB: FieldBitCount] = {
    nBits: BitCount = BitsForType[seb[sei].idType];
    IF nBits > FieldBitCount.LAST THEN {
      Log.ErrorSei[addressOverflow, sei];  nB ← 0}
    ELSE nB ← FieldBitCount[nBits];
    RETURN};

  ScanVariants: PROC [caseCtx: CTXIndex, proc: PROC [RecordSEIndex] RETURNS [BOOL]]
      RETURNS [BOOL] = {
    FOR sei: ISEIndex ← FirstCtxSe[caseCtx], NextSe[sei] UNTIL sei = ISENull DO
      rSei: Type = seb[sei].idInfo;
      WITH variant: seb[rSei] SELECT FROM
	cons =>
	  WITH variant SELECT FROM
	    record => IF proc[LOOPHOLE[rSei]] THEN RETURN [TRUE];
	    ENDCASE => ERROR;
	ENDCASE => NULL;	-- skip multiple identifiers
      ENDLOOP;
    RETURN [FALSE]};

  LayoutFields: PUBLIC PROC [rSei: RecordSEIndex, offset: CARDINAL] = {
    maxRecordSize: CARDINAL = CARDINAL.LAST/WordLength + 1;
    w: WordCount;
    b: CARDINAL;
    lastFillable: BOOL;
    lastSei: ISEIndex;

    AssignField: PROC [sei: ISEIndex] = {
      OPEN id: seb[sei];
      n: FieldBitCount;
      nW, nB: CARDINAL;
      saveIndex: CARDINAL = dataPtr.textIndex;
      dataPtr.textIndex ← tb[LOOPHOLE[id.idValue, Tree.Index]].info;
      n ← BitsForField[sei];
      nW ← n/WordLength;  nB ← n MOD WordLength;
      IF nW > 0 AND nB # 0 THEN {nW ← nW+1; nB ← 0};
      IF (nW > 0 OR b+nB > WordLength OR n = 0) AND b # 0 THEN {w ← w+1; b ← 0};
      dataPtr.textIndex ← saveIndex;
      IF b = 0 AND lastFillable THEN FillWord[lastSei];
      IF w >= maxRecordSize THEN Log.ErrorSei[addressOverflow, sei];
      id.idInfo ← nW*WordLength + nB;
      id.idValue ← BitAddress[wd:w, bd:b];
      lastSei ← sei;  lastFillable ← (nW = 0 AND n # 0);
      w ← w + nW;  b ← b + nB;
      IF b >= WordLength THEN {w ← w+1; b ← b - WordLength};
      IF (IF b=0 THEN w ELSE w+1) >= maxRecordSize THEN
   	Log.ErrorSei[addressOverflow, sei]};

    FillWord: PROC [sei: ISEIndex] = {
      t: BitAddress = seb[sei].idValue;
      width: CARDINAL = WordLength - t.bd;
      IF seb[rSei].machineDep AND width # seb[sei].idInfo THEN
	Log.WarningSei[recordGap, sei];
      seb[sei].idInfo ← width};

    FindFit: PROC [vSei: RecordSEIndex] RETURNS [BOOL] = {
      sei: ISEIndex ← FirstCtxSe[seb[vSei].fieldCtx];
      type: CSEIndex;
      IF sei = ISENull THEN RETURN [FALSE];
      type ← UnderType[seb[sei].idType];
      WITH seb[type] SELECT FROM
	union =>
	  IF controlled THEN sei ← tagSei
	  ELSE RETURN [ScanVariants[caseCtx, FindFit]];
	sequence => IF controlled THEN sei ← tagSei ELSE RETURN [FALSE];
	ENDCASE => NULL;
      RETURN [BitsForType[seb[sei].idType] + b <= WordLength]};

    vOrigin: CARDINAL;
    maxLength: CARDINAL;

    AssignVariant: PROC [vSei: RecordSEIndex] RETURNS [BOOL] = {
      LayoutFields[vSei, vOrigin];
      maxLength ← MAX[seb[vSei].length, maxLength];
      RETURN [FALSE]};

    eqLengths: BOOL;
    padEnd: CARDINAL;

    PadVariant: PROC [vSei: RecordSEIndex] RETURNS [BOOL] = {
      fillSei: ISEIndex ← ISENull;
      type: CSEIndex;
      fillOrigin, currentEnd: CARDINAL;
      ctx: CTXIndex = seb[vSei].fieldCtx;
      FOR sei: ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = ISENull DO
	IF LOOPHOLE[seb[sei].idValue, BitAddress].wd # w THEN EXIT;
	fillSei ← sei;
	ENDLOOP;
      IF fillSei # ISENull THEN {
	fillOrigin ← BitOffset[fillSei];
	currentEnd ← fillOrigin + seb[fillSei].idInfo;
	IF currentEnd < padEnd AND (currentEnd # 0 OR padEnd < WordLength) THEN {
	  type ← UnderType[seb[fillSei].idType];
	  WITH seb[type] SELECT FROM
	    union => {
	      saveLastSei: ISEIndex = lastSei;
	      IF controlled THEN lastSei ← tagSei;	-- for messages only
	      [] ← ScanVariants[caseCtx, PadVariant];
	      lastSei ← saveLastSei};
	    ENDCASE => IF seb[rSei].machineDep THEN Log.WarningSei[recordGap, fillSei];
	  seb[fillSei].idInfo ←  padEnd - fillOrigin}}
      ELSE IF vOrigin < padEnd AND (vOrigin # 0 OR padEnd < WordLength) THEN {
	IF seb[rSei].machineDep THEN Log.WarningSei[recordGap, lastSei];
	fillSei ← MakeCtxSe[nullName, CTXNull];
	seb[fillSei].public ← TRUE;  seb[fillSei].extended ← FALSE;
	seb[fillSei].constant ← seb[fillSei].immutable ← FALSE;
	seb[fillSei].linkSpace ← FALSE;
	seb[fillSei].idType ← dataPtr.idANY;
	seb[fillSei].idValue ← BitAddress[wd:w, bd:b];
	seb[fillSei].idInfo ← padEnd - vOrigin;
	seb[fillSei].mark3 ← seb[fillSei].mark4 ← TRUE;
	WITH seb[fillSei] SELECT FROM linked => link ← ctxb[ctx].seList ENDCASE => ERROR;
	ctxb[ctx].seList ← fillSei};
      seb[vSei].length ← MIN[
		maxLength,
		(seb[vSei].length + wordFill)/WordLength * WordLength];
      IF seb[vSei].length # maxLength THEN eqLengths ← FALSE;
      RETURN [FALSE]};

    type: CSEIndex;
    ctx: CTXIndex = seb[rSei].fieldCtx;
    w ← offset/WordLength;  b ← offset MOD WordLength;
    lastFillable ← FALSE;  lastSei ← ISENull;
    FOR sei: ISEIndex ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = ISENull DO
      IF ~seb[sei].constant THEN {
	type ← UnderType[seb[sei].idType];
	WITH seb[type] SELECT FROM
	  union => {
	    IF ~controlled THEN seb[sei].idValue ← BitAddress[wd:w, bd:b]
	    ELSE {AssignField[tagSei]; seb[sei].idValue ← seb[tagSei].idValue};
	    IF lastFillable AND b # 0 AND ~ScanVariants[caseCtx, FindFit] THEN {
	      FillWord[lastSei]; w ← w+1; b ← 0};
	    maxLength ← vOrigin ← w*WordLength + b;
	    [] ← ScanVariants[caseCtx, AssignVariant];
	    padEnd ← IF maxLength < WordLength
		THEN maxLength
		ELSE MAX[(vOrigin + wordFill)/WordLength, 1]*WordLength;
	    eqLengths ← TRUE;
	    [] ← ScanVariants[caseCtx, PadVariant];
	    hints.equalLengths ← eqLengths;
	    seb[sei].idInfo ←
		(maxLength - vOrigin) + (IF controlled THEN seb[tagSei].idInfo ELSE 0);
	    w ← maxLength/WordLength;  b ← maxLength MOD WordLength;
	    lastFillable ← FALSE};
	  sequence => {
	    IF ~controlled THEN seb[sei].idValue ← BitAddress[wd:w, bd:b]
	    ELSE {AssignField[tagSei]; seb[sei].idValue ← seb[tagSei].idValue};
	    IF lastFillable AND b # 0 THEN {FillWord[lastSei]; w ← w+1; b ← 0};
	    seb[sei].idInfo ← (CARDINAL[w]*WordLength+b) - BitOffset[sei];
	    lastFillable ← FALSE};
	  ENDCASE => AssignField[sei]};
      ENDLOOP;
    IF lastFillable AND b # 0 AND w > 0 THEN {FillWord[lastSei]; b ← 0; w ← w+1};
    seb[rSei].length ← w*WordLength + b};


  CheckFields: PUBLIC PROC [rSei: RecordSEIndex, origin: CARDINAL] = {
    vProfile: Profile;
    vI: CARDINAL;

    CountVar: VarScan = {vI ← vI + 1};

    InsertVar: VarScan = {
      vProfile[vI] ← [link:[symbol[sei]], key:BitOffset[sei]]; vI ← vI+1};

    b, newB: CARDINAL;
    sei, lastSei: ISEIndex;
    vI ← 0;  GenCtxVars[seb[rSei].fieldCtx, CountVar, FALSE];
    vProfile ← AllocateProfile[vI];
    vI ← 0;  GenCtxVars[seb[rSei].fieldCtx, InsertVar, FALSE];
    SortProfile[vProfile];
    b ← origin;  lastSei ← ISENull;
    FOR vI DECREASING IN [0 .. vProfile.length) DO
      sei ← NARROW[vProfile[vI].link, VarLink.symbol].index;
      SELECT TypeForm[seb[sei].idType] FROM
	union => CheckVariants[sei];
	sequence => {
	  IF vI # 0 THEN Log.ErrorSei[recordOverlap, sei];
	  CheckSequence[sei]};
	ENDCASE;
      SELECT (newB ← vProfile[vI].key) FROM
	> b => Log.ErrorSei[recordGap, lastSei];
	< b => Log.ErrorSei[recordOverlap, sei];
	ENDCASE;
      b ← newB + seb[sei].idInfo;  lastSei ← sei;
      ENDLOOP;
    ReleaseProfile[vProfile];
    IF b > WordLength AND b MOD WordLength # 0
      THEN {
	Log.ErrorSei[recordGap, lastSei];
	b ← ((b+wordFill)/WordLength) * WordLength};
    seb[rSei].length ← b};

  CheckVariants: PROC [sei: ISEIndex] = {
    type: CSEIndex = UnderType[seb[sei].idType];
    started: BOOL ← FALSE;
    eqLengths: BOOL ← TRUE;
    gaps: BOOL ← FALSE;
    origin, maxLength, size: CARDINAL;

    CheckVariant: PROC [rSei: RecordSEIndex] RETURNS [BOOL] = {
      length: CARDINAL;
      CheckFields[rSei, origin];  length ← seb[rSei].length;
      IF ~started THEN {maxLength ← length; started ← TRUE}
      ELSE {
	IF length MOD WordLength # 0 OR maxLength MOD WordLength # 0 THEN gaps ← TRUE;
	IF length # maxLength THEN {maxLength ← MAX[length, maxLength]; eqLengths ← FALSE}};
      RETURN [FALSE]};

    origin ← BitOffset[sei];
    WITH union: seb[type] SELECT FROM
      union => {
	IF union.controlled THEN {
	  newOrigin: CARDINAL = BitOffset[union.tagSei];
	  IF origin # newOrigin THEN Log.ErrorSei[fieldPosition, union.tagSei];
	  origin ← newOrigin + seb[union.tagSei].idInfo};
	[] ← ScanVariants[union.caseCtx, CheckVariant];
	size ← maxLength - BitOffset[sei];  union.hints.equalLengths ← eqLengths;
	IF gaps THEN Log.ErrorSei[recordGap, sei];
	SELECT TRUE FROM
	  (seb[sei].idInfo = 0) => seb[sei].idInfo ← size;
	  (size # seb[sei].idInfo) => Log.ErrorSei[fieldPosition, sei];
	  ENDCASE};
      ENDCASE => ERROR};

  CheckSequence: PROC [sei: ISEIndex] = {
    type: CSEIndex = UnderType[seb[sei].idType];
    origin, length: CARDINAL;
    origin ← BitOffset[sei];
    WITH seq: seb[type] SELECT FROM
      sequence => {
	IF seq.controlled THEN {
	  newOrigin: CARDINAL = BitOffset[seq.tagSei];
	  IF origin # newOrigin THEN Log.ErrorSei[fieldPosition, seq.tagSei];
	  origin ← newOrigin + seb[seq.tagSei].idInfo};
	IF origin MOD WordLength # 0 THEN Log.ErrorSei[fieldPosition, sei];
	length ← origin - BitOffset[sei];
	SELECT seb[sei].idInfo FROM
	  0 => seb[sei].idInfo ← length;
	  length => NULL;
	  ENDCASE => Log.ErrorSei[fieldPosition, sei]};
      ENDCASE => ERROR};

  }.