mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-25 14:24:11 +00:00
Estimated hours taken: 6
A general cleanup of the code in the runtime directory, aimed at
formulating a more coherent header file inclusion policy. This change
fixes a couple of bugs that prevented the runtime from compiling in
certain configurations (e.g. on muse) due to missing #includes, and
also fixes a few minor unrelated things such as the use of `size_t'
instead of `unsigned'.
Our header file inclusion policy is that every header file should
#include any other header files needed by the declarations or by the
macros it defines. Cyclic interface dependencies, where two header
files each #include the other, must be avoided (by splitting up header
files into smaller indepdent units, if necessary).
At some stage in the future we should rename all the header files to
`mercury_*.h', to avoid any possible name clashes with system or user
header files.
runtime/Mmake:
Add a new target `check_headers' to check that each
header file is syntactically valid in isolation.
runtime/imp.h:
runtime/mercury_float.h:
runtime/mercury_string.h:
runtime/mercury_types.h:
runtime/calls.h:
Move the code in "imp.h" into new header files.
"imp.h" now contains nothing but #includes.
runtime/conf.h.in:
runtime/*.h:
runtime/{label,prof,prof_mem}.c:
runtime/*.mod:
Update the #includes to reflect the new header file structure.
Add some missing header guards. Add some comments.
Put the correct years in most of the copyright notices.
runtime/heap.h:
Fix a bug: add #include "context.h", needed for
min_heap_reclamation_point.
runtime/context.h:
Fix a bug: add #include "memory.h", needed for MemoryZone.
Move the general description comment to the top of the file.
Fix the indentation of some comments. Add a couple of new comments.
runtime/context.mod:
Delete a couple of unnecessary declarations.
runtime/wrapper.mod:
Change the type used for memory sizes from `unsigned' to `size_t'.
Change the `-p' (primary cache size) option so that it is always
a size in kilobytes, rather than being schitzophrenic about
whether it is bytes or kilobytes.
runtime/regorder_base.h:
Removed, since it not used (and constitutes a
double-maintenance problem).
967 lines
20 KiB
C
967 lines
20 KiB
C
/*
|
|
** Copyright (C) 1994-1997 University of Melbourne.
|
|
** This file may only be copied under the terms of the GNU Library General
|
|
** Public License - see the file COPYING.LIB in the Mercury distribution.
|
|
*/
|
|
|
|
/*
|
|
** This module defines the register array and data regions of the
|
|
** execution algorithm.
|
|
** They are defined together here to allow us to control how they map
|
|
** onto direct mapped caches.
|
|
** We allocate a large arena, preferably aligned on a boundary that
|
|
** is a multiple of both the page size and the primary cache size.
|
|
**
|
|
** We then allocate the heap and the stacks in such a way that
|
|
**
|
|
** the register array
|
|
** the bottom of the heap
|
|
** the bottom of the detstack
|
|
** the bottom of the nondstack
|
|
**
|
|
** all start at different offsets from multiples of the primary cache size.
|
|
** This should reduce cache conflicts (especially for small programs).
|
|
**
|
|
** If the operating system of the machine supports the mprotect syscall,
|
|
** we also protect a chunk at the end of each area against access,
|
|
** thus detecting area overflow.
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#include "imp.h"
|
|
#include "conf.h"
|
|
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_SIGINFO
|
|
#include <sys/siginfo.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_MPROTECT
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#include <signal.h>
|
|
|
|
#ifdef HAVE_UCONTEXT
|
|
#include <ucontext.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_UCONTEXT
|
|
#include <sys/ucontext.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
|
|
#define getpagesize() sysconf(_SC_PAGESIZE)
|
|
#else
|
|
#ifndef HAVE_GETPAGESIZE
|
|
#define getpagesize() 8192
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef CONSERVATIVE_GC
|
|
#define memalign(a,s) GC_MALLOC_UNCOLLECTABLE(s)
|
|
#else
|
|
#ifdef HAVE_MEMALIGN
|
|
extern void *memalign(size_t, size_t);
|
|
#else
|
|
#define memalign(a,s) malloc(s)
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
** DESCRIPTION
|
|
** The function mprotect() changes the access protections on
|
|
** the mappings specified by the range [addr, addr + len) to be
|
|
** that specified by prot. Legitimate values for prot are the
|
|
** same as those permitted for mmap and are defined in
|
|
** <sys/mman.h> as:
|
|
**
|
|
** PROT_READ page can be read
|
|
** PROT_WRITE page can be written
|
|
** PROT_EXEC page can be executed
|
|
** PROT_NONE page can not be accessed
|
|
*/
|
|
#ifdef HAVE_MPROTECT
|
|
|
|
#ifdef CONSERVATIVE_GC
|
|
/*
|
|
** The conservative garbage collectors scans through
|
|
** all these areas, so we need to allow reads.
|
|
** XXX This probably causes efficiency problems:
|
|
** too much memory for the GC to scan, and it probably
|
|
** all gets paged in.
|
|
*/
|
|
#define MY_PROT PROT_READ
|
|
#else
|
|
#define MY_PROT PROT_NONE
|
|
#endif
|
|
|
|
/* The BSDI BSD/386 1.1 headers don't define PROT_NONE */
|
|
#ifndef PROT_NONE
|
|
#define PROT_NONE 0
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#ifdef HAVE_SIGINFO
|
|
static void complex_bushandler(int, siginfo_t *, void *);
|
|
static void complex_segvhandler(int, siginfo_t *, void *);
|
|
#else
|
|
static void simple_sighandler(int);
|
|
#endif
|
|
|
|
/*
|
|
** round_up(amount, align) returns `amount' rounded up to the nearest
|
|
** alignment boundary. `align' must be a power of 2.
|
|
*/
|
|
|
|
#define round_up(amount, align) ((((amount) - 1) | ((align) - 1)) + 1)
|
|
|
|
static void setup_mprotect(void);
|
|
#ifdef HAVE_SIGINFO
|
|
static bool try_munprotect(void *, void *);
|
|
static char *explain_context(ucontext_t *);
|
|
#endif
|
|
|
|
static void setup_signal(void);
|
|
|
|
Word fake_reg[MAX_FAKE_REG];
|
|
|
|
Word virtual_reg_map[MAX_REAL_REG] = VIRTUAL_REG_MAP_BODY;
|
|
|
|
unsigned long num_uses[MAX_RN];
|
|
|
|
MemoryZone *zone_table;
|
|
|
|
MemoryZone *used_memory_zones;
|
|
MemoryZone *free_memory_zones;
|
|
|
|
MemoryZone *detstack_zone;
|
|
#ifndef SPEED
|
|
MemoryZone *dumpstack_zone;
|
|
int dumpindex;
|
|
#endif
|
|
MemoryZone *nondetstack_zone;
|
|
#ifndef CONSERVATIVE_GC
|
|
MemoryZone *heap_zone;
|
|
MemoryZone *solutions_heap_zone;
|
|
Word *solutions_heap_pointer;
|
|
#endif
|
|
|
|
static size_t unit;
|
|
static size_t page_size;
|
|
|
|
static MemoryZone *get_zone(void);
|
|
static void unget_zone(MemoryZone *zone);
|
|
|
|
/*
|
|
** We manage the handing out of offets through the cache by
|
|
** computing the offsets once and storing them in an array
|
|
** (in shared memory if necessary). We then maintain a global
|
|
** counter used to index the array which we increment (modulo
|
|
** the size of the array) after handing out each offset.
|
|
*/
|
|
|
|
#define CACHE_SLICES 8
|
|
|
|
static size_t *offset_vector;
|
|
static int *offset_counter;
|
|
static SpinLock *offset_lock;
|
|
size_t next_offset(void);
|
|
|
|
static void init_memory_arena(void);
|
|
static void init_zones(void);
|
|
|
|
void init_memory(void)
|
|
{
|
|
/*
|
|
** Convert all the sizes are from kilobytes to bytes and
|
|
** make sure they are multiples of the page and cache sizes.
|
|
*/
|
|
|
|
page_size = getpagesize();
|
|
unit = max(page_size, pcache_size);
|
|
|
|
#ifdef CONSERVATIVE_GC
|
|
heap_zone_size = 0;
|
|
heap_size = 0;
|
|
solutions_heap_zone_size = 0;
|
|
solutions_heap_size = 0;
|
|
#else
|
|
heap_zone_size = round_up(heap_zone_size * 1024, unit);
|
|
heap_size = round_up(heap_size * 1024, unit);
|
|
solutions_heap_zone_size = round_up(solutions_heap_zone_size * 1024,
|
|
unit);
|
|
solutions_heap_size = round_up(solutions_heap_size * 1024, unit);
|
|
#endif
|
|
|
|
detstack_size = round_up(detstack_size * 1024, unit);
|
|
detstack_zone_size = round_up(detstack_zone_size * 1024, unit);
|
|
nondstack_size = round_up(nondstack_size * 1024, unit);
|
|
nondstack_zone_size = round_up(nondstack_zone_size * 1024, unit);
|
|
|
|
/*
|
|
** If the zone sizes were set to something too big, then
|
|
** set them to a single unit.
|
|
*/
|
|
|
|
#ifndef CONSERVATIVE_GC
|
|
if (heap_zone_size >= heap_size)
|
|
heap_zone_size = unit;
|
|
if (solutions_heap_zone_size >= solutions_heap_size)
|
|
solutions_heap_zone_size = unit;
|
|
#endif
|
|
|
|
if (detstack_zone_size >= detstack_size)
|
|
detstack_zone_size = unit;
|
|
|
|
if (nondstack_zone_size >= nondstack_size)
|
|
nondstack_zone_size = unit;
|
|
|
|
|
|
init_memory_arena();
|
|
init_zones();
|
|
}
|
|
|
|
/*
|
|
** init_memory_arena() allocates (if necessary) the top-level memory pool
|
|
** from which all allocations should come. If PARALLEL is defined, then
|
|
** this pool should be shared memory. In the absence of PARALLEL, it
|
|
** doesn't need to do anything, since with CONSERVATIVE_GC, the collector
|
|
** manages the heap, and without GC, we can allocate memory using memalign
|
|
** or malloc.
|
|
*/
|
|
static void init_memory_arena()
|
|
{
|
|
#ifdef PARALLEL
|
|
#ifndef CONSERVATIVE_GC
|
|
if (numprocs > 1) {
|
|
fatal_error("shared memory not implemented");
|
|
}
|
|
#else
|
|
if (numprocs > 1) {
|
|
fatal_error("shared memory not implemented with conservative gc");
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static void init_zones()
|
|
{
|
|
int i;
|
|
size_t fake_reg_offset;
|
|
|
|
/*
|
|
** Allocate the MemoryZone table.
|
|
*/
|
|
zone_table = allocate_bytes(MAX_ZONES * sizeof(MemoryZone));
|
|
|
|
/*
|
|
** Initialize the MemoryZone table.
|
|
*/
|
|
used_memory_zones = NULL;
|
|
free_memory_zones = zone_table;
|
|
for(i = 0; i < MAX_ZONES; i++)
|
|
{
|
|
zone_table[i].name = "unused";
|
|
zone_table[i].id = i;
|
|
zone_table[i].bottom = NULL;
|
|
zone_table[i].top = NULL;
|
|
zone_table[i].min = NULL;
|
|
#ifndef SPEED
|
|
zone_table[i].max = NULL;
|
|
#endif
|
|
#ifdef HAVE_MPROTECT
|
|
#ifdef HAVE_SIGINFO
|
|
zone_table[i].redzone = NULL;
|
|
#endif
|
|
zone_table[i].hardmax = NULL;
|
|
#endif
|
|
if (i+1 < MAX_ZONES)
|
|
zone_table[i].next = &(zone_table[i+1]);
|
|
else
|
|
zone_table[i].next = NULL;
|
|
}
|
|
|
|
offset_counter = allocate_object(int);
|
|
*offset_counter = 0;
|
|
|
|
offset_vector = allocate_array(size_t, CACHE_SLICES - 1);
|
|
|
|
fake_reg_offset = (Unsigned) fake_reg % pcache_size;
|
|
|
|
for (i = 0; i < CACHE_SLICES - 1; i++)
|
|
offset_vector[i] =
|
|
(fake_reg_offset + pcache_size / CACHE_SLICES)
|
|
% pcache_size;
|
|
}
|
|
|
|
|
|
void init_heap(void)
|
|
{
|
|
#ifndef CONSERVATIVE_GC
|
|
heap_zone = construct_zone("heap", 1, heap_size, next_offset(),
|
|
heap_zone_size, default_handler);
|
|
|
|
restore_transient_registers();
|
|
hp = heap_zone->min;
|
|
save_transient_registers();
|
|
|
|
solutions_heap_zone = construct_zone("solutions_heap", 1,
|
|
solutions_heap_size, next_offset(),
|
|
solutions_heap_zone_size, default_handler);
|
|
|
|
#endif
|
|
}
|
|
|
|
MemoryZone *get_zone(void)
|
|
{
|
|
MemoryZone *zone;
|
|
|
|
/*
|
|
** unlink the first zone on the free-list,
|
|
** link it onto the used-list and return it.
|
|
*/
|
|
zone = free_memory_zones;
|
|
if (zone == NULL)
|
|
{
|
|
fatal_error("no more memory zones");
|
|
}
|
|
free_memory_zones = free_memory_zones->next;
|
|
|
|
zone->next = used_memory_zones;
|
|
used_memory_zones = zone;
|
|
|
|
return zone;
|
|
}
|
|
|
|
void unget_zone(MemoryZone *zone)
|
|
{
|
|
MemoryZone *prev, *tmp;
|
|
|
|
/*
|
|
** Find the zone on the used list, and unlink it from
|
|
** the list, then link it onto the start of the free-list.
|
|
*/
|
|
for(prev = NULL, tmp = used_memory_zones;
|
|
tmp && tmp != zone; prev = tmp, tmp = tmp->next) ;
|
|
if (tmp == NULL)
|
|
fatal_error("memory zone not found!");
|
|
if (prev == NULL)
|
|
{
|
|
used_memory_zones = used_memory_zones->next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = tmp->next;
|
|
}
|
|
|
|
zone->next = free_memory_zones;
|
|
free_memory_zones = zone;
|
|
}
|
|
|
|
/*
|
|
** successive calls to next_offset return offsets modulo the primary
|
|
** cache size (carefully avoiding ever giving an offset that clashes
|
|
** with fake_reg_array). This is used to give different memory zones
|
|
** different starting points across the caches so that it is better
|
|
** utilized.
|
|
** An alternative implementation would be to increment the offset by
|
|
** a fixed amount (eg 2Kb) so that as primary caches get bigger, we
|
|
** allocate more offsets across them.
|
|
*/
|
|
size_t next_offset(void)
|
|
{
|
|
size_t offset;
|
|
|
|
get_lock(offset_lock);
|
|
|
|
offset = offset_vector[*offset_counter];
|
|
|
|
*offset_counter = (*offset_counter + 1) % (CACHE_SLICES - 1);
|
|
|
|
release_lock(offset_lock);
|
|
|
|
return offset;
|
|
}
|
|
|
|
MemoryZone *create_zone(const char *name, int id, size_t size,
|
|
size_t offset, size_t redsize,
|
|
bool ((*handler)(Word *addr, MemoryZone *zone, void *context)))
|
|
{
|
|
Word *base;
|
|
size_t total_size;
|
|
|
|
/*
|
|
** total allocation is:
|
|
** unit (roundup to page boundary)
|
|
** size (including redzone)
|
|
** unit (an extra page for protection if
|
|
** mprotect is being used)
|
|
*/
|
|
#ifdef HAVE_MPROTECT
|
|
total_size = size + 2 * unit;
|
|
#else
|
|
total_size = size + unit;
|
|
#endif
|
|
|
|
#ifndef PARALLEL
|
|
base = memalign(unit, total_size);
|
|
if (base == NULL)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "unable allocate memory zone: %s#%d", name, id);
|
|
fatal_error(buf);
|
|
}
|
|
#else
|
|
#error "allocation in shared memory not supported yet"
|
|
#endif
|
|
|
|
return construct_zone(name, id, base, size, offset, redsize, handler);
|
|
}
|
|
|
|
MemoryZone *construct_zone(const char *name, int id, Word *base,
|
|
size_t size, size_t offset, size_t redsize,
|
|
bool ((*handler)(Word *addr, MemoryZone *zone, void *context)))
|
|
{
|
|
MemoryZone *zone;
|
|
size_t total_size;
|
|
|
|
if (base == NULL)
|
|
fatal_error("construct_zone called with NULL pointer");
|
|
|
|
zone = get_zone();
|
|
|
|
zone->name = name;
|
|
zone->id = id;
|
|
|
|
#if defined(HAVE_MPROTECT) && defined(HAVE_SIGINFO)
|
|
zone->handler = handler;
|
|
#endif
|
|
|
|
zone->bottom = base;
|
|
|
|
#ifdef HAVE_MPROTECT
|
|
total_size = size + unit;
|
|
#else
|
|
total_size = size;
|
|
#endif
|
|
|
|
zone->top = (Word *) ((char *)base+total_size);
|
|
zone->min = (Word *) ((char *)base+offset);
|
|
#ifndef SPEED
|
|
zone->max = zone->min;
|
|
#endif
|
|
|
|
/*
|
|
** setup the redzone+hardzone
|
|
*/
|
|
#ifdef HAVE_MPROTECT
|
|
#ifdef HAVE_SIGINFO
|
|
zone->redzone_base = zone->redzone = (Word *)
|
|
round_up((Unsigned)base + size - redsize, unit);
|
|
if (mprotect((char *)zone->redzone, redsize + unit, MY_PROT) < 0)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "unable to set %s#%d redzone\n"
|
|
"base=%p, redzone=%p",
|
|
zone->name, zone->id, zone->bottom, zone->redzone);
|
|
fatal_error(buf);
|
|
}
|
|
#else /* !HAVE_SIGINFO */
|
|
zone->hardmax = (Word *) ((char *)zone->top-unit);
|
|
if (mprotect((char *)zone->hardmax, unit, MY_PROT) < 0)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "unable to set %s#%d hardmax\n"
|
|
"base=%p, hardmax=%p",
|
|
zone->name, zone->id, zone->bottom, zone->hardmax);
|
|
fatal_error(buf);
|
|
}
|
|
#endif /* HAVE_SIGINFO */
|
|
#endif /* HAVE_MPROTECT */
|
|
|
|
return zone;
|
|
}
|
|
|
|
void reset_zone(MemoryZone *zone)
|
|
{
|
|
#if defined(HAVE_MPROTECT) && defined(HAVE_SIGINFO)
|
|
zone->redzone = zone->redzone_base;
|
|
|
|
if (mprotect((char *)zone->redzone,
|
|
((char *)zone->top) - ((char *) zone->redzone), MY_PROT) < 0)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "unable to reset %s#%d redzone\n"
|
|
"base=%p, redzone=%p",
|
|
zone->name, zone->id, zone->bottom, zone->redzone);
|
|
fatal_error(buf);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(HAVE_MPROTECT) && defined(HAVE_SIGINFO)
|
|
/* try_munprotect is only useful if we have SIGINFO */
|
|
|
|
#define STDERR 2
|
|
|
|
#ifdef SPEED
|
|
|
|
static void print_dump_stack(void)
|
|
{
|
|
const char *msg = "You can get a stack dump by using grade debug\n";
|
|
write(STDERR, msg, strlen(msg));
|
|
}
|
|
|
|
#else /* !SPEED */
|
|
|
|
static void print_dump_stack(void)
|
|
{
|
|
int i;
|
|
int start;
|
|
int count;
|
|
char buf[2560];
|
|
|
|
strcpy(buf, "A dump of the det stack follows\n\n");
|
|
write(STDERR, buf, strlen(buf));
|
|
|
|
i = 0;
|
|
while (i < dumpindex)
|
|
{
|
|
start = i;
|
|
count = 1;
|
|
i++;
|
|
|
|
while (i < dumpindex &&
|
|
strcmp(((char **)(dumpstack_zone->min))[i],
|
|
((char **)(dumpstack_zone->min))[start]) == 0)
|
|
{
|
|
count++;
|
|
i++;
|
|
}
|
|
|
|
if (count > 1)
|
|
sprintf(buf, "%s * %d\n",
|
|
((char **)(dumpstack_zone->min))[start], count);
|
|
else
|
|
sprintf(buf, "%s\n",
|
|
((char **)(dumpstack_zone->min))[start]);
|
|
|
|
write(STDERR, buf, strlen(buf));
|
|
}
|
|
|
|
strcpy(buf, "\nend of stack dump\n");
|
|
write(STDERR, buf, strlen(buf));
|
|
|
|
}
|
|
|
|
#endif /* SPEED */
|
|
|
|
/*
|
|
** fatal_abort() prints an error message, possibly a stack dump, and then exits.
|
|
** It is like fatal_error(), except that it is safe to call
|
|
** from a signal handler.
|
|
*/
|
|
|
|
static void fatal_abort(void *context, const char *main_msg, int dump)
|
|
{
|
|
char *context_msg;
|
|
|
|
context_msg = explain_context((ucontext_t *) context);
|
|
write(STDERR, main_msg, strlen(main_msg));
|
|
write(STDERR, context_msg, strlen(context_msg));
|
|
|
|
if (dump)
|
|
print_dump_stack();
|
|
|
|
_exit(1);
|
|
}
|
|
|
|
static bool try_munprotect(void *addr, void *context)
|
|
{
|
|
Word * fault_addr;
|
|
Word * new_zone;
|
|
MemoryZone *zone;
|
|
|
|
fault_addr = (Word *) addr;
|
|
|
|
zone = used_memory_zones;
|
|
|
|
if (memdebug)
|
|
fprintf(stderr, "caught fault at %p\n", (void *)addr);
|
|
|
|
while(zone != NULL)
|
|
{
|
|
if (memdebug)
|
|
{
|
|
fprintf(stderr, "checking %s#%d: %p - %p\n",
|
|
zone->name, zone->id, (void *) zone->redzone,
|
|
(void *) zone->top);
|
|
}
|
|
|
|
if (zone->redzone <= fault_addr && fault_addr <= zone->top)
|
|
{
|
|
|
|
if (memdebug)
|
|
fprintf(stderr, "address is in %s#%d redzone\n",
|
|
zone->name, zone->id);
|
|
|
|
return zone->handler(fault_addr, zone, context);
|
|
}
|
|
zone = zone->next;
|
|
}
|
|
|
|
if (memdebug)
|
|
fprintf(stderr, "address not in any redzone.\n");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool default_handler(Word *fault_addr, MemoryZone *zone, void *context)
|
|
{
|
|
Word *new_zone;
|
|
size_t zone_size;
|
|
|
|
new_zone = (Word *) round_up((Unsigned) fault_addr + sizeof(Word), unit);
|
|
|
|
if (new_zone <= zone->hardmax)
|
|
{
|
|
zone_size = (char *)new_zone - (char *)zone->redzone;
|
|
|
|
if (memdebug)
|
|
{
|
|
fprintf(stderr, "trying to unprotect %s#%d from %p to %p (%x)\n",
|
|
zone->name, zone->id, (void *) zone->redzone, (void *) new_zone,
|
|
(int)zone_size);
|
|
}
|
|
if (mprotect((char *)zone->redzone, zone_size,
|
|
PROT_READ|PROT_WRITE) < 0)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "Mercury runtime: cannot unprotect %s#%d zone",
|
|
zone->name, zone->id);
|
|
perror(buf);
|
|
exit(1);
|
|
}
|
|
|
|
zone->redzone = new_zone;
|
|
|
|
if (memdebug)
|
|
{
|
|
fprintf(stderr, "successful: %s#%d redzone now %p to %p\n",
|
|
zone->name, zone->id, (void *) zone->redzone,
|
|
(void *) zone->top);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (memdebug)
|
|
{
|
|
fprintf(stderr, "can't unprotect last page of %s#%d\n",
|
|
zone->name, zone->id);
|
|
fflush(stdout);
|
|
}
|
|
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf,
|
|
"\nMercury runtime: memory zone %s#%d overflowed\n",
|
|
zone->name, zone->id);
|
|
fatal_abort(context, buf, FALSE);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool null_handler(Word *fault_addr, MemoryZone *zone, void *context)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
#else
|
|
/* not HAVE_MPROTECT || not HAVE_SIGINFO */
|
|
|
|
static bool try_munprotect(void *addr, void *context)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bool default_handler(Word *fault_addr, MemoryZone *zone, void *context)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bool null_handler(Word *fault_addr, MemoryZone *zone, void *context)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
#endif /* not HAVE_MPROTECT || not HAVE_SIGINFO */
|
|
|
|
#ifdef HAVE_SIGINFO
|
|
|
|
static void setup_signal(void)
|
|
{
|
|
struct sigaction act;
|
|
|
|
act.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
if (sigemptyset(&act.sa_mask) != 0)
|
|
{
|
|
perror("Mercury runtime: cannot set clear signal mask");
|
|
exit(1);
|
|
}
|
|
|
|
act.SIGACTION_FIELD = complex_bushandler;
|
|
if (sigaction(SIGBUS, &act, NULL) != 0)
|
|
{
|
|
perror("Mercury runtime: cannot set SIGBUS handler");
|
|
exit(1);
|
|
}
|
|
|
|
act.SIGACTION_FIELD = complex_segvhandler;
|
|
if (sigaction(SIGSEGV, &act, NULL) != 0)
|
|
{
|
|
perror("Mercury runtime: cannot set SIGSEGV handler");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void complex_bushandler(int sig, siginfo_t *info, void *context)
|
|
{
|
|
fflush(stdout);
|
|
|
|
if (sig != SIGBUS || !info || info->si_signo != SIGBUS)
|
|
{
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught strange bus error ***\n");
|
|
exit(1);
|
|
}
|
|
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught bus error ***\n");
|
|
|
|
if (info->si_code > 0)
|
|
{
|
|
fprintf(stderr, "cause: ");
|
|
switch (info->si_code)
|
|
{
|
|
|
|
case BUS_ADRALN:
|
|
fprintf(stderr, "invalid address alignment\n");
|
|
break;
|
|
|
|
case BUS_ADRERR:
|
|
fprintf(stderr, "non-existent physical address\n");
|
|
break;
|
|
|
|
case BUS_OBJERR:
|
|
fprintf(stderr, "object specific hardware error\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "unknown\n");
|
|
break;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "%s", explain_context((ucontext_t *) context));
|
|
fprintf(stderr, "address involved: %p\n",
|
|
(void *) info->si_addr);
|
|
}
|
|
|
|
dump_prev_locations();
|
|
fprintf(stderr, "exiting from signal handler\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void explain_segv(siginfo_t *info, void *context)
|
|
{
|
|
fflush(stdout);
|
|
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught segmentation violation ***\n");
|
|
|
|
if (!info) return;
|
|
|
|
if (info->si_code > 0)
|
|
{
|
|
fprintf(stderr, "cause: ");
|
|
switch (info->si_code)
|
|
{
|
|
|
|
case SEGV_MAPERR:
|
|
fprintf(stderr, "address not mapped to object\n");
|
|
break;
|
|
|
|
case SEGV_ACCERR:
|
|
fprintf(stderr, "bad permissions for mapped object\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "unknown\n");
|
|
break;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "%s", explain_context((ucontext_t *) context));
|
|
fprintf(stderr, "address involved: %p\n",
|
|
(void *) info->si_addr);
|
|
|
|
}
|
|
}
|
|
|
|
static void complex_segvhandler(int sig, siginfo_t *info, void *context)
|
|
{
|
|
if (sig != SIGSEGV || !info || info->si_signo != SIGSEGV)
|
|
{
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught strange segmentation violation ***\n");
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
** If we're debugging, print the segv explanation messages
|
|
** before we call try_munprotect. But if we're not debugging,
|
|
** only print them if try_munprotect fails.
|
|
*/
|
|
|
|
if (memdebug)
|
|
explain_segv(info, context);
|
|
|
|
if (try_munprotect(info->si_addr, context))
|
|
{
|
|
if (memdebug)
|
|
fprintf(stderr, "returning from signal handler\n\n");
|
|
|
|
return;
|
|
}
|
|
|
|
if (!memdebug)
|
|
explain_segv(info, context);
|
|
|
|
dump_prev_locations();
|
|
fprintf(stderr, "exiting from signal handler\n");
|
|
exit(1);
|
|
}
|
|
|
|
static char *explain_context(ucontext_t *context)
|
|
{
|
|
static char buf[100];
|
|
|
|
#ifdef PC_ACCESS
|
|
#ifdef PC_ACCESS_GREG
|
|
sprintf(buf, "PC at signal: %ld (%lx)\n",
|
|
(long) context->uc_mcontext.gregs[PC_ACCESS],
|
|
(long) context->uc_mcontext.gregs[PC_ACCESS]);
|
|
#else
|
|
sprintf(buf, "PC at signal: %ld (%lx)\n",
|
|
(long) context->uc_mcontext.PC_ACCESS,
|
|
(long) context->uc_mcontext.PC_ACCESS);
|
|
#endif
|
|
#else
|
|
/* if PC_ACCESS is not set, we don't know the context */
|
|
/* therefore we return an empty string to be printed */
|
|
buf[0] = '\0';
|
|
#endif
|
|
|
|
return buf;
|
|
}
|
|
|
|
#else
|
|
|
|
static void setup_signal(void)
|
|
{
|
|
if (signal(SIGBUS, simple_sighandler) == SIG_ERR)
|
|
{
|
|
perror("cannot set SIGBUS handler");
|
|
exit(1);
|
|
}
|
|
|
|
if (signal(SIGSEGV, simple_sighandler) == SIG_ERR)
|
|
{
|
|
perror("cannot set SIGSEGV handler");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void simple_sighandler(int sig)
|
|
{
|
|
fflush(stdout);
|
|
fprintf(stderr, "*** Mercury runtime: ");
|
|
|
|
switch (sig)
|
|
{
|
|
|
|
case SIGBUS:
|
|
fprintf(stderr, "caught bus error ***\n");
|
|
break;
|
|
|
|
case SIGSEGV: fprintf(stderr, "caught segmentation violation ***\n");
|
|
break;
|
|
|
|
default: fprintf(stderr, "caught unknown signal %d ***\n", sig);
|
|
break;
|
|
|
|
}
|
|
|
|
dump_prev_locations();
|
|
fprintf(stderr, "exiting from signal handler\n");
|
|
exit(1);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONSERVATIVE_GC
|
|
|
|
void *allocate_bytes(size_t numbytes)
|
|
{
|
|
void *tmp;
|
|
|
|
tmp = GC_MALLOC(numbytes);
|
|
|
|
if (tmp == NULL)
|
|
fatal_error("could not allocate memory");
|
|
|
|
return tmp;
|
|
}
|
|
|
|
#else
|
|
|
|
#ifdef PARALLEL
|
|
#error "shared memory not implemented"
|
|
#endif
|
|
|
|
void *allocate_bytes(size_t numbytes)
|
|
{
|
|
void *tmp;
|
|
|
|
tmp = malloc(numbytes);
|
|
|
|
if (tmp == NULL)
|
|
fatal_error("could not allocate memory");
|
|
|
|
return tmp;
|
|
}
|
|
|
|
#endif
|
|
|
|
void deallocate_memory(void *ptr)
|
|
{
|
|
#ifdef CONSERVATIVE_GC
|
|
GC_FREE(ptr);
|
|
#elif PARALLEL
|
|
#error "shared memory not implemented"
|
|
#else
|
|
free(ptr);
|
|
#endif
|
|
}
|