diff options
Diffstat (limited to 'labels.c')
-rw-r--r-- | labels.c | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/labels.c b/labels.c new file mode 100644 index 0000000..98408b4 --- /dev/null +++ b/labels.c @@ -0,0 +1,524 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 1996-2009 The NASM Authors - All Rights Reserved + * See the file AUTHORS included with the NASM distribution for + * the specific copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ----------------------------------------------------------------------- */ + +/* + * labels.c label handling for the Netwide Assembler + */ + +#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 16384 /* size of text blocks */ +#if (PERMTS_SIZE < IDLEN_MAX) + #error "IPERMTS_SIZE must be greater 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) { + nasm_error(ERR_NONFATAL, "identifier length exceed %i bytes", + IDLEN_MAX); + return NULL; + } + memcpy(label_str, prev, prevlen); + memcpy(label_str+prevlen, label, len+1); + label = label_str; + } else { + prev = ""; + prevlen = 0; + } + + lpp = (union label **) hash_find(<ab, 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; + } + + 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) +{ + 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 */ + +#ifdef DEBUG +#if DEBUG < 3 + if (!strncmp(label, "debugdump", 9)) +#endif + nasm_error(ERR_DEBUG, "redefine_label (%s, %"PRIx32", %"PRIx64", %s, %d, %d)", + label, segment, offset, special, is_norm, isextrn); +#endif + + lptr = find_label(label, 1); + if (!lptr) + nasm_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) +{ + union label *lptr; + int exi; + +#ifdef DEBUG +#if DEBUG<3 + if (!strncmp(label, "debugdump", 9)) +#endif + nasm_error(ERR_DEBUG, "define_label (%s, %"PRIx32", %"PRIx64", %s, %d, %d)", + label, segment, offset, special, is_norm, isextrn); +#endif + lptr = find_label(label, 1); + if (!lptr) + return; + if (lptr->defn.is_global & DEFINED_BIT) { + nasm_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) { + nasm_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) +{ + union label *lptr; + + lptr = find_label(label, 1); + if (!lptr) + return; + if ((lptr->defn.is_global & DEFINED_BIT) && + (passn == 1 || !(lptr->defn.is_global & COMMON_BIT))) { + nasm_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 { + nasm_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) +{ + union label *lptr; + + if (islocal(label)) { + nasm_error(ERR_NONFATAL, "attempt to declare local symbol `%s' as" + " global", label); + return; + } + lptr = find_label(label, 1); + if (!lptr) + return; + 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)) { + nasm_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(<ab, 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(<ab); + + 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; + + nasm_assert(len <= PERMTS_SIZE); + + 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. + */ |