/// <summary> /// Remaps labels that match opcode names. Updated names will be added to LabelMap. /// This should be run after localization and underscore concealment have finished. /// </summary> /// <remarks> /// Most assemblers don't like it if you create a label with the same name as an /// opcode, e.g. "jmp LSR" doesn't work. We can use the label map to work around /// the issue. /// /// Most assemblers regard mnemonics as case-insensitive, even if labels are /// case-sensitive, so we want to remap both "lsr" and "LSR". /// /// This doesn't really have anything to do with label localization other than that /// we're updating the label remap table. /// </remarks> public void FixOpcodeLabels() { if (LabelMap == null) { LabelMap = new Dictionary <string, string>(); } // Create a searchable list of opcode names using the current CPU definition. // (All tested assemblers that failed on opcode names only did so for names // that were part of the current definition, e.g. "TSB" was accepted as a label // when the CPU was set to 6502.) Dictionary <string, Asm65.OpDef> opnames = new Dictionary <string, Asm65.OpDef>(); Asm65.CpuDef cpuDef = mProject.CpuDef; for (int i = 0; i < 256; i++) { Asm65.OpDef op = cpuDef.GetOpDef(i); // There may be multiple entries with the same name (e.g. "NOP"). That's fine. opnames[op.Mnemonic.ToUpperInvariant()] = op; } // Create a list of all labels, for uniqueness testing. If a label has been // remapped, we add the remapped entry. // (All tested assemblers that failed on opcode names only did so for names // in their non-localized form. While "LSR" failed, "@LSR", "_LSR", ".LSR", etc. // were accepted. So if it was remapped by the localizer, we don't need to // worry about it.) SortedList <string, string> allLabels = new SortedList <string, string>(); for (int i = 0; i < mProject.FileDataLength; i++) { Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { continue; } LabelMap.TryGetValue(sym.Label, out string mapLabel); if (mapLabel != null) { allLabels.Add(mapLabel, mapLabel); } else { allLabels.Add(sym.Label, sym.Label); } } // Now run through the list of labels, looking for any that match opcode // mnemonics. for (int i = 0; i < mProject.FileDataLength; i++) { Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { // No label at this offset. continue; } string cmpLabel = sym.Label; if (LabelMap.TryGetValue(sym.Label, out string mapLabel)) { cmpLabel = mapLabel; } if (opnames.ContainsKey(cmpLabel.ToUpperInvariant())) { //Debug.WriteLine("Remapping label (op mnemonic): " + sym.Label); int uval = 0; string uniqueLabel; do { uval++; uniqueLabel = cmpLabel + "_" + uval.ToString(); } while (allLabels.ContainsKey(uniqueLabel)); allLabels.Add(uniqueLabel, uniqueLabel); LabelMap.Add(sym.Label, uniqueLabel); } } if (LabelMap.Count == 0) { // didn't do anything, lose the table LabelMap = null; } }
/// <summary> /// Analyzes labels to identify which ones may be treated as non-global. /// </summary> public void Analyze() { Debug.Assert(LocalPrefix.Length > 0); // Init global flags list. An entry is set if the associated offset has a global // label. It will be false if the entry has a local label, or no label. mGlobalFlags.SetAll(false); // Currently we only support the "local labels have scope that ends at a global // label" variety. The basic idea is to start by assuming that everything not // explicitly marked global is local, and then identify situations like this: // // lda :local // global eor #$ff // :local sta $00 // // The reference crosses a global label, so the "target" label must be made global. // This can have ripple effects, so we have to iterate. Note it doesn't matter // whether "global" is referenced anywhere. // // The current algorithm uses a straightforward O(n^2) approach. // // Step 1: generate source/target pairs and global label list // GenerateLists(); // // Step 2: walk through the list of global symbols, identifying source/target // pairs that cross them. If a pair matches, the target label is added to the // end of the mGlobalLabels list, and removed from the pair list. // // When we're done, mGlobalFlags[] will identify the offsets with global labels. // for (int index = 0; index < mGlobalLabels.Count; index++) { FindIntersectingPairs(mGlobalLabels[index]); } // We're done with these. Take out the trash. mGlobalLabels.Clear(); mOffsetPairs.Clear(); // // Step 3: remap global labels. There are three reasons we might need to do this: // (1) It has a leading underscore AND LocalPrefix is '_'. // (2) The label matches an opcode mnemonic (case-insensitive) AND NoOpcodeMnemonics // is set. // (3) It's a non-unique local that got promoted to global. // // In each case we need to modify the label to meet the assembler requirements, and // then modify the label until it's unique. // LabelMap = new Dictionary <string, string>(); Dictionary <string, string> allGlobalLabels = new Dictionary <string, string>(); bool remapUnders = (LocalPrefix == "_"); HashSet <string> rsvdNames = new HashSet <string>(); if (QuirkNoOpcodeMnemonics) { // Create a searchable list of opcode names using the current CPU definition. // (All tested assemblers that failed on opcode names only did so for names // that were part of the current definition, e.g. "TSB" was accepted as a label // when the CPU was set to 6502.) Asm65.CpuDef cpuDef = mProject.CpuDef; for (int i = 0; i < 256; i++) { Asm65.OpDef op = cpuDef.GetOpDef(i); // There may be multiple entries with the same name (e.g. "NOP"). That's fine. rsvdNames.Add(op.Mnemonic.ToUpperInvariant()); } } // Add any words that the assembler just doesn't like. if (ReservedWords != null) { foreach (string str in ReservedWords) { rsvdNames.Add(str); } } for (int i = 0; i < mProject.FileDataLength; i++) { if (!mGlobalFlags[i]) { continue; } Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { // Should only happen when we insert a dummy global label for the // "variables end scope" quirk. continue; } RemapGlobalSymbol(sym, allGlobalLabels, rsvdNames, remapUnders); } // Remap any project/platform symbols that clash with opcode mnemonics or have // leading underscores that aren't allowed. foreach (DefSymbol defSym in mProject.ActiveDefSymbolList) { //if (opNames != null && opNames.ContainsKey(defSym.Label.ToUpperInvariant())) { // // Clashed with mnemonic. Uniquify it. // Debug.WriteLine("Renaming clashing def sym: " + defSym.Label); // string newLabel = MakeUnique(defSym.Label, allGlobalLabels); // LabelMap[defSym.Label] = newLabel; // allGlobalLabels.Add(newLabel, newLabel); //} RemapGlobalSymbol(defSym, allGlobalLabels, rsvdNames, remapUnders); } // Remap any address region pre-labels with inappropriate values. IEnumerator <CommonUtil.AddressMap.AddressChange> addrIter = mProject.AddrMap.AddressChangeIterator; while (addrIter.MoveNext()) { CommonUtil.AddressMap.AddressChange change = addrIter.Current; if (!change.IsStart || !change.Region.HasValidPreLabel) { continue; } Symbol sym = new Symbol(change.Region.PreLabel, change.Region.PreLabelAddress, Symbol.Source.AddrPreLabel, Symbol.Type.ExternalAddr, Symbol.LabelAnnotation.None); RemapGlobalSymbol(sym, allGlobalLabels, rsvdNames, remapUnders); } // // Step 4: remap local labels. There are two operations here. // // For each pair of global labels that have locals between them, we need to walk // through the locals and confirm that they don't clash with each other. If they // do, we need to uniquify them within the local scope. (This is only an issue // for non-unique locals.) // // Once a unique name has been found, we add an entry to LabelMap that has the // label with the LocalPrefix and without the non-unique tag. // // We also need to deal with symbols with a leading underscore when // LocalPrefix is '_'. // int startGlobal = -1; int numLocals = 0; // Allocate a Dictionary here and pass it through so we don't have to allocate // a new one each time. Dictionary <string, string> scopedLocals = new Dictionary <string, string>(); for (int i = 0; i < mProject.FileDataLength; i++) { if (mGlobalFlags[i]) { if (startGlobal < 0) { // very first one startGlobal = i; continue; } else if (numLocals > 0) { // There were locals following the previous global. Process them. ProcessLocals(startGlobal, i, scopedLocals); startGlobal = i; numLocals = 0; } else { // Two adjacent globals. startGlobal = i; } } else { // Not a global. Is there a local symbol here? Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym != null) { numLocals++; } } } if (numLocals != 0) { // do the last bit ProcessLocals(startGlobal, mProject.FileDataLength, scopedLocals); } }