/* 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 licence given in the file "Licence" * distributed in the NASM archive. */ #include #include #include #include #include "nasm.h" #include "nasmlib.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 32 /* no. of labels/block */ #define LBLK_SIZE (LABEL_BLOCK*sizeof(union label)) #define LABEL_HASHES 37 /* no. of hash table entries */ #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 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, offset; char *label, *special; int is_global, is_norm; } defn; struct { int32_t movingon, 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 int global_offset_changed; /* defined in nasm.c */ static union label *ltab[LABEL_HASHES]; /* using a hash table */ static union label *lfree[LABEL_HASHES]; /* pointer into the above */ 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(char *string1, char *string2); static char *prevlabel; static int 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) { int hash = 0; char *p, *prev; int prevlen; union label *lptr; if (islocal(label)) prev = prevlabel; else prev = ""; prevlen = strlen(prev); p = prev; while (*p) hash += *p++; p = label; while (*p) hash += *p++; hash %= LABEL_HASHES; lptr = ltab[hash]; while (lptr->admin.movingon != END_LIST) { if (lptr->admin.movingon == END_BLOCK) { lptr = lptr->admin.next; if (!lptr) break; } if (!strncmp(lptr->defn.label, prev, prevlen) && !strcmp(lptr->defn.label + prevlen, label)) return lptr; lptr++; } if (create) { if (lfree[hash]->admin.movingon == END_BLOCK) { /* * must allocate a new block */ lfree[hash]->admin.next = (union label *)nasm_malloc(LBLK_SIZE); lfree[hash] = lfree[hash]->admin.next; init_block(lfree[hash]); } lfree[hash]->admin.movingon = BOGUS_VALUE; lfree[hash]->defn.label = perm_copy(prev, label); lfree[hash]->defn.special = NULL; lfree[hash]->defn.is_global = NOT_DEFINED_YET; return lfree[hash]++; } else return NULL; } int lookup_label(char *label, int32_t *segment, int32_t *offset) { union label *lptr; if (!initialized) return 0; lptr = find_label(label, 0); if (lptr && (lptr->defn.is_global & DEFINED_BIT)) { *segment = lptr->defn.segment; *offset = lptr->defn.offset; return 1; } else return 0; } int is_extern(char *label) { union label *lptr; if (!initialized) return 0; lptr = find_label(label, 0); if (lptr && (lptr->defn.is_global & EXTERN_BIT)) return 1; else return 0; } void redefine_label(char *label, int32_t segment, int32_t offset, char *special, int is_norm, int 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)segment; /* Don't warn that this parameter is unused */ (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; } global_offset_changed |= (lptr->defn.offset != offset); lptr->defn.offset = offset; 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, int32_t offset, char *special, int is_norm, int 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 & 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) { error(ERR_NONFATAL, "symbol `%s' redefined", label); return; } lptr->defn.is_global |= DEFINED_BIT; if (!islocalchar(label[0])) /* not local, but not special either */ prevlabel = lptr->defn.label; else error(ERR_NONFATAL, "attempt to define a local label as a " "common variable"); lptr->defn.segment = segment; lptr->defn.offset = 0; 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_NONFATAL, "symbol `%s': GLOBAL directive must" " appear before symbol definition", label); break; } } int init_labels(void) { int i; for (i = 0; i < LABEL_HASHES; i++) { ltab[i] = (union label *)nasm_malloc(LBLK_SIZE); if (!ltab[i]) return -1; /* can't initialise, panic */ init_block(ltab[i]); lfree[i] = ltab[i]; } perm_head = perm_tail = (struct permts *)nasm_malloc(sizeof(struct permts)); if (!perm_head) return -1; perm_head->next = NULL; perm_head->size = PERMTS_SIZE; perm_head->usage = 0; prevlabel = ""; initialized = TRUE; return 0; } void cleanup_labels(void) { int i; initialized = FALSE; for (i = 0; i < LABEL_HASHES; i++) { union label *lptr, *lhold; lptr = lhold = ltab[i]; while (lptr) { while (lptr->admin.movingon != END_BLOCK) lptr++; 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(char *string1, char *string2) { char *p, *q; int len = strlen(string1) + strlen(string2) + 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 = q = perm_tail->data + perm_tail->usage; while ((*q = *string1++)) q++; while ((*q++ = *string2++)) ; perm_tail->usage = q - perm_tail->data; return p; } /* * 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. */