From 78e48c75fdfa3ea9217079eb1f33d241507dafc7 Mon Sep 17 00:00:00 2001 From: drmortalwombat <90205530+drmortalwombat@users.noreply.github.com> Date: Mon, 10 Apr 2023 18:26:59 +0200 Subject: [PATCH] Auto place globals in zero page with -Oz option --- README.md | 1 + include/crt.c | 11 +++- oscar64/Compiler.cpp | 5 +- oscar64/CompilerTypes.h | 5 +- oscar64/Declaration.cpp | 2 +- oscar64/Declaration.h | 1 + oscar64/GlobalAnalyzer.cpp | 88 +++++++++++++++++++++++++++++++- oscar64/GlobalAnalyzer.h | 2 + oscar64/NativeCodeGenerator.cpp | 88 ++++++++++++++++++++++++++++++-- oscar64/NativeCodeGenerator.h | 3 +- oscar64/oscar64.cpp | 2 + samples/memmap/charsetload.d64 | Bin 174848 -> 174848 bytes samples/memmap/tsr.c | 4 +- 13 files changed, 199 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 83ff53e..472a128 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ The compiler is command line driven, and creates an executable .prg file. * -Os: optimize for size * -Oi: enable auto inline of small functions (part of O2/O3) * -Oa: optimize inline assembler (part of O2/O3) +* -Oz: enable auto placement of global variables in zero page (part of O3) * -g: create source level debug info and add source line numbers to asm listing * -tf: target format, may be prg, crt or bin * -tm : target machine diff --git a/include/crt.c b/include/crt.c index c0fd628..da1578c 100644 --- a/include/crt.c +++ b/include/crt.c @@ -14,11 +14,12 @@ #define regs __regs -void StackStart, StackEnd, BSSStart, BSSEnd, CodeStart, CodeEnd; +void StackStart, StackEnd, BSSStart, BSSEnd, CodeStart, CodeEnd, ZeroStart, ZeroEnd; #pragma section(code, 0x0000, CodeStart, CodeEnd) #pragma section(stack, 0x0000, StackStart, StackEnd) #pragma section(bss, 0x0000, BSSStart, BSSEnd) +#pragma section(zeropage, 0x0000, ZeroStart, ZeroEnd) char spentry = 0; @@ -236,6 +237,14 @@ l2: dey bne l2 w2: + ldx #StackEnd - 2 diff --git a/oscar64/Compiler.cpp b/oscar64/Compiler.cpp index 65742df..5a1fb12 100644 --- a/oscar64/Compiler.cpp +++ b/oscar64/Compiler.cpp @@ -251,8 +251,10 @@ bool Compiler::GenerateCode(void) { if (mTargetMachine == TMACH_ATARI) regionZeroPage = mLinker->AddRegion(identZeroPage, 0x00e0, 0x00ff); - else + else if (mCompilerOptions & (COPT_EXTENDED_ZERO_PAGE | COPT_TARGET_NES)) regionZeroPage = mLinker->AddRegion(identZeroPage, 0x0080, 0x00ff); + else + regionZeroPage = mLinker->AddRegion(identZeroPage, 0x00f7, 0x00ff); } LinkerRegion* regionStartup = mLinker->FindRegion(identStartup); @@ -576,6 +578,7 @@ bool Compiler::GenerateCode(void) mGlobalAnalyzer->CheckInterrupt(); mGlobalAnalyzer->AutoInline(); + mGlobalAnalyzer->AutoZeroPage(mCompilationUnits->mSectionZeroPage, regionZeroPage->mEnd - regionZeroPage->mStart); if (mCompilerOptions & COPT_VERBOSE3) mGlobalAnalyzer->DumpCallGraph(); diff --git a/oscar64/CompilerTypes.h b/oscar64/CompilerTypes.h index e4177af..9226ba7 100644 --- a/oscar64/CompilerTypes.h +++ b/oscar64/CompilerTypes.h @@ -10,8 +10,9 @@ static const uint64 COPT_OPTIMIZE_AUTO_INLINE = 0x00000010; static const uint64 COPT_OPTIMIZE_AUTO_INLINE_ALL = 0x00000020; static const uint64 COPT_OPTIMIZE_AUTO_UNROLL = 0x00000040; static const uint64 COPT_OPTIMIZE_CONST_EXPRESSIONS = 0x00000080; +static const uint64 COPT_OPTIMIZE_AUTO_ZEROPAGE = 0x00000100; -static const uint64 COPT_OPTIMIZE_CODE_SIZE = 0x00000100; +static const uint64 COPT_OPTIMIZE_CODE_SIZE = 0x00001000; static const uint64 COPT_EXTENDED_ZERO_PAGE = 0x00010000; @@ -39,7 +40,7 @@ static const uint64 COPT_OPTIMIZE_SIZE = COPT_OPTIMIZE_BASIC | COPT_OPTIMIZE_INL static const uint64 COPT_OPTIMIZE_SPEED = COPT_OPTIMIZE_BASIC | COPT_OPTIMIZE_INLINE | COPT_OPTIMIZE_AUTO_INLINE | COPT_OPTIMIZE_AUTO_UNROLL | COPT_OPTIMIZE_CONST_EXPRESSIONS | COPT_OPTIMIZE_ASSEMBLER; -static const uint64 COPT_OPTIMIZE_ALL = COPT_OPTIMIZE_BASIC | COPT_OPTIMIZE_INLINE | COPT_OPTIMIZE_AUTO_INLINE | COPT_OPTIMIZE_AUTO_INLINE_ALL | COPT_OPTIMIZE_AUTO_UNROLL | COPT_OPTIMIZE_CONST_EXPRESSIONS | COPT_OPTIMIZE_ASSEMBLER; +static const uint64 COPT_OPTIMIZE_ALL = COPT_OPTIMIZE_BASIC | COPT_OPTIMIZE_INLINE | COPT_OPTIMIZE_AUTO_INLINE | COPT_OPTIMIZE_AUTO_INLINE_ALL | COPT_OPTIMIZE_AUTO_UNROLL | COPT_OPTIMIZE_CONST_EXPRESSIONS | COPT_OPTIMIZE_ASSEMBLER | COPT_OPTIMIZE_AUTO_ZEROPAGE; enum TargetMachine { diff --git a/oscar64/Declaration.cpp b/oscar64/Declaration.cpp index 9f5839c..b5d6d89 100644 --- a/oscar64/Declaration.cpp +++ b/oscar64/Declaration.cpp @@ -597,7 +597,7 @@ Declaration::Declaration(const Location& loc, DecType type) : mLocation(loc), mType(type), mScope(nullptr), mData(nullptr), mIdent(nullptr), mSize(0), mOffset(0), mFlags(0), mComplexity(0), mLocalSize(0), mBase(nullptr), mParams(nullptr), mValue(nullptr), mNext(nullptr), mVarIndex(-1), mLinkerObject(nullptr), mCallers(nullptr), mCalled(nullptr), mAlignment(1), mInteger(0), mNumber(0), mMinValue(-0x80000000LL), mMaxValue(0x7fffffffLL), mFastCallBase(0), mFastCallSize(0), mStride(0), mStripe(1), - mCompilerOptions(0) + mCompilerOptions(0), mUseCount(0) {} Declaration::~Declaration(void) diff --git a/oscar64/Declaration.h b/oscar64/Declaration.h index 49cf01d..9e03a48 100644 --- a/oscar64/Declaration.h +++ b/oscar64/Declaration.h @@ -195,6 +195,7 @@ public: LinkerSection * mSection; const uint8 * mData; LinkerObject * mLinkerObject; + int mUseCount; GrowingArray mCallers, mCalled; diff --git a/oscar64/GlobalAnalyzer.cpp b/oscar64/GlobalAnalyzer.cpp index 2e71192..4ff9f48 100644 --- a/oscar64/GlobalAnalyzer.cpp +++ b/oscar64/GlobalAnalyzer.cpp @@ -1,7 +1,7 @@ #include "GlobalAnalyzer.h" GlobalAnalyzer::GlobalAnalyzer(Errors* errors, Linker* linker) - : mErrors(errors), mLinker(linker), mCalledFunctions(nullptr), mCallingFunctions(nullptr), mVariableFunctions(nullptr), mFunctions(nullptr), mCompilerOptions(COPT_DEFAULT) + : mErrors(errors), mLinker(linker), mCalledFunctions(nullptr), mCallingFunctions(nullptr), mVariableFunctions(nullptr), mFunctions(nullptr), mGlobalVariables(nullptr), mCompilerOptions(COPT_DEFAULT) { } @@ -50,6 +50,86 @@ void GlobalAnalyzer::DumpCallGraph(void) printf("LEAF %d -> %s[%d, %08llx]\n", from->mCallers.Size(), from->mIdent->mString, from->mComplexity, from->mFlags ); } } + + for (int i = 0; i < mGlobalVariables.Size(); i++) + { + Declaration* var = mGlobalVariables[i]; + printf("VAR %s[%d, %08llx, %d]\n", var->mIdent->mString, var->mSize, var->mFlags, var->mUseCount); + } +} + +static int VarUseCountScale(Declaration* type) +{ + if (type->mType == DT_TYPE_BOOL || type->mType == DT_TYPE_INTEGER || type->mType == DT_TYPE_FLOAT || type->mType == DT_TYPE_ENUM) + return 0x100 / type->mSize; + else if (type->mType == DT_TYPE_POINTER) + return 0x800; + else if (type->mType == DT_TYPE_ARRAY) + { + if (type->mSize > 0) + return VarUseCountScale(type->mBase) / type->mSize; + else + return 0; + } + else if (type->mSize == DT_TYPE_STRUCT) + { + int size = 0; + Declaration* e = type->mParams; + while (e) + { + int t = VarUseCountScale(e->mBase); + if (t == 0) + return 0; + size += t; + e = e->mNext; + } + return size / (type->mSize * type->mSize); + } + else + return 0; +} + +void GlobalAnalyzer::AutoZeroPage(LinkerSection* lszp, int zpsize) +{ + if (mCompilerOptions & COPT_OPTIMIZE_AUTO_ZEROPAGE) + { + GrowingArray vars(nullptr); + + for (int i = 0; i < mGlobalVariables.Size(); i++) + { + Declaration* var = mGlobalVariables[i]; + if (var->mFlags & DTF_ANALYZED) + { + if (var->mFlags & DTF_ZEROPAGE) + zpsize -= var->mSize; + else if (var->mValue) + ; + else + { + var->mUseCount *= VarUseCountScale(var->mBase); + if (var->mUseCount) + { + int j = 0; + while (j < vars.Size() && vars[j]->mUseCount > var->mUseCount) + j++; + vars.Insert(j, var); + } + } + } + } + + int i = 0; + while (i < vars.Size() && zpsize > 0) + { + if (vars[i]->mSize <= zpsize && !vars[i]->mLinkerObject) + { + vars[i]->mSection = lszp; + vars[i]->mFlags |= DTF_ZEROPAGE; + zpsize -= vars[i]->mSize; + } + i++; + } + } } void GlobalAnalyzer::AutoInline(void) @@ -247,6 +327,8 @@ void GlobalAnalyzer::CheckInterrupt(void) void GlobalAnalyzer::AnalyzeProcedure(Expression* exp, Declaration* dec) { + dec->mUseCount++; + if (dec->mFlags & DTF_FUNC_ANALYZING) { dec->mFlags |= DTF_FUNC_RECURSIVE; @@ -328,10 +410,14 @@ void GlobalAnalyzer::AnalyzeAssembler(Expression* exp, Declaration* procDec) void GlobalAnalyzer::AnalyzeGlobalVariable(Declaration* dec) { + dec->mUseCount++; + if (!(dec->mFlags & DTF_ANALYZED)) { dec->mFlags |= DTF_ANALYZED; + mGlobalVariables.Push(dec); + if (dec->mValue) { Analyze(dec->mValue, dec, false); diff --git a/oscar64/GlobalAnalyzer.h b/oscar64/GlobalAnalyzer.h index a27660a..d657a36 100644 --- a/oscar64/GlobalAnalyzer.h +++ b/oscar64/GlobalAnalyzer.h @@ -14,6 +14,7 @@ public: void AutoInline(void); void CheckFastcall(Declaration* procDec); void CheckInterrupt(void); + void AutoZeroPage(LinkerSection * lszp, int zpsize); void AnalyzeProcedure(Expression* exp, Declaration* procDec); void AnalyzeAssembler(Expression* exp, Declaration* procDec); @@ -26,6 +27,7 @@ protected: Linker* mLinker; GrowingArray mCalledFunctions, mCallingFunctions, mVariableFunctions, mFunctions; + GrowingArray mGlobalVariables; Declaration* Analyze(Expression* exp, Declaration* procDec, bool lhs); diff --git a/oscar64/NativeCodeGenerator.cpp b/oscar64/NativeCodeGenerator.cpp index 328dc8c..f55eb56 100644 --- a/oscar64/NativeCodeGenerator.cpp +++ b/oscar64/NativeCodeGenerator.cpp @@ -21713,6 +21713,67 @@ bool NativeCodeBasicBlock::PatchCrossBlockY2XFloodExit(const NativeCodeBasicBloc return false; } +void NativeCodeBasicBlock::PropagateZPAbsolute(void) +{ + if (!mVisited) + { + mVisited = true; + + mDataSet.Reset(); + + for (int i = 0; i < mIns.Size(); i++) + { + if (i + 1 < mIns.Size() && + mIns[i].mType == ASMIT_LDA && mIns[i].mMode == ASMIM_ABSOLUTE && mIns[i].mLinkerObject && (mIns[i].mLinkerObject->mFlags & LOBJF_ZEROPAGE) && + mIns[i + 1].mType == ASMIT_STA && mIns[i + 1].mMode == ASMIM_ZERO_PAGE) + { + int r = mIns[i + 1].mAddress; + + mDataSet.mRegs[r].mMode = NRDM_ABSOLUTE; + mDataSet.mRegs[r].mLinkerObject = mIns[i].mLinkerObject; + mDataSet.mRegs[r].mValue = mIns[i].mAddress; + mDataSet.mRegs[r].mFlags = mIns[i].mFlags; + i++; + } + else if (mIns[i].mType == ASMIT_JSR) + mDataSet.Reset(); + else if (mIns[i].mMode == ASMIM_ZERO_PAGE) + { + int r = mIns[i].mAddress; + + if (mIns[i].ChangesAddress()) + mDataSet.ResetZeroPage(r); + else if (mDataSet.mRegs[r].mMode == NRDM_ABSOLUTE) + { + mIns[i].mMode = ASMIM_ABSOLUTE; + mIns[i].mLinkerObject = mDataSet.mRegs[r].mLinkerObject; + mIns[i].mAddress = mDataSet.mRegs[r].mValue; + } + } + else if (mIns[i].mMode == ASMIM_ABSOLUTE || mIns[i].mMode == ASMIM_ABSOLUTE_X || mIns[i].mMode == ASMIM_ABSOLUTE_Y) + { + if (mIns[i].ChangesAddress()) + mDataSet.ResetAbsolute(mIns[i].mLinkerObject, mIns[i].mAddress); + } + else if (mIns[i].mMode == ASMIM_INDIRECT_Y) + { + int r = mIns[i].mAddress; + + if (mDataSet.mRegs[r].mMode == NRDM_ABSOLUTE && mDataSet.mRegs[r + 1].mMode == NRDM_ABSOLUTE && + mDataSet.mRegs[r].mLinkerObject == mDataSet.mRegs[r + 1].mLinkerObject && + mDataSet.mRegs[r].mValue + 1 == mDataSet.mRegs[r + 1].mValue) + { + mIns[i].mLinkerObject = mDataSet.mRegs[r].mLinkerObject; + mIns[i].mAddress = mDataSet.mRegs[r].mValue; + } + } + } + + if (mTrueJump) mTrueJump->PropagateZPAbsolute(); + if (mFalseJump) mFalseJump->PropagateZPAbsolute(); + } +} + bool NativeCodeBasicBlock::IsDominatedBy(const NativeCodeBasicBlock* block) const { if (this == block) @@ -30287,14 +30348,14 @@ bool NativeCodeBasicBlock::BlockSizeCopyReduction(NativeCodeProcedure* proc, int NativeCodeInstruction lins = mIns[si + 2 * i * j + 0]; NativeCodeInstruction sins = mIns[si + 2 * i * j + 1]; - if (lins.mMode == ASMIM_ZERO_PAGE) + if (lins.mMode == ASMIM_ZERO_PAGE || (lins.mLinkerObject && (lins.mLinkerObject->mFlags & LOBJF_ZEROPAGE))) lins.mMode = ASMIM_ZERO_PAGE_X; else { lins.mMode = ASMIM_ABSOLUTE_X; sz++; } - if (sins.mMode == ASMIM_ZERO_PAGE) + if (sins.mMode == ASMIM_ZERO_PAGE || (sins.mLinkerObject && (sins.mLinkerObject->mFlags & LOBJF_ZEROPAGE))) sins.mMode = ASMIM_ZERO_PAGE_X; else { @@ -30410,7 +30471,11 @@ void NativeCodeBasicBlock::BlockSizeReduction(NativeCodeProcedure* proc, int xen while (i < mIns.Size()) { - if (i + 6 < mIns.Size() && + if (mIns[i].mType == ASMIT_NOP) + { + i++; + } + else if (i + 6 < mIns.Size() && mIns[i + 0].mType == ASMIT_CLC && mIns[i + 1].mType == ASMIT_LDA && mIns[i + 1].mMode == ASMIM_ZERO_PAGE && mIns[i + 2].mType == ASMIT_ADC && mIns[i + 2].mMode == ASMIM_IMMEDIATE && mIns[i + 2].mAddress == 1 && @@ -36191,6 +36256,8 @@ bool NativeCodeBasicBlock::PeepHoleOptimizer(NativeCodeProcedure* proc, int pass mIns[i + 1].mType == ASMIT_STA && mIns[i + 1].mMode == ASMIM_ZERO_PAGE) { int n = 3; + if (mIns[i + 0].mLinkerObject && (mIns[i + 0].mLinkerObject->mFlags & LOBJF_ZEROPAGE)) + n = 100; if (mIns[i + 0].mFlags & NCIF_VOLATILE) n = 1; if (mIns[i + 1].mLive & (LIVE_CPU_REG_A | LIVE_CPU_REG_Z)) @@ -36229,6 +36296,8 @@ bool NativeCodeBasicBlock::PeepHoleOptimizer(NativeCodeProcedure* proc, int pass mIns[i + 1].mType == ASMIT_STX && mIns[i + 1].mMode == ASMIM_ZERO_PAGE) { int n = 3; + if (mIns[i + 0].mLinkerObject && (mIns[i + 0].mLinkerObject->mFlags & LOBJF_ZEROPAGE)) + n = 100; if (mIns[i + 0].mFlags & NCIF_VOLATILE) n = 1; if (mIns[i + 1].mLive & (LIVE_CPU_REG_X | LIVE_CPU_REG_Z)) @@ -36254,8 +36323,12 @@ bool NativeCodeBasicBlock::PeepHoleOptimizer(NativeCodeProcedure* proc, int pass mIns[i + 0].mType == ASMIT_STA && mIns[i + 0].mMode == ASMIM_ZERO_PAGE && mIns[i + 1].mType == ASMIT_STA && mIns[i + 1].mMode == ASMIM_ABSOLUTE && !(mIns[i + 1].mFlags & NCIF_VOLATILE)) { + int n = 2; + if (mIns[i + 1].mLinkerObject && (mIns[i + 1].mLinkerObject->mFlags & LOBJF_ZEROPAGE)) + n = 100; + proc->ResetPatched(); - if (CheckSingleUseGlobalLoad(this, mIns[i + 0].mAddress, i + 2, mIns[i + 1], 2)) + if (CheckSingleUseGlobalLoad(this, mIns[i + 0].mAddress, i + 2, mIns[i + 1], n)) { proc->ResetPatched(); if (PatchSingleUseGlobalLoad(this, mIns[i + 0].mAddress, i + 2, mIns[i + 1])) @@ -39500,6 +39573,13 @@ void NativeCodeProcedure::Optimize(void) NativeRegisterDataSet data; mEntryBlock->ValueForwarding(this, data, true, true); + ResetVisited(); + mEntryBlock->PropagateZPAbsolute(); + + BuildDataFlowSets(); + ResetVisited(); + mEntryBlock->RemoveUnusedResultInstructions(); + #if 1 ResetVisited(); mEntryBlock->BlockSizeReduction(this, -1, -1); diff --git a/oscar64/NativeCodeGenerator.h b/oscar64/NativeCodeGenerator.h index 1a9209f..9b4f71c 100644 --- a/oscar64/NativeCodeGenerator.h +++ b/oscar64/NativeCodeGenerator.h @@ -592,12 +592,13 @@ public: bool PatchCrossBlockY2XFlood(const NativeCodeBasicBlock* block, int at); bool PatchCrossBlockY2XFloodExit(const NativeCodeBasicBlock* block); + void PropagateZPAbsolute(void); + bool IsDominatedBy(const NativeCodeBasicBlock* block) const; void CheckLive(void); void CheckBlocks(bool sequence = false); void CheckVisited(void); - }; class NativeCodeProcedure diff --git a/oscar64/oscar64.cpp b/oscar64/oscar64.cpp index c4ef03b..51f937a 100644 --- a/oscar64/oscar64.cpp +++ b/oscar64/oscar64.cpp @@ -189,6 +189,8 @@ int main2(int argc, const char** argv) compiler->mCompilerOptions |= COPT_OPTIMIZE_ASSEMBLER; else if (arg[2] == 'i') compiler->mCompilerOptions |= COPT_OPTIMIZE_AUTO_INLINE; + else if (arg[2] == 'z') + compiler->mCompilerOptions |= COPT_OPTIMIZE_AUTO_ZEROPAGE; } else if (arg[1] == 'e') { diff --git a/samples/memmap/charsetload.d64 b/samples/memmap/charsetload.d64 index c49efc336aebf24d3f81ec7ac1c0636c5937dfaf..2b32b48533774ee2746d1aac1595035f54aeab4b 100644 GIT binary patch delta 565 zcmZ{hze~eF6vywS77r^bh}i1|6jDUN!QxW?0SAdV)LNl}Mp{7x!O=nNaySV(lyg(0 z8InOgFm}WNxUq1J~@7=v`;j*c4*;F`$SZdY4g4k}< zK}&>nxehE5)~!ou(hkI^sdfNU>m)5nx=vhM8r>oaj75-TuI->ijV1oFBC7RqP`@C3 zt$xHdBkIT;J@?*%5xkUwp-ux^pNz_p8!r|xY9V>~oPyEb-Rf=CZ!Dt6+p70!eK5`1 zW^rqo5eu`}4)Q}K#T8_Io==SfKT5-(vieUyEJ*U6dKLd_Mv#H#WS%gxp*+O&1uXS} zau#TlhE@MYH>B_`0-+8xgsFEfW!q_u9M=vfOno8$xW3mCJY>J6b?c{fH$r-rS2EBA zv-~atJ+M;7lh7UlgR5B>f>}P11+6EGlcx4WT&9`vC&J>ofun-Bd5vj;2SEgY?1AjMuUU?D{i99mrJKj0v-f=yJgh(ap}BK9w6m%~ZWp`4oz z&5#W0fvJlQB1mxRqUa(x=o~>8!4Gn=ii6WV{O*08_wIe`H?jIntbPosV$HyUI9}34 zRly0+`w%rX*&Ec#brB4HOs~Aj>?*MY)jW{;~!Nb6hkk$Vi_b zcdWQNxk9hnZ$S&*EW-Gpf$dL5Wy|eX8yK~bynHFb6tzE?X790#y~c=*S!y5ok*4Aa zvQ^@%2JoH!o|Xw?O6{K%EJ*&5w$J_BB|);?`3hlVTX{<9i&*L-HYWISmgtN_`=Jwsp`OyydXBZ}%rUjgVg8=W{RwCV!ZNVc0mq^Dqzr zgR2WL4kn*n0ByK{^Op8ZJf>NhXTmbNfs=xG6^&_vR5B7E*n#C%3D)uSqTHMKnW>aE t`rd)#qR!xgI*a~}o>8~6KFKXL@bI%TP?+I&Wzb+|^Sulw?`0)2@dbpq!3Y2V diff --git a/samples/memmap/tsr.c b/samples/memmap/tsr.c index 2c3a9bf..2de5802 100644 --- a/samples/memmap/tsr.c +++ b/samples/memmap/tsr.c @@ -11,12 +11,12 @@ // shrink size of startup section #pragma section(startup, 0); -#pragma region(startup, 0x0801, 0x0860, , , { startup } ) +#pragma region(startup, 0x0801, 0x0870, , , { startup } ) // section for code copy #pragma section(rcode, 0) -#pragma region(rcode, 0x0860, 0x0900, , , { rcode } ) +#pragma region(rcode, 0x0870, 0x0900, , , { rcode } ) // main section to stay resident, save three bytes at the // beginning to have space for an entry jump