/* 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 "compiler.h" #include #include #include #include #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 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 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 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) { 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++; } 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) { ltab = hash_init(); 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; } /* * 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. */