nasm/labels.c
H. Peter Anvin aeb0e0e9f1 BR 2781900: handle common labels while optimizing
When optimizing, we have to keep track of common labels, since a
common symbol cannot be optimized -- only the linker will know where
it will end up.  In that sense it is similar to an EXTERN symbol.

Thus, allow them to be entered in the symbol table but make sure we
don't holler too hard on redefinition.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
2009-06-27 16:30:00 -07:00

499 lines
15 KiB
C

/* labels.c label handling for the Netwide Assembler
*
* The Netwide Assembler is copyright (C) 1996 Simon Tatham and
* Julian Hall. All rights reserved. The software is
* redistributable under the license given in the file "LICENSE"
* distributed in the NASM archive.
*/
#include "compiler.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "nasm.h"
#include "nasmlib.h"
#include "hashtbl.h"
/*
* A local label is one that begins with exactly one period. Things
* that begin with _two_ periods are NASM-specific things.
*
* If TASM compatibility is enabled, a local label can also begin with
* @@, so @@local is a TASM compatible local label. Note that we only
* check for the first @ symbol, although TASM requires both.
*/
#define islocal(l) \
(tasm_compatible_mode ? \
(((l)[0] == '.' || (l)[0] == '@') && (l)[1] != '.') : \
((l)[0] == '.' && (l)[1] != '.'))
#define islocalchar(c) \
(tasm_compatible_mode ? \
((c) == '.' || (c) == '@') : \
((c) == '.'))
#define LABEL_BLOCK 128 /* no. of labels/block */
#define LBLK_SIZE (LABEL_BLOCK*sizeof(union label))
#define END_LIST -3 /* don't clash with NO_SEG! */
#define END_BLOCK -2
#define BOGUS_VALUE -4
#define PERMTS_SIZE 4096 /* size of text blocks */
#if (PERMTS_SIZE > IDLEN_MAX)
#error "IPERMTS_SIZE must be less than or equal to IDLEN_MAX"
#endif
/* values for label.defn.is_global */
#define DEFINED_BIT 1
#define GLOBAL_BIT 2
#define EXTERN_BIT 4
#define COMMON_BIT 8
#define NOT_DEFINED_YET 0
#define TYPE_MASK 3
#define LOCAL_SYMBOL (DEFINED_BIT)
#define GLOBAL_PLACEHOLDER (GLOBAL_BIT)
#define GLOBAL_SYMBOL (DEFINED_BIT|GLOBAL_BIT)
union label { /* actual label structures */
struct {
int32_t segment;
int64_t offset;
char *label, *special;
int is_global, is_norm;
} defn;
struct {
int32_t movingon;
int64_t dummy;
union label *next;
} admin;
};
struct permts { /* permanent text storage */
struct permts *next; /* for the linked list */
int size, usage; /* size and used space in ... */
char data[PERMTS_SIZE]; /* ... the data block itself */
};
extern int64_t global_offset_changed; /* defined in nasm.c */
static struct hash_table ltab; /* labels hash table */
static union label *ldata; /* all label data blocks */
static union label *lfree; /* labels free block */
static struct permts *perm_head; /* start of perm. text storage */
static struct permts *perm_tail; /* end of perm. text storage */
static void init_block(union label *blk);
static char *perm_copy(const char *string);
static char *prevlabel;
static bool initialized = false;
char lprefix[PREFIX_MAX] = { 0 };
char lpostfix[PREFIX_MAX] = { 0 };
/*
* Internal routine: finds the `union label' corresponding to the
* given label name. Creates a new one, if it isn't found, and if
* `create' is true.
*/
static union label *find_label(char *label, int create)
{
char *prev;
int prevlen, len;
union label *lptr, **lpp;
char label_str[IDLEN_MAX];
struct hash_insert ip;
if (islocal(label)) {
prev = prevlabel;
prevlen = strlen(prev);
len = strlen(label);
if (prevlen+len >= IDLEN_MAX)
return NULL; /* Error... */
memcpy(label_str, prev, prevlen);
memcpy(label_str+prevlen, label, len+1);
label = label_str;
} else {
prev = "";
prevlen = 0;
}
lpp = (union label **) hash_find(&ltab, label, &ip);
lptr = lpp ? *lpp : NULL;
if (lptr || !create)
return lptr;
/* Create a new label... */
if (lfree->admin.movingon == END_BLOCK) {
/*
* must allocate a new block
*/
lfree->admin.next =
(union label *)nasm_malloc(LBLK_SIZE);
lfree = lfree->admin.next;
init_block(lfree);
}
lfree->admin.movingon = BOGUS_VALUE;
lfree->defn.label = perm_copy(label);
lfree->defn.special = NULL;
lfree->defn.is_global = NOT_DEFINED_YET;
hash_add(&ip, lfree->defn.label, lfree);
return lfree++;
}
bool lookup_label(char *label, int32_t *segment, int64_t *offset)
{
union label *lptr;
if (!initialized)
return false;
lptr = find_label(label, 0);
if (lptr && (lptr->defn.is_global & DEFINED_BIT)) {
*segment = lptr->defn.segment;
*offset = lptr->defn.offset;
return true;
} else
return false;
}
bool is_extern(char *label)
{
union label *lptr;
if (!initialized)
return false;
lptr = find_label(label, 0);
return (lptr && (lptr->defn.is_global & EXTERN_BIT));
}
void redefine_label(char *label, int32_t segment, int64_t offset, char *special,
bool is_norm, bool isextrn, struct ofmt *ofmt,
efunc error)
{
union label *lptr;
int exi;
/* This routine possibly ought to check for phase errors. Most assemblers
* check for phase errors at this point. I don't know whether phase errors
* are even possible, nor whether they are checked somewhere else
*/
(void)special; /* Don't warn that this parameter is unused */
(void)is_norm; /* Don't warn that this parameter is unused */
(void)isextrn; /* Don't warn that this parameter is unused */
(void)ofmt; /* Don't warn that this parameter is unused */
#ifdef DEBUG
#if DEBUG<3
if (!strncmp(label, "debugdump", 9))
#endif
error(ERR_DEBUG, "redefine_label (%s, %ld, %08lx, %s, %d, %d)",
label, segment, offset, special, is_norm, isextrn);
#endif
lptr = find_label(label, 1);
if (!lptr)
error(ERR_PANIC, "can't find label `%s' on pass two", label);
if (!islocal(label)) {
if (!islocalchar(*label) && lptr->defn.is_norm)
prevlabel = lptr->defn.label;
}
if (lptr->defn.offset != offset)
global_offset_changed++;
lptr->defn.offset = offset;
lptr->defn.segment = segment;
if (pass0 == 1) {
exi = !!(lptr->defn.is_global & GLOBAL_BIT);
if (exi) {
char *xsymbol;
int slen;
slen = strlen(lprefix);
slen += strlen(lptr->defn.label);
slen += strlen(lpostfix);
slen++; /* room for that null char */
xsymbol = nasm_malloc(slen);
snprintf(xsymbol, slen, "%s%s%s", lprefix, lptr->defn.label,
lpostfix);
ofmt->symdef(xsymbol, segment, offset, exi,
special ? special : lptr->defn.special);
ofmt->current_dfmt->debug_deflabel(xsymbol, segment, offset,
exi,
special ? special : lptr->
defn.special);
/** nasm_free(xsymbol); ! outobj.c stores the pointer; ouch!!! **/
} else {
if ((lptr->defn.is_global & (GLOBAL_BIT | EXTERN_BIT)) !=
EXTERN_BIT) {
ofmt->symdef(lptr->defn.label, segment, offset, exi,
special ? special : lptr->defn.special);
ofmt->current_dfmt->debug_deflabel(label, segment, offset,
exi,
special ? special :
lptr->defn.special);
}
}
}
/* if (pass0 == 1) */
}
void define_label(char *label, int32_t segment, int64_t offset, char *special,
bool is_norm, bool isextrn, struct ofmt *ofmt, efunc error)
{
union label *lptr;
int exi;
#ifdef DEBUG
#if DEBUG<3
if (!strncmp(label, "debugdump", 9))
#endif
error(ERR_DEBUG, "define_label (%s, %ld, %08lx, %s, %d, %d)",
label, segment, offset, special, is_norm, isextrn);
#endif
lptr = find_label(label, 1);
if (lptr->defn.is_global & DEFINED_BIT) {
error(ERR_NONFATAL, "symbol `%s' redefined", label);
return;
}
lptr->defn.is_global |= DEFINED_BIT;
if (isextrn)
lptr->defn.is_global |= EXTERN_BIT;
if (!islocalchar(label[0]) && is_norm) {
/* not local, but not special either */
prevlabel = lptr->defn.label;
} else if (islocal(label) && !*prevlabel) {
error(ERR_NONFATAL, "attempt to define a local label before any"
" non-local labels");
}
lptr->defn.segment = segment;
lptr->defn.offset = offset;
lptr->defn.is_norm = (!islocalchar(label[0]) && is_norm);
if (pass0 == 1 || (!is_norm && !isextrn && (segment > 0) && (segment & 1))) {
exi = !!(lptr->defn.is_global & GLOBAL_BIT);
if (exi) {
char *xsymbol;
int slen;
slen = strlen(lprefix);
slen += strlen(lptr->defn.label);
slen += strlen(lpostfix);
slen++; /* room for that null char */
xsymbol = nasm_malloc(slen);
snprintf(xsymbol, slen, "%s%s%s", lprefix, lptr->defn.label,
lpostfix);
ofmt->symdef(xsymbol, segment, offset, exi,
special ? special : lptr->defn.special);
ofmt->current_dfmt->debug_deflabel(xsymbol, segment, offset,
exi,
special ? special : lptr->
defn.special);
/** nasm_free(xsymbol); ! outobj.c stores the pointer; ouch!!! **/
} else {
if ((lptr->defn.is_global & (GLOBAL_BIT | EXTERN_BIT)) !=
EXTERN_BIT) {
ofmt->symdef(lptr->defn.label, segment, offset, exi,
special ? special : lptr->defn.special);
ofmt->current_dfmt->debug_deflabel(label, segment, offset,
exi,
special ? special :
lptr->defn.special);
}
}
} /* if (pass0 == 1) */
}
void define_common(char *label, int32_t segment, int32_t size, char *special,
struct ofmt *ofmt, efunc error)
{
union label *lptr;
lptr = find_label(label, 1);
if ((lptr->defn.is_global & DEFINED_BIT) &&
(passn == 1 || !(lptr->defn.is_global & COMMON_BIT))) {
error(ERR_NONFATAL, "symbol `%s' redefined", label);
return;
}
lptr->defn.is_global |= DEFINED_BIT|COMMON_BIT;
if (!islocalchar(label[0])) {
prevlabel = lptr->defn.label;
} else {
error(ERR_NONFATAL, "attempt to define a local label as a "
"common variable");
return;
}
lptr->defn.segment = segment;
lptr->defn.offset = 0;
if (pass0 == 0)
return;
ofmt->symdef(lptr->defn.label, segment, size, 2,
special ? special : lptr->defn.special);
ofmt->current_dfmt->debug_deflabel(lptr->defn.label, segment, size, 2,
special ? special : lptr->defn.
special);
}
void declare_as_global(char *label, char *special, efunc error)
{
union label *lptr;
if (islocal(label)) {
error(ERR_NONFATAL, "attempt to declare local symbol `%s' as"
" global", label);
return;
}
lptr = find_label(label, 1);
switch (lptr->defn.is_global & TYPE_MASK) {
case NOT_DEFINED_YET:
lptr->defn.is_global = GLOBAL_PLACEHOLDER;
lptr->defn.special = special ? perm_copy(special) : NULL;
break;
case GLOBAL_PLACEHOLDER: /* already done: silently ignore */
case GLOBAL_SYMBOL:
break;
case LOCAL_SYMBOL:
if (!(lptr->defn.is_global & EXTERN_BIT)) {
error(ERR_WARNING, "symbol `%s': GLOBAL directive "
"after symbol definition is an experimental feature", label);
lptr->defn.is_global = GLOBAL_SYMBOL;
}
break;
}
}
int init_labels(void)
{
hash_init(&ltab, HASH_LARGE);
ldata = lfree = (union label *)nasm_malloc(LBLK_SIZE);
init_block(lfree);
perm_head =
perm_tail = (struct permts *)nasm_malloc(sizeof(struct permts));
perm_head->next = NULL;
perm_head->size = PERMTS_SIZE;
perm_head->usage = 0;
prevlabel = "";
initialized = true;
return 0;
}
void cleanup_labels(void)
{
union label *lptr, *lhold;
initialized = false;
hash_free(&ltab);
lptr = lhold = ldata;
while (lptr) {
lptr = &lptr[LABEL_BLOCK-1];
lptr = lptr->admin.next;
nasm_free(lhold);
lhold = lptr;
}
while (perm_head) {
perm_tail = perm_head;
perm_head = perm_head->next;
nasm_free(perm_tail);
}
}
static void init_block(union label *blk)
{
int j;
for (j = 0; j < LABEL_BLOCK - 1; j++)
blk[j].admin.movingon = END_LIST;
blk[LABEL_BLOCK - 1].admin.movingon = END_BLOCK;
blk[LABEL_BLOCK - 1].admin.next = NULL;
}
static char *perm_copy(const char *string)
{
char *p;
int len = strlen(string)+1;
if (perm_tail->size - perm_tail->usage < len) {
perm_tail->next =
(struct permts *)nasm_malloc(sizeof(struct permts));
perm_tail = perm_tail->next;
perm_tail->next = NULL;
perm_tail->size = PERMTS_SIZE;
perm_tail->usage = 0;
}
p = perm_tail->data + perm_tail->usage;
memcpy(p, string, len);
perm_tail->usage += len;
return p;
}
char *local_scope(char *label)
{
return islocal(label) ? prevlabel : "";
}
/*
* Notes regarding bug involving redefinition of external segments.
*
* Up to and including v0.97, the following code didn't work. From 0.97
* developers release 2 onwards, it will generate an error.
*
* EXTERN extlabel
* newlabel EQU extlabel + 1
*
* The results of allowing this code through are that two import records
* are generated, one for 'extlabel' and one for 'newlabel'.
*
* The reason for this is an inadequacy in the defined interface between
* the label manager and the output formats. The problem lies in how the
* output format driver tells that a label is an external label for which
* a label import record must be produced. Most (all except bin?) produce
* the record if the segment number of the label is not one of the internal
* segments that the output driver is producing.
*
* A simple fix to this would be to make the output formats keep track of
* which symbols they've produced import records for, and make them not
* produce import records for segments that are already defined.
*
* The best way, which is slightly harder but reduces duplication of code
* and should therefore make the entire system smaller and more stable is
* to change the interface between assembler, define_label(), and
* the output module. The changes that are needed are:
*
* The semantics of the 'isextern' flag passed to define_label() need
* examining. This information may or may not tell us what we need to
* know (ie should we be generating an import record at this point for this
* label). If these aren't the semantics, the semantics should be changed
* to this.
*
* The output module interface needs changing, so that the `isextern' flag
* is passed to the module, so that it can be easily tested for.
*/