diff options
Diffstat (limited to 'vere/ext/nasm/asm/labels.c')
-rw-r--r-- | vere/ext/nasm/asm/labels.c | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/vere/ext/nasm/asm/labels.c b/vere/ext/nasm/asm/labels.c new file mode 100644 index 0000000..f6b940f --- /dev/null +++ b/vere/ext/nasm/asm/labels.c @@ -0,0 +1,720 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 1996-2018 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 "nasm.h" +#include "nasmlib.h" +#include "error.h" +#include "hashtbl.h" +#include "labels.h" + +/* + * A dot-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 + * @@. + */ +static bool islocal(const char *l) +{ + if (tasm_compatible_mode) { + if (l[0] == '@' && l[1] == '@') + return true; + } + + return (l[0] == '.' && l[1] != '.'); +} + +/* + * Return true if this falls into NASM's '..' namespace + */ +static bool ismagic(const char *l) +{ + return l[0] == '.' && l[1] == '.' && l[2] != '@'; +} + +/* + * Return true if we should update the local label base + * as a result of this symbol. We must exclude local labels + * as well as any kind of special labels, including ..@ ones. + */ +static bool set_prevlabel(const char *l) +{ + if (tasm_compatible_mode) { + if (l[0] == '@' && l[1] == '@') + return false; + } + + return l[0] != '.'; +} + +#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 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 + +/* string values for enum label_type */ +static const char * const types[] = { + "local", "static", "global", "extern", "required", "common", + "special", "output format special" +}; + +union label { /* actual label structures */ + struct { + int32_t segment; + int32_t subsection; /* Available for ofmt->herelabel() */ + int64_t offset; + int64_t size; + int64_t defined; /* 0 if undefined, passn+1 for when defn seen */ + int64_t lastref; /* Last pass where we saw a reference */ + char *label, *mangled, *special; + const char *def_file; /* Where defined */ + int32_t def_line; + enum label_type type, mangled_type; + } defn; + struct { + int32_t movingon; + int64_t dummy; + union label *next; + } admin; +}; + +struct permts { /* permanent text storage */ + struct permts *next; /* for the linked list */ + unsigned int size, usage; /* size and used space in ... */ + char data[PERMTS_SIZE]; /* ... the data block itself */ +}; +#define PERMTS_HEADER offsetof(struct permts, data) + +uint64_t global_offset_changed; /* counter for global offset changes */ + +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_alloc(size_t len); +static char *perm_copy(const char *string); +static char *perm_copy3(const char *s1, const char *s2, const char *s3); +static const char *mangle_label_name(union label *lptr); + +static const char *prevlabel; + +static bool initialized = false; + +/* + * Emit a symdef to the output and the debug format backends. + */ +static void out_symdef(union label *lptr) +{ + int backend_type; + int64_t backend_offset; + + /* Backend-defined special segments are passed to symdef immediately */ + if (pass_final()) { + /* Emit special fixups for globals and commons */ + switch (lptr->defn.type) { + case LBL_GLOBAL: + case LBL_REQUIRED: + case LBL_COMMON: + if (lptr->defn.special) + ofmt->symdef(lptr->defn.mangled, 0, 0, 3, lptr->defn.special); + break; + default: + break; + } + return; + } + + if (pass_type() != PASS_STAB && lptr->defn.type != LBL_BACKEND) + return; + + /* Clean up this hack... */ + switch(lptr->defn.type) { + case LBL_EXTERN: + /* If not seen in the previous or this pass, drop it */ + if (lptr->defn.lastref < pass_count()) + return; + + /* Otherwise, promote to LBL_REQUIRED at this time */ + lptr->defn.type = LBL_REQUIRED; + + /* fall through */ + case LBL_GLOBAL: + case LBL_REQUIRED: + backend_type = 1; + backend_offset = lptr->defn.offset; + break; + case LBL_COMMON: + backend_type = 2; + backend_offset = lptr->defn.size; + break; + default: + backend_type = 0; + backend_offset = lptr->defn.offset; + break; + } + + /* Might be necessary for a backend symbol */ + mangle_label_name(lptr); + + ofmt->symdef(lptr->defn.mangled, lptr->defn.segment, + backend_offset, backend_type, + lptr->defn.special); + + /* + * NASM special symbols are not passed to the debug format; none + * of the current backends want to see them. + */ + if (lptr->defn.type == LBL_SPECIAL || lptr->defn.type == LBL_BACKEND) + return; + + dfmt->debug_deflabel(lptr->defn.mangled, lptr->defn.segment, + lptr->defn.offset, backend_type, + lptr->defn.special); +} + +/* + * 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(const char *label, bool create, bool *created) +{ + union label *lptr, **lpp; + char *label_str = NULL; + struct hash_insert ip; + + nasm_assert(label != NULL); + + if (islocal(label)) + label = label_str = nasm_strcat(prevlabel, label); + + lpp = (union label **) hash_find(<ab, label, &ip); + lptr = lpp ? *lpp : NULL; + + if (lptr || !create) { + if (created) + *created = false; + return lptr; + } + + /* Create a new label... */ + if (lfree->admin.movingon == END_BLOCK) { + /* + * must allocate a new block + */ + lfree->admin.next = nasm_malloc(LBLK_SIZE); + lfree = lfree->admin.next; + init_block(lfree); + } + + if (created) + *created = true; + + nasm_zero(*lfree); + lfree->defn.label = perm_copy(label); + lfree->defn.subsection = NO_SEG; + if (label_str) + nasm_free(label_str); + + hash_add(&ip, lfree->defn.label, lfree); + return lfree++; +} + +enum label_type lookup_label(const char *label, + int32_t *segment, int64_t *offset) +{ + union label *lptr; + + if (!initialized) + return LBL_none; + + lptr = find_label(label, false, NULL); + if (lptr && lptr->defn.defined) { + int64_t lpass = pass_count() + 1; + + lptr->defn.lastref = lpass; + *segment = lptr->defn.segment; + *offset = lptr->defn.offset; + return lptr->defn.type; + } + + return LBL_none; +} + +static inline bool is_global(enum label_type type) +{ + return type == LBL_GLOBAL || type == LBL_COMMON; +} + +static const char *mangle_strings[] = {"", "", "", ""}; +static bool mangle_string_set[ARRAY_SIZE(mangle_strings)]; + +/* + * Set a prefix or suffix + */ +void set_label_mangle(enum mangle_index which, const char *what) +{ + if (mangle_string_set[which]) + return; /* Once set, do not change */ + + mangle_strings[which] = perm_copy(what); + mangle_string_set[which] = true; +} + +/* + * Format a label name with appropriate prefixes and suffixes + */ +static const char *mangle_label_name(union label *lptr) +{ + const char *prefix; + const char *suffix; + + if (likely(lptr->defn.mangled && + lptr->defn.mangled_type == lptr->defn.type)) + return lptr->defn.mangled; /* Already mangled */ + + switch (lptr->defn.type) { + case LBL_GLOBAL: + case LBL_STATIC: + case LBL_EXTERN: + case LBL_REQUIRED: + prefix = mangle_strings[LM_GPREFIX]; + suffix = mangle_strings[LM_GSUFFIX]; + break; + case LBL_BACKEND: + case LBL_SPECIAL: + prefix = suffix = ""; + break; + default: + prefix = mangle_strings[LM_LPREFIX]; + suffix = mangle_strings[LM_LSUFFIX]; + break; + } + + lptr->defn.mangled_type = lptr->defn.type; + + if (!(*prefix) && !(*suffix)) + lptr->defn.mangled = lptr->defn.label; + else + lptr->defn.mangled = perm_copy3(prefix, lptr->defn.label, suffix); + + return lptr->defn.mangled; +} + +static void +handle_herelabel(union label *lptr, int32_t *segment, int64_t *offset) +{ + int32_t oldseg; + + if (likely(!ofmt->herelabel)) + return; + + if (unlikely(location.segment == NO_SEG)) + return; + + oldseg = *segment; + + if (oldseg == location.segment && *offset == location.offset) { + /* This label is defined at this location */ + int32_t newseg; + bool copyoffset = false; + + nasm_assert(lptr->defn.mangled); + newseg = ofmt->herelabel(lptr->defn.mangled, lptr->defn.type, + oldseg, &lptr->defn.subsection, ©offset); + if (likely(newseg == oldseg)) + return; + + *segment = newseg; + if (copyoffset) { + /* Maintain the offset from the old to the new segment */ + switch_segment(newseg); + location.offset = *offset; + } else { + /* Keep a separate offset for the new segment */ + *offset = switch_segment(newseg); + } + } +} + +static bool declare_label_lptr(union label *lptr, + enum label_type type, const char *special) +{ + enum label_type oldtype = lptr->defn.type; + + if (special && !special[0]) + special = NULL; + + if (oldtype == type || (!pass_stable() && oldtype == LBL_LOCAL) || + (oldtype == LBL_EXTERN && type == LBL_REQUIRED)) { + lptr->defn.type = type; + + if (special) { + if (!lptr->defn.special) + lptr->defn.special = perm_copy(special); + else if (nasm_stricmp(lptr->defn.special, special)) + nasm_nonfatal("symbol `%s' has inconsistent attributes `%s' and `%s'", + lptr->defn.label, lptr->defn.special, special); + } + return true; + } else if (is_extern(oldtype) && is_global(type)) { + /* EXTERN or REQUIRED can be replaced with GLOBAL or COMMON */ + lptr->defn.type = type; + + /* Override special unconditionally */ + if (special) + lptr->defn.special = perm_copy(special); + return true; + } else if (is_extern(type) && (is_global(oldtype) || is_extern(oldtype))) { + /* + * GLOBAL or COMMON ignore subsequent EXTERN or REQUIRED; + * REQUIRED ignores subsequent EXTERN. + */ + + /* Ignore special unless we don't already have one */ + if (!lptr->defn.special) + lptr->defn.special = perm_copy(special); + + return false; /* Don't call define_label() after this! */ + } + + nasm_nonfatal("symbol `%s' declared both as %s and %s", + lptr->defn.label, types[lptr->defn.type], types[type]); + return false; +} + +bool declare_label(const char *label, enum label_type type, const char *special) +{ + union label *lptr = find_label(label, true, NULL); + return declare_label_lptr(lptr, type, special); +} + +/* + * The "normal" argument decides if we should update the local segment + * base name or not. + */ +void define_label(const char *label, int32_t segment, + int64_t offset, bool normal) +{ + union label *lptr; + bool created, changed; + int64_t size; + int64_t lpass, lastdef; + + /* + * The backend may invoke this during initialization, at which + * pass_count() is zero, so add one so we never have a zero value + * for a defined variable. + */ + lpass = pass_count() + 1; + + /* + * Phase errors here can be one of two types: a new label appears, + * or the offset changes. Increment global_offset_changed when that + * happens, to tell the assembler core to make another pass. + */ + lptr = find_label(label, true, &created); + + lastdef = lptr->defn.defined; + + if (segment) { + /* We are actually defining this label */ + if (is_extern(lptr->defn.type)) { + /* auto-promote EXTERN/REQUIRED to GLOBAL */ + lptr->defn.type = LBL_GLOBAL; + lastdef = 0; /* We are "re-creating" this label */ + } + } else { + /* It's a pseudo-segment (extern, required, common) */ + segment = lptr->defn.segment ? lptr->defn.segment : seg_alloc(); + } + + if (lastdef || lptr->defn.type == LBL_BACKEND) { + /* + * We have seen this on at least one previous pass, or + * potentially earlier in this same pass (in which case we + * will probably error out further down.) + */ + mangle_label_name(lptr); + handle_herelabel(lptr, &segment, &offset); + } + + if (ismagic(label) && lptr->defn.type == LBL_LOCAL) + lptr->defn.type = LBL_SPECIAL; + + if (set_prevlabel(label) && normal) + prevlabel = lptr->defn.label; + + if (lptr->defn.type == LBL_COMMON) { + size = offset; + offset = 0; + } else { + size = 0; /* This is a hack... */ + } + + changed = created || !lastdef || + lptr->defn.segment != segment || + lptr->defn.offset != offset || + lptr->defn.size != size; + global_offset_changed += changed; + + if (lastdef == lpass) { + int32_t saved_line = 0; + const char *saved_fname = NULL; + int noteflags; + + /* + * Defined elsewhere in the program, seen in this pass. + */ + if (changed) { + nasm_nonfatal("label `%s' inconsistently redefined", lptr->defn.label); + noteflags = ERR_NONFATAL|ERR_HERE|ERR_NO_SEVERITY; + } else { + /*! + *!label-redef [off] label redefined to an identical value + *! warns if a label is defined more than once, but the + *! value is identical. It is an unconditional error to + *! define the same label more than once to \e{different} values. + */ + nasm_warn(WARN_LABEL_REDEF, + "info: label `%s' redefined to an identical value", lptr->defn.label); + noteflags = ERR_WARNING|ERR_HERE|ERR_NO_SEVERITY|WARN_LABEL_REDEF; + } + + src_get(&saved_line, &saved_fname); + src_set(lptr->defn.def_line, lptr->defn.def_file); + nasm_error(noteflags, "info: label `%s' originally defined", lptr->defn.label); + src_set(saved_line, saved_fname); + } else if (changed && pass_final() && lptr->defn.type != LBL_SPECIAL) { + /*! + *!label-redef-late [err] label (re)defined during code generation + *! the value of a label changed during the final, code-generation + *! pass. This may be the result of strange use of the + *! preprocessor. This is very likely to produce incorrect code and + *! may end up being an unconditional error in a future + *! version of NASM. + * + * WARN_LABEL_LATE defaults to an error, as this should never + * actually happen. Just in case this is a backwards + * compatibility problem, still make it a warning so that the + * user can suppress or demote it. + * + * Note: As a special case, LBL_SPECIAL symbols are allowed + * to be changed even during the last pass. + */ + nasm_warn(WARN_LABEL_REDEF_LATE|ERR_UNDEAD, + "label `%s' %s during code generation", + lptr->defn.label, created ? "defined" : "changed"); + } + lptr->defn.segment = segment; + lptr->defn.offset = offset; + lptr->defn.size = size; + lptr->defn.defined = lpass; + + if (changed || lastdef != lpass) + src_get(&lptr->defn.def_line, &lptr->defn.def_file); + + if (lastdef != lpass) + out_symdef(lptr); +} + +/* + * Define a special backend label + */ +void backend_label(const char *label, int32_t segment, int64_t offset) +{ + if (!declare_label(label, LBL_BACKEND, NULL)) + return; + + define_label(label, segment, offset, false); +} + +int init_labels(void) +{ + ldata = lfree = nasm_malloc(LBLK_SIZE); + init_block(lfree); + + perm_head = perm_tail = + 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 * safe_alloc perm_alloc(size_t len) +{ + char *p; + + if (perm_tail->size - perm_tail->usage < len) { + size_t alloc_len = (len > PERMTS_SIZE) ? len : PERMTS_SIZE; + perm_tail->next = nasm_malloc(PERMTS_HEADER + alloc_len); + perm_tail = perm_tail->next; + perm_tail->next = NULL; + perm_tail->size = alloc_len; + perm_tail->usage = 0; + } + p = perm_tail->data + perm_tail->usage; + perm_tail->usage += len; + return p; +} + +static char *perm_copy(const char *string) +{ + char *p; + size_t len; + + if (!string) + return NULL; + + len = strlen(string)+1; /* Include final NUL */ + + p = perm_alloc(len); + memcpy(p, string, len); + + return p; +} + +static char * +perm_copy3(const char *s1, const char *s2, const char *s3) +{ + char *p; + size_t l1 = strlen(s1); + size_t l2 = strlen(s2); + size_t l3 = strlen(s3)+1; /* Include final NUL */ + + p = perm_alloc(l1+l2+l3); + memcpy(p, s1, l1); + memcpy(p+l1, s2, l2); + memcpy(p+l1+l2, s3, l3); + + return p; +} + +const char *local_scope(const 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. + */ |