diff --git a/CommonUtil/Misc.cs b/CommonUtil/Misc.cs index 4180ee2..97e59b1 100644 --- a/CommonUtil/Misc.cs +++ b/CommonUtil/Misc.cs @@ -74,5 +74,35 @@ namespace CommonUtil { Debug.WriteLine("Crashed while crashing"); } } + + /// + /// Clears an array to a specific value, similar to memset() in libc. This is much + /// faster than setting array elements individually. + /// + /// + /// From https://stackoverflow.com/a/18659408/294248 + /// + /// Invokes Array.Copy() on overlapping elements. Other approaches involve using + /// Buffer.BlockCopy or unsafe code. Apparently .NET Core has an Array.Fill(), but + /// that doesn't exist in .NET Framework. + /// + /// We could get off the line a little faster by setting the first 16 or so elements in + /// a loop, bailing out if we finish early, so we don't start calling Array.Copy() until + /// it's actually faster to do so. I don't expect to be calling this often or for + /// small arrays though. + /// + /// Array element type. + /// Array reference. + /// Initialization value. + public static void Memset(T[] array, T elem) { + //Array.Fill(array, elem); + int length = array.Length; + if (length == 0) return; + array[0] = elem; + int count; + for (count = 1; count <= length / 2; count *= 2) + Array.Copy(array, 0, array, count, count); + Array.Copy(array, 0, array, count, length - count); + } } } diff --git a/SourceGen/Anattrib.cs b/SourceGen/Anattrib.cs index 799b0a9..6d86367 100644 --- a/SourceGen/Anattrib.cs +++ b/SourceGen/Anattrib.cs @@ -36,6 +36,8 @@ namespace SourceGen { InlineData = 1 << 2, // byte is inline data Data = 1 << 3, // byte is data + UsesDataBankReg = 1 << 4, // operand value should be merged with DBR + EntryPoint = 1 << 8, // external code branches here BranchTarget = 1 << 9, // internal code branches here ExternalBranch = 1 << 10, // this abs/rel branch lands outside input file @@ -113,6 +115,18 @@ namespace SourceGen { return IsInstructionStart || IsDataStart || IsInlineDataStart; } } + public bool UsesDataBankReg { + get { + return (mAttribFlags & AttribFlags.UsesDataBankReg) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.UsesDataBankReg; + } else { + mAttribFlags &= ~AttribFlags.UsesDataBankReg; + } + } + } public bool IsEntryPoint { get { return (mAttribFlags & AttribFlags.EntryPoint) != 0; diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs index 16dd1e4..08cbeca 100644 --- a/SourceGen/CodeAnalysis.cs +++ b/SourceGen/CodeAnalysis.cs @@ -838,14 +838,17 @@ namespace SourceGen { /// The operand's address, and if applicable, the operand's file offset, are /// stored in the Anattrib array. /// + /// Doesn't do anything with immediate data. + /// + /// /// For PC-relative operands (e.g. branches) it's tempting to simply adjust the file /// offset by the specified amount and convert that to an address. If the file /// has multiple ORGs, this can produce incorrect results. We need to convert the /// opcode's offset to an address, adjust by the operand, and then find the file /// offset that corresponds to the target address. - /// - /// Doesn't do anything with immediate data. - /// + /// + /// This is called once per instruction, on the analyzer's first visit. + /// /// Offset of the instruction opcode. /// Opcode being handled. (Passed in because the caller has it /// handy.) @@ -854,26 +857,36 @@ namespace SourceGen { int operand = op.GetOperand(mFileData, offset, mAnattribs[offset].StatusFlags); - // Add the bank to get a 24-bit address. We're currently using the program bank - // (K) rather than the data bank (B), which is correct for absolute and relative - // branches but wrong for 16-bit data operations. We currently have no way to - // know what the value of B is, so we use K because there's some small chance - // of it being correct. - // TODO(someday): figure out how to get the correct value for the B reg + // Add the bank to get a 24-bit address. For some instructions the relevant bank + // is known, because the operand is merged with the Program Bank Register (K) or + // is always in bank 0. For some we need the Data Bank Register (B). + // + // Instead of trying to track the B register during code analysis, we mark the + // relevant instructions now and fix them up later. We can get away with this + // because the DBR is only applied to data-load instructions, which don't affect + // the flow of the analysis pass. The value of B *is* affected by the analysis + // pass because a "smart PLB" handler needs to know where all the code is, so it's + // more efficient to figure it out later. int bank = mAnattribs[offset].Address & 0x7fff0000; // Extract target address. switch (op.AddrMode) { // These might refer to a location in the file, or might be external. - case OpDef.AddressMode.Abs: - case OpDef.AddressMode.AbsIndexX: - case OpDef.AddressMode.AbsIndexY: - case OpDef.AddressMode.AbsIndexXInd: - case OpDef.AddressMode.StackAbs: + case OpDef.AddressMode.Abs: // uses DBR iff !IsAbsolutePBR + case OpDef.AddressMode.AbsIndexX: // uses DBR + case OpDef.AddressMode.AbsIndexY: // uses DBR + if (!op.IsAbsolutePBR) { + mAnattribs[offset].UsesDataBankReg = true; + } + // Merge the PBR even if we eventually want the DBR; less to fix later. mAnattribs[offset].OperandAddress = operand | bank; break; - case OpDef.AddressMode.AbsInd: // JMP (addr), always bank 0 - case OpDef.AddressMode.AbsIndLong: // JMP [addr], always bank 0 + case OpDef.AddressMode.StackAbs: // assume PBR + case OpDef.AddressMode.AbsIndexXInd: // JMP (addr,X); uses program bank + mAnattribs[offset].OperandAddress = operand | bank; + break; + case OpDef.AddressMode.AbsInd: // JMP (addr); always bank 0 + case OpDef.AddressMode.AbsIndLong: // JMP [addr]; always bank 0 case OpDef.AddressMode.DP: case OpDef.AddressMode.DPIndexX: case OpDef.AddressMode.DPIndexY: @@ -888,7 +901,7 @@ namespace SourceGen { break; case OpDef.AddressMode.AbsIndexXLong: case OpDef.AddressMode.AbsLong: - // 24-bit address, don't add bank + // 24-bit address, don't alter bank mAnattribs[offset].OperandAddress = operand; break; case OpDef.AddressMode.PCRel: // rel operand; convert to absolute addr @@ -1207,5 +1220,88 @@ namespace SourceGen { throw new PluginException("Instr format rej: unknown sub type " + pluginSubType); } } + + /// + /// Data Bank Register value. + /// + /// + /// This is primarily a value from $00-ff, but we also want to encode the B=K special + /// mode. + /// + public enum DbrValue : short { + // $00-ff is bank number + Unknown = -1, // unknown / do-nothing + ProgramBankReg = -2 // set B=K + } + + /// + /// Determines the value of the Data Bank Register (DBR, register 'B') for relevant + /// instructions, and updates the Anattrib OperandOffset value. + /// + public void ApplyDataBankRegister(Dictionary userValues) { + Debug.Assert(!mCpuDef.HasAddr16); // 65816 only + + short[] bval = new short[mAnattribs.Length]; + + // Initialize all entries to "unknown". + Misc.Memset(bval, (short)DbrValue.Unknown); + + // Set B=K every time we cross an address boundary and the program bank changes. + DbrValue prevBank = DbrValue.Unknown; + foreach (AddressMap.AddressMapEntry ent in mAddrMap) { + int mapBank = ent.Addr >> 16; + if (mapBank != (int)prevBank) { + bval[ent.Offset] = (short)mapBank; + prevBank = (DbrValue)mapBank; + } + } + + // Apply the user-specified values. + foreach (KeyValuePair kvp in userValues) { + bval[kvp.Key] = (short)kvp.Value; + } + + // Run through the file, looking for PHK/PLB pairs. When we find one, set an + // entry for the PLB instruction unless an entry already exists there. + + // ? look for LDA #imm8 / PHA / PLB? + + // ... + + + // Run through file, updating instructions as needed. + short curVal = (short)DbrValue.Unknown; + for (int offset = 0; offset < mAnattribs.Length; offset++) { + if (bval[offset] != (short)DbrValue.Unknown) { + curVal = bval[offset]; + } + if (!mAnattribs[offset].UsesDataBankReg) { + continue; + } + Debug.Assert(mAnattribs[offset].IsInstructionStart); + Debug.Assert(curVal != (short)DbrValue.Unknown); + + int bank; + if (curVal == (short)DbrValue.ProgramBankReg) { + bank = mAnattribs[offset].Address >> 16; + } else { + Debug.Assert(curVal >= 0 && curVal < 256); + bank = curVal << 16; + } + + int newAddr = (mAnattribs[offset].OperandAddress & 0x0000ffff) | bank; + int newOffset = mAddrMap.AddressToOffset(offset, newAddr); + if (newAddr != mAnattribs[offset].OperandAddress || + newOffset != mAnattribs[offset].OperandOffset) { + Debug.WriteLine("DBR rewrite at +" + offset.ToString("x6") + ": $" + + mAnattribs[offset].OperandAddress.ToString("x6") + "/+" + + mAnattribs[offset].OperandOffset.ToString("x6") + " --> $" + + newAddr.ToString("x6") + "/+" + newOffset.ToString("x6")); + + mAnattribs[offset].OperandAddress = newAddr; + mAnattribs[offset].OperandOffset = newOffset; + } + } + } } } \ No newline at end of file diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 77255e1..f618a59 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -74,6 +74,11 @@ namespace SourceGen { /// public string[] Comments { get; private set; } + /// + /// Data Bank Register overrides. + /// + public Dictionary DbrValues { get; private set; } + /// /// Full line, possibly multi-line comments. /// @@ -274,6 +279,7 @@ namespace SourceGen { Comments[i] = string.Empty; } + DbrValues = new Dictionary(); LongComments = new Dictionary(); Notes = new SortedList(); @@ -807,6 +813,13 @@ namespace SourceGen { ca.Analyze(); reanalysisTimer.EndTask("CodeAnalysis.Analyze"); + if (!CpuDef.HasAddr16) { + // 24-bit address space, so DBR matters + reanalysisTimer.StartTask("CodeAnalysis.ApplyDataBankRegister"); + ca.ApplyDataBankRegister(DbrValues); + reanalysisTimer.EndTask("CodeAnalysis.ApplyDataBankRegister"); + } + // Save a copy of the current state. mCodeOnlyAnattribs = new Anattrib[mAnattribs.Length]; Array.Copy(mAnattribs, mCodeOnlyAnattribs, mAnattribs.Length); @@ -831,6 +844,8 @@ namespace SourceGen { DataAnalysis da = new DataAnalysis(this, mAnattribs); da.DebugLog = debugLog; + // Convert references to addresses into references to labels, generating labels + // as needed. reanalysisTimer.StartTask("DataAnalysis.AnalyzeDataTargets"); da.AnalyzeDataTargets(); reanalysisTimer.EndTask("DataAnalysis.AnalyzeDataTargets"); @@ -2103,6 +2118,27 @@ namespace SourceGen { UndoableChange.ReanalysisScope.CodeAndData); } break; + case UndoableChange.ChangeType.SetDataBank: { + // If there's no entry, treat it as an entry with value = Unknown. + if (!DbrValues.TryGetValue(offset, out CodeAnalysis.DbrValue current)) { + current = CodeAnalysis.DbrValue.Unknown; + } + if (current != (CodeAnalysis.DbrValue)oldValue) { + Debug.WriteLine("GLITCH: old DBR value mismatch (" + + current + " vs " + oldValue + ")"); + Debug.Assert(false); + } + if ((CodeAnalysis.DbrValue)newValue == CodeAnalysis.DbrValue.Unknown) { + DbrValues.Remove(offset); + } else { + DbrValues[offset] = (CodeAnalysis.DbrValue)newValue; + } + + // ignore affectedOffsets + Debug.Assert(uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.CodeAndData); + } + break; case UndoableChange.ChangeType.SetTypeHint: { // Always requires full code+data re-analysis. ApplyTypeHints((TypedRangeSet)oldValue, (TypedRangeSet)newValue); diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index e7dad1d..f315839 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -1858,6 +1858,41 @@ namespace SourceGen { EditLongComment(LineListGen.Line.HEADER_COMMENT_OFFSET); } + public bool CanEditDataBank() { + if (mProject.CpuDef.HasAddr16) { + return false; // only available for 65816 + } + if (SelectionAnalysis.mNumItemsSelected != 1) { + return false; + } + return (SelectionAnalysis.mLineType == LineListGen.Line.Type.Code /*|| + is data bank */); + } + + public void EditDataBank() { + int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); + int offset = CodeLineList[selIndex].FileOffset; + + CodeAnalysis.DbrValue curValue; + if (!mProject.DbrValues.TryGetValue(offset, out curValue)) { + curValue = CodeAnalysis.DbrValue.Unknown; + } + + EditDataBank dlg = new EditDataBank(mMainWin, mProject.AddrMap, mFormatter, curValue); + if (dlg.ShowDialog() != true) { + return; + } + + if (dlg.Result != curValue) { + Debug.WriteLine("Changing DBR at +" + offset.ToString("x6") + " to $" + + ((int)(dlg.Result)).ToString("x2")); + UndoableChange uc = + UndoableChange.CreateDataBankChange(offset, curValue, dlg.Result); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + public bool CanEditLabel() { if (SelectionAnalysis.mNumItemsSelected != 1) { return false; @@ -4095,7 +4130,7 @@ namespace SourceGen { } public void ShowFileHexDump() { - if (!OpenAnyFile(out string pathName)) { + if (!OpenAnyFile(null, out string pathName)) { return; } FileInfo fi = new FileInfo(pathName); @@ -4134,7 +4169,7 @@ namespace SourceGen { } public void SliceFiles() { - if (!OpenAnyFile(out string pathName)) { + if (!OpenAnyFile(null, out string pathName)) { return; } @@ -4144,7 +4179,7 @@ namespace SourceGen { } public void ConvertOmf() { - if (!OpenAnyFile(out string pathName)) { + if (!OpenAnyFile(Res.Strings.OMF_SELECT_FILE, out string pathName)) { return; } @@ -4183,11 +4218,14 @@ namespace SourceGen { ov.ShowDialog(); } - private bool OpenAnyFile(out string pathName) { + private bool OpenAnyFile(string title, out string pathName) { OpenFileDialog fileDlg = new OpenFileDialog() { Filter = Res.Strings.FILE_FILTER_ALL, FilterIndex = 1 }; + if (title != null) { + fileDlg.Title = title; + } if (fileDlg.ShowDialog() != true) { pathName = null; return false; @@ -4285,7 +4323,7 @@ namespace SourceGen { } public void Debug_ApplesoftToHtml() { - if (!OpenAnyFile(out string basPathName)) { + if (!OpenAnyFile(null, out string basPathName)) { return; } diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index 0bf9313..ffddd09 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -133,6 +133,7 @@ limitations under the License. Segment {0:D2}: Kind={1}; Attrs={2}; Name='{3}' Segment {0:D2}: {3} {1,-9} Name='{2}', Length={4} Seg{0:D2}: {1} '{2}' + Select OMF file The file doesn't exist. File is empty Unable to load data file diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index cd5da68..3b3317c 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -247,6 +247,8 @@ namespace SourceGen.Res { (string)Application.Current.FindResource("str_OmfSegHdrCommentFmt"); public static string OMF_SEG_NOTE_FMT = (string)Application.Current.FindResource("str_OmfSegNoteFmt"); + public static string OMF_SELECT_FILE = + (string)Application.Current.FindResource("str_OmfSelectFile"); public static string OPEN_DATA_DOESNT_EXIST = (string)Application.Current.FindResource("str_OpenDataDoesntExist"); public static string OPEN_DATA_EMPTY = diff --git a/SourceGen/RuntimeData/Help/advanced.html b/SourceGen/RuntimeData/Help/advanced.html index 1a76acb..f49d04c 100644 --- a/SourceGen/RuntimeData/Help/advanced.html +++ b/SourceGen/RuntimeData/Help/advanced.html @@ -339,6 +339,9 @@ not help you debug 6502 projects.

stackoverflow.com links.)
  • Applesoft to HTML. An experimental feature that formats an Applesoft program as HTML.
  • +
  • Apply Edit Commands. An experimental feature for importing values, + such as end-of-line comments, that have been generated by other + programs.
  • Apply Platform Symbols. An experimental feature for turning platform symbols into address labels. This will run through the list of all symbols loaded from .sym65 files and find addresses that fall within diff --git a/SourceGen/SGTestData/Source/20222-data-bank.S b/SourceGen/SGTestData/Source/20222-data-bank.S new file mode 100644 index 0000000..dc410cd --- /dev/null +++ b/SourceGen/SGTestData/Source/20222-data-bank.S @@ -0,0 +1,106 @@ +; Copyright 2020 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). +; +; Assembler: Merlin 32 + + org $1000 + +start + clc + xce + sep #$30 + mx %11 + + lda start + lda bank2 + + phk + plb + lda start + lda bank2 + + lda #$02 + pha + plb ;EDIT: set DBR=$02 + lda start + lda bank2 + + lda #$08 + pha + plb ;EDIT: set DBR=$08 + jsl bank2 + + lda #$02 + pha + plb ;EDIT: set DBR=$02 + jsl bank3 + + phk + plb + bit start + + jml alldone + +code2bz + dw snippet ;EDIT: format as 16-bit address +snippet rts + +code2zero + adr code2b ;EDIT: format as 24-bit address + + + + org $022000 +bank2 + ldal bank2 ;EDIT: set DBR=$08 + ldx #$00 + jsr code2a + jsr :call2bz + jsr :call2zero + nop + + lda #$03 + pha + plb ;EDIT: set DBR=$03 + lda bank3dat + + rtl + +:call2bz + jmp (code2bz) ;source/target in bank 0 +:call2zero + jmp [code2zero] ;source in bank 0 + +code2a + jmp (bank2addr,x) ;both addresses formed with K +code2b + nop + rts + +bank2addr + dw code2b + + + org $033000 +bank3 + ldal bank3 ;EDIT: set DBR=$02 + jsr :post_plb + beq :skip_mayhem +; Test PHK/PLB with an address shift in the middle. Not really something +; that should ever happen, but we need to handle everything. + phk + org $033020 ;EDIT: set address +:post_plb + plb +:self nop + bra :skip_mayhem + +:skip_mayhem + lda bank2addr + rtl + +bank3dat + dw bank3dat + + +alldone rts diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index 1d342fc..4452bb4 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -130,6 +130,9 @@ EditComment.xaml + + EditDataBank.xaml + EditDataOperand.xaml @@ -331,6 +334,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SourceGen/UndoableChange.cs b/SourceGen/UndoableChange.cs index 7810a8f..e349bf0 100644 --- a/SourceGen/UndoableChange.cs +++ b/SourceGen/UndoableChange.cs @@ -79,6 +79,9 @@ namespace SourceGen { // Adds, updates, or removes an AddressMap entry. SetAddress, + // Adds, updates, or removes a data bank register value. + SetDataBank, + // Changes the type hint. SetTypeHint, @@ -199,6 +202,30 @@ namespace SourceGen { return uc; } + /// + /// Creates an UndoableChange for a data bank register update. + /// + /// + /// + /// + /// + public static UndoableChange CreateDataBankChange(int offset, + CodeAnalysis.DbrValue oldValue, CodeAnalysis.DbrValue newValue) { + if (oldValue == newValue) { + Debug.WriteLine("No-op DBR change at +" + offset.ToString("x6") + ": " + oldValue); + } + UndoableChange uc = new UndoableChange(); + uc.Type = ChangeType.SetDataBank; + uc.Offset = offset; + uc.OldValue = oldValue; + uc.NewValue = newValue; + // We don't strictly need to re-analyze the code, since the current implementation + // handles it as a post-analysis fixup, but this lets us avoid having to compute the + // affected offsets. + uc.ReanalysisRequired = ReanalysisScope.CodeAndData; + return uc; + } + /// /// Creates an UndoableChange for a type hint update. Rather than adding a /// separate UndoableChange for each affected offset -- which could span the diff --git a/SourceGen/WpfGui/EditDataBank.xaml b/SourceGen/WpfGui/EditDataBank.xaml new file mode 100644 index 0000000..e1ec974 --- /dev/null +++ b/SourceGen/WpfGui/EditDataBank.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +