1 /*
2  * jmemdos.c  (jmemsys.c)
3  *
4  * Copyright (C) 1992, Thomas G. Lane.
5  * This file is part of the Independent JPEG Group's software.
6  * For conditions of distribution and use, see the accompanying README file.
7  *
8  * This file provides an MS-DOS-compatible implementation of the system-
9  * dependent portion of the JPEG memory manager.  Temporary data can be
10  * stored in extended or expanded memory as well as in regular DOS files.
11  *
12  * If you use this file, you must be sure that NEED_FAR_POINTERS is defined
13  * if you compile in a small-data memory model; it should NOT be defined if
14  * you use a large-data memory model.  This file is not recommended if you
15  * are using a flat-memory-space 386 environment such as DJGCC or Watcom C.
16  * Also, this code will NOT work if struct fields are aligned on greater than
17  * 2-byte boundaries.
18  *
19  * Based on code contributed by Ge' Weijers.
20  */
21 
22 /*
23  * If you have both extended and expanded memory, you may want to change the
24  * order in which they are tried in jopen_backing_store.  On a 286 machine
25  * expanded memory is usually faster, since extended memory access involves
26  * an expensive protected-mode-and-back switch.  On 386 and better, extended
27  * memory is usually faster.  As distributed, the code tries extended memory
28  * first (what? not everyone has a 386? :-).
29  *
30  * You can disable use of extended/expanded memory entirely by altering these
31  * definitions or overriding them from the Makefile (eg, -DEMS_SUPPORTED=0).
32  */
33 
34 #ifndef XMS_SUPPORTED
35 #define XMS_SUPPORTED  1
36 #endif
37 #ifndef EMS_SUPPORTED
38 #define EMS_SUPPORTED  1
39 #endif
40 
41 
42 #include "jinclude.h"
43 #include "jmemsys.h"
44 
45 #ifdef INCLUDES_ARE_ANSI
46 #include <stdlib.h>		/* to declare malloc(), free(), getenv() */
47 #else
48 extern void * malloc PP((size_t size));
49 extern void free PP((void *ptr));
50 extern char * getenv PP((const char * name));
51 #endif
52 
53 #ifdef NEED_FAR_POINTERS
54 
55 #ifdef __TURBOC__
56 /* These definitions work for Borland C (Turbo C) */
57 #include <alloc.h>		/* need farmalloc(), farfree() */
58 #define far_malloc(x)	farmalloc(x)
59 #define far_free(x)	farfree(x)
60 #else
61 /* These definitions work for Microsoft C and compatible compilers */
62 #include <malloc.h>		/* need _fmalloc(), _ffree() */
63 #define far_malloc(x)	_fmalloc(x)
64 #define far_free(x)	_ffree(x)
65 #endif
66 
67 #endif
68 
69 #ifdef DONT_USE_B_MODE		/* define mode parameters for fopen() */
70 #define READ_BINARY	"r"
71 #else
72 #define READ_BINARY	"rb"
73 #endif
74 
75 
76 /*
77  * Declarations for assembly-language support routines (see jmemdosa.asm).
78  *
79  * The functions are declared "far" as are all pointer arguments;
80  * this ensures the assembly source code will work regardless of the
81  * compiler memory model.  We assume "short" is 16 bits, "long" is 32.
82  */
83 
84 typedef void far * XMSDRIVER;	/* actually a pointer to code */
85 typedef struct {		/* registers for calling XMS driver */
86 	unsigned short ax, dx, bx;
87 	void far * ds_si;
88       } XMScontext;
89 typedef struct {		/* registers for calling EMS driver */
90 	unsigned short ax, dx, bx;
91 	void far * ds_si;
92       } EMScontext;
93 
94 EXTERN short far jdos_open PP((short far * handle, char far * filename));
95 EXTERN short far jdos_close PP((short handle));
96 EXTERN short far jdos_seek PP((short handle, long offset));
97 EXTERN short far jdos_read PP((short handle, void far * buffer,
98 			       unsigned short count));
99 EXTERN short far jdos_write PP((short handle, void far * buffer,
100 				unsigned short count));
101 EXTERN void far jxms_getdriver PP((XMSDRIVER far *));
102 EXTERN void far jxms_calldriver PP((XMSDRIVER, XMScontext far *));
103 EXTERN short far jems_available PP((void));
104 EXTERN void far jems_calldriver PP((EMScontext far *));
105 
106 
107 static external_methods_ptr methods; /* saved for access to error_exit */
108 
109 static long total_used;		/* total FAR memory requested so far */
110 
111 
112 /*
113  * Selection of a file name for a temporary file.
114  * This is highly system-dependent, and you may want to customize it.
115  */
116 
117 static int next_file_num;	/* to distinguish among several temp files */
118 
119 LOCAL void
select_file_name(char * fname)120 select_file_name (char * fname)
121 {
122   const char * env;
123   char * ptr;
124   FILE * tfile;
125 
126   /* Keep generating file names till we find one that's not in use */
127   for (;;) {
128     /* Get temp directory name from environment TMP or TEMP variable;
129      * if none, use "."
130      */
131     if ((env = (const char *) getenv("TMP")) == NULL)
132       if ((env = (const char *) getenv("TEMP")) == NULL)
133 	env = ".";
134     if (*env == '\0')		/* null string means "." */
135       env = ".";
136     ptr = fname;		/* copy name to fname */
137     while (*env != '\0')
138       *ptr++ = *env++;
139     if (ptr[-1] != '\\' && ptr[-1] != '/')
140       *ptr++ = '\\';		/* append backslash if not in env variable */
141     /* Append a suitable file name */
142     next_file_num++;		/* advance counter */
143     sprintf(ptr, "JPG%03d.TMP", next_file_num);
144     /* Probe to see if file name is already in use */
145     if ((tfile = fopen(fname, READ_BINARY)) == NULL)
146       break;
147     fclose(tfile);		/* oops, it's there; close tfile & try again */
148   }
149 }
150 
151 
152 /*
153  * Near-memory allocation and freeing are controlled by the regular library
154  * routines malloc() and free().
155  */
156 
157 GLOBAL void *
jget_small(size_t sizeofobject)158 jget_small (size_t sizeofobject)
159 {
160   /* near data space is NOT counted in total_used */
161 #ifndef NEED_FAR_POINTERS
162   total_used += sizeofobject;
163 #endif
164   return (void *) malloc(sizeofobject);
165 }
166 
167 GLOBAL void
jfree_small(void * object)168 jfree_small (void * object)
169 {
170   free(object);
171 }
172 
173 
174 /*
175  * Far-memory allocation and freeing
176  */
177 
178 #ifdef NEED_FAR_POINTERS
179 
180 GLOBAL void FAR *
jget_large(size_t sizeofobject)181 jget_large (size_t sizeofobject)
182 {
183   total_used += sizeofobject;
184   return (void FAR *) far_malloc(sizeofobject);
185 }
186 
187 GLOBAL void
jfree_large(void FAR * object)188 jfree_large (void FAR * object)
189 {
190   far_free(object);
191 }
192 
193 #endif
194 
195 
196 /*
197  * This routine computes the total memory space available for allocation.
198  * It's impossible to do this in a portable way; our current solution is
199  * to make the user tell us (with a default value set at compile time).
200  * If you can actually get the available space, it's a good idea to subtract
201  * a slop factor of 5% or so.
202  */
203 
204 #ifndef DEFAULT_MAX_MEM		/* so can override from makefile */
205 #define DEFAULT_MAX_MEM		300000L /* for total usage about 450K */
206 #endif
207 
208 GLOBAL long
jmem_available(long min_bytes_needed,long max_bytes_needed)209 jmem_available (long min_bytes_needed, long max_bytes_needed)
210 {
211   return methods->max_memory_to_use - total_used;
212 }
213 
214 
215 /*
216  * Backing store (temporary file) management.
217  * Backing store objects are only used when the value returned by
218  * jmem_available is less than the total space needed.  You can dispense
219  * with these routines if you have plenty of virtual memory; see jmemnobs.c.
220  */
221 
222 /*
223  * For MS-DOS we support three types of backing storage:
224  *   1. Conventional DOS files.  We access these by direct DOS calls rather
225  *      than via the stdio package.  This provides a bit better performance,
226  *      but the real reason is that the buffers to be read or written are FAR.
227  *      The stdio library for small-data memory models can't cope with that.
228  *   2. Extended memory, accessed per the XMS V2.0 specification.
229  *   3. Expanded memory, accessed per the LIM/EMS 4.0 specification.
230  * You'll need copies of those specs to make sense of the related code.
231  * The specs are available by Internet FTP from SIMTEL20 and its various
232  * mirror sites; see microsoft/xms20.arc and info/limems41.zip.
233  */
234 
235 
236 /*
237  * Access methods for a DOS file.
238  */
239 
240 
241 METHODDEF void
read_file_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)242 read_file_store (backing_store_ptr info, void FAR * buffer_address,
243 		 long file_offset, long byte_count)
244 {
245   if (jdos_seek(info->handle.file_handle, file_offset))
246     ERREXIT(methods, "seek failed on temporary file");
247   /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
248   if (byte_count > 65535L)	/* safety check */
249     ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
250   if (jdos_read(info->handle.file_handle, buffer_address,
251 		(unsigned short) byte_count))
252     ERREXIT(methods, "read failed on temporary file");
253 }
254 
255 
256 METHODDEF void
write_file_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)257 write_file_store (backing_store_ptr info, void FAR * buffer_address,
258 		  long file_offset, long byte_count)
259 {
260   if (jdos_seek(info->handle.file_handle, file_offset))
261     ERREXIT(methods, "seek failed on temporary file");
262   /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
263   if (byte_count > 65535L)	/* safety check */
264     ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
265   if (jdos_write(info->handle.file_handle, buffer_address,
266 		 (unsigned short) byte_count))
267     ERREXIT(methods, "write failed on temporary file --- out of disk space?");
268 }
269 
270 
271 METHODDEF void
close_file_store(backing_store_ptr info)272 close_file_store (backing_store_ptr info)
273 {
274   jdos_close(info->handle.file_handle);	/* close the file */
275   remove(info->temp_name);	/* delete the file */
276 /* If your system doesn't have remove(), try unlink() instead.
277  * remove() is the ANSI-standard name for this function, but
278  * unlink() was more common in pre-ANSI systems.
279  */
280   TRACEMS1(methods, 1, "Closed DOS file %d", info->handle.file_handle);
281 }
282 
283 
284 LOCAL boolean
open_file_store(backing_store_ptr info,long total_bytes_needed)285 open_file_store (backing_store_ptr info, long total_bytes_needed)
286 {
287   short handle;
288   char tracemsg[TEMP_NAME_LENGTH+40];
289 
290   select_file_name(info->temp_name);
291   if (jdos_open((short far *) & handle, (char far *) info->temp_name)) {
292     /* hack to get around TRACEMS' inability to handle string parameters */
293     sprintf(tracemsg, "Failed to create temporary file %s", info->temp_name);
294     ERREXIT(methods, tracemsg);	/* jopen_backing_store will fail anyway */
295     return FALSE;
296   }
297   info->handle.file_handle = handle;
298   info->read_backing_store = read_file_store;
299   info->write_backing_store = write_file_store;
300   info->close_backing_store = close_file_store;
301   /* hack to get around TRACEMS' inability to handle string parameters */
302   sprintf(tracemsg, "Opened DOS file %d  %s", handle, info->temp_name);
303   TRACEMS(methods, 1, tracemsg);
304   return TRUE;			/* succeeded */
305 }
306 
307 
308 /*
309  * Access methods for extended memory.
310  */
311 
312 #if XMS_SUPPORTED
313 
314 static XMSDRIVER xms_driver;	/* saved address of XMS driver */
315 
316 typedef union {			/* either long offset or real-mode pointer */
317 	long offset;
318 	void far * ptr;
319       } XMSPTR;
320 
321 typedef struct {		/* XMS move specification structure */
322 	long length;
323 	XMSH src_handle;
324 	XMSPTR src;
325 	XMSH dst_handle;
326 	XMSPTR dst;
327       } XMSspec;
328 
329 #define ODD(X)	(((X) & 1L) != 0)
330 
331 
332 METHODDEF void
read_xms_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)333 read_xms_store (backing_store_ptr info, void FAR * buffer_address,
334 		long file_offset, long byte_count)
335 {
336   XMScontext ctx;
337   XMSspec spec;
338   char endbuffer[2];
339 
340   /* The XMS driver can't cope with an odd length, so handle the last byte
341    * specially if byte_count is odd.  We don't expect this to be common.
342    */
343 
344   spec.length = byte_count & (~ 1L);
345   spec.src_handle = info->handle.xms_handle;
346   spec.src.offset = file_offset;
347   spec.dst_handle = 0;
348   spec.dst.ptr = buffer_address;
349 
350   ctx.ds_si = (void far *) & spec;
351   ctx.ax = 0x0b00;		/* EMB move */
352   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
353   if (ctx.ax != 1)
354     ERREXIT(methods, "read from extended memory failed");
355 
356   if (ODD(byte_count)) {
357     read_xms_store(info, (void FAR *) endbuffer,
358 		   file_offset + byte_count - 1L, 2L);
359     ((char FAR *) buffer_address)[byte_count - 1L] = endbuffer[0];
360   }
361 }
362 
363 
364 METHODDEF void
write_xms_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)365 write_xms_store (backing_store_ptr info, void FAR * buffer_address,
366 		 long file_offset, long byte_count)
367 {
368   XMScontext ctx;
369   XMSspec spec;
370   char endbuffer[2];
371 
372   /* The XMS driver can't cope with an odd length, so handle the last byte
373    * specially if byte_count is odd.  We don't expect this to be common.
374    */
375 
376   spec.length = byte_count & (~ 1L);
377   spec.src_handle = 0;
378   spec.src.ptr = buffer_address;
379   spec.dst_handle = info->handle.xms_handle;
380   spec.dst.offset = file_offset;
381 
382   ctx.ds_si = (void far *) & spec;
383   ctx.ax = 0x0b00;		/* EMB move */
384   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
385   if (ctx.ax != 1)
386     ERREXIT(methods, "write to extended memory failed");
387 
388   if (ODD(byte_count)) {
389     read_xms_store(info, (void FAR *) endbuffer,
390 		   file_offset + byte_count - 1L, 2L);
391     endbuffer[0] = ((char FAR *) buffer_address)[byte_count - 1L];
392     write_xms_store(info, (void FAR *) endbuffer,
393 		    file_offset + byte_count - 1L, 2L);
394   }
395 }
396 
397 
398 METHODDEF void
close_xms_store(backing_store_ptr info)399 close_xms_store (backing_store_ptr info)
400 {
401   XMScontext ctx;
402 
403   ctx.dx = info->handle.xms_handle;
404   ctx.ax = 0x0a00;
405   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
406   TRACEMS1(methods, 1, "Freed XMS handle %u", info->handle.xms_handle);
407   /* we ignore any error return from the driver */
408 }
409 
410 
411 LOCAL boolean
open_xms_store(backing_store_ptr info,long total_bytes_needed)412 open_xms_store (backing_store_ptr info, long total_bytes_needed)
413 {
414   XMScontext ctx;
415 
416   /* Get address of XMS driver */
417   jxms_getdriver((XMSDRIVER far *) & xms_driver);
418   if (xms_driver == NULL)
419     return FALSE;		/* no driver to be had */
420 
421   /* Get version number, must be >= 2.00 */
422   ctx.ax = 0x0000;
423   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
424   if (ctx.ax < (unsigned short) 0x0200)
425     return FALSE;
426 
427   /* Try to get space (expressed in kilobytes) */
428   ctx.dx = (unsigned short) ((total_bytes_needed + 1023L) >> 10);
429   ctx.ax = 0x0900;
430   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
431   if (ctx.ax != 1)
432     return FALSE;
433 
434   /* Succeeded, save the handle and away we go */
435   info->handle.xms_handle = ctx.dx;
436   info->read_backing_store = read_xms_store;
437   info->write_backing_store = write_xms_store;
438   info->close_backing_store = close_xms_store;
439   TRACEMS1(methods, 1, "Obtained XMS handle %u", ctx.dx);
440   return TRUE;			/* succeeded */
441 }
442 
443 #endif /* XMS_SUPPORTED */
444 
445 
446 /*
447  * Access methods for expanded memory.
448  */
449 
450 #if EMS_SUPPORTED
451 
452 /* The EMS move specification structure requires word and long fields aligned
453  * at odd byte boundaries.  Some compilers will align struct fields at even
454  * byte boundaries.  While it's usually possible to force byte alignment,
455  * that causes an overall performance penalty and may pose problems in merging
456  * JPEG into a larger application.  Instead we accept some rather dirty code
457  * here.  Note this code would fail if the hardware did not allow odd-byte
458  * word & long accesses, but all 80x86 CPUs do.
459  */
460 
461 typedef void far * EMSPTR;
462 
463 typedef union {			/* EMS move specification structure */
464 	long length;		/* It's easy to access first 4 bytes */
465 	char bytes[18];		/* Misaligned fields in here! */
466       } EMSspec;
467 
468 /* Macros for accessing misaligned fields */
469 #define FIELD_AT(spec,offset,type)  (*((type *) &(spec.bytes[offset])))
470 #define SRC_TYPE(spec)		FIELD_AT(spec,4,char)
471 #define SRC_HANDLE(spec)	FIELD_AT(spec,5,EMSH)
472 #define SRC_OFFSET(spec)	FIELD_AT(spec,7,unsigned short)
473 #define SRC_PAGE(spec)		FIELD_AT(spec,9,unsigned short)
474 #define SRC_PTR(spec)		FIELD_AT(spec,7,EMSPTR)
475 #define DST_TYPE(spec)		FIELD_AT(spec,11,char)
476 #define DST_HANDLE(spec)	FIELD_AT(spec,12,EMSH)
477 #define DST_OFFSET(spec)	FIELD_AT(spec,14,unsigned short)
478 #define DST_PAGE(spec)		FIELD_AT(spec,16,unsigned short)
479 #define DST_PTR(spec)		FIELD_AT(spec,14,EMSPTR)
480 
481 #define EMSPAGESIZE	16384L	/* gospel, see the EMS specs */
482 
483 #define HIBYTE(W)  (((W) >> 8) & 0xFF)
484 #define LOBYTE(W)  ((W) & 0xFF)
485 
486 
487 METHODDEF void
read_ems_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)488 read_ems_store (backing_store_ptr info, void FAR * buffer_address,
489 		long file_offset, long byte_count)
490 {
491   EMScontext ctx;
492   EMSspec spec;
493 
494   spec.length = byte_count;
495   SRC_TYPE(spec) = 1;
496   SRC_HANDLE(spec) = info->handle.ems_handle;
497   SRC_PAGE(spec)   = (unsigned short) (file_offset / EMSPAGESIZE);
498   SRC_OFFSET(spec) = (unsigned short) (file_offset % EMSPAGESIZE);
499   DST_TYPE(spec) = 0;
500   DST_HANDLE(spec) = 0;
501   DST_PTR(spec)    = buffer_address;
502 
503   ctx.ds_si = (void far *) & spec;
504   ctx.ax = 0x5700;		/* move memory region */
505   jems_calldriver((EMScontext far *) & ctx);
506   if (HIBYTE(ctx.ax) != 0)
507     ERREXIT(methods, "read from expanded memory failed");
508 }
509 
510 
511 METHODDEF void
write_ems_store(backing_store_ptr info,void FAR * buffer_address,long file_offset,long byte_count)512 write_ems_store (backing_store_ptr info, void FAR * buffer_address,
513 		 long file_offset, long byte_count)
514 {
515   EMScontext ctx;
516   EMSspec spec;
517 
518   spec.length = byte_count;
519   SRC_TYPE(spec) = 0;
520   SRC_HANDLE(spec) = 0;
521   SRC_PTR(spec)    = buffer_address;
522   DST_TYPE(spec) = 1;
523   DST_HANDLE(spec) = info->handle.ems_handle;
524   DST_PAGE(spec)   = (unsigned short) (file_offset / EMSPAGESIZE);
525   DST_OFFSET(spec) = (unsigned short) (file_offset % EMSPAGESIZE);
526 
527   ctx.ds_si = (void far *) & spec;
528   ctx.ax = 0x5700;		/* move memory region */
529   jems_calldriver((EMScontext far *) & ctx);
530   if (HIBYTE(ctx.ax) != 0)
531     ERREXIT(methods, "write to expanded memory failed");
532 }
533 
534 
535 METHODDEF void
close_ems_store(backing_store_ptr info)536 close_ems_store (backing_store_ptr info)
537 {
538   EMScontext ctx;
539 
540   ctx.ax = 0x4500;
541   ctx.dx = info->handle.ems_handle;
542   jems_calldriver((EMScontext far *) & ctx);
543   TRACEMS1(methods, 1, "Freed EMS handle %u", info->handle.ems_handle);
544   /* we ignore any error return from the driver */
545 }
546 
547 
548 LOCAL boolean
open_ems_store(backing_store_ptr info,long total_bytes_needed)549 open_ems_store (backing_store_ptr info, long total_bytes_needed)
550 {
551   EMScontext ctx;
552 
553   /* Is EMS driver there? */
554   if (! jems_available())
555     return FALSE;
556 
557   /* Get status, make sure EMS is OK */
558   ctx.ax = 0x4000;
559   jems_calldriver((EMScontext far *) & ctx);
560   if (HIBYTE(ctx.ax) != 0)
561     return FALSE;
562 
563   /* Get version, must be >= 4.0 */
564   ctx.ax = 0x4600;
565   jems_calldriver((EMScontext far *) & ctx);
566   if (HIBYTE(ctx.ax) != 0 || LOBYTE(ctx.ax) < 0x40)
567     return FALSE;
568 
569   /* Try to allocate requested space */
570   ctx.ax = 0x4300;
571   ctx.bx = (unsigned short) ((total_bytes_needed + EMSPAGESIZE-1L) / EMSPAGESIZE);
572   jems_calldriver((EMScontext far *) & ctx);
573   if (HIBYTE(ctx.ax) != 0)
574     return FALSE;
575 
576   /* Succeeded, save the handle and away we go */
577   info->handle.ems_handle = ctx.dx;
578   info->read_backing_store = read_ems_store;
579   info->write_backing_store = write_ems_store;
580   info->close_backing_store = close_ems_store;
581   TRACEMS1(methods, 1, "Obtained EMS handle %u", ctx.dx);
582   return TRUE;			/* succeeded */
583 }
584 
585 #endif /* EMS_SUPPORTED */
586 
587 
588 /*
589  * Initial opening of a backing-store object.
590  */
591 
592 GLOBAL void
jopen_backing_store(backing_store_ptr info,long total_bytes_needed)593 jopen_backing_store (backing_store_ptr info, long total_bytes_needed)
594 {
595   /* Try extended memory, then expanded memory, then regular file. */
596 #if XMS_SUPPORTED
597   if (open_xms_store(info, total_bytes_needed))
598     return;
599 #endif
600 #if EMS_SUPPORTED
601   if (open_ems_store(info, total_bytes_needed))
602     return;
603 #endif
604   if (open_file_store(info, total_bytes_needed))
605     return;
606   ERREXIT(methods, "Failed to create temporary file");
607 }
608 
609 
610 /*
611  * These routines take care of any system-dependent initialization and
612  * cleanup required.  Keep in mind that jmem_term may be called more than
613  * once.
614  */
615 
616 GLOBAL void
jmem_init(external_methods_ptr emethods)617 jmem_init (external_methods_ptr emethods)
618 {
619   methods = emethods;		/* save struct addr for error exit access */
620   emethods->max_memory_to_use = DEFAULT_MAX_MEM;
621   total_used = 0;
622   next_file_num = 0;
623 }
624 
625 GLOBAL void
jmem_term(void)626 jmem_term (void)
627 {
628   /* Microsoft C, at least in v6.00A, will not successfully reclaim freed
629    * blocks of size > 32Kbytes unless we give it a kick in the rear, like so:
630    */
631 #ifdef NEED_FHEAPMIN
632   _fheapmin();
633 #endif
634 }
635