// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { // This is similar in operation to the AsmTass64 implementation. See comments there. Debug.Assert(mPcDepth >= 0); int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { if (change.Region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (mPcDepth == 0 && mFirstIsOpen) { mPcDepth++; // Set the "real" PC for the first address change. If we're in "loadable" // mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero // and then use a pseudo-PC. if (mOutputMode == OutputMode.Loadable) { OutputLine("*", "=", SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty); return; } else { // set the real PC to address zero to ensure we get a full 64KB OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty); } } AddressMap.AddressRegion region = change.Region; string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr + " {", string.Empty); mPcDepth++; } else { mPcDepth--; if (mPcDepth > 0 || !mFirstIsOpen) { // close previous block OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArEndDirective), string.Empty, string.Empty); //";" + SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective)); } else { // mark initial "*=" region as closed, but don't output anything mFirstIsOpen = false; } } }
// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { // 64tass separates the "compile offset", which determines where the output fits // into the generated binary, and "program counter", which determines the code // the assembler generates. Since we need to explicitly specify every byte in // the output file, having a distinct compile offset isn't useful here. We want // to set it once before the first line of code, then leave it alone. // // Any subsequent ORG changes are made to the program counter, and take the form // of a pair of ops (".logical <addr>" to open, ".here" to end). Omitting the .here // causes an error. // // If this is a "streamable" file, meaning it won't actually load into 64K of RAM // without wrapping around, then we skip the "* = addr" (same as "* = 0") and just // start with ".logical" segments. // // The assembler's approach is best represented by having an address region that // spans the entire file, with one or more "logical" regions inside. In practice // (especially for multi-bank 65816 code) that may not be the case, but the // assembler is still expecting us to start with a "* =" and then fit everything // inside that. So we treat the first region specially, whether or not it wraps // the rest of the file. Debug.Assert(mPcDepth >= 0); int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { if (change.Region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (mPcDepth == 0 && mFirstIsOpen) { mPcDepth++; // Set the "real" PC for the first address change. If we're in "loadable" // mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero // and then use a pseudo-PC. if (mOutputMode == OutputMode.Loadable) { OutputLine("*", "=", SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty); return; } else { // Set the real PC to address zero to ensure we get a full 64KB. The // assembler assumes this as a default, so it can be omitted. //OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty); } } AddressMap.AddressRegion region = change.Region; string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr, string.Empty); mPcDepth++; } else { mPcDepth--; if (mPcDepth > 0 || !mFirstIsOpen) { // close previous block OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArEndDirective), string.Empty, string.Empty); } else { // mark initial "*=" region as closed, but don't output anything mFirstIsOpen = false; } } }
/// <summary> /// Generates the initial mGlobalFlags and mGlobalLabels lists, as well as the /// full cross-reference pair list. /// </summary> private void GenerateLists() { // For every offset that has a label, add an entry to the source/target pair list // for every offset that references it. // // If the label isn't marked as "local or global", add it to the global-label list. // // The first label encountered is always treated as global. Note it may not appear // at offset zero. bool first = true; for (int offset = 0; offset < mProject.FileDataLength; offset++) { // Find all user labels and auto labels. Symbol sym = mProject.GetAnattrib(offset).Symbol; // In cc65, variable declarations end the local label scope. We insert a // fake global symbol if we encounter a table with a nonzero number of entries. if (QuirkVariablesEndScope && mProject.LvTables.TryGetValue(offset, out LocalVariableTable value) && value.Count > 0) { mGlobalFlags[offset] = true; mGlobalLabels.Add(new OffsetLabel(offset, "!VARTAB!")); continue; } if (sym == null) { // No label at this offset. continue; } if (first || !sym.CanBeLocal) { first = false; mGlobalFlags[offset] = true; mGlobalLabels.Add(new OffsetLabel(offset, sym.Label)); // Don't add to pairs list. continue; } // If nothing actually references this label, the xref set will be empty. XrefSet xrefs = mProject.GetXrefSet(offset); if (xrefs != null) { foreach (XrefSet.Xref xref in xrefs) { if (!xref.IsByName) { continue; } mOffsetPairs.Add(new OffsetPair(xref.Offset, offset)); } } } // Add the AddressRegion pre-labels to the list, as globals. We may need to // re-map the label if it matches a mnemonic. IEnumerator <CommonUtil.AddressMap.AddressChange> addrIter = mProject.AddrMap.AddressChangeIterator; while (addrIter.MoveNext()) { CommonUtil.AddressMap.AddressChange change = addrIter.Current; if (!change.IsStart || !change.Region.HasValidPreLabel) { continue; } mGlobalFlags[change.Region.Offset] = true; mGlobalLabels.Add(new OffsetLabel(change.Region.Offset, change.Region.PreLabel)); } }
/// <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); } }
// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { AddressMap.AddressRegion region = change.Region; if (region.HasValidPreLabel || region.HasValidIsRelative) { // Need to output the previous ORG, if one is pending. if (mNextAddress >= 0) { OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), SourceFormatter.FormatHexValue(mNextAddress, 4), string.Empty); } } if (region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (region.HasValidIsRelative) { // Found a valid IsRelative. Switch to "relative mode" if not there already. mIsInRelative = true; } if (mIsInRelative) { // Once we see a region with IsRelative set, we output regions as we // find them until the next Flush. string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr, string.Empty); mNextAddress = -1; return; } } mNextAddress = nextAddress; }
/// <summary> /// Formats the address map for viewing. /// </summary> public static string GenerateString(DisasmProject project, Formatter formatter) { AddressMap addrMap = project.AddrMap; bool showBank = !project.CpuDef.HasAddr16; StringBuilder sb = new StringBuilder(); int depth = 0; int prevOffset = -1; int prevAddr = 0; int lastEndOffset = -1; sb.AppendLine("Address region map for \"" + project.DataFileName + "\""); sb.Append(CRLF); IEnumerator <AddressChange> iter = addrMap.AddressChangeIterator; while (iter.MoveNext()) { AddressChange change = iter.Current; if (change.IsStart) { //if (change.Offset == lastEndOffset) { // // Extra vertical space for a START following an END at the same offset. // PrintDepthLines(sb, depth, true); // sb.Append(CRLF); // lastEndOffset = -1; //} if (prevOffset >= 0 && change.Offset != prevOffset) { // Start of region at new offset. Output address info for space // between previous start or end. PrintAddressInfo(sb, formatter, depth, prevAddr, change.Offset - prevOffset, showBank); } // Start following end, or start following start after a gap. sb.Append(formatter.FormatOffset24(change.Offset)); PrintDepthLines(sb, depth, false); sb.Append("+- " + "start"); if (change.IsSynthetic) { sb.Append(" (auto-generated)"); } else { // If there's a label here, show it. Anattrib attr = project.GetAnattrib(change.Offset); if (attr.Symbol != null && !string.IsNullOrEmpty(attr.Symbol.Label)) { sb.Append(" '"); sb.Append(attr.Symbol.GenerateDisplayLabel(formatter)); sb.Append("'"); } } if (change.Region.HasValidPreLabel) { sb.Append(" pre='"); sb.Append(change.Region.PreLabel); sb.Append("'"); } sb.Append(CRLF); prevOffset = change.Offset; prevAddr = change.Address; depth++; } else { Debug.Assert(prevOffset >= 0); depth--; if (change.Offset + 1 != prevOffset) { // End of region at new offset. Output address info for space // between previous start or end. PrintAddressInfo(sb, formatter, depth + 1, prevAddr, change.Offset + 1 - prevOffset, showBank); } sb.Append(formatter.FormatOffset24(change.Offset)); PrintDepthLines(sb, depth, false); sb.Append("+- " + "end"); if (change.Region.IsFloating) { sb.Append(" (floating)"); } //PrintAddress(sb, formatter, change.Address, showBank); //sb.Append(")"); sb.Append(CRLF); // Add a blank line, but with the depth lines. if (depth > 0) { PrintDepthLines(sb, depth, true); } sb.Append(CRLF); // Use offset+1 here so it lines up with start records. prevOffset = lastEndOffset = change.Offset + 1; prevAddr = change.Address; } } Debug.Assert(depth == 0); return(sb.ToString()); }