-- File: DebugHeapAlto.mesa - last edit: 
-- Smokey, May 7, 1980 3:59 PM
-- Mark Jul 25, 1980 4:35 PM
-- Evans Mar 21, 1979 10:23 AM
-- Bruce October 3, 1980  1:49 PM
-- HGM February 14, 1981  10:31 PM

DIRECTORY
  Ascii USING [SP],
  ControlDefs USING [GlobalFrameHandle],
  DebugOps USING [StringExpToOctal],
  DebugUsefulDefs USING [Name, ShortREAD, ShortCopyREAD, Enumerate],
  Event USING [AddNotifier, Item, Masks, Notifier],
  FSPDefs USING [
    BlockSize, DestroyZone, FreeNode, MakeNode, MakeNewZone, NodeHeader,
    NodePointer, ZoneHeader, ZonePointer],
  FSP,
  Frames USING [Invalid],
  ImageDefs USING [BcdTime],
  Inline USING [COPY],
  Menu USING [Create, Handle, Instantiate, ItemObject, Items, MakeItem, MCRType],
  NubOps USING [WhereAmI],
  Put USING [CR, Char, Octal, Decimal, CurrentSelection, Line, Text],
  Selection USING [Convert, Number],
  String USING [AppendString, EqualString, InvalidNumber],
  Storage USING [Node, FreePages, FreeString, HeapZone, Pages, Prune],
  TextSW USING [SetEOF],
  Time USING [Append, Unpack],
  Tool USING [Create, MakeFileSW, MakeSWsProc],
  UserTerminal USING [BlinkDisplay],
  Window USING [Handle],
  WindowFont USING [CharWidth];

DebugHeapAlto: PROGRAM
  IMPORTS
    DebugOps, DebugUsefulDefs, Event, FSPDefs, Frames, Inline, ImageDefs, Menu,
    NubOps, Put, Selection, String, Storage, TextSW, Time, Tool, UserTerminal,
    WindowFont
  SHARES FSP, FSPDefs =
  BEGIN OPEN FSPDefs;

  --  global variable declarations

  heapData: HeapData ← [];
  isDebugger: BOOLEAN;
  toolWindow: Window.Handle;
  logSW: Window.Handle;
  event: Event.Item ← [eventMask: Event.Masks[newSession], eventProc: Notify];

  NodeDataHandle: TYPE = POINTER TO NodeData;
  NodeData: TYPE = MACHINE DEPENDENT RECORD [
    link: NodeDataHandle, size, count: CARDINAL];

  HeapData: TYPE = RECORD [
    fspFreeWords: CARDINAL ← 0,
    fspUsedWords: CARDINAL ← 0,
    fspFreeNodes: CARDINAL ← 0,
    fspUsedNodes: CARDINAL ← 0,
    nodeChain: NodeDataHandle ← NIL,
    lookup: BOOLEAN ← TRUE,
    myZone: FSPDefs.ZonePointer ← NULL,
    TheHeap: FSPDefs.ZonePointer ← NULL];

  --  HeapProc support Routines

  Notify: Event.Notifier = {
    IF why # newSession THEN RETURN;
    heapData.TheHeap ← NIL;
    heapData.lookup ← TRUE};

  FindHeap: PROCEDURE [FSPGFH: POINTER TO FRAME[FSP]] =
    BEGIN
    IF ~heapData.lookup THEN RETURN;
    IF isDebugger THEN
      BEGIN
      IF FSPGFH = NIL THEN FSPGFH ← LOOPHOLE[DebugUsefulDefs.Enumerate[Check]];
      IF FSPGFH = NIL THEN heapData.TheHeap ← NIL
      ELSE heapData.TheHeap ← DebugUsefulDefs.ShortREAD[@FSPGFH.TheHeap]
      END
    ELSE heapData.TheHeap ← Storage.HeapZone[];
    IF heapData.TheHeap = NIL THEN Put.Line[logSW, "No FSP found!"L]
    ELSE heapData.lookup ← FALSE;
    END;

  Check: PROCEDURE [gf: ControlDefs.GlobalFrameHandle] RETURNS [BOOLEAN] =
    BEGIN
    mod: STRING ← [40];
    DebugUsefulDefs.Name[
      mod, gf !
      Frames.Invalid =>
	BEGIN
	Put.Text[logSW, "Invalid global frame ["L];
	Put.Octal[logSW, f];
	Put.Line[logSW, "]"L];
	CONTINUE;
	END];
    RETURN[String.EqualString[mod, "FSP"L]]
    END;

  DebugHeapSetup: Tool.MakeSWsProc =
    BEGIN
    s: STRING = [40];
    logSW ← Tool.MakeFileSW[window, "DebugHeap.log"L];
    TextSW.SetEOF[logSW, 0];
    String.AppendString[s, "Debug Heap of "L];
    Time.Append[s, Time.Unpack[ImageDefs.BcdTime[]]];
    Put.Line[logSW, s];
    Put.CR[logSW];
    heapData.myZone ← MakeNewZone[Storage.Pages[3], 3*256, Storage.FreePages];
    Menu.Instantiate[Menu.Create[MakeMenuArray[], "HeapOps"L], window];
    END;

  DebugHeapUndoSetup: PROCEDURE = {FSPDefs.DestroyZone[heapData.myZone]};

  DestroyNodeChain: PROCEDURE =
    BEGIN OPEN FSPDefs, heapData;
    node: NodeDataHandle ← nodeChain;
    UNTIL node = NIL DO
      node ← node.link; FreeNode[myZone, nodeChain]; nodeChain ← node; ENDLOOP;
    END;

  MakeMenuArray: PROCEDURE RETURNS [menuItems: Menu.Items] =
    BEGIN OPEN Menu;
    nItems: CARDINAL = 10;
    menuItems ← DESCRIPTOR[Storage.Node[SIZE[ItemObject]*nItems], nItems];
    menuItems[0] ← MakeItem["Info"L, MCR];
    menuItems[1] ← MakeItem["Zones"L, MCR];
    menuItems[2] ← MakeItem["UsedNodes"L, MCR];
    menuItems[3] ← MakeItem["FreeNodes"L, MCR];
    menuItems[4] ← MakeItem["NodesOfSize"L, MCR];
    menuItems[5] ← MakeItem["AsciiContents"L, MCR];
    menuItems[6] ← MakeItem["OctalContents"L, MCR];
    menuItems[7] ← MakeItem["PruneHeap"L, MCR];
    menuItems[8] ← MakeItem["Set FSP GFH"L, MCR];
    menuItems[9] ← MakeItem["Verbose"L, MCR];
    END;

  MCR: Menu.MCRType =
    BEGIN OPEN heapData;
    n: CARDINAL;
    FindHeap[NIL];
    SELECT index FROM
      0 => DisplayHeapInfo[];
      1 => DisplayHeapZones[];
      2 => DisplayUsedNodes[];
      3 => DisplayFreeNodes[];
      4 =>
	BEGIN
	n ← Selection.Number[ ! String.InvalidNumber => GOTO ret];
	Put.Text[logSW, "NodesOfSize("L];
	Put.CurrentSelection[logSW];
	Put.Text[logSW, "): "L];
	DisplayHeapBlocks[n];
	END;
      5 =>
	BEGIN
	n ← Selection.Number[8 ! String.InvalidNumber => GOTO ret];
	DisplayHeapString[LOOPHOLE[n]];
	END;
      6 =>
	BEGIN
	n ← Selection.Number[8 ! String.InvalidNumber => GOTO ret];
	DisplayHeapItem[LOOPHOLE[n]];
	END;
      7 =>
	IF ~isDebugger THEN [] ← Storage.Prune[] ELSE UserTerminal.BlinkDisplay[];
      8 =>
	BEGIN
	s: STRING;
	FSPGFH: CARDINAL;
	IF ~isDebugger THEN GOTO Blink;
	IF (s ← Selection.Convert[string]) = NIL THEN GOTO Blink;
	FSPGFH ← DebugOps.StringExpToOctal[s];
	IF Check[LOOPHOLE[FSPGFH]] THEN {
	  lookup ← TRUE; FindHeap[LOOPHOLE[FSPGFH]]};
	Storage.FreeString[s];
	EXITS Blink => UserTerminal.BlinkDisplay[];
	END;
      9 => DisplayEverything[];
      ENDCASE;
    Put.CR[logSW];
    EXITS ret => BEGIN UserTerminal.BlinkDisplay[]; RETURN END;
    END;

  --  Records

  TwoChars: TYPE = RECORD [lh: CHARACTER, rh: CHARACTER];

  --  global constant declarations

  UsedNodeSize: FSPDefs.BlockSize = SIZE[inuse NodeHeader];
  FreeSize: FSPDefs.BlockSize = SIZE[free NodeHeader];
  ZoneHeaderSize: FSPDefs.BlockSize = SIZE[ZoneHeader];


  --  Heap (FSP) Display Routines 

  DisplayHeapBlocks: PUBLIC PROCEDURE [size: CARDINAL] =
    BEGIN
    DisplayNodeAddress: PROCEDURE [n: NodePointer, nh: NodeHeader]
      RETURNS [BOOLEAN] =
      BEGIN
      IF nh.length = size AND nh.state = inuse THEN
	BEGIN Put.Octal[logSW, n]; Put.Char[logSW, Ascii.SP]; END;
      RETURN[TRUE]
      END;
    EnumerateHeapNodes[DisplayNodeAddress];
    END;

  DisplayHeapItem: PUBLIC PROCEDURE [p: NodePointer] =
    BEGIN
    i: CARDINAL;
    found: BOOLEAN ← FALSE;
    DisplayNodeContents: PROCEDURE [n: NodePointer, nh: NodeHeader]
      RETURNS [BOOLEAN] =
      BEGIN
      IF n = p THEN
	BEGIN
	FOR i IN [1..nh.length) DO
	  IF i MOD 8 = 1 THEN
	    BEGIN
	    IF i # 1 THEN Put.CR[logSW];
	    Put.Octal[logSW, n + i];
	    Put.Char[logSW, '/];
	    END;
	  Put.Char[logSW, Ascii.SP];
	  Put.Octal[logSW, LocalREAD[n + i]];
	  ENDLOOP;
	found ← TRUE;
	RETURN[FALSE];
	END;
      RETURN[TRUE]
      END;
    EnumerateHeapNodes[DisplayNodeContents];
    IF ~found THEN Put.Line[logSW, " Not a Heap node"L];
    END;

  DisplayHeapString: PUBLIC PROCEDURE [p: NodePointer] =
    BEGIN
    i: CARDINAL;
    word: TwoChars;
    found: BOOLEAN ← FALSE;
    DisplayNodeContents: PROCEDURE [n: NodePointer, nh: NodeHeader]
      RETURNS [BOOLEAN] =
      BEGIN
      IF n = p THEN
	BEGIN
	Put.Octal[logSW, n + 1];
	Put.Char[logSW, '/];
	Put.Text[logSW, " ("L];
	Put.Decimal[logSW, LocalREAD[n + 1]];
	Put.Char[logSW, ',];
	Put.Decimal[logSW, LocalREAD[n + 2]];
	Put.Char[logSW, ')];
	Put.Char[logSW, '"];
	FOR i IN [3..nh.length) DO
	  word ← LocalREAD[n + i];
	  Put.Char[logSW, word.lh];
	  Put.Char[logSW, word.rh];
	  ENDLOOP;
	Put.Char[logSW, '"];
	found ← TRUE;
	RETURN[FALSE]
	END;
      RETURN[TRUE]
      END;
    EnumerateHeapNodes[DisplayNodeContents];
    IF ~found THEN Put.Line[logSW, "Not a Heap string"L];
    END;

  DisplayHeapInfo: PUBLIC PROCEDURE =
    BEGIN OPEN heapData;
    fspFreeWords ← fspUsedWords ← fspFreeNodes ← fspUsedNodes ← 0;
    Put.Text[logSW, "Heap Info:"L];
    EnumerateHeapNodes[CollectFreeNodes];
    DisplayInfo[];
    DestroyNodeChain[];
    END;

  DisplayHeapZones: PUBLIC PROCEDURE =
    BEGIN OPEN FSPDefs, heapData;
    z: ZonePointer;
    zh: ZoneHeader;
    Put.Text[logSW, "Heap Zones (Address,length)"L];
    FOR z ← TheHeap, zh.restOfZone UNTIL z = NIL DO
      zh ← GetZoneHeader[z];
      Put.Char[logSW, Ascii.SP];
      Put.Octal[logSW, z];
      Put.Char[logSW, ',];
      Put.Decimal[logSW, zh.length];
      ENDLOOP;
    END;

  DisplayUsedNodes: PUBLIC PROCEDURE =
    BEGIN OPEN heapData;
    fspFreeWords ← fspUsedWords ← fspFreeNodes ← fspUsedNodes ← 0;
    Put.Line[logSW, "Used nodes (length(count)): "L];
    EnumerateHeapNodes[CollectUsedNodes];
    DisplayHeapNumbers[];
    DestroyNodeChain[];
    END;

  DisplayFreeNodes: PUBLIC PROCEDURE =
    BEGIN OPEN heapData;
    fspFreeWords ← fspUsedWords ← fspFreeNodes ← fspUsedNodes ← 0;
    Put.Line[logSW, "Free nodes (length(count)): "L];
    EnumerateHeapNodes[CollectFreeNodes];
    DisplayHeapNumbers[];
    DestroyNodeChain[];
    END;

  DisplayEverything: PUBLIC PROCEDURE =
    BEGIN OPEN heapData;
    fspFreeWords ← fspUsedWords ← fspFreeNodes ← fspUsedNodes ← 0;
    Put.Line[logSW, "All nodes: "L];
    mode ← new;
    EnumerateHeapNodes[ShowEverything];
    Put.CR[logSW];
    DestroyNodeChain[];
    END;

  mode: {free, used, new};
  ShowEverything: PROCEDURE [n: NodePointer, nh: NodeHeader] RETURNS [BOOLEAN] =
    BEGIN OPEN heapData;
    WITH nh SELECT FROM
      inuse =>
	BEGIN
	IF mode # used THEN
	  BEGIN Put.CR[logSW]; Put.Text[logSW, "Used: "L]; mode ← used; END
	ELSE Put.Text[logSW, ", "];
	END;
      free =>
	BEGIN
	IF mode # free THEN
	  BEGIN Put.CR[logSW]; Put.Text[logSW, "Free: "L]; mode ← free; END
	ELSE Put.Text[logSW, ", "];
	END;
      ENDCASE;
    Put.Octal[logSW, n];
    Put.Text[logSW, " "L];
    Put.Decimal[logSW, nh.length];
    RETURN[TRUE];
    END;

  CollectUsedNodes: PROCEDURE [n: NodePointer, nh: NodeHeader] RETURNS [BOOLEAN] =
    BEGIN OPEN heapData;
    WITH nh SELECT FROM
      inuse =>
	BEGIN
	node: NodeDataHandle ← GetNodeData[length];
	node.count ← node.count + 1;
	fspUsedWords ← fspUsedWords + length;
	fspUsedNodes ← fspUsedNodes + 1;
	END;
      free =>
	BEGIN
	fspFreeWords ← fspFreeWords + length;
	fspFreeNodes ← fspFreeNodes + 1;
	END;
      ENDCASE;
    RETURN[TRUE];
    END;

  CollectFreeNodes: PROCEDURE [n: NodePointer, nh: NodeHeader] RETURNS [BOOLEAN] =
    BEGIN OPEN heapData;
    WITH nh SELECT FROM
      inuse =>
	BEGIN
	fspUsedWords ← fspUsedWords + length;
	fspUsedNodes ← fspUsedNodes + 1;
	END;
      free =>
	BEGIN
	node: NodeDataHandle ← GetNodeData[length];
	node.count ← node.count + 1;
	fspFreeWords ← fspFreeWords + length;
	fspFreeNodes ← fspFreeNodes + 1;
	END;
      ENDCASE;
    RETURN[TRUE];
    END;

  GetNodeData: PROCEDURE [length: CARDINAL] RETURNS [NodeDataHandle] =
    BEGIN OPEN heapData;
    node: NodeDataHandle ← LOOPHOLE[@heapData.nodeChain];
    DO
      IF node.link = NIL OR node.link.size > length THEN -- put new guy here
	BEGIN
	newNode: NodeDataHandle ← FSPDefs.MakeNode[myZone, SIZE[NodeData]];
	newNode↑ ← [link: node.link, size: length, count: 0];
	node.link ← newNode;
	RETURN[newNode];
	END;
      IF node.link.size = length THEN RETURN[node.link];
      node ← node.link;
      ENDLOOP;
    END;

  DisplayInfo: PUBLIC PROCEDURE =
    BEGIN OPEN heapData;
    Put.Text[logSW, "  Free words: "L];
    Put.Decimal[logSW, fspFreeWords];
    Put.Text[logSW, ", Free nodes: "L];
    Put.Decimal[logSW, fspFreeNodes];
    Put.Text[logSW, ", Used words: "L];
    Put.Decimal[logSW, fspUsedWords];
    Put.Text[logSW, ", Used nodes: "L];
    Put.Decimal[logSW, fspUsedNodes];
    END;

  DisplayHeapNumbers: PROCEDURE =
    BEGIN OPEN heapData;
    node: NodeDataHandle ← nodeChain;
    i: CARDINAL ← 0;
    CommaState: TYPE = {notGoing, sameLine, newLine};
    commaState: CommaState ← notGoing;
    itemsPerLine: CARDINAL =
      IF logSW = NIL THEN 5 ELSE logSW.box.dims.w/(WindowFont.CharWidth['W]*9);
    UNTIL node = NIL DO
      SELECT commaState FROM
	notGoing => commaState ← sameLine;
	sameLine => Put.Text[logSW, ", "L];
	newLine => {Put.Line[logSW, ","L]; commaState ← sameLine};
	ENDCASE;
      Put.Decimal[logSW, node.size];
      Put.Char[logSW, '(];
      Put.Decimal[logSW, node.count];
      Put.Char[logSW, ')];
      IF i = itemsPerLine THEN {i ← 0; commaState ← newLine} ELSE i ← i + 1;
      node ← node.link;
      ENDLOOP;
    END;

  --  routines to process the Heap

  EnumerateHeapNodes: PUBLIC PROCEDURE [
    proc: PROCEDURE [NodePointer, NodeHeader] RETURNS [BOOLEAN]] =
    BEGIN OPEN FSPDefs, heapData;
    -- funny code in here so this routine can be used in the Debbuger!
    rest: ZonePointer;
    z: ZonePointer;
    zh: ZoneHeader ← GetZoneHeader[TheHeap];
    node: NodePointer;
    nh: NodeHeader;
    nodeLength: BlockSize;
    FOR z ← TheHeap, rest UNTIL z = NIL DO
      zh ← GetZoneHeader[z];
      rest ← zh.restOfZone;
      FOR node ← LOOPHOLE[z + ZoneHeaderSize, NodePointer], node + nodeLength DO
	nh ← GetNodeHeader[node];
	IF nh.length = UsedNodeSize THEN EXIT;
	IF ~proc[node, nh] THEN RETURN;
	nodeLength ← nh.length;
	ENDLOOP;
      ENDLOOP;
    END;

  GetZoneHeader: PROCEDURE [z: ZonePointer] RETURNS [zh: ZoneHeader] =
    BEGIN LocalCopyRead[@zh, z, SIZE[ZoneHeader]]; END;

  GetNodeHeader: PROCEDURE [n: NodePointer] RETURNS [nh: NodeHeader] =
    BEGIN LocalCopyRead[@nh, n, SIZE[NodeHeader]]; END;

  LocalCopyRead: PROCEDURE [to, from: POINTER, count: CARDINAL] =
    BEGIN
    IF isDebugger THEN
      DebugUsefulDefs.ShortCopyREAD[to: to, from: from, nwords: count]
    ELSE Inline.COPY[to: to, from: from, nwords: count]
    END;

  LocalREAD: PROCEDURE [address: POINTER] RETURNS [word: UNSPECIFIED] =
    BEGIN
    RETURN[IF isDebugger THEN DebugUsefulDefs.ShortREAD[address] ELSE address↑];
    END;

  --  Mainline code

  Init: PROC =
    BEGIN
    isDebugger ←
      SELECT NubOps.WhereAmI[] FROM
	debugger, internaldebugger => TRUE,
	ENDCASE => FALSE;
    Event.AddNotifier[@event];
    toolWindow ← Tool.Create[
      makeSWsProc: DebugHeapSetup, name: "DebugHeap"L, initialState: default,
      movableBoundaries: FALSE];
    END;

  Init[];

  END...