From 4a30b6432b9df8120f0240a77829b09fe7f58060 Mon Sep 17 00:00:00 2001
From: Jeffrey Lee <jlee@gitlab.riscosopen.org>
Date: Sat, 10 Dec 2011 19:03:49 +0000
Subject: [PATCH] Improve heap manager. Add heap testbed. Add dummy
 implementation of some OS_ScreenMode reason codes.

Detail:
  s/HeapMan, hdr/KernelWS - Heap manager improvements:
    - Errors generated by interrupted heap operations that are forced to complete by a OS_Heap call from the background are now cached in kernel workspace until the foreground task is resumed. This prevents them from being potentially overwritten by MessageTrans running out of background error buffers.
    - Added new OS_Heap reason code, #7 - Get area aligned. This allows areas of memory to be allocated at specific (power-of-2) alignments, and optionally without crossing a given (power-of-2) boundary. Alignment & boundary calculations are performed using logical addresses.
    - Removed the limitation that all free and allocated blocks must be a multiple of 8 bytes in length. This change was required in order to allow OS_Heap 7 to function correctly. Now the only requirements are that blocks must be multiples of 4 bytes in length, at 4 byte alignment, with a minimum length of 8 bytes. 4 extra padding bytes may still be added to the end of allocations in order to avoid creating 4-byte free blocks.
  s/HeapMan, TestSrc/HeapTest/Makefile, TestSrc/HeapTest/c/testbed, TestSrc/HeapTest/s/asm - Added heap testbed program. Can either use the OS_Heap SWI or directly include a copy of the Kernel's heap manager sources.
  s/vdudecl, s/vduswis - Added dummy implementations of OS_ScreenMode 4, 5 and 6. This prevents the Wimp generating lots of "Unknown OS_ScreenMode reason code" errors when redrawing the screen.
  s/Arthur3, s/Oscli - Moved dotstring closer to where it's used to avoid "ADRL out of range" errors in Tungsten build
Admin:
  Tested in OMAP3 ROM & Tungsten ROM softload.
  Heap testbed successfully performed over 400 million heap ops, so there shouldn't be any serious bugs in the new code (touch wood)


Version 5.35, 4.79.2.128. Tagged as 'Kernel-5_35-4_79_2_128'
---
 Dev/HeapTest/Makefile  |  20 ++
 Dev/HeapTest/c/testbed | 500 +++++++++++++++++++++++++++++++++++++++++
 Dev/HeapTest/s/asm     | 113 ++++++++++
 VersionASM             |  10 +-
 VersionNum             |  14 +-
 hdr/KernelWS           |   2 +
 s/Arthur3              |   2 +
 s/HeapMan              | 458 +++++++++++++++++++++++++++++++++----
 s/Oscli                |   3 +-
 s/vdu/vdudecl          |   5 +-
 s/vdu/vduswis          |  54 ++++-
 11 files changed, 1124 insertions(+), 57 deletions(-)
 create mode 100644 Dev/HeapTest/Makefile
 create mode 100644 Dev/HeapTest/c/testbed
 create mode 100644 Dev/HeapTest/s/asm

diff --git a/Dev/HeapTest/Makefile b/Dev/HeapTest/Makefile
new file mode 100644
index 0000000..0bc4f42
--- /dev/null
+++ b/Dev/HeapTest/Makefile
@@ -0,0 +1,20 @@
+# Copyright 2011 Castle Technology Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+COMPONENT = HeapTest
+OBJS = asm testbed
+
+include CApp
+
+# Dynamic dependencies:
diff --git a/Dev/HeapTest/c/testbed b/Dev/HeapTest/c/testbed
new file mode 100644
index 0000000..420ee13
--- /dev/null
+++ b/Dev/HeapTest/c/testbed
@@ -0,0 +1,500 @@
+/* Copyright 2011 Castle Technology Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* OS_Heap testbed code. Creates a heap and randomly allocates/deallocates/
+   resizes blocks of memory to test for any bugs. Tests can either be performed
+   using the OS_Heap SWI or by compiling a special version of the heap code
+   from the kernel source folder (see USE_LOCAL_OSHEAP #define and s.asm)
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include "kernel.h"
+#include "swis.h"
+
+#include "Global/Heap.h"
+#include "Global/NewErrors.h"
+
+/* Whether to use the XOS_Heap SWI or the local copy of the heap code */
+#define USE_LOCAL_OSHEAP
+
+/* Maximum number of allocations to make */
+#define MAX_ALLOCS 1024
+
+#define VBIT (1<<28)
+
+#ifdef USE_LOCAL_OSHEAP
+extern _kernel_oserror *CallHeap(_kernel_swi_regs *r);
+#else
+#define CallHeap(R) _kernel_swi(OS_Heap,R,R)
+#endif
+
+/* Workspace */
+static uint32_t *heap=NULL; /* Main heap */
+static uint32_t *backup=NULL; /* Backup copy of heap */
+static uint32_t allocsize=0; /* Heap memory block size */
+static uint32_t *usedspace=NULL; /* Bitmap of used space; 1 bit per word */
+static uint32_t seed=0; /* RNG seed */
+static uint32_t sequence=0; /* Number of ops performed */
+static uint32_t numblocks=0; /* Number of blocks currently allocated */
+static uint32_t blocks[MAX_ALLOCS]; /* Offsets of blocks within heap */
+static uint32_t currentop = 0; /* Current heap operation */
+static uint32_t opsleft = 0; /* Number of ops left */
+static _kernel_swi_regs r;
+static _kernel_swi_regs last;
+
+/* Utility functions */
+
+static void init(void)
+{
+	srand(seed);
+	printf("Seed %08x alloc size %08x\n",seed,allocsize);
+	/* Make heap 4K aligned */
+	heap = (uint32_t *) (((uint32_t) malloc(allocsize+4096)+4095)&0xfffff000);
+	/* Same for backup */
+	backup = (uint32_t *) (((uint32_t) malloc(allocsize+4096)+4095)&0xfffff000);
+	/* Used space map */
+	usedspace = (uint32_t *) malloc(((allocsize+31*4)>>7)<<2);
+	memset(usedspace,0,((allocsize+31*4)>>7)<<2);
+	memset(heap,0,allocsize);
+	memset(backup,0,allocsize);
+}
+
+static uint32_t getrand(uint32_t max)
+{
+	uint64_t temp = ((uint64_t) max)*rand();
+	return (uint32_t) (temp/RAND_MAX);
+}
+
+static void dumpheap(uint32_t *h)
+{
+	fprintf(stderr,"heap @ %p:\nmag %x\nfree %x\nbase %x\nend %x\n",h,h[0],h[1],h[2],h[3]);
+	uint32_t free = h[1];
+	uint32_t next = 16;
+	if(free)
+		free += 4;
+	while(free)
+	{
+		if(free > next)
+		{
+			fprintf(stderr,"allocs between %x and %x:\n",next,free);
+			do {
+				fprintf(stderr,"%x: alloc size %x\n",next,h[next>>2]);
+				if((h[next>>2] > h[2]) || (h[next>>2]+next > h[2]) || (h[next>>2]&3) || !h[next>>2])
+				{
+					fprintf(stderr,"bad block, skipping rest\n");
+					break;
+				}
+				next += h[next>>2];
+			} while(free>next);
+			if(free!=next)
+				fprintf(stderr,"alloc mismatch! next=%x\n",next);
+		}
+		fprintf(stderr,"%x: free size %x next %x\n",free,h[(free+4)>>2],h[free>>2]);
+		if(h[(free+4)>>2] == h[free>>2])
+			fprintf(stderr,"consecutive free blocks!\n");
+		next = free+h[(free+4)>>2];
+		if((h[free>>2] & 3) || (h[free>>2] >= h[2]) || (h[free>>2]+free >= h[2]))
+		{
+			fprintf(stderr,"bad next ptr\n");
+			return;
+		}
+		if((h[(free+4)>>2] & 3) || (h[(free+4)>>2] >= h[2]) || (h[(free+4)>>2]+free >= h[2]))
+		{
+			fprintf(stderr,"bad size\n");
+			return;
+		}
+		if(!h[free>>2])
+		{
+			fprintf(stderr,"end of free list\n");
+			break;
+		}
+		free = free+h[free>>2];
+		if(free<next)
+		{
+			fprintf(stderr,"next free is inside current?\n");
+			return;
+		}
+	}
+	if(free > h[2])
+	{
+		fprintf(stderr,"free list extends beyond heap end\n");
+	}
+	if(next > h[2])
+	{
+		fprintf(stderr,"next ptr beyond heap end\n");
+	}
+	fprintf(stderr,"end allocs:\n");
+	while(next < h[2])
+	{
+		fprintf(stderr,"%x: alloc size %x\n",next,h[next>>2]);
+		if((h[next>>2] > h[2]) || (h[next>>2]+next > h[2]) || (h[next>>2]&3) || !h[next>>2])
+		{
+			fprintf(stderr,"bad block, skipping rest\n");
+			return;
+		}
+		next += h[next>>2];
+	}
+	fprintf(stderr,"end\n");
+}
+
+static bool heapvalid(uint32_t *h)
+{
+	uint32_t free = h[1];
+	uint32_t next = 16;
+	if(free)
+		free += 4;
+	while(free)
+	{
+		if(free > next)
+		{
+			do {
+				if((h[next>>2] > h[2]) || (h[next>>2]+next > h[2]) || (h[next>>2]&3) || !h[next>>2])
+				{
+					return false;
+				}
+				next += h[next>>2];
+			} while(free>next);
+			if(free!=next)
+				return false;
+		}
+		if(h[(free+4)>>2] == h[free>>2])
+			return false;
+		next = free+h[(free+4)>>2];
+		if((h[free>>2] & 3) || (h[free>>2] >= h[2]) || (h[free>>2]+free >= h[2]))
+		{
+			return false;
+		}
+		if((h[(free+4)>>2] & 3) || (h[(free+4)>>2] >= h[2]) || (h[(free+4)>>2]+free >= h[2]))
+		{
+			return false;
+		}
+		if(!h[free>>2])
+		{
+			break;
+		}
+		free = free+h[free>>2];
+		if(free<next)
+		{
+			return false;
+		}
+	}
+	if(free > h[2])
+	{
+		return false;
+	}
+	if(next > h[2])
+	{
+		return false;
+	}
+	while(next < h[2])
+	{
+		if((h[next>>2] > h[2]) || (h[next>>2]+next > h[2]) || (h[next>>2]&3) || !h[next>>2])
+		{
+			return false;
+		}
+		next += h[next>>2];
+	}
+	return true;
+}
+
+static void fail(void)
+{
+	fprintf(stderr,"Failed on sequence %d\n",sequence);
+	fprintf(stderr,"Last op registers:\n");
+	for(int i=0;i<5;i++)
+		fprintf(stderr,"r%d = %08x\n",i,last.r[i]);
+	fprintf(stderr,"Result registers:\n");
+	for(int i=0;i<5;i++)
+		fprintf(stderr,"r%d = %08x\n",i,r.r[i]);
+	fprintf(stderr,"Heap before op:\n");
+	dumpheap(backup);
+	fprintf(stderr,"Heap after op:\n");
+	dumpheap(heap);
+	fprintf(stderr,"Allocated blocks:\n");
+	for(uint32_t i=0;i<numblocks;i++)
+	{
+		fprintf(stderr,"%08x\n",blocks[i]);
+	}
+	exit(1);
+}
+
+static uint32_t blocksize(uint32_t offset)
+{
+	return heap[(offset-4)>>2];
+}
+
+static void tryuse(uint32_t offset)
+{
+	uint32_t len = blocksize(offset);
+	if((len-4 > allocsize-offset) || (len & 3) || (len<4))
+	{
+		fprintf(stderr,"tryuse: Bad block at %08x\n",offset);
+		fail();
+	}
+	offset >>= 2;
+	while(len)
+	{
+		if(usedspace[offset>>5] & (1<<(offset&31)))
+		{
+			fprintf(stderr,"tryuse: Overlapping block at %08x\n",offset<<2);
+			fail();
+		}
+		usedspace[offset>>5] |= 1<<(offset&31);
+		offset++;
+		len -= 4;
+	}
+}
+
+static void tryfree(uint32_t offset)
+{
+	uint32_t len = blocksize(offset);
+	if((len-4 > allocsize-offset) || (len & 3) || (len<4))
+	{
+		fprintf(stderr,"tryfree: Bad block at %08x\n",offset);
+		fail();
+	}
+	offset >>= 2;
+	while(len)
+	{
+		if(!(usedspace[offset>>5] & (1<<(offset&31))))
+		{
+			fprintf(stderr,"tryfree: Block at %08x already freed\n",offset<<2);
+			fail();
+		}
+		usedspace[offset>>5] -= 1<<(offset&31);
+		offset++;
+		len -= 4;
+	}
+}
+
+/* Main function */
+
+int main(int argc,char **argv)
+{
+	_kernel_oserror *err;
+
+	/* TODO - Take parameters from command line */
+	_swix(OS_ReadMonotonicTime,_OUT(0),&seed)
+	allocsize = 8*1024;
+
+	init();
+
+	/* Initialise heap */
+	r.r[0] = HeapReason_Init;
+	r.r[1] = (int) heap;
+	r.r[3] = allocsize;
+	err = CallHeap(&r);
+	if(err)
+	{
+		fprintf(stderr,"Heap initialise failed! %s\n",err->errmess);
+		exit(1);
+	}
+	usedspace[0] = 0xf;
+
+	/* Begin tests */
+	uint32_t temp,temp2,temp3,temp4;
+	while(heapvalid(heap))
+	{
+		if(!opsleft)
+		{
+			opsleft = getrand(128);
+			switch(getrand(4))
+			{
+			case 0:
+				currentop = HeapReason_Get;
+				break;
+			case 1:
+				currentop = HeapReason_Free;
+				break;
+			case 2:
+				currentop = HeapReason_ExtendBlock;
+				break;
+			default:
+				currentop = HeapReason_GetAligned;
+				break;
+			}
+		}
+		if(!(sequence&0xffff))
+		{
+//			printf(".");
+			dumpheap(heap);
+		}
+		sequence++;
+		r.r[0] = currentop;
+		memcpy(backup,heap,allocsize);
+		switch(currentop)
+		{
+		case HeapReason_Get:
+			if(numblocks == MAX_ALLOCS)
+			{
+				opsleft = 0;
+				break;
+			}
+			r.r[3] = temp = getrand(allocsize>>5)+1;
+			last = r;
+			err = CallHeap(&r);
+			if(err)
+			{
+				if(err->errnum != ErrorNumber_HeapFail_Alloc)
+				{
+					fprintf(stderr,"Failed allocating %08x bytes: %s\n",temp,err->errmess);
+					fail();
+				}
+			}
+			else
+			{
+				temp2 = blocks[numblocks++] = r.r[2]-((uint32_t)heap);
+				if(blocksize(temp2) < temp+4)
+				{
+					fprintf(stderr,"Failed to allocate requested block size: %08x bytes at %08x\n",temp,temp2);
+					fail();
+				}
+				tryuse(temp2);
+			}
+			break;
+		case HeapReason_Free:
+			if(!numblocks)
+			{
+				opsleft = 0;
+				break;
+			}
+			temp = getrand(numblocks);
+			r.r[2] = blocks[temp]+((uint32_t) heap);
+			tryfree(blocks[temp]); /* Must free beforehand */
+			last = r;
+			err = CallHeap(&r);
+			if(err)
+			{
+				fprintf(stderr,"Failed freeing block at %08x: %s\n",blocks[temp],err->errmess);
+				fail();
+			}
+			blocks[temp] = blocks[--numblocks];
+			break;
+		case HeapReason_ExtendBlock:
+			if(!numblocks)
+			{
+				opsleft = 0;
+				break;
+			}
+			temp = getrand(numblocks);
+			r.r[2] = blocks[temp]+((uint32_t) heap);
+			temp2 = getrand(allocsize>>4)-(allocsize>>5);
+			r.r[3] = temp2;
+			temp3 = blocksize(blocks[temp]);
+			tryfree(blocks[temp]); /* Must free beforehand */
+			last = r;
+			err = CallHeap(&r);
+			if(err)
+			{
+				if(err->errnum != ErrorNumber_HeapFail_Alloc)
+				{
+					fprintf(stderr,"Failed resizing block at %08x by %08x bytes: %s\n",blocks[temp],(int) temp2,err->errmess);
+					fail();
+				}
+				if(blocksize(blocks[temp]) != temp3)
+				{
+					fprintf(stderr,"Resize failed but block size changed\n");
+					fail();
+				}
+				tryuse(blocks[temp]);
+			}
+			else
+			{
+				if(r.r[2] && (r.r[2] != 0xffffffff))
+				{
+					if((int) (temp3+temp2) <= 4)
+					{
+						fprintf(stderr,"Resized block was kept when it should have been freed: block %08x by %08x\n",blocks[temp],(int) temp2);
+						fail();
+					}
+					blocks[temp] = r.r[2]-((uint32_t)heap);
+					tryuse(blocks[temp]);
+					if((blocksize(blocks[temp])-(temp3+temp2)) > 7)
+					{
+						fprintf(stderr,"Failed to resize block by required amount: block %08x by %08x\n",blocks[temp],(int) temp2);
+						fail();
+					}
+				}
+				else
+				{
+					if((int) (temp3+temp2) > 4)
+					{
+						fprintf(stderr,"Resized block was freed when it should have remained: block %08x by %08x\n",blocks[temp],(int) temp2);
+						fail();
+					}
+					blocks[temp] = blocks[--numblocks];
+				}
+			}
+			break;
+		case HeapReason_GetAligned:
+			if(numblocks == MAX_ALLOCS)
+			{
+				opsleft = 0;
+				break;
+			}
+			r.r[3] = temp = getrand(allocsize>>4)+1;
+			temp2 = 4<<getrand(9); /* Max 2K alignment (heap 4K aligned) */
+			temp3 = temp2*(1<<getrand(5));
+			if(temp3 > 4096) /* Max 2K boundary (heap 4K aligned) */
+				temp3 = 2048;
+			if(temp3 < temp)
+				temp3 = 0;
+			r.r[2] = temp2;
+			r.r[4] = temp3;
+			last = r;
+			err = CallHeap(&r);
+			if(err)
+			{
+				if(err->errnum != ErrorNumber_HeapFail_Alloc)
+				{
+					fprintf(stderr,"Failed allocating %08x bytes at alignment %08x boundary %08x: %s\n",temp,temp2,temp3,err->errmess);
+					fail();
+				}
+			}
+			else
+			{
+				temp4 = blocks[numblocks++] = r.r[2]-((uint32_t) heap);
+				if(blocksize(temp4) < temp+4)
+				{
+					fprintf(stderr,"Failed to allocate requested block size: %08x bytes at alignment %08x boundary %08x at %08x\n",temp,temp2,temp3,temp4);
+					fail();
+				}
+				if(temp4 & (temp2-1))
+				{
+					fprintf(stderr,"Block allocated at wrong alignment: %08x bytes at alignment %08x boundary %08x at %08x\n",temp,temp2,temp3,temp4);
+					fail();
+				}
+				if(temp3 && ((temp4 & ~(temp3-1)) != ((temp4+temp-1) & ~(temp3-1))))
+				{
+					fprintf(stderr,"Block crosses boundary: %08x bytes at alignment %08x boundary %08x at %08x\n",temp,temp2,temp3,temp4);
+					fail();
+				}
+				tryuse(temp4);
+			}
+			break;
+		}
+		if(opsleft)
+			opsleft--;
+	}
+	fprintf(stderr,"Heap corruption detected!\n");
+	fail();
+	return 0;
+}
diff --git a/Dev/HeapTest/s/asm b/Dev/HeapTest/s/asm
new file mode 100644
index 0000000..f136f9e
--- /dev/null
+++ b/Dev/HeapTest/s/asm
@@ -0,0 +1,113 @@
+; Copyright 2011 Castle Technology Ltd
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;     http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+
+; Assembler gubbins to allow a local copy of the heap manager to be used by
+; the testbed application
+
+
+        GET     Hdr:ListOpts
+        GET     Hdr:Macros
+        GET     Hdr:System
+        GET     Hdr:CPU.Arch
+        GET     Hdr:Machine.<Machine>
+        GET     Hdr:Heap
+        GET     Hdr:Proc
+        GET     Hdr:FSNumbers
+        GET     Hdr:HighFSI
+        GET     Hdr:NewErrors
+
+; Disable internationalisation
+              GBLL International
+International SETL {FALSE}
+
+; Indicate we're compiling the testbed
+            GBLL HeapTestbed
+HeapTestbed SETL {TRUE}
+
+; Heap debugging disabled for now
+           GBLL DebugHeaps
+DebugHeaps SETL {FALSE}
+
+; Fake zero page workspace
+
+                    ^ 0
+IRQsema             # 4
+HeapSavedReg_R0     # 4
+HeapSavedReg_R1     # 4
+HeapSavedReg_R2     # 4
+HeapSavedReg_R3     # 4
+HeapSavedReg_R4     # 4
+HeapSavedReg_R13    # 4
+HeapReturnedReg_R0  # 4
+HeapReturnedReg_R1  # 4
+HeapReturnedReg_R2  # 4
+HeapReturnedReg_R3  # 4
+HeapReturnedReg_R4  # 4
+HeapReturnedReg_R13 # 4
+HeapReturnedReg_PSR # 4
+ZeroPageSize        * @
+
+; Macros and other bits
+
+        MACRO
+        assert  $condition
+ [ :LNOT: ($condition)
+ ! 1,"Assert failed: $condition"
+ ]
+        MEND
+
+SVC2632 * SVC32_mode
+
+
+        AREA    testbeddata, DATA
+
+ZeroPage   % ZeroPageSize
+
+HeapBackgroundError  % 256
+
+        AREA    testbedcode, CODE
+
+; C interface
+
+          EXPORT  CallHeap
+CallHeap  ROUT
+          ; r0 = _kernel_swi_regs ptr for input/output
+          ; Returns error ptr in r0
+          Push    "r0,r4-r11,lr"
+          LDMIA   r0,{r0-r9}
+          SWI     OS_EnterOS ; Call in SVC mode?
+          BL      DoCallXOSHeap
+          MOVVS   r10,r0
+          MOVVC   r10,#0
+          SWI     OS_LeaveOS
+          Pull    "r11"
+          STMIA   r11,{r0-r9}
+          MOV     r0,r10
+          Pull    "r4-r11,pc"
+
+; Assembler bits for use by heap code
+
+DoCallXOSHeap
+          ; Fake an XOS_Heap SWI
+          ; Preserve r10-r12 and enter with PSR in lr
+          Push    "r10-r12,lr"
+          MRS     lr,CPSR
+          B       HeapEntry
+
+; Main heap manager code
+
+        GET      ^.^.s.HeapMan
+
+        END
diff --git a/VersionASM b/VersionASM
index 8c4cd11..5648e2f 100644
--- a/VersionASM
+++ b/VersionASM
@@ -13,11 +13,11 @@
                         GBLS    Module_ComponentPath
 Module_MajorVersion     SETS    "5.35"
 Module_Version          SETA    535
-Module_MinorVersion     SETS    "4.79.2.127"
-Module_Date             SETS    "27 Nov 2011"
-Module_ApplicationDate  SETS    "27-Nov-11"
+Module_MinorVersion     SETS    "4.79.2.128"
+Module_Date             SETS    "10 Dec 2011"
+Module_ApplicationDate  SETS    "10-Dec-11"
 Module_ComponentName    SETS    "Kernel"
 Module_ComponentPath    SETS    "castle/RiscOS/Sources/Kernel"
-Module_FullVersion      SETS    "5.35 (4.79.2.127)"
-Module_HelpVersion      SETS    "5.35 (27 Nov 2011) 4.79.2.127"
+Module_FullVersion      SETS    "5.35 (4.79.2.128)"
+Module_HelpVersion      SETS    "5.35 (10 Dec 2011) 4.79.2.128"
                         END
diff --git a/VersionNum b/VersionNum
index 01410f1..bb184ae 100644
--- a/VersionNum
+++ b/VersionNum
@@ -5,19 +5,19 @@
  *
  */
 #define Module_MajorVersion_CMHG        5.35
-#define Module_MinorVersion_CMHG        4.79.2.127
-#define Module_Date_CMHG                27 Nov 2011
+#define Module_MinorVersion_CMHG        4.79.2.128
+#define Module_Date_CMHG                10 Dec 2011
 
 #define Module_MajorVersion             "5.35"
 #define Module_Version                  535
-#define Module_MinorVersion             "4.79.2.127"
-#define Module_Date                     "27 Nov 2011"
+#define Module_MinorVersion             "4.79.2.128"
+#define Module_Date                     "10 Dec 2011"
 
-#define Module_ApplicationDate          "27-Nov-11"
+#define Module_ApplicationDate          "10-Dec-11"
 
 #define Module_ComponentName            "Kernel"
 #define Module_ComponentPath            "castle/RiscOS/Sources/Kernel"
 
-#define Module_FullVersion              "5.35 (4.79.2.127)"
-#define Module_HelpVersion              "5.35 (27 Nov 2011) 4.79.2.127"
+#define Module_FullVersion              "5.35 (4.79.2.128)"
+#define Module_HelpVersion              "5.35 (10 Dec 2011) 4.79.2.128"
 #define Module_LibraryVersionInfo       "5:35"
diff --git a/hdr/KernelWS b/hdr/KernelWS
index f32be4f..5e413f9 100644
--- a/hdr/KernelWS
+++ b/hdr/KernelWS
@@ -1980,6 +1980,8 @@ ROMBuildDate          #  128
 NewFX0Error           #  64
  ]
 
+HeapBackgroundError   #  256 ; For storing errors generated in the background by the forced completion of a foreground heap op
+
 KbuffsEnd             #  0
 KbuffsSize            *  KbuffsEnd - KbuffsBaseAddress  ;size of Kernel buffers area
 
diff --git a/s/Arthur3 b/s/Arthur3
index 76644e9..12bef03 100644
--- a/s/Arthur3
+++ b/s/Arthur3
@@ -1545,6 +1545,8 @@ ReadNumAuto Entry "r1,r3,r4"
 
 AutoString
         =       "Auto", 0
+dotstring
+        =       ".", 0
         ALIGN
 
 ReadSizeParm ROUT
diff --git a/s/HeapMan b/s/HeapMan
index 839f49e..794e7a4 100644
--- a/s/HeapMan
+++ b/s/HeapMan
@@ -33,6 +33,11 @@ TubeInfo      SETL {FALSE}
         GBLL    debheap
 debheap SETL    1=0
 
+    [ :LNOT: :DEF: HeapTestbed
+              GBLL HeapTestbed
+HeapTestbed   SETL {FALSE}
+    ]
+
  [ DebugHeaps
 FreeSpaceDebugMask * &04000000
 UsedSpaceDebugMask * &08000000
@@ -43,11 +48,11 @@ Nil     *       0
 hpd     RN      r1      ; The punter sees these
 addr    RN      r2
 size    RN      r3
+work    RN      r4
 
 HpTemp  RN      r10     ; But not these
 tp      RN      r11
 bp      RN      r12
-work    RN      r4      ; This is the only one we have to save.
 
 ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ; +                     H E A P   O R G A N I S A T I O N                     +
@@ -103,7 +108,7 @@ freblksize #    0
 
 ; The link field is Nil (0) for the last block in the list
 
-; Block sizes must be forced to a multiple of 8 bytes for subsequent link and
+; Block sizes must be forced to a minimum of 8 bytes for subsequent link and
 ; size information to be stored in them if they are disposed of by the user.
 
 ; They must also be capable of storing a 4 byte size field while allocated.
@@ -124,6 +129,17 @@ $label  BL      ValidateHpdSubr
         MEND
 
 
+; Call XOS_Heap SWI
+
+        MACRO
+        CallXOSHeap
+      [ HeapTestbed
+        BL      DoCallXOSHeap
+      |
+        SWI     XOS_Heap
+      ]
+        MEND
+
 ;****************************************************************************
 
 ; These bits of ExtendBlock are outside the IRQ HeapOp range because they
@@ -157,7 +173,7 @@ ReallocateInSafeZone
         Push    addr                    ; save for later freeing
 
         MOV     R0, #HeapReason_Get
-        SWI     XOS_Heap
+        CallXOSHeap
         Pull    addr, VS
         BVS     SafeNaffExtension
 
@@ -192,11 +208,11 @@ CopyForExtension
 
         MOV     R0, #HeapReason_Free
         Pull    addr                    ; heap block addr
-        SWI     XOS_Heap
+        CallXOSHeap
 
-        MOV     R0, #HeapReason_ExtendBlock
+        MOVVC   R0, #HeapReason_ExtendBlock
         WritePSRc SVC_mode + I_bit,work ; disable IRQs before we venture back
-        B       GoodExtension           ; into danger zone
+        BVC     GoodExtension           ; into danger zone
 
 SafeNaffExtension
         WritePSRc SVC_mode + I_bit,work  ; disable IRQs before we venture back
@@ -236,6 +252,14 @@ heapopdoneinbackground ROUT
                                       ; clear the interlock
         TST       R11, #V_bit         ; look at returned error
         BEQ       GoodHeapExit
+        ; Recover the error from our buffer
+        LDR       R0,=HeapBackgroundError
+        LDR       R10,[R0]
+        SWI       XMessageTrans_CopyError
+        ; Check that it worked - MessageTrans may be dead
+        LDR       R11,[R0]
+        TEQ       R10,R11
+        LDRNE     R0,=HeapBackgroundError ; Just return our internal buffer if MessageTrans couldn't provide one
         B         NaffHeapExit
 
 ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -247,8 +271,9 @@ heapopdoneinbackground ROUT
 
 ; In    r0       =  heap action requested
 ;       r1(hpd)  -> heap block
-;       r2(addr) -> start of block
+;       r2(addr) -> start of block, or required alignment
 ;       r3(size) =  size of block
+;       r4(work) =  boundary limitation
 
 ; Out   VClear -> Action performed
 ;       VSet   -> Something terrible has happened, error set
@@ -282,7 +307,7 @@ inspect_IRQ_stack
          ORR    R10, R10, #I32_bit:OR:SVC2632
          STR    R10, [R11, #4*6]               ; return into SVC26/32 mode with IRQs disabled
 
-         Push  "R0-R3, lr"
+         Push  "R0-R4, lr"
 
          LDR    R10, =ZeroPage+HeapSavedReg_R0
 
@@ -292,15 +317,30 @@ inspect_IRQ_stack
 ;         CMP    R12, #0
 ;         BNE    HeapInUse
 
-         LDMIA  R10, {R0-R3, R10, R11}
+         LDMIA  R10, {R0-R4, R11}
          SWI    XOS_Heap                ; with interrupts off!
          LDR    R12, =ZeroPage+HeapReturnedReg_R0
 
    ; Could we poke these into the IRQ stack too...?
    ; would allow interruptible IRQ processes to do heap ops!!!
          MRS    lr, CPSR
-         STMIA  R12, {R0-R3, R10, R11, lr}
-         Pull  "R0-R3, lr"
+         STMIA  R12, {R0-R4, R11, lr}
+; Any errors that were generated by the foreground operation may have ended up
+; using one of MessageTrans' IRQ buffers. Trouble is, any number of IRQ errors
+; could occur between now and when the foreground task gets the error. Avoid
+; the error getting clobbered by copying it into a special kernel buffer, and
+; then copy it back to a MessageTrans buffer once we're back in the foreground.
+         BVC    noheapbackgrounderror
+         LDR    R1,=HeapBackgroundError
+         MOV    LR,#256
+heapbackgrounderrorloop
+         LDMIA  R0!,{R2-R4,R12}
+         SUBS   LR,LR,#16
+         STMIA  R1!,{R2-R4,R12}
+         BNE    heapbackgrounderrorloop
+
+noheapbackgrounderror
+         Pull  "R0-R4, lr"
 
 iis_end                                 ; store the registers in the info block
         LDR     R12, =ZeroPage+HeapSavedReg_R0
@@ -334,6 +374,8 @@ HeapJumpTable ; Check reason codes against Hdr:Heap defs
         B       ExtendHeap
  assert ((.-HeapJumpTable) :SHR: 2) = HeapReason_ReadBlockSize
         B       ReadBlockSize
+ assert ((.-HeapJumpTable) :SHR: 2) = HeapReason_GetAligned
+        B       GetAreaAligned
  [ debheap
         B       ShowHeap
  ]
@@ -352,7 +394,12 @@ GoodHeapExit                            ; V cleared on entry to SWI dispatch
         Pull    lr
         ORRVS   lr, lr, #V_bit          ; VSet Exit
 
+      [ HeapTestbed
+        MSR     CPSR_cxsf, lr           ; Fake exit for testbed
+        Pull    "r10-r12,pc"
+      |
         ExitSWIHandler                  ; Like all good SWI handlers
+      ]
 
 ;HeapInUse
 ;     $HeapBadAsModuleBRA
@@ -592,8 +639,8 @@ GetArea ROUT
         BLE     garfailed_zero                  ; And -ve is invalid as well!
      ; note sizes of many megabytes thrown out by looking.
 
-        ADD     size, size, #(freblksize-1)+4   ; Make block size granular
-        BIC     size, size, #(freblksize-1)     ; with size field added
+        ADD     size, size, #3+4                ; Make block size multiple of 4
+        BIC     size, size, #3                  ; including header
 
         ADR     addr, hpdfree-frelink           ; addr:= @(hpd!free)-frelink
 
@@ -610,7 +657,11 @@ garloop
 ; If we have an exact fit (or as close as the granularity of the free list will
 ; allow), unlink this block and return it
 
-        BNE     SplitFreeBlock
+        CMP     HpTemp, #freblksize
+        BGE     SplitFreeBlock
+
+; Increase allocation size if there wasn't enough space to split the free block
+        ADD     size, size, HpTemp
 
  [ debheap
  LDR HpTemp, hpddebug
@@ -660,6 +711,7 @@ ResultIsAddrPlus4
         STR     size, [addr], #4        ; Store block size and increment addr
         Pull    "size"                  ; Return original value to the punter
                                     ; Note : real size got would be an option!
+        CLRV
         B       GoodHeapExit            ; RESULTIS addr
 
 
@@ -689,7 +741,7 @@ garmore
  ]
 
 garfailed
-        ADR     R0, ErrorBlock_HeapFail_Alloc
+        ADRL    R0, ErrorBlock_HeapFail_Alloc
       [ International
         BL      TranslateError
       ]
@@ -705,7 +757,7 @@ garfailed_badhpd
  [ debheap
  STRIM "Invalid heap descriptor"
  ]
-        ADR     R0, ErrorBlock_HeapFail_BadDesc
+        ADRL    R0, ErrorBlock_HeapFail_BadDesc
       [ International
         BL      TranslateError
       ]
@@ -719,6 +771,298 @@ garfailed_zero
 garfailed_zero * garfailed
  ]
 
+; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+;
+; GetAreaAligned. Top level HeapEntry
+; ==============
+;
+; Allocate an aligned block of memory from the heap
+
+; This is the same as GetArea, except it will only allocate areas with the given
+; (power-of-two) alignment.
+; Fails if requesting size = 0
+
+; In : hpd -> heap pointer
+;      size = size of block required
+;      addr = alignment (power of 2)
+;      work = boundary (power of 2, 0 for none)
+
+; Out : VClear : addr -> got a block
+;       VSet   : addr = 0, couldn't get block
+;       Rest of universe ok
+
+GetAreaAligned ROUT
+        Push   "size,work"
+        ValidateHpd garafailed
+
+ [ debheap
+; HpTemp not critical
+ LDR HpTemp, hpddebug
+ CMP HpTemp, #0
+ BEQ %FT00
+ Push  "r0, link"
+ MOV r0, size
+ DREG r0, "GetAreaAligned "
+ MOV r0, addr
+ DREG r0, "alignment "
+ MOV r0, work
+ DREG r0, "boundary "
+ BL iShowHeap
+ Pull "r0, link"
+00
+ ]
+
+        CMP     size, #0                        ; Can't deallocate 0, so there!
+        BLE     garafailed_zero                 ; And -ve is invalid as well!
+     ; note sizes of many megabytes thrown out by looking.
+
+        ADD     size, size, #3                  ; Make block size multiple of 4
+        BIC     size, size, #3                  ; excluding header
+
+        SUB     bp, addr, #1                    ; Store alignment-1 in bp
+        TST     bp, addr
+        BNE     garafailed_align                ; Must be power of 2!
+        CMP     bp, #3
+        MOVLT   bp, #3                          ; Minimum alignment is 4
+
+        SUB     r0, work, #1                    ; Store boundary-1 in r0
+        TST     r0, work
+        BNE     garafailed_boundary             ; Must be power of 2!
+
+        ADR     addr, hpdfree-frelink           ; addr:= @(hpd!free)-frelink
+
+        ; If we have a boundary, it must be >= alignment, and >= size
+        CMP     r0, #-1
+        BEQ     garaloop
+        CMP     r0, bp
+        CMPHS   work, size
+        BLO     garafailed_boundary2
+
+garaloop
+        LDR     tp, [addr, #frelink]        ; tp := addr!fre.link
+        CMP     tp, #Nil                    ; Is this the end of the chain ?
+        BEQ     garamore                    ;  - so try main blk
+        ADD     addr, addr, tp              ; convert offset
+        LDR     HpTemp, [addr, #fresize]
+
+; Calculate start and end addresses as if we were to allocate from this block
+        ADD     work,addr,#4 ; 4 bytes for storing block size
+        ADD     HpTemp,HpTemp,addr ; End of free block
+        ADD     work,work,bp
+garaloop2
+        BIC     work,work,bp ; work = start of user block
+        SUB     lr,work,addr
+        CMP     lr,#4
+        BEQ     garastartok ; Start alignment is exact
+        CMP     lr,#freblksize+4
+        BGE     garastartok ; Enough space to fit a free block at the start
+
+; We need a free block, but there isn't enough space for it.
+; Shift 'work' up by one unit of alignment and try again.
+
+        ADD     work,work,bp,LSL #1
+        B       garaloop2
+
+garastartok
+; Calculate block end address
+        ADD     lr,work,size ; End of user block
+        SUBS    lr,HpTemp,lr ; Gap after user block
+        BLO     garaloop ; Not big enough
+
+; Check boundary requirement
+        CMP     r0,#-1
+        BEQ     garaboundaryok
+        AND     lr,work,r0 ; Start offset within boundary
+        ADD     lr,lr,size
+        SUB     lr,lr,#1 ; Last byte of allocation
+        CMP     lr,r0
+        BLS     garaboundaryok
+
+; This allocation crosses a boundary. Shift 'work' up to be boundary aligned.
+        ADD     work,work,r0
+        BIC     work,work,r0
+        B       garaloop2 ; Loop back round to recheck everything (with small boundary sizes, we may have created a situation where we can't fit an initial free block)
+
+garaboundaryok
+
+; We have a suitable space to allocate from.
+        ADD     size,size,#4 ; Correct size to store
+        SUB     work,work,#4 ; Correct block start
+
+ [ debheap
+ LDR lr, hpddebug
+ CMP lr, #0
+ BEQ %FT60
+ WRLN "Using existing free block"
+60
+ ]
+
+; Note: bp now being used as scratch
+
+        ADD     bp,work,size ; End of user block
+        SUB     bp,HpTemp,bp ; Gap after user block
+
+        WritePSRc SVC_mode+I_bit, lr
+
+; Work out if we need a new free block afterwards
+        CMP     bp, #freblksize
+        ADDLT   size, size, bp ; Not enough space, so enlarge allocated block
+        BLT     %FT10
+
+; Create a new free block that will lie after our allocated block
+        SUB     HpTemp, HpTemp, bp
+        STR     bp, [HpTemp, #fresize]    ; Write size
+        LDR     bp, [addr, #frelink]
+        CMP     bp, #Nil
+        ADDNE   bp, bp, addr
+        SUBNE   bp, bp, HpTemp
+        STR     bp, [HpTemp, #frelink]    ; Write next ptr
+        SUB     HpTemp, HpTemp, addr
+        STR     HpTemp, [addr, #frelink]  ; Fix up link from previous block
+10
+
+; Shrink this free block to take up the space preceeding the allocated block.
+        SUBS    bp,work,addr
+        STRNE   bp, [addr, #fresize]
+        BNE     ResultIsWorkPlus4
+
+; No space for an initial free block. Get rid of it.
+        ASSERT  frelink=0 ; otherwise LDR bp,[addr,#frelink]!
+        LDR     bp, [addr]
+        CMP     bp, #0
+        ADDNE   bp, bp, tp
+        STR     bp, [addr, -tp]
+        B       ResultIsWorkPlus4
+
+; Got no more free blocks of length >= size, so try to allocate more heap space
+; out of the block described by hpd
+
+garamore
+ [ debheap
+ LDR work, hpddebug
+ CMP work, #0
+ BEQ %FT80
+ WRLN "Trying to get more from main block"
+80
+ ]
+        LDR     work, hpdbase
+        ADD     work, work, hpd
+        ADD     tp, work, #4
+        ADD     tp, tp, bp
+garamoreloop
+        BIC     tp, tp, bp            ; tp = pointer to return to user
+
+; Make sure there's enough space for a free block if necessary
+        SUB     HpTemp, tp, work      ; HpTemp = tp-(hpd+hpdbase)
+        CMP     HpTemp, #4
+        BEQ     garamoreok
+        CMP     HpTemp, #freblksize+4
+        ADDLT   tp, tp, bp, LSL #1 ; Not enough space for free block
+        BLT     garamoreloop
+
+garamoreok
+; Boundary check
+        CMP     r0, #-1
+        BEQ     garamoreboundaryok
+        AND     HpTemp, tp, r0
+        ADD     HpTemp, HpTemp, size
+        SUB     HpTemp, HpTemp, #1
+        CMP     HpTemp, r0
+        BLS     garamoreboundaryok
+
+; Shift 'tp' up to be boundary aligned
+        ADD     tp, tp, r0
+        BIC     tp, tp, r0
+        B       garamoreloop
+
+garamoreboundaryok
+        ADD     HpTemp, tp, size      ; New heap end
+        SUB     HpTemp, HpTemp, hpd   ; New heap size
+        LDR     lr, hpdend
+        CMP     HpTemp, lr
+        BGT     garafailed
+
+        WritePSRc SVC_mode+I_bit, lr
+
+; Set up the block to return to the user
+        ADD     size, size, #4
+        STR     size, [tp, #-4]!
+
+; Grow the heap
+        STR     HpTemp, hpdbase
+
+; Create preceeding free block if necessary
+        SUBS    HpTemp, tp, work
+        BEQ     ResultIsTpPlus4
+
+; Write the free block
+        STR     HpTemp, [work, #fresize]
+        MOV     HpTemp, #Nil
+        STR     HpTemp, [work, #frelink]
+
+; Patch up the preceeding block
+        SUB     HpTemp, work, addr
+        STR     HpTemp, [addr, #frelink]
+
+ResultIsTpPlus4
+; Block size is already stored
+        ADD     addr, tp, #4
+        Pull    "size,work"
+        MOV     r0,#HeapReason_GetAligned
+        CLRV
+        B       GoodHeapExit
+
+ResultIsWorkPlus4
+        STR     size, [work]           ; Store block size
+        ADD     addr, work, #4         ; Move to correct return reg & add offset
+        Pull    "size,work"
+        MOV     r0,#HeapReason_GetAligned
+        CLRV
+        B       GoodHeapExit
+
+garafailed
+        ADRL    R0, ErrorBlock_HeapFail_Alloc
+      [ International
+        BL      TranslateError
+      ]
+ [ debheap
+ WRLN " : GetAreaAligned failed"
+ ]
+garafail_common
+        MOV     addr, #0                ; addr := 0 if we couldn't allocate
+        Pull    "size,work"             ; RESULTIS 0
+        B       NaffHeapExit            ; VSet Exit
+
+garafailed_badhpd
+ [ debheap
+ STRIM "Invalid heap descriptor"
+ ]
+        ADRL    R0, ErrorBlock_HeapFail_BadDesc
+      [ International
+        BL      TranslateError
+      ]
+        B garafail_common
+
+ [ debheap
+garafailed_zero
+ STRIM "Can't allocate 0 or less bytes"
+ B garafailed
+garafailed_align
+ STRIM "Alignment not power of 2"
+ B garafailed
+garafailed_boundary
+ STRIM "Boundary not power of 2"
+ B garafailed
+garafailed_boundary2
+ STRIM "Boundary too small"
+ B garafailed
+ |
+garafailed_zero * garafailed
+garafailed_align * garafailed
+garafailed_boundary * garafailed
+garafailed_boundary2 * garafailed
+ ]
+
 ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ;
 ; FreeArea. Top level HeapEntry
@@ -787,7 +1131,7 @@ ExtendBlock
  CMP HpTemp, #0
  BEQ %FT00
  Push  "r0, link"
- DREG size, "ExtendBlock by ",concat
+ DREG size, "ExtendBlock by ",cc
  STRIM " block  at "
  SUB r0, addr, hpd
  SUB r0, r0, #4
@@ -799,8 +1143,8 @@ ExtendBlock
         BL      FindHeapBlock
         BVS     NaffExtension
 
-        ADD     size, size, #freblksize-1  ; round size as appropriate :
-        BICS    size, size, #freblksize-1  ; round up to nearest 8
+        ADD     size, size, #3             ; round size as appropriate :
+        BICS    size, size, #3             ; round up to nearest 4
 
         BEQ     GoodExtension              ; get the easy case done.
         BPL     MakeBlockBigger
@@ -808,7 +1152,8 @@ ExtendBlock
         RSB     size, size, #0
         LDR     bp, [addr, hpd]          ; get block size
         WritePSRc SVC_mode+I_bit, R14
-        SUBS    bp, bp, size             ; size of block left
+        SUB     bp, bp, size             ; size of block left
+        CMP     bp, #4
 
  [ debheap
 ; HpTemp not critical, GE/LT critical
@@ -818,22 +1163,40 @@ ExtendBlock
  BEQ %FT01
  WRLN "Freeing part of block"
 01
- CMP bp, #0  ; restore GE/Lt
+ CMP bp, #4  ; restore GE/Lt
  ]
 
         MOVLE    HpTemp, #-1               ; if discarding block, then
         STRLE    HpTemp, [stack]           ; make pointer really naff.
+        BLE      GoodShrink
 
-        STRGT    bp, [addr, hpd]           ; update size of block left
-        ADDGT    addr, addr, bp            ; offset of block to free
-        STRGT    size, [addr, hpd]         ; construct block for freeing
-
+        ; If we're only shrinking 4 bytes, only allow the shrink to go ahead
+        ; if there's a free block (or hpdbase) after us
+        CMP      size, #4
+        BGT      DoShrink
+        LDR      HpTemp, [hpd, tp]
+        CMP      HpTemp, #Nil
+        ADDNE    HpTemp, HpTemp, tp
+        LDREQ    HpTemp, hpdbase
+        ADD      HpTemp, HpTemp, hpd       ; Next free block ptr
+        SUB      HpTemp, HpTemp, addr      ; Offset from start of this block
+        SUB      HpTemp, HpTemp, size      ; Apply shrink amount to match bp
+        CMP      HpTemp, bp
+        MOVGT    size, #0                  ; Used block after us. Deny shrink.
+        BGT      GoodExtension
+        BLT      CorruptExtension          ; Heap corrupt!
+        ; Else there's a free block (or hpdbase) directly after us
+DoShrink
+        STR      bp, [addr, hpd]           ; update size of block left
+        ADD      addr, addr, bp            ; offset of block to free
+        STR      size, [addr, hpd]         ; construct block for freeing
+
+GoodShrink
         BL      FreeChunkWithConcatenation ; work still set from block lookup
 GoodExtension
         Pull    "addr, size, work"
  [ DebugHeaps
-        ADD     lr, size, #freblksize-1         ; work out how much we actually extended by
-        BICS    lr, lr, #freblksize-1
+        MOVS    lr, size                        ; work out how much we actually extended by
         BEQ     %FT99                           ; if zero or negative
         BMI     %FT99                           ; then nothing to do
         LDR     HpTemp, [addr, #-4]             ; get new block size
@@ -847,6 +1210,7 @@ GoodExtension
         BNE     %BT98
 99
  ]
+        CLRV
         B        GoodHeapExit
 
 MakeBlockBigger
@@ -871,7 +1235,7 @@ MakeBlockBigger
         LDRNE    HpTemp, [HpTemp, #fresize]
         LDREQ    HpTemp, hpdend
         SUBEQ    HpTemp, HpTemp, bp
-        BICEQ    HpTemp, HpTemp, #(freblksize-1)
+        BICEQ    HpTemp, HpTemp, #3
                                            ; force it to a sensible blocksize
         MRS      lr, CPSR                  ; save EQ/NE state
 
@@ -890,7 +1254,7 @@ MakeBlockBigger
  STRIM "Extending block into "
 02
  Pull "HpTemp,lr"
- msr ,CPSR_f, lr
+ msr CPSR_f, lr
  ]
 
         LDR      work, [addr, hpd]         ; get size back
@@ -925,8 +1289,13 @@ IntoFreeEntry
  Pull HpTemp
  ]
 
-        CMP      HpTemp, size
-        BNE      SplitFreeBlockForExtend
+        SUB      HpTemp, HpTemp, size      ; new freblk size
+        CMP      HpTemp, #4
+        BGT      SplitFreeBlockForExtend
+
+; Not enough space for a free block. Increase the grow amount a bit.
+        ADDEQ    work, work, #4
+        STREQ    work, [addr, hpd]
 
 ; free entry just right size : remove from free list
         LDR      HpTemp, [bp, hpd]         ; free link
@@ -942,7 +1311,6 @@ SplitFreeBlockForExtend
         STR      work, [tp, hpd]           ; prevnode points at right place
         ADD      work, work, tp            ; offset of new free entry
         ADD      work, work, hpd
-        SUB      HpTemp, HpTemp, size      ; new freblk size
         STR      HpTemp, [work, #fresize]
         LDR      HpTemp, [bp, hpd]
         CMP      HpTemp, #Nil
@@ -992,13 +1360,15 @@ hack_preceder
 ; work is prevprevfree offset
 ; size is amount block grows by
 ; addr is block offset
-        CMP      bp, #0
-        ADDNE    HpTemp, tp, hpd
-        STRNE    bp, [HpTemp, #fresize]    ; prevblock shrunk
-        BNE      copy_backwards
+        CMP      bp, #freblksize
+        ADDGE    HpTemp, tp, hpd
+        STRGE    bp, [HpTemp, #fresize]    ; prevblock shrunk
+        BGE      copy_backwards
 
  ; free freblk: work is still prevprevblk pointer
         LDR      HpTemp, [tp, hpd]
+        ADDNE    size, size, bp            ; Increase grow amount by any remainder
+        MOVNE    bp, #0                    ; And make sure the block does die
         CMP      HpTemp, #Nil
         ADDNE    HpTemp, HpTemp, tp        ; offset from heap start
         SUBNE    HpTemp, HpTemp, work
@@ -1015,7 +1385,7 @@ copy_backwards
  LDR r0, hpddebug
  CMP r0, #0
  BEQ %FT06
- DREG HpTemp, "copying -4+",concat
+ DREG HpTemp, "copying -4+",cc
  STRIM " from "
  SUB  R0, addr, hpd
  BL   PrintOffset
@@ -1074,7 +1444,7 @@ try_add_preceding_block
         BNE      ext_delink
         LDR      work, hpdend
         SUB      work, work, bp        ; get back real size
-        BIC      work, work, #(freblksize-1)
+        BIC      work, work, #3
         ADD      work, work, bp
         STR      work, hpdbase         ; all free allocated
         B        ext_hack
@@ -1111,9 +1481,15 @@ got_to_reallocate
 
         B       ReallocateInSafeZone
 
+CorruptExtension
+        ADRL    R0,ErrorBlock_HeapFail_BadLink
+      [ International
+        BL      TranslateError
+      ]
+
 NaffExtension
         Pull    "addr, size, work"
-        B        NaffHeapExit
+        B       NaffHeapExit
 
 
 ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -1522,7 +1898,7 @@ iShowHeap ROUT ; Internal entry point for debugging heap
         BL      PrintOffsetLine
 
         SUB     r0, work, bp            ; hpdend-hpdbase
-        DREG    r0,"Bytes free: ",concat, Word
+        DREG    r0,"Bytes free: ",cc, Word
         SUB     r0, bp, addr            ; hpdbase-hpdsize
         DREG    r0,", bytes used: ",, Word
         SWI     XOS_NewLine
@@ -1605,7 +1981,7 @@ PrintOffset
         DREG    r0
         CMP     R0, #0
         ADDNE   R0, R0, hpd
-        DREG    r0," (",concat
+        DREG    r0," (",cc
         STRIM   ")"
         GRAB   "R0, PC"
 
diff --git a/s/Oscli b/s/Oscli
index e12ed74..2f6473c 100644
--- a/s/Oscli
+++ b/s/Oscli
@@ -547,8 +547,7 @@ AliasOscliTooLong
 AliasStr_QA = "ALIAS$", 0
   ]
 AliasStr = "Alias$*", 0
-AliasDot = "Alias$"
-dotstring = ".", 0
+AliasDot = "Alias$.", 0
       ALIGN
 
   [ Oscli_QuickAliases
diff --git a/s/vdu/vdudecl b/s/vdu/vdudecl
index c760714..9a77f9a 100644
--- a/s/vdu/vdudecl
+++ b/s/vdu/vdudecl
@@ -68,7 +68,10 @@ ScreenModeReason_SelectMode             *       0
 ScreenModeReason_ReturnMode             *       1
 ScreenModeReason_EnumerateModes         *       2
 ScreenModeReason_SelectMonitorType      *       3
-ScreenModeReason_Limit                  *       4
+ScreenModeReason_ConfigureAcceleration  *       4
+ScreenModeReason_CleanCache             *       5
+ScreenModeReason_ForceCleanCache        *       6
+ScreenModeReason_Limit                  *       7
 
 ; Mode selector format
 
diff --git a/s/vdu/vduswis b/s/vdu/vduswis
index 37575ad..ce7493d 100644
--- a/s/vdu/vduswis
+++ b/s/vdu/vduswis
@@ -1933,7 +1933,10 @@ ScreenModeSWI Entry
         ASSERT  ScreenModeReason_ReturnMode = 1
         ASSERT  ScreenModeReason_EnumerateModes = 2
         ASSERT  ScreenModeReason_SelectMonitorType = 3
-        ASSERT  ScreenModeReason_Limit = 4
+        ASSERT  ScreenModeReason_ConfigureAcceleration = 4
+        ASSERT  ScreenModeReason_CleanCache = 5
+        ASSERT  ScreenModeReason_ForceCleanCache = 6
+        ASSERT  ScreenModeReason_Limit = 7
 
 ScreenModeSub
         CMP     r0, #ScreenModeReason_Limit
@@ -1943,6 +1946,9 @@ ScreenModeSub
         B       ScreenMode_ReturnMode
         B       ScreenMode_EnumerateModes
         B       ScreenMode_SelectMonitorType
+        B       ScreenMode_ConfigureAcceleration
+        B       ScreenMode_CleanCache
+        B       ScreenMode_ForceCleanCache
 
 ; unknown OS_ScreenMode reason code
 
@@ -2063,6 +2069,52 @@ ScreenMode_SelectMonitorType Entry "r0"
         STR     r1, [WsPtr, #CurrentMonitorType]        ; update current value
         EXIT
 
+;**************************************************************************
+;
+;       ScreenMode_ConfigureAcceleration - Configure screen memory cacheability
+;
+;       Internal routine called by ScreenModeSWI
+;
+; in:   r0 = reason code (4)
+;       r1 = flags:
+;              bit 0 : set to suspend cached screen until mode change
+;              bit 1 : set to suspend screen cleaning
+;              bit 2 : set to disable hardware acceleration
+;              other : reserved, must be 0
+;            or -1 to read current value
+;       r2 = number of VSyncs between automatic screen cleaning (1-3), or -1
+;       to read current value
+;
+; out:  r1 = new flag state
+;       r2 = new number of VSyncs between automatic screen cleaning
+;       r10-r12 may be corrupted
+;       All other registers preserved
+;
+
+ScreenMode_ConfigureAcceleration
+        ; Screen caching isn't supported yet. Just return dummy values.
+        MOV     r1,#1
+        MOV     r2,#1
+        MOV     pc,lr
+
+;**************************************************************************
+;
+;       ScreenMode_CleanCache - Clean screen memory from cache, if cache enabled
+;       ScreenMode_ForceCleanCache - Force clean of screen memory from cache
+;
+;       Internal routine called by ScreenModeSWI
+;
+; in:   r0 = reason code (5 or 6)
+;
+; out:  r10-r12 may be corrupted
+;       All other registers preserved
+;
+
+ScreenMode_CleanCache
+ScreenMode_ForceCleanCache
+        ; Screen caching isn't supported yet. Just do nothing.
+        MOV      pc,lr
+
 ;;;mjsHAL - VIDCDividerSWI is horrible VIDC specific API, compiled out
 ;;;
 ; Should not cause any problems on any machine.  STB flag just to be safe though.
-- 
GitLab