// // x65.cpp // // // Created by Carl-Henrik Skårstedt on 9/23/15. // // // A simple 6502 assembler // // // The MIT License (MIT) // // Copyright (c) 2015 Carl-Henrik Skårstedt // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Details, source and documentation at https://github.com/Sakrac/x65. // // "struse.h" can be found at https://github.com/Sakrac/struse, only the header file is required. // #define _CRT_SECURE_NO_WARNINGS // Windows shenanigans #define STRUSE_IMPLEMENTATION // include implementation of struse in this file #include "struse.h" // https://github.com/Sakrac/struse/blob/master/struse.h #include #include #include // if the number of resolved labels exceed this in one late eval then skip // checking for relevance and just eval all unresolved expressions. #define MAX_LABELS_EVAL_ALL 16 // Max number of nested scopes (within { and }) #define MAX_SCOPE_DEPTH 32 // Max number of nested conditional expressions #define MAX_CONDITIONAL_DEPTH 64 // The maximum complexity of expressions to be evaluated #define MAX_EVAL_VALUES 32 #define MAX_EVAL_OPER 64 // Max capacity of each label pool #define MAX_POOL_RANGES 4 #define MAX_POOL_BYTES 128 // To simplify some syntax disambiguation the preferred // ruleset can be specified on the command line. enum AsmSyntax { SYNTAX_SANE, SYNTAX_MERLIN }; // Internal status and error type enum StatusCode { STATUS_OK, // everything is fine STATUS_RELATIVE_SECTION, // value is relative to a single section STATUS_NOT_READY, // label could not be evaluated at this time STATUS_NOT_STRUCT, // return is not a struct. FIRST_ERROR, ERROR_UNDEFINED_CODE = FIRST_ERROR, ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION, ERROR_TOO_MANY_VALUES_IN_EXPRESSION, ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION, ERROR_UNBALANCED_RIGHT_PARENTHESIS, ERROR_EXPRESSION_OPERATION, ERROR_EXPRESSION_MISSING_VALUES, ERROR_INSTRUCTION_NOT_ZP, ERROR_INVALID_ADDRESSING_MODE, ERROR_BRANCH_OUT_OF_RANGE, ERROR_LABEL_MISPLACED_INTERNAL, ERROR_BAD_ADDRESSING_MODE, ERROR_UNEXPECTED_CHARACTER_IN_ADDRESSING_MODE, ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT, ERROR_MODIFYING_CONST_LABEL, ERROR_OUT_OF_LABELS_IN_POOL, ERROR_INTERNAL_LABEL_POOL_ERROR, ERROR_POOL_RANGE_EXPRESSION_EVAL, ERROR_LABEL_POOL_REDECLARATION, ERROR_POOL_LABEL_ALREADY_DEFINED, ERROR_STRUCT_ALREADY_DEFINED, ERROR_REFERENCED_STRUCT_NOT_FOUND, ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT, ERROR_REPT_COUNT_EXPRESSION, ERROR_HEX_WITH_ODD_NIBBLE_COUNT, ERROR_DS_MUST_EVALUATE_IMMEDIATELY, ERROR_NOT_AN_X65_OBJECT_FILE, ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY, ERROR_TOO_DEEP_SCOPE, ERROR_UNBALANCED_SCOPE_CLOSURE, ERROR_BAD_MACRO_FORMAT, ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY, ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION, ERROR_CONDITION_COULD_NOT_BE_RESOLVED, ERROR_ENDIF_WITHOUT_CONDITION, ERROR_ELSE_WITHOUT_IF, ERROR_STRUCT_CANT_BE_ASSEMBLED, ERROR_ENUM_CANT_BE_ASSEMBLED, ERROR_UNTERMINATED_CONDITION, ERROR_REPT_MISSING_SCOPE, ERROR_LINKER_MUST_BE_IN_FIXED_ADDRESS_SECTION, ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION, ERROR_UNABLE_TO_PROCESS, ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE, STATUSCODE_COUNT }; // The following strings are in the same order as StatusCode const char *aStatusStrings[STATUSCODE_COUNT] = { "ok", "relative section", "not ready", "name is not a struct", "Undefined code", "Unexpected character in expression", "Too many values in expression", "Too many operators in expression", "Unbalanced right parenthesis in expression", "Expression operation", "Expression missing values", "Instruction can not be zero page", "Invalid addressing mode for instruction", "Branch out of range", "Internal label organization mishap", "Bad addressing mode", "Unexpected character in addressing mode", "Unexpected label assignment format", "Changing value of label that is constant", "Out of labels in pool", "Internal label pool release confusion", "Label pool range evaluation failed", "Label pool was redeclared within its scope", "Pool label already defined", "Struct already defined", "Referenced struct not found", "Declare constant type not recognized (dc.?)", "rept count expression could not be evaluated", "hex must be followed by an even number of hex numbers", "DS directive failed to evaluate immediately", "File is not a valid x65 object file", "Errors after this point will stop execution", "Target address must evaluate immediately for this operation", "Scoping is too deep", "Unbalanced scope closure", "Unexpected macro formatting", "Align must evaluate immediately", "Out of memory for macro expansion", "Conditional could not be resolved", "#endif encountered outside conditional block", "#else or #elif outside conditional block", "Struct can not be assembled as is", "Enum can not be assembled as is", "Conditional assembly (#if/#ifdef) was not terminated in file or macro", "rept is missing a scope ('{ ... }')", "Link can only be used in a fixed address section", "Link can not be used in dummy sections", "Can not process this line", "Unexpected target offset for reloc or late evaluation", }; // Assembler directives enum AssemblerDirective { AD_ORG, // ORG: Assemble as if loaded at this address AD_LOAD, // LOAD: If applicable, instruct to load at this address AD_SECTION, // SECTION: Enable code that will be assigned a start address during a link step AD_LINK, // LINK: Put sections with this name at this address (must be ORG / fixed address section) AD_XDEF, // XDEF: Externally declare a label AD_INCOBJ, // INCOBJ: Read in an object file saved from a previous build AD_ALIGN, // ALIGN: Add to address to make it evenly divisible by this AD_MACRO, // MACRO: Create a macro AD_EVAL, // EVAL: Print expression to stdout during assemble AD_BYTES, // BYTES: Add 8 bit values to output AD_WORDS, // WORDS: Add 16 bit values to output AD_DC, // DC.B/DC.W: Declare constant (same as BYTES/WORDS) AD_TEXT, // TEXT: Add text to output AD_INCLUDE, // INCLUDE: Load and assemble another file at this address AD_INCBIN, // INCBIN: Load and directly insert another file at this address AD_CONST, // CONST: Prevent a label from mutating during assemble AD_LABEL, // LABEL: Create a mutable label (optional) AD_INCSYM, // INCSYM: Reference labels from another assemble AD_LABPOOL, // POOL: Create a pool of addresses to assign as labels dynamically AD_IF, // #IF: Conditional assembly follows based on expression AD_IFDEF, // #IFDEF: Conditional assembly follows based on label defined or not AD_ELSE, // #ELSE: Otherwise assembly AD_ELIF, // #ELIF: Otherwise conditional assembly follows AD_ENDIF, // #ENDIF: End a block of #IF/#IFDEF AD_STRUCT, // STRUCT: Declare a set of labels offset from a base address AD_ENUM, // ENUM: Declare a set of incremental labels AD_REPT, // REPT: Repeat the assembly of the bracketed code a number of times AD_INCDIR, // INCDIR: Add a folder to search for include files AD_HEX, // HEX: LISA assembler data block AD_EJECT, // EJECT: Page break for printing assembler code, ignore AD_LST, // LST: Controls symbol listing AD_DUMMY, // DUM: Start a dummy section (increment address but don't write anything???) AD_DUMMY_END, // DEND: End a dummy section AD_DS, // DS: Define section, zero out # bytes or rewind the address if negative AD_USR, // USR: MERLIN user defined pseudo op, runs some code at a hard coded address on apple II, on PC does nothing. }; // Operators are either instructions or directives enum OperationType { OT_NONE, OT_MNEMONIC, OT_DIRECTIVE }; // These are expression tokens in order of precedence (last is highest precedence) enum EvalOperator { EVOP_NONE, EVOP_VAL='a', // a, value => read from value queue EVOP_LPR, // b, left parenthesis EVOP_RPR, // c, right parenthesis EVOP_ADD, // d, + EVOP_SUB, // e, - EVOP_MUL, // f, * (note: if not preceded by value or right paren this is current PC) EVOP_DIV, // g, / EVOP_AND, // h, & EVOP_OR, // i, | EVOP_EOR, // j, ^ EVOP_SHL, // k, << EVOP_SHR, // l, >> EVOP_LOB, // m, low byte of 16 bit value EVOP_HIB, // n, high byte of 16 bit value EVOP_STP, // o, Unexpected input, should stop and evaluate what we have EVOP_NRY, // p, Not ready yet EVOP_ERR, // q, Error }; // Opcode encoding typedef struct { unsigned int op_hash; unsigned char index; // ground index unsigned char type; // mnemonic or } OP_ID; enum AddrMode { AMB_ZP_REL_X, // address mode bit index AMB_ZP, AMB_IMM, AMB_ABS, AMB_ZP_Y_REL, AMB_ZP_X, AMB_ABS_Y, AMB_ABS_X, AMB_REL, AMB_ACC, AMB_NON, AMB_COUNT, AMB_FLIPXY = AMB_COUNT, AMB_BRANCH, // address mode masks AMM_NON = 1<read) first = index+1; else count = index; } if (counthash) count--; return count; } // // // ASSEMBLER STATE // // // pairArray is basically two vectors sharing a size without constructors on growth or insert template class pairArray { protected: H *keys; V *values; unsigned int _count; unsigned int _capacity; public: pairArray() : keys(nullptr), values(nullptr), _count(0), _capacity(0) {} void reserve(unsigned int size) { if (size>_capacity) { H *new_keys = (H*)malloc(sizeof(H) * size); if (!new_keys) { return; } V *new_values = (V*)malloc(sizeof(V) * size); if (!new_values) { free(new_keys); return; } if (keys && values) { memcpy(new_keys, keys, sizeof(H) * _count); memcpy(new_values, values, sizeof(V) * _count); free(keys); free(values); } keys = new_keys; values = new_values; _capacity = size; } } bool insert(unsigned int pos) { if (pos>_count) return false; if (_count==_capacity) reserve(_capacity+64); if (pos<_count) { memmove(keys+pos+1, keys+pos, sizeof(H) * (_count-pos)); memmove(values+pos+1, values+pos, sizeof(V) * (_count-pos)); } memset(keys+pos, 0, sizeof(H)); memset(values+pos, 0, sizeof(V)); _count++; return true; } bool insert(unsigned int pos, H key) { if (insert(pos) && keys) { keys[pos] = key; return true; } return false; } void remove(unsigned int pos) { if (pos<_count) { _count--; if (pos<_count) { memmove(keys+pos, keys+pos+1, sizeof(H) * (_count-pos)); memmove(values+pos, values+pos+1, sizeof(V) * (_count-pos)); } } } H* getKeys() { return keys; } H& getKey(unsigned int pos) { return keys[pos]; } V* getValues() { return values; } V& getValue(unsigned int pos) { return values[pos]; } unsigned int count() const { return _count; } unsigned int capacity() const { return _capacity; } void clear() { if (keys!=nullptr) free(keys); keys = nullptr; if (values!=nullptr) free(values); values = nullptr; _capacity = 0; _count = 0; } }; // relocs are cheaper than full expressions and work with // local labels for relative sections which could otherwise // be out of scope at link time. struct Reloc { enum Type { NONE, WORD, LO_BYTE, HI_BYTE }; int base_value; int section_offset; // offset into this section int target_section; // which section does this reloc target? Type value_type; // byte or word size Reloc() : base_value(0), section_offset(-1), target_section(-1), value_type(NONE) {} Reloc(int base, int offs, int sect, Type t = WORD) : base_value(base), section_offset(offs), target_section(sect), value_type(t) {} }; typedef std::vector relocList; // For assembly listing this remembers the location of each line struct ListLine { int address; // start address of this line int size; // number of bytes generated for this line int line_offs; // offset into code strref source_name; // source file index name strref code; // line of code this represents }; typedef std::vector Listing; // start of data section support // Default is a fixed address section at $1000 // Whenever org or dum with address is encountered => new section // If org is fixed and < $200 then it is a dummy section Otherwise clear dummy section typedef struct Section { // section name, same named section => append strref name; // name of section for comparison // generated address status int load_address; // if assigned a load address int start_address; int address; // relative or absolute PC // merged sections int merged_offset; // -1 if not merged int merged_section; // which section merged with // data output unsigned char *output; // memory for this section unsigned char *curr; // current pointer for this section size_t output_capacity; // current output capacity // reloc data relocList *pRelocs; // link time resolve (not all sections need this) Listing *pListing; // if list output bool address_assigned; // address is absolute if assigned bool dummySection; // true if section does not generate data, only labels void reset() { name.clear(); start_address = address = load_address = 0x0; address_assigned = false; output = nullptr; curr = nullptr; dummySection = false; output_capacity = 0; merged_offset = -1; if (pRelocs) delete pRelocs; pRelocs = nullptr; if (pListing) delete pListing; pListing = nullptr; } void Cleanup() { if (output) free(output); reset(); } bool empty() const { return merged_offset<0 && curr==output; } int DataOffset() const { return int(curr - output); } size_t size() const { return curr - output; } const unsigned char *get() { return output; } int GetPC() const { return address; } void AddAddress(int value) { address += value; } void SetLoadAddress(int addr) { load_address = addr; } int GetLoadAddress() const { return load_address; } void SetDummySection(bool enable) { dummySection = enable; } bool IsDummySection() const { return dummySection; } bool IsRelativeSection() const { return address_assigned == false; } bool IsMergedSection() const { return merged_offset >= 0; } void AddReloc(int base, int offset, int section, Reloc::Type type = Reloc::WORD); Section() : pRelocs(nullptr), pListing(nullptr) { reset(); } Section(strref _name, int _address) : pRelocs(nullptr), pListing(nullptr) { reset(); name = _name; start_address = load_address = address = _address; address_assigned = true; } Section(strref _name) : pRelocs(nullptr), pListing(nullptr) { reset(); name = _name; start_address = load_address = address = 0; address_assigned = false; } ~Section() { reset(); } // Appending data to a section void CheckOutputCapacity(unsigned int addSize); void AddByte(int b); void AddWord(int w); void AddBin(unsigned const char *p, int size); void SetByte(size_t offs, int b) { output[offs] = b; } void SetWord(size_t offs, int w) { output[offs] = w; output[offs+1] = w>>8; } } Section; // Symbol list entry (in order of parsing) struct MapSymbol { strref name; // string name short value; bool local; // local variables bool resolved; }; typedef std::vector MapSymbolArray; // Data related to a label typedef struct { public: strref label_name; // the name of this label strref pool_name; // name of the pool that this label is related to int value; int section; // rel section address labels belong to a section, -1 if fixed address or assigned int mapIndex; // index into map symbols in case of late resolve bool evaluated; // a value may not yet be evaluated bool pc_relative; // this is an inline label describing a point in the code bool constant; // the value of this label can not change bool external; // this label is globally accessible } Label; // If an expression can't be evaluated immediately, this is required // to reconstruct the result when it can be. typedef struct { enum Type { // When an expression is evaluated late, determine how to encode the result LET_LABEL, // this evaluation applies to a label and not memory LET_ABS_REF, // calculate an absolute address and store at 0, +1 LET_BRANCH, // calculate a branch offset and store at this address LET_BYTE, // calculate a byte and store at this address }; int target; // offset into output buffer int address; // current pc int scope; // scope pc int section; // which section to apply to. int file_ref; // -1 if current or xdef'd otherwise index of file for label strref label; // valid if this is not a target but another label strref expression; strref source_file; Type type; } LateEval; // A macro is a text reference to where it was defined typedef struct { strref name; strref macro; strref source_name; // source file name (error output) strref source_file; // entire source file (req. for line #) } Macro; // All local labels are removed when a global label is defined but some when a scope ends typedef struct { strref label; int scope_depth; bool scope_reserve; // not released for global label, only scope } LocalLabelRecord; // Label pools allows C like stack frame label allocation typedef struct { strref pool_name; short numRanges; // normally 1 range, support multiple for ease of use short scopeDepth; // Required for scope closure cleanup unsigned short ranges[MAX_POOL_RANGES*2]; // 2 shorts per range unsigned int usedMap[(MAX_POOL_BYTES+15)>>4]; // 2 bits per byte to store byte count of label StatusCode Reserve(int numBytes, unsigned int &addr); StatusCode Release(unsigned int addr); } LabelPool; // One member of a label struct struct MemberOffset { unsigned short offset; unsigned int name_hash; strref name; strref sub_struct; }; // Label struct typedef struct { strref name; unsigned short first_member; unsigned short numMembers; unsigned short size; } LabelStruct; // object file labels that are not xdef'd end up here struct ExtLabels { pairArray labels; }; struct EvalContext { int pc; // current address at point of eval int scope_pc; // current scope open at point of eval int scope_end_pc; // late scope closure after eval int relative_section; // return can be relative to this section int file_ref; // can access private label from this file or -1 EvalContext(int _pc, int _scope, int _close, int _sect) : pc(_pc), scope_pc(_scope), scope_end_pc(_close), relative_section(_sect), file_ref(-1) {} }; // Source context is current file (include file, etc.) or current macro. typedef struct { strref source_name; // source file name (error output) strref source_file; // entire source file (req. for line #) strref code_segment; // the segment of the file for this context strref read_source; // current position/length in source file strref next_source; // next position/length in source file int repeat; // how many times to repeat this code segment void restart() { read_source = code_segment; } bool complete() { repeat--; return repeat <= 0; } } SourceContext; // Context stack is a stack of currently processing text class ContextStack { private: std::vector stack; SourceContext *currContext; public: ContextStack() : currContext(nullptr) { stack.reserve(32); } SourceContext& curr() { return *currContext; } void push(strref src_name, strref src_file, strref code_seg, int rept=1) { if (currContext) currContext->read_source = currContext->next_source; SourceContext context; context.source_name = src_name; context.source_file = src_file; context.code_segment = code_seg; context.read_source = code_seg; context.next_source = code_seg; context.repeat = rept; stack.push_back(context); currContext = &stack[stack.size()-1]; } void pop() { stack.pop_back(); currContext = stack.size() ? &stack[stack.size()-1] : nullptr; } bool has_work() { return currContext!=nullptr; } }; // The state of the assembler class Asm { public: pairArray labels; pairArray macros; pairArray labelPools; pairArray labelStructs; // labels matching xdef names will be marked as external pairArray xdefs; std::vector lateEval; std::vector localLabels; std::vector loadedData; // free when assembler is completed std::vector structMembers; // labelStructs refer to sets of structMembers std::vector includePaths; std::vector
allSections; std::vector externals; // external labels organized by object file MapSymbolArray map; // context for macros / include files ContextStack contextStack; // Current section (temp private so I don't access this directly and forget about it) private: Section *current_section; public: // Special syntax rules AsmSyntax syntax; // Conditional assembly vars int conditional_depth; char conditional_nesting[MAX_CONDITIONAL_DEPTH]; bool conditional_consumed[MAX_CONDITIONAL_DEPTH]; // Scope info int scope_address[MAX_SCOPE_DEPTH]; int scope_depth; // Eval relative result (only valid if EvalExpression returs STATUS_RELATIVE_SECTION) int lastEvalSection; int lastEvalValue; Reloc::Type lastEvalPart; bool symbol_export, last_label_local; bool errorEncountered; bool list_assembly; // Convert source to binary void Assemble(strref source, strref filename, bool obj_target); // Generate assembler listing if requested bool List(strref filename); // Generate source for all valid instructions bool AllOpcodes(strref filename); // Clean up memory allocations, reset assembler state void Cleanup(); // Make sure there is room to write more code void CheckOutputCapacity(unsigned int addSize); // Operations on current section void SetSection(strref name, int address); // fixed address section void SetSection(strref name); // relative address section StatusCode LinkSections(strref name); // link relative address sections with this name here void LinkLabelsToAddress(int section_id, int section_address); StatusCode LinkRelocs(int section_id, int section_address); void DummySection(int address); void DummySection(); void EndSection(); Section& CurrSection() { return *current_section; } Section& ExportSection(); int SectionId() { return int(current_section - &allSections[0]); } void AddByte(int b) { CurrSection().AddByte(b); } void AddWord(int w) { CurrSection().AddWord(w); } void AddBin(unsigned const char *p, int size) { CurrSection().AddBin(p, size); } // Object file handling StatusCode WriteObjectFile(strref filename); StatusCode ReadObjectFile(strref filename); // Macro management StatusCode AddMacro(strref macro, strref source_name, strref source_file, strref &left); StatusCode BuildMacro(Macro &m, strref arg_list); // Structs StatusCode BuildStruct(strref name, strref declaration); StatusCode EvalStruct(strref name, int &value); StatusCode BuildEnum(strref name, strref declaration); // Calculate a value based on an expression. EvalOperator RPNToken_Merlin(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, short §ion, int &value); EvalOperator RPNToken(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, short §ion, int &value); StatusCode EvalExpression(strref expression, const struct EvalContext &etx, int &result); // Access labels Label* GetLabel(strref label); Label* GetLabel(strref label, int file_ref); Label* AddLabel(unsigned int hash); bool MatchXDEF(strref label); StatusCode AssignLabel(strref label, strref line, bool make_constant = false); StatusCode AddressLabel(strref label); void LabelAdded(Label *pLabel, bool local=false); void IncludeSymbols(strref line); // Manage locals void MarkLabelLocal(strref label, bool scope_label = false); void FlushLocalLabels(int scope_exit = -1); // Label pools LabelPool* GetLabelPool(strref pool_name); StatusCode AddLabelPool(strref name, strref args); StatusCode AssignPoolLabel(LabelPool &pool, strref args); void FlushLabelPools(int scope_exit); // Late expression evaluation void AddLateEval(int target, int pc, int scope_pc, strref expression, strref source_file, LateEval::Type type); void AddLateEval(strref label, int pc, int scope_pc, strref expression, LateEval::Type type); StatusCode CheckLateEval(strref added_label=strref(), int scope_end = -1); // Assembler steps StatusCode ApplyDirective(AssemblerDirective dir, strref line, strref source_file); AddrMode GetAddressMode(strref line, bool flipXY, StatusCode &error, strref &expression); StatusCode AddOpcode(strref line, int index, strref source_file); StatusCode BuildLine(OP_ID *pInstr, int numInstructions, strref line); StatusCode BuildSegment(OP_ID *pInstr, int numInstructions); // Display error in stderr void PrintError(strref line, StatusCode error); // Conditional Status bool ConditionalAsm(); // Assembly is currently enabled bool NewConditional(); // Start a new conditional block void CloseConditional(); // Close a conditional block void CheckConditionalDepth(); // Check if this conditional will nest the assembly (a conditional is already consumed) void ConsumeConditional(); // This conditional block is going to be assembled, mark it as consumed bool ConditionalConsumed(); // Has a block of this conditional already been assembled? void SetConditional(); // This conditional block is not going to be assembled so mark that it is nesting bool ConditionalAvail(); // Returns true if this conditional can be consumed void ConditionalElse(); // Conditional else that does not enable block void EnableConditional(bool enable); // This conditional block is enabled and the prior wasn't // Conditional statement evaluation (A==B? A?) StatusCode EvalStatement(strref line, bool &result); // Add include folder void AddIncludeFolder(strref path); char* LoadText(strref filename, size_t &size); char* LoadBinary(strref filename, size_t &size); // constructor Asm() { Cleanup(); localLabels.reserve(256); loadedData.reserve(16); lateEval.reserve(64); } }; // Clean up work allocations void Asm::Cleanup() { for (std::vector::iterator i = loadedData.begin(); i != loadedData.end(); ++i) { if (char *data = *i) free(data); } map.clear(); labelPools.clear(); loadedData.clear(); labels.clear(); macros.clear(); allSections.clear(); for (std::vector::iterator exti = externals.begin(); exti !=externals.end(); ++exti) exti->labels.clear(); externals.clear(); SetSection(strref("default")); // this section is relocatable but is assigned address $1000 if exporting without directives current_section = &allSections[0]; syntax = SYNTAX_SANE; scope_depth = 0; conditional_depth = 0; conditional_nesting[0] = 0; conditional_consumed[0] = false; symbol_export = false; last_label_local = false; errorEncountered = false; list_assembly = false; } // Read in text data (main source, include, etc.) char* Asm::LoadText(strref filename, size_t &size) { strown<512> file(filename); std::vector::iterator i = includePaths.begin(); for(;;) { if (FILE *f = fopen(file.c_str(), "rb")) { fseek(f, 0, SEEK_END); size_t _size = ftell(f); fseek(f, 0, SEEK_SET); if (char *buf = (char*)calloc(_size, 1)) { fread(buf, _size, 1, f); fclose(f); size = _size; return buf; } fclose(f); } if (i==includePaths.end()) break; file.copy(*i); if (file.get_last()!='/' && file.get_last()!='\\') file.append('/'); file.append(filename); ++i; } size = 0; return nullptr; } // Read in binary data (incbin) char* Asm::LoadBinary(strref filename, size_t &size) { strown<512> file(filename); std::vector::iterator i = includePaths.begin(); for(;;) { if (FILE *f = fopen(file.c_str(), "rb")) { fseek(f, 0, SEEK_END); size_t _size = ftell(f); fseek(f, 0, SEEK_SET); if (char *buf = (char*)malloc(_size)) { fread(buf, _size, 1, f); fclose(f); size = _size; return buf; } fclose(f); } if (i==includePaths.end()) break; file.copy(*i); if (file.get_last()!='/' && file.get_last()!='\\') file.append('/'); file.append(filename); #ifdef WIN32 file.replace('/', '\\'); #endif ++i; } size = 0; return nullptr; } void Asm::SetSection(strref name, int address) { if (name) { for (std::vector
::iterator i = allSections.begin(); i!=allSections.end(); ++i) { if (i->name && name.same_str(i->name)) { current_section = &*i; return; } } } if (allSections.size()==allSections.capacity()) allSections.reserve(allSections.size() + 16); Section newSection(name, address); if (address < 0x200) // don't compile over zero page and stack frame (may be bad assumption) newSection.SetDummySection(true); allSections.push_back(newSection); current_section = &allSections[allSections.size()-1]; } void Asm::SetSection(strref name) { // should same name section within the same file be the same section? /* if (name) { for (std::vector
::iterator i = allSections.begin(); i!=allSections.end(); ++i) { if (i->name && name.same_str(i->name)) { current_section = &*i; return; } } }*/ if (allSections.size()==allSections.capacity()) allSections.reserve(allSections.size() + 16); Section newSection(name); allSections.push_back(newSection); current_section = &allSections[allSections.size()-1]; } // Fixed address dummy section void Asm::DummySection(int address) { if (CurrSection().empty() && address == CurrSection().GetPC()) { CurrSection().SetDummySection(true); return; } if (allSections.size()==allSections.capacity()) allSections.reserve(allSections.size() + 16); Section newSection(strref(), address); newSection.SetDummySection(true); allSections.push_back(newSection); current_section = &allSections[allSections.size()-1]; } // Current address dummy section void Asm::DummySection() { DummySection(CurrSection().GetPC()); } void Asm::EndSection() { int section = (int)(current_section - &allSections[0]); if (section) current_section = &allSections[section-1]; } // Return an appropriate section for exporting a binary Section& Asm::ExportSection() { std::vector
::iterator i = allSections.end(); bool hasRelativeSection = false; while (i != allSections.begin()) { --i; hasRelativeSection = hasRelativeSection || i->IsRelativeSection(); if (!i->IsDummySection() && !i->IsMergedSection() && !i->IsRelativeSection()) return *i; } if (hasRelativeSection) { // no fixed sections, make a new fixed section and return that for exporting. SetSection(strref(), 0x1000); LinkSections(strref()); } return CurrSection(); } // Apply labels assigned to addresses in a relative section a fixed address void Asm::LinkLabelsToAddress(int section_id, int section_address) { Label *pLabels = labels.getValues(); int numLabels = labels.count(); for (int l = 0; l < numLabels; l++) { if (pLabels->section == section_id) { pLabels->value += section_address; pLabels->section = -1; if (pLabels->mapIndex>=0 && pLabels->mapIndex<(int)map.size()) { struct MapSymbol &msym = map[pLabels->mapIndex]; msym.value = pLabels->value; msym.resolved = true; } CheckLateEval(pLabels->label_name); } ++pLabels; } } // go through relocs in all sections to see if any targets this section // relocate section to address! StatusCode Asm::LinkRelocs(int section_id, int section_address) { for (std::vector
::iterator j = allSections.begin(); j != allSections.end(); ++j) { Section &s2 = *j; if (s2.pRelocs) { relocList *pList = s2.pRelocs; relocList::iterator i = pList->end(); while (i != pList->begin()) { --i; if (i->target_section == section_id) { Section *trg_sect = &s2; size_t output_offs = 0; while (trg_sect->merged_offset>=0) { output_offs += trg_sect->merged_offset; trg_sect = &allSections[trg_sect->merged_section]; } unsigned char *trg = trg_sect->output + output_offs + i->section_offset; int value = i->base_value + section_address; if (i->value_type == Reloc::WORD) { if ((trg+1) >= (trg_sect->output + trg_sect->size())) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; *trg++ = (unsigned char)value; *trg = (unsigned char)(value >> 8); } else if (i->value_type == Reloc::LO_BYTE) { if (trg >= (trg_sect->output + trg_sect->size())) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; *trg = (unsigned char)value; } else if (i->value_type == Reloc::HI_BYTE) { if (trg >= (trg_sect->output + trg_sect->size())) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; *trg = (unsigned char)(value >> 8); } i = pList->erase(i); if (i != pList->end()) ++i; } } if (pList->empty()) { free(pList); s2.pRelocs = nullptr; } } } return STATUS_OK; } // Link sections with a specific name at this point StatusCode Asm::LinkSections(strref name) { if (CurrSection().IsRelativeSection()) return ERROR_LINKER_MUST_BE_IN_FIXED_ADDRESS_SECTION; if (CurrSection().IsDummySection()) return ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION; int section_id = 0; for (std::vector
::iterator i = allSections.begin(); i != allSections.end(); ++i) { if ((!name || i->name.same_str_case(name)) && i->IsRelativeSection() && !i->IsMergedSection()) { // found a section to link! Section &s = *i; // Get base addresses CheckOutputCapacity((int)s.size()); Section &curr = CurrSection(); int section_address = curr.GetPC(); unsigned char *section_out = curr.curr; if (s.output) memcpy(section_out, s.output, s.size()); CurrSection().address += (int)s.size(); CurrSection().curr += s.size(); free(s.output); s.output = 0; s.curr = 0; s.output_capacity = 0; // Update address range and mark section as merged s.start_address = section_address; s.address += section_address; s.address_assigned = true; s.merged_section = SectionId(); s.merged_offset = (int)(section_out - CurrSection().output); // Merge in the listing at this point if (s.pListing) { if (!curr.pListing) curr.pListing = new Listing; if ((curr.pListing->size() + s.pListing->size()) > curr.pListing->capacity()) curr.pListing->reserve(curr.pListing->size() + s.pListing->size() + 256); for (Listing::iterator si = s.pListing->begin(); si != s.pListing->end(); ++si) { struct ListLine lst = *si; lst.address += s.merged_offset; curr.pListing->push_back(lst); } delete s.pListing; s.pListing = nullptr; } // All labels in this section can now be assigned LinkLabelsToAddress(section_id, section_address); StatusCode status = LinkRelocs(section_id, section_address); if (status != STATUS_OK) return status; } ++section_id; } return STATUS_OK; } // Section based output capacity // Make sure there is room to assemble in void Section::CheckOutputCapacity(unsigned int addSize) { size_t currSize = curr - output; if ((addSize + currSize) >= output_capacity) { size_t newSize = currSize * 2; if (newSize < 64*1024) newSize = 64*1024; if ((addSize+currSize) > newSize) newSize += newSize; unsigned char *new_output = (unsigned char*)malloc(newSize); curr = new_output + (curr-output); free(output); output = new_output; output_capacity = newSize; } } // Add one byte to a section void Section::AddByte(int b) { if (!dummySection) { CheckOutputCapacity(1); *curr++ = (unsigned char)b; } address++; } // Add a 16 bit word to a section void Section::AddWord(int w) { if (!dummySection) { CheckOutputCapacity(2); *curr++ = (unsigned char)(w&0xff); *curr++ = (unsigned char)(w>>8); } address += 2; } // Add arbitrary length data to a section void Section::AddBin(unsigned const char *p, int size) { if (!dummySection) { CheckOutputCapacity(size); memcpy(curr, p, size); curr += size; } address += size; } // Add a relocation marker to a section void Section::AddReloc(int base, int offset, int section, Reloc::Type type) { if (!pRelocs) pRelocs = new relocList; if (pRelocs->size() == pRelocs->capacity()) pRelocs->reserve(pRelocs->size() + 32); pRelocs->push_back(Reloc(base, offset, section, type)); } // Make sure there is room to assemble in void Asm::CheckOutputCapacity(unsigned int addSize) { CurrSection().CheckOutputCapacity(addSize); } // // // MACROS // // // add a custom macro StatusCode Asm::AddMacro(strref macro, strref source_name, strref source_file, strref &left) { // name(optional params) { actual macro } strref name = macro.split_label(); macro.skip_whitespace(); if (macro[0]!='(' && macro[0]!='{') return ERROR_BAD_MACRO_FORMAT; unsigned int hash = name.fnv1a(); unsigned int ins = FindLabelIndex(hash, macros.getKeys(), macros.count()); Macro *pMacro = nullptr; while (ins < macros.count() && macros.getKey(ins)==hash) { if (name.same_str_case(macros.getValue(ins).name)) { pMacro = macros.getValues() + ins; break; } ++ins; } if (!pMacro) { macros.insert(ins, hash); pMacro = macros.getValues() + ins; } pMacro->name = name; int pos_bracket = macro.find('{'); if (pos_bracket < 0) { pMacro->macro = strref(); return ERROR_BAD_MACRO_FORMAT; } strref source = macro + pos_bracket; strref macro_body = source.scoped_block_skip(); pMacro->macro = strref(macro.get(), pos_bracket + macro_body.get_len() + 2); pMacro->source_name = source_name; pMacro->source_file = source_file; source.skip_whitespace(); left = source; return STATUS_OK; } // Compile in a macro StatusCode Asm::BuildMacro(Macro &m, strref arg_list) { strref macro_src = m.macro; strref params = macro_src[0]=='(' ? macro_src.scoped_block_skip() : strref(); params.trim_whitespace(); arg_list.trim_whitespace(); macro_src.skip_whitespace(); if (params) { arg_list = arg_list.scoped_block_skip(); strref pchk = params; strref arg = arg_list; int dSize = 0; while (strref param = pchk.split_token_trim(',')) { strref a = arg.split_token_trim(','); if (param.get_len() < a.get_len()) { int count = macro_src.substr_case_count(param); dSize += count * ((int)a.get_len() - (int)param.get_len()); } } int mac_size = macro_src.get_len() + dSize + 32; if (char *buffer = (char*)malloc(mac_size)) { loadedData.push_back(buffer); strovl macexp(buffer, mac_size); macexp.copy(macro_src); while (strref param = params.split_token_trim(',')) { strref a = arg_list.split_token_trim(','); macexp.replace(param, a); } contextStack.push(m.source_name, macexp.get_strref(), macexp.get_strref()); FlushLocalLabels(); return STATUS_OK; } else return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION; } contextStack.push(m.source_name, m.source_file, macro_src); FlushLocalLabels(); return STATUS_OK; } // // // STRUCTS AND ENUMS // // // Enums are Structs in disguise StatusCode Asm::BuildEnum(strref name, strref declaration) { unsigned int hash = name.fnv1a(); unsigned int ins = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count()); LabelStruct *pEnum = nullptr; while (ins < labelStructs.count() && labelStructs.getKey(ins)==hash) { if (name.same_str_case(labelStructs.getValue(ins).name)) { pEnum = labelStructs.getValues() + ins; break; } ++ins; } if (pEnum) return ERROR_STRUCT_ALREADY_DEFINED; labelStructs.insert(ins, hash); pEnum = labelStructs.getValues() + ins; pEnum->name = name; pEnum->first_member = (unsigned short)structMembers.size(); pEnum->numMembers = 0; pEnum->size = 0; // enums are 0 sized int value = 0; struct EvalContext etx(CurrSection().GetPC(), scope_address[scope_depth], -1, -1); while (strref line = declaration.line()) { line = line.before_or_full(','); line.trim_whitespace(); strref name = line.split_token_trim('='); if (line) { StatusCode error = EvalExpression(line, etx, value); if (error == STATUS_NOT_READY) return ERROR_ENUM_CANT_BE_ASSEMBLED; else if (error != STATUS_OK) return error; } struct MemberOffset member; member.offset = value; member.name = name; member.name_hash = member.name.fnv1a(); member.sub_struct = strref(); structMembers.push_back(member); ++value; pEnum->numMembers++; } return STATUS_OK; } StatusCode Asm::BuildStruct(strref name, strref declaration) { unsigned int hash = name.fnv1a(); unsigned int ins = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count()); LabelStruct *pStruct = nullptr; while (ins < labelStructs.count() && labelStructs.getKey(ins)==hash) { if (name.same_str_case(labelStructs.getValue(ins).name)) { pStruct = labelStructs.getValues() + ins; break; } ++ins; } if (pStruct) return ERROR_STRUCT_ALREADY_DEFINED; labelStructs.insert(ins, hash); pStruct = labelStructs.getValues() + ins; pStruct->name = name; pStruct->first_member = (unsigned short)structMembers.size(); unsigned int byte_hash = struct_byte.fnv1a(); unsigned int word_hash = struct_word.fnv1a(); unsigned short size = 0; unsigned short member_count = 0; while (strref line = declaration.line()) { line.trim_whitespace(); strref type = line.split_label(); line.skip_whitespace(); unsigned int type_hash = type.fnv1a(); unsigned short type_size = 0; LabelStruct *pSubStruct = nullptr; if (type_hash==byte_hash && struct_byte.same_str_case(type)) type_size = 1; else if (type_hash==word_hash && struct_word.same_str_case(type)) type_size = 2; else { unsigned int index = FindLabelIndex(type_hash, labelStructs.getKeys(), labelStructs.count()); while (index < labelStructs.count() && labelStructs.getKey(index)==type_hash) { if (type.same_str_case(labelStructs.getValue(index).name)) { pSubStruct = labelStructs.getValues() + index; break; } ++index; } if (!pSubStruct) { labelStructs.remove(ins); return ERROR_REFERENCED_STRUCT_NOT_FOUND; } type_size = pSubStruct->size; } // add the new member, don't grow vectors one at a time. if (structMembers.size() == structMembers.capacity()) structMembers.reserve(structMembers.size() + 64); struct MemberOffset member; member.offset = size; member.name = line.get_label(); member.name_hash = member.name.fnv1a(); member.sub_struct = pSubStruct ? pSubStruct->name : strref(); structMembers.push_back(member); size += type_size; member_count++; } pStruct->numMembers = member_count; pStruct->size = size; return STATUS_OK; } // Evaluate a struct offset as if it was a label StatusCode Asm::EvalStruct(strref name, int &value) { LabelStruct *pStruct = nullptr; unsigned short offset = 0; while (strref struct_seg = name.split_token('.')) { strref sub_struct = struct_seg; unsigned int seg_hash = struct_seg.fnv1a(); if (pStruct) { struct MemberOffset *member = &structMembers[pStruct->first_member]; bool found = false; for (int i=0; inumMembers; i++) { if (member->name_hash == seg_hash && member->name.same_str_case(struct_seg)) { offset += member->offset; sub_struct = member->sub_struct; found = true; break; } ++member; } if (!found) return ERROR_REFERENCED_STRUCT_NOT_FOUND; } if (sub_struct) { unsigned int hash = sub_struct.fnv1a(); unsigned int index = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count()); while (index < labelStructs.count() && labelStructs.getKey(index)==hash) { if (sub_struct.same_str_case(labelStructs.getValue(index).name)) { pStruct = labelStructs.getValues() + index; break; } ++index; } } else if (name) return STATUS_NOT_STRUCT; } if (pStruct == nullptr) return STATUS_NOT_STRUCT; value = offset; return STATUS_OK; } // // // EXPRESSIONS AND LATE EVALUATION // // // Get a single token from a merlin expression EvalOperator Asm::RPNToken_Merlin(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, short §ion, int &value) { char c = expression.get_first(); switch (c) { case '$': ++expression; value = expression.ahextoui_skip(); return EVOP_VAL; case '-': ++expression; return EVOP_SUB; case '+': ++expression; return EVOP_ADD; case '*': // asterisk means both multiply and current PC, disambiguate! ++expression; if (expression[0] == '*') return EVOP_STP; // double asterisks indicates comment else if (prev_op==EVOP_VAL || prev_op==EVOP_RPR) return EVOP_MUL; value = etx.pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; case '/': ++expression; return EVOP_DIV; case '>': if (expression.get_len() >= 2 && expression[1] == '>') { expression += 2; return EVOP_SHR; } ++expression; return EVOP_HIB; case '<': if (expression.get_len() >= 2 && expression[1] == '<') { expression += 2; return EVOP_SHL; } ++expression; return EVOP_LOB; case '%': // % means both binary and scope closure, disambiguate! if (expression[1]=='0' || expression[1]=='1') { ++expression; value = expression.abinarytoui_skip(); return EVOP_VAL; } if (etx.scope_end_pc<0) return EVOP_NRY; ++expression; value = etx.scope_end_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; case '|': case '.': ++expression; return EVOP_OR; // MERLIN: . is or, | is not used case '^': ++expression; return EVOP_EOR; case '&': ++expression; return EVOP_AND; case '(': if (prev_op!=EVOP_VAL) { ++expression; return EVOP_LPR; } return EVOP_STP; case ')': ++expression; return EVOP_RPR; case '"': if (expression[2] == '"') { value = expression[1]; expression += 3; return EVOP_VAL; } return EVOP_STP; case '\'': if (expression[2] == '\'') { value = expression[1]; expression += 3; return EVOP_VAL; } return EVOP_STP; case ',': case '?': default: { // MERLIN: ! is eor if (c == '!' && (prev_op == EVOP_VAL || prev_op == EVOP_RPR)) { ++expression; return EVOP_EOR; } else if (c == '!' && !(expression + 1).len_label()) { if (etx.scope_pc < 0) return EVOP_NRY; // ! by itself is current scope, !+label char is a local label ++expression; value = etx.scope_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; } else if (strref::is_number(c)) { if (prev_op == EVOP_VAL) return EVOP_STP; // value followed by value doesn't make sense, stop value = expression.atoi_skip(); return EVOP_VAL; } else if (c == '!' || c == ']' || c==':' || strref::is_valid_label(c)) { if (prev_op == EVOP_VAL) return EVOP_STP; // a value followed by a value does not make sense, probably start of a comment (ORCA/LISA?) char e0 = expression[0]; int start_pos = (e0==']' || e0==':' || e0=='!' || e0=='.') ? 1 : 0; strref label = expression.split_range_trim(label_end_char_range_merlin, start_pos); Label *pLabel = pLabel = GetLabel(label, etx.file_ref); if (!pLabel) { StatusCode ret = EvalStruct(label, value); if (ret == STATUS_OK) return EVOP_VAL; if (ret != STATUS_NOT_STRUCT) return EVOP_ERR; // partial struct } if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet) value = pLabel->value; section = pLabel->section; return EVOP_VAL; } else return EVOP_ERR; break; } } return EVOP_NONE; // shouldn't get here normally } // Get a single token from most non-apple II assemblers EvalOperator Asm::RPNToken(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, short §ion, int &value) { char c = expression.get_first(); switch (c) { case '$': ++expression; value = expression.ahextoui_skip(); return EVOP_VAL; case '-': ++expression; return EVOP_SUB; case '+': ++expression; return EVOP_ADD; case '*': // asterisk means both multiply and current PC, disambiguate! ++expression; if (expression[0] == '*') return EVOP_STP; // double asterisks indicates comment else if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) return EVOP_MUL; value = etx.pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; case '/': ++expression; return EVOP_DIV; case '>': if (expression.get_len() >= 2 && expression[1] == '>') { expression += 2; return EVOP_SHR; } ++expression; return EVOP_HIB; case '<': if (expression.get_len() >= 2 && expression[1] == '<') { expression += 2; return EVOP_SHL; } ++expression; return EVOP_LOB; case '%': // % means both binary and scope closure, disambiguate! if (expression[1] == '0' || expression[1] == '1') { ++expression; value = expression.abinarytoui_skip(); return EVOP_VAL; } if (etx.scope_end_pc<0) return EVOP_NRY; ++expression; value = etx.scope_end_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; case '|': ++expression; return EVOP_OR; case '^': ++expression; return EVOP_EOR; case '&': ++expression; return EVOP_AND; case '(': if (prev_op != EVOP_VAL) { ++expression; return EVOP_LPR; } return EVOP_STP; case ')': ++expression; return EVOP_RPR; case ',': case '?': case '\'': return EVOP_STP; default: { // ! by itself is current scope, !+label char is a local label if (c == '!' && !(expression + 1).len_label()) { if (etx.scope_pc < 0) return EVOP_NRY; ++expression; value = etx.scope_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL; } else if (strref::is_number(c)) { if (prev_op == EVOP_VAL) return EVOP_STP; // value followed by value doesn't make sense, stop value = expression.atoi_skip(); return EVOP_VAL; } else if (c == '!' || c == ':' || c=='.' || c=='@' || strref::is_valid_label(c)) { if (prev_op == EVOP_VAL) return EVOP_STP; // a value followed by a value does not make sense, probably start of a comment (ORCA/LISA?) char e0 = expression[0]; int start_pos = (e0 == ':' || e0 == '!' || e0 == '.') ? 1 : 0; strref label = expression.split_range_trim(label_end_char_range, start_pos); Label *pLabel = pLabel = GetLabel(label, etx.file_ref); if (!pLabel) { StatusCode ret = EvalStruct(label, value); if (ret == STATUS_OK) return EVOP_VAL; if (ret != STATUS_NOT_STRUCT) return EVOP_ERR; // partial struct } if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet) value = pLabel->value; section = pLabel->section; return EVOP_VAL; } return EVOP_ERR; } } return EVOP_NONE; // shouldn't get here normally } // // EvalExpression // Uses the Shunting Yard algorithm to convert to RPN first // which makes the actual calculation trivial and avoids recursion. // https://en.wikipedia.org/wiki/Shunting-yard_algorithm // // Return: // STATUS_OK means value is completely evaluated // STATUS_NOT_READY means value could not be evaluated right now // ERROR_* means there is an error in the expression // // Max number of unresolved sections to evaluate in a single expression #define MAX_EVAL_SECTIONS 4 StatusCode Asm::EvalExpression(strref expression, const struct EvalContext &etx, int &result) { int numValues = 0; int numOps = 0; char ops[MAX_EVAL_OPER]; // RPN expression int values[MAX_EVAL_VALUES]; // RPN values (in order of RPN EVOP_VAL operations) short section_ids[MAX_EVAL_SECTIONS]; // local index of each referenced section short section_val[MAX_EVAL_VALUES] = { 0 }; // each value can be assigned to one section, or -1 if fixed short num_sections = 0; // number of sections in section_ids (normally 0 or 1, can be up to MAX_EVAL_SECTIONS) values[0] = 0; // Initialize RPN if no expression { int sp = 0; char op_stack[MAX_EVAL_OPER]; EvalOperator prev_op = EVOP_NONE; expression.trim_whitespace(); while (expression) { int value = 0; short section = -1, index_section = -1; EvalOperator op = EVOP_NONE; if (syntax == SYNTAX_MERLIN) op = RPNToken_Merlin(expression, etx, prev_op, section, value); else op = RPNToken(expression, etx, prev_op, section, value); if (op == EVOP_ERR) return ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION; else if (op == EVOP_NRY) return STATUS_NOT_READY; if (section >= 0) { for (int s = 0; sp) break; ops[numOps++] = p; sp--; } op_stack[sp++] = op; } // check for out of bounds or unexpected input if (numValues==MAX_EVAL_VALUES) return ERROR_TOO_MANY_VALUES_IN_EXPRESSION; else if (numOps==MAX_EVAL_OPER || sp==MAX_EVAL_OPER) return ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION; prev_op = op; expression.skip_whitespace(); } while (sp) { sp--; ops[numOps++] = op_stack[sp]; } } // processing the result RPN will put the completed expression into values[0]. // values is used as both the queue and the stack of values since reads/writes won't // exceed itself. { int valIdx = 0; int ri = 0; // RPN index (value) int preByteVal = 0; // special case for relative reference to low byte / high byte short section_counts[MAX_EVAL_SECTIONS][MAX_EVAL_VALUES] = { 0 }; for (int o = 0; o> ri--; for (int i = 0; i>= values[ri]; break; case EVOP_LOB: // low byte if (ri) { preByteVal = values[ri - 1]; values[ri-1] &= 0xff; } break; case EVOP_HIB: if (ri) { preByteVal = values[ri - 1]; values[ri - 1] = (values[ri - 1] >> 8) & 0xff; } break; default: return ERROR_EXPRESSION_OPERATION; break; } } int section_index = -1; bool curr_relative = false; // If relative to any section unless specifically interested in a relative value then return not ready for (int i = 0; i=0) return STATUS_NOT_READY; else if (etx.relative_section==section_ids[i]) curr_relative = true; else if (etx.relative_section>=0) return STATUS_NOT_READY; section_index = i; } } result = values[0]; if (section_index>=0 && !curr_relative) { lastEvalSection = section_ids[section_index]; lastEvalValue = (ops[numOps - 1] == EVOP_LOB || ops[numOps - 1] == EVOP_HIB) ? preByteVal : result; lastEvalPart = ops[numOps - 1] == EVOP_LOB ? Reloc::LO_BYTE : (ops[numOps - 1] == EVOP_HIB ? Reloc::HI_BYTE : Reloc::WORD); return STATUS_RELATIVE_SECTION; } } return STATUS_OK; } // if an expression could not be evaluated, add it along with // the action to perform if it can be evaluated later. void Asm::AddLateEval(int target, int pc, int scope_pc, strref expression, strref source_file, LateEval::Type type) { LateEval le; le.address = pc; le.scope = scope_pc; le.target = target; le.section = (int)(&CurrSection() - &allSections[0]); le.file_ref = -1; // current or xdef'd le.label.clear(); le.expression = expression; le.source_file = source_file; le.type = type; lateEval.push_back(le); } void Asm::AddLateEval(strref label, int pc, int scope_pc, strref expression, LateEval::Type type) { LateEval le; le.address = pc; le.scope = scope_pc; le.target = 0; le.label = label; le.file_ref = -1; // current or xdef'd le.expression = expression; le.source_file.clear(); le.type = type; lateEval.push_back(le); } // When a label is defined or a scope ends check if there are // any related late label evaluators that can now be evaluated. StatusCode Asm::CheckLateEval(strref added_label, int scope_end) { bool evaluated_label = true; strref new_labels[MAX_LABELS_EVAL_ALL]; int num_new_labels = 0; if (added_label) new_labels[num_new_labels++] = added_label; bool all = !added_label; std::vector::iterator i = lateEval.begin(); while (evaluated_label) { evaluated_label = false; while (i != lateEval.end()) { int value = 0; // check if this expression is related to the late change (new label or end of scope) bool check = all || num_new_labels==MAX_LABELS_EVAL_ALL; for (int l=0; lexpression.find(new_labels[l]) >= 0; if (!check && scope_end>0) { int gt_pos = 0; while (gt_pos>=0 && !check) { gt_pos = i->expression.find_at('%', gt_pos); if (gt_pos>=0) { if (i->expression[gt_pos+1]=='%') gt_pos++; else check = true; gt_pos++; } } } if (check) { struct EvalContext etx(i->address, i->scope, scope_end, i->type == LateEval::LET_BRANCH ? SectionId() : -1); etx.file_ref = i->file_ref; StatusCode ret = EvalExpression(i->expression, etx, value); if (ret == STATUS_OK || ret==STATUS_RELATIVE_SECTION) { // Check if target section merged with another section int trg = i->target; int sec = i->section; if (i->type != LateEval::LET_LABEL) { if (allSections[sec].IsMergedSection()) { trg += allSections[sec].merged_offset; sec = allSections[sec].merged_section; } } bool resolved = true; switch (i->type) { case LateEval::LET_BRANCH: value -= i->address+1; if (value<-128 || value>127) return ERROR_BRANCH_OUT_OF_RANGE; if (trg >= allSections[sec].size()) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; allSections[sec].SetByte(trg, value); break; case LateEval::LET_BYTE: if (ret==STATUS_RELATIVE_SECTION) { if (i->section<0) resolved = false; else { allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, lastEvalPart==Reloc::HI_BYTE ? Reloc::HI_BYTE : Reloc::LO_BYTE); value = 0; } } if (trg >= allSections[sec].size()) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; allSections[sec].SetByte(trg, value); break; case LateEval::LET_ABS_REF: if (ret==STATUS_RELATIVE_SECTION) { if (i->section<0) resolved = false; else { allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, lastEvalPart); value = 0; } } if ((trg+1) >= allSections[sec].size()) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; allSections[sec].SetWord(trg, value); break; case LateEval::LET_LABEL: { Label *label = GetLabel(i->label, i->file_ref); if (!label) return ERROR_LABEL_MISPLACED_INTERNAL; label->value = value; label->evaluated = true; if (num_new_labelslabel_name; evaluated_label = true; char f = i->label[0], l = i->label.get_last(); LabelAdded(label, f=='.' || f=='!' || f=='@' || f==':' || l=='$'); break; } default: break; } if (resolved) i = lateEval.erase(i); } else ++i; } else ++i; } all = false; added_label.clear(); } return STATUS_OK; } // // // LABELS // // // Get a label record if it exists Label *Asm::GetLabel(strref label) { unsigned int label_hash = label.fnv1a(); unsigned int index = FindLabelIndex(label_hash, labels.getKeys(), labels.count()); while (index < labels.count() && label_hash == labels.getKey(index)) { if (label.same_str(labels.getValue(index).label_name)) return labels.getValues() + index; index++; } return nullptr; } // Get a protected label record from a file if it exists Label *Asm::GetLabel(strref label, int file_ref) { if (file_ref>=0 && file_ref<(int)externals.size()) { ExtLabels &labs = externals[file_ref]; unsigned int label_hash = label.fnv1a(); unsigned int index = FindLabelIndex(label_hash, labs.labels.getKeys(), labs.labels.count()); while (index < labs.labels.count() && label_hash == labs.labels.getKey(index)) { if (label.same_str(labs.labels.getValue(index).label_name)) return labs.labels.getValues() + index; index++; } } return GetLabel(label); } // If exporting labels, append this label to the list void Asm::LabelAdded(Label *pLabel, bool local) { if (pLabel && pLabel->evaluated && symbol_export) { if (map.size() == map.capacity()) map.reserve(map.size() + 256); MapSymbol sym; sym.name = pLabel->label_name; sym.resolved = pLabel->section < 0; sym.value = pLabel->value; sym.local = local; if (!sym.resolved) pLabel->mapIndex = (int)map.size(); else pLabel->mapIndex = -1; map.push_back(sym); } } // Add a label entry Label* Asm::AddLabel(unsigned int hash) { unsigned int index = FindLabelIndex(hash, labels.getKeys(), labels.count()); labels.insert(index, hash); return labels.getValues() + index; } // mark a label as a local label void Asm::MarkLabelLocal(strref label, bool scope_reserve) { LocalLabelRecord rec; rec.label = label; rec.scope_depth = scope_depth; rec.scope_reserve = scope_reserve; localLabels.push_back(rec); } // find all local labels or up to given scope level and remove them void Asm::FlushLocalLabels(int scope_exit) { // iterate from end of local label records and early out if the label scope is lower than the current. std::vector::iterator i = localLabels.end(); while (i!=localLabels.begin()) { --i; if (i->scope_depth < scope_depth) break; strref label = i->label; if (!i->scope_reserve || i->scope_depth<=scope_exit) { unsigned int index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count()); while (indexscope_reserve) { if (LabelPool *pool = GetLabelPool(labels.getValue(index).pool_name)) { pool->Release(labels.getValue(index).value); break; } } labels.remove(index); break; } ++index; } i = localLabels.erase(i); } } } // Get a label pool by name LabelPool* Asm::GetLabelPool(strref pool_name) { unsigned int pool_hash = pool_name.fnv1a(); unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count()); while (ins < labelPools.count() && pool_hash == labelPools.getKey(ins)) { if (pool_name.same_str(labelPools.getValue(ins).pool_name)) { return &labelPools.getValue(ins); } ins++; } return nullptr; } // When going out of scope, label pools are deleted. void Asm::FlushLabelPools(int scope_exit) { unsigned int i = 0; while (i= scope_exit) labelPools.remove(i); else ++i; } } // Add a label pool StatusCode Asm::AddLabelPool(strref name, strref args) { unsigned int pool_hash = name.fnv1a(); unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count()); unsigned int index = ins; while (index < labelPools.count() && pool_hash == labelPools.getKey(index)) { if (name.same_str(labelPools.getValue(index).pool_name)) return ERROR_LABEL_POOL_REDECLARATION; index++; } // check that there is at least one valid address int ranges = 0; int num32 = 0; unsigned short aRng[256]; struct EvalContext etx(CurrSection().GetPC(), scope_address[scope_depth], -1, -1); while (strref arg = args.split_token_trim(',')) { strref start = arg[0]=='(' ? arg.scoped_block_skip() : arg.split_token_trim('-'); int addr0 = 0, addr1 = 0; if (STATUS_OK != EvalExpression(start, etx, addr0)) return ERROR_POOL_RANGE_EXPRESSION_EVAL; if (STATUS_OK != EvalExpression(arg, etx, addr1)) return ERROR_POOL_RANGE_EXPRESSION_EVAL; if (addr1<=addr0 || addr0<0) return ERROR_POOL_RANGE_EXPRESSION_EVAL; aRng[ranges++] = addr0; aRng[ranges++] = addr1; num32 += (addr1-addr0+15)>>4; if (ranges >(MAX_POOL_RANGES*2) || num32 > ((MAX_POOL_BYTES+15)>>4)) return ERROR_POOL_RANGE_EXPRESSION_EVAL; } if (!ranges) return ERROR_POOL_RANGE_EXPRESSION_EVAL; LabelPool pool; pool.pool_name = name; pool.numRanges = ranges>>1; pool.scopeDepth = scope_depth; memset(pool.usedMap, 0, sizeof(unsigned int) * num32); for (int r = 0; rlabel_name = label; pLabel->pool_name = pool.pool_name; pLabel->evaluated = true; pLabel->section = -1; // pool labels are section-less pLabel->value = addr; pLabel->pc_relative = true; pLabel->constant = true; pLabel->external = false; MarkLabelLocal(label, true); return error; } // Request a label from a pool StatusCode LabelPool::Reserve(int numBytes, unsigned int &ret_addr) { unsigned int *map = usedMap; unsigned short *pRanges = ranges; for (int r = 0; r=a0 && sequence= a0) { if ((m & chk)==0) { sequence++; if (sequence == numBytes) break; } else sequence = 0; --addr; m <<= 2; } } if (sequence == numBytes) { unsigned int index = (a1-addr-numBytes); unsigned int *addr_map = range_map + (index>>4); unsigned int m = numBytes << (index << 1); for (int b = 0; b=a0 && addr>4; index &= 0xf; unsigned int u = *map, m = 3 << (index << 1); unsigned int b = u & m, bytes = b >> (index << 1); if (bytes) { for (unsigned int f = 0; f>2; if (!_m) { m <<= 30; *map-- = u; } else { m = _m; } } *map = u; return STATUS_OK; } else return ERROR_INTERNAL_LABEL_POOL_ERROR; } else map += (a1-a0+15)>>4; } return STATUS_OK; } // Check if a label is marked as an xdef bool Asm::MatchXDEF(strref label) { unsigned int hash = label.fnv1a(); unsigned int pos = FindLabelIndex(hash, xdefs.getKeys(), xdefs.count()); while (pos < xdefs.count() && xdefs.getKey(pos) == hash) { if (label.same_str_case(xdefs.getValue(pos))) return true; ++pos; } return false; } // assignment of label (