Login

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

/*

  SjASMPlus Z80 Cross Compiler - modified - RELOCATE extension

  Copyright (c) 2006 Sjoerd Mastijn (original SW)
  Copyright (c) 2020 Peter Ped Helcmanovsky (RELOCATE extension)

  This software is provided 'as-is', without any express or implied warranty.
  In no event will the authors be held liable for any damages arising from the
  use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it freely,
  subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim
         that you wrote the original software. If you use this software in a product,
         an acknowledgment in the product documentation would be appreciated but is
         not required.

  2. Altered source versions must be plainly marked as such, and must not be
         misrepresented as being the original software.

  3. This notice may not be removed or altered from any source distribution.

*/


// relocate.cpp

#include "sjdefs.h"

// local implementation stuff (shouldn't be visible through the header)
namespace Relocation {
        // when some part of opcode needs relocation, add its offset to the relocation table
        static void addOffsetToRelocate(const aint offset);

        static void refreshMaxTableCount();

        // local implementation specific data
        static constexpr aint ALTERNATIVE_OFFSET_WORD = +0x0272;        // must have at least one bit set in low three due to how some tests are written
        static constexpr aint ALTERNATIVE_OFFSET_HIGH = +0x0300;
        static TextFilePos startPos;                            // sourcefile position of last correct RELOCATE_START
        static size_t maxTableCount = 0;                        // maximum count of relocation data
        static std::vector<word> offsets;                       // offsets collected during current pass
        static std::vector<word> offsetsPrevious;       // offsets from pass2 (to be exported if pass3 is incomplete)
        static bool warnAboutContentChange = false;     // if any change in content between passes should be reported

        // public API variables
        aint alternative_offset = 0;    // offset to add to label value when evaluating alternatives
        EType type = OFF;                               // type of relocation block when inside
        bool areLabelsOffset = false;   // when the Labels should return the alternative value
        bool isResultAffected = false;  // when one of previous expression results was affected by it
        EType deltaType = OFF;                  // when isResultAffected && difference was precisely "+offset" or "+(offset>>8)"
}

// when some part of opcode needs relocation, add its offset to the relocation table
static void Relocation::addOffsetToRelocate(const aint offset) {
        // if table was already emitted from previous pass copy, warn about value discrepancies
        if (warnAboutContentChange) {
                const size_t newIndex = offsets.size();
                if (offsetsPrevious.size() <= newIndex || offsetsPrevious[newIndex] != offset) {
                        Warning("Relocation table seems internally inconsistent", "table content differs in last pass");
                        warnAboutContentChange = false;
                }
        }
        // add new offset to the relocation table
        offsets.push_back(offset);
}

void Relocation::resolveRelocationAffected(const aint opcodeRelOffset, EType minType) {
        if (type < minType) return;
        if (!isResultAffected) return;
        isResultAffected = false;                               // mark as processed
        if (minType <= deltaType) {
                if (INT_MAX == opcodeRelOffset) return;
                const aint address = (DISP_INSIDE_RELOCATE == PseudoORG) ? adrdisp : CurAddress;
                addOffsetToRelocate(address + opcodeRelOffset + type - deltaType);
                return;
        }
        // difference is not fixable by simple "+offset" relocator or in current mode
        WarningById(deltaType ? W_REL_UNSTABLE : W_REL_DIVERTS);
}

bool Relocation::checkAndWarn(bool doError) {
        // if nothing is affected by relocation, do nothing here
        if (!Relocation::isResultAffected) return false;
        // some result did set the "affected" flag, warn about it
        Relocation::isResultAffected = false;
        if (doError) {
                Error("Relocation makes one of the expressions unstable, use non-relocatable values only");
        } else {
                WarningById(W_REL_UNSTABLE);
        }
        return true;
}

// directives implementation

static void Relocation::refreshMaxTableCount() {
        if (maxTableCount < offsets.size()) {
                maxTableCount = offsets.size();
        }
        // add the relocate_count and relocate_size symbols only when RELOCATE feature was used
        if (type || maxTableCount) {
                isResultAffected = false;
                deltaType = OFF;
                LabelTable.Insert("relocate_count", maxTableCount, LABEL_IS_DEFL);
                LabelTable.Insert("relocate_size", maxTableCount * 2, LABEL_IS_DEFL);
        }
}

void Relocation::dirRELOCATE_START() {
        bool isHigh = !SkipBlanks(lp) && cmphstr(lp, "high");
        if (type) {
                sourcePosStack.push_back(startPos);
                Error("Relocation block already started here");
                sourcePosStack.pop_back();
                return;
        }
        const aint new_offset = isHigh ? ALTERNATIVE_OFFSET_HIGH : ALTERNATIVE_OFFSET_WORD;
        if (alternative_offset && alternative_offset != new_offset) {
                Error("HIGH mode can't be mixed with regular mode");
                return;
        }
        alternative_offset = new_offset;
        type = isHigh ? HIGH : REGULAR;
        assert(!sourcePosStack.empty());
        startPos = sourcePosStack.back();
        refreshMaxTableCount();
}

void Relocation::dirRELOCATE_END() {
        if (!type) {
                Error("Relocation block start for this end is missing");
                return;
        }
        if (DISP_INSIDE_RELOCATE == PseudoORG) {
                Error("End the current DISP block first");
                return;
        }
        type = OFF;
        refreshMaxTableCount();
}

void Relocation::dirRELOCATE_TABLE() {
        aint subtract_offset = 0;
        if (!SkipBlanks(lp)) {  // should be either empty remaining of line, or <subtract_offset>
                if (!ParseExpressionNoSyntaxError(lp, subtract_offset)) {
                        Error("[RELOCATE_TABLE] Syntax error in <subtract_offset>", bp, SUPPRESS);
                        return;
                }
        }
        refreshMaxTableCount();
        // dump the table into machine code output
        for (size_t offsetIndex = 0; offsetIndex < maxTableCount; ++offsetIndex) {
                // select offset from current pass table if possible, but use "offsetsPrevious" as backup
                const auto offset = (offsetIndex < offsets.size()) ? offsets[offsetIndex] : offsetsPrevious[offsetIndex];
                EmitWord(offset - subtract_offset);
        }
        warnAboutContentChange = (LASTPASS == pass);    // set in last pass to check consistency
}

void Relocation::InitPass() {
        // check if the relocation block is still open (missing RELOCATION_END in source)
        if (type) {
                sourcePosStack.push_back(startPos);
                Error("Missing end of relocation block started here");
                sourcePosStack.pop_back();
        }
        // keep copy of final offsets table from previous pass
        offsetsPrevious = offsets;
        // set the final count as the new maximum count (doesn't matter if it is smaller/bigger than old)
        maxTableCount = offsets.size();
        // clear the table for next pass and init the state
        offsets.clear();
        type = OFF;
}

//eof relocate.cpp